GLTFExporter.js 40.0 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
		var DEFAULT_OPTIONS = {
69
			binary: false,
70
			trs: false,
71
			onlyVisible: true,
72
			truncateDrawRange: true,
73
			embedImages: true,
74
			animations: [],
75
			forceIndices: false,
76 77
			forcePowerOfTwoTextures: false,
			includeCustomExtensions: false
78 79 80 81
		};

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

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

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

		}

F
Fernando Serrano 已提交
89
		var outputJSON = {
F
Fernando Serrano 已提交
90

F
Fernando Serrano 已提交
91
			asset: {
F
Fernando Serrano 已提交
92

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

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

M
Mr.doob 已提交
98
		};
F
Fernando Serrano 已提交
99 100

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

108
			attributes: new Map(),
109
			materials: new Map(),
110 111
			textures: new Map(),
			images: new Map()
112 113

		};
F
Fernando Serrano 已提交
114

115 116
		var cachedCanvas;

F
Fernando Serrano 已提交
117 118 119
		/**
		 * Compare two arrays
		 */
F
Fernando Serrano 已提交
120 121 122 123 124 125
		/**
		 * 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 已提交
126
		function equalArray( array1, array2 ) {
F
Fernando Serrano 已提交
127

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

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

M
Mugen87 已提交
132
			} );
F
Fernando Serrano 已提交
133

F
Fernando Serrano 已提交
134 135
		}

136 137 138 139 140
		/**
		 * Converts a string to an ArrayBuffer.
		 * @param  {string} text
		 * @return {ArrayBuffer}
		 */
141
		function stringToArrayBuffer( text ) {
142 143 144 145 146 147 148

			if ( window.TextEncoder !== undefined ) {

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

			}

149
			var array = new Uint8Array( new ArrayBuffer( text.length ) );
150

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

153
				var value = text.charCodeAt( i );
154

155
				// Replacing multi-byte character with space(0x20).
F
Fernando Serrano 已提交
156
				array[ i ] = value > 0xFF ? 0x20 : value;
157 158 159

			}

160
			return array.buffer;
161 162 163

		}

F
Fernando Serrano 已提交
164
		/**
165
		 * Get the min and max vectors from the given attribute
T
Takahiro 已提交
166 167 168
		 * @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 已提交
169 170
		 * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
		 */
T
Takahiro 已提交
171
		function getMinMax( attribute, start, count ) {
F
Fernando Serrano 已提交
172

F
Fernando Serrano 已提交
173
			var output = {
F
Fernando Serrano 已提交
174

F
Fernando Serrano 已提交
175 176
				min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
				max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
F
Fernando Serrano 已提交
177

F
Fernando Serrano 已提交
178 179
			};

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

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

F
Fernando Serrano 已提交
184
					var value = attribute.array[ i * attribute.itemSize + a ];
F
Fernando Serrano 已提交
185 186 187
					output.min[ a ] = Math.min( output.min[ a ], value );
					output.max[ a ] = Math.max( output.max[ a ], value );

F
Fernando Serrano 已提交
188
				}
F
Fernando Serrano 已提交
189

F
Fernando Serrano 已提交
190 191
			}

F
Fernando Serrano 已提交
192
			return output;
M
Mugen87 已提交
193

F
Fernando Serrano 已提交
194 195
		}

196 197 198 199 200 201 202 203 204 205 206 207 208
		/**
		 * 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 );

		}

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
		/**
		 * Checks if normal attribute values are normalized.
		 *
		 * @param {THREE.BufferAttribute} normal
		 * @returns {Boolean}
		 *
		 */
		function isNormalizedNormalAttribute( normal ) {

			if ( cachedData.attributes.has( normal ) ) {

				return false;

			}

T
Takahiro 已提交
224
			var v = new THREE.Vector3();
225

T
Takahiro 已提交
226
			for ( var i = 0, il = normal.count; i < il; i ++ ) {
227 228

				// 0.0005 is from glTF-validator
T
Takahiro 已提交
229
				if ( Math.abs( v.fromArray( normal.array, i * 3 ).length() - 1.0 ) > 0.0005 ) return false;
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

			}

			return true;

		}

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

			if ( cachedData.attributes.has( normal ) ) {

248
				return cachedData.attributes.get( normal );
249 250 251 252 253 254 255

			}

			var attribute = normal.clone();

			var v = new THREE.Vector3();

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

T
Takahiro 已提交
258
				v.fromArray( attribute.array, i * 3 );
259 260 261 262 263 264 265 266 267 268 269 270

				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 已提交
271
				v.toArray( attribute.array, i * 3 );
272 273 274 275 276 277 278 279 280

			}

			cachedData.attributes.set( normal, attribute );

			return attribute;

		}

281 282 283 284 285 286 287 288 289 290
		/**
		 * 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 ) {

291
			return Math.ceil( bufferSize / 4 ) * 4;
292 293

		}
M
Mugen87 已提交
294

F
Fernando Serrano 已提交
295
		/**
296 297
		 * Returns a buffer aligned to 4-byte boundary.
		 *
F
Fernando Serrano 已提交
298
		 * @param {ArrayBuffer} arrayBuffer Buffer to pad
299
		 * @param {Integer} paddingByte (Optional)
F
Fernando Serrano 已提交
300 301
		 * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
		 */
