Bläddra i källkod

Fix latest missing references

David Catuhe 6 år sedan
förälder
incheckning
b3a9937ef4

+ 1 - 1
loaders/src/glTF/2.0/Extensions/MSFT_audio_emitter.ts

@@ -2,7 +2,7 @@ import { Nullable } from "babylonjs/types";
 import { Vector3 } from "babylonjs/Maths/math";
 import { Tools } from "babylonjs/Misc/tools";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
-import { AnimationEvent } from "babylonjs/Animations/animation";
+import { AnimationEvent } from "babylonjs/Animations/animationEvent";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { Sound } from "babylonjs/Audio/sound";
 import { WeightedSound } from "babylonjs/Audio/weightedsound";

+ 2 - 2
loaders/src/glTF/2.0/glTFLoader.ts

@@ -5,7 +5,7 @@ import { LoadFileError, IFileRequest, Tools } from "babylonjs/Misc/tools";
 import { Camera } from "babylonjs/Cameras/camera";
 import { FreeCamera } from "babylonjs/Cameras/freeCamera";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
-import { Animation, IAnimationKey, AnimationKeyInterpolation } from "babylonjs/Animations/animation";
+import { Animation } from "babylonjs/Animations/animation";
 import { Bone } from "babylonjs/Bones/bone";
 import { Skeleton } from "babylonjs/Bones/skeleton";
 import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
@@ -22,11 +22,11 @@ import { MorphTarget } from "babylonjs/Morph/morphTarget";
 import { MorphTargetManager } from "babylonjs/Morph/morphTargetManager";
 import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
 import { Scene } from "babylonjs/scene";
-
 import { IProperty, AccessorType, CameraType, AnimationChannelTargetPath, AnimationSamplerInterpolation, AccessorComponentType, MaterialAlphaMode, TextureMinFilter, TextureWrapMode, TextureMagFilter, MeshPrimitiveMode } from "babylonjs-gltf2interface";
 import { _IAnimationSamplerData, IGLTF, ISampler, INode, IScene, IMesh, IAccessor, ISkin, ICamera, IAnimation, IAnimationChannel, IAnimationSampler, IBuffer, IBufferView, IMaterialPbrMetallicRoughness, IMaterial, ITextureInfo, ITexture, IImage, IMeshPrimitive, IArrayItem as IArrItem, _ISamplerData } from "./glTFLoaderInterfaces";
 import { IGLTFLoaderExtension } from "./glTFLoaderExtension";
 import { IGLTFLoader, GLTFFileLoader, GLTFLoaderState, IGLTFLoaderData, GLTFLoaderCoordinateSystemMode, GLTFLoaderAnimationStartMode } from "../glTFFileLoader";
