Browse Source

GLTFExporter: Basic support for keyframe animation.

Don McCurdy 7 years ago
parent
commit
fda56541b2
2 changed files with 112 additions and 15 deletions
  1. 1 0
      docs/examples/exporters/GLTFExporter.html
  2. 111 15
      examples/js/exporters/GLTFExporter.js

+ 1 - 0
docs/examples/exporters/GLTFExporter.html

@@ -80,6 +80,7 @@ exporter.parse( [ scene1, object1, object2, scene2 ], ...)
 			<li>truncateDrawRange - bool. Export just the attributes within the drawRange, if defined, instead of exporting the whole array. Default is true.</li>
 			<li>binary - bool. Export in binary (.glb) format, returning an ArrayBuffer. Default is false.</li>
 			<li>embedImages - bool. Export with images embedded into the glTF asset. Default is true.</li>
+			<li>animations - Array<[page:AnimationClip AnimationClip]>. List of animations to be included in the export.
 		</ul>
 		</div>
 		<div>

+ 111 - 15
examples/js/exporters/GLTFExporter.js

@@ -39,6 +39,13 @@ var THREE_TO_WEBGL = {
 	1008: WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR
 };
 
+var PATH_PROPERTIES = {
+	scale: 'scale',
+	position: 'translation',
+	quaternion: 'rotation',
+	morphTargetInfluences: 'weights'
+};
+
 //------------------------------------------------------------------------------
 // GLTF Exporter
 //------------------------------------------------------------------------------
