GLTFExporter.js 36.8 KB
Newer Older
F
Fernando Serrano 已提交
1 2
/**
 * @author fernandojsg / http://fernandojsg.com
3 4
 * @author Don McCurdy / https://www.donmccurdy.com
 * @author Takahiro / https://github.com/takahirox
F
Fernando Serrano 已提交
5 6
 */

M
Mugen87 已提交
7 8 9
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
10 11
var WEBGL_CONSTANTS = {
	POINTS: 0x0000,
F
Fernando Serrano 已提交
12 13 14 15 16 17
	LINES: 0x0001,
	LINE_LOOP: 0x0002,
	LINE_STRIP: 0x0003,
	TRIANGLES: 0x0004,
	TRIANGLE_STRIP: 0x0005,
	TRIANGLE_FAN: 0x0006,
18 19 20 21 22 23 24 25 26

	UNSIGNED_BYTE: 0x1401,
	UNSIGNED_SHORT: 0x1403,
	FLOAT: 0x1406,
	UNSIGNED_INT: 0x1405,
	ARRAY_BUFFER: 0x8892,
	ELEMENT_ARRAY_BUFFER: 0x8893,

	NEAREST: 0x2600,
F
Fernando Serrano 已提交
27 28 29 30 31
	LINEAR: 0x2601,
	NEAREST_MIPMAP_NEAREST: 0x2700,
	LINEAR_MIPMAP_NEAREST: 0x2701,
	NEAREST_MIPMAP_LINEAR: 0x2702,
	LINEAR_MIPMAP_LINEAR: 0x2703
32 33 34 35
};

var THREE_TO_WEBGL = {
	// @TODO Replace with computed property name [THREE.*] when available on es6
F
Fernando Serrano 已提交
36
	1003: WEBGL_CONSTANTS.NEAREST,
37 38 39 40
	1004: WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST,
	1005: WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR,
	1006: WEBGL_CONSTANTS.LINEAR,
	1007: WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST,
F
Fernando Serrano 已提交
41
	1008: WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR
M
Mugen87 已提交
42
};
43

44 45 46 47 48 49 50
var PATH_PROPERTIES = {
	scale: 'scale',
	position: 'translation',
	quaternion: 'rotation',
	morphTargetInfluences: 'weights'
};

F
Fernando Serrano 已提交
51 52 53
//------------------------------------------------------------------------------
// GLTF Exporter
//------------------------------------------------------------------------------
54
THREE.GLTFExporter = function () {};
F
Fernando Serrano 已提交
55 56 57 58

THREE.GLTFExporter.prototype = {

	constructor: THREE.GLTFExporter,
F
Fernando Serrano 已提交
59

F
Fernando Serrano 已提交
60 61 62
	/**
	 * Parse scenes and generate GLTF output
	 * @param  {THREE.Scene or [THREE.Scenes]} input   THREE.Scene or Array of THREE.Scenes
F
Fernando Serrano 已提交
63 64
	 * @param  {Function} onDone  Callback on completed
	 * @param  {Object} options options
F
Fernando Serrano 已提交
65
	 */
F
Fernando Serrano 已提交
66 67
	parse: function ( input, onDone, options ) {

68 69
		var DEFAULT_OPTIONS = {
			trs: false,
70
			onlyVisible: true,
71
			truncateDrawRange: true,
72
			embedImages: true,
73
			animations: [],
74
			forceIndices: false,
75
			forcePowerOfTwoTextures: false
76 77 78 79
		};

		options = Object.assign( {}, DEFAULT_OPTIONS, options );

D
Don McCurdy 已提交
80 81 82 83 84 85 86
		if ( options.animations.length > 0 ) {

			// Only TRS properties, and not matrices, may be targeted by animation.
			options.trs = true;

		}

F
Fernando Serrano 已提交
87
		var outputJSON = {
F
Fernando Serrano 已提交
88

F
Fernando Serrano 已提交
89
			asset: {
F
Fernando Serrano 已提交
90

F
Fernando Serrano 已提交
91
				version: "2.0",
M
Mr.doob 已提交
92
				generator: "THREE.GLTFExporter"
F
Fernando Serrano 已提交
93

M
Mr.doob 已提交
94
			}
F
Fernando Serrano 已提交
95

M
Mr.doob 已提交
96
		};
F
Fernando Serrano 已提交
97 98

		var byteOffset = 0;
99 100
		var buffers = [];
		var pending = [];
T
Takahiro 已提交
101
		var nodeMap = new Map();
D
Don McCurdy 已提交
102
		var skins = [];
103
		var extensionsUsed = {};
104 105
		var cachedData = {

106 107
			materials: new Map(),
			textures: new Map()
108 109

		};
F
Fernando Serrano 已提交
110

111 112
		var cachedCanvas;

F
Fernando Serrano 已提交
113 114 115
		/**
		 * Compare two arrays
		 */
F
Fernando Serrano 已提交
116 117 118 119 120 121
		/**
		 * Compare two arrays
		 * @param  {Array} array1 Array 1 to compare
		 * @param  {Array} array2 Array 2 to compare
		 * @return {Boolean}        Returns true if both arrays are equal
		 */
M
Mr.doob 已提交
122
		function equalArray( array1, array2 ) {
F
Fernando Serrano 已提交
123

M
Mugen87 已提交
124
			return ( array1.length === array2.length ) && array1.every( function ( element, index ) {
F
Fernando Serrano 已提交
125

M
Mr.doob 已提交
126
				return element === array2[ index ];
F
Fernando Serrano 已提交
127

M
Mugen87 已提交
128
			} );
F
Fernando Serrano 已提交
129

F
Fernando Serrano 已提交
130 131
		}

132 133 134 135 136
		/**
		 * Converts a string to an ArrayBuffer.
		 * @param  {string} text
		 * @return {ArrayBuffer}
		 */
137
		function stringToArrayBuffer( text ) {
138 139 140 141 142 143 144

			if ( window.TextEncoder !== undefined ) {

				return new TextEncoder().encode( text ).buffer;

			}

145
			var array = new Uint8Array( new ArrayBuffer( text.length ) );
146

147
			for ( var i = 0, il = text.length; i < il; i ++ ) {
148

149
				var value = text.charCodeAt( i );
150

151
				// Replacing multi-byte character with space(0x20).
F
Fernando Serrano 已提交
152
				array[ i ] = value > 0xFF ? 0x20 : value;
153 154 155

			}

156
			return array.buffer;
157 158 159

		}

F
Fernando Serrano 已提交
160
		/**
161
		 * Get the min and max vectors from the given attribute
T
Takahiro 已提交
162 163 164
		 * @param  {THREE.BufferAttribute} attribute Attribute to find the min/max in range from start to start + count
		 * @param  {Integer} start
		 * @param  {Integer} count
F
Fernando Serrano 已提交
165 166
		 * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
		 */
T
Takahiro 已提交
167
		function getMinMax( attribute, start, count ) {
F
Fernando Serrano 已提交
168

F
Fernando Serrano 已提交
169
			var output = {
F
Fernando Serrano 已提交
170

F
Fernando Serrano 已提交
171 172
				min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
				max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
F
Fernando Serrano 已提交
173

F
Fernando Serrano 已提交
174 175
			};

T
Takahiro 已提交
176
			for ( var i = start; i < start + count; i ++ ) {
F
Fernando Serrano 已提交
177

M
Mugen87 已提交
178
				for ( var a = 0; a < attribute.itemSize; a ++ ) {
F
Fernando Serrano 已提交
179

F
Fernando Serrano 已提交
180
					var value = attribute.array[ i * attribute.itemSize + a ];
F
Fernando Serrano 已提交
181 182 183
					output.min[ a ] = Math.min( output.min[ a ], value );
					output.max[ a ] = Math.max( output.max[ a ], value );

F
Fernando Serrano 已提交
184
				}
F
Fernando Serrano 已提交
185

F
Fernando Serrano 已提交
186 187
			}

F
Fernando Serrano 已提交
188
			return output;
M
Mugen87 已提交
189

F
Fernando Serrano 已提交
190 191
		}

192 193 194 195 196 197 198 199 200 201 202 203 204
		/**
		 * Checks if image size is POT.
		 *
		 * @param {Image} image The image to be checked.
		 * @returns {Boolean} Returns true if image size is POT.
		 *
		 */
		function isPowerOfTwo( image ) {

			return THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height );

		}

205 206 207 208 209 210 211 212 213 214
		/**
		 * Get the required size + padding for a buffer, rounded to the next 4-byte boundary.
		 * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
		 *
		 * @param {Integer} bufferSize The size the original buffer.
		 * @returns {Integer} new buffer size with required padding.
		 *
		 */
		function getPaddedBufferSize( bufferSize ) {

215
			return Math.ceil( bufferSize / 4 ) * 4;
216 217

		}
M
Mugen87 已提交
218

F
Fernando Serrano 已提交
219
		/**
220 221
		 * Returns a buffer aligned to 4-byte boundary.
		 *
F
Fernando Serrano 已提交
222
		 * @param {ArrayBuffer} arrayBuffer Buffer to pad
223
		 * @param {Integer} paddingByte (Optional)
F
Fernando Serrano 已提交
224 225
		 * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
		 */
226
		function getPaddedArrayBuffer( arrayBuffer, paddingByte ) {
227

228
			paddingByte = paddingByte || 0;
229

F
Fernando Serrano 已提交
230
			var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength );
231

232
			if ( paddedLength !== arrayBuffer.byteLength ) {
233

234 235
				var array = new Uint8Array( paddedLength );
				array.set( new Uint8Array( arrayBuffer ) );
236

237
				if ( paddingByte !== 0 ) {
238

T
Takahiro 已提交
239
					for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) {
240

241
						array[ i ] = paddingByte;
242 243 244 245

					}

				}
246

247
				return array.buffer;
F
Fernando Serrano 已提交
248 249 250 251 252 253 254

			}

			return arrayBuffer;

		}

