UNPKG

babylon-mmd

Version:
483 lines (482 loc) 28 kB
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);