GLTFExporter.js 51.9 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
	LINEAR: 0x2601,
	NEAREST_MIPMAP_NEAREST: 0x2700,
	LINEAR_MIPMAP_NEAREST: 0x2701,
	NEAREST_MIPMAP_LINEAR: 0x2702,
31
	LINEAR_MIPMAP_LINEAR: 0x2703,
32

33 34 35
	CLAMP_TO_EDGE: 33071,
	MIRRORED_REPEAT: 33648,
	REPEAT: 10497
M
Mugen87 已提交
36
};
37

38 39 40 41 42 43 44 45 46 47 48 49 50
var THREE_TO_WEBGL = {};

THREE_TO_WEBGL[ THREE.NearestFilter ] = WEBGL_CONSTANTS.NEAREST;
THREE_TO_WEBGL[ THREE.NearestMipMapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
THREE_TO_WEBGL[ THREE.NearestMipMapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
THREE_TO_WEBGL[ THREE.LinearFilter ] = WEBGL_CONSTANTS.LINEAR;
THREE_TO_WEBGL[ THREE.LinearMipMapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
THREE_TO_WEBGL[ THREE.LinearMipMapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;

THREE_TO_WEBGL[ THREE.ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
THREE_TO_WEBGL[ THREE.RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT;
THREE_TO_WEBGL[ THREE.MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT;

51 52 53 54 55 56 57
var PATH_PROPERTIES = {
	scale: 'scale',
	position: 'translation',
	quaternion: 'rotation',
	morphTargetInfluences: 'weights'
};

F
Fernando Serrano 已提交
58 59 60
//------------------------------------------------------------------------------
// GLTF Exporter
//------------------------------------------------------------------------------
61
THREE.GLTFExporter = function () {};
F
Fernando Serrano 已提交
62 63 64 65

THREE.GLTFExporter.prototype = {

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

F
Fernando Serrano 已提交
67 68 69
	/**
	 * Parse scenes and generate GLTF output
	 * @param  {THREE.Scene or [THREE.Scenes]} input   THREE.Scene or Array of THREE.Scenes
F
Fernando Serrano 已提交
70 71
	 * @param  {Function} onDone  Callback on completed
	 * @param  {Object} options options
F
Fernando Serrano 已提交
72
	 */
F
Fernando Serrano 已提交
73 74
	parse: function ( input, onDone, options ) {

75
		var DEFAULT_OPTIONS = {
76
			binary: false,
77
			trs: false,
78
			onlyVisible: true,
79
			truncateDrawRange: true,
80
			embedImages: true,
81
			animations: [],
82
			forceIndices: false,
83 84
			forcePowerOfTwoTextures: false,
			includeCustomExtensions: false
85 86 87 88
		};

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

D
Don McCurdy 已提交
89 90 91 92 93 94 95
		if ( options.animations.length > 0 ) {

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

		}

F
Fernando Serrano 已提交
96
		var outputJSON = {
F
Fernando Serrano 已提交
97

F
Fernando Serrano 已提交
98
			asset: {
F
Fernando Serrano 已提交
99

F
Fernando Serrano 已提交
100
				version: "2.0",
M
Mr.doob 已提交
101
				generator: "THREE.GLTFExporter"
F
Fernando Serrano 已提交
102

M
Mr.doob 已提交
103
			}
F
Fernando Serrano 已提交
104

M
Mr.doob 已提交
105
		};
F
Fernando Serrano 已提交
106 107

		var byteOffset = 0;
108 109
		var buffers = [];
		var pending = [];
T
Takahiro 已提交
110
		var nodeMap = new Map();
D
Don McCurdy 已提交
111
		var skins = [];
112
		var extensionsUsed = {};
113 114
		var cachedData = {

115
			meshes: new Map(),
116
			attributes: new Map(),
117
			attributesNormalized: new Map(),
118
			materials: new Map(),
119 120
			textures: new Map(),
			images: new Map()
121 122

		};
F
Fernando Serrano 已提交
123

124 125
		var cachedCanvas;

F
Fernando Serrano 已提交
126 127 128
		/**
		 * Compare two arrays
		 */
F
Fernando Serrano 已提交
129 130 131 132 133 134
		/**
		 * 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 已提交
135
		function equalArray( array1, array2 ) {
F
Fernando Serrano 已提交
136

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

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

M
Mugen87 已提交
141
			} );
F
Fernando Serrano 已提交
142

F
Fernando Serrano 已提交
143 144
		}

145 146 147 148 149
		/**
		 * Converts a string to an ArrayBuffer.
		 * @param  {string} text
		 * @return {ArrayBuffer}
		 */
150
		function stringToArrayBuffer( text ) {
151 152 153 154 155 156 157

			if ( window.TextEncoder !== undefined ) {

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

			}

158
			var array = new Uint8Array( new ArrayBuffer( text.length ) );
159

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

162
				var value = text.charCodeAt( i );
163

164
				// Replacing multi-byte character with space(0x20).
F
Fernando Serrano 已提交
165
				array[ i ] = value > 0xFF ? 0x20 : value;
166 167 168

			}

169
			return array.buffer;
170 171 172

		}

F
Fernando Serrano 已提交
173
		/**
174
		 * Get the min and max vectors from the given attribute
T
Takahiro 已提交
175 176 177
		 * @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 已提交
178 179
		 * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
		 */
T
Takahiro 已提交
180
		function getMinMax( attribute, start, count ) {
F
Fernando Serrano 已提交
181

F
Fernando Serrano 已提交
182
			var output = {
F
Fernando Serrano 已提交
183

F
Fernando Serrano 已提交
184 185
				min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
				max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
F
Fernando Serrano 已提交
186

F
Fernando Serrano 已提交
187 188
			};

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

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

F
Fernando Serrano 已提交
193
					var value = attribute.array[ i * attribute.itemSize + a ];
F
Fernando Serrano 已提交
194 195 196
					output.min[ a ] = Math.min( output.min[ a ], value );
					output.max[ a ] = Math.max( output.max[ a ], value );

F
Fernando Serrano 已提交
197
				}
F
Fernando Serrano 已提交
198

F
Fernando Serrano 已提交
199 200
			}

F
Fernando Serrano 已提交
201
			return output;
M
Mugen87 已提交
202

F
Fernando Serrano 已提交
203 204
		}

205 206 207 208 209 210 211 212 213 214 215 216 217
		/**
		 * 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 );

		}

218 219 220 221 222 223 224 225 226
		/**
		 * Checks if normal attribute values are normalized.
		 *
		 * @param {THREE.BufferAttribute} normal
		 * @returns {Boolean}
		 *
		 */
		function isNormalizedNormalAttribute( normal ) {

227
			if ( cachedData.attributesNormalized.has( normal ) ) {
228 229 230 231 232

				return false;

			}

T
Takahiro 已提交
233
			var v = new THREE.Vector3();
234

T
Takahiro 已提交
235
			for ( var i = 0, il = normal.count; i < il; i ++ ) {
236 237

				// 0.0005 is from glTF-validator
T
Takahiro 已提交
238
				if ( Math.abs( v.fromArray( normal.array, i * 3 ).length() - 1.0 ) > 0.0005 ) return false;
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

			}

			return true;

		}

		/**
		 * Creates normalized normal buffer attribute.
		 *
		 * @param {THREE.BufferAttribute} normal
		 * @returns {THREE.BufferAttribute}
		 *
		 */
		function createNormalizedNormalAttribute( normal ) {

255
			if ( cachedData.attributesNormalized.has( normal ) ) {
256

257
				return cachedData.attributesNormalized.get( normal );
258 259 260 261 262 263 264

			}

			var attribute = normal.clone();

			var v = new THREE.Vector3();

T
Takahiro 已提交
265
			for ( var i = 0, il = attribute.count; i < il; i ++ ) {
266

T
Takahiro 已提交
267
				v.fromArray( attribute.array, i * 3 );
268 269 270 271 272 273 274 275 276 277 278 279

				if ( v.x === 0 && v.y === 0 && v.z === 0 ) {

					// if values can't be normalized set (1, 0, 0)
					v.setX( 1.0 );

				} else {

					v.normalize();

				}

T
Takahiro 已提交
280
				v.toArray( attribute.array, i * 3 );
281 282 283

			}

284
			cachedData.attributesNormalized.set( normal, attribute );
285 286 287 288 289

			return attribute;

		}

290 291 292 293 294 295 296 297 298 299
		/**
		 * 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 ) {

300
			return Math.ceil( bufferSize / 4 ) * 4;
301 302

		}
M
Mugen87 已提交
303

F
Fernando Serrano 已提交
304
		/**
305 306
		 * Returns a buffer aligned to 4-byte boundary.
		 *
F
Fernando Serrano 已提交
307
		 * @param {ArrayBuffer} arrayBuffer Buffer to pad
308
		 * @param {Integer} paddingByte (Optional)
F
Fernando Serrano 已提交
309 310
		 * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
		 */
311
		function getPaddedArrayBuffer( arrayBuffer, paddingByte ) {
312

313
			paddingByte = paddingByte || 0;
314

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

317
			if ( paddedLength !== arrayBuffer.byteLength ) {
318

319 320
				var array = new Uint8Array( paddedLength );
				array.set( new Uint8Array( arrayBuffer ) );
321

322
				if ( paddingByte !== 0 ) {
323

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

326
						array[ i ] = paddingByte;
327 328 329 330

					}

				}
331

332
				return array.buffer;
F
Fernando Serrano 已提交
333 334 335 336 337 338 339

			}

			return arrayBuffer;

		}

340 341 342
		/**
		 * Serializes a userData.
		 *
343
		 * @param {THREE.Object3D|THREE.Material} object
R
Robert Long 已提交
344
		 * @param {Object} gltfProperty
345
		 */
R
Robert Long 已提交
346 347
		function serializeUserData( object, gltfProperty ) {

348
			if ( Object.keys( object.userData ).length === 0 ) {
R
Robert Long 已提交
349 350 351 352

				return;

			}
353 354 355

			try {

R
Robert Long 已提交
356 357
				var json = JSON.parse( JSON.stringify( object.userData ) );

358
				if ( options.includeCustomExtensions && json.gltfExtensions ) {
R
Robert Long 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372

					if ( gltfProperty.extensions === undefined ) {

						gltfProperty.extensions = {};

					}

					for ( var extensionName in json.gltfExtensions ) {

						gltfProperty.extensions[ extensionName ] = json.gltfExtensions[ extensionName ];
						extensionsUsed[ extensionName ] = true;

					}

373
					delete json.gltfExtensions;
R
Robert Long 已提交
374

375
				}
376

R
Robert Long 已提交
377 378 379 380 381
				if ( Object.keys( json ).length > 0 ) {

					gltfProperty.extras = json;

				}
382

383
			} catch ( error ) {
384

385 386
				console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' +
					'won\'t be serialized because of JSON.stringify error - ' + error.message );
387 388 389 390 391

			}

		}

392 393 394 395 396 397
		/**
		 * Applies a texture transform, if present, to the map definition. Requires
		 * the KHR_texture_transform extension.
		 */
		function applyTextureTransform( mapDef, texture ) {

M
Mugen87 已提交
398
			var didTransform = false;
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
			var transformDef = {};

			if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) {

				transformDef.offset = texture.offset.toArray();
				didTransform = true;

			}

			if ( texture.rotation !== 0 ) {

				transformDef.rotation = texture.rotation;
				didTransform = true;

			}

			if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) {

				transformDef.scale = texture.repeat.toArray();
D
Don McCurdy 已提交
418
				didTransform = true;
419 420 421 422 423 424 425 426 427 428 429 430 431

			}

			if ( didTransform ) {

				mapDef.extensions = mapDef.extensions || {};
				mapDef.extensions[ 'KHR_texture_transform' ] = transformDef;
				extensionsUsed[ 'KHR_texture_transform' ] = true;

			}

		}

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
		/**
		 * @param  {BufferAttribute} attribute
		 * @param  {Integer|undefined} start
		 * @param  {Integer|undefined} count
		 * @return {Boolean}
		 */
		function hasAttributeCache( attribute, start, count ) {

			return cachedData.attributes.has( attribute ) &&
				cachedData.attributes.get( attribute ).has( start ) &&
				cachedData.attributes.get( attribute ).get( start ).has( count );

		}

		/**
		 * @param  {BufferAttribute} attribute
		 * @param  {Integer|undefined} start
		 * @param  {Integer|undefined} count
		 * @return {Integer}
		 */
		function getAttributeCache( attribute, start, count ) {

			return cachedData.attributes.get( attribute ).get( start ).get( count );

		}

		/**
		 * @param  {BufferAttribute} attribute
460
		 * @param  {Integer} data
461 462 463
		 * @param  {Integer|undefined} start
		 * @param  {Integer|undefined} count
		 */
464
		function setAttributeCache( attribute, data, start, count ) {
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481

			if ( ! cachedData.attributes.has( attribute ) ) {

				cachedData.attributes.set( attribute, new Map() );

			}

			if ( ! cachedData.attributes.get( attribute ).has( start ) ) {

				cachedData.attributes.get( attribute ).set( start, new Map() );

			}

			cachedData.attributes.get( attribute ).get( start ).set( count, data );

		}

F
Fernando Serrano 已提交
482
		/**
F
Fernando Serrano 已提交
483
		 * Process a buffer to append to the default one.
484 485
		 * @param  {ArrayBuffer} buffer
		 * @return {Integer}
F
Fernando Serrano 已提交
486
		 */
487
		function processBuffer( buffer ) {
F
Fernando Serrano 已提交
488

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

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

493
			}
F
Fernando Serrano 已提交
494

495 496
			// All buffers are merged before export.
			buffers.push( buffer );
F
Fernando Serrano 已提交
497

498
			return 0;
F
Fernando Serrano 已提交
499

500
		}
F
Fernando Serrano 已提交
501

502 503 504 505 506 507 508 509 510 511
		/**
		 * 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 已提交
512

513
			if ( ! outputJSON.bufferViews ) {
514

515
				outputJSON.bufferViews = [];
M
Mugen87 已提交
516

517
			}
F
Fernando Serrano 已提交
518

519
			// Create a new dataview and dump the attribute's array into it
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536

			var componentSize;

			if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {

				componentSize = 1;

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

				componentSize = 2;

			} else {

				componentSize = 4;

			}

537
			var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize );
538
			var dataView = new DataView( new ArrayBuffer( byteLength ) );
539
			var offset = 0;
F
Fernando Serrano 已提交
540

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

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

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

549
					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
F
Fernando Serrano 已提交
550

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

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

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

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

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

561 562 563 564
					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {

						dataView.setUint8( offset, value );

F
Fernando Serrano 已提交
565
					}
F
Fernando Serrano 已提交
566

567
					offset += componentSize;
F
Fernando Serrano 已提交
568

F
Fernando Serrano 已提交
569
				}
F
Fernando Serrano 已提交
570

F
Fernando Serrano 已提交
571 572 573
			}

			var gltfBufferView = {
F
Fernando Serrano 已提交
574

575
				buffer: processBuffer( dataView.buffer ),
F
Fernando Serrano 已提交
576
				byteOffset: byteOffset,
577
				byteLength: byteLength
F
Fernando Serrano 已提交
578

F
Fernando Serrano 已提交
579 580
			};

581 582
			if ( target !== undefined ) gltfBufferView.target = target;

583 584 585
			if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {

				// Only define byteStride for vertex attributes.
586
				gltfBufferView.byteStride = attribute.itemSize * componentSize;
587 588 589

			}

590
			byteOffset += byteLength;
F
Fernando Serrano 已提交
591

F
Fernando Serrano 已提交
592
			outputJSON.bufferViews.push( gltfBufferView );
F
Fernando Serrano 已提交
593

594
			// @TODO Merge bufferViews where possible.
F
Fernando Serrano 已提交
595
			var output = {
F
Fernando Serrano 已提交
596

F
Fernando Serrano 已提交
597 598
				id: outputJSON.bufferViews.length - 1,
				byteLength: 0
F
Fernando Serrano 已提交
599

F
Fernando Serrano 已提交
600
			};
F
Fernando Serrano 已提交
601

F
Fernando Serrano 已提交
602
			return output;
F
Fernando Serrano 已提交
603

F
Fernando Serrano 已提交
604 605
		}

606 607 608 609 610
		/**
		 * Process and generate a BufferView from an image Blob.
		 * @param {Blob} blob
		 * @return {Promise<Integer>}
		 */
D
Don McCurdy 已提交
611
		function processBufferViewImage( blob ) {
612 613 614 615 616 617 618

			if ( ! outputJSON.bufferViews ) {

				outputJSON.bufferViews = [];

			}

D
Don McCurdy 已提交
619
			return new Promise( function ( resolve ) {
620 621 622

				var reader = new window.FileReader();
				reader.readAsArrayBuffer( blob );
D
Don McCurdy 已提交
623
				reader.onloadend = function () {
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638

					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 已提交
639
				};
640 641 642 643 644

			} );

		}

F
Fernando Serrano 已提交
645
		/**
F
Fernando Serrano 已提交
646
		 * Process attribute to generate an accessor
647 648
		 * @param  {THREE.BufferAttribute} attribute Attribute to process
		 * @param  {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
T
Takahiro 已提交
649 650
		 * @param  {Integer} start (Optional)
		 * @param  {Integer} count (Optional)
F
Fernando Serrano 已提交
651
		 * @return {Integer}           Index of the processed accessor on the "accessors" array
F
Fernando Serrano 已提交
652
		 */
T
Takahiro 已提交
653
		function processAccessor( attribute, geometry, start, count ) {
F
Fernando Serrano 已提交
654

D
Don McCurdy 已提交
655
			var types = {
F
Fernando Serrano 已提交
656

D
Don McCurdy 已提交
657 658 659 660 661
				1: 'SCALAR',
				2: 'VEC2',
				3: 'VEC3',
				4: 'VEC4',
				16: 'MAT4'
F
Fernando Serrano 已提交
662

D
Don McCurdy 已提交
663
			};
F
Fernando Serrano 已提交
664

665 666
			var componentType;

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

670
				componentType = WEBGL_CONSTANTS.FLOAT;
F
Fernando Serrano 已提交
671

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

674
				componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
F
Fernando Serrano 已提交
675

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

678
				componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
F
Fernando Serrano 已提交
679

680 681 682 683
			} else if ( attribute.array.constructor === Uint8Array ) {

				componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;

684
			} else {
F
Fernando Serrano 已提交
685

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

688
			}
F
Fernando Serrano 已提交
689

T
Takahiro 已提交
690 691
			if ( start === undefined ) start = 0;
			if ( count === undefined ) count = attribute.count;
692 693

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

T
Takahiro 已提交
696 697
				var end = start + count;
				var end2 = geometry.drawRange.count === Infinity
M
Mugen87 已提交
698 699
					? attribute.count
					: geometry.drawRange.start + geometry.drawRange.count;
T
Takahiro 已提交
700 701 702 703 704

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

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

706 707
			}

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