F
Fernando Serrano 已提交
255
		/**
F
Fernando Serrano 已提交
256
		 * Process a buffer to append to the default one.
257 258
		 * @param  {ArrayBuffer} buffer
		 * @return {Integer}
F
Fernando Serrano 已提交
259
		 */
260
		function processBuffer( buffer ) {
F
Fernando Serrano 已提交
261

M
Mugen87 已提交
262
			if ( ! outputJSON.buffers ) {
F
Fernando Serrano 已提交
263

264
				outputJSON.buffers = [ { byteLength: 0 } ];
F
Fernando Serrano 已提交
265

266
			}
F
Fernando Serrano 已提交
267

268 269
			// All buffers are merged before export.
			buffers.push( buffer );
F
Fernando Serrano 已提交
270

271
			return 0;
F
Fernando Serrano 已提交
272

273
		}
F
Fernando Serrano 已提交
274

275 276 277 278 279 280 281 282 283 284
		/**
		 * Process and generate a BufferView
		 * @param  {THREE.BufferAttribute} attribute
		 * @param  {number} componentType
		 * @param  {number} start
		 * @param  {number} count
		 * @param  {number} target (Optional) Target usage of the BufferView
		 * @return {Object}
		 */
		function processBufferView( attribute, componentType, start, count, target ) {
F
Fernando Serrano 已提交
285

286
			if ( ! outputJSON.bufferViews ) {
287

288
				outputJSON.bufferViews = [];
M
Mugen87 已提交
289

290
			}
F
Fernando Serrano 已提交
291

292
			// Create a new dataview and dump the attribute's array into it
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

			var componentSize;

			if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {

				componentSize = 1;

			} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {

				componentSize = 2;

			} else {

				componentSize = 4;

			}

310
			var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize );
311
			var dataView = new DataView( new ArrayBuffer( byteLength ) );
312
			var offset = 0;
F
Fernando Serrano 已提交
313

M
Mugen87 已提交
314
			for ( var i = start; i < start + count; i ++ ) {
F
Fernando Serrano 已提交
315

M
Mugen87 已提交
316
				for ( var a = 0; a < attribute.itemSize; a ++ ) {
F
Fernando Serrano 已提交
317

318 319
					// @TODO Fails on InterleavedBufferAttribute, and could probably be
					// optimized for normal BufferAttribute.
F
Fernando Serrano 已提交
320
					var value = attribute.array[ i * attribute.itemSize + a ];
F
Fernando Serrano 已提交
321

322
					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
F
Fernando Serrano 已提交
323

F
Fernando Serrano 已提交
324
						dataView.setFloat32( offset, value, true );
F
Fernando Serrano 已提交
325

326
					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) {
F
Fernando Serrano 已提交
327

S
selimbek 已提交
328
						dataView.setUint32( offset, value, true );
F
Fernando Serrano 已提交
329

330
					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
F
Fernando Serrano 已提交
331

F
Fernando Serrano 已提交
332
						dataView.setUint16( offset, value, true );
F
Fernando Serrano 已提交
333

334 335 336 337
					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {

						dataView.setUint8( offset, value );

F
Fernando Serrano 已提交
338
					}
F
Fernando Serrano 已提交
339

340
					offset += componentSize;
F
Fernando Serrano 已提交
341

F
Fernando Serrano 已提交
342
				}
F
Fernando Serrano 已提交
343

F
Fernando Serrano 已提交
344 345 346
			}

			var gltfBufferView = {
F
Fernando Serrano 已提交
347

348
				buffer: processBuffer( dataView.buffer ),
F
Fernando Serrano 已提交
349
				byteOffset: byteOffset,
350
				byteLength: byteLength
F
Fernando Serrano 已提交
351

F
Fernando Serrano 已提交
352 353
			};

354 355
			if ( target !== undefined ) gltfBufferView.target = target;

356 357 358
			if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {

				// Only define byteStride for vertex attributes.
359
				gltfBufferView.byteStride = attribute.itemSize * componentSize;
360 361 362

			}

363
			byteOffset += byteLength;
F
Fernando Serrano 已提交
364

F
Fernando Serrano 已提交
365
			outputJSON.bufferViews.push( gltfBufferView );
F
Fernando Serrano 已提交
366

367
			// @TODO Merge bufferViews where possible.
F
Fernando Serrano 已提交
368
			var output = {
F
Fernando Serrano 已提交
369

F
Fernando Serrano 已提交
370 371
				id: outputJSON.bufferViews.length - 1,
				byteLength: 0
F
Fernando Serrano 已提交
372

F
Fernando Serrano 已提交
373
			};
F
Fernando Serrano 已提交
374

F
Fernando Serrano 已提交
375
			return output;
F
Fernando Serrano 已提交
376

F
Fernando Serrano 已提交
377 378
		}

379 380 381 382 383
		/**
		 * Process and generate a BufferView from an image Blob.
		 * @param {Blob} blob
		 * @return {Promise<Integer>}
		 */
