Explorar o código

Merge pull request #900 from Palmer-JC/master

AnimationRange management / loading
David Catuhe %!s(int64=9) %!d(string=hai) anos
pai
achega
1e1cd6754b
Modificáronse 3 ficheiros con 168 adicións e 28 borrados
  1. 46 14
      src/Animations/babylon.animation.ts
  2. 44 0
      src/Bones/babylon.bone.ts
  3. 78 14
      src/Bones/babylon.skeleton.ts

+ 46 - 14
src/Animations/babylon.animation.ts

@@ -29,7 +29,7 @@
 
         public allowMatricesInterpolation = false;
 
-        private _ranges = new Array<AnimationRange>();
+        private _ranges : { [name: string] : AnimationRange; } = {};
 
         static _PrepareAnimation(targetProperty: string, framePerSecond: number, totalFrame: number,
             from: any, to: any, loopMode?: number, easingFunction?: EasingFunction): Animation {
@@ -113,26 +113,31 @@
         }
 
         public createRange(name: string, from: number, to: number): void {
-            this._ranges.push(new AnimationRange(name, from, to));
+            // check name not already in use; could happen for bones after serialized
+            if (! this._ranges[name]){
+                this._ranges[name] = new AnimationRange(name, from, to);
+            }
         }
 
-        public deleteRange(name: string): void {
-            for (var index = 0; index < this._ranges.length; index++) {
-                if (this._ranges[index].name === name) {
-                    this._ranges.splice(index, 1);
-                    return;
+        public deleteRange(name: string, deleteFrames = true): void {
+            if (this._ranges[name]){
+                if (deleteFrames) {
+                    var from = this._ranges[name].from;
+                    var to = this._ranges[name].to;
+ 
+                    // this loop MUST go high to low for multiple splices to work
+                    for (var key = this._keys.length - 1; key >= 0; key--) {
+                        if (this._keys[key].frame >= from  && this._keys[key].frame <= to) {
+                           this._keys.splice(key, 1); 
+                        }
+                    }
                 }
+                this._ranges[name] = undefined; // said much faster than 'delete this._range[name]' 
             }
         }
 
         public getRange(name: string): AnimationRange {
-            for (var index = 0; index < this._ranges.length; index++) {
-                if (this._ranges[index].name === name) {
-                    return this._ranges[index];
-                }
-            }
-
-            return null;
+            return this._ranges[name];
         }
 
         public reset(): void {
@@ -148,6 +153,17 @@
         public getKeys(): any[] {
             return this._keys;
         }
+        
+        public getHighestFrame() : number {
+            var ret = 0; 
+        
+            for (var key = 0, nKeys = this._keys.length; key < nKeys; key++) {
+                if (ret < this._keys[key].frame) {
+                    ret = this._keys[key].frame; 
+                }
+            }
+            return ret;
+        }
 
         public getEasingFunction() {
             return this._easingFunction;
@@ -498,6 +514,15 @@
 
                 serializationObject.keys.push(key);
             }
+            
+            serializationObject.ranges = [];
+            for (var name in this._ranges) {
+                var range: any = {};
+                range.name = name;
+                range.from = this._ranges[name].from;
+                range.to   = this._ranges[name].to;
+                serializationObject.ranges.push(range);
+            }
 
             return serializationObject;
         }
@@ -585,6 +610,13 @@
             }
 
             animation.setKeys(keys);
+            
+            if (parsedAnimation.ranges){
+               for (var index = 0; index < parsedAnimation.ranges.length; index++) {
+                   data = parsedAnimation.ranges[index];
+                   animation.createRange(data.name, data.from, data.to);
+               }
+            }
 
             return animation;
         }

+ 44 - 0
src/Bones/babylon.bone.ts

@@ -89,5 +89,49 @@
             this._currentRenderId++;
             this._skeleton._markAsDirty();
         }
+        
+        public copyAnimationRange(source : Bone, rangeName : string, frameOffset : number, rescaleAsRequired = false) : 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) ); 
+            }
+
+            // 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 scalingReqd = rescaleAsRequired && sourceBoneLength && this.length && sourceBoneLength !== this.length;
+            var ratio = scalingReqd ? this.length / sourceBoneLength : null;
+            
+            var destKeys = this.animations[0].getKeys();
+            
+            // loop vars declaration / initialization
+            var orig : {frame : number, value : Matrix};
+            var origScale = scalingReqd ? BABYLON.Vector3.Zero() : null;
+            var origRotation = scalingReqd ? new BABYLON.Quaternion() : null;
+            var origTranslation = scalingReqd ? BABYLON.Vector3.Zero() : null;
+            var mat : Matrix;
+
+            for (var key = 0, nKeys = sourceKeys.length; key < nKeys; key++) {
+                orig = sourceKeys[key];
+                if (orig.frame >= from  && orig.frame <= to) {
+                    if (scalingReqd) {
+                        orig.value.decompose(origScale, origRotation, origTranslation);
+                        origTranslation.scaleInPlace(ratio);
+                        mat = Matrix.Compose(origScale, origRotation, origTranslation);
+                    }else {
+                        mat = orig.value;
+                    }
+                    destKeys.push({frame: orig.frame + frameOffset, value: mat});
+                }
+            }
+            this.animations[0].createRange(rangeName, from + frameOffset, to + frameOffset);
+            return true;
+        }
     }
 } 