302
		function getPaddedArrayBuffer( arrayBuffer, paddingByte ) {
303

304
			paddingByte = paddingByte || 0;
305

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

308
			if ( paddedLength !== arrayBuffer.byteLength ) {
309

310 311
				var array = new Uint8Array( paddedLength );
				array.set( new Uint8Array( arrayBuffer ) );
312

313
				if ( paddingByte !== 0 ) {
314

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

317
						array[ i ] = paddingByte;
318 319 320 321

					}

				}
322

323
				return array.buffer;
F
Fernando Serrano 已提交
324 325 326 327 328 329 330

			}

			return arrayBuffer;

		}

331 332 333
		/**
		 * Serializes a userData.
		 *
334
		 * @param {THREE.Object3D|THREE.Material} object
R
Robert Long 已提交
335
		 * @param {Object} gltfProperty
336
		 */
R
Robert Long 已提交
337 338
		function serializeUserData( object, gltfProperty ) {

339
			if ( Object.keys( object.userData ).length === 0 ) {
R
Robert Long 已提交
340 341 342 343

				return;

			}
344 345 346

			try {

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

349
				if ( options.includeCustomExtensions && json.gltfExtensions ) {
R
Robert Long 已提交
350 351 352 353 354 355 356 357 358 359 360 361 362 363

					if ( gltfProperty.extensions === undefined ) {

						gltfProperty.extensions = {};

					}

					for ( var extensionName in json.gltfExtensions ) {

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

					}

364
					delete json.gltfExtensions;
R
Robert Long 已提交
365

366
				}
367

R
Robert Long 已提交
368 369 370 371 372
				if ( Object.keys( json ).length > 0 ) {

					gltfProperty.extras = json;

				}
373

374
			} catch ( error ) {
375

376 377
				console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' +
					'won\'t be serialized because of JSON.stringify error - ' + error.message );
378 379 380 381 382

			}

		}

F
Fernando Serrano 已提交
383
		/**
F
Fernando Serrano 已提交
384
		 * Process a buffer to append to the default one.
385 386
		 * @param  {ArrayBuffer} buffer
		 * @return {Integer}
F
Fernando Serrano 已提交
387
		 */
388
		function processBuffer( buffer ) {
F
Fernando Serrano 已提交
389

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

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

394
			}
F
Fernando Serrano 已提交
395

396 397
			// All buffers are merged before export.
			buffers.push( buffer );
F
Fernando Serrano 已提交
398

399
			return 0;
F
Fernando Serrano 已提交
400

401
		}
F
Fernando Serrano 已提交
402

403 404 405 406 407 408 409 410 411 412
		/**
		 * 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 已提交
413

414
			if ( ! outputJSON.bufferViews ) {
415

416
				outputJSON.bufferViews = [];
M
Mugen87 已提交
417

418
			}
F
Fernando Serrano 已提交
419

420
			// Create a new dataview and dump the attribute's array into it
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437

			var componentSize;

			if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {

				componentSize = 1;

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

				componentSize = 2;

			} else {

				componentSize = 4;

			}

438
			var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize );
439
			var dataView = new DataView( new ArrayBuffer( byteLength ) );
440
			var offset = 0;
F
Fernando Serrano 已提交
441

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

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

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

450
					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
F
Fernando Serrano 已提交
451

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

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

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

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

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

462 463 464 465
					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {

						dataView.setUint8( offset, value );

F
Fernando Serrano 已提交
466
					}
F
Fernando Serrano 已提交
467

468
					offset += componentSize;
F
Fernando Serrano 已提交
469

F
Fernando Serrano 已提交
470
				}
F
Fernando Serrano 已提交
471

F
Fernando Serrano 已提交
472 473 474
			}

			var gltfBufferView = {
F
Fernando Serrano 已提交
475

476
				buffer: processBuffer( dataView.buffer ),
F
Fernando Serrano 已提交
477
				byteOffset: byteOffset,
478
				byteLength: byteLength
F
Fernando Serrano 已提交
479

F
Fernando Serrano 已提交
480 481
			};

482 483
			if ( target !== undefined ) gltfBufferView.target = target;

484 485 486
			if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {

				// Only define byteStride for vertex attributes.
487
				gltfBufferView.byteStride = attribute.itemSize * componentSize;
488 489 490

			}

491
			byteOffset += byteLength;
F
Fernando Serrano 已提交
492

F
Fernando Serrano 已提交
493
			outputJSON.bufferViews.push( gltfBufferView );
F
Fernando Serrano 已提交
494

495
			// @TODO Merge bufferViews where possible.
F
Fernando Serrano 已提交
496
			var output = {
F
Fernando Serrano 已提交
497

F
Fernando Serrano 已提交
498 499
				id: outputJSON.bufferViews.length - 1,
				byteLength: 0
F
Fernando Serrano 已提交
500

F
Fernando Serrano 已提交
501
			};
F
Fernando Serrano 已提交
502

F
Fernando Serrano 已提交
503
			return output;
F
Fernando Serrano 已提交
504

F
Fernando Serrano 已提交
505 506
		}

507 508 509 510 511
		/**
		 * Process and generate a BufferView from an image Blob.
		 * @param {Blob} blob
		 * @return {Promise<Integer>}
		 */
D
Don McCurdy 已提交
512
		function processBufferViewImage( blob ) {
513 514 515 516 517 518 519

			if ( ! outputJSON.bufferViews ) {

				outputJSON.bufferViews = [];

			}

D
Don McCurdy 已提交
520
			return new Promise( function ( resolve ) {
521 522 523

				var reader = new window.FileReader();
				reader.readAsArrayBuffer( blob );
D
Don McCurdy 已提交
524
				reader.onloadend = function () {
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

					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 已提交
540
				};
541 542 543 544 545

			} );

		}