711
				return null;
712 713 714

			}

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

717 718 719 720 721 722
			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 已提交
723
				bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
724 725 726 727

			}

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

F
Fernando Serrano 已提交
729
			var gltfAccessor = {
F
Fernando Serrano 已提交
730

F
Fernando Serrano 已提交
731 732 733
				bufferView: bufferView.id,
				byteOffset: bufferView.byteOffset,
				componentType: componentType,
734
				count: count,
F
Fernando Serrano 已提交
735 736
				max: minMax.max,
				min: minMax.min,
D
Don McCurdy 已提交
737
				type: types[ attribute.itemSize ]
F
Fernando Serrano 已提交
738

F
Fernando Serrano 已提交
739 740
			};

741 742 743 744 745 746
			if ( ! outputJSON.accessors ) {

				outputJSON.accessors = [];

			}

F
Fernando Serrano 已提交
747 748 749
			outputJSON.accessors.push( gltfAccessor );

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

F
Fernando Serrano 已提交
751 752 753
		}

		/**
F
Fernando Serrano 已提交
754
		 * Process image
755
		 * @param  {Image} image to process
756
		 * @param  {Integer} format of the image (e.g. THREE.RGBFormat, THREE.RGBAFormat etc)
757
		 * @param  {Boolean} flipY before writing out the image
F
Fernando Serrano 已提交
758 759
		 * @return {Integer}     Index of the processed texture in the "images" array
		 */
