MMD ランタイム
MmdRuntimeクラス
MmdRuntimeは、babylon-mmdランタイムコンポーネントのコアクラスです。
MmdRuntimeは、他のすべてのランタイムコンポーネントを参照し、制御して、MMDモデルにアニメーションを適用します。
MmdRuntimeは以下の機能を提供します:
- 複数のMMDモデルを同時に制御
- 複数のMMDカメラを同時に制御
- カメラアニメーションの適用
- 物理シミュレーションの制御
MmdRuntimeを作成するコードは以下の通りです:
const mmdRuntime = new MmdRuntime(scene, null);
MmdRuntimeのコンストラクタは2つの引数を取ります:
scene:Sceneオブジェクトが提供された場合、MmdRuntimeのライフタイムはSceneオブジェクトに紐付けられます。つまり、Sceneが破棄されると、MmdRuntimeも自動的に破棄されます。nullが提供された場合は、MmdRuntimeのdispose()メソッドを手動で呼び出す必要があります。そうしないとメモリーリークが発生する可能性があります。physics:物理シミュレーションインプリメンテーションを提供します。nullが提供された場合、物理シミュレーションは無効になります。物理シミュレーションを有効にするには、MmdBulletPhysics、MmdAmmoPhysics、またはMmdPhysicsなどのIMmdPhysicsインターフェースを実装するクラスのインスタンスを提供する必要があります。
物理シミュレーションを処理するロジックはMmdRuntimeに含まれておらず、外部から注入されることに注意してください。
この設計により、物理エンジンインプリメンテーションを簡単に入れ替えることができ、独自のカスタム物理エンジンを実装し、使用するインプリメンテーションのみをバンドルしてバンドルサイズを削減することが可能です。
フレームアップデート
アニメーションを処理するには、アップデートファンクションMmdRuntime.beforePhysics()とMmdRuntime.afterPhysics()を毎フレーム呼び出す必要があります。
これら2つのメソッドは、物理シミュレーションが実行される前と後にそれぞれ呼び出される必要があります。
したがって、MmdRuntimeを使用するアプリケーションは、次のようなフレームループを持つ必要があります:
// sudo code for frame loop
for (; ;) {
mmdRuntime.beforePhysics();
simulatePhysics();
mmdRuntime.afterPhysics();
render();
}
これら2つのメソッドを毎フレーム呼び出す最も簡単な方法は、SceneのonBeforeAnimationsObservableとonBeforeRenderObservableイベントにコールバックを登録することです。
MmdRuntime.register()メソッドはSceneオブジェクトを引数として取り、内部的にこれら2つのイベントにコールバックを登録して、beforePhysics()とafterPhysics()が毎レンダリング時に自動的に呼び出されるようにします。
mmdRuntime.register(scene);
MmdRuntimeのアップデートを一時的に停止したい場合は、MmdRuntime.unregister()メソッドを呼び出して、登録されたコールバックを削除できます。
mmdRuntime.unregister(scene);
再生制御
MmdRuntimeのコア機能の1つは、MMDアニメーション再生の制御です。
MmdRuntimeは、アニメーションを制御するための以下のメソッドを提供します:
playAnimation(): Promise<void>:アニメーション再生を開始します。pauseAnimation(): void:アニメーション再生を一時停止します。seekAnimation(frameTime: number, forceEvaluate: boolean = false): Promise<void>:アニメーションを特定のフレームに移動します。forceEvaluateがtrueに設定されている場合、移動後すぐにアニメーションが評価されます。そうでない場合は、次のbeforePhysics(): void呼び出し時に評価されます。setManualAnimationDuration(frameTimeDuration: Nullable<number>): void:アニメーションの総フレーム時間を手動で設定します。デフォルトでは、アニメーションの総長は、評価に参加するすべてのMMDアニメーションの中で最も長いものに自動的に設定されます。このメソッドは、複数のアニメーションクリップがある場合やアニメーションクリップがない場合に便利です。nullが提供された場合、自動モードに戻ります。
MmdRuntimeは、アニメーション状態をチェックするための以下のプロパティを提供します:
isAnimationPlaying: boolean:アニメーションが現在再生中かどうかを示すブール値。timeScale: number:アニメーション再生速度を制御する数値。デフォルトは1.0。currentFrameTime: number:アニメーションの現在のフレーム時間を示す数値。currentTime: number:アニメーションの現在時間を秒単位で示す数値。animationFrameTimeDuration: number:アニメーションの総フレーム時間長を示す数値。animationDuration: number:アニメーションの総長を秒単位で示す数値。
MmdRuntimeは内部的に時間を表現するためにフレーム時間を使用します。MMDアニメーションは秒間30フレームで再生されるため、1秒は30フレーム時間に対応します。例えば、currentFrameTimeが60の場合、アニメーションが2秒間再生されたことを意味します。
アニメータブル
MmdRuntimeは、任意のアニメータブルオブジェクトを制御する機能を提供します。
MMDモデルに対しては、MmdRuntimeが直接アニメーション計算を処理しますが、MMDモデル以外のオブジェクトについては、各オブジェクトが委任されて独自のアニメーションを計算します。
これらのオブジェクトはIMmdRuntimeAnimatableインターフェースを実装する必要があり、MmdRuntimeのaddAnimatable()メソッドを通じて登録できます。
IMmdRuntimeAnimatableインターフェースを実装する典型的な例は、MmdCameraクラスです。
以下は、MmdCameraオブジェクトをMmdRuntimeに登録し、アニメーションを再生するサンプルコードです:
// initialize MmdRuntime
const mmdRuntime = new MmdRuntime(scene, null);
mmdRuntime.register(scene);
// load VMD animation
const vmdLoader = new VmdLoader();
const mmdAnimation = await vmdLoader.loadAsync("motion", "path/to/motion.vmd");
// create MmdCamera and set animation
const camera = new MmdCamera();
const runtimeAnimation = camera.createRuntimeAnimation(mmdAnimation);
camera.setRuntimeAnimation(runtimeAnimation);
// add MmdCamera to MmdRuntime and play animation
mmdRuntime.addAnimatable(camera);
mmdRuntime.playAnimation();
MmdModelクラス
MmdModelは、MMDモデルを表すクラスです。MmdModelは、MMDモデルのルートメッシュ(MMDメッシュとも呼ばれる)をラップし、モデルのボーン、モーフ、物理シミュレーションなどを制御するインターフェースを提供します。
MmdModelは基本的にMmdRuntimeによって制御され、MmdRuntimeのcreateMmdModel()またはcreateMmdModelFromSkeleton()メソッドを通じてのみ作成できます。
以下は、PMXモデルをロードしてMmdModelを作成するサンプルコードです:
// initialize MmdRuntime
const mmdRuntime = new MmdRuntime(scene, null);
mmdRuntime.register(scene);
// load VMD animation
const vmdLoader = new VmdLoader();
const mmdAnimation = await vmdLoader.loadAsync("motion", "path/to/motion.vmd");
// load PMX model
const assetContainer = await LoadAssetContainerAsync("path/to/model.pmx", scene)
assetContainer.addAllToScene();
const rootMesh = assetContainer.meshes[0] as Mesh;
// create MmdModel and set animation
const mmdModel = mmdRuntime.createMmdModel(rootMesh);
const runtimeAnimation = mmdModel.createRuntimeAnimation(mmdAnimation);
mmdModel.setRuntimeAnimation(runtimeAnimation);
// play animation
mmdRuntime.playAnimation();
MmdModelインスタンスが作成された瞬間から、MMDメッシュの様々なリソースがMmdModelによって管理されます。これにはMesh、Skeleton、Bone、Morph Target、Materialなどが含まれます。
MmdModelによって管理されるリソースを直接アクセスしたり変更したりすることは推奨されません。
特にSkeletonについては、MmdModelが内部的に計算メソッドをオーバーライドしているため、MmdModelによって管理されるSkeletonやBoneオブジェクトのメソッドを直接呼び出すと、予期しない動作を引き起こす可能性があります。
MmdModelを破棄すると、対応するMMDメッシュがランタイムから削除され、モデルによって管理されるすべてのリソースが解放されます。
mmdRuntime.destroyMmdModel(mmdModel);
MmdModelオブジェクトの主なプロパティは以下の通りです:
mesh: MmdSkinnedMesh | TrimmedMmdSkinnedMesh:MMDモデルのルートメッシュ。skeleton: IMmdLinkedBoneContainer:MMDモデルのスケルトン。worldTransformMatrices: Float32Array:MMDモデルのワールドトランスフォームマトリックスの配列。各ボーンのワールドトランスフォームマトリックスを含みます。ikSolverStates: Uint8Array:MMDモデルのIKソルバー状態の配列。各IKボーンのアクティベーション状態を含みます。rigidBodyStates: Uint8Array:MMDモデルのリジッドボディ状態の配列。各リジッドボディのアクティベーション状態を含みます。runtimeBones: readonly IMmdRuntimeBone[]:MMDモデルのボーンを表すMmdRuntimeBoneオブジェクトの配列。morph: MmdMorphController:MMDモデルのモーフを制御するMmdMorphControllerオブジェクト。
MmdModel作成オプション
MmdRuntimeのcreateMmdModel()メソッドを使用してMmdModelを作成する際、オプションオブジェクトを第2引数として渡して、モデルの動作をカスタマイズできます。
const mmdModel = mmdRuntime.createMmdModel(rootMesh, {
materialProxyConstructor: null,
buildPhysics: true,
trimMetadata: true
});
オプションオブジェクトには以下のプロパティがあります:
materialProxyConstructor: Nullable<IMmdMaterialProxyConstructor<TMaterial>>:マーテリアルプロキシのコンストラクタファンクション。提供された場合、MMDモデルの各マーテリアルに対してマーテリアルプロキシが作成され、マーテリアルパラメータの操作に使用されます。これによりマーテリアルモーフィングのサポートが可能になります。詳細については、Enable Material Morphingドキュメントを参照してください。デフォルトはnullです。buildPhysics: IMmdModelPhysicsCreationOptions | boolean:物理シミュレーション作成のオプション。trueが提供された場合、MMDモデルのメタデータに基づいてリジッドボディとコンストレイントが作成されます。IMmdModelPhysicsCreationOptionsタイプのオブジェクトが提供された場合、リジッドボディとコンストレイント作成のオプションを設定できます。詳細については、Apply Physics To MMD Modelsドキュメントを参照してください。デフォルトはtrueです。trimMetadata: boolean:trueが提供された場合、MMDモデルの作成時にのみ使用される不要なメタデータが、モデル作成後にMMDメッシュから削除されます。これによりメモリー使用量を削減できます。ただし、後で同じMMDメッシュからMmdModelを再作成したい場合は、このオプションをfalseに設定する必要があります。デフォルトはtrueです。
MmdRuntimeBoneクラス
MmdRuntimeBoneは、MMDモデルのボーンを表すクラスです。Babylon.jsのBoneクラスをラップし、ボーンのモーフ、IK、アペンドトランスフォームなどを制御するインターフェースを提供します。
MmdRuntimeBoneオブジェクトには、MmdModel.runtimeBonesプロパティを通じてアクセスできます。
MmdRuntimeBoneオブジェクトの主なプロパティは以下の通りです:
linkedBone: Bone:MmdRuntimeBoneによってラップされるBabylon.jsのBoneオブジェクト。name: string:ボーンの名前。parentBone: Nullable<MmdRuntimeBone>:親ボーン。ルートボーンの場合はnull。childBones: readonly MmdRuntimeBone[]:子ボーンの配列。transformOrder: number:ボーンのトランスフォーム順序。flag: number:PMXボーンフラグ値。transformAfterPhysics: boolean:物理シミュレーション後にトランスフォームが適用されるかどうか。worldMatrix: Float32Array:ボーンのワールドトランスフォームマトリックス。これはMmdModel.worldTransformMatrices配列の一部を参照します。ikSolverIndex: number:ボーンのIKソルバーインデックス。IKボーンでない場合は-1。MmdModel.ikSolverStates配列を通じてボーンのIKアクティベーション状態をチェックできます。rigidBodyIndices: readonly number[]:ボーンに接続されたリジッドボディのインデックス配列。各リジッドボディのアクティベーション状態はMmdModel.rigidBodyStates配列を通じてチェックできます。
MmdRuntimeBoneは以下のメソッドも提供します:
getWorldMatrixToRef(target: Matrix): Matrix:ボーンのワールドトランスフォームマトリックスをtargetマトリックスにコピーします。getWorldTranslationToRef(target: Vector3): Vector3:ボーンのワールド位置をtargetベクターにコピーします。setWorldTranslation(source: DeepImmutable<Vector3>): void:ボーンのワールド位置をsourceベクターに設定します。
MmdRuntimeBoneのこれらのプロパティとメソッドは、ボーンの状態を読み取りまたは設定するために使用できます。
以下は、MmdRuntimeBoneのメソッドを使用してMMDモデルのセンターボーンのワールド位置を出力するサンプルコードです:
const meshWorldMatrix = mmdModel.mesh.getWorldMatrix();
const boneWorldMatrix = new Matrix();
const centerBone = mmdModel.runtimeBones.find(bone => bone.name === "センター")!;
// The bone world matrix is based on model space, so you need to multiply the mesh world matrix.
centerBone.getWorldMatrixToRef(boneWorldMatrix).multiplyToRef(meshWorldMatrix, boneWorldMatrix);
const centerPosition = new Vector3();
boneWorldMatrix.getTranslationToRef(centerPosition);
console.log(`Center bone world position: ${centerPosition.toString()}`);
MmdMorphControllerクラス
MmdMorphControllerは、MMDモデルのモーフを制御するクラスです。
MmdMorphControllerは、バーテックスモーフ、ボーンモーフ、UVモーフ、マーテリアルモーフなどを制御するインターフェースを提供します。
MmdMorphControllerオブジェクトには、MmdModel.morphプロパティを通じてアクセスできます。
MmdMorphControllerオブジェクトの主なメソッドは以下の通りです:
setMorphWeight(morphName: string, weight: number): void:名前がmorphNameのモーフのウェイトを設定し、weightにします。指定された名前のモーフが存在しない場合、何も起こりません。getMorphWeight(morphName: string): number:名前がmorphNameのモーフの現在のウェイトを返します。指定された名前のモーフが存在しない場合、0を返します。getMorphIndices(morphName: string): readonly number[] | undefined:名前がmorphNameのモーフのインデックス配列を返します。指定された名前のモーフが存在しない場合、undefinedを返します。setMorphWeightFromIndex(morphIndex: number, weight: number): void:インデックスmorphIndexのモーフのウェイトをweightに設定します。指定されたインデックスのモーフが存在しない場合、何も起こりません。getMorphWeightFromIndex(morphIndex: number): number:インデックスmorphIndexのモーフの現在のウェイトを返します。指定されたインデックスのモーフが存在しない場合、undefinedを返します。getMorphWeights(): Readonly<ArrayLike<number>>:すべてのモーフのウェイト配列を返します。resetMorphWeights(): void:すべてのモーフのウェイトを0に初期化します。update(): void:モーフの状態を更新します。通常はMmdRuntimeによって自動的に呼び出されるため、直接呼び出す必要はありません。
デフォルトでは、MmdMorphControllerは内部的にインデックスを使用してモーフを制御します。そのため、モーフ名を使用してウェイトを設定または取得するメソッドは、内部的に名前をインデックスに変換するため、パフォーマンスが重要な状況では、インデックスを直接使用するメソッドを使用する方が良いでしょう。
フィジックス
MmdRuntimeは、物理シミュレーションのために注入された外部の物理エンジンインプリメンテーションを使用します。babylon-mmdは3つの物理エンジンインプリメンテーションを提供します:
MmdBulletPhysics:Bullet Physicsエンジンを使用します。Bullet PhysicsはC++で書かれた物理エンジンで、babylon-mmdは最適化されたWebAssemblyコンパイル版を提供します。MmdAmmoPhysics:Ammo.jsエンジンを使用します。MmdPhysics:Havok Physicsエンジンを使用します。
MmdRuntimeで物理シミュレーションを有効にするには、MmdRuntime作成時にこれらのクラスのいずれかのインスタンスを提供する必要があります。
物理シミュレーション設定の詳細については、Apply Physics To MMD Modelsドキュメントを参照してください。
WebAssemblyインプリメンテーション
MmdRuntimeでのIKソルブ、アペンドトランスフォーム、モーフ処理は、すべてTypeScriptで実装され、ブラウザのJavaScriptエンジンによって処理されます。
babylon-mmdは、より高速なパフォーマンスのためにWebAssembly (WASM)で実装されたMmdWasmRuntimeも提供します。MmdWasmRuntimeはMmdRuntimeとほぼ同じAPIを提供し、IKソルブ、アペンドトランスフォーム、モーフ、物理シミュレーションをWebAssemblyで処理して優れたパフォーマンスを実現します。
ただし、WASMインプリメンテーションは任意にカスタマイズすることが困難で、特殊なランタイム環境(例:React Native)では制限される可能性があります。
詳細については、MMD WebAssembly Runtimeドキュメントを参照してください。