| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994 |
- import { Animation } from "./animation";
- import { RuntimeAnimation } from "./runtimeAnimation";
- import { Nullable } from "../types";
- import { Observable } from "../Misc/observable";
- import { Scene } from "../scene";
- import { Matrix, Quaternion, Tmp, Vector3 } from '../Maths/math';
- 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: Animatable;
- /**
- * 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(): 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];
- this._runtimeAnimations.push(new RuntimeAnimation(target, animation, this._scene, this));
- }
- }
- /**
- * 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 = adjustTime * 1000 / (fps * this.speedRatio);
- 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,
- () => {
- this.onAnimationLoopObservable.notifyObservers(this);
- if (this.onAnimationLoop) {
- this.onAnimationLoop();
- }
- }
- );
- 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>;
- /**
- * 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)
- */
- stopAnimation(target: any, animationName?: string, targetMask?: (target: any) => boolean): void;
- /**
- * Stops and removes all animations that have been applied to the scene
- */
- stopAllAnimations(): void;
- }
- }
- Scene.prototype._animate = function(): void {
- if (!this.animationsEnabled || this._activeAnimatables.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;
- this._animationTimeLast = now;
- for (var index = 0; index < this._activeAnimatables.length; index++) {
- this._activeAnimatables[index]._animate(this._animationTime);
- }
- // 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 = Tmp.Vector3[0];
- let finalScaling = Tmp.Vector3[1];
- let finalQuaternion = Tmp.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 = Tmp.Vector3[2];
- let currentScaling = Tmp.Vector3[3];
- let currentQuaternion = Tmp.Quaternion[1];
- runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
- currentScaling.scaleAndAddToRef(scale, finalScaling);
- currentQuaternion.scaleAndAddToRef(scale, finalQuaternion);
- currentPosition.scaleAndAddToRef(scale, finalPosition);
- }
- Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, originalAnimation._workValue);
- return originalAnimation._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;
- };
|