760
		function processImage( image, format, flipY ) {
F
Fernando Serrano 已提交
761

762
			if ( ! cachedData.images.has( image ) ) {
763

764 765 766 767 768
				cachedData.images.set( image, {} );

			}

			var cachedImages = cachedData.images.get( image );
769 770
			var mimeType = format === THREE.RGBAFormat ? 'image/png' : 'image/jpeg';
			var key = mimeType + ":flipY/" + flipY.toString();
F
Fernando Serrano 已提交
771

772 773 774
			if ( cachedImages[ key ] !== undefined ) {

				return cachedImages[ key ];
775 776

			}
777

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

F
Fernando Serrano 已提交
780
				outputJSON.images = [];
F
Fernando Serrano 已提交
781

F
Fernando Serrano 已提交
782 783
			}

M
Mugen87 已提交
784
			var gltfImage = { mimeType: mimeType };
785

786
			if ( options.embedImages ) {
F
Fernando Serrano 已提交
787

788
				var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' );
789

790 791
				canvas.width = image.width;
				canvas.height = image.height;
792

793
				if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( image ) ) {
794

795
					console.warn( 'GLTFExporter: Resized non-power-of-two image.', image );
796 797 798 799 800 801

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

				}

802
				var ctx = canvas.getContext( '2d' );
803

804
				if ( flipY === true ) {
805

806
					ctx.translate( 0, canvas.height );
M
Mugen87 已提交
807
					ctx.scale( 1, - 1 );
808 809 810

				}

811
				ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
812

813 814 815 816 817 818 819 820 821 822 823
				if ( options.binary === true ) {

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

						canvas.toBlob( function ( blob ) {

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

								gltfImage.bufferView = bufferViewIndex;

								resolve();
824

825 826 827 828 829 830 831 832 833 834 835
							} );

						}, mimeType );

					} ) );

				} else {

					gltfImage.uri = canvas.toDataURL( mimeType );

				}
F
Fernando Serrano 已提交
836

F
Fernando Serrano 已提交
837
			} else {
F
Fernando Serrano 已提交
838

839
				gltfImage.uri = image.src;
F
Fernando Serrano 已提交
840

F
Fernando Serrano 已提交
841 842 843 844
			}

			outputJSON.images.push( gltfImage );

