Browse Source

fix bugs in KeyframeTrack.optimize/validate. Expand capabilities of PropertyBinding bind to work with bones. fix bugs with JSONLoader animation converter.

Ben Houston 10 years ago
parent
commit
4657a76fd3

+ 4 - 0
examples/webgl_animation_track_clip_mixer.html

@@ -273,6 +273,10 @@
 				
 				var clip7 = THREE.AnimationClipCreator.CreateMorphAnimation( mesh.geometry.morphTargets, 3 );
 				mixer.addAction( new THREE.AnimationAction( clip7, 0, 1, 1, true ) );
+
+				var clip8 = THREE.AnimationClip.FromJSONLoaderAnimation( geometry );
+				mixer.addAction( new THREE.AnimationAction( clip8, 0, 1, 1, true ) );
+				
 			}
 
 			function initGUI() {

+ 29 - 23
src/animation/AnimationClip.js

@@ -14,8 +14,8 @@ THREE.AnimationClip = function ( name, duration, tracks ) {
 
 	// TODO: maybe only do these on demand, as doing them here could potentially slow down loading
 	// but leaving these here during development as this ensures a lot of testing of these functions
-	this.trim();
-	this.optimize();
+	//this.trim();
+	//this.optimize();
 	
 };
 
@@ -89,18 +89,29 @@ THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader ) {
 		return null;
 	}
 
-	var convertTrack = function( trackName, animationKeys, animationKeyToValueFunc ) {
+	var convertTrack = function( trackName, animationKeys, propertyName, animationKeyToValueFunc ) {
 
 		var keys = [];
 
 		for( var k = 0; k < animationKeys.length; k ++ ) {
 
 			var animationKey = animationKeys[k];
-			keys.push( { time: animationKey.time, value: animationKeyToValueFunc( animationKey ) } );
+
+			if( animationKey[propertyName] !== undefined ) {
+
+				keys.push( { time: animationKey.time, value: animationKeyToValueFunc( animationKey ) } );
+			}
 	
 		}
 
-		return new THREE.KeyframeTrack( trackName, keys );
+		// only return track if there are actually keys.
+		if( keys.length > 0 ) {
+
+			return new THREE.KeyframeTrack( trackName, keys );
+
+		}
+
+		return null;
 
 	};
 
@@ -132,7 +143,7 @@ THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader ) {
 				if( animationKeys[k].morphTargets ) {
 					for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
 
-						morphTagetNames[ animationKeys[k].morphTargets[m] ] = -1;
+						morphTargetNames[ animationKeys[k].morphTargets[m] ] = -1;
 					}
 				}
 
@@ -161,31 +172,26 @@ THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader ) {
 		}
 		
 		// track contains positions...
