1 | ( function () {
|
2 |
|
3 | class GLTFExporter {
|
4 |
|
5 | constructor() {
|
6 |
|
7 | this.pluginCallbacks = [];
|
8 | this.register( function ( writer ) {
|
9 |
|
10 | return new GLTFLightExtension( writer );
|
11 |
|
12 | } );
|
13 | this.register( function ( writer ) {
|
14 |
|
15 | return new GLTFMaterialsUnlitExtension( writer );
|
16 |
|
17 | } );
|
18 | this.register( function ( writer ) {
|
19 |
|
20 | return new GLTFMaterialsTransmissionExtension( writer );
|
21 |
|
22 | } );
|
23 | this.register( function ( writer ) {
|
24 |
|
25 | return new GLTFMaterialsVolumeExtension( writer );
|
26 |
|
27 | } );
|
28 | this.register( function ( writer ) {
|
29 |
|
30 | return new GLTFMaterialsClearcoatExtension( writer );
|
31 |
|
32 | } );
|
33 | this.register( function ( writer ) {
|
34 |
|
35 | return new GLTFMaterialsIridescenceExtension( writer );
|
36 |
|
37 | } );
|
38 |
|
39 | }
|
40 | register( callback ) {
|
41 |
|
42 | if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
|
43 |
|
44 | this.pluginCallbacks.push( callback );
|
45 |
|
46 | }
|
47 |
|
48 | return this;
|
49 |
|
50 | }
|
51 | unregister( callback ) {
|
52 |
|
53 | if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) {
|
54 |
|
55 | this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 );
|
56 |
|
57 | }
|
58 |
|
59 | return this;
|
60 |
|
61 | }
|
62 |
|
63 | |
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | parse( input, onDone, onError, options ) {
|
71 |
|
72 | const writer = new GLTFWriter();
|
73 | const plugins = [];
|
74 | for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) {
|
75 |
|
76 | plugins.push( this.pluginCallbacks[ i ]( writer ) );
|
77 |
|
78 | }
|
79 |
|
80 | writer.setPlugins( plugins );
|
81 | writer.write( input, onDone, options ).catch( onError );
|
82 |
|
83 | }
|
84 | parseAsync( input, options ) {
|
85 |
|
86 | const scope = this;
|
87 | return new Promise( function ( resolve, reject ) {
|
88 |
|
89 | scope.parse( input, resolve, reject, options );
|
90 |
|
91 | } );
|
92 |
|
93 | }
|
94 |
|
95 | }
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | const WEBGL_CONSTANTS = {
|
102 | POINTS: 0x0000,
|
103 | LINES: 0x0001,
|
104 | LINE_LOOP: 0x0002,
|
105 | LINE_STRIP: 0x0003,
|
106 | TRIANGLES: 0x0004,
|
107 | TRIANGLE_STRIP: 0x0005,
|
108 | TRIANGLE_FAN: 0x0006,
|
109 | UNSIGNED_BYTE: 0x1401,
|
110 | UNSIGNED_SHORT: 0x1403,
|
111 | FLOAT: 0x1406,
|
112 | UNSIGNED_INT: 0x1405,
|
113 | ARRAY_BUFFER: 0x8892,
|
114 | ELEMENT_ARRAY_BUFFER: 0x8893,
|
115 | NEAREST: 0x2600,
|
116 | LINEAR: 0x2601,
|
117 | NEAREST_MIPMAP_NEAREST: 0x2700,
|
118 | LINEAR_MIPMAP_NEAREST: 0x2701,
|
119 | NEAREST_MIPMAP_LINEAR: 0x2702,
|
120 | LINEAR_MIPMAP_LINEAR: 0x2703,
|
121 | CLAMP_TO_EDGE: 33071,
|
122 | MIRRORED_REPEAT: 33648,
|
123 | REPEAT: 10497
|
124 | };
|
125 | const THREE_TO_WEBGL = {};
|
126 | THREE_TO_WEBGL[ THREE.NearestFilter ] = WEBGL_CONSTANTS.NEAREST;
|
127 | THREE_TO_WEBGL[ THREE.NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
|
128 | THREE_TO_WEBGL[ THREE.NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
|
129 | THREE_TO_WEBGL[ THREE.LinearFilter ] = WEBGL_CONSTANTS.LINEAR;
|
130 | THREE_TO_WEBGL[ THREE.LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
|
131 | THREE_TO_WEBGL[ THREE.LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
|
132 | THREE_TO_WEBGL[ THREE.ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
|
133 | THREE_TO_WEBGL[ THREE.RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT;
|
134 | THREE_TO_WEBGL[ THREE.MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
|
135 | const PATH_PROPERTIES = {
|
136 | scale: 'scale',
|
137 | position: 'translation',
|
138 | quaternion: 'rotation',
|
139 | morphTargetInfluences: 'weights'
|
140 | };
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | const GLB_HEADER_BYTES = 12;
|
146 | const GLB_HEADER_MAGIC = 0x46546C67;
|
147 | const GLB_VERSION = 2;
|
148 | const GLB_CHUNK_PREFIX_BYTES = 8;
|
149 | const GLB_CHUNK_TYPE_JSON = 0x4E4F534A;
|
150 | const GLB_CHUNK_TYPE_BIN = 0x004E4942;
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | |
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | function equalArray( array1, array2 ) {
|
163 |
|
164 | return array1.length === array2.length && array1.every( function ( element, index ) {
|
165 |
|
166 | return element === array2[ index ];
|
167 |
|
168 | } );
|
169 |
|
170 | }
|
171 |
|
172 | |
173 |
|
174 |
|
175 |
|
176 |
|
177 | function stringToArrayBuffer( text ) {
|
178 |
|
179 | return new TextEncoder().encode( text ).buffer;
|
180 |
|
181 | }
|
182 |
|
183 | |
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 | function isIdentityMatrix( matrix ) {
|
190 |
|
191 | return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] );
|
192 |
|
193 | }
|
194 |
|
195 | |
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | function getMinMax( attribute, start, count ) {
|
203 |
|
204 | const output = {
|
205 | min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
|
206 | max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
|
207 | };
|
208 | for ( let i = start; i < start + count; i ++ ) {
|
209 |
|
210 | for ( let a = 0; a < attribute.itemSize; a ++ ) {
|
211 |
|
212 | let value;
|
213 | if ( attribute.itemSize > 4 ) {
|
214 |
|
215 |
|
216 |
|
217 | value = attribute.array[ i * attribute.itemSize + a ];
|
218 |
|
219 | } else {
|
220 |
|
221 | if ( a === 0 ) value = attribute.getX( i ); else if ( a === 1 ) value = attribute.getY( i ); else if ( a === 2 ) value = attribute.getZ( i ); else if ( a === 3 ) value = attribute.getW( i );
|
222 |
|
223 | }
|
224 |
|
225 | output.min[ a ] = Math.min( output.min[ a ], value );
|
226 | output.max[ a ] = Math.max( output.max[ a ], value );
|
227 |
|
228 | }
|
229 |
|
230 | }
|
231 |
|
232 | return output;
|
233 |
|
234 | }
|
235 |
|
236 | |
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | function getPaddedBufferSize( bufferSize ) {
|
245 |
|
246 | return Math.ceil( bufferSize / 4 ) * 4;
|
247 |
|
248 | }
|
249 |
|
250 | |
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 | function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) {
|
258 |
|
259 | const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength );
|
260 | if ( paddedLength !== arrayBuffer.byteLength ) {
|
261 |
|
262 | const array = new Uint8Array( paddedLength );
|
263 | array.set( new Uint8Array( arrayBuffer ) );
|
264 | if ( paddingByte !== 0 ) {
|
265 |
|
266 | for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) {
|
267 |
|
268 | array[ i ] = paddingByte;
|
269 |
|
270 | }
|
271 |
|
272 | }
|
273 |
|
274 | return array.buffer;
|
275 |
|
276 | }
|
277 |
|
278 | return arrayBuffer;
|
279 |
|
280 | }
|
281 |
|
282 | function getCanvas() {
|
283 |
|
284 | if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
|
285 |
|
286 | return new OffscreenCanvas( 1, 1 );
|
287 |
|
288 | }
|
289 |
|
290 | return document.createElement( 'canvas' );
|
291 |
|
292 | }
|
293 |
|
294 | function getToBlobPromise( canvas, mimeType ) {
|
295 |
|
296 | if ( canvas.toBlob !== undefined ) {
|
297 |
|
298 | return new Promise( resolve => canvas.toBlob( resolve, mimeType ) );
|
299 |
|
300 | }
|
301 |
|
302 | let quality;
|
303 |
|
304 |
|
305 |
|
306 | if ( mimeType === 'image/jpeg' ) {
|
307 |
|
308 | quality = 0.92;
|
309 |
|
310 | } else if ( mimeType === 'image/webp' ) {
|
311 |
|
312 | quality = 0.8;
|
313 |
|
314 | }
|
315 |
|
316 | return canvas.convertToBlob( {
|
317 | type: mimeType,
|
318 | quality: quality
|
319 | } );
|
320 |
|
321 | }
|
322 |
|
323 | |
324 |
|
325 |
|
326 | class GLTFWriter {
|
327 |
|
328 | constructor() {
|
329 |
|
330 | this.plugins = [];
|
331 | this.options = {};
|
332 | this.pending = [];
|
333 | this.buffers = [];
|
334 | this.byteOffset = 0;
|
335 | this.buffers = [];
|
336 | this.nodeMap = new Map();
|
337 | this.skins = [];
|
338 | this.extensionsUsed = {};
|
339 | this.uids = new Map();
|
340 | this.uid = 0;
|
341 | this.json = {
|
342 | asset: {
|
343 | version: '2.0',
|
344 | generator: 'THREE.GLTFExporter'
|
345 | }
|
346 | };
|
347 | this.cache = {
|
348 | meshes: new Map(),
|
349 | attributes: new Map(),
|
350 | attributesNormalized: new Map(),
|
351 | materials: new Map(),
|
352 | textures: new Map(),
|
353 | images: new Map()
|
354 | };
|
355 |
|
356 | }
|
357 | setPlugins( plugins ) {
|
358 |
|
359 | this.plugins = plugins;
|
360 |
|
361 | }
|
362 |
|
363 | |
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 | async write( input, onDone, options ) {
|
370 |
|
371 | this.options = Object.assign( {}, {
|
372 |
|
373 | binary: false,
|
374 | trs: false,
|
375 | onlyVisible: true,
|
376 | maxTextureSize: Infinity,
|
377 | animations: [],
|
378 | includeCustomExtensions: false
|
379 | }, options );
|
380 | if ( this.options.animations.length > 0 ) {
|
381 |
|
382 |
|
383 | this.options.trs = true;
|
384 |
|
385 | }
|
386 |
|
387 | this.processInput( input );
|
388 | await Promise.all( this.pending );
|
389 | const writer = this;
|
390 | const buffers = writer.buffers;
|
391 | const json = writer.json;
|
392 | options = writer.options;
|
393 | const extensionsUsed = writer.extensionsUsed;
|
394 |
|
395 |
|
396 | const blob = new Blob( buffers, {
|
397 | type: 'application/octet-stream'
|
398 | } );
|
399 |
|
400 |
|
401 | const extensionsUsedList = Object.keys( extensionsUsed );
|
402 | if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList;
|
403 |
|
404 |
|
405 | if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size;
|
406 | if ( options.binary === true ) {
|
407 |
|
408 |
|
409 |
|
410 | const reader = new FileReader();
|
411 | reader.readAsArrayBuffer( blob );
|
412 | reader.onloadend = function () {
|
413 |
|
414 |
|
415 | const binaryChunk = getPaddedArrayBuffer( reader.result );
|
416 | const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
|
417 | binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true );
|
418 | binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true );
|
419 |
|
420 |
|
421 | const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 );
|
422 | const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
|
423 | jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true );
|
424 | jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true );
|
425 |
|
426 |
|
427 | const header = new ArrayBuffer( GLB_HEADER_BYTES );
|
428 | const headerView = new DataView( header );
|
429 | headerView.setUint32( 0, GLB_HEADER_MAGIC, true );
|
430 | headerView.setUint32( 4, GLB_VERSION, true );
|
431 | const totalByteLength = GLB_HEADER_BYTES + jsonChunkPrefix.byteLength + jsonChunk.byteLength + binaryChunkPrefix.byteLength + binaryChunk.byteLength;
|
432 | headerView.setUint32( 8, totalByteLength, true );
|
433 | const glbBlob = new Blob( [ header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk ], {
|
434 | type: 'application/octet-stream'
|
435 | } );
|
436 | const glbReader = new FileReader();
|
437 | glbReader.readAsArrayBuffer( glbBlob );
|
438 | glbReader.onloadend = function () {
|
439 |
|
440 | onDone( glbReader.result );
|
441 |
|
442 | };
|
443 |
|
444 | };
|
445 |
|
446 | } else {
|
447 |
|
448 | if ( json.buffers && json.buffers.length > 0 ) {
|
449 |
|
450 | const reader = new FileReader();
|
451 | reader.readAsDataURL( blob );
|
452 | reader.onloadend = function () {
|
453 |
|
454 | const base64data = reader.result;
|
455 | json.buffers[ 0 ].uri = base64data;
|
456 | onDone( json );
|
457 |
|
458 | };
|
459 |
|
460 | } else {
|
461 |
|
462 | onDone( json );
|
463 |
|
464 | }
|
465 |
|
466 | }
|
467 |
|
468 | }
|
469 |
|
470 | |
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 | serializeUserData( object, objectDef ) {
|
477 |
|
478 | if ( Object.keys( object.userData ).length === 0 ) return;
|
479 | const options = this.options;
|
480 | const extensionsUsed = this.extensionsUsed;
|
481 | try {
|
482 |
|
483 | const json = JSON.parse( JSON.stringify( object.userData ) );
|
484 | if ( options.includeCustomExtensions && json.gltfExtensions ) {
|
485 |
|
486 | if ( objectDef.extensions === undefined ) objectDef.extensions = {};
|
487 | for ( const extensionName in json.gltfExtensions ) {
|
488 |
|
489 | objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ];
|
490 | extensionsUsed[ extensionName ] = true;
|
491 |
|
492 | }
|
493 |
|
494 | delete json.gltfExtensions;
|
495 |
|
496 | }
|
497 |
|
498 | if ( Object.keys( json ).length > 0 ) objectDef.extras = json;
|
499 |
|
500 | } catch ( error ) {
|
501 |
|
502 | console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + 'won\'t be serialized because of JSON.stringify error - ' + error.message );
|
503 |
|
504 | }
|
505 |
|
506 | }
|
507 |
|
508 | |
509 |
|
510 |
|
511 |
|
512 |
|
513 | getUID( attribute, isRelativeCopy = false ) {
|
514 |
|
515 | if ( this.uids.has( attribute ) === false ) {
|
516 |
|
517 | const uids = new Map();
|
518 | uids.set( true, this.uid ++ );
|
519 | uids.set( false, this.uid ++ );
|
520 | this.uids.set( attribute, uids );
|
521 |
|
522 | }
|
523 |
|
524 | const uids = this.uids.get( attribute );
|
525 | return uids.get( isRelativeCopy );
|
526 |
|
527 | }
|
528 |
|
529 | |
530 |
|
531 |
|
532 |
|
533 |
|
534 |
|
535 | isNormalizedNormalAttribute( normal ) {
|
536 |
|
537 | const cache = this.cache;
|
538 | if ( cache.attributesNormalized.has( normal ) ) return false;
|
539 | const v = new THREE.Vector3();
|
540 | for ( let i = 0, il = normal.count; i < il; i ++ ) {
|
541 |
|
542 |
|
543 | if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false;
|
544 |
|
545 | }
|
546 |
|
547 | return true;
|
548 |
|
549 | }
|
550 |
|
551 | |
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 | createNormalizedNormalAttribute( normal ) {
|
559 |
|
560 | const cache = this.cache;
|
561 | if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal );
|
562 | const attribute = normal.clone();
|
563 | const v = new THREE.Vector3();
|
564 | for ( let i = 0, il = attribute.count; i < il; i ++ ) {
|
565 |
|
566 | v.fromBufferAttribute( attribute, i );
|
567 | if ( v.x === 0 && v.y === 0 && v.z === 0 ) {
|
568 |
|
569 |
|
570 | v.setX( 1.0 );
|
571 |
|
572 | } else {
|
573 |
|
574 | v.normalize();
|
575 |
|
576 | }
|
577 |
|
578 | attribute.setXYZ( i, v.x, v.y, v.z );
|
579 |
|
580 | }
|
581 |
|
582 | cache.attributesNormalized.set( normal, attribute );
|
583 | return attribute;
|
584 |
|
585 | }
|
586 |
|
587 | |
588 |
|
589 |
|
590 |
|
591 |
|
592 |
|
593 |
|
594 | applyTextureTransform( mapDef, texture ) {
|
595 |
|
596 | let didTransform = false;
|
597 | const transformDef = {};
|
598 | if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) {
|
599 |
|
600 | transformDef.offset = texture.offset.toArray();
|
601 | didTransform = true;
|
602 |
|
603 | }
|
604 |
|
605 | if ( texture.rotation !== 0 ) {
|
606 |
|
607 | transformDef.rotation = texture.rotation;
|
608 | didTransform = true;
|
609 |
|
610 | }
|
611 |
|
612 | if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) {
|
613 |
|
614 | transformDef.scale = texture.repeat.toArray();
|
615 | didTransform = true;
|
616 |
|
617 | }
|
618 |
|
619 | if ( didTransform ) {
|
620 |
|
621 | mapDef.extensions = mapDef.extensions || {};
|
622 | mapDef.extensions[ 'KHR_texture_transform' ] = transformDef;
|
623 | this.extensionsUsed[ 'KHR_texture_transform' ] = true;
|
624 |
|
625 | }
|
626 |
|
627 | }
|
628 | buildMetalRoughTexture( metalnessMap, roughnessMap ) {
|
629 |
|
630 | if ( metalnessMap === roughnessMap ) return metalnessMap;
|
631 | function getEncodingConversion( map ) {
|
632 |
|
633 | if ( map.encoding === THREE.sRGBEncoding ) {
|
634 |
|
635 | return function SRGBToLinear( c ) {
|
636 |
|
637 | return c < 0.04045 ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
|
638 |
|
639 | };
|
640 |
|
641 | }
|
642 |
|
643 | return function LinearToLinear( c ) {
|
644 |
|
645 | return c;
|
646 |
|
647 | };
|
648 |
|
649 | }
|
650 |
|
651 | console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
|
652 | const metalness = metalnessMap?.image;
|
653 | const roughness = roughnessMap?.image;
|
654 | const width = Math.max( metalness?.width || 0, roughness?.width || 0 );
|
655 | const height = Math.max( metalness?.height || 0, roughness?.height || 0 );
|
656 | const canvas = getCanvas();
|
657 | canvas.width = width;
|
658 | canvas.height = height;
|
659 | const context = canvas.getContext( '2d' );
|
660 | context.fillStyle = '#00ffff';
|
661 | context.fillRect( 0, 0, width, height );
|
662 | const composite = context.getImageData( 0, 0, width, height );
|
663 | if ( metalness ) {
|
664 |
|
665 | context.drawImage( metalness, 0, 0, width, height );
|
666 | const convert = getEncodingConversion( metalnessMap );
|
667 | const data = context.getImageData( 0, 0, width, height ).data;
|
668 | for ( let i = 2; i < data.length; i += 4 ) {
|
669 |
|
670 | composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
|
671 |
|
672 | }
|
673 |
|
674 | }
|
675 |
|
676 | if ( roughness ) {
|
677 |
|
678 | context.drawImage( roughness, 0, 0, width, height );
|
679 | const convert = getEncodingConversion( roughnessMap );
|
680 | const data = context.getImageData( 0, 0, width, height ).data;
|
681 | for ( let i = 1; i < data.length; i += 4 ) {
|
682 |
|
683 | composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
|
684 |
|
685 | }
|
686 |
|
687 | }
|
688 |
|
689 | context.putImageData( composite, 0, 0 );
|
690 |
|
691 |
|
692 |
|
693 | const reference = metalnessMap || roughnessMap;
|
694 | const texture = reference.clone();
|
695 | texture.source = new THREE.Source( canvas );
|
696 | texture.encoding = THREE.LinearEncoding;
|
697 | return texture;
|
698 |
|
699 | }
|
700 |
|
701 | |
702 |
|
703 |
|
704 |
|
705 |
|
706 | processBuffer( buffer ) {
|
707 |
|
708 | const json = this.json;
|
709 | const buffers = this.buffers;
|
710 | if ( ! json.buffers ) json.buffers = [ {
|
711 | byteLength: 0
|
712 | } ];
|
713 |
|
714 |
|
715 | buffers.push( buffer );
|
716 | return 0;
|
717 |
|
718 | }
|
719 |
|
720 | |
721 |
|
722 |
|
723 |
|
724 |
|
725 |
|
726 |
|
727 |
|
728 |
|
729 | processBufferView( attribute, componentType, start, count, target ) {
|
730 |
|
731 | const json = this.json;
|
732 | if ( ! json.bufferViews ) json.bufferViews = [];
|
733 |
|
734 |
|
735 |
|
736 | let componentSize;
|
737 | if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {
|
738 |
|
739 | componentSize = 1;
|
740 |
|
741 | } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
|
742 |
|
743 | componentSize = 2;
|
744 |
|
745 | } else {
|
746 |
|
747 | componentSize = 4;
|
748 |
|
749 | }
|
750 |
|
751 | const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize );
|
752 | const dataView = new DataView( new ArrayBuffer( byteLength ) );
|
753 | let offset = 0;
|
754 | for ( let i = start; i < start + count; i ++ ) {
|
755 |
|
756 | for ( let a = 0; a < attribute.itemSize; a ++ ) {
|
757 |
|
758 | let value;
|
759 | if ( attribute.itemSize > 4 ) {
|
760 |
|
761 |
|
762 |
|
763 | value = attribute.array[ i * attribute.itemSize + a ];
|
764 |
|
765 | } else {
|
766 |
|
767 | if ( a === 0 ) value = attribute.getX( i ); else if ( a === 1 ) value = attribute.getY( i ); else if ( a === 2 ) value = attribute.getZ( i ); else if ( a === 3 ) value = attribute.getW( i );
|
768 |
|
769 | }
|
770 |
|
771 | if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
|
772 |
|
773 | dataView.setFloat32( offset, value, true );
|
774 |
|
775 | } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) {
|
776 |
|
777 | dataView.setUint32( offset, value, true );
|
778 |
|
779 | } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
|
780 |
|
781 | dataView.setUint16( offset, value, true );
|
782 |
|
783 | } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {
|
784 |
|
785 | dataView.setUint8( offset, value );
|
786 |
|
787 | }
|
788 |
|
789 | offset += componentSize;
|
790 |
|
791 | }
|
792 |
|
793 | }
|
794 |
|
795 | const bufferViewDef = {
|
796 | buffer: this.processBuffer( dataView.buffer ),
|
797 | byteOffset: this.byteOffset,
|
798 | byteLength: byteLength
|
799 | };
|
800 | if ( target !== undefined ) bufferViewDef.target = target;
|
801 | if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {
|
802 |
|
803 |
|
804 | bufferViewDef.byteStride = attribute.itemSize * componentSize;
|
805 |
|
806 | }
|
807 |
|
808 | this.byteOffset += byteLength;
|
809 | json.bufferViews.push( bufferViewDef );
|
810 |
|
811 |
|
812 | const output = {
|
813 | id: json.bufferViews.length - 1,
|
814 | byteLength: 0
|
815 | };
|
816 | return output;
|
817 |
|
818 | }
|
819 |
|
820 | |
821 |
|
822 |
|
823 |
|
824 |
|
825 | processBufferViewImage( blob ) {
|
826 |
|
827 | const writer = this;
|
828 | const json = writer.json;
|
829 | if ( ! json.bufferViews ) json.bufferViews = [];
|
830 | return new Promise( function ( resolve ) {
|
831 |
|
832 | const reader = new FileReader();
|
833 | reader.readAsArrayBuffer( blob );
|
834 | reader.onloadend = function () {
|
835 |
|
836 | const buffer = getPaddedArrayBuffer( reader.result );
|
837 | const bufferViewDef = {
|
838 | buffer: writer.processBuffer( buffer ),
|
839 | byteOffset: writer.byteOffset,
|
840 | byteLength: buffer.byteLength
|
841 | };
|
842 | writer.byteOffset += buffer.byteLength;
|
843 | resolve( json.bufferViews.push( bufferViewDef ) - 1 );
|
844 |
|
845 | };
|
846 |
|
847 | } );
|
848 |
|
849 | }
|
850 |
|
851 | |
852 |
|
853 |
|
854 |
|
855 |
|
856 |
|
857 |
|
858 |
|
859 | processAccessor( attribute, geometry, start, count ) {
|
860 |
|
861 | const json = this.json;
|
862 | const types = {
|
863 | 1: 'SCALAR',
|
864 | 2: 'VEC2',
|
865 | 3: 'VEC3',
|
866 | 4: 'VEC4',
|
867 | 16: 'MAT4'
|
868 | };
|
869 | let componentType;
|
870 |
|
871 |
|
872 | if ( attribute.array.constructor === Float32Array ) {
|
873 |
|
874 | componentType = WEBGL_CONSTANTS.FLOAT;
|
875 |
|
876 | } else if ( attribute.array.constructor === Uint32Array ) {
|
877 |
|
878 | componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
|
879 |
|
880 | } else if ( attribute.array.constructor === Uint16Array ) {
|
881 |
|
882 | componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
|
883 |
|
884 | } else if ( attribute.array.constructor === Uint8Array ) {
|
885 |
|
886 | componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
|
887 |
|
888 | } else {
|
889 |
|
890 | throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' );
|
891 |
|
892 | }
|
893 |
|
894 | if ( start === undefined ) start = 0;
|
895 | if ( count === undefined ) count = attribute.count;
|
896 |
|
897 |
|
898 | if ( count === 0 ) return null;
|
899 | const minMax = getMinMax( attribute, start, count );
|
900 | let bufferViewTarget;
|
901 |
|
902 |
|
903 |
|
904 | if ( geometry !== undefined ) {
|
905 |
|
906 | bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
|
907 |
|
908 | }
|
909 |
|
910 | const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget );
|
911 | const accessorDef = {
|
912 | bufferView: bufferView.id,
|
913 | byteOffset: bufferView.byteOffset,
|
914 | componentType: componentType,
|
915 | count: count,
|
916 | max: minMax.max,
|
917 | min: minMax.min,
|
918 | type: types[ attribute.itemSize ]
|
919 | };
|
920 | if ( attribute.normalized === true ) accessorDef.normalized = true;
|
921 | if ( ! json.accessors ) json.accessors = [];
|
922 | return json.accessors.push( accessorDef ) - 1;
|
923 |
|
924 | }
|
925 |
|
926 | |
927 |
|
928 |
|
929 |
|
930 |
|
931 |
|
932 |
|
933 |
|
934 | processImage( image, format, flipY, mimeType = 'image/png' ) {
|
935 |
|
936 | const writer = this;
|
937 | const cache = writer.cache;
|
938 | const json = writer.json;
|
939 | const options = writer.options;
|
940 | const pending = writer.pending;
|
941 | if ( ! cache.images.has( image ) ) cache.images.set( image, {} );
|
942 | const cachedImages = cache.images.get( image );
|
943 | const key = mimeType + ':flipY/' + flipY.toString();
|
944 | if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ];
|
945 | if ( ! json.images ) json.images = [];
|
946 | const imageDef = {
|
947 | mimeType: mimeType
|
948 | };
|
949 | const canvas = getCanvas();
|
950 | canvas.width = Math.min( image.width, options.maxTextureSize );
|
951 | canvas.height = Math.min( image.height, options.maxTextureSize );
|
952 | const ctx = canvas.getContext( '2d' );
|
953 | if ( flipY === true ) {
|
954 |
|
955 | ctx.translate( 0, canvas.height );
|
956 | ctx.scale( 1, - 1 );
|
957 |
|
958 | }
|
959 |
|
960 | if ( image.data !== undefined ) {
|
961 |
|
962 |
|
963 |
|
964 | if ( format !== THREE.RGBAFormat ) {
|
965 |
|
966 | console.error( 'GLTFExporter: Only THREE.RGBAFormat is supported.' );
|
967 |
|
968 | }
|
969 |
|
970 | if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) {
|
971 |
|
972 | console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image );
|
973 |
|
974 | }
|
975 |
|
976 | const data = new Uint8ClampedArray( image.height * image.width * 4 );
|
977 | for ( let i = 0; i < data.length; i += 4 ) {
|
978 |
|
979 | data[ i + 0 ] = image.data[ i + 0 ];
|
980 | data[ i + 1 ] = image.data[ i + 1 ];
|
981 | data[ i + 2 ] = image.data[ i + 2 ];
|
982 | data[ i + 3 ] = image.data[ i + 3 ];
|
983 |
|
984 | }
|
985 |
|
986 | ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 );
|
987 |
|
988 | } else {
|
989 |
|
990 | ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
|
991 |
|
992 | }
|
993 |
|
994 | if ( options.binary === true ) {
|
995 |
|
996 | pending.push( getToBlobPromise( canvas, mimeType ).then( blob => writer.processBufferViewImage( blob ) ).then( bufferViewIndex => {
|
997 |
|
998 | imageDef.bufferView = bufferViewIndex;
|
999 |
|
1000 | } ) );
|
1001 |
|
1002 | } else {
|
1003 |
|
1004 | if ( canvas.toDataURL !== undefined ) {
|
1005 |
|
1006 | imageDef.uri = canvas.toDataURL( mimeType );
|
1007 |
|
1008 | } else {
|
1009 |
|
1010 | pending.push( getToBlobPromise( canvas, mimeType ).then( blob => new FileReader().readAsDataURL( blob ) ).then( dataURL => {
|
1011 |
|
1012 | imageDef.uri = dataURL;
|
1013 |
|
1014 | } ) );
|
1015 |
|
1016 | }
|
1017 |
|
1018 | }
|
1019 |
|
1020 | const index = json.images.push( imageDef ) - 1;
|
1021 | cachedImages[ key ] = index;
|
1022 | return index;
|
1023 |
|
1024 | }
|
1025 |
|
1026 | |
1027 |
|
1028 |
|
1029 |
|
1030 |
|
1031 | processSampler( map ) {
|
1032 |
|
1033 | const json = this.json;
|
1034 | if ( ! json.samplers ) json.samplers = [];
|
1035 | const samplerDef = {
|
1036 | magFilter: THREE_TO_WEBGL[ map.magFilter ],
|
1037 | minFilter: THREE_TO_WEBGL[ map.minFilter ],
|
1038 | wrapS: THREE_TO_WEBGL[ map.wrapS ],
|
1039 | wrapT: THREE_TO_WEBGL[ map.wrapT ]
|
1040 | };
|
1041 | return json.samplers.push( samplerDef ) - 1;
|
1042 |
|
1043 | }
|
1044 |
|
1045 | |
1046 |
|
1047 |
|
1048 |
|
1049 |
|
1050 | processTexture( map ) {
|
1051 |
|
1052 | const cache = this.cache;
|
1053 | const json = this.json;
|
1054 | if ( cache.textures.has( map ) ) return cache.textures.get( map );
|
1055 | if ( ! json.textures ) json.textures = [];
|
1056 | let mimeType = map.userData.mimeType;
|
1057 | if ( mimeType === 'image/webp' ) mimeType = 'image/png';
|
1058 | const textureDef = {
|
1059 | sampler: this.processSampler( map ),
|
1060 | source: this.processImage( map.image, map.format, map.flipY, mimeType )
|
1061 | };
|
1062 | if ( map.name ) textureDef.name = map.name;
|
1063 | this._invokeAll( function ( ext ) {
|
1064 |
|
1065 | ext.writeTexture && ext.writeTexture( map, textureDef );
|
1066 |
|
1067 | } );
|
1068 | const index = json.textures.push( textureDef ) - 1;
|
1069 | cache.textures.set( map, index );
|
1070 | return index;
|
1071 |
|
1072 | }
|
1073 |
|
1074 | |
1075 |
|
1076 |
|
1077 |
|
1078 |
|
1079 | processMaterial( material ) {
|
1080 |
|
1081 | const cache = this.cache;
|
1082 | const json = this.json;
|
1083 | if ( cache.materials.has( material ) ) return cache.materials.get( material );
|
1084 | if ( material.isShaderMaterial ) {
|
1085 |
|
1086 | console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' );
|
1087 | return null;
|
1088 |
|
1089 | }
|
1090 |
|
1091 | if ( ! json.materials ) json.materials = [];
|
1092 |
|
1093 |
|
1094 | const materialDef = {
|
1095 | pbrMetallicRoughness: {}
|
1096 | };
|
1097 | if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) {
|
1098 |
|
1099 | console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' );
|
1100 |
|
1101 | }
|
1102 |
|
1103 |
|
1104 | const color = material.color.toArray().concat( [ material.opacity ] );
|
1105 | if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) {
|
1106 |
|
1107 | materialDef.pbrMetallicRoughness.baseColorFactor = color;
|
1108 |
|
1109 | }
|
1110 |
|
1111 | if ( material.isMeshStandardMaterial ) {
|
1112 |
|
1113 | materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
|
1114 | materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness;
|
1115 |
|
1116 | } else {
|
1117 |
|
1118 | materialDef.pbrMetallicRoughness.metallicFactor = 0.5;
|
1119 | materialDef.pbrMetallicRoughness.roughnessFactor = 0.5;
|
1120 |
|
1121 | }
|
1122 |
|
1123 |
|
1124 | if ( material.metalnessMap || material.roughnessMap ) {
|
1125 |
|
1126 | const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap );
|
1127 | const metalRoughMapDef = {
|
1128 | index: this.processTexture( metalRoughTexture )
|
1129 | };
|
1130 | this.applyTextureTransform( metalRoughMapDef, metalRoughTexture );
|
1131 | materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
|
1132 |
|
1133 | }
|
1134 |
|
1135 |
|
1136 | if ( material.map ) {
|
1137 |
|
1138 | const baseColorMapDef = {
|
1139 | index: this.processTexture( material.map )
|
1140 | };
|
1141 | this.applyTextureTransform( baseColorMapDef, material.map );
|
1142 | materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
|
1143 |
|
1144 | }
|
1145 |
|
1146 | if ( material.emissive ) {
|
1147 |
|
1148 |
|
1149 | const emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity );
|
1150 | const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b );
|
1151 | if ( maxEmissiveComponent > 1 ) {
|
1152 |
|
1153 | emissive.multiplyScalar( 1 / maxEmissiveComponent );
|
1154 | console.warn( 'THREE.GLTFExporter: Some emissive components exceed 1; emissive has been limited' );
|
1155 |
|
1156 | }
|
1157 |
|
1158 | if ( maxEmissiveComponent > 0 ) {
|
1159 |
|
1160 | materialDef.emissiveFactor = emissive.toArray();
|
1161 |
|
1162 | }
|
1163 |
|
1164 |
|
1165 | if ( material.emissiveMap ) {
|
1166 |
|
1167 | const emissiveMapDef = {
|
1168 | index: this.processTexture( material.emissiveMap )
|
1169 | };
|
1170 | this.applyTextureTransform( emissiveMapDef, material.emissiveMap );
|
1171 | materialDef.emissiveTexture = emissiveMapDef;
|
1172 |
|
1173 | }
|
1174 |
|
1175 | }
|
1176 |
|
1177 |
|
1178 | if ( material.normalMap ) {
|
1179 |
|
1180 | const normalMapDef = {
|
1181 | index: this.processTexture( material.normalMap )
|
1182 | };
|
1183 | if ( material.normalScale && material.normalScale.x !== 1 ) {
|
1184 |
|
1185 |
|
1186 |
|
1187 | normalMapDef.scale = material.normalScale.x;
|
1188 |
|
1189 | }
|
1190 |
|
1191 | this.applyTextureTransform( normalMapDef, material.normalMap );
|
1192 | materialDef.normalTexture = normalMapDef;
|
1193 |
|
1194 | }
|
1195 |
|
1196 |
|
1197 | if ( material.aoMap ) {
|
1198 |
|
1199 | const occlusionMapDef = {
|
1200 | index: this.processTexture( material.aoMap ),
|
1201 | texCoord: 1
|
1202 | };
|
1203 | if ( material.aoMapIntensity !== 1.0 ) {
|
1204 |
|
1205 | occlusionMapDef.strength = material.aoMapIntensity;
|
1206 |
|
1207 | }
|
1208 |
|
1209 | this.applyTextureTransform( occlusionMapDef, material.aoMap );
|
1210 | materialDef.occlusionTexture = occlusionMapDef;
|
1211 |
|
1212 | }
|
1213 |
|
1214 |
|
1215 | if ( material.transparent ) {
|
1216 |
|
1217 | materialDef.alphaMode = 'BLEND';
|
1218 |
|
1219 | } else {
|
1220 |
|
1221 | if ( material.alphaTest > 0.0 ) {
|
1222 |
|
1223 | materialDef.alphaMode = 'MASK';
|
1224 | materialDef.alphaCutoff = material.alphaTest;
|
1225 |
|
1226 | }
|
1227 |
|
1228 | }
|
1229 |
|
1230 |
|
1231 | if ( material.side === THREE.DoubleSide ) materialDef.doubleSided = true;
|
1232 | if ( material.name !== '' ) materialDef.name = material.name;
|
1233 | this.serializeUserData( material, materialDef );
|
1234 | this._invokeAll( function ( ext ) {
|
1235 |
|
1236 | ext.writeMaterial && ext.writeMaterial( material, materialDef );
|
1237 |
|
1238 | } );
|
1239 | const index = json.materials.push( materialDef ) - 1;
|
1240 | cache.materials.set( material, index );
|
1241 | return index;
|
1242 |
|
1243 | }
|
1244 |
|
1245 | |
1246 |
|
1247 |
|
1248 |
|
1249 |
|
1250 | processMesh( mesh ) {
|
1251 |
|
1252 | const cache = this.cache;
|
1253 | const json = this.json;
|
1254 | const meshCacheKeyParts = [ mesh.geometry.uuid ];
|
1255 | if ( Array.isArray( mesh.material ) ) {
|
1256 |
|
1257 | for ( let i = 0, l = mesh.material.length; i < l; i ++ ) {
|
1258 |
|
1259 | meshCacheKeyParts.push( mesh.material[ i ].uuid );
|
1260 |
|
1261 | }
|
1262 |
|
1263 | } else {
|
1264 |
|
1265 | meshCacheKeyParts.push( mesh.material.uuid );
|
1266 |
|
1267 | }
|
1268 |
|
1269 | const meshCacheKey = meshCacheKeyParts.join( ':' );
|
1270 | if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey );
|
1271 | const geometry = mesh.geometry;
|
1272 | let mode;
|
1273 |
|
1274 |
|
1275 | if ( mesh.isLineSegments ) {
|
1276 |
|
1277 | mode = WEBGL_CONSTANTS.LINES;
|
1278 |
|
1279 | } else if ( mesh.isLineLoop ) {
|
1280 |
|
1281 | mode = WEBGL_CONSTANTS.LINE_LOOP;
|
1282 |
|
1283 | } else if ( mesh.isLine ) {
|
1284 |
|
1285 | mode = WEBGL_CONSTANTS.LINE_STRIP;
|
1286 |
|
1287 | } else if ( mesh.isPoints ) {
|
1288 |
|
1289 | mode = WEBGL_CONSTANTS.POINTS;
|
1290 |
|
1291 | } else {
|
1292 |
|
1293 | mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
|
1294 |
|
1295 | }
|
1296 |
|
1297 | const meshDef = {};
|
1298 | const attributes = {};
|
1299 | const primitives = [];
|
1300 | const targets = [];
|
1301 |
|
1302 |
|
1303 | const nameConversion = {
|
1304 | uv: 'TEXCOORD_0',
|
1305 | uv2: 'TEXCOORD_1',
|
1306 | color: 'COLOR_0',
|
1307 | skinWeight: 'WEIGHTS_0',
|
1308 | skinIndex: 'JOINTS_0'
|
1309 | };
|
1310 | const originalNormal = geometry.getAttribute( 'normal' );
|
1311 | if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) {
|
1312 |
|
1313 | console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' );
|
1314 | geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) );
|
1315 |
|
1316 | }
|
1317 |
|
1318 |
|
1319 |
|
1320 | let modifiedAttribute = null;
|
1321 | for ( let attributeName in geometry.attributes ) {
|
1322 |
|
1323 |
|
1324 | if ( attributeName.slice( 0, 5 ) === 'morph' ) continue;
|
1325 | const attribute = geometry.attributes[ attributeName ];
|
1326 | attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
|
1327 |
|
1328 |
|
1329 |
|
1330 | const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
|
1331 | if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName;
|
1332 | if ( cache.attributes.has( this.getUID( attribute ) ) ) {
|
1333 |
|
1334 | attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) );
|
1335 | continue;
|
1336 |
|
1337 | }
|
1338 |
|
1339 |
|
1340 | modifiedAttribute = null;
|
1341 | const array = attribute.array;
|
1342 | if ( attributeName === 'JOINTS_0' && ! ( array instanceof Uint16Array ) && ! ( array instanceof Uint8Array ) ) {
|
1343 |
|
1344 | console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
|
1345 | modifiedAttribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
|
1346 |
|
1347 | }
|
1348 |
|
1349 | const accessor = this.processAccessor( modifiedAttribute || attribute, geometry );
|
1350 | if ( accessor !== null ) {
|
1351 |
|
1352 | attributes[ attributeName ] = accessor;
|
1353 | cache.attributes.set( this.getUID( attribute ), accessor );
|
1354 |
|
1355 | }
|
1356 |
|
1357 | }
|
1358 |
|
1359 | if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal );
|
1360 |
|
1361 |
|
1362 | if ( Object.keys( attributes ).length === 0 ) return null;
|
1363 |
|
1364 |
|
1365 | if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {
|
1366 |
|
1367 | const weights = [];
|
1368 | const targetNames = [];
|
1369 | const reverseDictionary = {};
|
1370 | if ( mesh.morphTargetDictionary !== undefined ) {
|
1371 |
|
1372 | for ( const key in mesh.morphTargetDictionary ) {
|
1373 |
|
1374 | reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key;
|
1375 |
|
1376 | }
|
1377 |
|
1378 | }
|
1379 |
|
1380 | for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {
|
1381 |
|
1382 | const target = {};
|
1383 | let warned = false;
|
1384 | for ( const attributeName in geometry.morphAttributes ) {
|
1385 |
|
1386 |
|
1387 |
|
1388 |
|
1389 | if ( attributeName !== 'position' && attributeName !== 'normal' ) {
|
1390 |
|
1391 | if ( ! warned ) {
|
1392 |
|
1393 | console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' );
|
1394 | warned = true;
|
1395 |
|
1396 | }
|
1397 |
|
1398 | continue;
|
1399 |
|
1400 | }
|
1401 |
|
1402 | const attribute = geometry.morphAttributes[ attributeName ][ i ];
|
1403 | const gltfAttributeName = attributeName.toUpperCase();
|
1404 |
|
1405 |
|
1406 |
|
1407 |
|
1408 |
|
1409 |
|
1410 | const baseAttribute = geometry.attributes[ attributeName ];
|
1411 | if ( cache.attributes.has( this.getUID( attribute, true ) ) ) {
|
1412 |
|
1413 | target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) );
|
1414 | continue;
|
1415 |
|
1416 | }
|
1417 |
|
1418 |
|
1419 | const relativeAttribute = attribute.clone();
|
1420 | if ( ! geometry.morphTargetsRelative ) {
|
1421 |
|
1422 | for ( let j = 0, jl = attribute.count; j < jl; j ++ ) {
|
1423 |
|
1424 | relativeAttribute.setXYZ( j, attribute.getX( j ) - baseAttribute.getX( j ), attribute.getY( j ) - baseAttribute.getY( j ), attribute.getZ( j ) - baseAttribute.getZ( j ) );
|
1425 |
|
1426 | }
|
1427 |
|
1428 | }
|
1429 |
|
1430 | target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry );
|
1431 | cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] );
|
1432 |
|
1433 | }
|
1434 |
|
1435 | targets.push( target );
|
1436 | weights.push( mesh.morphTargetInfluences[ i ] );
|
1437 | if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
|
1438 |
|
1439 | }
|
1440 |
|
1441 | meshDef.weights = weights;
|
1442 | if ( targetNames.length > 0 ) {
|
1443 |
|
1444 | meshDef.extras = {};
|
1445 | meshDef.extras.targetNames = targetNames;
|
1446 |
|
1447 | }
|
1448 |
|
1449 | }
|
1450 |
|
1451 | const isMultiMaterial = Array.isArray( mesh.material );
|
1452 | if ( isMultiMaterial && geometry.groups.length === 0 ) return null;
|
1453 | const materials = isMultiMaterial ? mesh.material : [ mesh.material ];
|
1454 | const groups = isMultiMaterial ? geometry.groups : [ {
|
1455 | materialIndex: 0,
|
1456 | start: undefined,
|
1457 | count: undefined
|
1458 | } ];
|
1459 | for ( let i = 0, il = groups.length; i < il; i ++ ) {
|
1460 |
|
1461 | const primitive = {
|
1462 | mode: mode,
|
1463 | attributes: attributes
|
1464 | };
|
1465 | this.serializeUserData( geometry, primitive );
|
1466 | if ( targets.length > 0 ) primitive.targets = targets;
|
1467 | if ( geometry.index !== null ) {
|
1468 |
|
1469 | let cacheKey = this.getUID( geometry.index );
|
1470 | if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) {
|
1471 |
|
1472 | cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count;
|
1473 |
|
1474 | }
|
1475 |
|
1476 | if ( cache.attributes.has( cacheKey ) ) {
|
1477 |
|
1478 | primitive.indices = cache.attributes.get( cacheKey );
|
1479 |
|
1480 | } else {
|
1481 |
|
1482 | primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );
|
1483 | cache.attributes.set( cacheKey, primitive.indices );
|
1484 |
|
1485 | }
|
1486 |
|
1487 | if ( primitive.indices === null ) delete primitive.indices;
|
1488 |
|
1489 | }
|
1490 |
|
1491 | const material = this.processMaterial( materials[ groups[ i ].materialIndex ] );
|
1492 | if ( material !== null ) primitive.material = material;
|
1493 | primitives.push( primitive );
|
1494 |
|
1495 | }
|
1496 |
|
1497 | meshDef.primitives = primitives;
|
1498 | if ( ! json.meshes ) json.meshes = [];
|
1499 | this._invokeAll( function ( ext ) {
|
1500 |
|
1501 | ext.writeMesh && ext.writeMesh( mesh, meshDef );
|
1502 |
|
1503 | } );
|
1504 | const index = json.meshes.push( meshDef ) - 1;
|
1505 | cache.meshes.set( meshCacheKey, index );
|
1506 | return index;
|
1507 |
|
1508 | }
|
1509 |
|
1510 | |
1511 |
|
1512 |
|
1513 |
|
1514 |
|
1515 | processCamera( camera ) {
|
1516 |
|
1517 | const json = this.json;
|
1518 | if ( ! json.cameras ) json.cameras = [];
|
1519 | const isOrtho = camera.isOrthographicCamera;
|
1520 | const cameraDef = {
|
1521 | type: isOrtho ? 'orthographic' : 'perspective'
|
1522 | };
|
1523 | if ( isOrtho ) {
|
1524 |
|
1525 | cameraDef.orthographic = {
|
1526 | xmag: camera.right * 2,
|
1527 | ymag: camera.top * 2,
|
1528 | zfar: camera.far <= 0 ? 0.001 : camera.far,
|
1529 | znear: camera.near < 0 ? 0 : camera.near
|
1530 | };
|
1531 |
|
1532 | } else {
|
1533 |
|
1534 | cameraDef.perspective = {
|
1535 | aspectRatio: camera.aspect,
|
1536 | yfov: THREE.MathUtils.degToRad( camera.fov ),
|
1537 | zfar: camera.far <= 0 ? 0.001 : camera.far,
|
1538 | znear: camera.near < 0 ? 0 : camera.near
|
1539 | };
|
1540 |
|
1541 | }
|
1542 |
|
1543 |
|
1544 | if ( camera.name !== '' ) cameraDef.name = camera.type;
|
1545 | return json.cameras.push( cameraDef ) - 1;
|
1546 |
|
1547 | }
|
1548 |
|
1549 | |
1550 |
|
1551 |
|
1552 |
|
1553 |
|
1554 |
|
1555 |
|
1556 |
|
1557 |
|
1558 |
|
1559 | processAnimation( clip, root ) {
|
1560 |
|
1561 | const json = this.json;
|
1562 | const nodeMap = this.nodeMap;
|
1563 | if ( ! json.animations ) json.animations = [];
|
1564 | clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root );
|
1565 | const tracks = clip.tracks;
|
1566 | const channels = [];
|
1567 | const samplers = [];
|
1568 | for ( let i = 0; i < tracks.length; ++ i ) {
|
1569 |
|
1570 | const track = tracks[ i ];
|
1571 | const trackBinding = THREE.PropertyBinding.parseTrackName( track.name );
|
1572 | let trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
|
1573 | const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];
|
1574 | if ( trackBinding.objectName === 'bones' ) {
|
1575 |
|
1576 | if ( trackNode.isSkinnedMesh === true ) {
|
1577 |
|
1578 | trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex );
|
1579 |
|
1580 | } else {
|
1581 |
|
1582 | trackNode = undefined;
|
1583 |
|
1584 | }
|
1585 |
|
1586 | }
|
1587 |
|
1588 | if ( ! trackNode || ! trackProperty ) {
|
1589 |
|
1590 | console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
|
1591 | return null;
|
1592 |
|
1593 | }
|
1594 |
|
1595 | const inputItemSize = 1;
|
1596 | let outputItemSize = track.values.length / track.times.length;
|
1597 | if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {
|
1598 |
|
1599 | outputItemSize /= trackNode.morphTargetInfluences.length;
|
1600 |
|
1601 | }
|
1602 |
|
1603 | let interpolation;
|
1604 |
|
1605 |
|
1606 |
|
1607 |
|
1608 |
|
1609 |
|
1610 | if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
|
1611 |
|
1612 | interpolation = 'CUBICSPLINE';
|
1613 |
|
1614 |
|
1615 |
|
1616 |
|
1617 | outputItemSize /= 3;
|
1618 |
|
1619 | } else if ( track.getInterpolation() === THREE.InterpolateDiscrete ) {
|
1620 |
|
1621 | interpolation = 'STEP';
|
1622 |
|
1623 | } else {
|
1624 |
|
1625 | interpolation = 'LINEAR';
|
1626 |
|
1627 | }
|
1628 |
|
1629 | samplers.push( {
|
1630 | input: this.processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ),
|
1631 | output: this.processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ),
|
1632 | interpolation: interpolation
|
1633 | } );
|
1634 | channels.push( {
|
1635 | sampler: samplers.length - 1,
|
1636 | target: {
|
1637 | node: nodeMap.get( trackNode ),
|
1638 | path: trackProperty
|
1639 | }
|
1640 | } );
|
1641 |
|
1642 | }
|
1643 |
|
1644 | json.animations.push( {
|
1645 | name: clip.name || 'clip_' + json.animations.length,
|
1646 | samplers: samplers,
|
1647 | channels: channels
|
1648 | } );
|
1649 | return json.animations.length - 1;
|
1650 |
|
1651 | }
|
1652 |
|
1653 | |
1654 |
|
1655 |
|
1656 |
|
1657 | processSkin( object ) {
|
1658 |
|
1659 | const json = this.json;
|
1660 | const nodeMap = this.nodeMap;
|
1661 | const node = json.nodes[ nodeMap.get( object ) ];
|
1662 | const skeleton = object.skeleton;
|
1663 | if ( skeleton === undefined ) return null;
|
1664 | const rootJoint = object.skeleton.bones[ 0 ];
|
1665 | if ( rootJoint === undefined ) return null;
|
1666 | const joints = [];
|
1667 | const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 );
|
1668 | const temporaryBoneInverse = new THREE.Matrix4();
|
1669 | for ( let i = 0; i < skeleton.bones.length; ++ i ) {
|
1670 |
|
1671 | joints.push( nodeMap.get( skeleton.bones[ i ] ) );
|
1672 | temporaryBoneInverse.copy( skeleton.boneInverses[ i ] );
|
1673 | temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 );
|
1674 |
|
1675 | }
|
1676 |
|
1677 | if ( json.skins === undefined ) json.skins = [];
|
1678 | json.skins.push( {
|
1679 | inverseBindMatrices: this.processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ),
|
1680 | joints: joints,
|
1681 | skeleton: nodeMap.get( rootJoint )
|
1682 | } );
|
1683 | const skinIndex = node.skin = json.skins.length - 1;
|
1684 | return skinIndex;
|
1685 |
|
1686 | }
|
1687 |
|
1688 | |
1689 |
|
1690 |
|
1691 |
|
1692 |
|
1693 | processNode( object ) {
|
1694 |
|
1695 | const json = this.json;
|
1696 | const options = this.options;
|
1697 | const nodeMap = this.nodeMap;
|
1698 | if ( ! json.nodes ) json.nodes = [];
|
1699 | const nodeDef = {};
|
1700 | if ( options.trs ) {
|
1701 |
|
1702 | const rotation = object.quaternion.toArray();
|
1703 | const position = object.position.toArray();
|
1704 | const scale = object.scale.toArray();
|
1705 | if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) {
|
1706 |
|
1707 | nodeDef.rotation = rotation;
|
1708 |
|
1709 | }
|
1710 |
|
1711 | if ( ! equalArray( position, [ 0, 0, 0 ] ) ) {
|
1712 |
|
1713 | nodeDef.translation = position;
|
1714 |
|
1715 | }
|
1716 |
|
1717 | if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) {
|
1718 |
|
1719 | nodeDef.scale = scale;
|
1720 |
|
1721 | }
|
1722 |
|
1723 | } else {
|
1724 |
|
1725 | if ( object.matrixAutoUpdate ) {
|
1726 |
|
1727 | object.updateMatrix();
|
1728 |
|
1729 | }
|
1730 |
|
1731 | if ( isIdentityMatrix( object.matrix ) === false ) {
|
1732 |
|
1733 | nodeDef.matrix = object.matrix.elements;
|
1734 |
|
1735 | }
|
1736 |
|
1737 | }
|
1738 |
|
1739 |
|
1740 | if ( object.name !== '' ) nodeDef.name = String( object.name );
|
1741 | this.serializeUserData( object, nodeDef );
|
1742 | if ( object.isMesh || object.isLine || object.isPoints ) {
|
1743 |
|
1744 | const meshIndex = this.processMesh( object );
|
1745 | if ( meshIndex !== null ) nodeDef.mesh = meshIndex;
|
1746 |
|
1747 | } else if ( object.isCamera ) {
|
1748 |
|
1749 | nodeDef.camera = this.processCamera( object );
|
1750 |
|
1751 | }
|
1752 |
|
1753 | if ( object.isSkinnedMesh ) this.skins.push( object );
|
1754 | if ( object.children.length > 0 ) {
|
1755 |
|
1756 | const children = [];
|
1757 | for ( let i = 0, l = object.children.length; i < l; i ++ ) {
|
1758 |
|
1759 | const child = object.children[ i ];
|
1760 | if ( child.visible || options.onlyVisible === false ) {
|
1761 |
|
1762 | const nodeIndex = this.processNode( child );
|
1763 | if ( nodeIndex !== null ) children.push( nodeIndex );
|
1764 |
|
1765 | }
|
1766 |
|
1767 | }
|
1768 |
|
1769 | if ( children.length > 0 ) nodeDef.children = children;
|
1770 |
|
1771 | }
|
1772 |
|
1773 | this._invokeAll( function ( ext ) {
|
1774 |
|
1775 | ext.writeNode && ext.writeNode( object, nodeDef );
|
1776 |
|
1777 | } );
|
1778 | const nodeIndex = json.nodes.push( nodeDef ) - 1;
|
1779 | nodeMap.set( object, nodeIndex );
|
1780 | return nodeIndex;
|
1781 |
|
1782 | }
|
1783 |
|
1784 | |
1785 |
|
1786 |
|
1787 |
|
1788 | processScene( scene ) {
|
1789 |
|
1790 | const json = this.json;
|
1791 | const options = this.options;
|
1792 | if ( ! json.scenes ) {
|
1793 |
|
1794 | json.scenes = [];
|
1795 | json.scene = 0;
|
1796 |
|
1797 | }
|
1798 |
|
1799 | const sceneDef = {};
|
1800 | if ( scene.name !== '' ) sceneDef.name = scene.name;
|
1801 | json.scenes.push( sceneDef );
|
1802 | const nodes = [];
|
1803 | for ( let i = 0, l = scene.children.length; i < l; i ++ ) {
|
1804 |
|
1805 | const child = scene.children[ i ];
|
1806 | if ( child.visible || options.onlyVisible === false ) {
|
1807 |
|
1808 | const nodeIndex = this.processNode( child );
|
1809 | if ( nodeIndex !== null ) nodes.push( nodeIndex );
|
1810 |
|
1811 | }
|
1812 |
|
1813 | }
|
1814 |
|
1815 | if ( nodes.length > 0 ) sceneDef.nodes = nodes;
|
1816 | this.serializeUserData( scene, sceneDef );
|
1817 |
|
1818 | }
|
1819 |
|
1820 | |
1821 |
|
1822 |
|
1823 |
|
1824 | processObjects( objects ) {
|
1825 |
|
1826 | const scene = new THREE.Scene();
|
1827 | scene.name = 'AuxScene';
|
1828 | for ( let i = 0; i < objects.length; i ++ ) {
|
1829 |
|
1830 |
|
1831 |
|
1832 | scene.children.push( objects[ i ] );
|
1833 |
|
1834 | }
|
1835 |
|
1836 | this.processScene( scene );
|
1837 |
|
1838 | }
|
1839 |
|
1840 | |
1841 |
|
1842 |
|
1843 | processInput( input ) {
|
1844 |
|
1845 | const options = this.options;
|
1846 | input = input instanceof Array ? input : [ input ];
|
1847 | this._invokeAll( function ( ext ) {
|
1848 |
|
1849 | ext.beforeParse && ext.beforeParse( input );
|
1850 |
|
1851 | } );
|
1852 | const objectsWithoutScene = [];
|
1853 | for ( let i = 0; i < input.length; i ++ ) {
|
1854 |
|
1855 | if ( input[ i ] instanceof THREE.Scene ) {
|
1856 |
|
1857 | this.processScene( input[ i ] );
|
1858 |
|
1859 | } else {
|
1860 |
|
1861 | objectsWithoutScene.push( input[ i ] );
|
1862 |
|
1863 | }
|
1864 |
|
1865 | }
|
1866 |
|
1867 | if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene );
|
1868 | for ( let i = 0; i < this.skins.length; ++ i ) {
|
1869 |
|
1870 | this.processSkin( this.skins[ i ] );
|
1871 |
|
1872 | }
|
1873 |
|
1874 | for ( let i = 0; i < options.animations.length; ++ i ) {
|
1875 |
|
1876 | this.processAnimation( options.animations[ i ], input[ 0 ] );
|
1877 |
|
1878 | }
|
1879 |
|
1880 | this._invokeAll( function ( ext ) {
|
1881 |
|
1882 | ext.afterParse && ext.afterParse( input );
|
1883 |
|
1884 | } );
|
1885 |
|
1886 | }
|
1887 | _invokeAll( func ) {
|
1888 |
|
1889 | for ( let i = 0, il = this.plugins.length; i < il; i ++ ) {
|
1890 |
|
1891 | func( this.plugins[ i ] );
|
1892 |
|
1893 | }
|
1894 |
|
1895 | }
|
1896 |
|
1897 | }
|
1898 |
|
1899 | |
1900 |
|
1901 |
|
1902 |
|
1903 |
|
1904 | class GLTFLightExtension {
|
1905 |
|
1906 | constructor( writer ) {
|
1907 |
|
1908 | this.writer = writer;
|
1909 | this.name = 'KHR_lights_punctual';
|
1910 |
|
1911 | }
|
1912 | writeNode( light, nodeDef ) {
|
1913 |
|
1914 | if ( ! light.isLight ) return;
|
1915 | if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) {
|
1916 |
|
1917 | console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light );
|
1918 | return;
|
1919 |
|
1920 | }
|
1921 |
|
1922 | const writer = this.writer;
|
1923 | const json = writer.json;
|
1924 | const extensionsUsed = writer.extensionsUsed;
|
1925 | const lightDef = {};
|
1926 | if ( light.name ) lightDef.name = light.name;
|
1927 | lightDef.color = light.color.toArray();
|
1928 | lightDef.intensity = light.intensity;
|
1929 | if ( light.isDirectionalLight ) {
|
1930 |
|
1931 | lightDef.type = 'directional';
|
1932 |
|
1933 | } else if ( light.isPointLight ) {
|
1934 |
|
1935 | lightDef.type = 'point';
|
1936 | if ( light.distance > 0 ) lightDef.range = light.distance;
|
1937 |
|
1938 | } else if ( light.isSpotLight ) {
|
1939 |
|
1940 | lightDef.type = 'spot';
|
1941 | if ( light.distance > 0 ) lightDef.range = light.distance;
|
1942 | lightDef.spot = {};
|
1943 | lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0;
|
1944 | lightDef.spot.outerConeAngle = light.angle;
|
1945 |
|
1946 | }
|
1947 |
|
1948 | if ( light.decay !== undefined && light.decay !== 2 ) {
|
1949 |
|
1950 | console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.' );
|
1951 |
|
1952 | }
|
1953 |
|
1954 | if ( light.target && ( light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== - 1 ) ) {
|
1955 |
|
1956 | 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.' );
|
1957 |
|
1958 | }
|
1959 |
|
1960 | if ( ! extensionsUsed[ this.name ] ) {
|
1961 |
|
1962 | json.extensions = json.extensions || {};
|
1963 | json.extensions[ this.name ] = {
|
1964 | lights: []
|
1965 | };
|
1966 | extensionsUsed[ this.name ] = true;
|
1967 |
|
1968 | }
|
1969 |
|
1970 | const lights = json.extensions[ this.name ].lights;
|
1971 | lights.push( lightDef );
|
1972 | nodeDef.extensions = nodeDef.extensions || {};
|
1973 | nodeDef.extensions[ this.name ] = {
|
1974 | light: lights.length - 1
|
1975 | };
|
1976 |
|
1977 | }
|
1978 |
|
1979 | }
|
1980 |
|
1981 | |
1982 |
|
1983 |
|
1984 |
|
1985 |
|
1986 | class GLTFMaterialsUnlitExtension {
|
1987 |
|
1988 | constructor( writer ) {
|
1989 |
|
1990 | this.writer = writer;
|
1991 | this.name = 'KHR_materials_unlit';
|
1992 |
|
1993 | }
|
1994 | writeMaterial( material, materialDef ) {
|
1995 |
|
1996 | if ( ! material.isMeshBasicMaterial ) return;
|
1997 | const writer = this.writer;
|
1998 | const extensionsUsed = writer.extensionsUsed;
|
1999 | materialDef.extensions = materialDef.extensions || {};
|
2000 | materialDef.extensions[ this.name ] = {};
|
2001 | extensionsUsed[ this.name ] = true;
|
2002 | materialDef.pbrMetallicRoughness.metallicFactor = 0.0;
|
2003 | materialDef.pbrMetallicRoughness.roughnessFactor = 0.9;
|
2004 |
|
2005 | }
|
2006 |
|
2007 | }
|
2008 |
|
2009 | |
2010 |
|
2011 |
|
2012 |
|
2013 |
|
2014 | class GLTFMaterialsClearcoatExtension {
|
2015 |
|
2016 | constructor( writer ) {
|
2017 |
|
2018 | this.writer = writer;
|
2019 | this.name = 'KHR_materials_clearcoat';
|
2020 |
|
2021 | }
|
2022 | writeMaterial( material, materialDef ) {
|
2023 |
|
2024 | if ( ! material.isMeshPhysicalMaterial ) return;
|
2025 | const writer = this.writer;
|
2026 | const extensionsUsed = writer.extensionsUsed;
|
2027 | const extensionDef = {};
|
2028 | extensionDef.clearcoatFactor = material.clearcoat;
|
2029 | if ( material.clearcoatMap ) {
|
2030 |
|
2031 | const clearcoatMapDef = {
|
2032 | index: writer.processTexture( material.clearcoatMap )
|
2033 | };
|
2034 | writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap );
|
2035 | extensionDef.clearcoatTexture = clearcoatMapDef;
|
2036 |
|
2037 | }
|
2038 |
|
2039 | extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness;
|
2040 | if ( material.clearcoatRoughnessMap ) {
|
2041 |
|
2042 | const clearcoatRoughnessMapDef = {
|
2043 | index: writer.processTexture( material.clearcoatRoughnessMap )
|
2044 | };
|
2045 | writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap );
|
2046 | extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef;
|
2047 |
|
2048 | }
|
2049 |
|
2050 | if ( material.clearcoatNormalMap ) {
|
2051 |
|
2052 | const clearcoatNormalMapDef = {
|
2053 | index: writer.processTexture( material.clearcoatNormalMap )
|
2054 | };
|
2055 | writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap );
|
2056 | extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef;
|
2057 |
|
2058 | }
|
2059 |
|
2060 | materialDef.extensions = materialDef.extensions || {};
|
2061 | materialDef.extensions[ this.name ] = extensionDef;
|
2062 | extensionsUsed[ this.name ] = true;
|
2063 |
|
2064 | }
|
2065 |
|
2066 | }
|
2067 |
|
2068 | |
2069 |
|
2070 |
|
2071 |
|
2072 |
|
2073 | class GLTFMaterialsIridescenceExtension {
|
2074 |
|
2075 | constructor( writer ) {
|
2076 |
|
2077 | this.writer = writer;
|
2078 | this.name = 'KHR_materials_iridescence';
|
2079 |
|
2080 | }
|
2081 | writeMaterial( material, materialDef ) {
|
2082 |
|
2083 | if ( ! material.isMeshPhysicalMaterial ) return;
|
2084 | const writer = this.writer;
|
2085 | const extensionsUsed = writer.extensionsUsed;
|
2086 | const extensionDef = {};
|
2087 | extensionDef.iridescenceFactor = material.iridescence;
|
2088 | if ( material.iridescenceMap ) {
|
2089 |
|
2090 | const iridescenceMapDef = {
|
2091 | index: writer.processTexture( material.iridescenceMap )
|
2092 | };
|
2093 | writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap );
|
2094 | extensionDef.iridescenceTexture = iridescenceMapDef;
|
2095 |
|
2096 | }
|
2097 |
|
2098 | extensionDef.iridescenceIor = material.iridescenceIOR;
|
2099 | extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ];
|
2100 | extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ];
|
2101 | if ( material.iridescenceThicknessMap ) {
|
2102 |
|
2103 | const iridescenceThicknessMapDef = {
|
2104 | index: writer.processTexture( material.iridescenceThicknessMap )
|
2105 | };
|
2106 | writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap );
|
2107 | extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef;
|
2108 |
|
2109 | }
|
2110 |
|
2111 | materialDef.extensions = materialDef.extensions || {};
|
2112 | materialDef.extensions[ this.name ] = extensionDef;
|
2113 | extensionsUsed[ this.name ] = true;
|
2114 |
|
2115 | }
|
2116 |
|
2117 | }
|
2118 |
|
2119 | |
2120 |
|
2121 |
|
2122 |
|
2123 |
|
2124 | class GLTFMaterialsTransmissionExtension {
|
2125 |
|
2126 | constructor( writer ) {
|
2127 |
|
2128 | this.writer = writer;
|
2129 | this.name = 'KHR_materials_transmission';
|
2130 |
|
2131 | }
|
2132 | writeMaterial( material, materialDef ) {
|
2133 |
|
2134 | if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return;
|
2135 | const writer = this.writer;
|
2136 | const extensionsUsed = writer.extensionsUsed;
|
2137 | const extensionDef = {};
|
2138 | extensionDef.transmissionFactor = material.transmission;
|
2139 | if ( material.transmissionMap ) {
|
2140 |
|
2141 | const transmissionMapDef = {
|
2142 | index: writer.processTexture( material.transmissionMap )
|
2143 | };
|
2144 | writer.applyTextureTransform( transmissionMapDef, material.transmissionMap );
|
2145 | extensionDef.transmissionTexture = transmissionMapDef;
|
2146 |
|
2147 | }
|
2148 |
|
2149 | materialDef.extensions = materialDef.extensions || {};
|
2150 | materialDef.extensions[ this.name ] = extensionDef;
|
2151 | extensionsUsed[ this.name ] = true;
|
2152 |
|
2153 | }
|
2154 |
|
2155 | }
|
2156 |
|
2157 | |
2158 |
|
2159 |
|
2160 |
|
2161 |
|
2162 | class GLTFMaterialsVolumeExtension {
|
2163 |
|
2164 | constructor( writer ) {
|
2165 |
|
2166 | this.writer = writer;
|
2167 | this.name = 'KHR_materials_volume';
|
2168 |
|
2169 | }
|
2170 | writeMaterial( material, materialDef ) {
|
2171 |
|
2172 | if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return;
|
2173 | const writer = this.writer;
|
2174 | const extensionsUsed = writer.extensionsUsed;
|
2175 | const extensionDef = {};
|
2176 | extensionDef.thicknessFactor = material.thickness;
|
2177 | if ( material.thicknessMap ) {
|
2178 |
|
2179 | const thicknessMapDef = {
|
2180 | index: writer.processTexture( material.thicknessMap )
|
2181 | };
|
2182 | writer.applyTextureTransform( thicknessMapDef, material.thicknessMap );
|
2183 | extensionDef.thicknessTexture = thicknessMapDef;
|
2184 |
|
2185 | }
|
2186 |
|
2187 | extensionDef.attenuationDistance = material.attenuationDistance;
|
2188 | extensionDef.attenuationColor = material.attenuationColor.toArray();
|
2189 | materialDef.extensions = materialDef.extensions || {};
|
2190 | materialDef.extensions[ this.name ] = extensionDef;
|
2191 | extensionsUsed[ this.name ] = true;
|
2192 |
|
2193 | }
|
2194 |
|
2195 | }
|
2196 |
|
2197 | |
2198 |
|
2199 |
|
2200 | GLTFExporter.Utils = {
|
2201 | insertKeyframe: function ( track, time ) {
|
2202 |
|
2203 | const tolerance = 0.001;
|
2204 | const valueSize = track.getValueSize();
|
2205 | const times = new track.TimeBufferType( track.times.length + 1 );
|
2206 | const values = new track.ValueBufferType( track.values.length + valueSize );
|
2207 | const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );
|
2208 | let index;
|
2209 | if ( track.times.length === 0 ) {
|
2210 |
|
2211 | times[ 0 ] = time;
|
2212 | for ( let i = 0; i < valueSize; i ++ ) {
|
2213 |
|
2214 | values[ i ] = 0;
|
2215 |
|
2216 | }
|
2217 |
|
2218 | index = 0;
|
2219 |
|
2220 | } else if ( time < track.times[ 0 ] ) {
|
2221 |
|
2222 | if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;
|
2223 | times[ 0 ] = time;
|
2224 | times.set( track.times, 1 );
|
2225 | values.set( interpolant.evaluate( time ), 0 );
|
2226 | values.set( track.values, valueSize );
|
2227 | index = 0;
|
2228 |
|
2229 | } else if ( time > track.times[ track.times.length - 1 ] ) {
|
2230 |
|
2231 | if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {
|
2232 |
|
2233 | return track.times.length - 1;
|
2234 |
|
2235 | }
|
2236 |
|
2237 | times[ times.length - 1 ] = time;
|
2238 | times.set( track.times, 0 );
|
2239 | values.set( track.values, 0 );
|
2240 | values.set( interpolant.evaluate( time ), track.values.length );
|
2241 | index = times.length - 1;
|
2242 |
|
2243 | } else {
|
2244 |
|
2245 | for ( let i = 0; i < track.times.length; i ++ ) {
|
2246 |
|
2247 | if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;
|
2248 | if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {
|
2249 |
|
2250 | times.set( track.times.slice( 0, i + 1 ), 0 );
|
2251 | times[ i + 1 ] = time;
|
2252 | times.set( track.times.slice( i + 1 ), i + 2 );
|
2253 | values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
|
2254 | values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
|
2255 | values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );
|
2256 | index = i + 1;
|
2257 | break;
|
2258 |
|
2259 | }
|
2260 |
|
2261 | }
|
2262 |
|
2263 | }
|
2264 |
|
2265 | track.times = times;
|
2266 | track.values = values;
|
2267 | return index;
|
2268 |
|
2269 | },
|
2270 | mergeMorphTargetTracks: function ( clip, root ) {
|
2271 |
|
2272 | const tracks = [];
|
2273 | const mergedTracks = {};
|
2274 | const sourceTracks = clip.tracks;
|
2275 | for ( let i = 0; i < sourceTracks.length; ++ i ) {
|
2276 |
|
2277 | let sourceTrack = sourceTracks[ i ];
|
2278 | const sourceTrackBinding = THREE.PropertyBinding.parseTrackName( sourceTrack.name );
|
2279 | const sourceTrackNode = THREE.PropertyBinding.findNode( root, sourceTrackBinding.nodeName );
|
2280 | if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) {
|
2281 |
|
2282 |
|
2283 | tracks.push( sourceTrack );
|
2284 | continue;
|
2285 |
|
2286 | }
|
2287 |
|
2288 | if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {
|
2289 |
|
2290 | if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
|
2291 |
|
2292 |
|
2293 |
|
2294 | throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );
|
2295 |
|
2296 | }
|
2297 |
|
2298 | console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' );
|
2299 | sourceTrack = sourceTrack.clone();
|
2300 | sourceTrack.setInterpolation( THREE.InterpolateLinear );
|
2301 |
|
2302 | }
|
2303 |
|
2304 | const targetCount = sourceTrackNode.morphTargetInfluences.length;
|
2305 | const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];
|
2306 | if ( targetIndex === undefined ) {
|
2307 |
|
2308 | throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex );
|
2309 |
|
2310 | }
|
2311 |
|
2312 | let mergedTrack;
|
2313 |
|
2314 |
|
2315 |
|
2316 | if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {
|
2317 |
|
2318 | mergedTrack = sourceTrack.clone();
|
2319 | const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );
|
2320 | for ( let j = 0; j < mergedTrack.times.length; j ++ ) {
|
2321 |
|
2322 | values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];
|
2323 |
|
2324 | }
|
2325 |
|
2326 |
|
2327 |
|
2328 | mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences';
|
2329 | mergedTrack.values = values;
|
2330 | mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
|
2331 | tracks.push( mergedTrack );
|
2332 | continue;
|
2333 |
|
2334 | }
|
2335 |
|
2336 | const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );
|
2337 | mergedTrack = mergedTracks[ sourceTrackNode.uuid ];
|
2338 |
|
2339 |
|
2340 |
|
2341 | for ( let j = 0; j < mergedTrack.times.length; j ++ ) {
|
2342 |
|
2343 | mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );
|
2344 |
|
2345 | }
|
2346 |
|
2347 |
|
2348 |
|
2349 |
|
2350 | for ( let j = 0; j < sourceTrack.times.length; j ++ ) {
|
2351 |
|
2352 | const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
|
2353 | mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];
|
2354 |
|
2355 | }
|
2356 |
|
2357 | }
|
2358 |
|
2359 | clip.tracks = tracks;
|
2360 | return clip;
|
2361 |
|
2362 | }
|
2363 | };
|
2364 |
|
2365 | THREE.GLTFExporter = GLTFExporter;
|
2366 |
|
2367 | } )();
|