+import { IAnimationKey, AnimationKeyInterpolation } from 'babylonjs/Animations/animationKey';
 
 interface IFileRequestInfo extends IFileRequest {
     _lengthComputable?: boolean;

+ 2 - 1
serializers/src/glTF/2.0/glTFAnimation.ts

@@ -3,13 +3,14 @@ import { AnimationSamplerInterpolation, AnimationChannelTargetPath, AccessorType
 import { Nullable } from "babylonjs/types";
 import { Vector3, Quaternion } from "babylonjs/Maths/math";
 import { Tools } from "babylonjs/Misc/tools";
-import { Animation, IAnimationKey, AnimationKeyInterpolation } from "babylonjs/Animations/animation";
+import { Animation } from "babylonjs/Animations/animation";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { Scene } from "babylonjs/scene";
 
 import { _BinaryWriter } from "./glTFExporter";
 import { _GLTFUtilities } from "./glTFUtilities";
+import { IAnimationKey, AnimationKeyInterpolation } from 'babylonjs/Animations/animationKey';
 
 /**
  * @hidden

+ 574 - 0
src/Animations/animatable.ts

@@ -4,6 +4,10 @@ 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
@@ -421,3 +425,573 @@ export class Animatable {
         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;
+
+        /** @hidden */
+        _animate(): 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;
+};

+ 5 - 209
src/Animations/animation.ts

@@ -1,225 +1,21 @@
 import { IEasingFunction, EasingFunction } from "./easing";
-import { Path2, Vector3, Quaternion, Vector2, Color3, Size, Matrix } from "../Maths/math";
+import { Vector3, Quaternion, Vector2, Color3, Size, Matrix } from "../Maths/math";
 import { Scalar } from "../Maths/math.scalar";
 
 import { Nullable } from "../types";
 import { Scene } from "../scene";
 import { IAnimatable } from "../Misc/tools";
-import { Node } from "../node";
 import { SerializationHelper } from "../Misc/decorators";
 import { _TypeStore } from '../Misc/typeStore';
+import { IAnimationKey, AnimationKeyInterpolation } from './animationKey';
+import { AnimationRange } from './animationRange';
+import { AnimationEvent } from './animationEvent';
+import { Node } from "../node";
 
 declare type Animatable = import("./animatable").Animatable;
 declare type RuntimeAnimation = import("./runtimeAnimation").RuntimeAnimation;
 
 /**
- * Represents the range of an animation
- */
-export class AnimationRange {
-    /**
-     * Initializes the range of an animation
-     * @param name The name of the animation range
-     * @param from The starting frame of the animation
-     * @param to The ending frame of the animation
-     */
-    constructor(
-        /**The name of the animation range**/
-        public name: string,
-        /**The starting frame of the animation */
-        public from: number,
-        /**The ending frame of the animation*/
-        public to: number) {
-    }
-
-    /**
-     * Makes a copy of the animation range
-     * @returns A copy of the animation range
-     */
-    public clone(): AnimationRange {
-        return new AnimationRange(this.name, this.from, this.to);
-    }
-}
-
-/**
- * Composed of a frame, and an action function
- */
-export class AnimationEvent {
-    /**
-     * Specifies if the animation event is done
-     */
-    public isDone: boolean = false;
-
-    /**
-     * Initializes the animation event
-     * @param frame The frame for which the event is triggered
-     * @param action The event to perform when triggered
-     * @param onlyOnce Specifies if the event should be triggered only once
-     */
-    constructor(
-        /** The frame for which the event is triggered **/
-        public frame: number,
-        /** The event to perform when triggered **/
-        public action: (currentFrame: number) => void,
-        /** Specifies if the event should be triggered only once**/
-        public onlyOnce?: boolean) {
-    }
-
-    /** @hidden */
-    public _clone(): AnimationEvent {
-        return new AnimationEvent(this.frame, this.action, this.onlyOnce);
-    }
-}
-
-/**
- * A cursor which tracks a point on a path
- */
-export class PathCursor {
-    /**
-     * Stores path cursor callbacks for when an onchange event is triggered
-     */
-    private _onchange = new Array<(cursor: PathCursor) => void>();
-
-    /**
-     * The value of the path cursor
-     */
-    value: number = 0;
-
-    /**
-     * The animation array of the path cursor
-     */
-    animations = new Array<Animation>();
-
-    /**
-     * Initializes the path cursor
-     * @param path The path to track
-     */
-    constructor(private path: Path2) {
-    }
-
-    /**
-     * Gets the cursor point on the path
-     * @returns A point on the path cursor at the cursor location
-     */
-    public getPoint(): Vector3 {
-        var point = this.path.getPointAtLengthPosition(this.value);
-        return new Vector3(point.x, 0, point.y);
-    }
-
-    /**
-     * Moves the cursor ahead by the step amount
-     * @param step The amount to move the cursor forward
-     * @returns This path cursor
-     */
-    public moveAhead(step: number = 0.002): PathCursor {
-        this.move(step);
-
-        return this;
-    }
-
-    /**
-     * Moves the cursor behind by the step amount
-     * @param step The amount to move the cursor back
-     * @returns This path cursor
-     */
-    public moveBack(step: number = 0.002): PathCursor {
-        this.move(-step);
-
-        return this;
-    }
-
-    /**
-     * Moves the cursor by the step amount
-     * If the step amount is greater than one, an exception is thrown
-     * @param step The amount to move the cursor
-     * @returns This path cursor
-     */
-    public move(step: number): PathCursor {
-
-        if (Math.abs(step) > 1) {
-            throw "step size should be less than 1.";
-        }
-
-        this.value += step;
-        this.ensureLimits();
-        this.raiseOnChange();
-
-        return this;
-    }
-
-    /**
-     * Ensures that the value is limited between zero and one
-     * @returns This path cursor
-     */
-    private ensureLimits(): PathCursor {
-        while (this.value > 1) {
-            this.value -= 1;
-        }
-        while (this.value < 0) {
-            this.value += 1;
-        }
-
-        return this;
-    }
-
-    /**
-     * Runs onchange callbacks on change (used by the animation engine)
-     * @returns This path cursor
-     */
-    private raiseOnChange(): PathCursor {
-        this._onchange.forEach((f) => f(this));
-
-        return this;
-    }
-
-    /**
-     * Executes a function on change
-     * @param f A path cursor onchange callback
-     * @returns This path cursor
-     */
-    public onchange(f: (cursor: PathCursor) => void): PathCursor {
-        this._onchange.push(f);
-
-        return this;
-    }
-}
-
-/**
- * Defines an interface which represents an animation key frame
- */
-export interface IAnimationKey {
-    /**
-     * Frame of the key frame
-     */
-    frame: number;
-    /**
-     * Value at the specifies key frame
-     */
-    value: any;
-    /**
-     * The input tangent for the cubic hermite spline
-     */
-    inTangent?: any;
-    /**
-     * The output tangent for the cubic hermite spline
-     */
-    outTangent?: any;
-    /**
-     * The animation interpolation type
-     */
-    interpolation?: AnimationKeyInterpolation;
-}
-
-/**
- * Enum for the animation key frame interpolation type
- */
-export enum AnimationKeyInterpolation {
-    /**
-     * Do not interpolate between keys and use the start key value only. Tangents are ignored
-     */
-    STEP = 1
-}
-
-/**
  * Class used to store any kind of animation
  */
 export class Animation {

+ 0 - 579
src/Animations/animationComponent.ts

@@ -1,579 +0,0 @@
-import { Scene } from "../scene";
-import { Node } from "../node";
-import { RuntimeAnimation } from './runtimeAnimation';
-import { Matrix, Tmp, Quaternion, Vector3 } from '../Maths/math';
-import { Nullable } from '../types';
-import { Animation } from './animation';
-import { Animatable } from './animatable';
-import { PrecisionDate } from '../Misc/precisionDate';
-import { Bone } from '../Bones/bone';
-
-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;
-
-        /** @hidden */
-        _animate(): 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;
-};

+ 29 - 0
src/Animations/animationEvent.ts

@@ -0,0 +1,29 @@
+/**
+ * Composed of a frame, and an action function
+ */
+export class AnimationEvent {
+    /**
+     * Specifies if the animation event is done
+     */
+    public isDone: boolean = false;
+
+    /**
+     * Initializes the animation event
+     * @param frame The frame for which the event is triggered
+     * @param action The event to perform when triggered
+     * @param onlyOnce Specifies if the event should be triggered only once
+     */
+    constructor(
+        /** The frame for which the event is triggered **/
+        public frame: number,
+        /** The event to perform when triggered **/
+        public action: (currentFrame: number) => void,
+        /** Specifies if the event should be triggered only once**/
+        public onlyOnce?: boolean) {
+    }
+
+    /** @hidden */
+    public _clone(): AnimationEvent {
+        return new AnimationEvent(this.frame, this.action, this.onlyOnce);
+    }
+}

+ 2 - 1
src/Animations/animationGroup.ts

@@ -1,5 +1,6 @@
 import { Animatable } from "./animatable";
-import { IAnimationKey, Animation } from "./animation";
+import { Animation } from "./animation";
+import { IAnimationKey } from "./animationKey";
 
 import { Scene, IDisposable } from "../scene";
 import { Observable } from "../Misc/observable";

+ 35 - 0
src/Animations/animationKey.ts

@@ -0,0 +1,35 @@
+/**
+ * Defines an interface which represents an animation key frame
+ */
+export interface IAnimationKey {
+    /**
+     * Frame of the key frame
+     */
+    frame: number;
+    /**
+     * Value at the specifies key frame
+     */
+    value: any;
+    /**
+     * The input tangent for the cubic hermite spline
+     */
+    inTangent?: any;
+    /**
+     * The output tangent for the cubic hermite spline
+     */
+    outTangent?: any;
+    /**
+     * The animation interpolation type
+     */
+    interpolation?: AnimationKeyInterpolation;
+}
+
+/**
+ * Enum for the animation key frame interpolation type
+ */
+export enum AnimationKeyInterpolation {
+    /**
+     * Do not interpolate between keys and use the start key value only. Tangents are ignored
+     */
+    STEP = 1
+}

+ 27 - 0
src/Animations/animationRange.ts

@@ -0,0 +1,27 @@
+/**
+ * Represents the range of an animation
+ */
+export class AnimationRange {
+    /**
+     * Initializes the range of an animation
+     * @param name The name of the animation range
+     * @param from The starting frame of the animation
+     * @param to The ending frame of the animation
+     */
+    constructor(
+        /**The name of the animation range**/
+        public name: string,
+        /**The starting frame of the animation */
+        public from: number,
+        /**The ending frame of the animation*/
+        public to: number) {
+    }
+
+    /**
+     * Makes a copy of the animation range
+     * @returns A copy of the animation range
+     */
+    public clone(): AnimationRange {
+        return new AnimationRange(this.name, this.from, this.to);
+    }
+}

+ 5 - 1
src/Animations/index.ts

@@ -3,4 +3,8 @@ export * from "./animation";
 export * from "./animationGroup";
 export * from "./animationPropertiesOverride";
 export * from "./easing";
-export * from "./runtimeAnimation";
+export * from "./runtimeAnimation";
+export * from "./animationEvent";
+export * from "./animationGroup";
+export * from "./animationKey";
+export * from "./animationRange";

+ 114 - 0
src/Animations/pathCursor.ts

@@ -0,0 +1,114 @@
+import { Path2, Vector3 } from '../Maths/math';
+
+/**
+ * A cursor which tracks a point on a path
+ */
+export class PathCursor {
+    /**
+     * Stores path cursor callbacks for when an onchange event is triggered
+     */
+    private _onchange = new Array<(cursor: PathCursor) => void>();
+
+    /**
+     * The value of the path cursor
+     */
+    value: number = 0;
+
+    /**
+     * The animation array of the path cursor
+     */
+    animations = new Array<Animation>();
+
+    /**
+     * Initializes the path cursor
+     * @param path The path to track
+     */
+    constructor(private path: Path2) {
+    }
+
+    /**
+     * Gets the cursor point on the path
+     * @returns A point on the path cursor at the cursor location
+     */
+    public getPoint(): Vector3 {
+        var point = this.path.getPointAtLengthPosition(this.value);
+        return new Vector3(point.x, 0, point.y);
+    }
+
+    /**
+     * Moves the cursor ahead by the step amount
+     * @param step The amount to move the cursor forward
+     * @returns This path cursor
+     */
+    public moveAhead(step: number = 0.002): PathCursor {
+        this.move(step);
+
+        return this;
+    }
+
+    /**
+     * Moves the cursor behind by the step amount
+     * @param step The amount to move the cursor back
+     * @returns This path cursor
+     */
+    public moveBack(step: number = 0.002): PathCursor {
+        this.move(-step);
+
+        return this;
+    }
+
+    /**
+     * Moves the cursor by the step amount
+     * If the step amount is greater than one, an exception is thrown
+     * @param step The amount to move the cursor
+     * @returns This path cursor
+     */
+    public move(step: number): PathCursor {
+
+        if (Math.abs(step) > 1) {
+            throw "step size should be less than 1.";
+        }
+
+        this.value += step;
+        this.ensureLimits();
+        this.raiseOnChange();
+
+        return this;
+    }
+
+    /**
+     * Ensures that the value is limited between zero and one
+     * @returns This path cursor
+     */
+    private ensureLimits(): PathCursor {
+        while (this.value > 1) {
+            this.value -= 1;
+        }
+        while (this.value < 0) {
+            this.value += 1;
+        }
+
+        return this;
+    }
+
+    /**
+     * Runs onchange callbacks on change (used by the animation engine)
+     * @returns This path cursor
+     */
+    private raiseOnChange(): PathCursor {
+        this._onchange.forEach((f) => f(this));
+
+        return this;
+    }
+
+    /**
+     * Executes a function on change
+     * @param f A path cursor onchange callback
+     * @returns This path cursor
+     */
+    public onchange(f: (cursor: PathCursor) => void): PathCursor {
+        this._onchange.push(f);
+
+        return this;
+    }
+}

+ 2 - 1
src/Animations/runtimeAnimation.ts

@@ -1,6 +1,7 @@
 import { DeepImmutable } from "../types";
 import { Quaternion, Vector3, Vector2, Size, Color3, Matrix } from "../Maths/math";
-import { Animation, AnimationEvent } from "./animation";
+import { Animation } from "./animation";
+import { AnimationEvent } from "./animationEvent";
 
 declare type Animatable = import("./animatable").Animatable;
 

+ 2 - 1
src/Bones/skeleton.ts

@@ -9,7 +9,8 @@ import { AbstractMesh } from "../Meshes/abstractMesh";
 import { RawTexture } from "../Materials/Textures/rawTexture";
 import { Animatable } from "../Animations/animatable";
 import { AnimationPropertiesOverride } from "../Animations/animationPropertiesOverride";
-import { AnimationRange, Animation } from "../Animations/animation";
+import { Animation } from "../Animations/animation";
+import { AnimationRange } from "../Animations/animationRange";
 import { EngineStore } from "../Engines/engineStore";
 import { Constants } from "../Engines/constants";
 import { Logger } from "../Misc/logger";

+ 1 - 2
src/node.ts

@@ -10,8 +10,7 @@ import { EngineStore } from "./Engines/engineStore";
 declare type Animatable = import("./Animations/animatable").Animatable;
 declare type AnimationPropertiesOverride = import("./Animations/animationPropertiesOverride").AnimationPropertiesOverride;
 declare type Animation = import("./Animations/animation").Animation;
-declare type AnimationRange = import("./Animations/animation").AnimationRange;
-
+declare type AnimationRange = import("./Animations/animationRange").AnimationRange;
 declare type AbstractMesh = import("./Meshes/abstractMesh").AbstractMesh;
 
 /**

+ 1 - 1
tests/es6Modules/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"engineOnly":303950,"sceneOnly":567058,"minGridMaterial":695590,"minStandardMaterial":791420}
+{"engineOnly":303948,"sceneOnly":512262,"minGridMaterial":640875,"minStandardMaterial":761667}