F
Fernando Serrano 已提交
546
		/**
F
Fernando Serrano 已提交
547
		 * Process attribute to generate an accessor
548 549
		 * @param  {THREE.BufferAttribute} attribute Attribute to process
		 * @param  {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
T
Takahiro 已提交
550 551
		 * @param  {Integer} start (Optional)
		 * @param  {Integer} count (Optional)
F
Fernando Serrano 已提交
552
		 * @return {Integer}           Index of the processed accessor on the "accessors" array
F
Fernando Serrano 已提交
553
		 */
T
Takahiro 已提交
554
		function processAccessor( attribute, geometry, start, count ) {
F
Fernando Serrano 已提交
555

D
Don McCurdy 已提交
556
			var types = {
F
Fernando Serrano 已提交
557

D
Don McCurdy 已提交
558 559 560 561 562
				1: 'SCALAR',
				2: 'VEC2',
				3: 'VEC3',
				4: 'VEC4',
				16: 'MAT4'
F
Fernando Serrano 已提交
563

D
Don McCurdy 已提交
564
			};
F
Fernando Serrano 已提交
565

566 567
			var componentType;

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

571
				componentType = WEBGL_CONSTANTS.FLOAT;
F
Fernando Serrano 已提交
572

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

575
				componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
F
Fernando Serrano 已提交
576

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

579
				componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
F
Fernando Serrano 已提交
580

581 582 583 584
			} else if ( attribute.array.constructor === Uint8Array ) {

				componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;

585
			} else {
F
Fernando Serrano 已提交
586

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

589
			}
F
Fernando Serrano 已提交
590

T
Takahiro 已提交
591 592
			if ( start === undefined ) start = 0;
			if ( count === undefined ) count = attribute.count;
593 594

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

T
Takahiro 已提交
597 598
				var end = start + count;
				var end2 = geometry.drawRange.count === Infinity
M
Mugen87 已提交
599 600
					? attribute.count
					: geometry.drawRange.start + geometry.drawRange.count;
T
Takahiro 已提交
601 602 603 604 605

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

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

607 608
			}

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

612
				return null;
613 614 615

			}

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

618 619 620 621 622 623
			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 已提交
624
				bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
625 626 627 628

			}

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

F
Fernando Serrano 已提交
630
			var gltfAccessor = {
F
Fernando Serrano 已提交
631

F
Fernando Serrano 已提交
632 633 634
				bufferView: bufferView.id,
				byteOffset: bufferView.byteOffset,
				componentType: componentType,
635
				count: count,
F
Fernando Serrano 已提交
636 637
				max: minMax.max,
				min: minMax.min,
D
Don McCurdy 已提交
638
				type: types[ attribute.itemSize ]
F
Fernando Serrano 已提交
639

F
Fernando Serrano 已提交
640 641
			};

642 643 644 645 646 647
			if ( ! outputJSON.accessors ) {

				outputJSON.accessors = [];

			}

F
Fernando Serrano 已提交
648 649 650
			outputJSON.accessors.push( gltfAccessor );

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

F
Fernando Serrano 已提交
652 653 654
		}

		/**
F
Fernando Serrano 已提交
655
		 * Process image
656
		 * @param  {Image} image to process
657
		 * @param  {Integer} format of the image (e.g. THREE.RGBFormat, THREE.RGBAFormat etc)
658
		 * @param  {Boolean} flipY before writing out the image
F
Fernando Serrano 已提交
659 660
		 * @return {Integer}     Index of the processed texture in the "images" array
		 */
661
		function processImage( image, format, flipY ) {
F
Fernando Serrano 已提交
662

663
			if ( ! cachedData.images.has( image ) ) {
664

665 666 667 668 669
				cachedData.images.set( image, {} );

			}

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

673 674 675
			if ( cachedImages[ key ] !== undefined ) {

				return cachedImages[ key ];
676 677

			}
678

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

F
Fernando Serrano 已提交
681
				outputJSON.images = [];
F
Fernando Serrano 已提交
682

F
Fernando Serrano 已提交
683 684
			}

M
Mugen87 已提交
685
			var gltfImage = { mimeType: mimeType };
686

687
			if ( options.embedImages ) {
F
Fernando Serrano 已提交
688

689
				var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' );
690

691 692
				canvas.width = image.width;
				canvas.height = image.height;
693

694
				if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( image ) ) {
695

696
					console.warn( 'GLTFExporter: Resized non-power-of-two image.', image );
697 698 699 700 701 702

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

				}

703
				var ctx = canvas.getContext( '2d' );
704

705
				if ( flipY === true ) {
706

707
					ctx.translate( 0, canvas.height );
M
Mugen87 已提交
708
					ctx.scale( 1, - 1 );
709 710 711

				}

712
				ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
713

714 715 716 717 718 719 720 721 722 723 724
				if ( options.binary === true ) {

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

						canvas.toBlob( function ( blob ) {

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

								gltfImage.bufferView = bufferViewIndex;

								resolve();
725

726 727 728 729 730 731 732 733 734 735 736
							} );

						}, mimeType );

					} ) );

				} else {

					gltfImage.uri = canvas.toDataURL( mimeType );

				}
F
Fernando Serrano 已提交
737

F
Fernando Serrano 已提交
738
			} else {
F
Fernando Serrano 已提交
739

740
				gltfImage.uri = image.src;
F
Fernando Serrano 已提交
741

F
Fernando Serrano 已提交
742 743 744 745
			}

			outputJSON.images.push( gltfImage );

746
			var index = outputJSON.images.length - 1;
747
			cachedImages[ key ] = index;
