babylon-mmd
Version:
babylon.js mmd loader and runtime
272 lines (271 loc) • 11.2 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Camera } from "@babylonjs/core/Cameras/camera";
import { Matrix, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { serialize, serializeAsVector3 } from "@babylonjs/core/Misc/decorators";
import { Observable } from "@babylonjs/core/Misc/observable";
import { Node } from "@babylonjs/core/node";
import { CreateMmdRuntimeAnimationHandle } from "./mmdRuntimeAnimationHandle";
Node.AddNodeConstructor("MmdCamera", (name, scene) => {
return () => new MmdCamera(name, undefined, scene);
});
/**
* MMD camera
*
* The MMD camera is a type of Arc Rotate Camera that determines the transform of the camera by the center position, rotation(yaw pitch roll), and distance parameters
*/
export class MmdCamera extends Camera {
/**
* Gets or sets a boolean indicating that the scaling of the parent hierarchy will not be taken in account by the camera
*/
ignoreParentScaling = false;
/**
* Define the current rotation of the camera
*/
rotation = new Vector3();
/**
* Define the current distance of the camera from its target (default: -45)
*/
distance = -45;
_viewMatrix = Matrix.Zero();
_tmpUpVector = Vector3.Zero();
_tmpTargetVector = Vector3.Zero();
/**
* Observable triggered when the current animation is changed
*
* Value is 30fps frame time duration of the animation
*/
onAnimationDurationChangedObservable;
_animationHandleMap;
_currentAnimation;
/**
* Creates a new MMD camera
* @param name Defines the name of the camera in the scene
* @param position Defines the position of the camera
* @param scene Defines the scene the camera belongs too
* @param setActiveOnSceneIfNoneActive Defines if the camera should be set as active after creation if no other camera have been defined in the scene
*/
constructor(name, position = new Vector3(0, 10, 0), scene, setActiveOnSceneIfNoneActive = true) {
super(name, position, scene, setActiveOnSceneIfNoneActive);
// mmd default fov
this.fov = 30 * (Math.PI / 180);
this.onAnimationDurationChangedObservable = new Observable();
this._animationHandleMap = new Map();
this._currentAnimation = null;
}
/**
* Bind the animation to the camera and return a handle to the runtime animation
* @param animation MMD animation or MMD camera animation group to add
* @returns A handle to the runtime animation
*/
createRuntimeAnimation(animation) {
let runtimeAnimation;
if (animation.createRuntimeCameraAnimation) {
runtimeAnimation = animation.createRuntimeCameraAnimation(this);
}
else {
throw new Error("animation is not MmdAnimation or MmdCameraAnimationContainer or MmdCompositeAnimation. are you missing import \"babylon-mmd/esm/Runtime/Animation/mmdRuntimeCameraAnimation\" or \"babylon-mmd/esm/Runtime/Animation/mmdRuntimeCameraAnimationContainer\" or \"babylon-mmd/esm/Runtime/Animation/mmdCompositeRuntimeCameraAnimation\"?");
}
const handle = CreateMmdRuntimeAnimationHandle();
this._animationHandleMap.set(handle, runtimeAnimation);
return handle;
}
/**
* Destroy a runtime animation by its handle
* @param handle The handle of the runtime animation to destroy
* @returns True if the animation was destroyed, false if it was not found
*/
destroyRuntimeAnimation(handle) {
const animation = this._animationHandleMap.get(handle);
if (animation === undefined)
return false;
if (this._currentAnimation === animation) {
this._currentAnimation = null;
if (animation.animation.endFrame !== 0) {
this.onAnimationDurationChangedObservable.notifyObservers(0);
}
}
this._animationHandleMap.delete(handle);
animation.dispose?.();
return true;
}
/**
* Set the current animation of the camera
*
* If handle is null, the current animation will be cleared
*
* @param handle The handle of the animation to set as current
* @throws {Error} if the animation with the handle is not found
*/
setRuntimeAnimation(handle) {
if (handle === null) {
if (this._currentAnimation !== null) {
const endFrame = this._currentAnimation.animation.endFrame;
this._currentAnimation = null;
if (endFrame !== 0) {
this.onAnimationDurationChangedObservable.notifyObservers(0);
}
}
return;
}
const animation = this._animationHandleMap.get(handle);
if (animation === undefined) {
throw new Error(`Animation with handle ${handle} not found`);
}
const oldAnimationEndFrame = this._currentAnimation?.animation.endFrame ?? 0;
this._currentAnimation = animation;
if (oldAnimationEndFrame !== animation.animation.endFrame) {
this.onAnimationDurationChangedObservable.notifyObservers(animation.animation.endFrame);
}
}
/**
* Get the runtime animation map of the camera
*/
get runtimeAnimations() {
return this._animationHandleMap;
}
/**
* Get the current animation of the camera
*/
get currentAnimation() {
return this._currentAnimation;
}
/**
* Duration of the animation in 30fps frame time
*/
get animationFrameTimeDuration() {
if (this._currentAnimation === null) {
return 0;
}
return this._currentAnimation.animation.endFrame;
}
/**
* Animate the camera
* @param frameTime The 30fps frame time
*/
animate(frameTime) {
if (this._currentAnimation === null)
return;
this._currentAnimation.animate(frameTime);
}
_storedPosition = null;
_storedRotation = null;
_storedDistance = 0;
/**
* Store current camera state of the camera (fov, position, rotation, etc..)
* @returns the camera
*/
storeState() {
this._storedPosition = this.position.clone();
this._storedRotation = this.rotation.clone();
this._storedDistance = this.distance;
return super.storeState();
}
/**
* Restored camera state. You must call storeState() first
* @returns whether it was successful or not
* @internal
*/
_restoreStateValues() {
if (!super._restoreStateValues()) {
return false;
}
this.position = this._storedPosition.clone();
this.rotation = this._storedRotation.clone();
this.distance = this._storedDistance;
return true;
}
/** @internal */
_initCache() {
super._initCache();
this._cache.rotation = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
this._cache.distance = Number.MAX_VALUE;
}
/**
* @internal
*/
_updateCache(ignoreParentClass) {
if (!ignoreParentClass) {
super._updateCache();
}
this._cache.rotation.copyFrom(this.rotation);
this._cache.distance = this.distance;
}
// Synchronized
/** @internal */
_isSynchronizedViewMatrix() {
if (!super._isSynchronizedViewMatrix()) {
return false;
}
return (this._cache.rotation.equals(this.rotation) &&
this._cache.distance === this.distance);
}
static _RotationMatrix = new Matrix();
static _CameraEyePosition = new Vector3();
static _UpVector = new Vector3();
static _TargetVector = new Vector3();
/** @internal */
_getViewMatrix() {
const rotationMatrix = Matrix.RotationYawPitchRollToRef(-this.rotation.y, -this.rotation.x, -this.rotation.z, MmdCamera._RotationMatrix);
const cameraEyePosition = this.position.addToRef(Vector3.TransformCoordinatesFromFloatsToRef(0, 0, this.distance, rotationMatrix, MmdCamera._CameraEyePosition), MmdCamera._CameraEyePosition);
const targetVector = Vector3.TransformNormalFromFloatsToRef(0, 0, 1, rotationMatrix, MmdCamera._TargetVector)
.addInPlace(cameraEyePosition);
const upVector = Vector3.TransformNormalFromFloatsToRef(0, 1, 0, rotationMatrix, MmdCamera._UpVector);
if (this.ignoreParentScaling) {
if (this.parent) {
const parentWorldMatrix = this.parent.getWorldMatrix();
Vector3.TransformCoordinatesToRef(cameraEyePosition, parentWorldMatrix, this._globalPosition);
Vector3.TransformCoordinatesToRef(targetVector, parentWorldMatrix, this._tmpTargetVector);
Vector3.TransformNormalToRef(upVector, parentWorldMatrix, this._tmpUpVector);
this._markSyncedWithParent();
}
else {
this._globalPosition.copyFrom(cameraEyePosition);
this._tmpTargetVector.copyFrom(targetVector);
this._tmpUpVector.copyFrom(upVector);
}
if (this.getScene().useRightHandedSystem) {
Matrix.LookAtRHToRef(this._globalPosition, this._tmpTargetVector, this._tmpUpVector, this._viewMatrix);
}
else {
Matrix.LookAtLHToRef(this._globalPosition, this._tmpTargetVector, this._tmpUpVector, this._viewMatrix);
}
return this._viewMatrix;
}
if (this.getScene().useRightHandedSystem) {
Matrix.LookAtRHToRef(cameraEyePosition, targetVector, upVector, this._viewMatrix);
}
else {
Matrix.LookAtLHToRef(cameraEyePosition, targetVector, upVector, this._viewMatrix);
}
if (this.parent) {
const parentWorldMatrix = this.parent.getWorldMatrix();
this._viewMatrix.invert();
this._viewMatrix.multiplyToRef(parentWorldMatrix, this._viewMatrix);
this._viewMatrix.getTranslationToRef(this._globalPosition);
this._viewMatrix.invert();
this._markSyncedWithParent();
}
else {
this._globalPosition.copyFrom(cameraEyePosition);
}
return this._viewMatrix;
}
/**
* Gets the current object class name.
* @returns the class name
*/
getClassName() {
return "MmdCamera";
}
}
__decorate([
serializeAsVector3()
], MmdCamera.prototype, "rotation", void 0);
__decorate([
serialize()
], MmdCamera.prototype, "distance", void 0);