D
Don McCurdy 已提交
384
		function processBufferViewImage( blob ) {
385 386 387 388 389 390 391

			if ( ! outputJSON.bufferViews ) {

				outputJSON.bufferViews = [];

			}

D
Don McCurdy 已提交
392
			return new Promise( function ( resolve ) {
393 394 395

				var reader = new window.FileReader();
				reader.readAsArrayBuffer( blob );
D
Don McCurdy 已提交
396
				reader.onloadend = function () {
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411

					var buffer = getPaddedArrayBuffer( reader.result );

					var bufferView = {
						buffer: processBuffer( buffer ),
						byteOffset: byteOffset,
						byteLength: buffer.byteLength
					};

					byteOffset += buffer.byteLength;

					outputJSON.bufferViews.push( bufferView );

					resolve( outputJSON.bufferViews.length - 1 );

D
Don McCurdy 已提交
412
				};
413 414 415 416 417

			} );

		}

F
Fernando Serrano 已提交
418
		/**
F
Fernando Serrano 已提交
419
		 * Process attribute to generate an accessor
420 421
		 * @param  {THREE.BufferAttribute} attribute Attribute to process
		 * @param  {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
T
Takahiro 已提交
422 423
		 * @param  {Integer} start (Optional)
		 * @param  {Integer} count (Optional)
F
Fernando Serrano 已提交
424
		 * @return {Integer}           Index of the processed accessor on the "accessors" array
F
Fernando Serrano 已提交
425
		 */
T
Takahiro 已提交
426
		function processAccessor( attribute, geometry, start, count ) {
F
Fernando Serrano 已提交
427

D
Don McCurdy 已提交
428
			var types = {
F
Fernando Serrano 已提交
429

D
Don McCurdy 已提交
430 431 432 433 434
				1: 'SCALAR',
				2: 'VEC2',
				3: 'VEC3',
				4: 'VEC4',
				16: 'MAT4'
F
Fernando Serrano 已提交
435

D
Don McCurdy 已提交
436
			};
F
Fernando Serrano 已提交
437

438 439
			var componentType;

F
Fernando Serrano 已提交
440
			// Detect the component type of the attribute array (float, uint or ushort)
441
			if ( attribute.array.constructor === Float32Array ) {
F
Fernando Serrano 已提交
442

443
				componentType = WEBGL_CONSTANTS.FLOAT;
F
Fernando Serrano 已提交
444

445
			} else if ( attribute.array.constructor === Uint32Array ) {
F
Fernando Serrano 已提交
446

447
				componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
F
Fernando Serrano 已提交
448

449
			} else if ( attribute.array.constructor === Uint16Array ) {
F
Fernando Serrano 已提交
450

451
				componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
F
Fernando Serrano 已提交
452

453 454 455 456
			} else if ( attribute.array.constructor === Uint8Array ) {

				componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;

457
			} else {
F
Fernando Serrano 已提交
458

459
				throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' );
F
Fernando Serrano 已提交
460

461
			}
F
Fernando Serrano 已提交
462

T
Takahiro 已提交
463 464
			if ( start === undefined ) start = 0;
			if ( count === undefined ) count = attribute.count;
465 466

			// @TODO Indexed buffer geometry with drawRange not supported yet
467
			if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) {
M
Mugen87 已提交
468

T
Takahiro 已提交
469 470 471 472 473 474 475 476 477
				var end = start + count;
				var end2 = geometry.drawRange.count === Infinity
						? attribute.count
						: geometry.drawRange.start + geometry.drawRange.count;

				start = Math.max( start, geometry.drawRange.start );
				count = Math.min( end, end2 ) - start;

				if ( count < 0 ) count = 0;
M
Mugen87 已提交
478

479 480
			}

481
			// Skip creating an accessor if the attribute doesn't have data to export
F
Fernando Serrano 已提交
482
			if ( count === 0 ) {
483

484
				return null;
485 486 487

			}

T
Takahiro 已提交
488 489
			var minMax = getMinMax( attribute, start, count );

490 491 492 493 494 495
			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 ) {

T
Takahiro 已提交
496
				bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
497 498 499 500

			}

			var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget );
F
Fernando Serrano 已提交
501

F
Fernando Serrano 已提交
502
			var gltfAccessor = {
F
Fernando Serrano 已提交
503

F
Fernando Serrano 已提交
504 505 506
				bufferView: bufferView.id,
				byteOffset: bufferView.byteOffset,
				componentType: componentType,
507
				count: count,
F
Fernando Serrano 已提交
508 509
				max: minMax.max,
				min: minMax.min,
D
Don McCurdy 已提交
510
				type: types[ attribute.itemSize ]
F
Fernando Serrano 已提交
511

F
Fernando Serrano 已提交
512 513
			};

514 515 516 517 518 519
			if ( ! outputJSON.accessors ) {

				outputJSON.accessors = [];

			}

F
Fernando Serrano 已提交
520 521 522
			outputJSON.accessors.push( gltfAccessor );

			return outputJSON.accessors.length - 1;
F
Fernando Serrano 已提交
523

F
Fernando Serrano 已提交
524 525 526
		}

		/**
F
Fernando Serrano 已提交
527 528 529 530
		 * Process image
		 * @param  {Texture} map Texture to process
		 * @return {Integer}     Index of the processed texture in the "images" array
		 */
M
Mr.doob 已提交
531
		function processImage( map ) {
F
Fernando Serrano 已提交
532

533
			// @TODO Cache
534

M
Mugen87 已提交
535
			if ( ! outputJSON.images ) {
F
Fernando Serrano 已提交
536

F
Fernando Serrano 已提交
537
				outputJSON.images = [];
F
Fernando Serrano 已提交
538

F
Fernando Serrano 已提交
539 540
			}

541
			var mimeType = map.format === THREE.RGBAFormat ? 'image/png' : 'image/jpeg';
M
Mugen87 已提交
542
			var gltfImage = { mimeType: mimeType };
543

544
			if ( options.embedImages ) {
F
Fernando Serrano 已提交
545

546
				var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' );
547

548 549
				canvas.width = map.image.width;
				canvas.height = map.image.height;
550

551
				if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( map.image ) ) {
552

553
					console.warn( 'GLTFExporter: Resized non-power-of-two image.', map.image );
554 555 556 557 558 559

					canvas.width = THREE.Math.floorPowerOfTwo( canvas.width );
					canvas.height = THREE.Math.floorPowerOfTwo( canvas.height );

				}

560
				var ctx = canvas.getContext( '2d' );
561 562 563

				if ( map.flipY === true ) {

564
					ctx.translate( 0, canvas.height );
M
Mugen87 已提交
565
					ctx.scale( 1, - 1 );
566 567 568

				}

569
				ctx.drawImage( map.image, 0, 0, canvas.width, canvas.height );
570

571 572 573 574 575 576 577 578 579 580 581
				if ( options.binary === true ) {

					pending.push( new Promise( function ( resolve ) {

						canvas.toBlob( function ( blob ) {

							processBufferViewImage( blob ).then( function ( bufferViewIndex ) {

								gltfImage.bufferView = bufferViewIndex;

								resolve();
582

583 584 585 586 587 588 589 590 591 592 593
							} );

						}, mimeType );

					} ) );

				} else {

					gltfImage.uri = canvas.toDataURL( mimeType );

				}
F
Fernando Serrano 已提交
594

F
Fernando Serrano 已提交
595
			} else {
F
Fernando Serrano 已提交
596

597
				gltfImage.uri = map.image.src;
F
Fernando Serrano 已提交
598

F
Fernando Serrano 已提交
599 600 601 602
			}

			outputJSON.images.push( gltfImage );

T
Takahiro 已提交
603
			return outputJSON.images.length - 1;
F
Fernando Serrano 已提交
604