845
			var index = outputJSON.images.length - 1;
846
			cachedImages[ key ] = index;
F
Fernando Serrano 已提交
847

848
			return index;
F
Fernando Serrano 已提交
849

F
Fernando Serrano 已提交
850 851 852 853 854 855 856
		}

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

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

F
Fernando Serrano 已提交
861
				outputJSON.samplers = [];
F
Fernando Serrano 已提交
862

F
Fernando Serrano 已提交
863 864 865
			}

			var gltfSampler = {
F
Fernando Serrano 已提交
866

867 868 869 870
				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 已提交
871

F
Fernando Serrano 已提交
872 873 874 875 876
			};

			outputJSON.samplers.push( gltfSampler );

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

F
Fernando Serrano 已提交
878 879 880 881 882 883 884
		}

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

T
Takahiro 已提交
887
			if ( cachedData.textures.has( map ) ) {
T
Takahiro 已提交
888

T
Takahiro 已提交
889
				return cachedData.textures.get( map );
T
Takahiro 已提交
890 891 892

			}

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

F
Fernando Serrano 已提交
895
				outputJSON.textures = [];
F
Fernando Serrano 已提交
896

F
Fernando Serrano 已提交
897 898 899
			}

			var gltfTexture = {
F
Fernando Serrano 已提交
900

F
Fernando Serrano 已提交
901
				sampler: processSampler( map ),
M
Mugen87 已提交
902
				source: processImage( map.image, map.format, map.flipY )
F
Fernando Serrano 已提交
903

F
Fernando Serrano 已提交
904 905 906 907
			};

			outputJSON.textures.push( gltfTexture );

T
Takahiro 已提交
908
			var index = outputJSON.textures.length - 1;
T
Takahiro 已提交
909
			cachedData.textures.set( map, index );
T
Takahiro 已提交
910 911

			return index;
F
Fernando Serrano 已提交
912

F
Fernando Serrano 已提交
913 914 915 916 917 918
		}

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

T
Takahiro 已提交
922
			if ( cachedData.materials.has( material ) ) {
923

T
Takahiro 已提交
924
				return cachedData.materials.get( material );
925 926 927

			}

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

F
Fernando Serrano 已提交
930
				outputJSON.materials = [];
F
Fernando Serrano 已提交
931

F
Fernando Serrano 已提交
932
			}
F
Fernando Serrano 已提交
933

934
			if ( material.isShaderMaterial ) {
935 936 937 938 939 940

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

			}

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

944
				pbrMetallicRoughness: {}
F
Fernando Serrano 已提交
945

946
			};
947

948
			if ( material.isMeshBasicMaterial ) {
949 950 951 952 953

				gltfMaterial.extensions = { KHR_materials_unlit: {} };

				extensionsUsed[ 'KHR_materials_unlit' ] = true;

954
			} else if ( ! material.isMeshStandardMaterial ) {
955 956 957 958 959

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

			}

960 961
			// pbrMetallicRoughness.baseColorFactor
			var color = material.color.toArray().concat( [ material.opacity ] );
F
Fernando Serrano 已提交
962

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

965
				gltfMaterial.pbrMetallicRoughness.baseColorFactor = color;
966 967 968

			}

969
			if ( material.isMeshStandardMaterial ) {
970

971 972
				gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness;
973

974
			} else if ( material.isMeshBasicMaterial ) {
975 976 977 978

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

M
Mr.doob 已提交
979
			} else {
980

M
Mugen87 已提交
981 982
				gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5;
F
Fernando Serrano 已提交
983

984
			}
985

986 987 988 989 990
			// pbrMetallicRoughness.metallicRoughnessTexture
			if ( material.metalnessMap || material.roughnessMap ) {

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

991 992 993
					var metalRoughMapDef = { index: processTexture( material.metalnessMap ) };
					applyTextureTransform( metalRoughMapDef, material.metalnessMap );
					gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
994 995 996 997 998 999 1000 1001 1002

				} else {

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

				}

			}

1003 1004
			// pbrMetallicRoughness.baseColorTexture
			if ( material.map ) {
1005

1006 1007 1008
				var baseColorMapDef = { index: processTexture( material.map ) };
				applyTextureTransform( baseColorMapDef, material.map );
				gltfMaterial.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
1009

1010
			}
1011

1012 1013 1014
			if ( material.isMeshBasicMaterial ||
				material.isLineBasicMaterial ||
				material.isPointsMaterial ) {
1015 1016 1017 1018

			} else {

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

M
Mugen87 已提交
1021
				if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) {
1022 1023 1024

					gltfMaterial.emissiveFactor = emissive;

F
Fernando Serrano 已提交
1025
				}
1026 1027 1028 1029

				// emissiveTexture
				if ( material.emissiveMap ) {

1030 1031 1032
					var emissiveMapDef = { index: processTexture( material.emissiveMap ) };
					applyTextureTransform( emissiveMapDef, material.emissiveMap );
					gltfMaterial.emissiveTexture = emissiveMapDef;
1033 1034 1035 1036 1037 1038 1039 1040

				}

			}

			// normalTexture
			if ( material.normalMap ) {

1041
				var normalMapDef = { index: processTexture( material.normalMap ) };
1042

M
Mugen87 已提交
1043
				if ( material.normalScale.x !== - 1 ) {
1044 1045 1046

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

M
Mugen87 已提交
1047
						console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' );
1048 1049 1050

					}

1051
					normalMapDef.scale = material.normalScale.x;
1052 1053 1054

				}

1055 1056 1057 1058
				applyTextureTransform( normalMapDef, material.normalMap );

				gltfMaterial.normalTexture = normalMapDef;

F
Fernando Serrano 已提交
1059 1060
			}

1061 1062 1063
			// occlusionTexture
			if ( material.aoMap ) {

M
Mugen87 已提交
1064
				var occlusionMapDef = {
1065 1066 1067
					index: processTexture( material.aoMap ),
					texCoord: 1
				};
1068

1069 1070
				if ( material.aoMapIntensity !== 1.0 ) {

1071
					occlusionMapDef.strength = material.aoMapIntensity;
1072 1073 1074

				}

1075 1076 1077 1078
				applyTextureTransform( occlusionMapDef, material.aoMap );

				gltfMaterial.occlusionTexture = occlusionMapDef;

1079 1080 1081
			}

			// alphaMode
1082
			if ( material.transparent || material.alphaTest > 0.0 ) {
1083

1084
				gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK';
1085

1086 1087
				// Write alphaCutoff if it's non-zero and different from the default (0.5).
				if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) {
1088 1089 1090 1091

					gltfMaterial.alphaCutoff = material.alphaTest;

				}
1092 1093 1094 1095

			}

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

F
Fernando Serrano 已提交
1098
				gltfMaterial.doubleSided = true;
1099

F
Fernando Serrano 已提交
1100 1101
			}

1102
			if ( material.name !== '' ) {
1103

F
Fernando Serrano 已提交
1104
				gltfMaterial.name = material.name;
1105

F
Fernando Serrano 已提交
1106 1107
			}

R
Robert Long 已提交
1108
			serializeUserData( material, gltfMaterial );
1109

F
Fernando Serrano 已提交
1110
			outputJSON.materials.push( gltfMaterial );
F
Fernando Serrano 已提交
1111

1112
			var index = outputJSON.materials.length - 1;
T
Takahiro 已提交
1113
			cachedData.materials.set( material, index );
1114 1115

			return index;
1116

