Преглед изворни кода

Provide AnimationClip objects from GLTFLoader.

Replaces GLTFLoader.Animations, GLTFAnimation and GLTFInterpolator, and
adds support for AnimationMixer / AnimationAction controls.
Don McCurdy пре 8 година
родитељ
комит
9aab3c6b98
2 измењених фајлова са 77 додато и 239 уклоњено
  1. 59 218
      examples/js/loaders/GLTFLoader.js
  2. 18 21
      examples/webgl_loader_gltf.html

+ 59 - 218
examples/js/loaders/GLTFLoader.js

@@ -54,14 +54,14 @@ THREE.GLTFLoader = ( function () {
 
 			} );
 
-			parser.parse( function ( scene, cameras, animations ) {
+			parser.parse( function ( scene, cameras, clips ) {
 
 				console.timeEnd( 'GLTFLoader' );
 
 				var glTF = {
 					"scene": scene,
 					"cameras": cameras,
-					"animations": animations
+					"clips": clips
 				};
 
 				callback( glTF );
@@ -241,233 +241,66 @@ THREE.GLTFLoader = ( function () {
 	};
 
 
-	/* GLTFANIMATION */
+	/* ANIMATION */
 
-	GLTFLoader.Animations = new GLTFRegistry();
-
-	// Construction/initialization
-	function GLTFAnimation( interps ) {
-
-		this.running = false;
-		this.loop = false;
-		this.duration = 0;
-		this.startTime = 0;
-		this.interps = [];
-
-		this.uuid = THREE.Math.generateUUID();
-
-		if ( interps ) {
-
-			this.createInterpolators( interps );
-
-		}
-
-	}
-
-	GLTFAnimation.prototype.createInterpolators = function ( interps ) {
+	function createClip( name, interps ) {
+		var tracks = [];
 
 		for ( var i = 0, len = interps.length; i < len; i ++ ) {
 
-			var interp = new GLTFInterpolator( interps[ i ] );
-			this.interps.push( interp );
-			this.duration = Math.max( this.duration, interp.duration );
-
-		}
-
-	};
-
-	// Start/stop
-	GLTFAnimation.prototype.play = function () {
-
-		if ( this.running )
-			return;
-
-		this.startTime = Date.now();
-		this.running = true;
-		GLTFLoader.Animations.add( this.uuid, this );
-
-	};
-
-	GLTFAnimation.prototype.stop = function () {
-
-		this.running = false;
-		GLTFLoader.Animations.remove( this.uuid );
-
-	};
-
-	// Update - drive key frame evaluation
-	GLTFAnimation.prototype.update = function () {
-
-		if ( ! this.running ) return;
-
-		var now = Date.now();
-		var deltat = ( now - this.startTime ) / 1000;
-		var t = deltat % this.duration;
-		var nCycles = Math.floor( deltat / this.duration );
-		var interps = this.interps;
-
-		if ( nCycles >= 1 && ! this.loop ) {
-
-			this.running = false;
-
-			for ( var i = 0, l = interps.length; i < l; i ++ ) {
-
-				interps[ i ].interp( this.duration );
-
-			}
-
-			this.stop();
-
-		} else {
-
-			for ( var i = 0, l = interps.length; i < l; i ++ ) {
-
-				interps[ i ].interp( t );
-
-			}
-
-		}
-
-	};
-
-	/* GLTFINTERPOLATOR */
-
-	function GLTFInterpolator( param ) {
-
-		this.keys = param.keys;
-		this.values = param.values;
-		this.count = param.count;
-		this.type = param.type;
-		this.path = param.path;
-		this.isRot = false;
-
-		var node = param.target;
-		node.updateMatrix();
-		node.matrixAutoUpdate = true;
-		this.targetNode = node;
-
-		switch ( param.path ) {
-
-			case "translation" :
-
-				this.target = node.position;
-				this.originalValue = node.position.clone();
-				break;
-
-			case "rotation" :
+			validateInterpolator( interps[ i ] );
 
-				this.target = node.quaternion;
-				this.originalValue = node.quaternion.clone();
-				this.isRot = true;
-				break;
-
-			case "scale" :
-
-				this.target = node.scale;
-				this.originalValue = node.scale.clone();
-				break;
+			interps[ i ].target.updateMatrix();
+			interps[ i ].target.matrixAutoUpdate = true;
 
+			tracks.push( new THREE.KeyframeTrack(
+				interps[ i ].name,
+				interps[ i ].times,
+				interps[ i ].values,
+				interps[ i ].type
+			) );
 		}
 
-		this.duration = this.keys[ this.count - 1 ];
-
-		this.vec1 = new THREE.Vector3();
-		this.vec2 = new THREE.Vector3();
-		this.vec3 = new THREE.Vector3();
-		this.quat1 = new THREE.Quaternion();
-		this.quat2 = new THREE.Quaternion();
-		this.quat3 = new THREE.Quaternion();
-
+		return new THREE.AnimationClip( name, undefined, tracks );
 	}
 
-	//Interpolation and tweening methods
-	GLTFInterpolator.prototype.interp = function ( t ) {
-
-		if ( t == this.keys[ 0 ] ) {
-
-			if ( this.isRot ) {
-
-				this.quat3.fromArray( this.values );
-
-			} else {
-
-				this.vec3.fromArray( this.values );
-
-			}
-
-		} else if ( t < this.keys[ 0 ] ) {
+	/**
+	 * Interp times are frequently non-sequential in the monster and cesium man
+	 * models. That's probably a sign that the exporter is misbehaving, but to
+	 * keep this backward-compatible we can swallow the errors.
+	 */
+	function validateInterpolator( interp ) {
 
-			if ( this.isRot ) {
+		var times = [];
+		var values = [];
+		var prevTime = null;
+		var currTime = null;
 
-				this.quat1.copy( this.originalValue );
-				this.quat2.fromArray( this.values );
-				THREE.Quaternion.slerp( this.quat1, this.quat2, this.quat3, t / this.keys[ 0 ] );
+		var stride = interp.values.length / interp.times.length;
 
-			} else {
-
-				this.vec3.copy( this.originalValue );
-				this.vec2.fromArray( this.values );
-				this.vec3.lerp( this.vec2, t / this.keys[ 0 ] );
-
-			}
-
-		} else if ( t >= this.keys[ this.count - 1 ] ) {
-
-			if ( this.isRot ) {
-
-				this.quat3.fromArray( this.values, ( this.count - 1 ) * 4 );
+		for ( var i = 0; i < interp.times.length; i ++ ) {
 
-			} else {
-
-				this.vec3.fromArray( this.values, ( this.count - 1 ) * 3 );
-
-			}
-
-		} else {
+			currTime = interp.times[ i ];
 
-			for ( var i = 0; i < this.count - 1; i ++ ) {
+			if (prevTime !== null && prevTime <= currTime) {
 
-				var key1 = this.keys[ i ];
-				var key2 = this.keys[ i + 1 ];
+				times.push( currTime );
 
-				if ( t >= key1 && t <= key2 ) {
+				for ( var j = 0; j < stride; j++ ) {
 
-					if ( this.isRot ) {
-
-						this.quat1.fromArray( this.values, i * 4 );
-						this.quat2.fromArray( this.values, ( i + 1 ) * 4 );
-						THREE.Quaternion.slerp( this.quat1, this.quat2, this.quat3, ( t - key1 ) / ( key2 - key1 ) );
-
-					} else {
-
-						this.vec3.fromArray( this.values, i * 3 );
-						this.vec2.fromArray( this.values, ( i + 1 ) * 3 );
-						this.vec3.lerp( this.vec2, ( t - key1 ) / ( key2 - key1 ) );
-
-					}
+					values.push( interp.values[ i * stride + j ] );
 
 				}
 
 			}
 
+			prevTime = currTime;
 		}
 
-		if ( this.target ) {
-
-			if ( this.isRot ) {
-
-				this.target.copy( this.quat3 );
-
-			} else {
-
-				this.target.copy( this.vec3 );
-
-			}
-
-		}
-
-	};
+		interp.times = new Float32Array( times );
+		interp.values = new Float32Array( values );
 
+	}
 
 	/*********************************/
 	/********** INTERNALS ************/
@@ -539,6 +372,16 @@ THREE.GLTFLoader = ( function () {
 		'MAT4': 16
 	};
 
+	var PATH_PROPERTIES = {
+		scale: 'scale',
+		translation: 'position',
+		rotation: 'quaternion'
+	};
+
+	var INTERPOLATION = {
+		LINEAR: THREE.InterpolateLinear
+	};
+
 	/* UTILITY FUNCTIONS */
 
 	function _each( object, callback, thisObj ) {
@@ -798,7 +641,7 @@ THREE.GLTFLoader = ( function () {
 
 			"scenes",
 			"cameras",
-			"animations"
+			"clips"
 
 		] ).then( function ( dependencies ) {
 
@@ -813,16 +656,16 @@ THREE.GLTFLoader = ( function () {
 
 			}
 
-			var animations = [];
+			var clips = [];
 
-			for ( var name in dependencies.animations ) {
+			for ( var name in dependencies.clips ) {
 
-				var animation = dependencies.animations[ name ];
-				animations.push( animation );
+				var clip = dependencies.clips[ name ];
+				clips.push( clip );
 
 			}
 
-			callback( scene, cameras, animations );
+			callback( scene, cameras, clips);
 
 		}.bind( this ) );
 
@@ -1435,7 +1278,9 @@ THREE.GLTFLoader = ( function () {
 
 	};
 
-	GLTFParser.prototype.loadAnimations = function () {
+
+
+	GLTFParser.prototype.loadClips = function () {
 
 		var scope = this;
 
@@ -1470,12 +1315,11 @@ THREE.GLTFLoader = ( function () {
 						if ( node ) {
 
 							var interp = {
-								keys: inputAccessor.array,
+								times: inputAccessor.array,
 								values: outputAccessor.array,
-								count: inputAccessor.count,
 								target: node,
-								path: target.path,
-								type: sampler.interpolation
+								type: INTERPOLATION[ sampler.interpolation ],
+								name: node.name + '.' + PATH_PROPERTIES[ target.path ]
 							};
 
 							interps.push( interp );
@@ -1486,10 +1330,7 @@ THREE.GLTFLoader = ( function () {
 
 				}
 
-				var _animation = new GLTFAnimation( interps );
-				_animation.name = "animation_" + animationId;
-
-				return _animation;
+				return createClip( "animation_" + animationId, interps );
 
 			} );
 

+ 18 - 21
examples/webgl_loader_gltf.html

@@ -137,6 +137,8 @@
 			var cameraNames = [];
 			var defaultCamera = null;
 			var gltf = null;
+			var mixer = null;
+			var clock = new THREE.Clock();
 
 			function onload() {
 
@@ -312,17 +314,18 @@
 
 					}
 
-					if (gltf.animations && gltf.animations.length) {
+					if (gltf.clips && gltf.clips.length) {
 
-						var i, len = gltf.animations.length;
+						mixer = new THREE.AnimationMixer(object);
+
+						var i, len = gltf.clips.length;
 						for (i = 0; i < len; i++) {
-							var animation = gltf.animations[i];
-							animation.loop = true;
+							var clip = gltf.clips[i];
 							// There's .3333 seconds junk at the tail of the Monster animation that
 							// keeps it from looping cleanly. Clip it at 3 seconds
 							if (sceneInfo.animationTime)
-								animation.duration = sceneInfo.animationTime;
-							animation.play();
+								clip.duration = sceneInfo.animationTime;
+							mixer.clipAction(clip).play();
 						}
 					}
 
@@ -353,7 +356,7 @@
 
 			function animate() {
 				requestAnimationFrame( animate );
-				THREE.GLTFLoader.Animations.update();
+				if (mixer) mixer.update(clock.getDelta());
 				THREE.GLTFLoader.Shaders.update(scene, camera);
 				if (cameraIndex == 0)
 					orbitControls.update();
@@ -513,16 +516,17 @@
 
 			function toggleAnimations() {
 
-				var i, len = gltf.animations.length;
+				var i, len = gltf.clips.length;
 
 				for (i = 0; i < len; i++) {
 
-					var animation = gltf.animations[i];
+					var clip = gltf.clips[i];
+					var action = mixer.existingAction( clip );
 
-					if (animation.running) {
-						animation.stop();
+					if (action.isRunning()) {
+						action.stop();
 					} else {
-						animation.play();
+						action.play();
 					}
 
 				}
@@ -549,17 +553,10 @@
 				cameraNames = [];
 				defaultCamera = null;
 
-				if (!loader || !gltf.animations)
+				if (!loader || !mixer)
 					return;
 
-				var i, len = gltf.animations.length;
-
-				for (i = 0; i < len; i++) {
-					var animation = gltf.animations[i];
-					if (animation.running) {
-						animation.stop();
-					}
-				}
+				mixer.stopAllAction();
 
 			}