@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
896 lines • 40 kB
JavaScript
import { Observable } from "../Misc/observable.js";
import { RuntimeAnimation } from "./runtimeAnimation.js";
import { Animation } from "./animation.js";
import { PrecisionDate } from "../Misc/precisionDate.js";
import { Matrix, Quaternion, TmpVectors, Vector3 } from "../Maths/math.vector.js";
/**
* Class used to store an actual running animation
*/
export class Animatable {
/**
* Gets the root Animatable used to synchronize and normalize animations
*/
get syncRoot() {
return this._syncRoot;
}
/**
* Gets the current frame of the first RuntimeAnimation
* Used to synchronize Animatables
*/
get masterFrame() {
if (this._runtimeAnimations.length === 0) {
return 0;
}
return this._runtimeAnimations[0].currentFrame;
}
/**
* Gets or sets the animatable weight (-1.0 by default meaning not weighted)
*/
get weight() {
return this._weight;
}
set weight(value) {
if (value === -1) {
// -1 is ok and means no weight
this._weight = -1;
return;
}
// Else weight must be in [0, 1] range
this._weight = Math.min(Math.max(value, 0), 1.0);
}
/**
* Gets or sets the speed ratio to apply to the animatable (1.0 by default)
*/
get speedRatio() {
return this._speedRatio;
}
set speedRatio(value) {
for (let index = 0; index < this._runtimeAnimations.length; index++) {
const animation = this._runtimeAnimations[index];
animation._prepareForSpeedRatioChange(value);
}
this._speedRatio = value;
// Resync _manualJumpDelay in case goToFrame was called before speedRatio was set.
if (this._goToFrame !== null) {
this.goToFrame(this._goToFrame);
}
}
/**
* Gets the elapsed time since the animatable started in milliseconds
*/
get elapsedTime() {
return this._localDelayOffset === null ? 0 : this._scene._animationTime - this._localDelayOffset;
}
/**
* Creates a new Animatable
* @param scene defines the hosting scene
* @param target defines the target object
* @param fromFrame defines the starting frame number (default is 0)
* @param toFrame defines the ending frame number (default is 100)
* @param loopAnimation defines if the animation must loop (default is false)
* @param speedRatio defines the factor to apply to animation speed (default is 1)
* @param onAnimationEnd defines a callback to call when animation ends if it is not looping
* @param animations defines a group of animation to add to the new Animatable
* @param onAnimationLoop defines a callback to call when animation loops
* @param isAdditive defines whether the animation should be evaluated additively
* @param playOrder defines the order in which this animatable should be processed in the list of active animatables (default: 0)
*/
constructor(scene,
/** defines the target object */
target,
/** [0] defines the starting frame number (default is 0) */
fromFrame = 0,
/** [100] defines the ending frame number (default is 100) */
toFrame = 100,
/** [false] defines if the animation must loop (default is false) */
loopAnimation = false, speedRatio = 1.0,
/** defines a callback to call when animation ends if it is not looping */
onAnimationEnd, animations,
/** defines a callback to call when animation loops */
onAnimationLoop,
/** [false] defines whether the animation should be evaluated additively */
isAdditive = false,
/** [0] defines the order in which this animatable should be processed in the list of active animatables (default: 0) */
playOrder = 0) {
this.target = target;
this.fromFrame = fromFrame;
this.toFrame = toFrame;
this.loopAnimation = loopAnimation;
this.onAnimationEnd = onAnimationEnd;
this.onAnimationLoop = onAnimationLoop;
this.isAdditive = isAdditive;
this.playOrder = playOrder;
this._localDelayOffset = null;
this._pausedDelay = null;
this._manualJumpDelay = null;
/** @hidden */
this._runtimeAnimations = new Array();
this._paused = false;
this._speedRatio = 1;
this._weight = -1.0;
this._previousWeight = -1.0;
this._syncRoot = null;
this._frameToSyncFromJump = null;
this._goToFrame = null;
/**
* Gets or sets a boolean indicating if the animatable must be disposed and removed at the end of the animation.
* This will only apply for non looping animation (default is true)
*/
this.disposeOnEnd = true;
/**
* Gets a boolean indicating if the animation has started
*/
this.animationStarted = false;
/**
* Observer raised when the animation ends
*/
this.onAnimationEndObservable = new Observable();
/**
* Observer raised when the animation loops
*/
this.onAnimationLoopObservable = new Observable();
this._scene = scene;
if (animations) {
this.appendAnimations(target, animations);
}
this._speedRatio = speedRatio;
scene._activeAnimatables.push(this);
}
// Methods
/**
* Synchronize and normalize current Animatable with a source Animatable
* This is useful when using animation weights and when animations are not of the same length
* @param root defines the root Animatable to synchronize with (null to stop synchronizing)
* @returns the current Animatable
*/
syncWith(root) {
this._syncRoot = root;
if (root) {
// Make sure this animatable will animate after the root
const index = this._scene._activeAnimatables.indexOf(this);
if (index > -1) {
this._scene._activeAnimatables.splice(index, 1);
this._scene._activeAnimatables.push(this);
}
}
return this;
}
/**
* Gets the list of runtime animations
* @returns an array of RuntimeAnimation
*/
getAnimations() {
return this._runtimeAnimations;
}
/**
* Adds more animations to the current animatable
* @param target defines the target of the animations
* @param animations defines the new animations to add
*/
appendAnimations(target, animations) {
for (let index = 0; index < animations.length; index++) {
const animation = animations[index];
const newRuntimeAnimation = new RuntimeAnimation(target, animation, this._scene, this);
newRuntimeAnimation._onLoop = () => {
this.onAnimationLoopObservable.notifyObservers(this);
if (this.onAnimationLoop) {
this.onAnimationLoop();
}
};
this._runtimeAnimations.push(newRuntimeAnimation);
}
}
/**
* Gets the source animation for a specific property
* @param property defines the property to look for
* @returns null or the source animation for the given property
*/
getAnimationByTargetProperty(property) {
const runtimeAnimations = this._runtimeAnimations;
for (let index = 0; index < runtimeAnimations.length; index++) {
if (runtimeAnimations[index].animation.targetProperty === property) {
return runtimeAnimations[index].animation;
}
}
return null;
}
/**
* Gets the runtime animation for a specific property
* @param property defines the property to look for
* @returns null or the runtime animation for the given property
*/
getRuntimeAnimationByTargetProperty(property) {
const runtimeAnimations = this._runtimeAnimations;
for (let index = 0; index < runtimeAnimations.length; index++) {
if (runtimeAnimations[index].animation.targetProperty === property) {
return runtimeAnimations[index];
}
}
return null;
}
/**
* Resets the animatable to its original state
*/
reset() {
const runtimeAnimations = this._runtimeAnimations;
for (let index = 0; index < runtimeAnimations.length; index++) {
runtimeAnimations[index].reset(true);
}
this._localDelayOffset = null;
this._pausedDelay = null;
}
/**
* Allows the animatable to blend with current running animations
* @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-blending
* @param blendingSpeed defines the blending speed to use
*/
enableBlending(blendingSpeed) {
const runtimeAnimations = this._runtimeAnimations;
for (let index = 0; index < runtimeAnimations.length; index++) {
runtimeAnimations[index].animation.enableBlending = true;
runtimeAnimations[index].animation.blendingSpeed = blendingSpeed;
}
}
/**
* Disable animation blending
* @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-blending
*/
disableBlending() {
const runtimeAnimations = this._runtimeAnimations;
for (let index = 0; index < runtimeAnimations.length; index++) {
runtimeAnimations[index].animation.enableBlending = false;
}
}
/**
* Jump directly to a given frame
* @param frame defines the frame to jump to
* @param useWeight defines whether the animation weight should be applied to the image to be jumped to (false by default)
*/
goToFrame(frame, useWeight = false) {
const runtimeAnimations = this._runtimeAnimations;
if (runtimeAnimations[0]) {
const fps = runtimeAnimations[0].animation.framePerSecond;
this._frameToSyncFromJump = this._frameToSyncFromJump ?? runtimeAnimations[0].currentFrame;
const delay = this.speedRatio === 0 ? 0 : (((frame - this._frameToSyncFromJump) / fps) * 1000) / this.speedRatio;
this._manualJumpDelay = -delay;
}
for (let index = 0; index < runtimeAnimations.length; index++) {
runtimeAnimations[index].goToFrame(frame, useWeight ? this._weight : -1);
}
this._goToFrame = frame;
}
/**
* Returns true if the animations for this animatable are paused
*/
get paused() {
return this._paused;
}
/**
* Pause the animation
*/
pause() {
if (this._paused) {
return;
}
this._paused = true;
}
/**
* Restart the animation
*/
restart() {
this._paused = false;
}
_raiseOnAnimationEnd() {
if (this.onAnimationEnd) {
this.onAnimationEnd();
}
this.onAnimationEndObservable.notifyObservers(this);
}
/**
* Stop and delete the current animation
* @param animationName defines a string used to only stop some of the runtime animations instead of all
* @param targetMask a function that determines if the animation should be stopped based on its target (all animations will be stopped if both this and animationName are empty)
* @param useGlobalSplice if true, the animatables will be removed by the caller of this function (false by default)
* @param skipOnAnimationEnd defines if the system should not raise onAnimationEnd. Default is false
*/
stop(animationName, targetMask, useGlobalSplice = false, skipOnAnimationEnd = false) {
if (animationName || targetMask) {
const idx = this._scene._activeAnimatables.indexOf(this);
if (idx > -1) {
const runtimeAnimations = this._runtimeAnimations;
for (let index = runtimeAnimations.length - 1; index >= 0; index--) {
const runtimeAnimation = runtimeAnimations[index];
if (animationName && runtimeAnimation.animation.name != animationName) {
continue;
}
if (targetMask && !targetMask(runtimeAnimation.target)) {
continue;
}
runtimeAnimation.dispose();
runtimeAnimations.splice(index, 1);
}
if (runtimeAnimations.length == 0) {
if (!useGlobalSplice) {
this._scene._activeAnimatables.splice(idx, 1);
}
if (!skipOnAnimationEnd) {
this._raiseOnAnimationEnd();
}
}
}
}
else {
const index = this._scene._activeAnimatables.indexOf(this);
if (index > -1) {
if (!useGlobalSplice) {
this._scene._activeAnimatables.splice(index, 1);
}
const runtimeAnimations = this._runtimeAnimations;
for (let index = 0; index < runtimeAnimations.length; index++) {
runtimeAnimations[index].dispose();
}
this._runtimeAnimations.length = 0;
if (!skipOnAnimationEnd) {
this._raiseOnAnimationEnd();
}
}
}
}
/**
* Wait asynchronously for the animation to end
* @returns a promise which will be fulfilled when the animation ends
*/
waitAsync() {
return new Promise((resolve) => {
this.onAnimationEndObservable.add(() => {
resolve(this);
}, undefined, undefined, this, true);
});
}
/**
* @internal
*/
_animate(delay) {
if (this._paused) {
this.animationStarted = false;
if (this._pausedDelay === null) {
this._pausedDelay = delay;
}
return true;
}
if (this._localDelayOffset === null) {
this._localDelayOffset = delay;
this._pausedDelay = null;
}
else if (this._pausedDelay !== null) {
this._localDelayOffset += delay - this._pausedDelay;
this._pausedDelay = null;
}
if (this._manualJumpDelay !== null) {
this._localDelayOffset += this._manualJumpDelay;
this._manualJumpDelay = null;
this._frameToSyncFromJump = null;
}
this._goToFrame = null;
if (this._weight === 0 && this._previousWeight === 0) {
// We consider that an animatable with a weight === 0 is "actively" paused
return true;
}
this._previousWeight = this._weight;
// Animating
let running = false;
const runtimeAnimations = this._runtimeAnimations;
let index;
for (index = 0; index < runtimeAnimations.length; index++) {
const animation = runtimeAnimations[index];
const isRunning = animation.animate(delay - this._localDelayOffset, this.fromFrame, this.toFrame, this.loopAnimation, this._speedRatio, this._weight);
running = running || isRunning;
}
this.animationStarted = running;
if (!running) {
if (this.disposeOnEnd) {
// Remove from active animatables
index = this._scene._activeAnimatables.indexOf(this);
this._scene._activeAnimatables.splice(index, 1);
// Dispose all runtime animations
for (index = 0; index < runtimeAnimations.length; index++) {
runtimeAnimations[index].dispose();
}
}
this._raiseOnAnimationEnd();
if (this.disposeOnEnd) {
this.onAnimationEnd = null;
this.onAnimationLoop = null;
this.onAnimationLoopObservable.clear();
this.onAnimationEndObservable.clear();
}
}
return running;
}
}
/** @internal */
function ProcessLateAnimationBindingsForMatrices(holder) {
if (holder.totalWeight === 0 && holder.totalAdditiveWeight === 0) {
return holder.originalValue;
}
let normalizer = 1.0;
const finalPosition = TmpVectors.Vector3[0];
const finalScaling = TmpVectors.Vector3[1];
const finalQuaternion = TmpVectors.Quaternion[0];
let startIndex = 0;
const originalAnimation = holder.animations[0];
const originalValue = holder.originalValue;
let scale = 1;
let skipOverride = false;
if (holder.totalWeight < 1.0) {
// We need to mix the original value in
scale = 1.0 - holder.totalWeight;
originalValue.decompose(finalScaling, finalQuaternion, finalPosition);
}
else {
startIndex = 1;
// We need to normalize the weights
normalizer = holder.totalWeight;
scale = originalAnimation.weight / normalizer;
if (scale == 1) {
if (holder.totalAdditiveWeight) {
skipOverride = true;
}
else {
return originalAnimation.currentValue;
}
}
originalAnimation.currentValue.decompose(finalScaling, finalQuaternion, finalPosition);
}
// Add up the override animations
if (!skipOverride) {
finalScaling.scaleInPlace(scale);
finalPosition.scaleInPlace(scale);
finalQuaternion.scaleInPlace(scale);
for (let animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
const runtimeAnimation = holder.animations[animIndex];
if (runtimeAnimation.weight === 0) {
continue;
}
scale = runtimeAnimation.weight / normalizer;
const currentPosition = TmpVectors.Vector3[2];
const currentScaling = TmpVectors.Vector3[3];
const currentQuaternion = TmpVectors.Quaternion[1];
runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
currentScaling.scaleAndAddToRef(scale, finalScaling);
currentQuaternion.scaleAndAddToRef(Quaternion.Dot(finalQuaternion, currentQuaternion) > 0 ? scale : -scale, finalQuaternion);
currentPosition.scaleAndAddToRef(scale, finalPosition);
}
finalQuaternion.normalize();
}
// Add up the additive animations
for (let animIndex = 0; animIndex < holder.additiveAnimations.length; animIndex++) {
const runtimeAnimation = holder.additiveAnimations[animIndex];
if (runtimeAnimation.weight === 0) {
continue;
}
const currentPosition = TmpVectors.Vector3[2];
const currentScaling = TmpVectors.Vector3[3];
const currentQuaternion = TmpVectors.Quaternion[1];
runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
currentScaling.multiplyToRef(finalScaling, currentScaling);
Vector3.LerpToRef(finalScaling, currentScaling, runtimeAnimation.weight, finalScaling);
finalQuaternion.multiplyToRef(currentQuaternion, currentQuaternion);
Quaternion.SlerpToRef(finalQuaternion, currentQuaternion, runtimeAnimation.weight, finalQuaternion);
currentPosition.scaleAndAddToRef(runtimeAnimation.weight, finalPosition);
}
const workValue = originalAnimation ? originalAnimation._animationState.workValue : TmpVectors.Matrix[0].clone();
Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, workValue);
return workValue;
}
/** @internal */
function ProcessLateAnimationBindingsForQuaternions(holder, refQuaternion) {
if (holder.totalWeight === 0 && holder.totalAdditiveWeight === 0) {
return refQuaternion;
}
const originalAnimation = holder.animations[0];
const originalValue = holder.originalValue;
let cumulativeQuaternion = refQuaternion;
if (holder.totalWeight === 0 && holder.totalAdditiveWeight > 0) {
cumulativeQuaternion.copyFrom(originalValue);
}
else if (holder.animations.length === 1) {
Quaternion.SlerpToRef(originalValue, originalAnimation.currentValue, Math.min(1.0, holder.totalWeight), cumulativeQuaternion);
if (holder.totalAdditiveWeight === 0) {
return cumulativeQuaternion;
}
}
else if (holder.animations.length > 1) {
// Add up the override animations
let normalizer = 1.0;
let quaternions;
let weights;
if (holder.totalWeight < 1.0) {
const scale = 1.0 - holder.totalWeight;
quaternions = [];
weights = [];
quaternions.push(originalValue);
weights.push(scale);
}
else {
if (holder.animations.length === 2) {
// Slerp as soon as we can
Quaternion.SlerpToRef(holder.animations[0].currentValue, holder.animations[1].currentValue, holder.animations[1].weight / holder.totalWeight, refQuaternion);
if (holder.totalAdditiveWeight === 0) {
return refQuaternion;
}
}
quaternions = [];
weights = [];
normalizer = holder.totalWeight;
}
for (let animIndex = 0; animIndex < holder.animations.length; animIndex++) {
const runtimeAnimation = holder.animations[animIndex];
quaternions.push(runtimeAnimation.currentValue);
weights.push(runtimeAnimation.weight / normalizer);
}
// https://gamedev.stackexchange.com/questions/62354/method-for-interpolation-between-3-quaternions
let cumulativeAmount = 0;
for (let index = 0; index < quaternions.length;) {
if (!index) {
Quaternion.SlerpToRef(quaternions[index], quaternions[index + 1], weights[index + 1] / (weights[index] + weights[index + 1]), refQuaternion);
cumulativeQuaternion = refQuaternion;
cumulativeAmount = weights[index] + weights[index + 1];
index += 2;
continue;
}
cumulativeAmount += weights[index];
Quaternion.SlerpToRef(cumulativeQuaternion, quaternions[index], weights[index] / cumulativeAmount, cumulativeQuaternion);
index++;
}
}
// Add up the additive animations
for (let animIndex = 0; animIndex < holder.additiveAnimations.length; animIndex++) {
const runtimeAnimation = holder.additiveAnimations[animIndex];
if (runtimeAnimation.weight === 0) {
continue;
}
cumulativeQuaternion.multiplyToRef(runtimeAnimation.currentValue, TmpVectors.Quaternion[0]);
Quaternion.SlerpToRef(cumulativeQuaternion, TmpVectors.Quaternion[0], runtimeAnimation.weight, cumulativeQuaternion);
}
return cumulativeQuaternion;
}
/** @internal */
function ProcessLateAnimationBindings(scene) {
if (!scene._registeredForLateAnimationBindings.length) {
return;
}
for (let index = 0; index < scene._registeredForLateAnimationBindings.length; index++) {
const target = scene._registeredForLateAnimationBindings.data[index];
for (const path in target._lateAnimationHolders) {
const holder = target._lateAnimationHolders[path];
const originalAnimation = holder.animations[0];
const originalValue = holder.originalValue;
if (originalValue === undefined || originalValue === null) {
continue;
}
const matrixDecomposeMode = Animation.AllowMatrixDecomposeForInterpolation && originalValue.m; // ie. data is matrix
let finalValue = target[path];
if (matrixDecomposeMode) {
finalValue = ProcessLateAnimationBindingsForMatrices(holder);
}
else {
const quaternionMode = originalValue.w !== undefined;
if (quaternionMode) {
finalValue = ProcessLateAnimationBindingsForQuaternions(holder, finalValue || Quaternion.Identity());
}
else {
let startIndex = 0;
let normalizer = 1.0;
const originalAnimationIsLoopRelativeFromCurrent = originalAnimation && originalAnimation._animationState.loopMode === Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT;
if (holder.totalWeight < 1.0) {
// We need to mix the original value in
if (originalAnimationIsLoopRelativeFromCurrent) {
finalValue = originalValue.clone ? originalValue.clone() : originalValue;
}
else if (originalAnimation && originalValue.scale) {
finalValue = originalValue.scale(1.0 - holder.totalWeight);
}
else if (originalAnimation) {
finalValue = originalValue * (1.0 - holder.totalWeight);
}
else if (originalValue.clone) {
finalValue = originalValue.clone();
}
else {
finalValue = originalValue;
}
}
else if (originalAnimation) {
// We need to normalize the weights
normalizer = holder.totalWeight;
const scale = originalAnimation.weight / normalizer;
if (scale !== 1) {
if (originalAnimation.currentValue.scale) {
finalValue = originalAnimation.currentValue.scale(scale);
}
else {
finalValue = originalAnimation.currentValue * scale;
}
}
else {
finalValue = originalAnimation.currentValue;
}
if (originalAnimationIsLoopRelativeFromCurrent) {
if (finalValue.addToRef) {
finalValue.addToRef(originalValue, finalValue);
}
else {
finalValue += originalValue;
}
}
startIndex = 1;
}
// Add up the override animations
for (let animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
const runtimeAnimation = holder.animations[animIndex];
const scale = runtimeAnimation.weight / normalizer;
if (!scale) {
continue;
}
else if (runtimeAnimation.currentValue.scaleAndAddToRef) {
runtimeAnimation.currentValue.scaleAndAddToRef(scale, finalValue);
}
else {
finalValue += runtimeAnimation.currentValue * scale;
}
}
// Add up the additive animations
for (let animIndex = 0; animIndex < holder.additiveAnimations.length; animIndex++) {
const runtimeAnimation = holder.additiveAnimations[animIndex];
const scale = runtimeAnimation.weight;
if (!scale) {
continue;
}
else if (runtimeAnimation.currentValue.scaleAndAddToRef) {
runtimeAnimation.currentValue.scaleAndAddToRef(scale, finalValue);
}
else {
finalValue += runtimeAnimation.currentValue * scale;
}
}
}
}
target[path] = finalValue;
}
target._lateAnimationHolders = {};
}
scene._registeredForLateAnimationBindings.reset();
}
/** @internal */
export function RegisterTargetForLateAnimationBinding(scene, runtimeAnimation, originalValue) {
const target = runtimeAnimation.target;
scene._registeredForLateAnimationBindings.pushNoDuplicate(target);
if (!target._lateAnimationHolders) {
target._lateAnimationHolders = {};
}
if (!target._lateAnimationHolders[runtimeAnimation.targetPath]) {
target._lateAnimationHolders[runtimeAnimation.targetPath] = {
totalWeight: 0,
totalAdditiveWeight: 0,
animations: [],
additiveAnimations: [],
originalValue: originalValue,
};
}
if (runtimeAnimation.isAdditive) {
target._lateAnimationHolders[runtimeAnimation.targetPath].additiveAnimations.push(runtimeAnimation);
target._lateAnimationHolders[runtimeAnimation.targetPath].totalAdditiveWeight += runtimeAnimation.weight;
}
else {
target._lateAnimationHolders[runtimeAnimation.targetPath].animations.push(runtimeAnimation);
target._lateAnimationHolders[runtimeAnimation.targetPath].totalWeight += runtimeAnimation.weight;
}
}
/**
* Initialize all the inter dependecies between the animations and Scene and Bone
* @param sceneClass defines the scene prototype to use
* @param boneClass defines the bone prototype to use
*/
export function AddAnimationExtensions(sceneClass, boneClass) {
if (boneClass) {
boneClass.prototype.copyAnimationRange = function (source, rangeName, frameOffset, rescaleAsRequired = false, skelDimensionsRatio = null) {
// all animation may be coming from a library skeleton, so may need to create animation
if (this.animations.length === 0) {
this.animations.push(new Animation(this.name, "_matrix", source.animations[0].framePerSecond, Animation.ANIMATIONTYPE_MATRIX, 0));
this.animations[0].setKeys([]);
}
// get animation info / verify there is such a range from the source bone
const sourceRange = source.animations[0].getRange(rangeName);
if (!sourceRange) {
return false;
}
const from = sourceRange.from;
const to = sourceRange.to;
const sourceKeys = source.animations[0].getKeys();
// rescaling prep
const sourceBoneLength = source.length;
const sourceParent = source.getParent();
const parent = this.getParent();
const parentScalingReqd = rescaleAsRequired && sourceParent && sourceBoneLength && this.length && sourceBoneLength !== this.length;
const parentRatio = parentScalingReqd && parent && sourceParent ? parent.length / sourceParent.length : 1;
const dimensionsScalingReqd = rescaleAsRequired && !parent && skelDimensionsRatio && (skelDimensionsRatio.x !== 1 || skelDimensionsRatio.y !== 1 || skelDimensionsRatio.z !== 1);
const destKeys = this.animations[0].getKeys();
// loop vars declaration
let orig;
let origTranslation;
let mat;
for (let key = 0, nKeys = sourceKeys.length; key < nKeys; key++) {
orig = sourceKeys[key];
if (orig.frame >= from && orig.frame <= to) {
if (rescaleAsRequired) {
mat = orig.value.clone();
// scale based on parent ratio, when bone has parent
if (parentScalingReqd) {
origTranslation = mat.getTranslation();
mat.setTranslation(origTranslation.scaleInPlace(parentRatio));
// scale based on skeleton dimension ratio when root bone, and value is passed
}
else if (dimensionsScalingReqd && skelDimensionsRatio) {
origTranslation = mat.getTranslation();
mat.setTranslation(origTranslation.multiplyInPlace(skelDimensionsRatio));
// use original when root bone, and no data for skelDimensionsRatio
}
else {
mat = orig.value;
}
}
else {
mat = orig.value;
}
destKeys.push({ frame: orig.frame + frameOffset, value: mat });
}
}
this.animations[0].createRange(rangeName, from + frameOffset, to + frameOffset);
return true;
};
}
if (!sceneClass) {
return;
}
sceneClass.prototype._animate = function (customDeltaTime) {
if (!this.animationsEnabled) {
return;
}
// Getting time
const now = PrecisionDate.Now;
if (!this._animationTimeLast) {
if (this._pendingData.length > 0) {
return;
}
this._animationTimeLast = now;
}
this.deltaTime = customDeltaTime !== undefined ? customDeltaTime : this.useConstantAnimationDeltaTime ? 16.0 : (now - this._animationTimeLast) * this.animationTimeScale;
this._animationTimeLast = now;
const animatables = this._activeAnimatables;
if (animatables.length === 0) {
return;
}
this._animationTime += this.deltaTime;
const animationTime = this._animationTime;
for (let index = 0; index < animatables.length; index++) {
const animatable = animatables[index];
if (!animatable._animate(animationTime) && animatable.disposeOnEnd) {
index--; // Array was updated
}
}
// Late animation bindings
ProcessLateAnimationBindings(this);
};
sceneClass.prototype.sortActiveAnimatables = function () {
this._activeAnimatables.sort((a, b) => {
return a.playOrder - b.playOrder;
});
};
sceneClass.prototype.beginWeightedAnimation = function (target, from, to, weight = 1.0, loop, speedRatio = 1.0, onAnimationEnd, animatable, targetMask, onAnimationLoop, isAdditive = false) {
const returnedAnimatable = this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, false, targetMask, onAnimationLoop, isAdditive);
returnedAnimatable.weight = weight;
return returnedAnimatable;
};
sceneClass.prototype.beginAnimation = function (target, from, to, loop, speedRatio = 1.0, onAnimationEnd, animatable, stopCurrent = true, targetMask, onAnimationLoop, isAdditive = false) {
// get speed speedRatio, to and from, based on the sign and value(s)
if (speedRatio < 0) {
const tmp = from;
from = to;
to = tmp;
speedRatio = -speedRatio;
}
// if from > to switch speed ratio
if (from > to) {
speedRatio = -speedRatio;
}
if (stopCurrent) {
this.stopAnimation(target, undefined, targetMask);
}
if (!animatable) {
animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, undefined, onAnimationLoop, isAdditive);
}
const shouldRunTargetAnimations = targetMask ? targetMask(target) : true;
// Local animations
if (target.animations && shouldRunTargetAnimations) {
animatable.appendAnimations(target, target.animations);
}
// Children animations
if (target.getAnimatables) {
const animatables = target.getAnimatables();
for (let index = 0; index < animatables.length; index++) {
this.beginAnimation(animatables[index], from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, onAnimationLoop);
}
}
animatable.reset();
return animatable;
};
sceneClass.prototype.beginHierarchyAnimation = function (target, directDescendantsOnly, from, to, loop, speedRatio = 1.0, onAnimationEnd, animatable, stopCurrent = true, targetMask, onAnimationLoop, isAdditive = false) {
const children = target.getDescendants(directDescendantsOnly);
const result = [];
result.push(this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, undefined, isAdditive));
for (const child of children) {
result.push(this.beginAnimation(child, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, undefined, isAdditive));
}
return result;
};
sceneClass.prototype.beginDirectAnimation = function (target, animations, from, to, loop, speedRatio = 1.0, onAnimationEnd, onAnimationLoop, isAdditive = false) {
// get speed speedRatio, to and from, based on the sign and value(s)
if (speedRatio < 0) {
const tmp = from;
from = to;
to = tmp;
speedRatio = -speedRatio;
}
// if from > to switch speed ratio
if (from > to) {
speedRatio = -speedRatio;
}
const animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, animations, onAnimationLoop, isAdditive);
return animatable;
};
sceneClass.prototype.beginDirectHierarchyAnimation = function (target, directDescendantsOnly, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop, isAdditive = false) {
const children = target.getDescendants(directDescendantsOnly);
const result = [];
result.push(this.beginDirectAnimation(target, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop, isAdditive));
for (const child of children) {
result.push(this.beginDirectAnimation(child, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop, isAdditive));
}
return result;
};
sceneClass.prototype.getAnimatableByTarget = function (target) {
for (let index = 0; index < this._activeAnimatables.length; index++) {
if (this._activeAnimatables[index].target === target) {
return this._activeAnimatables[index];
}
}
return null;
};
sceneClass.prototype.getAllAnimatablesByTarget = function (target) {
const result = [];
for (let index = 0; index < this._activeAnimatables.length; index++) {
if (this._activeAnimatables[index].target === target) {
result.push(this._activeAnimatables[index]);
}
}
return result;
};
sceneClass.prototype.stopAnimation = function (target, animationName, targetMask) {
const animatables = this.getAllAnimatablesByTarget(target);
for (const animatable of animatables) {
animatable.stop(animationName, targetMask);
}
};
sceneClass.prototype.stopAllAnimations = function () {
if (this._activeAnimatables) {
for (let i = 0; i < this._activeAnimatables.length; i++) {
this._activeAnimatables[i].stop(undefined, undefined, true);
}
this._activeAnimatables.length = 0;
}
for (const group of this.animationGroups) {
group.stop();
}
};
}
//# sourceMappingURL=animatable.core.js.map