F
Fernando Serrano 已提交
1117 1118 1119
		}

		/**
F
Fernando Serrano 已提交
1120 1121 1122
		 * Process mesh
		 * @param  {THREE.Mesh} mesh Mesh to process
		 * @return {Integer}      Index of the processed mesh in the "meshes" array
F
Fernando Serrano 已提交
1123 1124
		 */
		function processMesh( mesh ) {
F
Fernando Serrano 已提交
1125

1126 1127 1128 1129 1130 1131 1132
			var cacheKey = mesh.geometry.uuid + ':' + mesh.material.uuid;
			if ( cachedData.meshes.has( cacheKey ) ) {

				return cachedData.meshes.get( cacheKey );

			}

F
Fernando Serrano 已提交
1133
			var geometry = mesh.geometry;
F
Fernando Serrano 已提交
1134

M
Mr.doob 已提交
1135 1136
			var mode;

1137
			// Use the correct mode
1138
			if ( mesh.isLineSegments ) {
1139

1140
				mode = WEBGL_CONSTANTS.LINES;
1141

1142
			} else if ( mesh.isLineLoop ) {
1143

1144
				mode = WEBGL_CONSTANTS.LINE_LOOP;
1145

1146
			} else if ( mesh.isLine ) {
1147

1148
				mode = WEBGL_CONSTANTS.LINE_STRIP;
1149

1150
			} else if ( mesh.isPoints ) {
1151

1152
				mode = WEBGL_CONSTANTS.POINTS;
1153 1154 1155

			} else {

M
Mugen87 已提交
1156
				if ( ! geometry.isBufferGeometry ) {
L
linbingquan 已提交
1157

1158
					console.warn( 'GLTFExporter: Exporting THREE.Geometry will increase file size. Use THREE.BufferGeometry instead.' );
1159 1160 1161 1162 1163 1164 1165

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

				}

1166 1167
				if ( mesh.drawMode === THREE.TriangleFanDrawMode ) {

F
Fernando Serrano 已提交
1168
					console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' );
1169
					mode = WEBGL_CONSTANTS.TRIANGLE_FAN;
1170 1171 1172

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

1173
					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINE_STRIP : WEBGL_CONSTANTS.TRIANGLE_STRIP;
1174 1175 1176

				} else {

1177
					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
1178 1179 1180 1181

				}

			}
F
Fernando Serrano 已提交
1182

T
Takahiro 已提交
1183
			var gltfMesh = {};
1184

T
Takahiro 已提交
1185 1186 1187
			var attributes = {};
			var primitives = [];
			var targets = [];
F
Fernando Serrano 已提交
1188

F
Fernando Serrano 已提交
1189 1190
			// Conversion between attributes names in threejs and gltf spec
			var nameConversion = {
F
Fernando Serrano 已提交
1191

F
Fernando Serrano 已提交
1192 1193
				uv: 'TEXCOORD_0',
				uv2: 'TEXCOORD_1',
1194 1195 1196
				color: 'COLOR_0',
				skinWeight: 'WEIGHTS_0',
				skinIndex: 'JOINTS_0'
F
Fernando Serrano 已提交
1197

F
Fernando Serrano 已提交
1198 1199
			};

1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
			var originalNormal = geometry.getAttribute( 'normal' );

			if ( originalNormal !== undefined && ! isNormalizedNormalAttribute( originalNormal ) ) {

				console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' );

				geometry.addAttribute( 'normal', createNormalizedNormalAttribute( originalNormal ) );

			}

1210
			// @QUESTION Detect if .vertexColors = THREE.VertexColors?
F
Fernando Serrano 已提交
1211
			// For every attribute create an accessor
D
Don McCurdy 已提交
1212
			var modifiedAttribute = null;
F
Fernando Serrano 已提交
1213 1214
			for ( var attributeName in geometry.attributes ) {

1215 1216 1217
				// Ignore morph target attributes, which are exported later.
				if ( attributeName.substr( 0, 5 ) === 'morph' ) continue;

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

1221 1222
				// Prefix all geometry attributes except the ones specifically
				// listed in the spec; non-spec attributes are considered custom.
1223 1224 1225 1226 1227 1228
				var validVertexAttributes =
						/^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
				if ( ! validVertexAttributes.test( attributeName ) ) {

					attributeName = '_' + attributeName;

1229 1230
				}

1231
				if ( hasAttributeCache( attribute ) ) {
1232

1233
					attributes[ attributeName ] = getAttributeCache( attribute );
1234 1235 1236 1237
					continue;

				}

1238
				// JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
1239
				modifiedAttribute = null;
1240
				var array = attribute.array;
1241
				if ( attributeName === 'JOINTS_0' &&
1242 1243
					! ( array instanceof Uint16Array ) &&
					! ( array instanceof Uint8Array ) ) {
1244 1245

					console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
1246
					modifiedAttribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
1247 1248 1249

				}

1250 1251
				var accessor = processAccessor( modifiedAttribute || attribute, geometry );
				if ( accessor !== null ) {
D
Don McCurdy 已提交
1252

1253
					attributes[ attributeName ] = accessor;
1254
					setAttributeCache( attribute, accessor );
D
Don McCurdy 已提交
1255 1256 1257 1258 1259

				}

			}

1260 1261
			if ( originalNormal !== undefined ) geometry.addAttribute( 'normal', originalNormal );

1262 1263 1264 1265 1266 1267 1268
			// Skip if no exportable attributes found
			if ( Object.keys( attributes ).length === 0 ) {

				return null;

			}

D
Don McCurdy 已提交
1269 1270 1271
			// Morph targets
			if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {

1272
				var weights = [];
1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285
				var targetNames = [];
				var reverseDictionary = {};

				if ( mesh.morphTargetDictionary !== undefined ) {

					for ( var key in mesh.morphTargetDictionary ) {

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

					}

				}

D
Don McCurdy 已提交
1286 1287 1288 1289
				for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {

					var target = {};

1290 1291
					var warned = false;

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

T
Takahiro 已提交
1294
						// glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
1295
						// Three.js doesn't support TANGENT yet.
T
Takahiro 已提交
1296 1297 1298

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

1299 1300 1301 1302 1303 1304 1305
							if ( ! warned ) {

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

							}

T
Takahiro 已提交
1306 1307 1308 1309
							continue;

						}

D
Don McCurdy 已提交
1310
						var attribute = geometry.morphAttributes[ attributeName ][ i ];
1311
						var gltfAttributeName = attributeName.toUpperCase();
T
Takahiro 已提交
1312

1313
						// Three.js morph attribute has absolute values while the one of glTF has relative values.
T
Takahiro 已提交
1314 1315 1316 1317 1318
						//
						// glTF 2.0 Specification:
						// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets

						var baseAttribute = geometry.attributes[ attributeName ];
1319

1320
						if ( hasAttributeCache( attribute ) ) {
1321

1322
							target[ gltfAttributeName ] = getAttributeCache( attribute );
1323 1324 1325 1326
							continue;

						}

T
Takahiro 已提交
1327
						// Clones attribute not to override
1328
						var relativeAttribute = attribute.clone();
T
Takahiro 已提交
1329 1330 1331

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

1332
							relativeAttribute.setXYZ(
T
Takahiro 已提交
1333 1334 1335 1336 1337 1338 1339 1340
								j,
								attribute.getX( j ) - baseAttribute.getX( j ),
								attribute.getY( j ) - baseAttribute.getY( j ),
								attribute.getZ( j ) - baseAttribute.getZ( j )
							);

						}

1341
						target[ gltfAttributeName ] = processAccessor( relativeAttribute, geometry );
1342
						setAttributeCache( baseAttribute, target[ gltfAttributeName ] );
D
Don McCurdy 已提交
1343 1344 1345

					}

T
Takahiro 已提交
1346
					targets.push( target );
