babylon-mmd
Version:
babylon.js mmd loader and runtime
483 lines (482 loc) • 28 kB
JavaScript
import { _StaticOffsetValueColor3, _StaticOffsetValueColor4, _StaticOffsetValueQuaternion, _StaticOffsetValueSize, _StaticOffsetValueVector2, _StaticOffsetValueVector3, Animation } from "@babylonjs/core/Animations/animation";
import { AnimationKeyInterpolation } from "@babylonjs/core/Animations/animationKey";
import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
import { Quaternion, Vector2, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { RegisterClass } from "@babylonjs/core/Misc/typeStore";
import { BezierInterpolate } from "./bezierInterpolate";
/**
* extends of AnimationKeyInterpolation to add bezier interpolation
*/
export const AnimationKeyInterpolationBezier = 2;
/**
* Partial implementation of cubic bezier interpolated animation
*/
export class BezierAnimation extends Animation {
/**
* Slerped tangent quaternion animation type
*/
static ANIMATIONTYPE_SLERP_TANGENT_QUATERNION = 8;
// NOTE: be careful with the Babylon.js _interpolate method changes
/**
* @internal Internal use only
*/
_interpolate(currentFrame, state, searchClosestKeyOnly = false) {
if (state.loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && state.repeatCount > 0) {
return state.highLimitValue.clone ? state.highLimitValue.clone() : state.highLimitValue;
}
const keys = this._keys;
let key;
if (!this._coreAnimation) {
const keysLength = keys.length;
key = state.key;
while (key >= 0 && currentFrame < keys[key].frame) {
key -= 1;
}
while (key + 1 <= keysLength - 1 && currentFrame >= keys[key + 1].frame) {
key += 1;
}
state.key = key;
if (key < 0) {
return searchClosestKeyOnly ? undefined : this._getKeyValue(keys[0].value);
}
else if (key + 1 > keysLength - 1) {
return searchClosestKeyOnly ? undefined : this._getKeyValue(keys[keysLength - 1].value);
}
this._key = key;
}
else {
key = this._coreAnimation._key;
}
const startKey = keys[key];
const endKey = keys[key + 1];
if (searchClosestKeyOnly && (currentFrame === startKey.frame || currentFrame === endKey.frame)) {
return undefined;
}
const startValue = this._getKeyValue(startKey.value);
const endValue = this._getKeyValue(endKey.value);
if (startKey.interpolation === AnimationKeyInterpolation.STEP) {
if (endKey.frame > currentFrame) {
return startValue;
}
else {
return endValue;
}
}
const useTangent = startKey.outTangent !== undefined && endKey.inTangent !== undefined;
const frameDelta = endKey.frame - startKey.frame;
// gradient : percent of currentFrame between the frame inf and the frame sup
let gradient = (currentFrame - startKey.frame) / frameDelta;
// check for easingFunction and correction of gradient
const easingFunction = startKey.easingFunction || this.getEasingFunction();
// can also be undefined, if not provided
if (easingFunction) {
gradient = easingFunction.ease(gradient);
}
if (startKey.interpolation === AnimationKeyInterpolation.NONE) {
switch (this.dataType) {
// Float
case Animation.ANIMATIONTYPE_FLOAT: {
const floatValue = useTangent
? this.floatInterpolateFunctionWithTangents(startValue, startKey.outTangent * frameDelta, endValue, endKey.inTangent * frameDelta, gradient)
: this.floatInterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return floatValue;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return (state.offsetValue ?? 0) * state.repeatCount + floatValue;
}
break;
}
// Quaternion
case Animation.ANIMATIONTYPE_QUATERNION: {
const quatValue = useTangent
? this.quaternionInterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient)
: this.quaternionInterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return quatValue;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return quatValue.addInPlace((state.offsetValue || _StaticOffsetValueQuaternion).scale(state.repeatCount));
}
return quatValue;
}
// Vector3
case Animation.ANIMATIONTYPE_VECTOR3: {
const vec3Value = useTangent
? this.vector3InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient)
: this.vector3InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return vec3Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return vec3Value.add((state.offsetValue || _StaticOffsetValueVector3).scale(state.repeatCount));
}
break;
}
// Vector2
case Animation.ANIMATIONTYPE_VECTOR2: {
const vec2Value = useTangent
? this.vector2InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient)
: this.vector2InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return vec2Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return vec2Value.add((state.offsetValue || _StaticOffsetValueVector2).scale(state.repeatCount));
}
break;
}
// Size
case Animation.ANIMATIONTYPE_SIZE: {
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return this.sizeInterpolateFunction(startValue, endValue, gradient);
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return this.sizeInterpolateFunction(startValue, endValue, gradient).add((state.offsetValue || _StaticOffsetValueSize).scale(state.repeatCount));
}
break;
}
// Color3
case Animation.ANIMATIONTYPE_COLOR3: {
const color3Value = useTangent
? this.color3InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient)
: this.color3InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return color3Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return color3Value.add((state.offsetValue || _StaticOffsetValueColor3).scale(state.repeatCount));
}
break;
}
// Color4
case Animation.ANIMATIONTYPE_COLOR4: {
const color4Value = useTangent
? this.color4InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient)
: this.color4InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return color4Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return color4Value.add((state.offsetValue || _StaticOffsetValueColor4).scale(state.repeatCount));
}
break;
}
// Matrix
case Animation.ANIMATIONTYPE_MATRIX: {
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO: {
if (Animation.AllowMatricesInterpolation) {
return this.matrixInterpolateFunction(startValue, endValue, gradient, state.workValue);
}
return startValue;
}
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT: {
return startValue;
}
}
break;
}
}
}
else { // bezier
switch (this.dataType) {
// Float
case Animation.ANIMATIONTYPE_FLOAT: {
const floatValue = useTangent
? this.floatInterpolateFunctionWithControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.floatInterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return floatValue;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return (state.offsetValue ?? 0) * state.repeatCount + floatValue;
}
break;
}
// Quaternion
case Animation.ANIMATIONTYPE_QUATERNION: {
const quatValue = useTangent
? this.quaternionInterpolateFunctionWithControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.quaternionInterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return quatValue;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return quatValue.addInPlace((state.offsetValue || _StaticOffsetValueQuaternion).scale(state.repeatCount));
}
return quatValue;
}
// Quaternion using slerp
case BezierAnimation.ANIMATIONTYPE_SLERP_TANGENT_QUATERNION: {
const quatValue = useTangent
? this.quaternionInterpolateFunctionWithSlerpControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.quaternionInterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return quatValue;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return quatValue.addInPlace((state.offsetValue || _StaticOffsetValueQuaternion).scale(state.repeatCount));
}
return quatValue;
}
// Vector3
case Animation.ANIMATIONTYPE_VECTOR3: {
const vec3Value = useTangent
? this.vector3InterpolateFunctionWithControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.vector3InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return vec3Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return vec3Value.add((state.offsetValue || _StaticOffsetValueVector3).scale(state.repeatCount));
}
break;
}
// Vector2
case Animation.ANIMATIONTYPE_VECTOR2: {
const vec2Value = useTangent
? this.vector2InterpolateFunctionWithControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.vector2InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return vec2Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return vec2Value.add((state.offsetValue || _StaticOffsetValueVector2).scale(state.repeatCount));
}
break;
}
// Size
case Animation.ANIMATIONTYPE_SIZE: {
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return this.sizeInterpolateFunction(startValue, endValue, gradient);
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return this.sizeInterpolateFunction(startValue, endValue, gradient).add((state.offsetValue || _StaticOffsetValueSize).scale(state.repeatCount));
}
break;
}
// Color3
case Animation.ANIMATIONTYPE_COLOR3: {
const color3Value = useTangent
? this.color3InterpolateFunctionWithControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.color3InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return color3Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return color3Value.add((state.offsetValue || _StaticOffsetValueColor3).scale(state.repeatCount));
}
break;
}
// Color4
case Animation.ANIMATIONTYPE_COLOR4: {
const color4Value = useTangent
? this.color4InterpolateFunctionWithControlPoints(startValue, startKey.outTangent, endValue, endKey.inTangent, gradient)
: this.color4InterpolateFunction(startValue, endValue, gradient);
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO:
return color4Value;
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT:
return color4Value.add((state.offsetValue || _StaticOffsetValueColor4).scale(state.repeatCount));
}
break;
}
// Matrix
case Animation.ANIMATIONTYPE_MATRIX: {
switch (state.loopMode) {
case Animation.ANIMATIONLOOPMODE_CYCLE:
case Animation.ANIMATIONLOOPMODE_CONSTANT:
case Animation.ANIMATIONLOOPMODE_YOYO: {
if (Animation.AllowMatricesInterpolation) {
return this.matrixInterpolateFunction(startValue, endValue, gradient, state.workValue);
}
return startValue;
}
case Animation.ANIMATIONLOOPMODE_RELATIVE:
case Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT: {
return startValue;
}
}
break;
}
}
}
return 0;
}
/**
* Interpolates a scalar cubically from control points
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate
* @returns Interpolated scalar value
*/
floatInterpolateFunctionWithControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weight = BezierInterpolate(outTangent.x, inTangent.x, outTangent.y, inTangent.y, gradient);
return startValue * (1 - weight) + endValue * weight;
}
/**
* Interpolates a quaternion cubically from control points
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation curve
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate
* @returns Interpolated quaternion value
*/
quaternionInterpolateFunctionWithControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weightX = BezierInterpolate(outTangent[0].x, inTangent[0].x, outTangent[0].y, inTangent[0].y, gradient);
const weightY = BezierInterpolate(outTangent[1].x, inTangent[1].x, outTangent[1].y, inTangent[1].y, gradient);
const weightZ = BezierInterpolate(outTangent[2].x, inTangent[2].x, outTangent[2].y, inTangent[2].y, gradient);
const weightW = BezierInterpolate(outTangent[3].x, inTangent[3].x, outTangent[3].y, inTangent[3].y, gradient);
return new Quaternion(startValue.x * (1 - weightX) + endValue.x * weightX, startValue.y * (1 - weightY) + endValue.y * weightY, startValue.z * (1 - weightZ) + endValue.z * weightZ, startValue.w * (1 - weightW) + endValue.w * weightW);
}
/**
* Interpolates a quaternion cubically from control points with slerp
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation curve
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate
* @returns Interpolated quaternion value
*/
quaternionInterpolateFunctionWithSlerpControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weight = BezierInterpolate(outTangent.x, inTangent.x, outTangent.y, inTangent.y, gradient);
return Quaternion.Slerp(startValue, endValue, weight);
}
/**
* Interpolates a Vector3 cubically from control points
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate (value between 0 and 1)
* @returns InterpolatedVector3 value
*/
vector3InterpolateFunctionWithControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weightX = BezierInterpolate(outTangent[0].x, inTangent[0].x, outTangent[0].y, inTangent[0].y, gradient);
const weightY = BezierInterpolate(outTangent[1].x, inTangent[1].x, outTangent[1].y, inTangent[1].y, gradient);
const weightZ = BezierInterpolate(outTangent[2].x, inTangent[2].x, outTangent[2].y, inTangent[2].y, gradient);
return new Vector3(startValue.x * (1 - weightX) + endValue.x * weightX, startValue.y * (1 - weightY) + endValue.y * weightY, startValue.z * (1 - weightZ) + endValue.z * weightZ);
}
/**
* Interpolates a Vector2 cubically from control points
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate (value between 0 and 1)
* @returns Interpolated Vector2 value
*/
vector2InterpolateFunctionWithControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weightX = BezierInterpolate(outTangent[0].x, inTangent[0].x, outTangent[0].y, inTangent[0].y, gradient);
const weightY = BezierInterpolate(outTangent[1].x, inTangent[1].x, outTangent[1].y, inTangent[1].y, gradient);
return new Vector2(startValue.x * (1 - weightX) + endValue.x * weightX, startValue.y * (1 - weightY) + endValue.y * weightY);
}
/**
* Interpolates a Color3 cubically from control points
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate
* @returns interpolated value
*/
color3InterpolateFunctionWithControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weightR = BezierInterpolate(outTangent[0].x, inTangent[0].x, outTangent[0].y, inTangent[0].y, gradient);
const weightG = BezierInterpolate(outTangent[1].x, inTangent[1].x, outTangent[1].y, inTangent[1].y, gradient);
const weightB = BezierInterpolate(outTangent[2].x, inTangent[2].x, outTangent[2].y, inTangent[2].y, gradient);
return new Color3(startValue.r * (1 - weightR) + endValue.r * weightR, startValue.g * (1 - weightG) + endValue.g * weightG, startValue.b * (1 - weightB) + endValue.b * weightB);
}
/**
* Interpolates a Color4 cubically from control points
* @param startValue Start value of the animation curve
* @param outTangent End tangent of the animation
* @param endValue End value of the animation curve
* @param inTangent Start tangent of the animation curve
* @param gradient Scalar amount to interpolate
* @returns interpolated value
*/
color4InterpolateFunctionWithControlPoints(startValue, outTangent, endValue, inTangent, gradient) {
const weightR = BezierInterpolate(outTangent.r, inTangent.r, outTangent.g, inTangent.g, gradient);
const weightG = BezierInterpolate(outTangent.b, inTangent.b, outTangent.g, inTangent.g, gradient);
const weightB = BezierInterpolate(outTangent.b, inTangent.b, outTangent.g, inTangent.g, gradient);
const weightA = BezierInterpolate(outTangent.a, inTangent.a, outTangent.g, inTangent.g, gradient);
return new Color4(startValue.r * (1 - weightR) + endValue.r * weightR, startValue.g * (1 - weightG) + endValue.g * weightG, startValue.b * (1 - weightB) + endValue.b * weightB, startValue.a * (1 - weightA) + endValue.a * weightA);
}
// NOTE: clone method is just a copy of the original method with a different return type. becareful to babylon.js internal changes
/**
* Makes a copy of the animation
* @returns Cloned animation
*/
clone() {
const clone = new BezierAnimation(this.name, this.targetPropertyPath.join("."), this.framePerSecond, this.dataType, this.loopMode);
clone.enableBlending = this.enableBlending;
clone.blendingSpeed = this.blendingSpeed;
if (this._keys) {
clone.setKeys(this._keys);
}
if (this._ranges) {
clone._ranges = {};
for (const name in this._ranges) {
const range = this._ranges[name];
if (!range) {
continue;
}
clone._ranges[name] = range.clone();
}
}
return clone;
}
}
RegisterClass("BABYLON.BezierAnimation", BezierAnimation);