プログラミングの備忘録

プログラムをつくる過程を残すもの

processingの備忘録 -Box2D-

f:id:taq2777:20220304174824p:plain

こんにちは。

今回は「Box2Dを触ってみる」という題でやっていきます。


Box2Dとは

「Box2D」は古典力学的な計算をしてくれるライブラリで、「物理エンジン」や「剛体シミュレーター」的なものにあたります。


これまでにブラウン運動天体的なもの、壁で反射する物体などつくってきましたが、これらで扱ったのは「質点」と呼ばれる「体積は無いが質量はある」という性質をもった仮想的な物体です。(名の通り「質(量をもった)点」)

プログラム上では「ellipse()」などで形があるように描画をしていましたが、実際にはその重心しか考えていないため、何かにぶつかっても回転しないなど現実世界での物理と反するような動きをすることもありました。
(回転しなくても違和感がないように丸を使っていたといっても良い。)

しかし「剛体」(=体積と質量をもった物体)を考えれば、力を加える場所によっては回転が起こります。
これなら現実世界に近い挙動になります。
(ただ、剛体は力を加えても変形しないという性質ももつため、この部分は現実世界と反しますが…)


ということで長く無駄な前置きでしたが、「Box2D(剛体)を扱う = 現実世界と似た挙動をシミュレートできる」という点で面白いです。


それでは、これから触ってみます。


ライブラリのインストール

まずはライブラリを導入します。

Processingのウィンドウの上の方にある「スケッチ」から「ライブラリをインポート」「ライブラリの追加…」を選び、「Libraries」タブで「Box2D」と検索して出てくるものを「Install」すれば完了です。

検索結果に「Box2D for Processing」「Fisica」「LiquidFunProcessing」と3つのライブラリが出てきますが、「Box2D for Processing」は少々煩雑となっていること(ライブラリを呼び出すときの「import shiffman.box2d.*;」という製作者の主張の激しさも気になる)、「LiquidFunProcessing」は流体に特化したものであることから、今回は「Fisica」を使うことにしました。

実際に使う際はコードの始めに、

import fisica.*;

と追加します。


プログラムをつくる

では、プログラムをつくってみます。
(全ては触れきれません。悪しからず。)

落下する箱

まずは書き方を理解するためにも、簡単なものからつくってみます。

import fisica.*; //ライブラリのインポート

FWorld world; //世界の名前

void setup(){
   size(500, 500);
  
   Fisica.init(this); //ライブラリの初期化
   world = new FWorld(); //世界の初期化
   world.setEdges(); //画面端を壁にする
}

void draw(){
   background(255);
  
   world.step(); //世界の情報を更新(ラプラスの悪魔的な)
   world.draw(); //世界を描画
}

void mousePressed(){
   FBox box = new FBox(50, 50); //箱をつくる(Fbox(width, height))
   box.setPosition(mouseX, mouseY); //箱の位置
   world.add(box); //世界に箱を追加する
}


物体を追加するときには、まずクラスから「こういう物体をつくるよ」と宣言して、大きさを指定します。
そして、位置や速度などの情報を決めます。
(こちら (Javadocs: fisica) を見てみると、摩擦係数や反発係数、密度などいろいろ設定できるみたいです。)
最後に、世界に追加します。


実行すると、クリックするたびに箱が追加されます。
そして、物理エンジンなので重力があり、バランスが悪ければ箱が回転しながら落ちたりします。
こんなに短いコードなのに。さすがライブラリ。

f:id:taq2777:20220304144934p:plain


書いてみると、かなり簡単になっていることがわかります。
個人的には見やすくて良いのですが、裏を返せばそれだけ自由度が低いということにもなりかねないので一長一短ですかね。


任意の位置に壁をつくる

続いて、任意の位置に壁をつくって滑らせてみます。

import fisica.*;

FWorld world;

void setup(){
   size(500, 500);
  
   Fisica.init(this);
   world = new FWorld();
   world.setEdges();
   
   FBox wall = new FBox(200, 20); //壁(というか床)をつくる
   wall.setPosition(width/2, height/2); //位置
   wall.setRotation(radians(10)); //回転
   wall.setStatic(true); //固定の有無(true:固定)
   wall.setFill(0); //色(0:黒)
   world.add(wall); //追加
}

void draw(){
   background(255);
  
   world.step();
   world.draw();
}

void mousePressed(){
    if(mouseButton == LEFT){ //左クリックで
       FBox box = new FBox(50, 50); //箱をつくる
       box.setPosition(mouseX, mouseY); //位置
       box.setFriction(1); //摩擦係数
       world.add(box); //追加
    }
    if(mouseButton == RIGHT){ //右クリックで
        FCircle circle = new FCircle(50); //球をつくる
        circle.setPosition(mouseX, mouseY); //位置
        world.add(circle); //追加
    }
}

f:id:taq2777:20220304152002p:plain


多角形の追加

import fisica.*;

FWorld world;

void setup(){
     size(500, 500);
  
     Fisica.init(this);
     world = new FWorld();
     world.setEdges();
   
     FBox wall = new FBox(200, 20);
     wall.setPosition(width/2, height/2);
     wall.setRotation(radians(10));
     wall.setStatic(true);
     wall.setFill(0);
     world.add(wall);
}

void draw(){
     background(255);
  
     world.step();
     world.draw();
}

void mousePressed(){
    FPoly polygon = new FPoly(); //多角形をつくる

    //頂点を指定
    polygon.vertex(mouseX, mouseY);
    polygon.vertex(mouseX-50, mouseY+20);
    polygon.vertex(mouseX-30, mouseY+40);
    polygon.vertex(mouseX+20, mouseY+50);
    polygon.vertex(mouseX+30, mouseY+20);

    polygon.setFriction(1); //摩擦係数
    world.add(polygon); //追加
}

f:id:taq2777:20220304155326p:plain


その他、手描きの図形を追加したり、スライム?水滴?のようなぷるぷるした物体を追加したりもできるようです。


流体っぽく

import fisica.*;

FWorld world;
ArrayList<FCircle> circles = new ArrayList<FCircle>(); //球をArrayListで管理

void setup(){
    size(500, 500);
    
    Fisica.init(this);
    world = new FWorld();
    world.setEdges();
    world.setEdgesRestitution(1); //壁の反発係数
    world.setEdgesFriction(0); //壁の摩擦係数
    
    for(int i = 0; i < 1000; i ++){ //1000回繰り返す
        circles.add(new FCircle(5)); //球をつくる
        FCircle c = circles.get(i); //i番目の球の情報を取り出す
        c.setPosition(10+5*(i%20), height/3+5*(i/20)); //格子状に並べる
        c.setRestitution(0.9); //球同士の反発係数
        c.setFriction(0); //球同士の摩擦係数
        world.add(c); //追加
    }
}

void draw(){
    background(255);
    
    world.step();
    world.draw();
}

f:id:taq2777:20220304174232g:plain


まとめ

以上、Processingの物理演算ライブラリ「fisica」を触ってみました。

まだまだたくさんの機能があるので今後また触ることがあるかもしれません。
また、他のライブラリ「Box2D for Processing」「LiquidFunProcessing」も触ってみて比較してみたいですね。

将来的には、剛体力学の勉強がてらこんな感じの物理エンジンを自作してみたいと思っています。お楽しみに。
(いつになるかわかりませんが…)


参考