ProcessingでBox2Dを使う (JBox2D)

2013-09-01

iforce2dのBox2Dのチュートリアルとかを少し動かしてみて、なかなか面白いと思った。

C++でのやり方は(Testbed上で動かしただけだけど)ある程度わかったので、今度はJavaで動かしてみたいと思った。Java環境で簡単にグラフィクスを扱うことができるProcessingでBox2Dを使ってみようとググってみたところ、BoxWrap2Dというのを使うページがトップにヒットした。これはBox2DをJavaにポートしたJBox2Dというものの薄いラッパー、とのこと。説明の通りやって動いたんだけど、ソースがPhysicsのインスタンスを作っただけで勝手に世界が更新されていて仕組みがよくわからなかったので、もっとピュアなレベルで触ってみようと、直接JBox2Dを試すことにした。

ただちょっと、JBox2Dのzipをダウンロードしたはいいが、ソースから.jarを作る方法がよくわからなかった(makeとかできるのか)ので、これまたJBox2Dを使う簡易ライブラリというPBox2Dのzipを使った。

Processingで外部のライブラリ(.jar)を使うには、スケッチブックのフォルダのlibraries内に突っ込めばいいようだ(Macだと~/Document/Processing/libraries/)。library.propertiesとかいう定義ファイルが必要みたいだが、その辺はPBox2Dのアーカイブに含まれている。

JBox2Dの動かし方はC++のBox2Dとだいたい一緒。サンプルのHelloWorldをテキスト出力だけじゃなく、Processingで描画するようにしたものがこちら:

import org.jbox2d.common.*;
import org.jbox2d.dynamics.*;
import org.jbox2d.collision.shapes.*;

float BOX_HALF_WIDTH = 0.5f;
float BOX_HALF_HEIGHT = 0.2f;
float SCALE = 50.0f;

World world;
Body body;

void setup() {
size(640, 480);

Vec2 gravity = new Vec2(0.0f, 9.8f);
world = new World(gravity);

// Define the ground body.
BodyDef groundBodyDef = new BodyDef();
groundBodyDef.position.set(0.0f, 10.0f);
Body groundBody = world.createBody(groundBodyDef);
PolygonShape groundBox = new PolygonShape();
groundBox.setAsBox(50.0f, 10.0f);
groundBody.createFixture(groundBox, 0.0f);

// Define the dynamic body. We set its position and call the body factory.
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyType.DYNAMIC;
bodyDef.position.set(0.0f, -4.0f);
body = world.createBody(bodyDef);
PolygonShape dynamicBox = new PolygonShape();
dynamicBox.setAsBox(BOX_HALF_WIDTH, BOX_HALF_HEIGHT);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 1.0f;
fixtureDef.restitution = 0.5f;
body.createFixture(fixtureDef);
}

void mousePressed() {
float x = (mouseX - width * 0.5f) / SCALE;
float y = (mouseY - height) / SCALE;
body.setTransform(new Vec2(x, y), random(2 * PI));
body.setLinearVelocity(new Vec2(0, 0));
body.setAngularVelocity(0);
body.setAwake(true);
}

int FPS = 60;
int velocityIterations = 6;
int positionIterations = 2;

int lastUpdateMillis = 0;
void draw() {
int now = millis();
while (now - lastUpdateMillis >= 1000 / FPS) {
// Use fixed time step.
world.step(1.0f / FPS, velocityIterations, positionIterations);
lastUpdateMillis += 1000 / FPS;
}

background(128);

drawBody();
}

void drawBody() {
Vec2 position = body.getPosition();
float angle = body.getAngle();

stroke(0);
strokeWeight(1.0f / SCALE);
fill(255);
pushMatrix();
translate(width * 0.5f, height);
scale(SCALE, SCALE);
translate(position.x, position.y);
rotate(angle);
rect(-BOX_HALF_WIDTH, -BOX_HALF_HEIGHT, BOX_HALF_WIDTH * 2, BOX_HALF_HEIGHT * 2);
popMatrix();
}
  • Box2DではY軸のプラスが上としているんだけど、そうする必要はないよねってことで、下をプラスに変更してみた。
  • Processingではmillis()関数で起動してからの時間(ミリ秒)が取れる。ただ、Box2DのWorldStep()メソッドの刻み時間は固定にした方がいいとのこと。
  • C++でボディが動的であることを示すフラグb2_dynamicBodyは、JavaではBodyType.DYNAMIC

今後はジョイントなどを試す。

追記

  • PBox2Dに含まれていたJBox2Dのjarには、なぜかWheelJointが含まれていないという致命的な問題があった。
  • http://labs.uechoco.com/blog/2008/03/processingjar.html に従って、ProcessingにJBox2Dのjarを追加することができた。
    • つまづきポイントは、jarのファイル名にハイフンやピリオドを入れないことと、ライブラリのディレクトリ名とjarのファイル名を同じにすること。