D
Don McCurdy 已提交
1347

1348
					weights.push( mesh.morphTargetInfluences[ i ] );
1349
					if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
1350

D
Don McCurdy 已提交
1351
				}
F
Fernando Serrano 已提交
1352

1353 1354
				gltfMesh.weights = weights;

1355 1356 1357 1358 1359 1360 1361
				if ( targetNames.length > 0 ) {

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

				}

F
Fernando Serrano 已提交
1362 1363
			}

1364
			var forceIndices = options.forceIndices;
T
Takahiro 已提交
1365
			var isMultiMaterial = Array.isArray( mesh.material );
1366

1367
			if ( isMultiMaterial && geometry.groups.length === 0 ) return null;
1368

1369
			if ( ! forceIndices && geometry.index === null && isMultiMaterial ) {
1370 1371

				// temporal workaround.
1372
				console.warn( 'THREE.GLTFExporter: Creating index for non-indexed multi-material mesh.' );
1373 1374 1375 1376
				forceIndices = true;

			}

1377
			var didForceIndices = false;
T
Takahiro 已提交
1378

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

1381
				var indices = [];
T
Takahiro 已提交
1382

1383
				for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) {
T
Takahiro 已提交
1384 1385 1386 1387 1388

					indices[ i ] = i;

				}

1389
				geometry.setIndex( indices );
T
Takahiro 已提交
1390

1391
				didForceIndices = true;
T
Takahiro 已提交
1392 1393 1394

			}

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

1398
			for ( var i = 0, il = groups.length; i < il; i ++ ) {
T
Takahiro 已提交
1399 1400 1401 1402 1403 1404

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

R
Robert Long 已提交
1405
				serializeUserData( geometry, primitive );
1406

T
Takahiro 已提交
1407 1408 1409 1410
				if ( targets.length > 0 ) primitive.targets = targets;

				if ( geometry.index !== null ) {

1411
					if ( hasAttributeCache( geometry.index, groups[ i ].start, groups[ i ].count ) ) {
1412

1413
						primitive.indices = getAttributeCache( geometry.index, groups[ i ].start, groups[ i ].count );
1414 1415 1416 1417

					} else {

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

					}
T
Takahiro 已提交
1421

G
Gary Oberbrunner 已提交
1422 1423
					if ( primitive.indices === null ) delete primitive.indices;

T
Takahiro 已提交
1424 1425
				}

1426
				var material = processMaterial( materials[ groups[ i ].materialIndex ] );
1427

1428
				if ( material !== null ) {
1429

1430
					primitive.material = material;
1431 1432

				}
T
Takahiro 已提交
1433

1434 1435
				primitives.push( primitive );

T
Takahiro 已提交
1436 1437
			}

1438
			if ( didForceIndices ) {
T
Takahiro 已提交
1439

1440
				geometry.setIndex( null );
T
Takahiro 已提交
1441 1442 1443 1444 1445

			}

			gltfMesh.primitives = primitives;

1446 1447 1448 1449 1450
			if ( ! outputJSON.meshes ) {

				outputJSON.meshes = [];

			}
F
Fernando Serrano 已提交
1451

F
Fernando Serrano 已提交
1452 1453
			outputJSON.meshes.push( gltfMesh );

1454 1455 1456 1457
			var index = outputJSON.meshes.length - 1;
			cachedData.meshes.set( cacheKey, index );

			return index;
M
Mugen87 已提交
1458

F
Fernando Serrano 已提交
1459 1460
		}

F
Fernando Serrano 已提交
1461 1462 1463 1464 1465 1466
		/**
		 * 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 已提交
1467

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

F
Fernando Serrano 已提交
1470
				outputJSON.cameras = [];
F
Fernando Serrano 已提交
1471

F
Fernando Serrano 已提交
1472 1473
			}

1474
			var isOrtho = camera.isOrthographicCamera;
F
Fernando Serrano 已提交
1475 1476

			var gltfCamera = {
F
Fernando Serrano 已提交
1477

F
Fernando Serrano 已提交
1478
				type: isOrtho ? 'orthographic' : 'perspective'
F
Fernando Serrano 已提交
1479

F
Fernando Serrano 已提交
1480 1481 1482 1483 1484 1485 1486 1487
			};

			if ( isOrtho ) {

				gltfCamera.orthographic = {

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

F
Fernando Serrano 已提交
1491
				};
F
Fernando Serrano 已提交
1492 1493 1494 1495 1496 1497

			} else {

				gltfCamera.perspective = {

					aspectRatio: camera.aspect,
1498
					yfov: THREE.Math.degToRad( camera.fov ),
1499 1500
					zfar: camera.far <= 0 ? 0.001 : camera.far,
					znear: camera.near < 0 ? 0 : camera.near
F
Fernando Serrano 已提交
1501 1502 1503 1504 1505

				};

			}

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

F
Fernando Serrano 已提交
1508
				gltfCamera.name = camera.type;
F
Fernando Serrano 已提交
1509

F
Fernando Serrano 已提交
1510 1511 1512 1513 1514
			}

			outputJSON.cameras.push( gltfCamera );

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

F
Fernando Serrano 已提交
1516 1517
		}

1518 1519 1520 1521 1522 1523 1524 1525 1526 1527
		/**
		 * 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 已提交
1528
		function processAnimation( clip, root ) {
1529 1530 1531 1532 1533 1534 1535

			if ( ! outputJSON.animations ) {

				outputJSON.animations = [];

			}

1536
			clip = THREE.GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root );
1537 1538

			var tracks = clip.tracks;
1539 1540 1541
			var channels = [];
			var samplers = [];

1542
			for ( var i = 0; i < tracks.length; ++ i ) {
1543

1544
				var track = tracks[ i ];
1545 1546 1547 1548
				var trackBinding = THREE.PropertyBinding.parseTrackName( track.name );
				var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
				var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];

1549
				if ( trackBinding.objectName === 'bones' ) {
1550

1551 1552 1553 1554 1555 1556 1557 1558 1559
					if ( trackNode.isSkinnedMesh === true ) {

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

					} else {

						trackNode = undefined;

					}
1560 1561 1562

				}

1563 1564 1565
				if ( ! trackNode || ! trackProperty ) {

					console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
1566
					return null;
1567 1568 1569

				}

D
Don McCurdy 已提交
1570 1571 1572 1573 1574 1575 1576 1577 1578
				var inputItemSize = 1;
				var outputItemSize = track.values.length / track.times.length;

				if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {

					outputItemSize /= trackNode.morphTargetInfluences.length;

				}

T
Takahiro 已提交
1579 1580
				var interpolation;

1581 1582
				// @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE

1583
				// Detecting glTF cubic spline interpolant by checking factory method's special property
1584 1585
				// GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
				// valid value from .getInterpolation().
1586
				if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
T
Takahiro 已提交
1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604

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

				}

1605 1606
				samplers.push( {

D
Don McCurdy 已提交
1607 1608
					input: processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ),
					output: processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ),
T
Takahiro 已提交
1609
					interpolation: interpolation
1610 1611 1612 1613 1614 1615 1616

				} );

				channels.push( {

					sampler: samplers.length - 1,
					target: {
T
Takahiro 已提交
1617
						node: nodeMap.get( trackNode ),
1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636
						path: trackProperty
					}

				} );

			}

			outputJSON.animations.push( {

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

			} );

			return outputJSON.animations.length - 1;

		}

D
Don McCurdy 已提交
1637 1638
		function processSkin( object ) {

T
Takahiro 已提交
1639
			var node = outputJSON.nodes[ nodeMap.get( object ) ];
D
Don McCurdy 已提交
1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650

			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 已提交
1651
				joints.push( nodeMap.get( skeleton.bones[ i ] ) );
D
Don McCurdy 已提交
1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666

				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 已提交
1667
				skeleton: nodeMap.get( rootJoint )
D
Don McCurdy 已提交
1668 1669 1670 1671 1672 1673 1674 1675 1676

			} );

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

			return skinIndex;

		}

1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
		function processLight( light ) {

			var lightDef = {};

			if ( light.name ) lightDef.name = light.name;

			lightDef.color = light.color.toArray();

			lightDef.intensity = light.intensity;

			if ( light.isDirectionalLight ) {

				lightDef.type = 'directional';

			} else if ( light.isPointLight ) {

				lightDef.type = 'point';
D
Don McCurdy 已提交
1694
				if ( light.distance > 0 ) lightDef.range = light.distance;
1695 1696 1697 1698

			} else if ( light.isSpotLight ) {

				lightDef.type = 'spot';
D
Don McCurdy 已提交
1699
				if ( light.distance > 0 ) lightDef.range = light.distance;
1700
				lightDef.spot = {};
M
Mugen87 已提交
1701
				lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0;
1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716
				lightDef.spot.outerConeAngle = light.angle;

			}

			if ( light.decay !== undefined && light.decay !== 2 ) {

				console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, '
					+ 'and expects light.decay=2.' );

			}

			if ( light.target
					&& ( light.target.parent !== light
					 || light.target.position.x !== 0
					 || light.target.position.y !== 0
M
Mugen87 已提交
1717
					 || light.target.position.z !== - 1 ) ) {
1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729

				console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, '
					+ 'make light.target a child of the light with position 0,0,-1.' );

			}

			var lights = outputJSON.extensions[ 'KHR_lights_punctual' ].lights;
			lights.push( lightDef );
			return lights.length - 1;

		}

F
Fernando Serrano 已提交
1730 1731 1732 1733 1734
		/**
		 * Process Object3D node
		 * @param  {THREE.Object3D} node Object3D to processNode
		 * @return {Integer}      Index of the node in the nodes list
		 */