F
Fernando Serrano 已提交
605 606 607 608 609 610 611
		}

		/**
		 * Process sampler
		 * @param  {Texture} map Texture to process
		 * @return {Integer}     Index of the processed texture in the "samplers" array
		 */
M
Mr.doob 已提交
612
		function processSampler( map ) {
F
Fernando Serrano 已提交
613

M
Mugen87 已提交
614
			if ( ! outputJSON.samplers ) {
F
Fernando Serrano 已提交
615

F
Fernando Serrano 已提交
616
				outputJSON.samplers = [];
F
Fernando Serrano 已提交
617

F
Fernando Serrano 已提交
618 619 620
			}

			var gltfSampler = {
F
Fernando Serrano 已提交
621

622 623 624 625
				magFilter: THREE_TO_WEBGL[ map.magFilter ],
				minFilter: THREE_TO_WEBGL[ map.minFilter ],
				wrapS: THREE_TO_WEBGL[ map.wrapS ],
				wrapT: THREE_TO_WEBGL[ map.wrapT ]
F
Fernando Serrano 已提交
626

F
Fernando Serrano 已提交
627 628 629 630 631
			};

			outputJSON.samplers.push( gltfSampler );

			return outputJSON.samplers.length - 1;
F
Fernando Serrano 已提交
632

F
Fernando Serrano 已提交
633 634 635 636 637 638 639
		}

		/**
		 * Process texture
		 * @param  {Texture} map Map to process
		 * @return {Integer}     Index of the processed texture in the "textures" array
		 */
M
Mr.doob 已提交
640
		function processTexture( map ) {
F
Fernando Serrano 已提交
641

T
Takahiro 已提交
642
			if ( cachedData.textures.has( map ) ) {
T
Takahiro 已提交
643

T
Takahiro 已提交
644
				return cachedData.textures.get( map );
T
Takahiro 已提交
645 646 647

			}

M
Mugen87 已提交
648
			if ( ! outputJSON.textures ) {
F
Fernando Serrano 已提交
649

F
Fernando Serrano 已提交
650
				outputJSON.textures = [];
F
Fernando Serrano 已提交
651

F
Fernando Serrano 已提交
652 653 654
			}

			var gltfTexture = {
F
Fernando Serrano 已提交
655

F
Fernando Serrano 已提交
656 657
				sampler: processSampler( map ),
				source: processImage( map )
F
Fernando Serrano 已提交
658

F
Fernando Serrano 已提交
659 660 661 662
			};

			outputJSON.textures.push( gltfTexture );

T
Takahiro 已提交
663
			var index = outputJSON.textures.length - 1;
T
Takahiro 已提交
664
			cachedData.textures.set( map, index );
T
Takahiro 已提交
665 666

			return index;
F
Fernando Serrano 已提交
667

F
Fernando Serrano 已提交
668 669 670 671 672 673
		}

		/**
		 * Process material
		 * @param  {THREE.Material} material Material to process
		 * @return {Integer}      Index of the processed material in the "materials" array
F
Fernando Serrano 已提交
674
		 */
M
Mr.doob 已提交
675
		function processMaterial( material ) {
F
Fernando Serrano 已提交
676

T
Takahiro 已提交
677
			if ( cachedData.materials.has( material ) ) {
678

T
Takahiro 已提交
679
				return cachedData.materials.get( material );
680 681 682

			}

M
Mugen87 已提交
683
			if ( ! outputJSON.materials ) {
F
Fernando Serrano 已提交
684

F
Fernando Serrano 已提交
685
				outputJSON.materials = [];
F
Fernando Serrano 已提交
686

F
Fernando Serrano 已提交
687
			}
F
Fernando Serrano 已提交
688

689
			if ( material.isShaderMaterial ) {
690 691 692 693 694 695

				console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' );
				return null;

			}

F
Fernando Serrano 已提交
696
			// @QUESTION Should we avoid including any attribute that has the default value?
697
			var gltfMaterial = {
F
Fernando Serrano 已提交
698

699
				pbrMetallicRoughness: {}
F
Fernando Serrano 已提交
700

701
			};
702

703
			if ( material.isMeshBasicMaterial ) {
704 705 706 707 708

				gltfMaterial.extensions = { KHR_materials_unlit: {} };

				extensionsUsed[ 'KHR_materials_unlit' ] = true;

709
			} else if ( ! material.isMeshStandardMaterial ) {
710 711 712 713 714

				console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' );

			}

715 716
			// pbrMetallicRoughness.baseColorFactor
			var color = material.color.toArray().concat( [ material.opacity ] );
F
Fernando Serrano 已提交
717

M
Mugen87 已提交
718
			if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) {
719

720
				gltfMaterial.pbrMetallicRoughness.baseColorFactor = color;
721 722 723

			}

724
			if ( material.isMeshStandardMaterial ) {
725

726 727
				gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness;
728

729
			} else if ( material.isMeshBasicMaterial ) {
730 731 732 733

				gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.0;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.9;

M
Mr.doob 已提交
734
			} else {
735

M
Mugen87 已提交
736 737
				gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5;
F
Fernando Serrano 已提交
738

739
			}
740

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
			// pbrMetallicRoughness.metallicRoughnessTexture
			if ( material.metalnessMap || material.roughnessMap ) {

				if ( material.metalnessMap === material.roughnessMap ) {

					gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture = {

						index: processTexture( material.metalnessMap )

					};

				} else {

					console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' );

				}

			}

760 761
			// pbrMetallicRoughness.baseColorTexture
			if ( material.map ) {
762

763
				gltfMaterial.pbrMetallicRoughness.baseColorTexture = {
F
Fernando Serrano 已提交
764

765
					index: processTexture( material.map )
F
Fernando Serrano 已提交
766

767
				};
768

769
			}
770

771 772 773
			if ( material.isMeshBasicMaterial ||
				material.isLineBasicMaterial ||
				material.isPointsMaterial ) {
774 775 776 777

			} else {

				// emissiveFactor
778
				var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray();
F
Fernando Serrano 已提交
779

M
Mugen87 已提交
780
				if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) {
781 782 783

					gltfMaterial.emissiveFactor = emissive;

F
Fernando Serrano 已提交
784
				}
785 786 787 788 789 790

				// emissiveTexture
				if ( material.emissiveMap ) {

					gltfMaterial.emissiveTexture = {

791
						index: processTexture( material.emissiveMap )
792 793 794 795 796 797 798 799 800 801 802

					};

				}

			}

			// normalTexture
			if ( material.normalMap ) {

				gltfMaterial.normalTexture = {
F
Fernando Serrano 已提交
803

804
					index: processTexture( material.normalMap )
F
Fernando Serrano 已提交
805

806 807
				};

M
Mugen87 已提交
808
				if ( material.normalScale.x !== - 1 ) {
809 810 811

					if ( material.normalScale.x !== material.normalScale.y ) {

M
Mugen87 已提交
812
						console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' );
813 814 815 816 817 818 819

					}

					gltfMaterial.normalTexture.scale = material.normalScale.x;

				}

F
Fernando Serrano 已提交
820 821
			}

822 823 824 825
			// occlusionTexture
			if ( material.aoMap ) {

				gltfMaterial.occlusionTexture = {
F
Fernando Serrano 已提交
826

827
					index: processTexture( material.aoMap )
F
Fernando Serrano 已提交
828

829 830
				};

831 832 833 834 835 836
				if ( material.aoMapIntensity !== 1.0 ) {

					gltfMaterial.occlusionTexture.strength = material.aoMapIntensity;

				}

837 838 839
			}

			// alphaMode