+ 78 - 14
src/Bones/babylon.skeleton.ts

@@ -8,7 +8,7 @@
         private _animatables: IAnimatable[];
         private _identity = Matrix.Identity();
 
-        private _ranges = new Array<AnimationRange>();
+        private _ranges : { [name: string] : AnimationRange; } = {};
 
         constructor(public name: string, public id: string, scene: Scene) {
             this.bones = [];
@@ -33,26 +33,74 @@
 
         // Methods
         public createAnimationRange(name: string, from: number, to: number): void {
-            this._ranges.push(new AnimationRange(name, from, to));
+            // check name not already in use
+            if (! this._ranges[name]){
+                this._ranges[name] = new AnimationRange(name, from, to);
+                for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
+                    if (this.bones[i].animations[0]) {
+                        this.bones[i].animations[0].createRange(name, from, to);
+                    }
+                }
+            }
         }
 
-        public deleteAnimationRange(name: string): void {
-            for (var index = 0; index < this._ranges.length; index++) {
-                if (this._ranges[index].name === name) {
-                    this._ranges.splice(index, 1);
-                    return;
+        public deleteAnimationRange(name: string, deleteFrames = true): void {
+            for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
+                if (this.bones[i].animations[0]) {
+                    this.bones[i].animations[0].deleteRange(name, deleteFrames);
                 }
             }
+            this._ranges[name] = undefined; // said much faster than 'delete this._range[name]' 
         }
 
         public getAnimationRange(name: string): AnimationRange {
-            for (var index = 0; index < this._ranges.length; index++) {
-                if (this._ranges[index].name === name) {
-                    return this._ranges[index];
-                }
+            return this._ranges[name];
+        }
+
+        /** 
+         *  note: This is not for a complete retargeting, only between very similar skeleton's with only possible bone length differences
+         */
+        public copyAnimationRange(source : Skeleton, name : string, rescaleAsRequired = false) : boolean {
+            if (this._ranges[name] || !source.getAnimationRange(name) ){
+               return false; 
+            }
+            var ret = true;
+            var frameOffset = this._getHighestAnimationFrame() + 1;
+            
+            // make a dictionary of source skeleton's bones, so exact same order or doublely nested loop is not required
+            var boneDict = {};
+            var sourceBones = source.bones;
+            for (var i = 0, nBones = sourceBones.length; i < nBones; i++) {
+                boneDict[sourceBones[i].name] = sourceBones[i];
             }
 
-            return null;
+            for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
+                var boneName = this.bones[i].name;
+                var sourceBone = boneDict[boneName];
+                if (sourceBone){
+                    ret = ret && this.bones[i].copyAnimationRange(sourceBone, name, frameOffset, rescaleAsRequired);
+                }else{
+                    BABYLON.Tools.Warn("copyAnimationRange: not same rig, missing source bone " + name);
+                    ret = false;
+                }
+            }
+            // do not call createRange(), since it also is done to bones, which was already done
+            var range = source.getAnimationRange(name);
+            this._ranges[name] = new AnimationRange(name, range.from + frameOffset, range.to + frameOffset);
+            return ret;
+        }
+        
+        private _getHighestAnimationFrame() : number {
+            var ret = 0; 
+            for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
+                if (this.bones[i].animations[0]) {
+                    var highest = this.bones[i].animations[0].getHighestFrame();
+                    if (ret < highest) {
+                        ret = highest; 
+                    }
+                }
+            }
+            return ret;
         }
 
         public beginAnimation(name: string, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void): void {
@@ -109,7 +157,7 @@
 
             return this._animatables;
         }
-
+        
         public clone(name: string, id: string): Skeleton {
             var result = new Skeleton(name, id || name, this._scene);
 
@@ -163,6 +211,15 @@
                 if (bone.animations && bone.animations.length > 0) {
                     serializedBone.animation = bone.animations[0].serialize();
                 }
+                
+                serializationObject.ranges = [];
+                for (var name in this._ranges) {
+                    var range: any = {};
+                    range.name = name;
+                    range.from = this._ranges[name].from;
+                    range.to   = this._ranges[name].to;
+                    serializationObject.ranges.push(range);
+                }
             }
             return serializationObject;
         }
@@ -188,7 +245,14 @@
                     bone.animations.push(Animation.Parse(parsedBone.animation));
                 }
             }
-
+            
+            // placed after bones, so createAnimationRange can cascade down
+            if (parsedSkeleton.ranges){
+               for (var index = 0; index < parsedSkeleton.ranges.length; index++) {
+                   var data = parsedSkeleton.ranges[index];
+                   skeleton.createAnimationRange(data.name, data.from, data.to);
+               }
+            }
             return skeleton;
         }
     }