babylon-mmd
Version:
babylon.js mmd loader and runtime
328 lines (327 loc) • 12.3 kB
TypeScript
import type { Material } from "@babylonjs/core/Materials/material";
import type { Mesh } from "@babylonjs/core/Meshes/mesh";
import { Observable } from "@babylonjs/core/Misc/observable";
import type { Scene } from "@babylonjs/core/scene";
import type { Nullable } from "@babylonjs/core/types";
import type { IPlayer } from "./Audio/IAudioPlayer";
import type { IMmdMaterialProxyConstructor } from "./IMmdMaterialProxy";
import type { IMmdRuntime } from "./IMmdRuntime";
import type { IMmdRuntimeAnimatable } from "./IMmdRuntimeAnimatable";
import type { IMmdLinkedBoneContainer } from "./IMmdRuntimeLinkedBone";
import type { MmdSkinnedMesh } from "./mmdMesh";
import { MmdModel } from "./mmdModel";
import type { IMmdPhysics } from "./Physics/IMmdPhysics";
/**
* Options for constructing MMD model physics
*/
export interface IMmdModelPhysicsCreationOptions {
/**
* Physics world ID (default: MmdWasmRuntime.physics.nextWorldId or MmdBulletPhysics.nextWorldId)
*
* value must be range of unsigned 32-bit integer
*
* By default, the physics world ID is automatically assigned and incremented
*
* If you need physics interaction between models, you can specify the same world ID
*
* Separating physics worlds can improve performance and enable parallel processing when using multi-threaded physics runtime (e.g. MmdWasmInstanceTypeMPR)
*/
worldId?: number;
/**
* Physics world IDs that share kinematic and static physics objects (default: [])
*
* value must be range of unsigned 32-bit integer
*
* If you need physics interaction by across multiple physics worlds, you can specify the world IDs that share only kinematic and static physics objects
*
* Dynamic physics objects are not shared between physics worlds. Only kinematic objects are shared
*/
kinematicSharedWorldIds?: number[];
/**
* If true, the offset for the constraint frame is forcibly disabled. (Default: false)
*
* This is useful for reproducing the behavior of MMD physics.
* But the constraint can be broken if the offset is forcibly disabled.
*
* For handling the constraint breaking, you can set fixedTimeStep to 1 / 120 or lower.
*/
disableOffsetForConstraintFrame?: boolean;
}
/**
* Options for creating MMD model
*
* Generic type `TMaterial` is used to specify the material type of the model
*/
export interface IMmdModelCreationOptions<TMaterial extends Material = Material> {
/**
* Material proxy constructor is required if you want to use material morphing (default: null)
*/
materialProxyConstructor?: Nullable<IMmdMaterialProxyConstructor<TMaterial>>;
/**
* Whether to build physics (default: true)
*/
buildPhysics?: IMmdModelPhysicsCreationOptions | boolean;
/**
* Whether to trim metadata (default: true)
*
* If true, bone, morph, rigidbody and joint metadata are trimmed when you create `MmdModel` instance from `MmdSkinnedMesh`
*
* This is useful for reducing memory usage
*
* You can't recreate `MmdModel` instance from `MmdSkinnedMesh` if you set this to true
*/
trimMetadata?: boolean;
}
/**
* MMD runtime orchestrates several MMD components (models, camera, audio)
*
* MMD runtime handles updates and synchronization of MMD components
*
* It can also create and remove runtime components
*/
export declare class MmdRuntime implements IMmdRuntime<MmdModel> {
private readonly _physics;
private readonly _models;
private readonly _animatables;
private _audioPlayer;
/**
* Whether to automatically initialize rigid bodies transform and velocity (default: true)
*
* auto physics initialization is triggered when
* - animation seek is far from current frame time (more than 2 seconds)
* - browser tab is stop rendering and resumed
* - animation is played from the frame 0
*/
autoPhysicsInitialization: boolean;
private _loggingEnabled;
/** @internal */
log: (message: string) => void;
/** @internal */
warn: (message: string) => void;
/** @internal */
error: (message: string) => void;
private _isRegistered;
/**
* This observable is notified when animation duration is changed
*/
readonly onAnimationDurationChangedObservable: Observable<void>;
/**
* This observable is notified when animation is played
*/
readonly onPlayAnimationObservable: Observable<void>;
/**
* This observable is notified when animation is paused
*/
readonly onPauseAnimationObservable: Observable<void>;
/**
* This observable is notified when animation is seeked
*/
readonly onSeekAnimationObservable: Observable<void>;
/**
* This observable is notified when animation is evaluated (usually every frame)
*/
readonly onAnimationTickObservable: Observable<void>;
private _currentFrameTime;
private _animationTimeScale;
private _animationPaused;
private _animationFrameTimeDuration;
private _useManualAnimationDuration;
private readonly _needToInitializePhysicsModels;
private _beforePhysicsBinded;
private readonly _afterPhysicsBinded;
private readonly _bindedDispose;
private readonly _disposeObservableObject;
/**
* Creates a new MMD runtime
* @param scene Objects that limit the lifetime of this instance
* @param physics Physics builder
*/
constructor(scene?: Nullable<Scene>, physics?: Nullable<IMmdPhysics>);
/**
* Dispose MMD runtime
*
* Destroy all MMD models and unregister this runtime from scene
* @param scene Scene
*/
dispose(scene: Scene): void;
/**
* Create MMD model from mesh that has MMD metadata
*
* The skeletons in the mesh where the MmdModel was created no longer follow the usual matrix update policy
* @param mmdSkinnedMesh MmdSkinnedMesh
* @param options Creation options
* @returns MMD model
* @throws {Error} if mesh is not `MmdSkinnedMesh`
*/
createMmdModel<TMaterial extends Material>(mmdSkinnedMesh: Mesh, options?: IMmdModelCreationOptions<TMaterial>): MmdModel;
/**
* Create MMD model from humanoid mesh and virtual skeleton
*
* this method is useful for supporting humanoid models, usually used by `HumanoidMmd`
* @param mmdSkinnedMesh MmdSkinnedMesh
* @param skeleton Skeleton or Virtualized skeleton
* @param options Creation options
*/
createMmdModelFromSkeleton<TMaterial extends Material>(mmdSkinnedMesh: MmdSkinnedMesh, skeleton: IMmdLinkedBoneContainer, options?: IMmdModelCreationOptions<TMaterial>): MmdModel;
/**
* Destroy MMD model
*
* Dispose all resources used at MMD runtime and restores the skeleton to the usual matrix update policy
*
* After calling the `destroyMmdModel` once, the mesh is no longer able to `createMmdModel` because the metadata is lost
* @param mmdModel MMD model to destroy
* @throws {Error} if model is not found
*/
destroyMmdModel(mmdModel: MmdModel): void;
/**
* Queue MMD model to initialize physics
*
* Actual physics initialization is done by the before physics stage
* @param mmdModel MMD model
*/
initializeMmdModelPhysics(mmdModel: MmdModel): void;
/**
* Queue all MMD models to initialize physics
*
* Actual physics initialization is done by the before physics stage
*
* If you set onlyAnimated true, it only initializes physics for animated models
*/
initializeAllMmdModelsPhysics(onlyAnimated: boolean): void;
/**
* Add Animatable object to the runtime
*
* Usually this is MMD camera, but you can add any object that implements `IMmdRuntimeAnimatable`
* @param animatable Animatable object
* @return true if the animatable is added, false if the animatable is already added
*/
addAnimatable(animatable: IMmdRuntimeAnimatable): boolean;
/**
* Remove Animatable object from the runtime
* @param animatable Animatable object
* @return true if the animatable is removed, false if the animatable is not found
*/
removeAnimatable(animatable: IMmdRuntimeAnimatable): boolean;
private _setAudioPlayerLastValue;
/**
* Set audio player to sync with animation
*
* If you set up audio Player while playing an animation, it try to play the audio from the current animation time
* And returns Promise because this operation is asynchronous. In most cases, you don't have to await this Promise
* @param audioPlayer Audio player
* @returns Promise
*/
setAudioPlayer(audioPlayer: Nullable<IPlayer>): Promise<void>;
/**
* Register MMD runtime to scene
*
* register `beforePhysics` and `afterPhysics` to scene Observables
*
* If you need a more complex update method you can call `beforePhysics` and `afterPhysics` manually
* @param scene Scene
*/
register(scene: Scene): void;
/**
* Unregister MMD runtime from scene
* @param scene Scene
*/
unregister(scene: Scene): void;
/**
* Before the physics stage, update animations and run MMD runtime solvers
*
* @param deltaTime Delta time in milliseconds
*/
beforePhysics(deltaTime: number): void;
/**
* After the physics stage, update physics and run MMD runtime solvers
*/
afterPhysics(): void;
private readonly _onAnimationDurationChanged;
private _computeAnimationDuration;
private readonly _onAudioDurationChanged;
private readonly _onAudioPlaybackRateChanged;
private readonly _onAudioPlay;
private readonly _onAudioPause;
private readonly _onAudioSeek;
private _playAnimationInternal;
/**
* Play animation from the current animation time
*
* If audio player is set, it try to play the audio from the current animation time
*
* It returns Promise because playing audio is asynchronous
* @returns Promise
*/
playAnimation(): Promise<void>;
/**
* Pause animation
*/
pauseAnimation(): void;
private _seekAnimationInternal;
/**
* Seek animation to the specified frame time
*
* If you set forceEvaluate true, the animation is evaluated even if the animation is not playing
*
* If audio player is set and not paused, it try to play the audio from the seek time so it returns Promise
* @param frameTime Time in 30fps frame
* @param forceEvaluate Whether to force evaluate animation
* @returns Promise
*/
seekAnimation(frameTime: number, forceEvaluate?: boolean): Promise<void>;
/**
* Whether animation is playing
*/
get isAnimationPlaying(): boolean;
/**
* MMD models created by this runtime
*/
get models(): readonly MmdModel[];
/**
* Animatable objects that added to this runtime
*/
get animatables(): readonly IMmdRuntimeAnimatable[];
/**
* Audio player
*/
get audioPlayer(): Nullable<IPlayer>;
/**
* Current animation time scale (default: 1)
*/
get timeScale(): number;
set timeScale(value: number);
/**
* Current animation time in 30fps frame
*/
get currentFrameTime(): number;
/**
* Current animation time in seconds
*/
get currentTime(): number;
/**
* Current animation duration in 30fps frame
*/
get animationFrameTimeDuration(): number;
/**
* Current animation duration in seconds
*/
get animationDuration(): number;
/**
* Set animation duration manually
*
* When the difference between the length of the song and the length of the animation is large, it can be helpful to adjust the animation duration manually
* @param frameTimeDuration Time in 30fps frame
*/
setManualAnimationDuration(frameTimeDuration: Nullable<number>): void;
/**
* Enable or disable debug logging (default: false)
*/
get loggingEnabled(): boolean;
set loggingEnabled(value: boolean);
private _logEnabled;
private _logDisabled;
private _warnEnabled;
private _warnDisabled;
private _errorEnabled;
private _errorDisabled;
}