@@ -53,8 +60,6 @@ THREE.GLTFExporter.prototype = {
 	 * @param  {THREE.Scene or [THREE.Scenes]} input   THREE.Scene or Array of THREE.Scenes
 	 * @param  {Function} onDone  Callback on completed
 	 * @param  {Object} options options
-	 *                          trs: Exports position, rotation and scale instead of matrix
-	 *                          binary: Exports `.glb` as ArrayBuffer, instead of `.gltf` as JSON
 	 */
 	parse: function ( input, onDone, options ) {
 
@@ -62,7 +67,8 @@ THREE.GLTFExporter.prototype = {
 			trs: false,
 			onlyVisible: true,
 			truncateDrawRange: true,
-			embedImages: true
+			embedImages: true,
+			animations: []
 		};
 
 		options = Object.assign( {}, DEFAULT_OPTIONS, options );
@@ -80,6 +86,7 @@ THREE.GLTFExporter.prototype = {
 
 		var byteOffset = 0;
 		var dataViews = [];
+		var nodeMap = {};
 		var cachedData = {
 
 			images: {},
@@ -136,8 +143,8 @@ THREE.GLTFExporter.prototype = {
 		}
 
 		/**
-		 * Get the min and he max vectors from the given attribute
-		 * @param  {THREE.WebGLAttribute} attribute Attribute to find the min/max
+		 * Get the min and max vectors from the given attribute
+		 * @param  {THREE.BufferAttribute} attribute Attribute to find the min/max
 		 * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
 		 */
 		function getMinMax( attribute ) {
@@ -232,12 +239,14 @@ THREE.GLTFExporter.prototype = {
 
 		/**
 		 * Process and generate a BufferView
-		 * @param  {[type]} data [description]
-		 * @return {[type]}      [description]
+		 * @param  {THREE.BufferAttribute} data
+		 * @param  {number} componentType
+		 * @param  {number} start
+		 * @param  {number} count
+		 * @param  {number} target (Optional) Target usage of the BufferView
+		 * @return {Object}
 		 */
-		function processBufferView( data, componentType, start, count ) {
-
-			var isVertexAttributes = componentType === WEBGL_CONSTANTS.FLOAT;
+		function processBufferView( data, componentType, start, count, target ) {
 
 			if ( ! outputJSON.bufferViews ) {
 
@@ -255,11 +264,12 @@ THREE.GLTFExporter.prototype = {
 				buffer: processBuffer( data, componentType, start, count ),
 				byteOffset: byteOffset,
 				byteLength: byteLength,
-				byteStride: data.itemSize * componentSize,
-				target: isVertexAttributes ? WEBGL_CONSTANTS.ARRAY_BUFFER : WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER
+				byteStride: data.itemSize * componentSize
 
 			};
 
+			if ( target !== undefined ) gltfBufferView.target = target;
+
 			byteOffset += byteLength;
 
 			outputJSON.bufferViews.push( gltfBufferView );
@@ -278,7 +288,8 @@ THREE.GLTFExporter.prototype = {
 
 		/**
 		 * Process attribute to generate an accessor
-		 * @param  {THREE.WebGLAttribute} attribute Attribute to process
+		 * @param  {THREE.BufferAttribute} attribute Attribute to process
+		 * @param  {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
 		 * @return {Integer}           Index of the processed accessor on the "accessors" array
 		 */
 		function processAccessor( attribute, geometry ) {
@@ -325,14 +336,25 @@ THREE.GLTFExporter.prototype = {
 			var count = attribute.count;
 
 			// @TODO Indexed buffer geometry with drawRange not supported yet
-			if ( options.truncateDrawRange && geometry.index === null ) {
+			if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) {
 
 				start = geometry.drawRange.start;
 				count = geometry.drawRange.count !== Infinity ? geometry.drawRange.count : attribute.count;
 
 			}
 
-			var bufferView = processBufferView( attribute, componentType, start, count );
+			var bufferViewTarget;
+
+			// If geometry isn't provided, don't infer the target usage of the bufferView. For
+			// animation samplers, target must not be set.
+			if ( geometry !== undefined ) {
+
+				var isVertexAttributes = componentType === WEBGL_CONSTANTS.FLOAT;
+				bufferViewTarget = isVertexAttributes ? WEBGL_CONSTANTS.ARRAY_BUFFER : WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER;
+
+			}
+
+			var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget );
 
 			var gltfAccessor = {
 
@@ -829,6 +851,73 @@ THREE.GLTFExporter.prototype = {
 
 		}
 
+		/**
+		 * Creates glTF animation entry from AnimationClip object.
+		 *
+		 * Status:
+		 * - Only properties listed in PATH_PROPERTIES may be animated.
+		 * - Only LINEAR and STEP interpolation currently supported.
+		 *
+		 * @param {THREE.AnimationClip} clip
+		 * @param {THREE.Object3D} root
+		 * @return {number}
+		 */
+		function processAnimation ( clip, root ) {
+
+			if ( ! outputJSON.animations ) {
+
+				outputJSON.animations = [];
+
+			}
+
+			var channels = [];
+			var samplers = [];
+
+			for ( var i = 0; i < clip.tracks.length; ++ i ) {
+
+				var track = clip.tracks[ i ];
+				var trackBinding = THREE.PropertyBinding.parseTrackName( track.name );
+				var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
+				var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];
+
+				if ( ! trackNode || ! trackProperty ) {
+
+					console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
+
+				}
+
+				samplers.push( {
+
+					input: processAccessor( new THREE.BufferAttribute( track.times, 1 ) ),
+					output: processAccessor( new THREE.BufferAttribute( track.values, 1 ) ),
+					interpolation: track.interpolation === THREE.InterpolateDiscrete ? 'STEP' : 'LINEAR'
+
+				} );
+
+				channels.push( {
+
+					sampler: samplers.length - 1,
+					target: {
+						node: nodeMap[ trackNode.uuid ],
+						path: trackProperty
+					}
+
+				} );
+
+			}
+
+			outputJSON.animations.push( {
+
+				name: clip.name || 'clip_' + outputJSON.animations.length,
+				samplers: samplers,
+				channels: channels
+
+			} );
+
+			return outputJSON.animations.length - 1;
+
+		}
+
 		/**
 		 * Process Object3D node
 		 * @param  {THREE.Object3D} node Object3D to processNode
@@ -995,6 +1084,7 @@ THREE.GLTFExporter.prototype = {
 					if ( node !== null ) {
 
 						nodes.push( node );
+						nodeMap[ child.uuid ] = node;
 
 					}
 
@@ -1057,6 +1147,12 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
+			for ( var i = 0; i < options.animations.length; ++ i ) {
+
+				processAnimation( options.animations[ i ], input[ 0 ] );
+
+			}
+
 		}
 
 		processInput( input );