メインコンテンツまでスキップ

バレット物理演算

このセクションでは、babylon-mmdでBullet Physicsエンジンバインディングを使用する方法について説明します。

Bullet Physics概要

babylon-mmdは、MMDモデルの物理シミュレーションにBullet Physicsエンジンを使用します。

Bullet Physicsエンジンは、C++で書かれたオープンソース物理エンジンで、衝突検出とリジッドボディダイナミクスシミュレーションをサポートします。

通常、このエンジンを使用するには、emscriptenを使用してC++コードをWebAssembly (WASM)にコンパイルしたAmmo.jsライブラリを使用します。

しかし、babylon-mmdはemscriptenを使用しないという異なるアプローチを取ります。代わりに、FFIを通じてBullet PhysicsエンジンをRustソースコードに統合し、その後wasm-bindgenを使用してWASMにコンパイルします。

このプロセスでは、すべてのBullet Physicsバインディングコードが手動で書かれ、Ammo.jsと比較して優れたパフォーマンスを提供します。

Bullet Physics統合フォーム

Bullet Physicsエンジンは、babylon-mmdに2つの主要なフォームで統合されています。

Bullet Physics JavaScriptバインディング

このバインディングにより、Bullet PhysicsエンジンをJavaScriptから直接呼び出すことができます。バインディングはbabylon-mmd/esm/Runtime/Optimized/Physics/Bindディレクトリにあります。

このアプローチを使用してMMDランタイムを作成するコードは以下の通りです:

const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeMPR());
const physicsRuntime = new MultiPhysicsRuntime(mmdWasmInstance);
physicsRuntime.setGravity(new Vector3(0, -9.8 * 10, 0));
physicsRuntime.register(scene);

const mmdRuntime = new MmdRuntime(scene, new MmdBulletPhysics(physicsRuntime));

この場合、MultiPhysicsRuntimeクラスは複数のモデルの物理シミュレーションを並列処理するオブジェクトで、シミュレーションを制御できます。

MmdWasmPhysicsの使用

このアプローチは、Rustで書かれたMmdWasmRuntimeからBullet Physicsエンジンを呼び出します。このメソッドはJavaScriptに公開されたバインディングを使用せず、Rustから直接Bullet Physicsエンジンを呼び出して、FFIオーバーヘッドを削減します。

このアプローチを使用してMMDランタイムを作成するコードは以下の通りです:

const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeMPR());
const mmdRuntime = new MmdWasmRuntime(scene, new MmdWasmPhysics(mmdWasmInstance));

この場合でも、MultiPhysicsRuntimeと似たMmdWasmPhysicsRuntimeImplクラスを使用して物理シミュレーションを制御できます:

const physicsRuntime = mmdRuntime.physics!.getImpl(MmdWasmPhysicsRuntimeImpl);

主な違いは、MultiPhysicsRuntimeWASMリソースを直接所有するのに対し、MmdWasmPhysicsRuntimeImplMmdWasmRuntimeが所有するWASMリソースを参照することです。

Bullet Physicsバインディングオブジェクトのメモリー管理

Bullet Physicsエンジンバインディングは、メモリーを管理するためにFinalizationRegistryを使用します。

したがって、babylon-mmd/esm/Runtime/Optimized/Physics/Bindディレクトリのバインディングコードを直接使用する場合、メモリーは自動的に解放されます。

メモリー管理を手動で制御したい場合は、dispose()メソッドを呼び出してメモリーを明示的に解放できます。

const rigidBody = new RigidBody(physicsRuntime, rbInfo);
// rigidBodyを使用
rigidBody.dispose(); // メモリーを明示的に解放

Bullet Physics APIの使用

babylon-mmd/esm/Runtime/Optimized/Physics/BindディレクトリのBullet Physicsバインディングコードは、MMDモデルに関連しない一般的な物理シミュレーションにも使用できます。

以下は、Bullet Physicsバインディングを使用してキューブを地面に落下させる簡単な例です:

const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeSPR());
const physicsRuntime = new NullPhysicsRuntime(mmdWasmInstance);
const physicsWorld = new PhysicsWorld(physicsRuntime);

// create ground mesh
const ground = CreatePlane("ground", { size: 120 }, scene);
ground.rotationQuaternion = Quaternion.RotationAxis(new Vector3(1, 0, 0), Math.PI / 2);

