babylon-mmd
Version:
babylon.js mmd loader and runtime
151 lines (150 loc) • 6.74 kB
JavaScript
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);
};