ずっと君のターン

2008-02-03

Box2Dユーザマニュアル

| 12:27 |  Box2Dユーザマニュアルを含むブックマーク

Box2Dflashのドキュメントが見つからないので仕方なしに訳す。

まだ途中

----

http://www.box2d.org/manual.html

Box2Dユーザマニュアル

はじめに

Box2Dはゲーム用の2D剛体シミュレーターです。 これを利用するとゲーム内のオブジェクトをもっともらしく動かしたり、世界をよりインタラクティブに見せたり出来ます。 ゲームの観点から見ると物理エンジンは単なる手続き的なアニメーションシステムにすぎません。 アニメーターにお金を払って(またはお願いして)アクターを動かしてもらう代わりに、アイザックニュートンに指揮をお任せできます。

Box2Dは移植可能なC++で記述されています。 エンジンで定義される型のほとんどはb2というプレフィクスで始まります。 これで君のゲームエンジンと名前の衝突が起きないようになると期待しています。

要件

このマニュアルでは読者が質量、力、トルク、衝撃などの物理的な概念になじみがあると仮定しています。 もしそうでなければChris HeckerとDavid Baraff(ぐぐれ)の書いたたくさんのチュートリアルをまず読んでください。 チュートリアルを深く理解する必要はありませんが、基本的なコンセプトがうまくまとめられていてBox2Dを利用する助けになるでしょう。

Wikipediaも物理や数学の知識の宝庫です。 内容が慎重に書かれてある分、Googleより有益なこともあるでしょう。