// create ground rigid body with static plane shape
const groundShape = new PhysicsStaticPlaneShape(runtime, new Vector3(0, 0, -1), 0);
const groundRbInfo = new RigidBodyConstructionInfo(wasmInstance);
groundRbInfo.shape = groundShape;
groundRbInfo.setInitialTransform(ground.getWorldMatrix());
groundRbInfo.motionType = MotionType.Static;

const groundRigidBody = new RigidBody(runtime, groundRbInfo);
world.addRigidBody(groundRigidBody);

// create box mesh
const baseBox = CreateBox("box", { size: 2 }, scene);
baseBox.position = new Vector3(0, 20, 0);
baseBox.rotationQuaternion = Quaternion.Identity();

// create box rigid body with box shape
const boxShape = new PhysicsBoxShape(runtime, new Vector3(1, 1, 1));
const boxRbInfo = new RigidBodyConstructionInfo(wasmInstance);
boxRbInfo.shape = boxShape;
boxRbInfo.setInitialTransform(baseBox.getWorldMatrix());
boxRbInfo.motionType = MotionType.Dynamic;

// create box rigid body
const boxRigidBody = new RigidBody(runtime, boxRbInfo);
world.addRigidBody(boxRigidBody);

const matrix = new Matrix();

// register onBeforeRenderObservable to update physics simulation
scene.onBeforeRenderObservable.add(() => {
world.stepSimulation(1 / 60, 10, 1 / 60);

boxRigidBody.getTransformMatrixToRef(matrix);
matrix.getTranslationToRef(baseBox.position);
Quaternion.FromRotationMatrixToRef(matrix, baseBox.rotationQuaternion!);
});

Bullet Physicsバインディングはいくつかのコンポーネントで構成されており、状況に応じて必要なコンポーネントのみを選択して使用できます。

  • PhysicsShape:物理シミュレーションで使用される衝突シェイプを表すクラス。
    • Bullet PhysicsのbtCollisionShapeに対応。
  • RigidBody:物理シミュレーションで使用されるリジッドボディを表すクラス。
    • Bullet PhysicsのbtRigidBodyに対応。
  • RigidBodyConstructionInfo:リジッドボディ作成のための情報を含むクラス。
    • Bullet PhysicsのbtRigidBody::btRigidBodyConstructionInfoに対応。
  • Constraint:物理シミュレーションで使用されるコンストレイントを表すクラス。
    • Bullet PhysicsのbtTypedConstraintに対応。
  • PhysicsWorld:物理シミュレーションを管理するクラス。
    • Bullet PhysicsのbtDynamicsWorldに対応。
  • PhysicsRuntime:バッファード評価を処理するロジックを含むPhysicsWorldのラッパークラス。

フィジックスシェイプ

フィジックスシェイプは、物理シミュレーションで使用される衝突シェイプを表すクラスです。

babylon-mmdは以下のフィジックスシェイプクラスを提供します:

  • PhysicsBoxShape:ボックス衝突シェイプを表すクラス。
    • Bullet PhysicsのbtBoxShapeに対応。
  • PhysicsSphereShape:スフィア衝突シェイプを表すクラス。
    • Bullet PhysicsのbtSphereShapeに対応。
  • PhysicsCapsuleShape:カプセル衝突シェイプを表すクラス。
    • Bullet PhysicsのbtCapsuleShapeに対応。
  • PhysicsStaticPlaneShape:無限プレーン衝突シェイプを表すクラス。
    • Bullet PhysicsのbtStaticPlaneShapeに対応。

Bullet Physicsは他にも多くのフィジックスシェイプをサポートしていますが、babylon-mmdではMMDモデルに必要な衝突シェイプバインディングのみを実装しています。

リジッドボディ

リジッドボディは、物理シミュレーションで使用されるリジッドボディを表します。

リジッドボディクラスを作成するには、RigidBodyConstructionInfoオブジェクトを使用して初期化する必要があります。

リジッドボディクラスは2つのタイプのインプリメンテーションで提供されます:

  • RigidBody:単一のリジッドボディオブジェクトを表すクラス。
  • RigidBodyBundle:複数のリジッドボディオブジェクトを単一のオブジェクトとしてバンドルして処理できるクラス。

