複数の形状を組み合わせる方法

btCompoundShapeの定義

Bulletではポリゴンモデルを読み込むことで 任意形状を扱うことができる. 一方,モデルファイルを用意しなくても単純な形状の組み合わせでも複雑な形状を再現できる. 例えば,球と円錐を組み合わせればチェスの駒のような形状にできるし, 立方体を組み合わせることでL字やT字のブロックを表現できる. このような複数の形状を組み合わせた形状として,btCompoundShapeが用意されている.

btCompoundShapeはそれ自身は何の形状も表していないが, その子ノードとしてBulletの各種形状(btBoxShapeやbtSphere,btCylinderShapeなど)を持たせることで, それらを組み合わせた形状として扱える.

以下にカプセル形状(円筒の両端に半球を付けた形状)を2つ組み合わせた例を示す.

// カプセルを組み合わせた形状
btCompoundShape *compound = new btCompoundShape;
btTransform trans;

// 大きさの異なるカプセル形状を2つ定義
btScalar r = 0.1, l = 0.5;
btCapsuleShape *capsule1 = new btCapsuleShape(r, 1.2*l);
btCapsuleShape *capsule2 = new btCapsuleShape(r, l);

// カプセル形状1をbtCompoundShapeに子ノードとして追加
trans.setIdentity();
compound->addChildShape(trans, capsule1);

// カプセル形状2をbtCompoundShapeに90度回転して子ノードとして追加
trans.setRotation(btQuaternion(0, 0, 0.5*RX_PI));
compound->addChildShape(trans, capsule2);

// btCompoundShapeのAABBを再計算
compound->recalculateLocalAabb();

// btRigidBodyの作成とワールドへの追加
trans.setIdentity();
trans.setOrigin(btVector3(0, GROUND_HEIGHT+1.0, 0));
btRigidBody* body1 = CreateRigidBody(0.2, trans, compound, 0);
g_dynamicsworld->addRigidBody(body1);

2つのカプセル形状(btCapsuleShape)とbtCompundShapeを定義した後, addChildShapeメンバ関数を用いて,btCapsuleShapeをbtCompundShapeに登録する. 登録後,形状全体のAABBをrecalculateLocalAabb関数で更新する. 後はいつも通りbtRigidBodyを作って,ワールドに追加すればよい.

上記の例を実際に実装した結果を以下に示す.


btCompoundShapeの例

btCompoundShapeの描画

btCompoundShapeをOpenGLで描画する場合はgetChildShape関数で複合形状に含まれるすべての子形状とその位置/姿勢を取得し, 描画する必要がある. そのために衝突形状を引数として読み込んでその形状タイプごとに描画する関数を作成し, その関数を子ノードごとに再帰的の呼び出すことで描画処理を行う. 以下のそのコード例を示す.

/*!
 * Bulletの衝突形状を描画
 * @param[in] shape 衝突形状
 */
void DrawBulletShape(const btCollisionShape *shape)
{
  int shapetype = shape->getShapeType();

  glPushMatrix();

  // 形状の種類ごとに描画
  if(shapetype == BOX_SHAPE_PROXYTYPE){
    // ボックス形状の描画処理をここに記述
  }
  else if(shapetype == SPHERE_SHAPE_PROXYTYPE){
    // 球形状の描画処理をここに記述
  }
  else if(shapetype == CAPSULE_SHAPE_PROXYTYPE){
    // カプセル形状の描画処理をここに記述
  }
  else if(shapetype == COMPOUND_SHAPE_PROXYTYPE){
    // 複合形状の描画処理
    const btCompoundShape* compound = static_cast(shape);
    int num_child = compound->getNumChildShapes();
    btScalar mc[16];
    for(int j = 0; j < num_child; ++j){
      // Compound Shapeを構成するchild shapeを再帰的にDrawBulletShapeに渡す
      compound->getChildTransform(j).getOpenGLMatrix(mc);
      glPushMatrix();
      glMultMatrixf(mc);
      DrawBulletShape(compound->getChildShape(j));
      glPopMatrix();
    }
  }
  glPopMatrix();
}

形状がCOMPOUND_SHAPE_PROXYTYPEである場合は,getNumChildShapes関数で子形状の数を調べて, その数だけループを回す. ループ内ではgetChildTransformで位置姿勢をOpenGLの変換行列として取得,設定し, getChildShape関数で取得したインスタンスを引数として渡して,関数自身を再帰的に呼び出している.

戻る