F
Fernando Serrano 已提交
748

749
			return index;
F
Fernando Serrano 已提交
750

F
Fernando Serrano 已提交
751 752 753 754 755 756 757
		}

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

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

F
Fernando Serrano 已提交
762
				outputJSON.samplers = [];
F
Fernando Serrano 已提交
763

F
Fernando Serrano 已提交
764 765 766
			}

			var gltfSampler = {
F
Fernando Serrano 已提交
767

768 769 770 771
				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 已提交
772

F
Fernando Serrano 已提交
773 774 775 776 777
			};

			outputJSON.samplers.push( gltfSampler );

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

F
Fernando Serrano 已提交
779 780 781 782 783 784 785
		}

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

T
Takahiro 已提交
788
			if ( cachedData.textures.has( map ) ) {
T
Takahiro 已提交
789

T
Takahiro 已提交
790
				return cachedData.textures.get( map );
T
Takahiro 已提交
791 792 793

			}

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

F
Fernando Serrano 已提交
796
				outputJSON.textures = [];
F
Fernando Serrano 已提交
797

F
Fernando Serrano 已提交
798 799 800
			}

			var gltfTexture = {
F
Fernando Serrano 已提交
801

F
Fernando Serrano 已提交
802
				sampler: processSampler( map ),
M
Mugen87 已提交
803
				source: processImage( map.image, map.format, map.flipY )
F
Fernando Serrano 已提交
804

F
Fernando Serrano 已提交
805 806 807 808
			};

			outputJSON.textures.push( gltfTexture );

T
Takahiro 已提交
809
			var index = outputJSON.textures.length - 1;
T
Takahiro 已提交
810
			cachedData.textures.set( map, index );
T
Takahiro 已提交
811 812

			return index;
F
Fernando Serrano 已提交
813

F
Fernando Serrano 已提交
814 815 816 817 818 819
		}

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

T
Takahiro 已提交
823
			if ( cachedData.materials.has( material ) ) {
824

T
Takahiro 已提交
825
				return cachedData.materials.get( material );
826 827 828

			}

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

F
Fernando Serrano 已提交
831
				outputJSON.materials = [];
F
Fernando Serrano 已提交
832

F
Fernando Serrano 已提交
833
			}
F
Fernando Serrano 已提交
834

835
			if ( material.isShaderMaterial ) {
836 837 838 839 840 841

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

			}

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

845
				pbrMetallicRoughness: {}
F
Fernando Serrano 已提交
846

847
			};
848

849
			if ( material.isMeshBasicMaterial ) {
850 851 852 853 854

				gltfMaterial.extensions = { KHR_materials_unlit: {} };

				extensionsUsed[ 'KHR_materials_unlit' ] = true;

855
			} else if ( ! material.isMeshStandardMaterial ) {
856 857 858 859 860

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

			}

861 862
			// pbrMetallicRoughness.baseColorFactor
			var color = material.color.toArray().concat( [ material.opacity ] );
F
Fernando Serrano 已提交
863

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

866
				gltfMaterial.pbrMetallicRoughness.baseColorFactor = color;
867 868 869

			}

870
			if ( material.isMeshStandardMaterial ) {
871

872 873
				gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness;
874

875
			} else if ( material.isMeshBasicMaterial ) {
876 877 878 879

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

M
Mr.doob 已提交
880
			} else {
881

M
Mugen87 已提交
882 883
				gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5;
				gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5;
F
Fernando Serrano 已提交
884

885
			}
886

887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
			// 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.' );

				}

			}

906 907
			// pbrMetallicRoughness.baseColorTexture
			if ( material.map ) {
908

909
				gltfMaterial.pbrMetallicRoughness.baseColorTexture = {
F
Fernando Serrano 已提交
910

911
					index: processTexture( material.map )
F
Fernando Serrano 已提交
912

913
				};
914

915
			}
916

917 918 919
			if ( material.isMeshBasicMaterial ||
				material.isLineBasicMaterial ||
				material.isPointsMaterial ) {
920 921 922 923

			} else {

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

M
Mugen87 已提交
926
				if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) {
927 928 929

					gltfMaterial.emissiveFactor = emissive;

F
Fernando Serrano 已提交
930
				}
931 932 933 934 935 936

				// emissiveTexture
				if ( material.emissiveMap ) {

					gltfMaterial.emissiveTexture = {

937
						index: processTexture( material.emissiveMap )
938 939 940 941 942 943 944 945 946 947 948

					};

				}

			}

			// normalTexture
			if ( material.normalMap ) {

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

950
					index: processTexture( material.normalMap )
F
Fernando Serrano 已提交
951

952 953
				};

M
Mugen87 已提交
954
				if ( material.normalScale.x !== - 1 ) {
955 956 957

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

M
Mugen87 已提交
958
						console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' );
959 960 961 962 963 964 965

					}

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

				}

F
Fernando Serrano 已提交
966 967
			}

968 969 970 971
			// occlusionTexture
			if ( material.aoMap ) {

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

973
					index: processTexture( material.aoMap )
F
Fernando Serrano 已提交
974

975 976
				};

977 978 979 980 981 982
				if ( material.aoMapIntensity !== 1.0 ) {

					gltfMaterial.occlusionTexture.strength = material.aoMapIntensity;

				}

983 984 985
			}

			// alphaMode
986
			if ( material.transparent || material.alphaTest > 0.0 ) {
987

988
				gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK';
989

990 991
				// Write alphaCutoff if it's non-zero and different from the default (0.5).
				if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) {
992 993 994 995

					gltfMaterial.alphaCutoff = material.alphaTest;

				}