M
Mr.doob 已提交
1735
		function processNode( object ) {
F
Fernando Serrano 已提交
1736

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

F
Fernando Serrano 已提交
1739
				outputJSON.nodes = [];
F
Fernando Serrano 已提交
1740

F
Fernando Serrano 已提交
1741 1742
			}

F
Fernando Serrano 已提交
1743 1744 1745
			var gltfNode = {};

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

F
Fernando Serrano 已提交
1747 1748 1749 1750
				var rotation = object.quaternion.toArray();
				var position = object.position.toArray();
				var scale = object.scale.toArray();

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

F
Fernando Serrano 已提交
1753
					gltfNode.rotation = rotation;
F
Fernando Serrano 已提交
1754

F
Fernando Serrano 已提交
1755 1756
				}

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

D
Don McCurdy 已提交
1759
					gltfNode.translation = position;
F
Fernando Serrano 已提交
1760

F
Fernando Serrano 已提交
1761 1762
				}

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

F
Fernando Serrano 已提交
1765
					gltfNode.scale = scale;
F
Fernando Serrano 已提交
1766

F
Fernando Serrano 已提交
1767 1768 1769
				}

			} else {
F
Fernando Serrano 已提交
1770

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

F
Fernando Serrano 已提交
1774
					gltfNode.matrix = object.matrix.elements;
F
Fernando Serrano 已提交
1775

F
Fernando Serrano 已提交
1776
				}
F
Fernando Serrano 已提交
1777

F
Fernando Serrano 已提交
1778 1779
			}

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

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

F
Fernando Serrano 已提交
1785 1786
			}

R
Robert Long 已提交
1787
			serializeUserData( object, gltfNode );
1788

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

1791 1792
				var mesh = processMesh( object );

1793
				if ( mesh !== null ) {
1794 1795 1796 1797

					gltfNode.mesh = mesh;

				}
F
Fernando Serrano 已提交
1798

1799
			} else if ( object.isCamera ) {
F
Fernando Serrano 已提交
1800

F
Fernando Serrano 已提交
1801
				gltfNode.camera = processCamera( object );
F
Fernando Serrano 已提交
1802

1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817
			} else if ( object.isDirectionalLight || object.isPointLight || object.isSpotLight ) {

				if ( ! extensionsUsed[ 'KHR_lights_punctual' ] ) {

					outputJSON.extensions = outputJSON.extensions || {};
					outputJSON.extensions[ 'KHR_lights_punctual' ] = { lights: [] };
					extensionsUsed[ 'KHR_lights_punctual' ] = true;

				}

				gltfNode.extensions = gltfNode.extensions || {};
				gltfNode.extensions[ 'KHR_lights_punctual' ] = { light: processLight( object ) };

			} else if ( object.isLight ) {

1818
				console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', object );
1819 1820
				return null;

F
Fernando Serrano 已提交
1821 1822
			}

1823
			if ( object.isSkinnedMesh ) {
D
Don McCurdy 已提交
1824 1825 1826 1827 1828

				skins.push( object );

			}

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

1831
				var children = [];
F
Fernando Serrano 已提交
1832 1833

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

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

1837 1838
					if ( child.visible || options.onlyVisible === false ) {

1839 1840
						var node = processNode( child );

1841
						if ( node !== null ) {
1842

1843
							children.push( node );
1844 1845

						}
F
Fernando Serrano 已提交
1846

F
Fernando Serrano 已提交
1847
					}
F
Fernando Serrano 已提交
1848

F
Fernando Serrano 已提交
1849
				}
F
Fernando Serrano 已提交
1850

1851 1852 1853 1854 1855 1856 1857
				if ( children.length > 0 ) {

					gltfNode.children = children;

				}


F
Fernando Serrano 已提交
1858 1859 1860 1861
			}

			outputJSON.nodes.push( gltfNode );

T
Takahiro 已提交
1862 1863
			var nodeIndex = outputJSON.nodes.length - 1;
			nodeMap.set( object, nodeIndex );
1864 1865

			return nodeIndex;
F
Fernando Serrano 已提交
1866

F
Fernando Serrano 已提交
1867 1868 1869
		}

		/**
F
Fernando Serrano 已提交
1870
		 * Process Scene
F
Fernando Serrano 已提交
1871 1872 1873
		 * @param  {THREE.Scene} node Scene to process
		 */
		function processScene( scene ) {
F
Fernando Serrano 已提交
1874

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

F
Fernando Serrano 已提交
1877 1878
				outputJSON.scenes = [];
				outputJSON.scene = 0;
F
Fernando Serrano 已提交
1879

F
Fernando Serrano 已提交
1880 1881 1882
			}

			var gltfScene = {
F
Fernando Serrano 已提交
1883

F
Fernando Serrano 已提交
1884
				nodes: []
F
Fernando Serrano 已提交
1885

F
Fernando Serrano 已提交
1886 1887
			};

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

F
Fernando Serrano 已提交
1890
				gltfScene.name = scene.name;
F
Fernando Serrano 已提交
1891

F
Fernando Serrano 已提交
1892 1893
			}

M
makc 已提交
1894 1895 1896 1897 1898 1899
			if ( scene.userData && Object.keys( scene.userData ).length > 0 ) {

				gltfScene.extras = serializeUserData( scene );

			}

F
Fernando Serrano 已提交
1900
			outputJSON.scenes.push( gltfScene );
F
Fernando Serrano 已提交
1901

1902 1903
			var nodes = [];

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

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

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

1910 1911
					var node = processNode( child );

1912
					if ( node !== null ) {
1913

1914
						nodes.push( node );
1915 1916

					}
