浏览代码

AnimationRange management / loading

AnimationRanges are now allowed in a .babylon.  They may also delete
the frames of the animation, when the ranges objects are.

Skeletons with the same bones, may now also copy ranges.  Rescaling can
be done for bones of different lengths (not tested yet).  This
facilitates separating animation from meshes using multiple babylons:
- A .babylon with meshes & skeletons without animation
- A library .babylon with a skeleton and all the animations

Loaded like:

var scene = new BABYLON.Scene(engine);
        
BABYLON.SceneLoader.Append(url, "meshes.babylon", scene);
BABYLON.SceneLoader.Append(url, "skeleton_library.babylon", scene);
scene.executeWhenReady(function () {
     var meshSkeleton = scene.getSkeletonByName("name");
     var library = scene.getSkeletonByName("library");

     meshSkeleton.copyAnimationRange(library, "stock", true);
     meshSkeleton.beginAnimation("stock", true);

     ...
});
jeff 9 年之前
父节点
当前提交
a5a0619630
共有 3 个文件被更改,包括 168 次插入28 次删除
  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;
         }
     }