996 997 998 999

			}

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

F
Fernando Serrano 已提交
1002
				gltfMaterial.doubleSided = true;
1003

F
Fernando Serrano 已提交
1004 1005
			}

1006
			if ( material.name !== '' ) {
1007

F
Fernando Serrano 已提交
1008
				gltfMaterial.name = material.name;
1009

F
Fernando Serrano 已提交
1010 1011
			}

R
Robert Long 已提交
1012
			serializeUserData( material, gltfMaterial );
1013

F
Fernando Serrano 已提交
1014
			outputJSON.materials.push( gltfMaterial );
F
Fernando Serrano 已提交
1015

1016
			var index = outputJSON.materials.length - 1;
T
Takahiro 已提交
1017
			cachedData.materials.set( material, index );
1018 1019

			return index;
1020

F
Fernando Serrano 已提交
1021 1022 1023
		}

		/**
F
Fernando Serrano 已提交
1024 1025 1026
		 * Process mesh
		 * @param  {THREE.Mesh} mesh Mesh to process
		 * @return {Integer}      Index of the processed mesh in the "meshes" array
F
Fernando Serrano 已提交
1027 1028
		 */
		function processMesh( mesh ) {
F
Fernando Serrano 已提交
1029

F
Fernando Serrano 已提交
1030
			var geometry = mesh.geometry;
F
Fernando Serrano 已提交
1031

M
Mr.doob 已提交
1032 1033
			var mode;

1034
			// Use the correct mode
1035
			if ( mesh.isLineSegments ) {
1036

1037
				mode = WEBGL_CONSTANTS.LINES;
1038

1039
			} else if ( mesh.isLineLoop ) {
1040

1041
				mode = WEBGL_CONSTANTS.LINE_LOOP;
1042

1043
			} else if ( mesh.isLine ) {
1044

1045
				mode = WEBGL_CONSTANTS.LINE_STRIP;
1046

1047
			} else if ( mesh.isPoints ) {
1048

1049
				mode = WEBGL_CONSTANTS.POINTS;
1050 1051 1052

			} else {

M
Mugen87 已提交
1053
				if ( ! geometry.isBufferGeometry ) {
1054 1055 1056 1057 1058 1059 1060

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

				}

1061 1062
				if ( mesh.drawMode === THREE.TriangleFanDrawMode ) {

F
Fernando Serrano 已提交
1063
					console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' );
1064
					mode = WEBGL_CONSTANTS.TRIANGLE_FAN;
1065 1066 1067

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

1068
					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINE_STRIP : WEBGL_CONSTANTS.TRIANGLE_STRIP;
1069 1070 1071

				} else {

1072
					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
1073 1074 1075 1076

				}

			}
F
Fernando Serrano 已提交
1077

T
Takahiro 已提交
1078
			var gltfMesh = {};
1079

T
Takahiro 已提交
1080 1081 1082
			var attributes = {};
			var primitives = [];
			var targets = [];
F
Fernando Serrano 已提交
1083

F
Fernando Serrano 已提交
1084 1085
			// Conversion between attributes names in threejs and gltf spec
			var nameConversion = {
F
Fernando Serrano 已提交
1086

F
Fernando Serrano 已提交
1087 1088
				uv: 'TEXCOORD_0',
				uv2: 'TEXCOORD_1',
1089 1090 1091
				color: 'COLOR_0',
				skinWeight: 'WEIGHTS_0',
				skinIndex: 'JOINTS_0'
F
Fernando Serrano 已提交
1092

F
Fernando Serrano 已提交
1093 1094
			};

1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
			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 ) );

			}

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

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

1112
				// JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
1113
				var array = attribute.array;
1114
				if ( attributeName === 'JOINTS_0' &&
1115 1116
					! ( array instanceof Uint16Array ) &&
					! ( array instanceof Uint8Array ) ) {
1117 1118

					console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
1119
					attribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
1120 1121 1122

				}

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

1125
					var accessor = processAccessor( attribute, geometry );
1126
					if ( accessor !== null ) {
D
Don McCurdy 已提交
1127

1128 1129 1130
						attributes[ attributeName ] = accessor;

					}
D
Don McCurdy 已提交
1131 1132 1133 1134 1135

				}

			}

1136 1137
			if ( originalNormal !== undefined ) geometry.addAttribute( 'normal', originalNormal );

1138 1139 1140 1141 1142 1143 1144
			// Skip if no exportable attributes found
			if ( Object.keys( attributes ).length === 0 ) {

				return null;

			}

D
Don McCurdy 已提交
1145 1146 1147
			// Morph targets
			if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {

1148
				var weights = [];
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
				var targetNames = [];
				var reverseDictionary = {};

				if ( mesh.morphTargetDictionary !== undefined ) {

					for ( var key in mesh.morphTargetDictionary ) {

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

					}

				}

D
Don McCurdy 已提交
1162 1163 1164 1165
				for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {

					var target = {};

1166 1167
					var warned = false;

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

T
Takahiro 已提交
1170
						// glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
1171
						// Three.js doesn't support TANGENT yet.
T
Takahiro 已提交
1172 1173 1174

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

1175 1176 1177 1178 1179 1180 1181
							if ( ! warned ) {

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

							}

T
Takahiro 已提交
1182 1183 1184 1185
							continue;

						}

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

1188
						// Three.js morph attribute has absolute values while the one of glTF has relative values.
T
Takahiro 已提交
1189 1190 1191 1192 1193 1194
						//
						// 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
1195
						var relativeAttribute = attribute.clone();
T
Takahiro 已提交
1196 1197 1198

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

1199
							relativeAttribute.setXYZ(
T
Takahiro 已提交
1200 1201 1202 1203 1204 1205 1206 1207
								j,
								attribute.getX( j ) - baseAttribute.getX( j ),
								attribute.getY( j ) - baseAttribute.getY( j ),
								attribute.getZ( j ) - baseAttribute.getZ( j )
							);

						}