1917 1918 1919

				}

1920
			}
1921

1922
			if ( nodes.length > 0 ) {
F
Fernando Serrano 已提交
1923

1924
				gltfScene.nodes = nodes;
F
Fernando Serrano 已提交
1925

F
Fernando Serrano 已提交
1926
			}
F
Fernando Serrano 已提交
1927

R
Robert Long 已提交
1928 1929
			serializeUserData( scene, gltfScene );

F
Fernando Serrano 已提交
1930 1931
		}

1932 1933 1934 1935
		/**
		 * Creates a THREE.Scene to hold a list of objects and parse it
		 * @param  {Array} objects List of objects to process
		 */
M
Mr.doob 已提交
1936
		function processObjects( objects ) {
1937 1938

			var scene = new THREE.Scene();
1939
			scene.name = 'AuxScene';
1940

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

1943 1944 1945
				// 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 ] );
1946 1947 1948 1949 1950 1951 1952

			}

			processScene( scene );

		}

1953
		function processInput( input ) {
1954

1955
			input = input instanceof Array ? input : [ input ];
F
Fernando Serrano 已提交
1956

1957
			var objectsWithoutScene = [];
M
Mr.doob 已提交
1958

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

1961
				if ( input[ i ] instanceof THREE.Scene ) {
1962 1963 1964

					processScene( input[ i ] );

1965
				} else {
1966

1967
					objectsWithoutScene.push( input[ i ] );
1968

1969
				}
F
Fernando Serrano 已提交
1970

F
Fernando Serrano 已提交
1971
			}
F
Fernando Serrano 已提交
1972

1973
			if ( objectsWithoutScene.length > 0 ) {
1974

1975
				processObjects( objectsWithoutScene );
1976 1977

			}
F
Fernando Serrano 已提交
1978

D
Don McCurdy 已提交
1979 1980 1981 1982 1983 1984
			for ( var i = 0; i < skins.length; ++ i ) {

				processSkin( skins[ i ] );

			}

1985 1986 1987 1988 1989 1990
			for ( var i = 0; i < options.animations.length; ++ i ) {

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

			}

F
Fernando Serrano 已提交
1991
		}
F
Fernando Serrano 已提交
1992

D
Don McCurdy 已提交
1993
		processInput( input );
F
Fernando Serrano 已提交
1994

1995
		Promise.all( pending ).then( function () {
F
Fernando Serrano 已提交
1996

1997 1998
			// Merge buffers.
			var blob = new Blob( buffers, { type: 'application/octet-stream' } );
1999

2000 2001 2002 2003
			// Declare extensions.
			var extensionsUsedList = Object.keys( extensionsUsed );
			if ( extensionsUsedList.length > 0 ) outputJSON.extensionsUsed = extensionsUsedList;

2004
			if ( outputJSON.buffers && outputJSON.buffers.length > 0 ) {
2005

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

2009
				var reader = new window.FileReader();
2010

2011
				if ( options.binary === true ) {
2012

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

2015 2016 2017
					var GLB_HEADER_BYTES = 12;
					var GLB_HEADER_MAGIC = 0x46546C67;
					var GLB_VERSION = 2;
2018

2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032
					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.
2033
						var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( outputJSON ) ), 0x20 );
2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062
						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 已提交
2063

2064 2065
					};

2066
				} else {
2067

2068 2069
					reader.readAsDataURL( blob );
					reader.onloadend = function () {
2070

2071 2072 2073
						var base64data = reader.result;
						outputJSON.buffers[ 0 ].uri = base64data;
						onDone( outputJSON );
2074

2075
					};
F
Fernando Serrano 已提交
2076

2077
				}
F
Fernando Serrano 已提交
2078

2079
			} else {
F
Fernando Serrano 已提交
2080

2081
				onDone( outputJSON );
2082

2083
			}
2084

2085
		} );
2086

F
Fernando Serrano 已提交
2087
	}
M
Mr.doob 已提交
2088

D
Don McCurdy 已提交
2089
};
2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188

THREE.GLTFExporter.Utils = {

	insertKeyframe: function ( track, time ) {

		var tolerance = 0.001; // 1ms
		var valueSize = track.getValueSize();

		var times = new track.TimeBufferType( track.times.length + 1 );
		var values = new track.ValueBufferType( track.values.length + valueSize );
		var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );

		var index;

		if ( track.times.length === 0 ) {

			times[ 0 ] = time;

			for ( var i = 0; i < valueSize; i ++ ) {

				values[ i ] = 0;

			}

			index = 0;

		} else if ( time < track.times[ 0 ] ) {

			if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;

			times[ 0 ] = time;
			times.set( track.times, 1 );

			values.set( interpolant.evaluate( time ), 0 );
			values.set( track.values, valueSize );

			index = 0;

		} else if ( time > track.times[ track.times.length - 1 ] ) {

			if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {

				return track.times.length - 1;

			}

			times[ times.length - 1 ] = time;
			times.set( track.times, 0 );

			values.set( track.values, 0 );
			values.set( interpolant.evaluate( time ), track.values.length );

			index = times.length - 1;

		} else {

			for ( var i = 0; i < track.times.length; i ++ ) {

				if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;

				if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {

					times.set( track.times.slice( 0, i + 1 ), 0 );
					times[ i + 1 ] = time;
					times.set( track.times.slice( i + 1 ), i + 2 );

					values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
					values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
					values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );

					index = i + 1;

					break;

				}

			}

		}

		track.times = times;
		track.values = values;

		return index;

	},

	mergeMorphTargetTracks: function ( clip, root ) {

		var tracks = [];
		var mergedTracks = {};
		var sourceTracks = clip.tracks;

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

			var sourceTrack = sourceTracks[ i ];
			var sourceTrackBinding = THREE.PropertyBinding.parseTrackName( sourceTrack.name );
			var sourceTrackNode = THREE.PropertyBinding.findNode( root, sourceTrackBinding.nodeName );

2189
			if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) {
2190

2191
				// Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is.
2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210
				tracks.push( sourceTrack );
				continue;

			}

			if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete
				&& sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {

				if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {

					// This should never happen, because glTF morph target animations
					// affect all targets already.
					throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );

				}

				console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' );

				sourceTrack = sourceTrack.clone();
A
aardgoose 已提交
2211
				sourceTrack.setInterpolation( THREE.InterpolateLinear );
2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282

			}

			var targetCount = sourceTrackNode.morphTargetInfluences.length;
			var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];

			if ( targetIndex === undefined ) {

				throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex );

			}

			var mergedTrack;

			// If this is the first time we've seen this object, create a new
			// track to store merged keyframe data for each morph target.
			if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {

				mergedTrack = sourceTrack.clone();

				var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );

				for ( var j = 0; j < mergedTrack.times.length; j ++ ) {

					values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];

				}

				mergedTrack.name = '.morphTargetInfluences';
				mergedTrack.values = values;

				mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
				tracks.push( mergedTrack );

				continue;

			}

			var mergedKeyframeIndex = 0;
			var sourceKeyframeIndex = 0;
			var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );

			mergedTrack = mergedTracks[ sourceTrackNode.uuid ];

			// For every existing keyframe of the merged track, write a (possibly
			// interpolated) value from the source track.
			for ( var j = 0; j < mergedTrack.times.length; j ++ ) {

				mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );

			}

			// For every existing keyframe of the source track, write a (possibly
			// new) keyframe to the merged track. Values from the previous loop may
			// be written again, but keyframes are de-duplicated.
			for ( var j = 0; j < sourceTrack.times.length; j ++ ) {

				var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
				mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];

			}

		}

		clip.tracks = tracks;

		return clip;

	}

};