840
			if ( material.transparent || material.alphaTest > 0.0 ) {
841

842
				gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK';
843

844 845
				// Write alphaCutoff if it's non-zero and different from the default (0.5).
				if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) {
846 847 848 849

					gltfMaterial.alphaCutoff = material.alphaTest;

				}
850 851 852 853

			}

			// doubleSided
F
Fernando Serrano 已提交
854
			if ( material.side === THREE.DoubleSide ) {
855

F
Fernando Serrano 已提交
856
				gltfMaterial.doubleSided = true;
857

F
Fernando Serrano 已提交
858 859
			}

860
			if ( material.name !== '' ) {
861

F
Fernando Serrano 已提交
862
				gltfMaterial.name = material.name;
863

F
Fernando Serrano 已提交
864 865
			}

F
Fernando Serrano 已提交
866
			outputJSON.materials.push( gltfMaterial );
F
Fernando Serrano 已提交
867

868
			var index = outputJSON.materials.length - 1;
T
Takahiro 已提交
869
			cachedData.materials.set( material, index );
870 871

			return index;
872

F
Fernando Serrano 已提交
873 874 875
		}

		/**
F
Fernando Serrano 已提交
876 877 878
		 * Process mesh
		 * @param  {THREE.Mesh} mesh Mesh to process
		 * @return {Integer}      Index of the processed mesh in the "meshes" array
F
Fernando Serrano 已提交
879 880
		 */
		function processMesh( mesh ) {
F
Fernando Serrano 已提交
881

F
Fernando Serrano 已提交
882
			var geometry = mesh.geometry;
F
Fernando Serrano 已提交
883

M
Mr.doob 已提交
884 885
			var mode;

886
			// Use the correct mode
887
			if ( mesh.isLineSegments ) {
888

889
				mode = WEBGL_CONSTANTS.LINES;
890

891
			} else if ( mesh.isLineLoop ) {
892

893
				mode = WEBGL_CONSTANTS.LINE_LOOP;
894

895
			} else if ( mesh.isLine ) {
896

897
				mode = WEBGL_CONSTANTS.LINE_STRIP;
898

899
			} else if ( mesh.isPoints ) {
900

901
				mode = WEBGL_CONSTANTS.POINTS;
902 903 904

			} else {

M
Mugen87 已提交
905
				if ( ! geometry.isBufferGeometry ) {
906 907 908 909 910 911 912

					var geometryTemp = new THREE.BufferGeometry();
					geometryTemp.fromGeometry( geometry );
					geometry = geometryTemp;

				}

913 914
				if ( mesh.drawMode === THREE.TriangleFanDrawMode ) {

F
Fernando Serrano 已提交
915
					console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' );
916
					mode = WEBGL_CONSTANTS.TRIANGLE_FAN;
917 918 919

				} else if ( mesh.drawMode === THREE.TriangleStripDrawMode ) {

920
					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINE_STRIP : WEBGL_CONSTANTS.TRIANGLE_STRIP;
921 922 923

				} else {

924
					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
925 926 927 928

				}

			}
F
Fernando Serrano 已提交
929

T
Takahiro 已提交
930
			var gltfMesh = {};
931

T
Takahiro 已提交
932 933 934
			var attributes = {};
			var primitives = [];
			var targets = [];
F
Fernando Serrano 已提交
935

F
Fernando Serrano 已提交
936 937
			// Conversion between attributes names in threejs and gltf spec
			var nameConversion = {
F
Fernando Serrano 已提交
938

F
Fernando Serrano 已提交
939 940
				uv: 'TEXCOORD_0',
				uv2: 'TEXCOORD_1',
941 942 943
				color: 'COLOR_0',
				skinWeight: 'WEIGHTS_0',
				skinIndex: 'JOINTS_0'
F
Fernando Serrano 已提交
944

F
Fernando Serrano 已提交
945 946
			};

947
			// @QUESTION Detect if .vertexColors = THREE.VertexColors?
F
Fernando Serrano 已提交
948
			// For every attribute create an accessor
F
Fernando Serrano 已提交
949 950
			for ( var attributeName in geometry.attributes ) {

F
Fernando Serrano 已提交
951
				var attribute = geometry.attributes[ attributeName ];
F
Fernando Serrano 已提交
952
				attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
D
Don McCurdy 已提交
953

954
				// JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
955
				var array = attribute.array;
956
				if ( attributeName === 'JOINTS_0' &&
957 958
					! ( array instanceof Uint16Array ) &&
					! ( array instanceof Uint8Array ) ) {
959 960

					console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
961
					attribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
962 963 964

				}

D
Don McCurdy 已提交
965
				if ( attributeName.substr( 0, 5 ) !== 'MORPH' ) {
F
Fernando Serrano 已提交
966

967
					var accessor = processAccessor( attribute, geometry );
968
					if ( accessor !== null ) {
D
Don McCurdy 已提交
969

970 971 972
						attributes[ attributeName ] = accessor;

					}
D
Don McCurdy 已提交
973 974 975 976 977

				}

			}

978 979 980 981 982 983 984
			// Skip if no exportable attributes found
			if ( Object.keys( attributes ).length === 0 ) {

				return null;

			}

D
Don McCurdy 已提交
985 986 987
			// Morph targets
			if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {

988
				var weights = [];
989 990 991 992 993 994 995 996 997 998 999 1000 1001
				var targetNames = [];
				var reverseDictionary = {};

				if ( mesh.morphTargetDictionary !== undefined ) {

					for ( var key in mesh.morphTargetDictionary ) {

						reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key;

					}

				}

D
Don McCurdy 已提交
1002 1003 1004 1005
				for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {

					var target = {};

1006 1007
					var warned = false;

D
Don McCurdy 已提交
1008 1009
					for ( var attributeName in geometry.morphAttributes ) {

T
Takahiro 已提交
1010
						// glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
1011
						// Three.js doesn't support TANGENT yet.
T
Takahiro 已提交
1012 1013 1014

						if ( attributeName !== 'position' && attributeName !== 'normal' ) {

1015 1016 1017 1018 1019 1020 1021
							if ( ! warned ) {

								console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' );
								warned = true;

							}

T
Takahiro 已提交
1022 1023 1024 1025
							continue;

						}

D
Don McCurdy 已提交
1026
						var attribute = geometry.morphAttributes[ attributeName ][ i ];
T
Takahiro 已提交
1027

1028
						// Three.js morph attribute has absolute values while the one of glTF has relative values.
T
Takahiro 已提交
1029 1030 1031 1032 1033 1034
						//
						// glTF 2.0 Specification:
						// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets

						var baseAttribute = geometry.attributes[ attributeName ];
						// Clones attribute not to override
1035
						var relativeAttribute = attribute.clone();
T
Takahiro 已提交
1036 1037 1038

						for ( var j = 0, jl = attribute.count; j < jl; j ++ ) {

1039
							relativeAttribute.setXYZ(
T
Takahiro 已提交
1040 1041 1042 1043 1044 1045 1046 1047
								j,
								attribute.getX( j ) - baseAttribute.getX( j ),
								attribute.getY( j ) - baseAttribute.getY( j ),
								attribute.getZ( j ) - baseAttribute.getZ( j )
							);

						}

1048
						target[ attributeName.toUpperCase() ] = processAccessor( relativeAttribute, geometry );
D
Don McCurdy 已提交
1049 1050 1051

					}

T
Takahiro 已提交
1052
					targets.push( target );
D
Don McCurdy 已提交
1053

1054
					weights.push( mesh.morphTargetInfluences[ i ] );
1055
					if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
1056

D
Don McCurdy 已提交
1057
				}
F
Fernando Serrano 已提交
1058

1059 1060
				gltfMesh.weights = weights;

1061 1062 1063 1064 1065 1066 1067
				if ( targetNames.length > 0 ) {

					gltfMesh.extras = {};
					gltfMesh.extras.targetNames = targetNames;

				}

F
Fernando Serrano 已提交
1068 1069
			}

1070
			var forceIndices = options.forceIndices;
T
Takahiro 已提交
1071
			var isMultiMaterial = Array.isArray( mesh.material );
1072

1073
			if ( ! forceIndices && geometry.index === null && isMultiMaterial ) {
1074 1075

				// temporal workaround.
1076
				console.warn( 'THREE.GLTFExporter: Creating index for non-indexed multi-material mesh.' );
1077 1078 1079 1080
				forceIndices = true;

			}

1081
			var didForceIndices = false;
T
Takahiro 已提交
1082

1083
			if ( geometry.index === null && forceIndices ) {
T
Takahiro 已提交
1084

1085
				var indices = [];
T
Takahiro 已提交
1086

1087
				for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) {
T
Takahiro 已提交
1088 1089 1090 1091 1092

					indices[ i ] = i;

				}

1093
				geometry.setIndex( indices );
T
Takahiro 已提交
1094

1095
				didForceIndices = true;
T
Takahiro 已提交
1096 1097 1098

			}