1208
						target[ attributeName.toUpperCase() ] = processAccessor( relativeAttribute, geometry );
D
Don McCurdy 已提交
1209 1210 1211

					}

T
Takahiro 已提交
1212
					targets.push( target );
D
Don McCurdy 已提交
1213

1214
					weights.push( mesh.morphTargetInfluences[ i ] );
1215
					if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
1216

D
Don McCurdy 已提交
1217
				}
F
Fernando Serrano 已提交
1218

1219 1220
				gltfMesh.weights = weights;

1221 1222 1223 1224 1225 1226 1227
				if ( targetNames.length > 0 ) {

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

				}

F
Fernando Serrano 已提交
1228 1229
			}

1230
			var forceIndices = options.forceIndices;
T
Takahiro 已提交
1231
			var isMultiMaterial = Array.isArray( mesh.material );
1232

1233
			if ( isMultiMaterial && geometry.groups.length === 0 ) return null;
1234

1235
			if ( ! forceIndices && geometry.index === null && isMultiMaterial ) {
1236 1237

				// temporal workaround.
1238
				console.warn( 'THREE.GLTFExporter: Creating index for non-indexed multi-material mesh.' );
1239 1240 1241 1242
				forceIndices = true;

			}

1243
			var didForceIndices = false;
T
Takahiro 已提交
1244

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

1247
				var indices = [];
T
Takahiro 已提交
1248

1249
				for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) {
T
Takahiro 已提交
1250 1251 1252 1253 1254

					indices[ i ] = i;

				}

1255
				geometry.setIndex( indices );
T
Takahiro 已提交
1256

1257
				didForceIndices = true;
T
Takahiro 已提交
1258 1259 1260

			}

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

1264
			for ( var i = 0, il = groups.length; i < il; i ++ ) {
T
Takahiro 已提交
1265 1266 1267 1268 1269 1270

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

R
Robert Long 已提交
1271
				serializeUserData( geometry, primitive );
1272

T
Takahiro 已提交
1273 1274 1275 1276 1277 1278 1279 1280
				if ( targets.length > 0 ) primitive.targets = targets;

				if ( geometry.index !== null ) {

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

				}

1281
				var material = processMaterial( materials[ groups[ i ].materialIndex ] );
1282

1283
				if ( material !== null ) {
1284

1285
					primitive.material = material;
1286 1287

				}
T
Takahiro 已提交
1288

1289 1290
				primitives.push( primitive );

T
Takahiro 已提交
1291 1292
			}

1293
			if ( didForceIndices ) {
T
Takahiro 已提交
1294

1295
				geometry.setIndex( null );
T
Takahiro 已提交
1296 1297 1298 1299 1300

			}

			gltfMesh.primitives = primitives;

1301 1302 1303 1304 1305
			if ( ! outputJSON.meshes ) {

				outputJSON.meshes = [];

			}
F
Fernando Serrano 已提交
1306

F
Fernando Serrano 已提交
1307 1308 1309
			outputJSON.meshes.push( gltfMesh );

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

F
Fernando Serrano 已提交
1311 1312
		}