-		if( animationKeys[0].pos ) {
+		var positionTrack = convertTrack( boneName + '.position', animationKeys, 'pos', function( animationKey ) {
+				return new THREE.Vector3().fromArray( animationKey.pos )
+			} );
 
-			tracks.push( convertTracks( boneName + '.position', animationKeys, function( dataValue ) {
-					return new THREE.Vector3().fromArray( animationKey.pos )
-				} );
+		if( positionTrack ) tracks.push( positionTrack );
 
-		}
 		
 		// track contains quaternions...
-		if( animationKeys[0].rot ) {
-
-			tracks.push( convertTracks( boneName + '.quaternion', animationKeys, function( dataValue ) {
-					return new THREE.Quaternion().fromArray( animationKey.rot )
-				} );
+		var quaternionTrack = convertTrack( boneName + '.quaternion', animationKeys, 'rot', function( animationKey ) {
+				return new THREE.Quaternion().fromArray( animationKey.rot )
+			} );
 
-		}
+		if( quaternionTrack ) tracks.push( quaternionTrack );
 
 		// track contains quaternions...
-		if( animationKeys[0].scl ) {
-
-			tracks.push( convertTracks( boneName + '.quaternion', animationKeys, function( dataValue ) {
-					return new THREE.Vector3().fromArray( animationKey.scl )
-				} );
+		var scaleTrack = convertTrack( boneName + '.quaternion', animationKeys, 'scl', function( animationKey ) {
+				return new THREE.Vector3().fromArray( animationKey.scl )
+			} );
 
-		}
+		if( scaleTrack ) tracks.push( scaleTrack );
 	}
 
 	var clip = new THREE.AnimationClip( clipName, duration, tracks );

+ 4 - 0
src/animation/AnimationUtils.js

@@ -31,6 +31,8 @@
  	// TODO/OPTIMIZATION: Accumulator should be writable and it will get rid of the *.clone() calls that are likely slow.
 	getLerpFunc: function( exemplarValue, interTrack ) {
 
+		if( exemplarValue === undefined || exemplarValue === null ) throw new Error( "examplarValue is null" );
+
 		var typeName = typeof exemplarValue;
 		switch( typeName ) {
 		 	case "object": {
@@ -38,6 +40,7 @@
 				if( exemplarValue.lerp ) {
 
 					return function( a, b, alpha ) {
+						//console.log( a, b );
 						return a.clone().lerp( b, alpha );
 					}
 
@@ -45,6 +48,7 @@
 				if( exemplarValue.slerp ) {
 
 					return function( a, b, alpha ) {
+						//console.log( a, b );
 						return a.clone().slerp( b, alpha );
 					}
 

+ 13 - 8
src/animation/KeyframeTrack.js

@@ -94,7 +94,7 @@ THREE.KeyframeTrack.prototype = {
 			this.keys.sort( keyComparator );
 		}
 
-	}();
+	}(),
 
 	// ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
 	// TODO: ensure that all key.values in a track are all of the same type (otherwise interpolation makes no sense.)
@@ -116,7 +116,7 @@ THREE.KeyframeTrack.prototype = {
 				return;
 			}
 
-			if( ( typeof currKey.time ) !== 'Number' || currKey.time == NaN ) {
+			if( ( typeof currKey.time ) !== 'number' || Number.isNaN( currKey.time ) ) {
 				console.error( "  key.time is not a valid number", this, i, currKey );
 				return;
 			}
@@ -150,6 +150,7 @@ THREE.KeyframeTrack.prototype = {
 		for( var i = 1; i < this.keys.length - 1; i ++ ) {
 			var currKey = this.keys[i];
 			var nextKey = this.keys[i+1];
+			//console.log( prevKey, currKey, nextKey );
 
 			// if prevKey & currKey are the same time, remove currKey.  If you want immediate adjacent keys, use an epsilon offset
 			// it is not possible to have two keys at the same time as we sort them.  The sort is not stable on keys with the same time.
@@ -159,7 +160,7 @@ THREE.KeyframeTrack.prototype = {
 			}
 
 			// remove completely unnecessary keyframes that are the same as their prev and next keys
-			if( equalsFunc( prevKey.value, currKey.value ) && equals( currKey.value, nextKey.value ) ) {
+			if( equalsFunc( prevKey.value, currKey.value ) && equalsFunc( currKey.value, nextKey.value ) ) {
 				console.log(  'removing key identical to prev and next', currKey );
 				continue;
 			}
@@ -169,8 +170,11 @@ THREE.KeyframeTrack.prototype = {
 			newKeys.push( currKey );
 			prevKey = currKey;
 		}
+		newKeys.push( this.keys[ this.keys.length - 1 ] );
 
-		console.log( '  track optimization removed keys:', ( this.keys.length - newKeys.length ), this.name );
+		if( ( this.keys.length - newKeys.length ) > 0 ) {
+			console.log( '  optimizing removed keys:', ( this.keys.length - newKeys.length ), this.name );
+		}
 		this.keys = newKeys;
 
 	},
@@ -198,10 +202,11 @@ THREE.KeyframeTrack.prototype = {
 
 		// remove last keys first because it doesn't affect the position of the first keys (the otherway around doesn't work as easily)
 		// TODO: Figure out if there is an array subarray function... might be faster
-		var keysWithoutLastKeys = this.keys.splice( this.keys.length - lastKeysToRemove, lastKeysToRemove );
-		var keysWithoutFirstKeys = keysWithoutLastKeys.splice( 0, firstKeysToRemove );
-		this.keys = keysWithoutFirstKeys;
-
+		if( ( firstKeysToRemove + lastKeysToRemove ) > 0 ) {
+			console.log(  '  triming removed keys: first and last', firstKeysToRemove, lastKeysToRemove, this.keys );
+			this.keys = this.keys.splice( firstKeysToRemove, this.keys.length - lastKeysToRemove - firstKeysToRemove );;
+			console.log(  '  result', this.keys );
+		}
 
 	}
 

+ 55 - 51
src/animation/PropertyBinding.js

@@ -40,7 +40,7 @@ THREE.PropertyBinding.prototype = {
 
 	accumulate: function( value, weight ) {
 		
-		var lerp = THREE.AnimationUtils.getLerpFunc( this.cumulativeValue, true );
+		var lerp = THREE.AnimationUtils.getLerpFunc( value, true );
 
 		this.accumulate = function( value, weight ) {
 
@@ -78,44 +78,62 @@ THREE.PropertyBinding.prototype = {
 				return;
 			}
 
-			if( this.material ) {
-				targetObject = targetObject.material;
-				if( this.materialIndex !== undefined ) {
-					if( targetObject.materials ) {
-						targetObject = targetObject.materials[ this.materialIndex ];
+			if( this.objectName ) {
+				// special case were we need to reach deeper into the hierarchy to get the face materials....
+				if( this.objectName === "materials" ) {
+					if( ! targetObject.material ) {
+						console.error( '  can not bind to material as node does not have a material', this );
+						return;				
 					}
-					else {
-						console.error( "  trying to submaterial via index, but no materials exist:", targetObject );
+					if( ! targetObject.material.materials ) {
+						console.error( '  can not bind to material.materials as node.material does not have a materials array', this );
 						return;				
 					}
+					targetObject = targetObject.material.materials;
 				}
-			}
+				else if( this.objectName === "bones" ) {
+					if( ! targetObject.skeleton ) {
+						console.error( '  can not bind to bones as node does not have a skeleton', this );
+					}
+					targetObject = targetObject.skeleton.bones;
 
-	 		// special case mappings
-	 		var nodeProperty = null;
-			if( this.propertyName === "materials" ) {
-				if( ! targetObject.material ) {
-					console.error( '  can not bind to material as node does not have a material', this );
-					return;				
+					// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
+					
+					// support resolving morphTarget names into indices.
+					console.log( "  resolving bone name: ", this.objectIndex );
+					for( var i = 0; i < this.node.skeleton.bones.length; i ++ ) {
+						if( this.node.skeleton.bones[i].name === this.objectIndex ) {
+							console.log( "  resolved to index: ", i );
+							this.objectIndex = i;
+							break;
+						}
+					}
 				}
-				if( ! targetObject.material.materials ) {
-					console.error( '  can not bind to material.materials as node.material does not have a materials array', this );
-					return;				
+				else {
+
+					if( targetObject[ this.objectName ] === undefined ) {
+						console.error( '  can not bind to objectName of node, undefined', this );			
+						return;
+					}
+					targetObject = targetObject[ this.objectName ];
 				}
-				nodeProperty = targetObject.material.materials;
-			}
-			if( this.propertyName === "bones" ) {
-				if( ! targetObject.skeleton ) {
-					console.error( '  can not bind to bones as node does not have a skeleton', this );
+				
+				if( this.objectIndex !== undefined ) {
+					if( targetObject[ this.objectIndex ] === undefined ) {
+						console.error( "  trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
+						return;				
+					}
+
+					targetObject = targetObject[ this.objectIndex ];
 				}
-				nodeProperty = targetObject.skeleton.bones;
+
 			}
-			else {
-				nodeProperty = targetObject[ this.propertyName ];
-				if( ! nodeProperty ) {
-					console.error( "  trying to update property for track: " + this.nodeName + '.' + this.propertyName + " but it wasn't found.", targetObject );				
-					return;
-				}
+
+	 		// special case mappings
+	 		var nodeProperty = targetObject[ this.propertyName ];
+			if( ! nodeProperty ) {
+				console.error( "  trying to update property for track: " + this.nodeName + '.' + this.propertyName + " but it wasn't found.", targetObject );				
+				return;
 			}
 
 			// access a sub element of the property array (only primitives are supported right now)
@@ -141,20 +159,6 @@ THREE.PropertyBinding.prototype = {
 						}
 					}
 				}
-				else if( this.propertyName === "bones" ) {
-					// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
-					
-					// support resolving morphTarget names into indices.
-					console.log( "  resolving bone name: ", this.propertyIndex );
-					for( var i = 0; i < this.node.skeleton.bones.length; i ++ ) {
-						if( this.node.skeleton.bones[i].name === this.propertyIndex ) {
-							console.log( "  resolved to index: ", i );
-							this.propertyIndex = i;
-							break;
-						}
-					}
-				}
-
 
 				//console.log( '  update property array ' + this.propertyName + '[' + this.propertyIndex + '] via assignment.' );				
 				this.internalApply = function() {
@@ -221,12 +225,12 @@ THREE.PropertyBinding.parseTrackName = function( trackName ) {
 	//    nodeName.property[accessor]
 	//    nodeName.material.property[accessor]
 	//    uuid.property[accessor]
-	//    uuid.material.property[accessor]
+	//    uuid.objectName[objectIndex].propertyName[propertyIndex]
 	//    parentName/nodeName.property
 	//    parentName/parentName/nodeName.property[index]
 	// created and tested via https://regex101.com/#javascript
 
-	var re = /^(([\w]+\/)*)([\w-]+)?(\.material(\[([\w]+)\])?)?(\.([\w]+)(\[([\w]+)\])?)?$/; 
+	var re = /^(([\w]+\/)*)([\w-]+)?(\.([\w]+)(\[([\w]+)\])?)?(\.([\w]+)(\[([\w]+)\])?)$/; 
 	var matches = re.exec(trackName);
 
 	if( ! matches ) {
@@ -238,12 +242,12 @@ THREE.PropertyBinding.parseTrackName = function( trackName ) {
     }
 
 	var results = {
-		directoryName: matches[0],
+		directoryName: matches[1],
 		nodeName: matches[3], 	// allowed to be null, specified root node.
-		material: ( matches[4] && matches[4].length > 0 ),
-		materialIndex: matches[6],
-		propertyName: matches[8],
-		propertyIndex: matches[10]	// allowed to be null, specifies that the whole property is set.
+		objectName: matches[5],
+		objectIndex: matches[7],
+		propertyName: matches[9],
+		propertyIndex: matches[11]	// allowed to be null, specifies that the whole property is set.
 	};
 
 	//console.log( "PropertyBinding.parseTrackName", trackName, results, matches );