F
Fernando Serrano 已提交
1099
			var materials = isMultiMaterial ? mesh.material : [ mesh.material ];
T
Takahiro 已提交
1100
			var groups = isMultiMaterial ? mesh.geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];
T
Takahiro 已提交
1101

1102
			for ( var i = 0, il = groups.length; i < il; i ++ ) {
T
Takahiro 已提交
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116

				var primitive = {
					mode: mode,
					attributes: attributes,
				};

				if ( targets.length > 0 ) primitive.targets = targets;

				if ( geometry.index !== null ) {

					primitive.indices = processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );

				}

1117
				var material = processMaterial( materials[ groups[ i ].materialIndex ] );
1118

1119
				if ( material !== null ) {
1120

1121
					primitive.material = material;
1122 1123

				}
T
Takahiro 已提交
1124

1125 1126
				primitives.push( primitive );

T
Takahiro 已提交
1127 1128
			}

1129
			if ( didForceIndices ) {
T
Takahiro 已提交
1130

1131
				geometry.setIndex( null );
T
Takahiro 已提交
1132 1133 1134

			}

1135 1136
			if ( primitives.length === 0 ) {

1137
				return null;
1138 1139 1140

			}

T
Takahiro 已提交
1141 1142
			gltfMesh.primitives = primitives;

1143 1144 1145 1146 1147
			if ( ! outputJSON.meshes ) {

				outputJSON.meshes = [];

			}
F
Fernando Serrano 已提交
1148

F
Fernando Serrano 已提交
1149 1150 1151
			outputJSON.meshes.push( gltfMesh );

			return outputJSON.meshes.length - 1;
M
Mugen87 已提交
1152

F
Fernando Serrano 已提交
1153 1154
		}

F
Fernando Serrano 已提交
1155 1156 1157 1158 1159 1160
		/**
		 * Process camera
		 * @param  {THREE.Camera} camera Camera to process
		 * @return {Integer}      Index of the processed mesh in the "camera" array
		 */
		function processCamera( camera ) {
F
Fernando Serrano 已提交
1161

M
Mugen87 已提交
1162
			if ( ! outputJSON.cameras ) {
F
Fernando Serrano 已提交
1163

F
Fernando Serrano 已提交
1164
				outputJSON.cameras = [];
F
Fernando Serrano 已提交
1165

F
Fernando Serrano 已提交
1166 1167
			}

1168
			var isOrtho = camera.isOrthographicCamera;
F
Fernando Serrano 已提交
1169 1170

			var gltfCamera = {
F
Fernando Serrano 已提交
1171

F
Fernando Serrano 已提交
1172
				type: isOrtho ? 'orthographic' : 'perspective'
F
Fernando Serrano 已提交
1173

F
Fernando Serrano 已提交
1174 1175 1176 1177 1178 1179 1180 1181
			};

			if ( isOrtho ) {

				gltfCamera.orthographic = {

					xmag: camera.right * 2,
					ymag: camera.top * 2,
1182 1183
					zfar: camera.far <= 0 ? 0.001 : camera.far,
					znear: camera.near < 0 ? 0 : camera.near
F
Fernando Serrano 已提交
1184

F
Fernando Serrano 已提交
1185
				};
F
Fernando Serrano 已提交
1186 1187 1188 1189 1190 1191 1192

			} else {

				gltfCamera.perspective = {

					aspectRatio: camera.aspect,
					yfov: THREE.Math.degToRad( camera.fov ) / camera.aspect,
1193 1194
					zfar: camera.far <= 0 ? 0.001 : camera.far,
					znear: camera.near < 0 ? 0 : camera.near
F
Fernando Serrano 已提交
1195 1196 1197 1198 1199

				};

			}

1200
			if ( camera.name !== '' ) {
F
Fernando Serrano 已提交
1201

F
Fernando Serrano 已提交
1202
				gltfCamera.name = camera.type;
F
Fernando Serrano 已提交
1203

F
Fernando Serrano 已提交
1204 1205 1206 1207 1208
			}

			outputJSON.cameras.push( gltfCamera );

			return outputJSON.cameras.length - 1;
M
Mugen87 已提交
1209

F
Fernando Serrano 已提交
1210 1211
		}

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
		/**
		 * Creates glTF animation entry from AnimationClip object.
		 *
		 * Status:
		 * - Only properties listed in PATH_PROPERTIES may be animated.
		 *
		 * @param {THREE.AnimationClip} clip
		 * @param {THREE.Object3D} root
		 * @return {number}
		 */
M
Mugen87 已提交
1222
		function processAnimation( clip, root ) {
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239

			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 ];

1240
				if ( trackBinding.objectName === 'bones' ) {
1241

1242 1243 1244 1245 1246 1247 1248 1249 1250
					if ( trackNode.isSkinnedMesh === true ) {

						trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex );

					} else {

						trackNode = undefined;

					}
1251 1252 1253

				}

1254 1255 1256
				if ( ! trackNode || ! trackProperty ) {

					console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
1257
					return null;
1258 1259 1260

				}

D
Don McCurdy 已提交
1261 1262 1263 1264 1265 1266 1267 1268 1269
				var inputItemSize = 1;
				var outputItemSize = track.values.length / track.times.length;

				if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {

					outputItemSize /= trackNode.morphTargetInfluences.length;

				}

T
Takahiro 已提交
1270 1271
				var interpolation;

1272 1273
				// @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE

1274
				// Detecting glTF cubic spline interpolant by checking factory method's special property
1275 1276
				// GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
				// valid value from .getInterpolation().
1277
				if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
T
Takahiro 已提交
1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295

					interpolation = 'CUBICSPLINE';

					// itemSize of CUBICSPLINE keyframe is 9
					// (VEC3 * 3: inTangent, splineVertex, and outTangent)
					// but needs to be stored as VEC3 so dividing by 3 here.
					outputItemSize /= 3;

				} else if ( track.getInterpolation() === THREE.InterpolateDiscrete ) {

					interpolation = 'STEP';

				} else {

					interpolation = 'LINEAR';

				}

1296 1297
				samplers.push( {

D
Don McCurdy 已提交
1298 1299
					input: processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ),
					output: processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ),
T
Takahiro 已提交
1300
					interpolation: interpolation