F
Fernando Serrano 已提交
1313 1314 1315 1316 1317 1318
		/**
		 * 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 已提交
1319

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

F
Fernando Serrano 已提交
1322
				outputJSON.cameras = [];
F
Fernando Serrano 已提交
1323

F
Fernando Serrano 已提交
1324 1325
			}

1326
			var isOrtho = camera.isOrthographicCamera;
F
Fernando Serrano 已提交
1327 1328

			var gltfCamera = {
F
Fernando Serrano 已提交
1329

F
Fernando Serrano 已提交
1330
				type: isOrtho ? 'orthographic' : 'perspective'
F
Fernando Serrano 已提交
1331

F
Fernando Serrano 已提交
1332 1333 1334 1335 1336 1337 1338 1339
			};

			if ( isOrtho ) {

				gltfCamera.orthographic = {

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

F
Fernando Serrano 已提交
1343
				};
F
Fernando Serrano 已提交
1344 1345 1346 1347 1348 1349 1350

			} else {

				gltfCamera.perspective = {

					aspectRatio: camera.aspect,
					yfov: THREE.Math.degToRad( camera.fov ) / camera.aspect,
1351 1352
					zfar: camera.far <= 0 ? 0.001 : camera.far,
					znear: camera.near < 0 ? 0 : camera.near
F
Fernando Serrano 已提交
1353 1354 1355 1356 1357

				};

			}

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

F
Fernando Serrano 已提交
1360
				gltfCamera.name = camera.type;
F
Fernando Serrano 已提交
1361

F
Fernando Serrano 已提交
1362 1363 1364 1365 1366
			}

			outputJSON.cameras.push( gltfCamera );

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

F
Fernando Serrano 已提交
1368 1369
		}

1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
		/**
		 * 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 已提交
1380
		function processAnimation( clip, root ) {
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397

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

1398
				if ( trackBinding.objectName === 'bones' ) {
1399

1400 1401 1402 1403 1404 1405 1406 1407 1408
					if ( trackNode.isSkinnedMesh === true ) {

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

					} else {

						trackNode = undefined;

					}
1409 1410 1411

				}

1412 1413 1414
				if ( ! trackNode || ! trackProperty ) {

					console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
1415
					return null;
1416 1417 1418

				}

D
Don McCurdy 已提交
1419 1420 1421 1422 1423 1424 1425 1426 1427
				var inputItemSize = 1;
				var outputItemSize = track.values.length / track.times.length;

				if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {

					outputItemSize /= trackNode.morphTargetInfluences.length;

				}

T
Takahiro 已提交
1428 1429
				var interpolation;

1430 1431
				// @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE

1432
				// Detecting glTF cubic spline interpolant by checking factory method's special property
1433 1434
				// GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
				// valid value from .getInterpolation().
1435
				if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
T
Takahiro 已提交
1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453

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

				}

1454 1455
				samplers.push( {

D
Don McCurdy 已提交
1456 1457
					input: processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ),
					output: processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ),
T
Takahiro 已提交
1458
					interpolation: interpolation
1459 1460 1461 1462 1463 1464 1465

				} );

				channels.push( {

					sampler: samplers.length - 1,
					target: {
T
Takahiro 已提交
1466
						node: nodeMap.get( trackNode ),
1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485
						path: trackProperty
					}

				} );

			}

			outputJSON.animations.push( {

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

			} );

			return outputJSON.animations.length - 1;

		}

D
Don McCurdy 已提交
1486 1487
		function processSkin( object ) {

T
Takahiro 已提交
1488
			var node = outputJSON.nodes[ nodeMap.get( object ) ];
D
Don McCurdy 已提交
1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499

			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 已提交
1500
				joints.push( nodeMap.get( skeleton.bones[ i ] ) );
D
Don McCurdy 已提交
1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515

				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 已提交
1516
				skeleton: nodeMap.get( rootJoint )
D
Don McCurdy 已提交
1517 1518 1519 1520 1521 1522 1523 1524 1525

			} );

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

			return skinIndex;

		}

F
Fernando Serrano 已提交
1526 1527 1528 1529 1530
		/**
		 * Process Object3D node
		 * @param  {THREE.Object3D} node Object3D to processNode
		 * @return {Integer}      Index of the node in the nodes list
		 */
M
Mr.doob 已提交
1531
		function processNode( object ) {
F
Fernando Serrano 已提交
1532

1533
			if ( object.isLight ) {
1534 1535

				console.warn( 'GLTFExporter: Unsupported node type:', object.constructor.name );
1536
				return null;
1537 1538 1539

			}

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

F
Fernando Serrano 已提交
1542
				outputJSON.nodes = [];
F
Fernando Serrano 已提交
1543

F
Fernando Serrano 已提交
1544 1545
			}

F
Fernando Serrano 已提交
1546 1547 1548
			var gltfNode = {};

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

F
Fernando Serrano 已提交
1550 1551 1552 1553
				var rotation = object.quaternion.toArray();
				var position = object.position.toArray();
				var scale = object.scale.toArray();

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

F
Fernando Serrano 已提交
1556
					gltfNode.rotation = rotation;
F
Fernando Serrano 已提交
1557

F
Fernando Serrano 已提交
1558 1559
				}

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

D
Don McCurdy 已提交
1562
					gltfNode.translation = position;
F
Fernando Serrano 已提交
1563

F
Fernando Serrano 已提交
1564 1565
				}

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

F
Fernando Serrano 已提交
1568
					gltfNode.scale = scale;
F
Fernando Serrano 已提交
1569

F
Fernando Serrano 已提交
1570 1571 1572
				}

			} else {
F
Fernando Serrano 已提交
1573

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

F
Fernando Serrano 已提交
1577
					gltfNode.matrix = object.matrix.elements;
F
Fernando Serrano 已提交
1578

F
Fernando Serrano 已提交
1579
				}
F
Fernando Serrano 已提交
1580

F
Fernando Serrano 已提交
1581 1582
			}

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

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

F
Fernando Serrano 已提交
1588 1589
			}

R
Robert Long 已提交
1590
			serializeUserData( object, gltfNode );
1591

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

1594 1595
				var mesh = processMesh( object );

1596
				if ( mesh !== null ) {
1597 1598 1599 1600

					gltfNode.mesh = mesh;

				}
F
Fernando Serrano 已提交
1601

1602
			} else if ( object.isCamera ) {
F
Fernando Serrano 已提交
1603

F
Fernando Serrano 已提交
1604
				gltfNode.camera = processCamera( object );
F
Fernando Serrano 已提交
1605

F
Fernando Serrano 已提交
1606 1607
			}

1608
			if ( object.isSkinnedMesh ) {
D
Don McCurdy 已提交
1609 1610 1611 1612 1613

				skins.push( object );

			}

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

1616
				var children = [];
F
Fernando Serrano 已提交
1617 1618

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

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

1622 1623
					if ( child.visible || options.onlyVisible === false ) {

1624 1625
						var node = processNode( child );

1626
						if ( node !== null ) {
1627

1628
							children.push( node );
1629 1630

						}
F
Fernando Serrano 已提交
1631

F
Fernando Serrano 已提交
1632
					}
F
Fernando Serrano 已提交
1633

F
Fernando Serrano 已提交
1634
				}
F
Fernando Serrano 已提交
1635

1636 1637 1638 1639 1640 1641 1642
				if ( children.length > 0 ) {

					gltfNode.children = children;

				}


F
Fernando Serrano 已提交
1643 1644 1645 1646
			}

			outputJSON.nodes.push( gltfNode );