RigidBodyBundleクラスは、複数のリジッドボディオブジェクトを一度に作成する際にリジッドボディオブジェクト間のメモリーローカリティを改善することで優れたパフォーマンスを提供します。

RigidBodyBundleを効率的に初期化するために、RigidBodyConstructionInfoListクラスも提供されています。

RigidBodyConstructionInfoListクラスは、複数のRigidBodyConstructionInfoオブジェクトを単一のオブジェクトとしてバンドルして処理できるクラスです。

以下はRigidBodyBundleを使用する例です:

const boxShape = new PhysicsBoxShape(runtime, new Vector3(1, 1, 1));

const rbCount = 10;
const rbInfoList = new RigidBodyConstructionInfoList(wasmInstance, rbCount);
for (let k = 0; k < rbCount; ++k) {
rbInfoList.setShape(k, boxShape);
const initialTransform = Matrix.TranslationToRef(xOffset, 1 + k * 2, zOffset, matrix);
rbInfoList.setInitialTransform(k, initialTransform);
rbInfoList.setFriction(k, 1.0);
rbInfoList.setLinearDamping(k, 0.3);
rbInfoList.setAngularDamping(k, 0.3);
}
const boxRigidBodyBundle = new RigidBodyBundle(runtime, rbInfoList);
world.addRigidBodyBundle(boxRigidBodyBundle, worldId);

コンストレイント

コンストレイントは、物理シミュレーションで使用されるコンストレイントを表します。

babylon-mmdは以下のコンストレイントクラスを提供します:

  • Generic6DofConstraint:6自由度コンストレイントを表すクラス。
    • Bullet PhysicsのbtGeneric6DofConstraintに対応。
  • Generic6DofSpringConstraint:スプリング付き6自由度コンストレイントを表すクラス。
    • Bullet PhysicsのbtGeneric6DofSpringConstraintに対応。

Bullet Physicsは他にも多くのコンストレイントをサポートしていますが、babylon-mmdではMMDモデルに必要なコンストレイントバインディングのみを実装しています。

フィジックスワールド

フィジックスワールドは、物理シミュレーションを管理するクラスです。

フィジックスワールドクラスは2つのタイプのインプリメンテーションで提供されます:

  • PhysicsWorld:単一の物理シミュレーションワールドを表すクラス。
  • MultiPhysicsWorld:複数の物理シミュレーションワールドを並列処理するクラス。
    • 各ワールド間の相互作用のためのAPIが提供されています。

リジッドボディとコンストレイントオブジェクトは、物理シミュレーションに参加するために、フィジックスワールドまたはマルチフィジックスワールドオブジェクトに追加する必要があります。

フィジックスランタイム

フィジックスランタイムは、バッファード評価を処理するロジックを含むフィジックスワールドのラッパークラスです。

フィジックスランタイムクラスは3つのタイプのインプリメンテーションで提供されます:

  • NullPhysicsRuntime:ランタイムなしでフィジックスワールドを使用するためのクラス。
  • PhysicsRuntime:フィジックスワールドを処理するクラス。
  • MultiPhysicsRuntime:マルチフィジックスワールドを処理するクラス。

PhysicsRuntimeMultiPhysicsRuntimeクラスはバッファード評価をサポートします。これは、マルチスレッディングが可能な環境でPhysicsRuntime.evaluationTypeプロパティがPhysicsRuntimeEvaluationType.Bufferedに設定された場合、物理シミュレーションが別のワーカースレッドで処理されることを意味します。

physicsRuntime.evaluationType = PhysicsRuntimeEvaluationType.Buffered;
備考

PhysicsWorldまたはMultiPhysicsWorldオブジェクトは、ロックを使用した適切な同期処理のタスクを実行しますが、これを直接実装することは非常に困難です。

したがって、バッファード評価を使用する際にランタイムなしでNullPhysicsRuntimeを使用して物理シミュレーションを制御することは非常に複雑なタスクであり、推奨されません。

備考

MMDランタイムと互換性のあるフィジックスランタイムはMultiPhysicsRuntimeであり、他のフィジックスランタイムはMMDランタイムと互換性がありません

追加リソース

Bullet Physicsバインディングは、最初にbabylon-bulletphysicsリポジトリで開発され、後にbabylon-mmdに統合されました。

したがって、babylon-bulletphysicsリポジトリでBullet Physicsバインディングのより多くの例とテストコードを確認できます。