1301 1302 1303 1304 1305 1306 1307

				} );

				channels.push( {

					sampler: samplers.length - 1,
					target: {
T
Takahiro 已提交
1308
						node: nodeMap.get( trackNode ),
1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
						path: trackProperty
					}

				} );

			}

			outputJSON.animations.push( {

				name: clip.name || 'clip_' + outputJSON.animations.length,
				samplers: samplers,
				channels: channels

			} );

			return outputJSON.animations.length - 1;

		}

D
Don McCurdy 已提交
1328 1329
		function processSkin( object ) {

T
Takahiro 已提交
1330
			var node = outputJSON.nodes[ nodeMap.get( object ) ];
D
Don McCurdy 已提交
1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341

			var skeleton = object.skeleton;
			var rootJoint = object.skeleton.bones[ 0 ];

			if ( rootJoint === undefined ) return null;

			var joints = [];
			var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 );

			for ( var i = 0; i < skeleton.bones.length; ++ i ) {

T
Takahiro 已提交
1342
				joints.push( nodeMap.get( skeleton.bones[ i ] ) );
D
Don McCurdy 已提交
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357

				skeleton.boneInverses[ i ].toArray( inverseBindMatrices, i * 16 );

			}

			if ( outputJSON.skins === undefined ) {

				outputJSON.skins = [];

			}

			outputJSON.skins.push( {

				inverseBindMatrices: processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ),
				joints: joints,
T
Takahiro 已提交
1358
				skeleton: nodeMap.get( rootJoint )
D
Don McCurdy 已提交
1359 1360 1361 1362 1363 1364 1365 1366 1367

			} );

			var skinIndex = node.skin = outputJSON.skins.length - 1;

			return skinIndex;

		}

F
Fernando Serrano 已提交
1368 1369 1370 1371 1372
		/**
		 * Process Object3D node
		 * @param  {THREE.Object3D} node Object3D to processNode
		 * @return {Integer}      Index of the node in the nodes list
		 */
M
Mr.doob 已提交
1373
		function processNode( object ) {
F
Fernando Serrano 已提交
1374

1375
			if ( object.isLight ) {
1376 1377

				console.warn( 'GLTFExporter: Unsupported node type:', object.constructor.name );
1378
				return null;
1379 1380 1381

			}

M
Mugen87 已提交
1382
			if ( ! outputJSON.nodes ) {
F
Fernando Serrano 已提交
1383

F
Fernando Serrano 已提交
1384
				outputJSON.nodes = [];
F
Fernando Serrano 已提交
1385

F
Fernando Serrano 已提交
1386 1387
			}

F
Fernando Serrano 已提交
1388 1389 1390
			var gltfNode = {};

			if ( options.trs ) {
F
Fernando Serrano 已提交
1391

F
Fernando Serrano 已提交
1392 1393 1394 1395
				var rotation = object.quaternion.toArray();
				var position = object.position.toArray();
				var scale = object.scale.toArray();

M
Mugen87 已提交
1396
				if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) {
F
Fernando Serrano 已提交
1397

F
Fernando Serrano 已提交
1398
					gltfNode.rotation = rotation;
F
Fernando Serrano 已提交
1399

F
Fernando Serrano 已提交
1400 1401
				}

M
Mugen87 已提交
1402
				if ( ! equalArray( position, [ 0, 0, 0 ] ) ) {
F
Fernando Serrano 已提交
1403

D
Don McCurdy 已提交
1404
					gltfNode.translation = position;
F
Fernando Serrano 已提交
1405

F
Fernando Serrano 已提交
1406 1407
				}

M
Mugen87 已提交
1408
				if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) {
F
Fernando Serrano 已提交
1409

F
Fernando Serrano 已提交
1410
					gltfNode.scale = scale;
F
Fernando Serrano 已提交
1411

F
Fernando Serrano 已提交
1412 1413 1414
				}

			} else {
F
Fernando Serrano 已提交
1415

F
Fernando Serrano 已提交
1416
				object.updateMatrix();
M
Mugen87 已提交
1417
				if ( ! equalArray( object.matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ) ) {
F
Fernando Serrano 已提交
1418

F
Fernando Serrano 已提交
1419
					gltfNode.matrix = object.matrix.elements;
F
Fernando Serrano 已提交
1420

F
Fernando Serrano 已提交
1421
				}
F
Fernando Serrano 已提交
1422

F
Fernando Serrano 已提交
1423 1424
			}

1425
			// We don't export empty strings name because it represents no-name in Three.js.
1426
			if ( object.name !== '' ) {
F
Fernando Serrano 已提交
1427

C
Christopher Cook 已提交
1428
				gltfNode.name = String( object.name );
F
Fernando Serrano 已提交
1429

F
Fernando Serrano 已提交
1430 1431
			}

1432 1433 1434 1435 1436 1437
			if ( object.userData && Object.keys( object.userData ).length > 0 ) {

				try {

					gltfNode.extras = JSON.parse( JSON.stringify( object.userData ) );

M
Mugen87 已提交
1438
				} catch ( e ) {
1439

M
Mugen87 已提交
1440
					throw new Error( 'THREE.GLTFExporter: userData can\'t be serialized' );
1441 1442 1443 1444 1445

				}

			}

1446
			if ( object.isMesh || object.isLine || object.isPoints ) {
F
Fernando Serrano 已提交
1447

1448 1449
				var mesh = processMesh( object );

1450
				if ( mesh !== null ) {
1451 1452 1453 1454

					gltfNode.mesh = mesh;

				}
F
Fernando Serrano 已提交
1455

1456
			} else if ( object.isCamera ) {
F
Fernando Serrano 已提交
1457

F
Fernando Serrano 已提交
1458
				gltfNode.camera = processCamera( object );
F
Fernando Serrano 已提交
1459

F
Fernando Serrano 已提交
1460 1461
			}

1462
			if ( object.isSkinnedMesh ) {
D
Don McCurdy 已提交
1463 1464 1465 1466 1467

				skins.push( object );

			}

F
Fernando Serrano 已提交
1468
			if ( object.children.length > 0 ) {
F
Fernando Serrano 已提交
1469

1470
				var children = [];
F
Fernando Serrano 已提交
1471 1472

				for ( var i = 0, l = object.children.length; i < l; i ++ ) {
F
Fernando Serrano 已提交
1473

F
Fernando Serrano 已提交
1474
					var child = object.children[ i ];
F
Fernando Serrano 已提交
1475

1476 1477
					if ( child.visible || options.onlyVisible === false ) {

1478 1479
						var node = processNode( child );

1480
						if ( node !== null ) {
1481

1482
							children.push( node );
1483 1484

						}
F
Fernando Serrano 已提交
1485

F
Fernando Serrano 已提交
1486
					}
F
Fernando Serrano 已提交
1487

F
Fernando Serrano 已提交
1488
				}
F
Fernando Serrano 已提交
1489

1490 1491 1492 1493 1494 1495 1496
				if ( children.length > 0 ) {

					gltfNode.children = children;

				}


F
Fernando Serrano 已提交
1497 1498 1499 1500
			}

			outputJSON.nodes.push( gltfNode );

T
Takahiro 已提交
1501 1502
			var nodeIndex = outputJSON.nodes.length - 1;
			nodeMap.set( object, nodeIndex );
1503 1504

			return nodeIndex;
F
Fernando Serrano 已提交
1505

