暇つぶしに日本語訳&cocos2d-x対応をしてみました。
まぁ英語力は決して高くないので間違いが多々あるとは思いますが、そんなときは
原文を読んでください(笑)
内容はcocos2dで軟体のオブジェクトをシミュレートするという話です。
最近cocos2d-xをやっているのでこれをcocos2d-x用に変換したものをGitHubで公開しています。
まだ完成してませんが、順次アップデートして行きます
Box2Dによる軟体の物理演算 Part 1 / 4
剛体と軟体
このパートはBox2Dでの軟体物理オブジェクトのシミュレート方法を説明する4パートからなるシリーズの最初のパートです。
完全にマッピングした、ぐにゃぐにゃする物体の描画を試して行きます。
Box2Dは剛体の物理エンジンです。つまり、物体は変形不可です。
しかし、変形可能な物体が欲しくなる場合があると思います。
いい例として水風船があげられます。
水風船はグニャグニャとしていて、地面にぶつかったときには変形するはずです。
軟体物理エンジンはこのような物体を扱うことが出来ます。
あいにく、Box2Dは軟体物理エンジンではありません。
しかしディスタンスジョイントを使ってBox2Dでこれらをシミュレートする方法があります。
ディスタンスジョイント
ディスタンスジョイントは2点間の一定の距離を保ちます。
軟体物理シミュレーションを可能にするのに役立つディスタンスジョイントの重要な特徴は、弾力的接続をシミュレートするために
柔らかくすることができるということです。
まずBox2Dで骨組みのようなホイールを作ります。そして柔軟性をシミュレートする為に
全ての物体を弾力的ディスタンスジョイントで接続します。
ディスタンスジョイントに関する詳細はこちら
ホイール構造

上の形状がBox2Dで作ろうとしている物です。
外輪の円の数が多いほど、円に見えます。
チュートリアルの後半で、このBox2D上のホイール構造にマッピングする為にOpenGLでのポリゴン生成を学びます。
ホイールは内側の円を囲むように構成されています。
全ての円は互いにディスタンスジョイントで接続されています。
それらのディスタンスジョイントはバネのようになるように設計されています。
こちらを読むといいでしょう。cocos2dでのOpenGLを使った円の描画についての記事です。
数学的な話はこちらでするのがいいでしょう。
さぁ、やってみよう!
Cocos2DのBox2Dプロジェクトを作りましょう。
デバッグモードでジョイントが見れるようにb2DebugDraw::e_jointBitをオンにしたくなると思います。
ここではワールドの設定方法については割愛します。もしどうやるか分からなければ、このサイト上で
説明している記事を見つけることができます。また、よく参照できるようにソースコードも投稿されるでしょう。
HelloWorldLayer.mmファイルで以下のコードが見つかります。MyNodeは軟体オブジェクトを扱う為のカスタムノードです。
レイヤーに追加し、スクリーンの中央に移動します。
// MyNodeは軟体オブジェクト
MyNode *node = [MyNode node];
node.position = ccp(240, 160);
[node createPhysicsObject:world];
[self addChild:node];
MyNodeのソースです。
外側の円を作成し、中央の円の周りに円状に配置します。
そのときにそれら全てをディスタンスジョイントで接続します。
コードには何をしているか理解できるようにコメントをつけてあります。
#import "CCNode.h"
#import "Box2D.h"
typedef struct {
GLfloat x;
GLfloat y;
} Vertex2D;
static inline Vertex2D Vertex2DMake(GLfloat inX, GLfloat inY) {
Vertex2D ret;
ret.x = inX;
ret.y = inY;
return ret;
}
@interface MyNode : CCNode {
}
- (void) createPhysicsObject:(b2World*)world;
@end
#import "cocos2d.h"
#import "MyNode.h"
#define PTM_RATIO 32.f
@implementation MyNode
- (id) init {
self = [super init];
return self;
}
// 外輪の円の数 (増やすとよりスムーズな丸になる)
#define NUM_SEGMENTS 12
- (void) createPhysicsObject:(b2World *)world {
// Centerは中央の円の位置
b2Vec2 center = b2Vec2(240/PTM_RATIO, 260/PTM_RATIO);
b2CircleShape circleShape;
circleShape.m_radius = 0.25f;
b2FixtureDef fixtureDef;
fixtureDef.shape = &circleShape;
fixtureDef.density = 0.1;
fixtureDef.restitution = 0.05;
fixtureDef.friction = 1.0;
// より大きな値を設定すればより弾力をもつ
float springiness = 4.0;
// Delta angle to step by
float deltaAngle = (2.f * M_PI) / NUM_SEGMENTS;
// ホイールの半径
float radius = 50;
// ジョイントを接続する時に参照し直せるように
// ボディを保持しておく必要がある
NSMutableArray *bodies = [NSMutableArray array];
// 各セグメントの処理
for (int i = 0; i < NUM_SEGMENTS; i++) {
// 現在の角度
float theta = deltaAngle*i;
// θからx、y座標を計算する
float x = radius*cosf(theta);
float y = radius*sinf(theta);
// Box2dの座標に変換する為にPTM_RATIOで割ることを覚えておいてください。
b2Vec2 circlePosition = b2Vec2(x/PTM_RATIO, y/PTM_RATIO);
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
// ポジションは中央に関連づけられるべきです
bodyDef.position = (center + circlePosition);
// ボディとフィクスチャを生成する
b2Body *body;
body = world->CreateBody(&bodyDef);
body->CreateFixture(&fixtureDef);
// 後でジョイントを接続する為に配列に追加します。
// b2BoydはC++のオブジェクトなのでNSMutableArrayにインサートする場合には
// NSValueでラップしなければなりません。
[bodies addObject:[NSValue valueWithPointer:body]];
}
// 内側の中央の円
b2BodyDef innerCircleBodyDef;
innerCircleBodyDef.type = b2_dynamicBody;
// ポジションを中央に
innerCircleBodyDef.position = center;
b2Body *innerCircleBody = world->CreateBody(&innerCircleBodyDef);
innerCircleBody->CreateFixture(&fixtureDef);
// ジョイントを接続
b2DistanceJointDef jointDef;
for (int i = 0; i < NUM_SEGMENTS; i++) {
// 隣
int neighborIndex = (i + 1) % NUM_SEGMENTS;
// 現在と隣のボディを取得する
b2Body *currentBody = (b2Body*)[[bodies objectAtIndex:i] pointerValue];
b2Body *neighborBody = (b2Body*)[[bodies objectAtIndex:neighborIndex] pointerValue];
// 外輪の円をそれぞれ接続する
jointDef.Initialize(currentBody, neighborBody,
currentBody->GetWorldCenter(),
neighborBody->GetWorldCenter() );
jointDef.collideConnected = true;
jointDef.frequencyHz = springiness;
jointDef.dampingRatio = 0.5f;
world->CreateJoint(&jointDef);
// 中央の円を外側の円と接続する
jointDef.Initialize(currentBody, innerCircleBody, currentBody->GetWorldCenter(), center);
jointDef.collideConnected = true;
jointDef.frequencyHz = springiness;
jointDef.dampingRatio = 0.5;
world->CreateJoint(&jointDef);
}
}
@end
ふー、以外とブログに載せるの大変だなぁ・・・。
ブログに載せる用にプラグインでもいれるかぁ。
part2は訳とプログラムは出来ているのでちょっとまってください。
ではでは。