これは必須の要件ではありませんが、Box2Dの内部に興味があるならこのスライド(http://www.gphysics.com/files/GDC2007_ErinCatto.zip)を参照しましょう。

核となるコンセプト

Box2Dにはいくつかの基盤となるオブジェクトがあります。 ここではそれらを簡単に定義するにとどめ、詳細はこのドキュメントの後半で紹介します。

剛体(rigid body)
非常に堅く、2体の距離が完全に一定であるような物質の塊。ダイアモンドのように堅いと考えてください。移行の説明ではrigid bodyの代わりにbodyと言う単語を用います。
シェイプ
A 2D piece of collision geometry that is fixed to a rigid body.
制約 constraint
制約はボディから角度の自由度を取り去る物理的な接続です。 2Dではボディの角度は3自由度です。 もしあるボディを取り上げ(振り子のように)壁にピンで止めたなら、ボディを壁に「制約した」と言います。 こうなるとボディはピンの周りを回転することしかできず、制約によって2つの角自由度が奪われたことになります。
接触制約 contact constraint
剛体を貫通することを防ぎ、摩擦をシミュレートするために設計された特殊な制約です。 接触制約はBox2Dに自動的に作成されるだけで、ユーザが作成することはできません。
節 joint
二つ以上のボディをまとめて保持するための制約です。 Box2Dは次のタイプのジョイントをサポートします:回転・直動・距離など。 ジョイントはlimitとmotorをサポートする場合があります。
joint limit
joint limitはジョイントの動きの幅を制限します。 例えば人間のひじはある幅の間でしか動けません。
joint motor
joint motorはジョイントの角度の自由度に従って結合されたボディを駆動します。 例えば、モーターを使ってひじを回転できます。
world
物理的なワールドはボディやシェイプやそれら全ての制約の集合です。 Box2Dではワールドを複数作ることもできますが、通常その必要はありません。

Hello Box2D

Box2Dの配布物にはHello Worldプロジェクトが含まれます。 このプログラムは大きな土台のボックスと、小さな動的ボックスを一つずつ含みます。 このコードはグラフィックスを一切含まないので、がっかりする準備をしておきましょう(^_^;

(Box2Dの)世界を生成

全てのBox2Dプログラムはworldオブジェクトの作成から始まります。 これはメモリやオブジェクトやシミュレーションを管理する物理的なハブになります。

worldオブジェクトを作成するには、まずworldの境界になるボックスを定義しなければいけません。 Box2Dは境界ボックスを高速に衝突を検知するために利用します。 そのサイズは致命的にはなりませんが、よりよく調節すればパフォーマンスが向上します。 ボックスは小さすぎるよりは大きすぎる方が好ましい結果を生みます。

    b2AABB worldAABB;
    worldAABB.minVertex.Set(-100.0f, -100.0f);
    worldAABB.maxVertex.Set(100.0f, 100.0f);

次に重力ベクトルを定義します。 そうです、重力を横向きに設定することも可能です(単にモニタを回転させてもいいですが)。 また、ワールドが休憩中にはボディをスリープする許可を与えられます。 スリープ中のボディは全くシミュレーションが行われません。

    b2Vec2 gravity(0.0f, -10.0f);
    bool doSleep = true;

これでワールドオブジェクトを作成できます。 通常 であればワールドはヒープに作成し、ポインタをゲーム構造体に保持しますが、この例ではワールドをスタックに作成すればうまく動きます。

   b2World world(worldAABB, gravity, doSleep);

さて、これで物理的なワールドができました。 これからいくつかのモノを追加してみましょう。

グランドボックスの作成

ボディは次のステップにしたがって作成されます。

  1. 形・摩擦・密度などを指定してシェイプを定義する
  2. シェイプのリスト・位置・速度などを指定してボディを定義する
  3. worldオブジェクトを使用してボディを生成する

従ってまずはグランドボックスの定義を作成しましょう。 定義に含まれるデータはBox2Dによって複製されるので、スタックに作成しても、ゲーム構造体に含めても構いません。 シェイプデータはこれを利用してインスタンス化されます。

グランドボックスの定義は次のようになります。

    b2BoxDef groundBoxDef;
    groundBoxDef.extents.Set(50.0f, 10.0f);
    groundBoxDef.density = 0.0f;

extentsは単にボックスの幅の半分を示すベクトルです。 つまり、今回の場合はグランドボックスは100ユニットの幅(x軸)と、20ユニットの高さ(y軸)を持ちます。 Box2Dはメートル・キログラム・秒を使うように設定されています。 なのでextentsはメートルであると考えてください。 ただし、このドキュメントで後で説明するように、単位系を変更することもできます。

密度はゼロに設定されます。 ボディにアタッチされる全てのシェイプの密度がゼロの場合、ボディは静的なものとして扱われます。

ここでグランドボディを作成します。 それにはボディの定義が必要です。 ボディ定義を使ってボディの初期位置やそれに含まれるシェイプリストを指定します。 今回はたった一つのシェイプが含まれるだけです。

    b2BodyDef groundBodyDef;
    groundBodyDef.position.Set(0.0f, -10.0f);
    groundBodyDef.AddShape(&groundBoxDef);

ボディ定義はグランドボディを作るためにworldオブジェクトに渡されます。

    b2Body* ground = world.CreateBody(&groundBodyDef);

もう一度言いますが、Box2Dはshapまたはボディ定義の参照を保持しません。 データをb2Body構造体にコピーするだけです。

シェイプが静的であっても、全てのシェイプは親としてボディを持つ必要があることに注意してください。 ただし、静的なシェイプを全て単一の静的なボディにアタッチしても構いません。 静的なボディはBox2Dのコード内部の一貫性をまし、潜在的なバグを減らすの役に立っています。A that's a good thing.

懸命な読者はこの辺でパターンにお気づきでしょう。 ほとんどのBox2D型はプレフィクスとしてb2を持ちます。 これはコード内で名前が衝突する可能性を減らします。

動きのあるボディの作成

これでグランドボディが作成できました。 動的なボディの作成にも同様なテクニックが使用できます。 次元以外の主な違いは、ボックスシェイプの密度がゼロよりも大きな値を持つことです。 そうするとBox2Dはボディを動的なものとして扱います。 動的なボックスを作成するコードは次のようになります。

    b2BoxDef boxDef;
    boxDef.extents.Set(1.0f, 1.0f);
    boxDef.density = 1.0f;
    boxDef.friction = 0.3f;
    b2BodyDef bodyDef;
    bodyDef.position.Set(0.0f, 4.0f);
    bodyDef.AddShape(&boxDef);
    b2Body* body = world.CreateBody(&bodyDef);

初期化については以上です。 これでシミュレーションを開始する準備が整いました。

(Box2Dの)世界をシミュレートする

これでグランドボックスと動的なボックスの初期化が終わりました。 ニュートンを解き放ち、仕事をさせる準備は完了です。 考えることは残りたった2-3個です。

Box2Dはintegratorと呼ばれる数値計算のコードを少し使います。 インテグレータは物理平衡を離散時間でシミュレートします。 これは、本質的にはスクリーン上でぱらぱら漫画を表示しているのと同じである伝統的なゲームループとよく合います。 従ってBox2Dのタイムステップを設定しなければいけません。 一般に、ゲーム用の物理エンジンは少なくとも60Hzまたは1/60秒のタイムステップに設定することが好まれます。 もっと大きなタイムステップにしても構いませんが、worldを定義をより注意深く設定してやる必要があります。 また頻繁ににタイムステップを変えるのは好ましくありません。 そのため(ほんとうにどうしても必要な場合を除き)フレームレートに応じてタイムステップを変えるようなことは避けましょう。 もしそうすると決心したなら、Box2Dに多少の修正が必要なのでフォーラムで私に連絡してください。 ややこしい話は省略すると、タイムステップは次のようになります。

    float32 timeStep = 1.0f / 60.0f;

インテグレータだけではなく、Box2Dは制約ソルバーと呼ばれるコードも使用します。 制約ソルバーはシミュレーション内の全ての制約を一度に解決します。 制約が一つであれば完璧に解決できます。 しかしある制約だけを解決すると、他の制約にすこし齟齬がでるかもしれません。 良い解決策を得るためには、何度も全ての制約をイテレートする必要があります。 Box2Dが推奨するイテレーション数は10です。 この数はあなたの好きに設定できますが、スピードと正確さにはトレードオフがあることを心にとどめておいてください。 少ないイテレーション数を使えば使うほどパフォーマンスはあがりますが、正確さが犠牲になります。 逆に、イテレーション数を増やせば増やすほど、パフォーマンスが落ちる代わりにシミュレーションの精度はあがります。 今回選んだイテレーション数は以下のようになります。

    int32 iterations = 10;

これでシミュレーションループを開始する準備が整いました。 ゲーム内ではこのシミュレーションループはゲームループと統合できます。 ゲームループの各ステップでb2World::Stepを呼んでください。 フレームレートと物理タイムステップに依存しますが、一度だけ呼べば十分です。

Hello Worldプログラムはとことんシンプルになるように設計されているので、なんのグラフィックスも出力しません。 ただし何も出力しないのではあまりに退屈なので、動的ボディの位置と角度を出力するコードを埋め込んであります。 Yay! これがシミュレーションタイム全部で1秒を60ステップでシミュレーションするループです。

    for (int32 i = 0; i < 60; ++i)
    {
        world.Step(timeStep, iterations);
        b2Vec2 position = body->GetOriginPosition();
        float32 rotation = body->GetRotation();
        printf("%4.2f %4.2f %4.2f\n", position.x, position.y, rotation);
    }
クリーンアップ

worldがスコープから外れるか、ポインタに対してdeleteを呼んで削除されると、ボディとジョイントのために使用されていた領域は全て開放されます。 これは物事を簡単にしてくれます。 However, you will need to nullify any body or joint pointers you have because they will become invalid.

APIデザイン

メモリ管理
ファクトリと定義
ユーザーデータ

b2Shape, b2Body, b2Jointクラスはvoid*型のユーザーデータをアタッチできます。これはBox2Dデータを確認するときに便利なので、どうやって君のゲームエンジン内のデータ構造とそれらを関係付けるか決めたいと思うでしょう。

例えば、アクター上の剛体にアクターのポインタを持たせるような使い方が典型的です。この場合は、循環参照を組み立てます。アクターがあれば、ボディが手に入りますし、ボディがあればアクターが手に入ります。

    GameActor* actor = GameCreateActor();
    b2BodyDef bodyDef;
    bodyDef.userData = actor;
    actor->body = box2Dworld->CreateBody(&bodyDef);

ユーザーデータが必要になる場合の例は次のようなものです。

  • 衝突の結果としてアクターにダメージを与える
  • 軸が設定されたボックスの中にプレイヤーがいるときに準備されたイベントを開始する
  • ジョイントが破壊されることをBox2Dが通知するときにゲーム構造体にアクセスする

ユーザーデータはオプショナルで、どんな値でも設定できることを覚えておきましょう。ただし、一貫性には気をつけるべきです。例えば、あるボディでアクターポインタを保持するなら、全てのボディでアクターポインタを保持するべきです。あるボディにはアクターポインタを保持し、別のボディにはまた別のポインタを設定することのないようにしましょう。これはアクターポインタを別のポインタにキャストしようとしてクラッシュを起こす原因になりかねません。

C++の扱い
叩き台

World

b2Worldクラスはボディとジョイントを保持します。 シミュレーションの全ての側面がここで管理され、(AABBクエリのような)非同期のクエリも認められます。 Box2Dとのやりとりのほとんどはb2Worldオブジェクトを通じて行われます。

Worldの生成と削除
Worldの使用
- シミュレーション
- Worldの探求
- AABBクエリ

領域に含まれる全てのシェイプを決定したいこともときどきあるでしょう。 b2Worldクラスにはbroad phaseデータ構造を使う高速なlog(N)メソッドがあります。 world座標系でAABBを渡すと、b2Worldは潜在的にAABBと交わる全シェイプの配列を返します。 関数が実際に行うのはシェイプのAABBが指定されたAABBに交わる全てのシェイプを返すことなので、この結果は正確とは言えません。 例えば、以下のコードは指定されたAABBと交わる可能性のあるシェイプを全て見つけ出し、関連するボディを全て起動します。

    b2AABB aabb;
    aabb.minVertex.Set(-1.0f, -1.0f);
    aabb.maxVertex.Set(1.0f, 1.0f);
    const int32 k_bufferSize = 10;
    b2Shape *buffer[k_bufferSize];
    int32 count = b2World->Query(aabb, buffer, k_bufferSize);
    for (int32 i = 0; i < count; ++i)
    {
        buffer[i]->GetBody()->WakeUp();
    }

シェイプ

シェイプはボディにアタッチされる図形の集合です。 シェイプはまたボディの質量を定義するのにも使用されます。 つまり、あなたが密度を指定すると、Box2Dは質量を計算して設定します。

シェイプは摩擦と反発のプロパティも保持します。 シェイプは衝突フィルタリングの情報も保持していて、ゲームオブジェクトのいくつかとの間で衝突が発生しないようにもできます。

シェイプは常にボディに所有されています。 単一のボディに複数のシェイプをアタッチすることもできます。 シェイプは抽象クラスでBox2Dではシェイプに属するたくさんの型が定義されています。 勇気があるなら、自分自身でシェイプ型(とその衝突アルゴリズム)を定義することもできます。 ではどうやって定義するのでしょう?

シェイプの定義
- ローカルな位置と回転
- 摩擦と反発
- 密度
- 衝突フィルタリング

衝突フィルタリングはシェイプ間の衝突を防ぐ仕組みです。例えば、自転車に乗るキャラクタを作るとしましょう。自転車と地形は衝突して欲しいし、キャラクタも地形に衝突して欲しいでしょうが、キャラクタと自転車は衝突して欲しくないに違いありません(重なり合う必要があるからです)。Box2Dはこのようなカテゴリとグループを使ってこのようなフィルタリングを実現します。

Box2Dは16の衝突カテゴリをサポートしています。シェイプのそれぞれがどのカテゴリに属するかを指定でき、シェイプとその他のカテゴリの衝突をどう処理するかを指定できます。例えば、プレイヤー同士は衝突せず、モンスター同士も衝突しないけれど、プレイヤーとモンスターは衝突するような多人数参加型のゲームを作成できます。これはmasking bitsを使って実現されます。つまり

    playerShapeDef.categoryBits = 0x0002;
    monsterShapeDef.categoryBits = 0x0004;
    playerShape.maskBits = 0x0004;
    monsterShapeDef.maskBits = 0x0002;

衝突グループを使ってグループ全体のインデックスを指定することもできます。同じグループインデックスを持つ全てのシェイプを常に衝突するようにしたり(正のインデックス)、絶対に衝突しない(負のインデックス)ようにもできます。グループインデックスは通常例えばバイクのパーツのような何らかの関係を持つものたちに使用されます。次の例ではshape1とshape2は常に衝突し、shape3とshape4は絶対に衝突しません。

    shape1Def.groupIndex = 2;
    shape2Def.groupIndex = 2;
    shape3Def.groupIndex = -8;
    shape4Def.groupIndex = -8;

異なるグループのシェイプ同士の衝突はカテゴリとマスクビットによってフィルタリングされます。つまり、グループによるフィルタリングはカテゴリによるフィルタリングよりも優先されます。

Box2Dにはまた別の衝突フィルタリングもあることを忘れないでください。例えば次のようなものがあります。

  • スタティックボディのシェイプは衝突しません
  • 同じボディに属するシェイプ同士は衝突しません
  • ジョイントで結合されたボディ同士の衝突は起きるか起きないかを任意に指定できます
- 円の定義

b2CircleDefはb2ShapeDefを継承していて、半径が追加されています。円の軸はそのセンターになります。

- 四角の定義
- ポリゴンの定義

b2PolyDefは凸型のポリゴンを実現するのに使用されます。正しく使うには少しトリッキーなところがありますので、よく読んでください。頂点の最大数はb2_maxPolyVerticesで定義され、最初は8になっています。もっとたくさんの頂点が必要なときはb2Settings.hにあるb2_maxPolyVerticesを修正する必要があります。

ポリゴン定義を構築するには、使用する頂点の数を指定しなければいけません。頂点は反時計回り(CCW)に指定します。また、ポリゴンは凸型でなければいけません。つまり, each vertex must point outwards to some degree.さらに、頂点は重なってはいけません。Box2Dが自動的にループを閉じてくれます。

Box2Dの将来のバージョンにはポイントの集合から自動的に凸型のポリゴンを作成してくれるツールが添付されるでしょうから、それほど神経質になる必要はありません。

三角形型のポリゴンの例は以下のようになります。

    b2PolyDef triangleDef;
    triangleDef.vertexCount = 3;
    triangleDef.vertices[0].Set(-1.0f, 0.0f);
    triangleDef.vertices[1].Set(1.0f, 0.0f);
    triangleDef.vertices[2].Set(0.0f, 2.0f);

ポリゴンの頂点は一般的には原点を中心にする必要がありますが、これは必須ではありません。ポリゴンシェイプの原点は頂点列によって暗黙的に決定されます。


シェイプの生成と削除(をしない)
シェイプの使用

ボディ

ボディは位置と速度を持ちます。 またボディには力とトルクと衝撃を与えることができます。

ボディはシェイプのバックボーンになります。 ボディはシェイプを保持し、ワールドを動き回ります。 Box2D内ではボディは常に剛体です。 つまり一つのボディにアタッチされた二つのシェイプは決して相対的な位置を変えません。

ボディの定義
- シェイプの追加
- 位置と回転

- 速度
- ダンピング
- スリープパラメータ
- 継続的な衝突検知
ボディの生成と削除
静的・動的ボディ
ボディの使用

ジョイント

ジョイントはボディとワールド、またはそれら同士を制約するのに利用されます。 ゲームに含まれる典型的な例としては、ragdolls, teeters, and pulleyなどがあります。 ジョイントはおもしろい動きを作るためにいろいろなやり方で組み合わせることができます。

あるジョイントは制限を追加するので動きの幅を規定できます。 また別のジョイントは所定の速度で所定の力またはトルクを超えるまでジョイントを駆動するためのモーターを提供します。

ジョイントモーターはいろんな使い道があります。 モーターを使ってジョイントの速度を実際の位置と希望する位置と比例させ、位置をコントロールできます。 また、モーターを使ってジョイントの速度をゼロまたは小さな値に設定し、最大の力/トルクを与えることでジョイントの摩擦をシミュレートすることもできます。 このときモーターは付加が十分に大きくなるまでジョイントの動きを妨げようとします。

ジョイントの定義

ジョイントタイプにはそれぞれb2JointDefから派生する定義があります。 ジョイントは全て二つの異なるボディを接続します。 メモリを浪費したいなら、静的なボディの間にジョイントを設定しても構いません(^^;

あらゆるタイプのジョイントにユーザーデータを指定でき、アタッチされたボディがお互いに衝突しないようにフラグを設定できます。 実際はデフォルトでそのようになっていて、接続された二体の衝突を認めたければcollideConnectedという真偽値を設定する必要があります。

それ以外のジョイント定義データはジョイントタイプによります。 以下でそれを説明しましょう。

- 距離ジョイント

最もシンプルなジョイントの一つが距離ジョイントで、二つのボディの2点間の距離を一定に保ちます。 距離ジョイントを指定する時点で二つのボディは既に場に置かれている必要があります。 そのあとワールド座標系でアンカーポイントを二つ指定します。 一番目のアンカーポイントがbody1に接続され、二つ目のアンカーポイントはbody2に接続されます。 これらの点は距離が一定になります。

http://www.box2d.org/images/distanceJoint.gif

距離ジョイントの定義例は以下です。 この例ではボディは衝突を認められいて、アンカーポイントはそれぞれの重心です。

    b2DistanceJointDef jointDef;
    jointDef.body1 = myBody1;
    jointDef.body2 = myBody2;
    jointDef.collideConnected = true;
    jointDef.anchorPoint1 = myBody1->GetCenterPosition();
    jointDef.anchorPoint2 = myBody2->GetCenterPosition();
- 回転ジョイント

回転ジョイントを使うは二体がアンカーポイントを共有しなければいけません。回転ジョイントは角自由度を二体の相対角一つしか持たず、その角度はジョイント角と呼ばれます。

http://www.box2d.org/images/revoluteJoint.gif

回転を指定するにはワールド空間に二つのボディと一つのアンカーポイントが必要です。Box2Dはボディがすでに適切な位置にあることを仮定しています。

今回の例では二つのボディは一方のボディの重心にある回転ジョイントによって接続されています。

    b2RevoluteJointDef jointDef;
    jointDef.body1 = myBody1;
    jointDef.body2 = myBody2;
    jointDef.anchorPoint = myBody1->GetCenterPosition();

回転ジョイントの角度はbody2がアンカーポイントに対して反時計回りに回る時にせいとみなされます。Box2Dでのすべての角度と同じで、回転角はラジアンで表されます。回転ジョイントの角度は、二体の現在の回転角に関係なく、ジョイントが作成されたときをゼロとされます。

ジョイント角を制御したい場合もあるかもしれません。そのために回転ジョイントはオプションとしてジョイントの制限やモーターをシミュレートできます。

ジョイントリミットはジョイント角を下限と上限の間に保つよう制限します。リミットはそれを保つのに必要なトルクを発生させます。リミットの幅はゼロを含む必要があります。もし含んでいなければシミュレーションが始まると同時にジョイントがふらふらすることになります。

ジョイントモーターを使うとジョイントスピード(角速度)を指定できます。速度は正の値でも負の値でも構いません。モーターには無限大の力を設定することもできますが、基本的にはあまり望ましくありません。こういう文句を聞いたことがあるでしょう。

「絶対的な力が不動の物体にかかると何が起きるのか?」

これはあまりうれしくない状況だと言うほかありません。なので、ジョイントモーターにはトルクの最大値を設定するようにしましょう。トルクが指定された最大値を超えるまではジョイントモーターは指定された速度を保つようになります。最大トルクを超えてしまうとジョイントは減速し、and can even reverse.

ジョイントモーターをジョイントの摩擦をシミュレートするのに使うこともできます。ジョイントスピードをゼロに設定し、最大トルクをsome small, but significant valueに設定しましょう。モーターはジョイントが回転するのを妨げようとしますが、ある負荷を超えると動き出します。

上の回転ジョイント定義の改訂版は以下のようになります。今回はジョイントは正ゲインされていて、モーターも使用可能です。モーターはジョイントの摩擦をシミュレートするために設定されました。

    b2RevoluteJointDef jointDef;
    jointDef.body1 = myBody1;
    jointDef.body2 = myBody2;
    jointDef.anchorPoint = myBody1->GetCenterPosition();
    jointDef.lowerAngle = -0.5f * b2_pi; // -90 degrees
    jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees
    jointDef.enableLimit = true;
    jointDef.motorTorque = 10.0f;
    jointDef.motorSpeed = 0.0f;
    jointDef.enableMotor = true;
- 直動ジョイント

直動ジョイントを使うと二つのボディを特定のローカル軸に沿って相対的に移動することができます。直動ジョイントは相対的な回転を妨げます。そのため、直動ジョイントは角自由度を一つしか持ちません。

http://www.box2d.org/images/prismaticJoint.gif

直動ジョイント定義は回転ジョイントで説明したものとほとんど同じです。単に角度を距離に、トルクを力に読み替えてください。このアナロジーを使った、ジョイントリミットと摩擦モーターを含む直動ジョイント定義の例は次のようになります。

    b2PrismaticJointDef jointDef;
    jointDef.body1 = myBody1;
    jointDef.body2 = myBody2;
    jointDef.anchorPoint = myBody1->GetCenterPosition();
    jointDef.axis.Set(1.0f, 0.0f);
    jointDef.lowerTranslation = -5.0f;
    jointDef.upperTranslation = 2.5f;
    jointDef.enableLimit = true;
    jointDef.motorForce = 1.0f;
    jointDef.motorSpeed = 0.0f;
    jointDef.enableMotor = true;

回転ジョイントは隠れた軸を画面の外に持ちます。直動ジョイントは画面と平行に明示的な軸が必要です。この軸は二つのボディに固定されていてそれらの動きに従います。

回転ジョイントと同様に、直動ジョイントは作成直後にはジョイントの移動がゼロに設定されています。So be sure zero is between your lower and upper translation limits.

- プーリージョイント

length1 + length2 == constant

http://www.box2d.org/images/pulleyJoint.gif

- ギアジョイント

http://www.box2d.org/images/gearJoint.gif

- マウスジョイント

マウスジョイントはテストベッドでボディをドラッグするのに使用されています。これは将来高い確率で修正されるのでここでは詳細に付いては説明しません。

ジョイントの生成と破棄
接続されていないジョイント
ジョイントの使用

ほとんどのシミュレーションではジョイントを作ると、破棄するときまでそれにアクセスすることはありません。しかしジョイントにはリッチなシミュレーションを作るのに便利なデータがたくさん含まれています。

なによりまず、ジョイントからはボディとアンカーポイントとユーザーデータが取得できます。

    b2Body* GetBody1();
    b2Body* GetBody2();
    b2Vec2 GetAnchor1();
    b2Vec2 GetAnchor2();
    void* GetUserData();

全てのジョイントにはリアクション力とトルクがあります。このリアクション力はボディ2のアンカーポイントにかかります。リアクション力を使ってジョイントを破壊したり、何か別のゲームイベントのトリガーにしたりできます。Box2Dはリアクションのインパルスを保持してるので、リアクション力を得るにはタイムステップの逆数が必要です。これらの関数はいくばくかの数値計算が必要です。用のない時に呼び出さないようにしましょう。

    b2Vec2 GetReactionForce(float32 invTimeStep);
    float32 GetReactionTorque(float32 invTimeStep);
- 距離ジョイントの使用

距離ジョイントにはモータがないので追加のランタイムメソッドは存在しません。

- 回転ジョイントの使用

次のメソッドで回転ジョイントの角度、速度、モータートルクにアクセスできます。

float32 GetJointAngle() const;
float32 GetJointSpeed() const;
float32 GetMotorTorque() const;

モーターのパラメーターは以下を使用して更新することも可能です。

void SetMotorSpeed(float32 speed);
void SetMaxMotorTorque(float32 torque);

ジョイントモーターにはいくつか面白い特徴があります。ジョイントの速度はステップの度に設定できるので、ジョイントを正弦波や任意の関数にしたがって振動させることもできます。

... ゲームループ開始 ...
myJoint->SetMotorSpeed(cosf(0.5f * time));
... ゲームループ終了 ...

またジョイントモータを使用して望む角度に合わせることもできます。例えば:

... ゲームループ開始 ...
float32 angleError = myJoint->GetJointAngle() - angleTarget;
float32 gain = 0.1f;
myJoint->SetMotorSpeed(-gain * angleError);
... ゲームループ終了 ...

一般に、パラメータを異常に大きな値にすべきではありません。ジョイントが不安定になる場合があります。

- 直動ジョイントの使用
- プーリージョイントの使用
- ギアジョイントの使用

コンタクト

導入

コンタクトはBox2Dがシェイプ同士の衝突を管理するために作るオブジェクトです。 異なる種類のシェイプ同士の衝突を管理するためにb2Contactから派生するいろんな種類のコンタクトがあります。 例えば、ポリゴンとポリゴンの衝突を管理するコンタクトクラスもありますし、円と円の衝突を管理するまた別のコンタクトクラスもあります。 通常、このことはあなたにとって重要ではありません。参考までに説明しただけです。

以下はコンタクトに関連する用語説明です。これらはBox2D独自のものですが、他の物理エンジンでも同様なものを見つかるかもしれません。

コンタクト
-
コンタクトコールバック

Box2Dはコンタクトコールバックに付いて単純なアプローチを取ります。つまり、そのようなものは存在しません。なぜか?それは、Box2Dはタイムステップごとに全ての接触点を保持していて、好きなときにそのデータにアクセスできるからです。

接触オブジェクトを走査するとどのオブジェクトを破壊すればいいか決定できるでしょう。例えば、乗り物が壁に衝突すると壁を破壊するとか。通常であれば、これはバグの起きやすい状況です。なぜなら処理中の衝突オブジェクトはそこに含まれるボディが破壊されるとき同時に削除する必要があるからです。Box2Dはこの問題の次のように解決します。

Box2Dはボディをすぐには破壊しません。あなたがボディを削除してもBox2Dはそれをワールドのボディリストから除いて別の削除対象リストに追加するだけです。ボディは実際にはb2World::Stepが次に呼び出されたときに削除されます。

処理中のコンタクト

以下はCollisionProcessingテストから抜き出したスニペットです。 このコードは接触しているシェイプの全てのコンタクトをイテレートして、軽いほうの親ボディを破壊します。 Box2Dではボディのデストラクションは遅延されるので、同じボディが複数回破壊される場合があることに注意してください。

for (b2Contact* c = m_world->GetContactList(); c; c = c->GetNext())
{
    if (c->GetManifoldCount() > 0)
    {
        b2Body* body1 = c->GetShape1()->GetBody();
        b2Body* body2 = c->GetShape2()->GetBody();
        float32 mass1 = body1->GetMass();
        float32 mass2 = body2->GetMass();

        if (mass1 > 0.0f && mass2 > 0.0f)
        {
            if (mass2 > mass1)
            {
                m_world->DestroyBody(body1);
                body1 = NULL;
            }
            else
            {
                m_world->DestroyBody(body2);
                body2 = NULL;
            }
        }
    }
}

iforce2diforce2d 2008/07/18 14:14 Box2D の Wiki にこのページへのリンクを入れたけど、いいんですかね?

technohippytechnohippy 2008/07/18 14:41 ありがとうございます。でもバージョンアップして結構APIが変わってるんですよね。こっちは古いまま・・・。