F
Fernando Serrano 已提交
1506 1507 1508
		}

		/**
F
Fernando Serrano 已提交
1509
		 * Process Scene
F
Fernando Serrano 已提交
1510 1511 1512
		 * @param  {THREE.Scene} node Scene to process
		 */
		function processScene( scene ) {
F
Fernando Serrano 已提交
1513

M
Mugen87 已提交
1514
			if ( ! outputJSON.scenes ) {
F
Fernando Serrano 已提交
1515

F
Fernando Serrano 已提交
1516 1517
				outputJSON.scenes = [];
				outputJSON.scene = 0;
F
Fernando Serrano 已提交
1518

F
Fernando Serrano 已提交
1519 1520 1521
			}

			var gltfScene = {
F
Fernando Serrano 已提交
1522

F
Fernando Serrano 已提交
1523
				nodes: []
F
Fernando Serrano 已提交
1524

F
Fernando Serrano 已提交
1525 1526
			};

1527
			if ( scene.name !== '' ) {
F
Fernando Serrano 已提交
1528

F
Fernando Serrano 已提交
1529
				gltfScene.name = scene.name;
F
Fernando Serrano 已提交
1530

F
Fernando Serrano 已提交
1531 1532
			}

F
Fernando Serrano 已提交
1533
			outputJSON.scenes.push( gltfScene );
F
Fernando Serrano 已提交
1534

1535 1536
			var nodes = [];

F
Fernando Serrano 已提交
1537
			for ( var i = 0, l = scene.children.length; i < l; i ++ ) {
F
Fernando Serrano 已提交
1538

F
Fernando Serrano 已提交
1539 1540
				var child = scene.children[ i ];

1541
				if ( child.visible || options.onlyVisible === false ) {
F
Fernando Serrano 已提交
1542

1543 1544
					var node = processNode( child );

1545
					if ( node !== null ) {
1546

1547
						nodes.push( node );
1548 1549

					}
1550 1551 1552

				}

1553
			}
1554

1555
			if ( nodes.length > 0 ) {
F
Fernando Serrano 已提交
1556

1557
				gltfScene.nodes = nodes;
F
Fernando Serrano 已提交
1558

F
Fernando Serrano 已提交
1559
			}
F
Fernando Serrano 已提交
1560

F
Fernando Serrano 已提交
1561 1562
		}

1563 1564 1565 1566
		/**
		 * Creates a THREE.Scene to hold a list of objects and parse it
		 * @param  {Array} objects List of objects to process
		 */
M
Mr.doob 已提交
1567
		function processObjects( objects ) {
1568 1569

			var scene = new THREE.Scene();
1570
			scene.name = 'AuxScene';
1571

M
Mugen87 已提交
1572
			for ( var i = 0; i < objects.length; i ++ ) {
1573

1574 1575 1576
				// We push directly to children instead of calling `add` to prevent
				// modify the .parent and break its original scene and hierarchy
				scene.children.push( objects[ i ] );
1577 1578 1579 1580 1581 1582 1583

			}

			processScene( scene );

		}

1584
		function processInput( input ) {
1585

1586
			input = input instanceof Array ? input : [ input ];
F
Fernando Serrano 已提交
1587

1588
			var objectsWithoutScene = [];
M
Mr.doob 已提交
1589

M
Mugen87 已提交
1590
			for ( var i = 0; i < input.length; i ++ ) {
F
Fernando Serrano 已提交
1591

1592
				if ( input[ i ] instanceof THREE.Scene ) {
1593 1594 1595

					processScene( input[ i ] );

1596
				} else {
1597

1598
					objectsWithoutScene.push( input[ i ] );
1599

1600
				}
F
Fernando Serrano 已提交
1601

F
Fernando Serrano 已提交
1602
			}
F
Fernando Serrano 已提交
1603

1604
			if ( objectsWithoutScene.length > 0 ) {
1605

1606
				processObjects( objectsWithoutScene );
1607 1608

			}
F
Fernando Serrano 已提交
1609

D
Don McCurdy 已提交
1610 1611 1612 1613 1614 1615
			for ( var i = 0; i < skins.length; ++ i ) {

				processSkin( skins[ i ] );

			}

1616 1617 1618 1619 1620 1621
			for ( var i = 0; i < options.animations.length; ++ i ) {

				processAnimation( options.animations[ i ], input[ 0 ] );

			}

F
Fernando Serrano 已提交
1622
		}
F
Fernando Serrano 已提交
1623

D
Don McCurdy 已提交
1624
		processInput( input );
F
Fernando Serrano 已提交
1625

1626
		Promise.all( pending ).then( function () {
F
Fernando Serrano 已提交
1627

1628 1629
			// Merge buffers.
			var blob = new Blob( buffers, { type: 'application/octet-stream' } );
1630

1631 1632 1633 1634
			// Declare extensions.
			var extensionsUsedList = Object.keys( extensionsUsed );
			if ( extensionsUsedList.length > 0 ) outputJSON.extensionsUsed = extensionsUsedList;

1635
			if ( outputJSON.buffers && outputJSON.buffers.length > 0 ) {
1636

1637 1638
				// Update bytelength of the single buffer.
				outputJSON.buffers[ 0 ].byteLength = blob.size;
F
Fernando Serrano 已提交
1639

1640
				var reader = new window.FileReader();
1641

1642
				if ( options.binary === true ) {
1643

1644
					// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
1645

1646 1647 1648
					var GLB_HEADER_BYTES = 12;
					var GLB_HEADER_MAGIC = 0x46546C67;
					var GLB_VERSION = 2;
1649

1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663
					var GLB_CHUNK_PREFIX_BYTES = 8;
					var GLB_CHUNK_TYPE_JSON = 0x4E4F534A;
					var GLB_CHUNK_TYPE_BIN = 0x004E4942;

					reader.readAsArrayBuffer( blob );
					reader.onloadend = function () {

						// Binary chunk.
						var binaryChunk = getPaddedArrayBuffer( reader.result );
						var binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
						binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true );
						binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true );

						// JSON chunk.
1664
						var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( outputJSON ) ), 0x20 );
1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
						var jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
						jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true );
						jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true );

						// GLB header.
						var header = new ArrayBuffer( GLB_HEADER_BYTES );
						var headerView = new DataView( header );
						headerView.setUint32( 0, GLB_HEADER_MAGIC, true );
						headerView.setUint32( 4, GLB_VERSION, true );
						var totalByteLength = GLB_HEADER_BYTES
							+ jsonChunkPrefix.byteLength + jsonChunk.byteLength
							+ binaryChunkPrefix.byteLength + binaryChunk.byteLength;
						headerView.setUint32( 8, totalByteLength, true );

						var glbBlob = new Blob( [
							header,
							jsonChunkPrefix,
							jsonChunk,
							binaryChunkPrefix,
							binaryChunk
						], { type: 'application/octet-stream' } );

						var glbReader = new window.FileReader();
						glbReader.readAsArrayBuffer( glbBlob );
						glbReader.onloadend = function () {

							onDone( glbReader.result );

						};
F
Fernando Serrano 已提交
1694

1695 1696
					};

1697
				} else {
1698

1699 1700
					reader.readAsDataURL( blob );
					reader.onloadend = function () {
1701

1702 1703 1704
						var base64data = reader.result;
						outputJSON.buffers[ 0 ].uri = base64data;
						onDone( outputJSON );
1705

1706
					};
F
Fernando Serrano 已提交
1707

1708
				}
F
Fernando Serrano 已提交
1709

1710
			} else {
F
Fernando Serrano 已提交
1711

1712
				onDone( outputJSON );
1713

1714
			}
1715

1716
		} );
1717

F
Fernando Serrano 已提交
1718
	}
M
Mr.doob 已提交
1719

D
Don McCurdy 已提交
1720
};