1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000 |
- import { Animation } from "./animation";
- import { RuntimeAnimation } from "./runtimeAnimation";
- import { Nullable } from "../types";
- import { Observable } from "../Misc/observable";
- import { Scene } from "../scene";
- import { Matrix, Quaternion, Vector3, TmpVectors } from '../Maths/math.vector';
- import { PrecisionDate } from '../Misc/precisionDate';
- import { Bone } from '../Bones/bone';
- import { Node } from "../node";
- /**
- * Class used to store an actual running animation
- */
- export class Animatable {
- private _localDelayOffset: Nullable<number> = null;
- private _pausedDelay: Nullable<number> = null;
- private _runtimeAnimations = new Array<RuntimeAnimation>();
- private _paused = false;
- private _scene: Scene;
- private _speedRatio = 1;
- private _weight = -1.0;
- private _syncRoot: Nullable<Animatable> = 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)
- */
- public disposeOnEnd = true;
- /**
- * Gets a boolean indicating if the animation has started
- */
- public animationStarted = false;
- /**
- * Observer raised when the animation ends
- */
- public onAnimationEndObservable = new Observable<Animatable>();
- /**
- * Observer raised when the animation loops
- */
- public onAnimationLoopObservable = new Observable<Animatable>();
- /**
- * Gets the root Animatable used to synchronize and normalize animations
- */
- public get syncRoot(): Nullable<Animatable> {
- return this._syncRoot;
- }
- /**
- * Gets the current frame of the first RuntimeAnimation
- * Used to synchronize Animatables
- */
- public get masterFrame(): number {
- 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)
- */
- public get weight(): number {
- return this._weight;
- }
- public set weight(value: number) {
- 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)
- */
- public get speedRatio(): number {
- return this._speedRatio;
- }
- public set speedRatio(value: number) {
- for (var index = 0; index < this._runtimeAnimations.length; index++) {
- var animation = this._runtimeAnimations[index];
- animation._prepareForSpeedRatioChange(value);
- }
- this._speedRatio = value;
- }
- /**
- * 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
- */
- constructor(scene: Scene,
- /** defines the target object */
- public target: any,
- /** defines the starting frame number (default is 0) */
- public fromFrame: number = 0,
- /** defines the ending frame number (default is 100) */
- public toFrame: number = 100,
- /** defines if the animation must loop (default is false) */
- public loopAnimation: boolean = false,
- speedRatio: number = 1.0,
- /** defines a callback to call when animation ends if it is not looping */
- public onAnimationEnd?: Nullable<() => void>,
- animations?: Animation[],
- /** defines a callback to call when animation loops */
- public onAnimationLoop?: Nullable<() => void>) {
- 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
- * @returns the current Animatable
- */
- public syncWith(root: Animatable): Animatable {
- this._syncRoot = root;
- if (root) {
- // Make sure this animatable will animate after the root
- let 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
- */
- public getAnimations(): RuntimeAnimation[] {
- 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
- */
- public appendAnimations(target: any, animations: Animation[]): void {
- for (var index = 0; index < animations.length; index++) {
- var animation = animations[index];
- let 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 propertyu to look for
- * @returns null or the source animation for the given property
- */
- public getAnimationByTargetProperty(property: string): Nullable<Animation> {
- var runtimeAnimations = this._runtimeAnimations;
- for (var 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 propertyu to look for
- * @returns null or the runtime animation for the given property
- */
- public getRuntimeAnimationByTargetProperty(property: string): Nullable<RuntimeAnimation> {
- var runtimeAnimations = this._runtimeAnimations;
- for (var index = 0; index < runtimeAnimations.length; index++) {
- if (runtimeAnimations[index].animation.targetProperty === property) {
- return runtimeAnimations[index];
- }
- }
- return null;
- }
- /**
- * Resets the animatable to its original state
- */
- public reset(): void {
- var runtimeAnimations = this._runtimeAnimations;
- for (var 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 http://doc.babylonjs.com/babylon101/animations#animation-blending
- * @param blendingSpeed defines the blending speed to use
- */
- public enableBlending(blendingSpeed: number): void {
- var runtimeAnimations = this._runtimeAnimations;
- for (var index = 0; index < runtimeAnimations.length; index++) {
- runtimeAnimations[index].animation.enableBlending = true;
- runtimeAnimations[index].animation.blendingSpeed = blendingSpeed;
- }
- }
- /**
- * Disable animation blending
- * @see http://doc.babylonjs.com/babylon101/animations#animation-blending
- */
- public disableBlending(): void {
- var runtimeAnimations = this._runtimeAnimations;
- for (var 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
- */
- public goToFrame(frame: number): void {
- var runtimeAnimations = this._runtimeAnimations;
- if (runtimeAnimations[0]) {
- var fps = runtimeAnimations[0].animation.framePerSecond;
- var currentFrame = runtimeAnimations[0].currentFrame;
- var adjustTime = frame - currentFrame;
- var delay = this.speedRatio !== 0 ? adjustTime * 1000 / (fps * this.speedRatio) : 0;
- if (this._localDelayOffset === null) {
- this._localDelayOffset = 0;
- }
- this._localDelayOffset -= delay;
- }
- for (var index = 0; index < runtimeAnimations.length; index++) {
- runtimeAnimations[index].goToFrame(frame);
- }
- }
- /**
- * Pause the animation
- */
- public pause(): void {
- if (this._paused) {
- return;
- }
- this._paused = true;
- }
- /**
- * Restart the animation
- */
- public restart(): void {
- this._paused = false;
- }
- private _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)
- */
- public stop(animationName?: string, targetMask?: (target: any) => boolean): void {
- if (animationName || targetMask) {
- var idx = this._scene._activeAnimatables.indexOf(this);
- if (idx > -1) {
- var runtimeAnimations = this._runtimeAnimations;
- for (var 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) {
- this._scene._activeAnimatables.splice(idx, 1);
- this._raiseOnAnimationEnd();
- }
- }
- } else {
- var index = this._scene._activeAnimatables.indexOf(this);
- if (index > -1) {
- this._scene._activeAnimatables.splice(index, 1);
- var runtimeAnimations = this._runtimeAnimations;
- for (var index = 0; index < runtimeAnimations.length; index++) {
- runtimeAnimations[index].dispose();
- }
- this._raiseOnAnimationEnd();
- }
- }
- }
- /**
- * Wait asynchronously for the animation to end
- * @returns a promise which will be fullfilled when the animation ends
- */
- public waitAsync(): Promise<Animatable> {
- return new Promise((resolve, reject) => {
- this.onAnimationEndObservable.add(() => {
- resolve(this);
- }, undefined, undefined, this, true);
- });
- }
- /** @hidden */
- public _animate(delay: number): boolean {
- 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._weight === 0) { // We consider that an animation with a weight === 0 is "actively" paused
- return true;
- }
- // Animating
- var running = false;
- var runtimeAnimations = this._runtimeAnimations;
- var index: number;
- for (index = 0; index < runtimeAnimations.length; index++) {
- var animation = runtimeAnimations[index];
- var 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;
- }
- }
- declare module "../scene" {
- export interface Scene {
- /** @hidden */
- _registerTargetForLateAnimationBinding(runtimeAnimation: RuntimeAnimation, originalValue: any): void;
- /** @hidden */
- _processLateAnimationBindingsForMatrices(holder: {
- totalWeight: number,
- animations: RuntimeAnimation[],
- originalValue: Matrix
- }): any;
- /** @hidden */
- _processLateAnimationBindingsForQuaternions(holder: {
- totalWeight: number,
- animations: RuntimeAnimation[],
- originalValue: Quaternion
- }, refQuaternion: Quaternion): Quaternion;
- /** @hidden */
- _processLateAnimationBindings(): void;
- /**
- * Will start the animation sequence of a given target
- * @param target defines the target
- * @param from defines from which frame should animation start
- * @param to defines until which frame should animation run.
- * @param weight defines the weight to apply to the animation (1.0 by default)
- * @param loop defines if the animation loops
- * @param speedRatio defines the speed in which to run the animation (1.0 by default)
- * @param onAnimationEnd defines the function to be executed when the animation ends
- * @param animatable defines an animatable object. If not provided a new one will be created from the given params
- * @param targetMask defines if the target should be animated if animations are present (this is called recursively on descendant animatables regardless of return value)
- * @param onAnimationLoop defines the callback to call when an animation loops
- * @returns the animatable object created for this animation
- */
- beginWeightedAnimation(target: any, from: number, to: number, weight: number, loop?: boolean, speedRatio?: number,
- onAnimationEnd?: () => void, animatable?: Animatable, targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable;
- /**
- * Will start the animation sequence of a given target
- * @param target defines the target
- * @param from defines from which frame should animation start
- * @param to defines until which frame should animation run.
- * @param loop defines if the animation loops
- * @param speedRatio defines the speed in which to run the animation (1.0 by default)
- * @param onAnimationEnd defines the function to be executed when the animation ends
- * @param animatable defines an animatable object. If not provided a new one will be created from the given params
- * @param stopCurrent defines if the current animations must be stopped first (true by default)
- * @param targetMask defines if the target should be animate if animations are present (this is called recursively on descendant animatables regardless of return value)
- * @param onAnimationLoop defines the callback to call when an animation loops
- * @returns the animatable object created for this animation
- */
- beginAnimation(target: any, from: number, to: number, loop?: boolean, speedRatio?: number,
- onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent?: boolean,
- targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable;
- /**
- * Will start the animation sequence of a given target and its hierarchy
- * @param target defines the target
- * @param directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
- * @param from defines from which frame should animation start
- * @param to defines until which frame should animation run.
- * @param loop defines if the animation loops
- * @param speedRatio defines the speed in which to run the animation (1.0 by default)
- * @param onAnimationEnd defines the function to be executed when the animation ends
- * @param animatable defines an animatable object. If not provided a new one will be created from the given params
- * @param stopCurrent defines if the current animations must be stopped first (true by default)
- * @param targetMask defines if the target should be animated if animations are present (this is called recursively on descendant animatables regardless of return value)
- * @param onAnimationLoop defines the callback to call when an animation loops
- * @returns the list of created animatables
- */
- beginHierarchyAnimation(target: any, directDescendantsOnly: boolean, from: number, to: number, loop?: boolean, speedRatio?: number,
- onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent?: boolean,
- targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable[];
- /**
- * Begin a new animation on a given node
- * @param target defines the target where the animation will take place
- * @param animations defines the list of animations to start
- * @param from defines the initial value
- * @param to defines the final value
- * @param loop defines if you want animation to loop (off by default)
- * @param speedRatio defines the speed ratio to apply to all animations
- * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
- * @param onAnimationLoop defines the callback to call when an animation loops
- * @returns the list of created animatables
- */
- beginDirectAnimation(target: any, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable;
- /**
- * Begin a new animation on a given node and its hierarchy
- * @param target defines the root node where the animation will take place
- * @param directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
- * @param animations defines the list of animations to start
- * @param from defines the initial value
- * @param to defines the final value
- * @param loop defines if you want animation to loop (off by default)
- * @param speedRatio defines the speed ratio to apply to all animations
- * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
- * @param onAnimationLoop defines the callback to call when an animation loops
- * @returns the list of animatables created for all nodes
- */
- beginDirectHierarchyAnimation(target: Node, directDescendantsOnly: boolean, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable[];
- /**
- * Gets the animatable associated with a specific target
- * @param target defines the target of the animatable
- * @returns the required animatable if found
- */
- getAnimatableByTarget(target: any): Nullable<Animatable>;
- /**
- * Gets all animatables associated with a given target
- * @param target defines the target to look animatables for
- * @returns an array of Animatables
- */
- getAllAnimatablesByTarget(target: any): Array<Animatable>;
- /**
- * Stops and removes all animations that have been applied to the scene
- */
- stopAllAnimations(): void;
- }
- }
- Scene.prototype._animate = function(): void {
- if (!this.animationsEnabled) {
- return;
- }
- const animatables = this._activeAnimatables;
- if (animatables.length === 0) {
- return;
- }
- // Getting time
- var now = PrecisionDate.Now;
- if (!this._animationTimeLast) {
- if (this._pendingData.length > 0) {
- return;
- }
- this._animationTimeLast = now;
- }
- var deltaTime = this.useConstantAnimationDeltaTime ? 16.0 : (now - this._animationTimeLast) * this.animationTimeScale;
- this._animationTime += deltaTime;
- const animationTime = this._animationTime;
- this._animationTimeLast = now;
- for (let index = 0; index < animatables.length; index++) {
- let animatable = animatables[index];
- if (!animatable._animate(animationTime) && animatable.disposeOnEnd) {
- index--; // Array was updated
- }
- }
- // Late animation bindings
- this._processLateAnimationBindings();
- };
- Scene.prototype.beginWeightedAnimation = function(target: any, from: number, to: number, weight = 1.0, loop?: boolean, speedRatio: number = 1.0,
- onAnimationEnd?: () => void, animatable?: Animatable, targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable {
- let returnedAnimatable = this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, false, targetMask, onAnimationLoop);
- returnedAnimatable.weight = weight;
- return returnedAnimatable;
- };
- Scene.prototype.beginAnimation = function(target: any, from: number, to: number, loop?: boolean, speedRatio: number = 1.0,
- onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent = true,
- targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable {
- if (from > to && speedRatio > 0) {
- speedRatio *= -1;
- }
- if (stopCurrent) {
- this.stopAnimation(target, undefined, targetMask);
- }
- if (!animatable) {
- animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, undefined, onAnimationLoop);
- }
- const shouldRunTargetAnimations = targetMask ? targetMask(target) : true;
- // Local animations
- if (target.animations && shouldRunTargetAnimations) {
- animatable.appendAnimations(target, target.animations);
- }
- // Children animations
- if (target.getAnimatables) {
- var animatables = target.getAnimatables();
- for (var index = 0; index < animatables.length; index++) {
- this.beginAnimation(animatables[index], from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, onAnimationLoop);
- }
- }
- animatable.reset();
- return animatable;
- };
- Scene.prototype.beginHierarchyAnimation = function(target: any, directDescendantsOnly: boolean, from: number, to: number, loop?: boolean, speedRatio: number = 1.0,
- onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent = true,
- targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable[] {
- let children = target.getDescendants(directDescendantsOnly);
- let result = [];
- result.push(this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask));
- for (var child of children) {
- result.push(this.beginAnimation(child, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask));
- }
- return result;
- };
- Scene.prototype.beginDirectAnimation = function(target: any, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable {
- if (speedRatio === undefined) {
- speedRatio = 1.0;
- }
- var animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, animations, onAnimationLoop);
- return animatable;
- };
- Scene.prototype.beginDirectHierarchyAnimation = function(target: Node, directDescendantsOnly: boolean, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable[] {
- let children = target.getDescendants(directDescendantsOnly);
- let result = [];
- result.push(this.beginDirectAnimation(target, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop));
- for (var child of children) {
- result.push(this.beginDirectAnimation(child, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop));
- }
- return result;
- };
- Scene.prototype.getAnimatableByTarget = function(target: any): Nullable<Animatable> {
- for (var index = 0; index < this._activeAnimatables.length; index++) {
- if (this._activeAnimatables[index].target === target) {
- return this._activeAnimatables[index];
- }
- }
- return null;
- };
- Scene.prototype.getAllAnimatablesByTarget = function(target: any): Array<Animatable> {
- let result = [];
- for (var index = 0; index < this._activeAnimatables.length; index++) {
- if (this._activeAnimatables[index].target === target) {
- result.push(this._activeAnimatables[index]);
- }
- }
- return result;
- };
- /**
- * Will stop the animation of the given target
- * @param target - the target
- * @param animationName - the name of the animation to stop (all animations will be stopped if both this and targetMask are empty)
- * @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)
- */
- Scene.prototype.stopAnimation = function(target: any, animationName?: string, targetMask?: (target: any) => boolean): void {
- var animatables = this.getAllAnimatablesByTarget(target);
- for (var animatable of animatables) {
- animatable.stop(animationName, targetMask);
- }
- };
- /**
- * Stops and removes all animations that have been applied to the scene
- */
- Scene.prototype.stopAllAnimations = function(): void {
- if (this._activeAnimatables) {
- for (let i = 0; i < this._activeAnimatables.length; i++) {
- this._activeAnimatables[i].stop();
- }
- this._activeAnimatables = [];
- }
- for (var group of this.animationGroups) {
- group.stop();
- }
- };
- Scene.prototype._registerTargetForLateAnimationBinding = function(runtimeAnimation: RuntimeAnimation, originalValue: any): void {
- let target = runtimeAnimation.target;
- this._registeredForLateAnimationBindings.pushNoDuplicate(target);
- if (!target._lateAnimationHolders) {
- target._lateAnimationHolders = {};
- }
- if (!target._lateAnimationHolders[runtimeAnimation.targetPath]) {
- target._lateAnimationHolders[runtimeAnimation.targetPath] = {
- totalWeight: 0,
- animations: [],
- originalValue: originalValue
- };
- }
- target._lateAnimationHolders[runtimeAnimation.targetPath].animations.push(runtimeAnimation);
- target._lateAnimationHolders[runtimeAnimation.targetPath].totalWeight += runtimeAnimation.weight;
- };
- Scene.prototype._processLateAnimationBindingsForMatrices = function(holder: {
- totalWeight: number,
- animations: RuntimeAnimation[],
- originalValue: Matrix
- }): any {
- let normalizer = 1.0;
- let finalPosition = TmpVectors.Vector3[0];
- let finalScaling = TmpVectors.Vector3[1];
- let finalQuaternion = TmpVectors.Quaternion[0];
- let startIndex = 0;
- let originalAnimation = holder.animations[0];
- let originalValue = holder.originalValue;
- var scale = 1;
- if (holder.totalWeight < 1.0) {
- // We need to mix the original value in
- originalValue.decompose(finalScaling, finalQuaternion, finalPosition);
- scale = 1.0 - holder.totalWeight;
- } else {
- startIndex = 1;
- // We need to normalize the weights
- normalizer = holder.totalWeight;
- originalAnimation.currentValue.decompose(finalScaling, finalQuaternion, finalPosition);
- scale = originalAnimation.weight / normalizer;
- if (scale == 1) {
- return originalAnimation.currentValue;
- }
- }
- finalScaling.scaleInPlace(scale);
- finalPosition.scaleInPlace(scale);
- finalQuaternion.scaleInPlace(scale);
- for (var animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
- var runtimeAnimation = holder.animations[animIndex];
- var scale = runtimeAnimation.weight / normalizer;
- let currentPosition = TmpVectors.Vector3[2];
- let currentScaling = TmpVectors.Vector3[3];
- let currentQuaternion = TmpVectors.Quaternion[1];
- runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
- currentScaling.scaleAndAddToRef(scale, finalScaling);
- currentQuaternion.scaleAndAddToRef(scale, finalQuaternion);
- currentPosition.scaleAndAddToRef(scale, finalPosition);
- }
- let workValue = originalAnimation._animationState.workValue;
- Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, workValue);
- return workValue;
- };
- Scene.prototype._processLateAnimationBindingsForQuaternions = function(holder: {
- totalWeight: number,
- animations: RuntimeAnimation[],
- originalValue: Quaternion
- }, refQuaternion: Quaternion): Quaternion {
- let originalAnimation = holder.animations[0];
- let originalValue = holder.originalValue;
- if (holder.animations.length === 1) {
- Quaternion.SlerpToRef(originalValue, originalAnimation.currentValue, Math.min(1.0, holder.totalWeight), refQuaternion);
- return refQuaternion;
- }
- let normalizer = 1.0;
- let quaternions: Array<Quaternion>;
- let weights: Array<number>;
- if (holder.totalWeight < 1.0) {
- let 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);
- return refQuaternion;
- }
- quaternions = [];
- weights = [];
- normalizer = holder.totalWeight;
- }
- for (var animIndex = 0; animIndex < holder.animations.length; animIndex++) {
- let 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;
- let cumulativeQuaternion: Nullable<Quaternion> = null;
- for (var index = 0; index < quaternions.length;) {
- if (!cumulativeQuaternion) {
- 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++;
- }
- return cumulativeQuaternion!;
- };
- Scene.prototype._processLateAnimationBindings = function(): void {
- if (!this._registeredForLateAnimationBindings.length) {
- return;
- }
- for (var index = 0; index < this._registeredForLateAnimationBindings.length; index++) {
- var target = this._registeredForLateAnimationBindings.data[index];
- for (var path in target._lateAnimationHolders) {
- var holder = target._lateAnimationHolders[path];
- let originalAnimation: RuntimeAnimation = holder.animations[0];
- let originalValue = holder.originalValue;
- let matrixDecomposeMode = Animation.AllowMatrixDecomposeForInterpolation && originalValue.m; // ie. data is matrix
- let finalValue: any = target[path];
- if (matrixDecomposeMode) {
- finalValue = this._processLateAnimationBindingsForMatrices(holder);
- } else {
- let quaternionMode = originalValue.w !== undefined;
- if (quaternionMode) {
- finalValue = this._processLateAnimationBindingsForQuaternions(holder, finalValue || Quaternion.Identity());
- } else {
- let startIndex = 0;
- let normalizer = 1.0;
- if (holder.totalWeight < 1.0) {
- // We need to mix the original value in
- if (originalValue.scale) {
- finalValue = originalValue.scale(1.0 - holder.totalWeight);
- } else {
- finalValue = originalValue * (1.0 - holder.totalWeight);
- }
- } else {
- // We need to normalize the weights
- normalizer = holder.totalWeight;
- let 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;
- }
- startIndex = 1;
- }
- for (var animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
- var runtimeAnimation = holder.animations[animIndex];
- var scale = runtimeAnimation.weight / normalizer;
- if (runtimeAnimation.currentValue.scaleAndAddToRef) {
- runtimeAnimation.currentValue.scaleAndAddToRef(scale, finalValue);
- } else {
- finalValue += runtimeAnimation.currentValue * scale;
- }
- }
- }
- }
- target[path] = finalValue;
- }
- target._lateAnimationHolders = {};
- }
- this._registeredForLateAnimationBindings.reset();
- };
- declare module "../Bones/bone" {
- export interface Bone {
- /**
- * Copy an animation range from another bone
- * @param source defines the source bone
- * @param rangeName defines the range name to copy
- * @param frameOffset defines the frame offset
- * @param rescaleAsRequired defines if rescaling must be applied if required
- * @param skelDimensionsRatio defines the scaling ratio
- * @returns true if operation was successful
- */
- copyAnimationRange(source: Bone, rangeName: string, frameOffset: number, rescaleAsRequired: boolean, skelDimensionsRatio: Nullable<Vector3>): boolean;
- }
- }
- Bone.prototype.copyAnimationRange = function(source: Bone, rangeName: string, frameOffset: number, rescaleAsRequired = false, skelDimensionsRatio: Nullable<Vector3> = null): boolean {
- // 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
- var sourceRange = source.animations[0].getRange(rangeName);
- if (!sourceRange) {
- return false;
- }
- var from = sourceRange.from;
- var to = sourceRange.to;
- var sourceKeys = source.animations[0].getKeys();
- // rescaling prep
- var sourceBoneLength = source.length;
- var sourceParent = source.getParent();
- var parent = this.getParent();
- var parentScalingReqd = rescaleAsRequired && sourceParent && sourceBoneLength && this.length && sourceBoneLength !== this.length;
- var parentRatio = parentScalingReqd && parent && sourceParent ? parent.length / sourceParent.length : 1;
- var dimensionsScalingReqd = rescaleAsRequired && !parent && skelDimensionsRatio && (skelDimensionsRatio.x !== 1 || skelDimensionsRatio.y !== 1 || skelDimensionsRatio.z !== 1);
- var destKeys = this.animations[0].getKeys();
- // loop vars declaration
- var orig: { frame: number, value: Matrix };
- var origTranslation: Vector3;
- var mat: Matrix;
- for (var 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;
- };
|