UNPKG

babylon-mmd

Version:
151 lines (150 loc) 6.74 kB
import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { MmdCompositeAnimation } from "./mmdCompositeAnimation"; /** * Mmd composite runtime camera animation * * An object with mmd composite animation and camera binding information */ export class MmdCompositeRuntimeCameraAnimation { /** * The animation data */ animation; _camera; _runtimeAnimations; _onSpanAdded; _onSpanRemoved; constructor(animation, camera, runtimeAnimations, onSpanAdded, onSpanRemoved) { this.animation = animation; this._camera = camera; this._runtimeAnimations = runtimeAnimations; this._onSpanAdded = onSpanAdded; this._onSpanRemoved = onSpanRemoved; animation.onSpanAddedObservable.add(onSpanAdded); animation.onSpanRemovedObservable.add(onSpanRemoved); } static _ActiveAnimationSpans = []; static _ActiveRuntimeAnimations = []; static _CameraPosition = new Vector3(); static _CameraRotation = new Vector3(); /** * Update animation * @param frameTime Frame time in 30fps */ animate(frameTime) { frameTime = Math.max(this.animation.startFrame, Math.min(this.animation.endFrame, frameTime)); const spans = this.animation.spans; const runtimeAnimations = this._runtimeAnimations; const activeAnimationSpans = MmdCompositeRuntimeCameraAnimation._ActiveAnimationSpans; const activeRuntimeAnimations = MmdCompositeRuntimeCameraAnimation._ActiveRuntimeAnimations; for (let i = 0; i < spans.length; ++i) { const span = spans[i]; const runtimeAnimation = runtimeAnimations[i]; if (runtimeAnimation !== null && 0 < span.weight && span.isInSpan(frameTime)) { activeAnimationSpans.push(span); activeRuntimeAnimations.push(runtimeAnimation); } } let totalWeight = 0; for (let i = 0; i < activeAnimationSpans.length; ++i) { totalWeight += activeAnimationSpans[i].getEasedWeight(activeAnimationSpans[i].getFrameTime(frameTime)); } const camera = this._camera; if (totalWeight === 0) { // avoid divide by zero // camera does not have rest pose // camera.position.setAll(0); // camera.rotation.setAll(0); // camera.distance = 0; // camera.fov = 0; activeAnimationSpans.length = 0; activeRuntimeAnimations.length = 0; return; } if (totalWeight === 1 && activeAnimationSpans.length === 1) { // for one animation, just animate it const span = activeAnimationSpans[0]; const runtimeAnimation = activeRuntimeAnimations[0]; runtimeAnimation.animate(span.getFrameTime(frameTime)); activeAnimationSpans.length = 0; activeRuntimeAnimations.length = 0; return; } const normalizer = totalWeight < 1.0 ? 1.0 : 1.0 / totalWeight; const position = MmdCompositeRuntimeCameraAnimation._CameraPosition.setAll(0); const rotation = MmdCompositeRuntimeCameraAnimation._CameraRotation.setAll(0); let distance = 0; let fov = 0; for (let i = 0; i < activeAnimationSpans.length; ++i) { const span = activeAnimationSpans[i]; const runtimeAnimation = activeRuntimeAnimations[i]; const frameTimeInSpan = span.getFrameTime(frameTime); runtimeAnimation.animate(frameTimeInSpan); const weight = span.getEasedWeight(frameTimeInSpan) * normalizer; camera.position.scaleAndAddToRef(weight, position); camera.rotation.scaleAndAddToRef(weight, rotation); distance += camera.distance * weight; fov += camera.fov * weight; } camera.position.copyFrom(position); camera.rotation.copyFrom(rotation); camera.distance = distance; camera.fov = fov; activeAnimationSpans.length = 0; activeRuntimeAnimations.length = 0; } /** * Dispose. remove all event listeners */ dispose() { if (this._onSpanAdded !== null) { this.animation.onSpanAddedObservable.removeCallback(this._onSpanAdded); this.animation.onSpanRemovedObservable.removeCallback(this._onSpanRemoved); this._onSpanAdded = null; this._onSpanRemoved = null; } } /** * Bind animation to camera * @param animation Animation to bind * @param camera Bind target * @returns MmdCompositeRuntimeCameraAnimation instance */ static Create(animation, camera) { const runtimeAnimations = new Array(animation.spans.length).fill(null); const spans = animation.spans; for (let i = 0; i < spans.length; ++i) { const animation = spans[i].animation; if (animation.createRuntimeCameraAnimation !== undefined) { const runtimeAnimation = animation.createRuntimeCameraAnimation(camera); runtimeAnimations[i] = runtimeAnimation; } else if (animation.createRuntimeModelAnimation === undefined) { throw new Error(`animation ${animation.name} is not bindable. are you missing import "babylon-mmd/esm/Runtime/Animation/mmdRuntimeCameraAnimation" or "babylon-mmd/esm/Runtime/Animation/mmdRuntimeCameraAnimationContainer"?`); } } const onSpanAdded = (span) => { const animation = span.animation; if (animation.createRuntimeCameraAnimation !== undefined) { const runtimeAnimation = animation.createRuntimeCameraAnimation(camera); runtimeAnimations.push(runtimeAnimation); } else if (animation.createRuntimeModelAnimation === undefined) { throw new Error(`animation ${animation.name} is not bindable. are you missing import "babylon-mmd/esm/Runtime/Animation/mmdRuntimeCameraAnimation" or "babylon-mmd/esm/Runtime/Animation/mmdRuntimeCameraAnimationContainer"?`); } else { runtimeAnimations.push(null); } }; const onSpanRemoved = (removeIndex) => { runtimeAnimations.splice(removeIndex, 1); }; return new MmdCompositeRuntimeCameraAnimation(animation, camera, runtimeAnimations, onSpanAdded, onSpanRemoved); } } /** * Create runtime camera animation * @param camera Bind target * @returns MmdRuntimeCameraAnimation instance */ MmdCompositeAnimation.prototype.createRuntimeCameraAnimation = function (camera) { return MmdCompositeRuntimeCameraAnimation.Create(this, camera); };