T
Takahiro 已提交
1647 1648
			var nodeIndex = outputJSON.nodes.length - 1;
			nodeMap.set( object, nodeIndex );
1649 1650

			return nodeIndex;
F
Fernando Serrano 已提交
1651

F
Fernando Serrano 已提交
1652 1653 1654
		}

		/**
F
Fernando Serrano 已提交
1655
		 * Process Scene
F
Fernando Serrano 已提交
1656 1657 1658
		 * @param  {THREE.Scene} node Scene to process
		 */
		function processScene( scene ) {
F
Fernando Serrano 已提交
1659

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

F
Fernando Serrano 已提交
1662 1663
				outputJSON.scenes = [];
				outputJSON.scene = 0;
F
Fernando Serrano 已提交
1664

F
Fernando Serrano 已提交
1665 1666 1667
			}

			var gltfScene = {
F
Fernando Serrano 已提交
1668

F
Fernando Serrano 已提交
1669
				nodes: []
F
Fernando Serrano 已提交
1670

F
Fernando Serrano 已提交
1671 1672
			};

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

F
Fernando Serrano 已提交
1675
				gltfScene.name = scene.name;
F
Fernando Serrano 已提交
1676

F
Fernando Serrano 已提交
1677 1678
			}

F
Fernando Serrano 已提交
1679
			outputJSON.scenes.push( gltfScene );
F
Fernando Serrano 已提交
1680

1681 1682
			var nodes = [];

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

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

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

1689 1690
					var node = processNode( child );

1691
					if ( node !== null ) {
1692

1693
						nodes.push( node );
1694 1695

					}
1696 1697 1698

				}

1699
			}
1700

1701
			if ( nodes.length > 0 ) {
F
Fernando Serrano 已提交
1702

1703
				gltfScene.nodes = nodes;
F
Fernando Serrano 已提交
1704

F
Fernando Serrano 已提交
1705
			}
F
Fernando Serrano 已提交
1706

R
Robert Long 已提交
1707 1708
			serializeUserData( scene, gltfScene );

F
Fernando Serrano 已提交
1709 1710
		}

1711 1712 1713 1714
		/**
		 * Creates a THREE.Scene to hold a list of objects and parse it
		 * @param  {Array} objects List of objects to process
		 */
M
Mr.doob 已提交
1715
		function processObjects( objects ) {
1716 1717

			var scene = new THREE.Scene();
1718
			scene.name = 'AuxScene';
1719

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

1722 1723 1724
				// 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 ] );
1725 1726 1727 1728 1729 1730 1731

			}

			processScene( scene );

		}

1732
		function processInput( input ) {
1733

1734
			input = input instanceof Array ? input : [ input ];
F
Fernando Serrano 已提交
1735

1736
			var objectsWithoutScene = [];
M
Mr.doob 已提交
1737

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

1740
				if ( input[ i ] instanceof THREE.Scene ) {
1741 1742 1743

					processScene( input[ i ] );

1744
				} else {
1745

1746
					objectsWithoutScene.push( input[ i ] );
1747

1748
				}
F
Fernando Serrano 已提交
1749

F
Fernando Serrano 已提交
1750
			}
F
Fernando Serrano 已提交
1751

1752
			if ( objectsWithoutScene.length > 0 ) {
1753

1754
				processObjects( objectsWithoutScene );
1755 1756

			}
F
Fernando Serrano 已提交
1757

D
Don McCurdy 已提交
1758 1759 1760 1761 1762 1763
			for ( var i = 0; i < skins.length; ++ i ) {

				processSkin( skins[ i ] );

			}

1764 1765 1766 1767 1768 1769
			for ( var i = 0; i < options.animations.length; ++ i ) {

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

			}

F
Fernando Serrano 已提交
1770
		}
F
Fernando Serrano 已提交
1771

D
Don McCurdy 已提交
1772
		processInput( input );
F
Fernando Serrano 已提交
1773

1774
		Promise.all( pending ).then( function () {
F
Fernando Serrano 已提交
1775

1776 1777
			// Merge buffers.
			var blob = new Blob( buffers, { type: 'application/octet-stream' } );
1778

1779 1780 1781 1782
			// Declare extensions.
			var extensionsUsedList = Object.keys( extensionsUsed );
			if ( extensionsUsedList.length > 0 ) outputJSON.extensionsUsed = extensionsUsedList;

1783
			if ( outputJSON.buffers && outputJSON.buffers.length > 0 ) {
1784

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

1788
				var reader = new window.FileReader();
1789

1790
				if ( options.binary === true ) {
1791

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

1794 1795 1796
					var GLB_HEADER_BYTES = 12;
					var GLB_HEADER_MAGIC = 0x46546C67;
					var GLB_VERSION = 2;
1797

1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811
					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.
1812
						var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( outputJSON ) ), 0x20 );
1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841
						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 已提交
1842

1843 1844
					};

1845
				} else {
1846

1847 1848
					reader.readAsDataURL( blob );
					reader.onloadend = function () {
1849

1850 1851 1852
						var base64data = reader.result;
						outputJSON.buffers[ 0 ].uri = base64data;
						onDone( outputJSON );
1853

1854
					};
F
Fernando Serrano 已提交
1855

1856
				}
F
Fernando Serrano 已提交
1857

1858
			} else {
F
Fernando Serrano 已提交
1859

1860
				onDone( outputJSON );
1861

1862
			}
1863

1864
		} );
1865

F
Fernando Serrano 已提交
1866
	}
M
Mr.doob 已提交
1867

D
Don McCurdy 已提交
1868
};