├── README.md
├── index.html
├── models
└── pistol.glb
├── vendor
├── GLTFLoader.js
├── OrbitControls.js
└── three.min.js
├── viewer.css
└── viewer.js
/README.md:
--------------------------------------------------------------------------------
1 | # GLB-Viewer
2 | 3D Viewer Web application for viewing glb files inspired by Autodesk Forge Viewer
3 |
4 | ## Features
5 | 1. Importing glb files
6 | 2. Measure distance b/w two points
7 | 3. Explode faces or separate objects of the model
8 | 4. Fully responsive with mobile support
9 | 5. Standard 3D views with orientation cube
10 |
11 | ## Demo
12 | https://glb-viewer.blogspot.com/
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Viewer3D
4 |
5 |
6 |
7 |
8 |
19 |
20 |
21 |
22 |
45 |
46 |
47 |
56 |
75 |
79 |
82 |
83 |
Loading...
84 |
85 |
86 |
87 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
127 |
128 |
--------------------------------------------------------------------------------
/models/pistol.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytezeroseven/GLB-Viewer/c77af4667b8f1abcbadf1293dfa968508725e3cd/models/pistol.glb
--------------------------------------------------------------------------------
/vendor/GLTFLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Rich Tibbett / https://github.com/richtr
3 | * @author mrdoob / http://mrdoob.com/
4 | * @author Tony Parisi / http://www.tonyparisi.com/
5 | * @author Takahiro / https://github.com/takahirox
6 | * @author Don McCurdy / https://www.donmccurdy.com
7 | */
8 |
9 | THREE.GLTFLoader = ( function () {
10 |
11 | function GLTFLoader( manager ) {
12 |
13 | THREE.Loader.call( this, manager );
14 |
15 | this.dracoLoader = null;
16 | this.ddsLoader = null;
17 |
18 | }
19 |
20 | GLTFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
21 |
22 | constructor: GLTFLoader,
23 |
24 | load: function ( url, onLoad, onProgress, onError ) {
25 |
26 | var scope = this;
27 |
28 | var resourcePath;
29 |
30 | if ( this.resourcePath !== '' ) {
31 |
32 | resourcePath = this.resourcePath;
33 |
34 | } else if ( this.path !== '' ) {
35 |
36 | resourcePath = this.path;
37 |
38 | } else {
39 |
40 | resourcePath = THREE.LoaderUtils.extractUrlBase( url );
41 |
42 | }
43 |
44 | // Tells the LoadingManager to track an extra item, which resolves after
45 | // the model is fully loaded. This means the count of items loaded will
46 | // be incorrect, but ensures manager.onLoad() does not fire early.
47 | scope.manager.itemStart( url );
48 |
49 | var _onError = function ( e ) {
50 |
51 | if ( onError ) {
52 |
53 | onError( e );
54 |
55 | } else {
56 |
57 | console.error( e );
58 |
59 | }
60 |
61 | scope.manager.itemError( url );
62 | scope.manager.itemEnd( url );
63 |
64 | };
65 |
66 | var loader = new THREE.FileLoader( scope.manager );
67 |
68 | loader.setPath( this.path );
69 | loader.setResponseType( 'arraybuffer' );
70 |
71 | if ( scope.crossOrigin === 'use-credentials' ) {
72 |
73 | loader.setWithCredentials( true );
74 |
75 | }
76 |
77 | loader.load( url, function ( data ) {
78 |
79 | try {
80 |
81 | scope.parse( data, resourcePath, function ( gltf ) {
82 |
83 | onLoad( gltf );
84 |
85 | scope.manager.itemEnd( url );
86 |
87 | }, _onError );
88 |
89 | } catch ( e ) {
90 |
91 | _onError( e );
92 |
93 | }
94 |
95 | }, onProgress, _onError );
96 |
97 | },
98 |
99 | setDRACOLoader: function ( dracoLoader ) {
100 |
101 | this.dracoLoader = dracoLoader;
102 | return this;
103 |
104 | },
105 |
106 | setDDSLoader: function ( ddsLoader ) {
107 |
108 | this.ddsLoader = ddsLoader;
109 | return this;
110 |
111 | },
112 |
113 | parse: function ( data, path, onLoad, onError ) {
114 |
115 | var content;
116 | var extensions = {};
117 |
118 | if ( typeof data === 'string' ) {
119 |
120 | content = data;
121 |
122 | } else {
123 |
124 | var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );
125 |
126 | if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
127 |
128 | try {
129 |
130 | extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
131 |
132 | } catch ( error ) {
133 |
134 | if ( onError ) onError( error );
135 | return;
136 |
137 | }
138 |
139 | content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
140 |
141 | } else {
142 |
143 | content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
144 |
145 | }
146 |
147 | }
148 |
149 | var json = JSON.parse( content );
150 |
151 | if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
152 |
153 | if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
154 | return;
155 |
156 | }
157 |
158 | if ( json.extensionsUsed ) {
159 |
160 | for ( var i = 0; i < json.extensionsUsed.length; ++ i ) {
161 |
162 | var extensionName = json.extensionsUsed[ i ];
163 | var extensionsRequired = json.extensionsRequired || [];
164 |
165 | switch ( extensionName ) {
166 |
167 | case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
168 | extensions[ extensionName ] = new GLTFLightsExtension( json );
169 | break;
170 |
171 | case EXTENSIONS.KHR_MATERIALS_UNLIT:
172 | extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
173 | break;
174 |
175 | case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
176 | extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
177 | break;
178 |
179 | case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
180 | extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
181 | break;
182 |
183 | case EXTENSIONS.MSFT_TEXTURE_DDS:
184 | extensions[ extensionName ] = new GLTFTextureDDSExtension( this.ddsLoader );
185 | break;
186 |
187 | case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
188 | extensions[ extensionName ] = new GLTFTextureTransformExtension();
189 | break;
190 |
191 | case EXTENSIONS.KHR_MESH_QUANTIZATION:
192 | extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
193 | break;
194 |
195 | default:
196 |
197 | if ( extensionsRequired.indexOf( extensionName ) >= 0 ) {
198 |
199 | console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );
200 |
201 | }
202 |
203 | }
204 |
205 | }
206 |
207 | }
208 |
209 | var parser = new GLTFParser( json, extensions, {
210 |
211 | path: path || this.resourcePath || '',
212 | crossOrigin: this.crossOrigin,
213 | manager: this.manager
214 |
215 | } );
216 |
217 | parser.parse( onLoad, onError );
218 |
219 | }
220 |
221 | } );
222 |
223 | /* GLTFREGISTRY */
224 |
225 | function GLTFRegistry() {
226 |
227 | var objects = {};
228 |
229 | return {
230 |
231 | get: function ( key ) {
232 |
233 | return objects[ key ];
234 |
235 | },
236 |
237 | add: function ( key, object ) {
238 |
239 | objects[ key ] = object;
240 |
241 | },
242 |
243 | remove: function ( key ) {
244 |
245 | delete objects[ key ];
246 |
247 | },
248 |
249 | removeAll: function () {
250 |
251 | objects = {};
252 |
253 | }
254 |
255 | };
256 |
257 | }
258 |
259 | /*********************************/
260 | /********** EXTENSIONS ***********/
261 | /*********************************/
262 |
263 | var EXTENSIONS = {
264 | KHR_BINARY_GLTF: 'KHR_binary_glTF',
265 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
266 | KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
267 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
268 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
269 | KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
270 | KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
271 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
272 | };
273 |
274 | /**
275 | * DDS Texture Extension
276 | *
277 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds
278 | *
279 | */
280 | function GLTFTextureDDSExtension( ddsLoader ) {
281 |
282 | if ( ! ddsLoader ) {
283 |
284 | throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' );
285 |
286 | }
287 |
288 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS;
289 | this.ddsLoader = ddsLoader;
290 |
291 | }
292 |
293 | /**
294 | * Punctual Lights Extension
295 | *
296 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
297 | */
298 | function GLTFLightsExtension( json ) {
299 |
300 | this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
301 |
302 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
303 | this.lightDefs = extension.lights || [];
304 |
305 | }
306 |
307 | GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
308 |
309 | var lightDef = this.lightDefs[ lightIndex ];
310 | var lightNode;
311 |
312 | var color = new THREE.Color( 0xffffff );
313 | if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
314 |
315 | var range = lightDef.range !== undefined ? lightDef.range : 0;
316 |
317 | switch ( lightDef.type ) {
318 |
319 | case 'directional':
320 | lightNode = new THREE.DirectionalLight( color );
321 | lightNode.target.position.set( 0, 0, - 1 );
322 | lightNode.add( lightNode.target );
323 | break;
324 |
325 | case 'point':
326 | lightNode = new THREE.PointLight( color );
327 | lightNode.distance = range;
328 | break;
329 |
330 | case 'spot':
331 | lightNode = new THREE.SpotLight( color );
332 | lightNode.distance = range;
333 | // Handle spotlight properties.
334 | lightDef.spot = lightDef.spot || {};
335 | lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
336 | lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
337 | lightNode.angle = lightDef.spot.outerConeAngle;
338 | lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
339 | lightNode.target.position.set( 0, 0, - 1 );
340 | lightNode.add( lightNode.target );
341 | break;
342 |
343 | default:
344 | throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' );
345 |
346 | }
347 |
348 | // Some lights (e.g. spot) default to a position other than the origin. Reset the position
349 | // here, because node-level parsing will only override position if explicitly specified.
350 | lightNode.position.set( 0, 0, 0 );
351 |
352 | lightNode.decay = 2;
353 |
354 | if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
355 |
356 | lightNode.name = lightDef.name || ( 'light_' + lightIndex );
357 |
358 | return Promise.resolve( lightNode );
359 |
360 | };
361 |
362 | /**
363 | * Unlit Materials Extension
364 | *
365 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
366 | */
367 | function GLTFMaterialsUnlitExtension() {
368 |
369 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
370 |
371 | }
372 |
373 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () {
374 |
375 | return THREE.MeshBasicMaterial;
376 |
377 | };
378 |
379 | GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) {
380 |
381 | var pending = [];
382 |
383 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
384 | materialParams.opacity = 1.0;
385 |
386 | var metallicRoughness = materialDef.pbrMetallicRoughness;
387 |
388 | if ( metallicRoughness ) {
389 |
390 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
391 |
392 | var array = metallicRoughness.baseColorFactor;
393 |
394 | materialParams.color.fromArray( array );
395 | materialParams.opacity = array[ 3 ];
396 |
397 | }
398 |
399 | if ( metallicRoughness.baseColorTexture !== undefined ) {
400 |
401 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
402 |
403 | }
404 |
405 | }
406 |
407 | return Promise.all( pending );
408 |
409 | };
410 |
411 | /* BINARY EXTENSION */
412 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
413 | var BINARY_EXTENSION_HEADER_LENGTH = 12;
414 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
415 |
416 | function GLTFBinaryExtension( data ) {
417 |
418 | this.name = EXTENSIONS.KHR_BINARY_GLTF;
419 | this.content = null;
420 | this.body = null;
421 |
422 | var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
423 |
424 | this.header = {
425 | magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ),
426 | version: headerView.getUint32( 4, true ),
427 | length: headerView.getUint32( 8, true )
428 | };
429 |
430 | if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
431 |
432 | throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );
433 |
434 | } else if ( this.header.version < 2.0 ) {
435 |
436 | throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' );
437 |
438 | }
439 |
440 | var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
441 | var chunkIndex = 0;
442 |
443 | while ( chunkIndex < chunkView.byteLength ) {
444 |
445 | var chunkLength = chunkView.getUint32( chunkIndex, true );
446 | chunkIndex += 4;
447 |
448 | var chunkType = chunkView.getUint32( chunkIndex, true );
449 | chunkIndex += 4;
450 |
451 | if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
452 |
453 | var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
454 | this.content = THREE.LoaderUtils.decodeText( contentArray );
455 |
456 | } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
457 |
458 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
459 | this.body = data.slice( byteOffset, byteOffset + chunkLength );
460 |
461 | }
462 |
463 | // Clients must ignore chunks with unknown types.
464 |
465 | chunkIndex += chunkLength;
466 |
467 | }
468 |
469 | if ( this.content === null ) {
470 |
471 | throw new Error( 'THREE.GLTFLoader: JSON content not found.' );
472 |
473 | }
474 |
475 | }
476 |
477 | /**
478 | * DRACO Mesh Compression Extension
479 | *
480 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
481 | */
482 | function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
483 |
484 | if ( ! dracoLoader ) {
485 |
486 | throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
487 |
488 | }
489 |
490 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
491 | this.json = json;
492 | this.dracoLoader = dracoLoader;
493 | this.dracoLoader.preload();
494 |
495 | }
496 |
497 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) {
498 |
499 | var json = this.json;
500 | var dracoLoader = this.dracoLoader;
501 | var bufferViewIndex = primitive.extensions[ this.name ].bufferView;
502 | var gltfAttributeMap = primitive.extensions[ this.name ].attributes;
503 | var threeAttributeMap = {};
504 | var attributeNormalizedMap = {};
505 | var attributeTypeMap = {};
506 |
507 | for ( var attributeName in gltfAttributeMap ) {
508 |
509 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
510 |
511 | threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
512 |
513 | }
514 |
515 | for ( attributeName in primitive.attributes ) {
516 |
517 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
518 |
519 | if ( gltfAttributeMap[ attributeName ] !== undefined ) {
520 |
521 | var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
522 | var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
523 |
524 | attributeTypeMap[ threeAttributeName ] = componentType;
525 | attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
526 |
527 | }
528 |
529 | }
530 |
531 | return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
532 |
533 | return new Promise( function ( resolve ) {
534 |
535 | dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
536 |
537 | for ( var attributeName in geometry.attributes ) {
538 |
539 | var attribute = geometry.attributes[ attributeName ];
540 | var normalized = attributeNormalizedMap[ attributeName ];
541 |
542 | if ( normalized !== undefined ) attribute.normalized = normalized;
543 |
544 | }
545 |
546 | resolve( geometry );
547 |
548 | }, threeAttributeMap, attributeTypeMap );
549 |
550 | } );
551 |
552 | } );
553 |
554 | };
555 |
556 | /**
557 | * Texture Transform Extension
558 | *
559 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
560 | */
561 | function GLTFTextureTransformExtension() {
562 |
563 | this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
564 |
565 | }
566 |
567 | GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) {
568 |
569 | texture = texture.clone();
570 |
571 | if ( transform.offset !== undefined ) {
572 |
573 | texture.offset.fromArray( transform.offset );
574 |
575 | }
576 |
577 | if ( transform.rotation !== undefined ) {
578 |
579 | texture.rotation = transform.rotation;
580 |
581 | }
582 |
583 | if ( transform.scale !== undefined ) {
584 |
585 | texture.repeat.fromArray( transform.scale );
586 |
587 | }
588 |
589 | if ( transform.texCoord !== undefined ) {
590 |
591 | console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );
592 |
593 | }
594 |
595 | texture.needsUpdate = true;
596 |
597 | return texture;
598 |
599 | };
600 |
601 | /**
602 | * Specular-Glossiness Extension
603 | *
604 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
605 | */
606 |
607 | /**
608 | * A sub class of THREE.StandardMaterial with some of the functionality
609 | * changed via the `onBeforeCompile` callback
610 | * @pailhead
611 | */
612 |
613 | function GLTFMeshStandardSGMaterial( params ) {
614 |
615 | THREE.MeshStandardMaterial.call( this );
616 |
617 | this.isGLTFSpecularGlossinessMaterial = true;
618 |
619 | //various chunks that need replacing
620 | var specularMapParsFragmentChunk = [
621 | '#ifdef USE_SPECULARMAP',
622 | ' uniform sampler2D specularMap;',
623 | '#endif'
624 | ].join( '\n' );
625 |
626 | var glossinessMapParsFragmentChunk = [
627 | '#ifdef USE_GLOSSINESSMAP',
628 | ' uniform sampler2D glossinessMap;',
629 | '#endif'
630 | ].join( '\n' );
631 |
632 | var specularMapFragmentChunk = [
633 | 'vec3 specularFactor = specular;',
634 | '#ifdef USE_SPECULARMAP',
635 | ' vec4 texelSpecular = texture2D( specularMap, vUv );',
636 | ' texelSpecular = sRGBToLinear( texelSpecular );',
637 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
638 | ' specularFactor *= texelSpecular.rgb;',
639 | '#endif'
640 | ].join( '\n' );
641 |
642 | var glossinessMapFragmentChunk = [
643 | 'float glossinessFactor = glossiness;',
644 | '#ifdef USE_GLOSSINESSMAP',
645 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );',
646 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture',
647 | ' glossinessFactor *= texelGlossiness.a;',
648 | '#endif'
649 | ].join( '\n' );
650 |
651 | var lightPhysicalFragmentChunk = [
652 | 'PhysicalMaterial material;',
653 | 'material.diffuseColor = diffuseColor.rgb;',
654 | 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );',
655 | 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );',
656 | 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 );// 0.0525 corresponds to the base mip of a 256 cubemap.',
657 | 'material.specularRoughness += geometryRoughness;',
658 | 'material.specularRoughness = min( material.specularRoughness, 1.0 );',
659 | 'material.specularColor = specularFactor.rgb;',
660 | ].join( '\n' );
661 |
662 | var uniforms = {
663 | specular: { value: new THREE.Color().setHex( 0xffffff ) },
664 | glossiness: { value: 1 },
665 | specularMap: { value: null },
666 | glossinessMap: { value: null }
667 | };
668 |
669 | this._extraUniforms = uniforms;
670 |
671 | // please see #14031 or #13198 for an alternate approach
672 | this.onBeforeCompile = function ( shader ) {
673 |
674 | for ( var uniformName in uniforms ) {
675 |
676 | shader.uniforms[ uniformName ] = uniforms[ uniformName ];
677 |
678 | }
679 |
680 | shader.fragmentShader = shader.fragmentShader.replace( 'uniform float roughness;', 'uniform vec3 specular;' );
681 | shader.fragmentShader = shader.fragmentShader.replace( 'uniform float metalness;', 'uniform float glossiness;' );
682 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapParsFragmentChunk );
683 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapParsFragmentChunk );
684 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapFragmentChunk );
685 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapFragmentChunk );
686 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', lightPhysicalFragmentChunk );
687 |
688 | };
689 |
690 | /*eslint-disable*/
691 | Object.defineProperties(
692 | this,
693 | {
694 | specular: {
695 | get: function () { return uniforms.specular.value; },
696 | set: function ( v ) { uniforms.specular.value = v; }
697 | },
698 | specularMap: {
699 | get: function () { return uniforms.specularMap.value; },
700 | set: function ( v ) { uniforms.specularMap.value = v; }
701 | },
702 | glossiness: {
703 | get: function () { return uniforms.glossiness.value; },
704 | set: function ( v ) { uniforms.glossiness.value = v; }
705 | },
706 | glossinessMap: {
707 | get: function () { return uniforms.glossinessMap.value; },
708 | set: function ( v ) {
709 |
710 | uniforms.glossinessMap.value = v;
711 | //how about something like this - @pailhead
712 | if ( v ) {
713 |
714 | this.defines.USE_GLOSSINESSMAP = '';
715 | // set USE_ROUGHNESSMAP to enable vUv
716 | this.defines.USE_ROUGHNESSMAP = '';
717 |
718 | } else {
719 |
720 | delete this.defines.USE_ROUGHNESSMAP;
721 | delete this.defines.USE_GLOSSINESSMAP;
722 |
723 | }
724 |
725 | }
726 | }
727 | }
728 | );
729 |
730 | /*eslint-enable*/
731 | delete this.metalness;
732 | delete this.roughness;
733 | delete this.metalnessMap;
734 | delete this.roughnessMap;
735 |
736 | this.setValues( params );
737 |
738 | }
739 |
740 | GLTFMeshStandardSGMaterial.prototype = Object.create( THREE.MeshStandardMaterial.prototype );
741 | GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial;
742 |
743 | GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) {
744 |
745 | THREE.MeshStandardMaterial.prototype.copy.call( this, source );
746 | this.specularMap = source.specularMap;
747 | this.specular.copy( source.specular );
748 | this.glossinessMap = source.glossinessMap;
749 | this.glossiness = source.glossiness;
750 | delete this.metalness;
751 | delete this.roughness;
752 | delete this.metalnessMap;
753 | delete this.roughnessMap;
754 | return this;
755 |
756 | };
757 |
758 | function GLTFMaterialsPbrSpecularGlossinessExtension() {
759 |
760 | return {
761 |
762 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
763 |
764 | specularGlossinessParams: [
765 | 'color',
766 | 'map',
767 | 'lightMap',
768 | 'lightMapIntensity',
769 | 'aoMap',
770 | 'aoMapIntensity',
771 | 'emissive',
772 | 'emissiveIntensity',
773 | 'emissiveMap',
774 | 'bumpMap',
775 | 'bumpScale',
776 | 'normalMap',
777 | 'normalMapType',
778 | 'displacementMap',
779 | 'displacementScale',
780 | 'displacementBias',
781 | 'specularMap',
782 | 'specular',
783 | 'glossinessMap',
784 | 'glossiness',
785 | 'alphaMap',
786 | 'envMap',
787 | 'envMapIntensity',
788 | 'refractionRatio',
789 | ],
790 |
791 | getMaterialType: function () {
792 |
793 | return GLTFMeshStandardSGMaterial;
794 |
795 | },
796 |
797 | extendParams: function ( materialParams, materialDef, parser ) {
798 |
799 | var pbrSpecularGlossiness = materialDef.extensions[ this.name ];
800 |
801 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
802 | materialParams.opacity = 1.0;
803 |
804 | var pending = [];
805 |
806 | if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {
807 |
808 | var array = pbrSpecularGlossiness.diffuseFactor;
809 |
810 | materialParams.color.fromArray( array );
811 | materialParams.opacity = array[ 3 ];
812 |
813 | }
814 |
815 | if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {
816 |
817 | pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) );
818 |
819 | }
820 |
821 | materialParams.emissive = new THREE.Color( 0.0, 0.0, 0.0 );
822 | materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
823 | materialParams.specular = new THREE.Color( 1.0, 1.0, 1.0 );
824 |
825 | if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {
826 |
827 | materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor );
828 |
829 | }
830 |
831 | if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {
832 |
833 | var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
834 | pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) );
835 | pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) );
836 |
837 | }
838 |
839 | return Promise.all( pending );
840 |
841 | },
842 |
843 | createMaterial: function ( materialParams ) {
844 |
845 | var material = new GLTFMeshStandardSGMaterial( materialParams );
846 | material.fog = true;
847 |
848 | material.color = materialParams.color;
849 |
850 | material.map = materialParams.map === undefined ? null : materialParams.map;
851 |
852 | material.lightMap = null;
853 | material.lightMapIntensity = 1.0;
854 |
855 | material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap;
856 | material.aoMapIntensity = 1.0;
857 |
858 | material.emissive = materialParams.emissive;
859 | material.emissiveIntensity = 1.0;
860 | material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap;
861 |
862 | material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap;
863 | material.bumpScale = 1;
864 |
865 | material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap;
866 | material.normalMapType = THREE.TangentSpaceNormalMap;
867 |
868 | if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale;
869 |
870 | material.displacementMap = null;
871 | material.displacementScale = 1;
872 | material.displacementBias = 0;
873 |
874 | material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap;
875 | material.specular = materialParams.specular;
876 |
877 | material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap;
878 | material.glossiness = materialParams.glossiness;
879 |
880 | material.alphaMap = null;
881 |
882 | material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap;
883 | material.envMapIntensity = 1.0;
884 |
885 | material.refractionRatio = 0.98;
886 |
887 | return material;
888 |
889 | },
890 |
891 | };
892 |
893 | }
894 |
895 | /**
896 | * Mesh Quantization Extension
897 | *
898 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
899 | */
900 | function GLTFMeshQuantizationExtension() {
901 |
902 | this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;
903 |
904 | }
905 |
906 | /*********************************/
907 | /********** INTERPOLATION ********/
908 | /*********************************/
909 |
910 | // Spline Interpolation
911 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
912 | function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
913 |
914 | THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
915 |
916 | }
917 |
918 | GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype );
919 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
920 |
921 | GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) {
922 |
923 | // Copies a sample value to the result buffer. See description of glTF
924 | // CUBICSPLINE values layout in interpolate_() function below.
925 |
926 | var result = this.resultBuffer,
927 | values = this.sampleValues,
928 | valueSize = this.valueSize,
929 | offset = index * valueSize * 3 + valueSize;
930 |
931 | for ( var i = 0; i !== valueSize; i ++ ) {
932 |
933 | result[ i ] = values[ offset + i ];
934 |
935 | }
936 |
937 | return result;
938 |
939 | };
940 |
941 | GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
942 |
943 | GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
944 |
945 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
946 |
947 | var result = this.resultBuffer;
948 | var values = this.sampleValues;
949 | var stride = this.valueSize;
950 |
951 | var stride2 = stride * 2;
952 | var stride3 = stride * 3;
953 |
954 | var td = t1 - t0;
955 |
956 | var p = ( t - t0 ) / td;
957 | var pp = p * p;
958 | var ppp = pp * p;
959 |
960 | var offset1 = i1 * stride3;
961 | var offset0 = offset1 - stride3;
962 |
963 | var s2 = - 2 * ppp + 3 * pp;
964 | var s3 = ppp - pp;
965 | var s0 = 1 - s2;
966 | var s1 = s3 - pp + p;
967 |
968 | // Layout of keyframe output values for CUBICSPLINE animations:
969 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
970 | for ( var i = 0; i !== stride; i ++ ) {
971 |
972 | var p0 = values[ offset0 + i + stride ]; // splineVertex_k
973 | var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
974 | var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
975 | var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
976 |
977 | result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
978 |
979 | }
980 |
981 | return result;
982 |
983 | };
984 |
985 | /*********************************/
986 | /********** INTERNALS ************/
987 | /*********************************/
988 |
989 | /* CONSTANTS */
990 |
991 | var WEBGL_CONSTANTS = {
992 | FLOAT: 5126,
993 | //FLOAT_MAT2: 35674,
994 | FLOAT_MAT3: 35675,
995 | FLOAT_MAT4: 35676,
996 | FLOAT_VEC2: 35664,
997 | FLOAT_VEC3: 35665,
998 | FLOAT_VEC4: 35666,
999 | LINEAR: 9729,
1000 | REPEAT: 10497,
1001 | SAMPLER_2D: 35678,
1002 | POINTS: 0,
1003 | LINES: 1,
1004 | LINE_LOOP: 2,
1005 | LINE_STRIP: 3,
1006 | TRIANGLES: 4,
1007 | TRIANGLE_STRIP: 5,
1008 | TRIANGLE_FAN: 6,
1009 | UNSIGNED_BYTE: 5121,
1010 | UNSIGNED_SHORT: 5123
1011 | };
1012 |
1013 | var WEBGL_COMPONENT_TYPES = {
1014 | 5120: Int8Array,
1015 | 5121: Uint8Array,
1016 | 5122: Int16Array,
1017 | 5123: Uint16Array,
1018 | 5125: Uint32Array,
1019 | 5126: Float32Array
1020 | };
1021 |
1022 | var WEBGL_FILTERS = {
1023 | 9728: THREE.NearestFilter,
1024 | 9729: THREE.LinearFilter,
1025 | 9984: THREE.NearestMipmapNearestFilter,
1026 | 9985: THREE.LinearMipmapNearestFilter,
1027 | 9986: THREE.NearestMipmapLinearFilter,
1028 | 9987: THREE.LinearMipmapLinearFilter
1029 | };
1030 |
1031 | var WEBGL_WRAPPINGS = {
1032 | 33071: THREE.ClampToEdgeWrapping,
1033 | 33648: THREE.MirroredRepeatWrapping,
1034 | 10497: THREE.RepeatWrapping
1035 | };
1036 |
1037 | var WEBGL_TYPE_SIZES = {
1038 | 'SCALAR': 1,
1039 | 'VEC2': 2,
1040 | 'VEC3': 3,
1041 | 'VEC4': 4,
1042 | 'MAT2': 4,
1043 | 'MAT3': 9,
1044 | 'MAT4': 16
1045 | };
1046 |
1047 | var ATTRIBUTES = {
1048 | POSITION: 'position',
1049 | NORMAL: 'normal',
1050 | TANGENT: 'tangent',
1051 | TEXCOORD_0: 'uv',
1052 | TEXCOORD_1: 'uv2',
1053 | COLOR_0: 'color',
1054 | WEIGHTS_0: 'skinWeight',
1055 | JOINTS_0: 'skinIndex',
1056 | };
1057 |
1058 | var PATH_PROPERTIES = {
1059 | scale: 'scale',
1060 | translation: 'position',
1061 | rotation: 'quaternion',
1062 | weights: 'morphTargetInfluences'
1063 | };
1064 |
1065 | var INTERPOLATION = {
1066 | CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
1067 | // keyframe track will be initialized with a default interpolation type, then modified.
1068 | LINEAR: THREE.InterpolateLinear,
1069 | STEP: THREE.InterpolateDiscrete
1070 | };
1071 |
1072 | var ALPHA_MODES = {
1073 | OPAQUE: 'OPAQUE',
1074 | MASK: 'MASK',
1075 | BLEND: 'BLEND'
1076 | };
1077 |
1078 | var MIME_TYPE_FORMATS = {
1079 | 'image/png': THREE.RGBAFormat,
1080 | 'image/jpeg': THREE.RGBFormat
1081 | };
1082 |
1083 | /* UTILITY FUNCTIONS */
1084 |
1085 | function resolveURL( url, path ) {
1086 |
1087 | // Invalid URL
1088 | if ( typeof url !== 'string' || url === '' ) return '';
1089 |
1090 | // Host Relative URL
1091 | if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
1092 |
1093 | path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
1094 |
1095 | }
1096 |
1097 | // Absolute URL http://,https://,//
1098 | if ( /^(https?:)?\/\//i.test( url ) ) return url;
1099 |
1100 | // Data URI
1101 | if ( /^data:.*,.*$/i.test( url ) ) return url;
1102 |
1103 | // Blob URL
1104 | if ( /^blob:.*$/i.test( url ) ) return url;
1105 |
1106 | // Relative URL
1107 | return path + url;
1108 |
1109 | }
1110 |
1111 | /**
1112 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
1113 | */
1114 | function createDefaultMaterial( cache ) {
1115 |
1116 | if ( cache[ 'DefaultMaterial' ] === undefined ) {
1117 |
1118 | cache[ 'DefaultMaterial' ] = new THREE.MeshStandardMaterial( {
1119 | color: 0xFFFFFF,
1120 | emissive: 0x000000,
1121 | metalness: 1,
1122 | roughness: 1,
1123 | transparent: false,
1124 | depthTest: true,
1125 | side: THREE.FrontSide
1126 | } );
1127 |
1128 | }
1129 |
1130 | return cache[ 'DefaultMaterial' ];
1131 |
1132 | }
1133 |
1134 | function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
1135 |
1136 | // Add unknown glTF extensions to an object's userData.
1137 |
1138 | for ( var name in objectDef.extensions ) {
1139 |
1140 | if ( knownExtensions[ name ] === undefined ) {
1141 |
1142 | object.userData.gltfExtensions = object.userData.gltfExtensions || {};
1143 | object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
1144 |
1145 | }
1146 |
1147 | }
1148 |
1149 | }
1150 |
1151 | /**
1152 | * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object
1153 | * @param {GLTF.definition} gltfDef
1154 | */
1155 | function assignExtrasToUserData( object, gltfDef ) {
1156 |
1157 | if ( gltfDef.extras !== undefined ) {
1158 |
1159 | if ( typeof gltfDef.extras === 'object' ) {
1160 |
1161 | Object.assign( object.userData, gltfDef.extras );
1162 |
1163 | } else {
1164 |
1165 | console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
1166 |
1167 | }
1168 |
1169 | }
1170 |
1171 | }
1172 |
1173 | /**
1174 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
1175 | *
1176 | * @param {THREE.BufferGeometry} geometry
1177 | * @param {Array} targets
1178 | * @param {GLTFParser} parser
1179 | * @return {Promise}
1180 | */
1181 | function addMorphTargets( geometry, targets, parser ) {
1182 |
1183 | var hasMorphPosition = false;
1184 | var hasMorphNormal = false;
1185 |
1186 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1187 |
1188 | var target = targets[ i ];
1189 |
1190 | if ( target.POSITION !== undefined ) hasMorphPosition = true;
1191 | if ( target.NORMAL !== undefined ) hasMorphNormal = true;
1192 |
1193 | if ( hasMorphPosition && hasMorphNormal ) break;
1194 |
1195 | }
1196 |
1197 | if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
1198 |
1199 | var pendingPositionAccessors = [];
1200 | var pendingNormalAccessors = [];
1201 |
1202 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1203 |
1204 | var target = targets[ i ];
1205 |
1206 | if ( hasMorphPosition ) {
1207 |
1208 | var pendingAccessor = target.POSITION !== undefined
1209 | ? parser.getDependency( 'accessor', target.POSITION )
1210 | : geometry.attributes.position;
1211 |
1212 | pendingPositionAccessors.push( pendingAccessor );
1213 |
1214 | }
1215 |
1216 | if ( hasMorphNormal ) {
1217 |
1218 | var pendingAccessor = target.NORMAL !== undefined
1219 | ? parser.getDependency( 'accessor', target.NORMAL )
1220 | : geometry.attributes.normal;
1221 |
1222 | pendingNormalAccessors.push( pendingAccessor );
1223 |
1224 | }
1225 |
1226 | }
1227 |
1228 | return Promise.all( [
1229 | Promise.all( pendingPositionAccessors ),
1230 | Promise.all( pendingNormalAccessors )
1231 | ] ).then( function ( accessors ) {
1232 |
1233 | var morphPositions = accessors[ 0 ];
1234 | var morphNormals = accessors[ 1 ];
1235 |
1236 | if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
1237 | if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
1238 | geometry.morphTargetsRelative = true;
1239 |
1240 | return geometry;
1241 |
1242 | } );
1243 |
1244 | }
1245 |
1246 | /**
1247 | * @param {THREE.Mesh} mesh
1248 | * @param {GLTF.Mesh} meshDef
1249 | */
1250 | function updateMorphTargets( mesh, meshDef ) {
1251 |
1252 | mesh.updateMorphTargets();
1253 |
1254 | if ( meshDef.weights !== undefined ) {
1255 |
1256 | for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) {
1257 |
1258 | mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
1259 |
1260 | }
1261 |
1262 | }
1263 |
1264 | // .extras has user-defined data, so check that .extras.targetNames is an array.
1265 | if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
1266 |
1267 | var targetNames = meshDef.extras.targetNames;
1268 |
1269 | if ( mesh.morphTargetInfluences.length === targetNames.length ) {
1270 |
1271 | mesh.morphTargetDictionary = {};
1272 |
1273 | for ( var i = 0, il = targetNames.length; i < il; i ++ ) {
1274 |
1275 | mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
1276 |
1277 | }
1278 |
1279 | } else {
1280 |
1281 | console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
1282 |
1283 | }
1284 |
1285 | }
1286 |
1287 | }
1288 |
1289 | function createPrimitiveKey( primitiveDef ) {
1290 |
1291 | var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
1292 | var geometryKey;
1293 |
1294 | if ( dracoExtension ) {
1295 |
1296 | geometryKey = 'draco:' + dracoExtension.bufferView
1297 | + ':' + dracoExtension.indices
1298 | + ':' + createAttributesKey( dracoExtension.attributes );
1299 |
1300 | } else {
1301 |
1302 | geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
1303 |
1304 | }
1305 |
1306 | return geometryKey;
1307 |
1308 | }
1309 |
1310 | function createAttributesKey( attributes ) {
1311 |
1312 | var attributesKey = '';
1313 |
1314 | var keys = Object.keys( attributes ).sort();
1315 |
1316 | for ( var i = 0, il = keys.length; i < il; i ++ ) {
1317 |
1318 | attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
1319 |
1320 | }
1321 |
1322 | return attributesKey;
1323 |
1324 | }
1325 |
1326 | /* GLTF PARSER */
1327 |
1328 | function GLTFParser( json, extensions, options ) {
1329 |
1330 | this.json = json || {};
1331 | this.extensions = extensions || {};
1332 | this.options = options || {};
1333 |
1334 | // loader object cache
1335 | this.cache = new GLTFRegistry();
1336 |
1337 | // BufferGeometry caching
1338 | this.primitiveCache = {};
1339 |
1340 | this.textureLoader = new THREE.TextureLoader( this.options.manager );
1341 | this.textureLoader.setCrossOrigin( this.options.crossOrigin );
1342 |
1343 | this.fileLoader = new THREE.FileLoader( this.options.manager );
1344 | this.fileLoader.setResponseType( 'arraybuffer' );
1345 |
1346 | if ( this.options.crossOrigin === 'use-credentials' ) {
1347 |
1348 | this.fileLoader.setWithCredentials( true );
1349 |
1350 | }
1351 |
1352 | }
1353 |
1354 | GLTFParser.prototype.parse = function ( onLoad, onError ) {
1355 |
1356 | var parser = this;
1357 | var json = this.json;
1358 | var extensions = this.extensions;
1359 |
1360 | // Clear the loader cache
1361 | this.cache.removeAll();
1362 |
1363 | // Mark the special nodes/meshes in json for efficient parse
1364 | this.markDefs();
1365 |
1366 | Promise.all( [
1367 |
1368 | this.getDependencies( 'scene' ),
1369 | this.getDependencies( 'animation' ),
1370 | this.getDependencies( 'camera' ),
1371 |
1372 | ] ).then( function ( dependencies ) {
1373 |
1374 | var result = {
1375 | scene: dependencies[ 0 ][ json.scene || 0 ],
1376 | scenes: dependencies[ 0 ],
1377 | animations: dependencies[ 1 ],
1378 | cameras: dependencies[ 2 ],
1379 | asset: json.asset,
1380 | parser: parser,
1381 | userData: {}
1382 | };
1383 |
1384 | addUnknownExtensionsToUserData( extensions, result, json );
1385 |
1386 | assignExtrasToUserData( result, json );
1387 |
1388 | onLoad( result );
1389 |
1390 | } ).catch( onError );
1391 |
1392 | };
1393 |
1394 | /**
1395 | * Marks the special nodes/meshes in json for efficient parse.
1396 | */
1397 | GLTFParser.prototype.markDefs = function () {
1398 |
1399 | var nodeDefs = this.json.nodes || [];
1400 | var skinDefs = this.json.skins || [];
1401 | var meshDefs = this.json.meshes || [];
1402 |
1403 | var meshReferences = {};
1404 | var meshUses = {};
1405 |
1406 | // Nothing in the node definition indicates whether it is a Bone or an
1407 | // Object3D. Use the skins' joint references to mark bones.
1408 | for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
1409 |
1410 | var joints = skinDefs[ skinIndex ].joints;
1411 |
1412 | for ( var i = 0, il = joints.length; i < il; i ++ ) {
1413 |
1414 | nodeDefs[ joints[ i ] ].isBone = true;
1415 |
1416 | }
1417 |
1418 | }
1419 |
1420 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To
1421 | // avoid having more than one THREE.Mesh with the same name, count
1422 | // references and rename instances below.
1423 | //
1424 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
1425 | for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
1426 |
1427 | var nodeDef = nodeDefs[ nodeIndex ];
1428 |
1429 | if ( nodeDef.mesh !== undefined ) {
1430 |
1431 | if ( meshReferences[ nodeDef.mesh ] === undefined ) {
1432 |
1433 | meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0;
1434 |
1435 | }
1436 |
1437 | meshReferences[ nodeDef.mesh ] ++;
1438 |
1439 | // Nothing in the mesh definition indicates whether it is
1440 | // a SkinnedMesh or Mesh. Use the node's mesh reference
1441 | // to mark SkinnedMesh if node has skin.
1442 | if ( nodeDef.skin !== undefined ) {
1443 |
1444 | meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
1445 |
1446 | }
1447 |
1448 | }
1449 |
1450 | }
1451 |
1452 | this.json.meshReferences = meshReferences;
1453 | this.json.meshUses = meshUses;
1454 |
1455 | };
1456 |
1457 | /**
1458 | * Requests the specified dependency asynchronously, with caching.
1459 | * @param {string} type
1460 | * @param {number} index
1461 | * @return {Promise}
1462 | */
1463 | GLTFParser.prototype.getDependency = function ( type, index ) {
1464 |
1465 | var cacheKey = type + ':' + index;
1466 | var dependency = this.cache.get( cacheKey );
1467 |
1468 | if ( ! dependency ) {
1469 |
1470 | switch ( type ) {
1471 |
1472 | case 'scene':
1473 | dependency = this.loadScene( index );
1474 | break;
1475 |
1476 | case 'node':
1477 | dependency = this.loadNode( index );
1478 | break;
1479 |
1480 | case 'mesh':
1481 | dependency = this.loadMesh( index );
1482 | break;
1483 |
1484 | case 'accessor':
1485 | dependency = this.loadAccessor( index );
1486 | break;
1487 |
1488 | case 'bufferView':
1489 | dependency = this.loadBufferView( index );
1490 | break;
1491 |
1492 | case 'buffer':
1493 | dependency = this.loadBuffer( index );
1494 | break;
1495 |
1496 | case 'material':
1497 | dependency = this.loadMaterial( index );
1498 | break;
1499 |
1500 | case 'texture':
1501 | dependency = this.loadTexture( index );
1502 | break;
1503 |
1504 | case 'skin':
1505 | dependency = this.loadSkin( index );
1506 | break;
1507 |
1508 | case 'animation':
1509 | dependency = this.loadAnimation( index );
1510 | break;
1511 |
1512 | case 'camera':
1513 | dependency = this.loadCamera( index );
1514 | break;
1515 |
1516 | case 'light':
1517 | dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
1518 | break;
1519 |
1520 | default:
1521 | throw new Error( 'Unknown type: ' + type );
1522 |
1523 | }
1524 |
1525 | this.cache.add( cacheKey, dependency );
1526 |
1527 | }
1528 |
1529 | return dependency;
1530 |
1531 | };
1532 |
1533 | /**
1534 | * Requests all dependencies of the specified type asynchronously, with caching.
1535 | * @param {string} type
1536 | * @return {Promise>}
1537 | */
1538 | GLTFParser.prototype.getDependencies = function ( type ) {
1539 |
1540 | var dependencies = this.cache.get( type );
1541 |
1542 | if ( ! dependencies ) {
1543 |
1544 | var parser = this;
1545 | var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
1546 |
1547 | dependencies = Promise.all( defs.map( function ( def, index ) {
1548 |
1549 | return parser.getDependency( type, index );
1550 |
1551 | } ) );
1552 |
1553 | this.cache.add( type, dependencies );
1554 |
1555 | }
1556 |
1557 | return dependencies;
1558 |
1559 | };
1560 |
1561 | /**
1562 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1563 | * @param {number} bufferIndex
1564 | * @return {Promise}
1565 | */
1566 | GLTFParser.prototype.loadBuffer = function ( bufferIndex ) {
1567 |
1568 | var bufferDef = this.json.buffers[ bufferIndex ];
1569 | var loader = this.fileLoader;
1570 |
1571 | if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {
1572 |
1573 | throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );
1574 |
1575 | }
1576 |
1577 | // If present, GLB container is required to be the first buffer.
1578 | if ( bufferDef.uri === undefined && bufferIndex === 0 ) {
1579 |
1580 | return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body );
1581 |
1582 | }
1583 |
1584 | var options = this.options;
1585 |
1586 | return new Promise( function ( resolve, reject ) {
1587 |
1588 | loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {
1589 |
1590 | reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );
1591 |
1592 | } );
1593 |
1594 | } );
1595 |
1596 | };
1597 |
1598 | /**
1599 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1600 | * @param {number} bufferViewIndex
1601 | * @return {Promise}
1602 | */
1603 | GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) {
1604 |
1605 | var bufferViewDef = this.json.bufferViews[ bufferViewIndex ];
1606 |
1607 | return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {
1608 |
1609 | var byteLength = bufferViewDef.byteLength || 0;
1610 | var byteOffset = bufferViewDef.byteOffset || 0;
1611 | return buffer.slice( byteOffset, byteOffset + byteLength );
1612 |
1613 | } );
1614 |
1615 | };
1616 |
1617 | /**
1618 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
1619 | * @param {number} accessorIndex
1620 | * @return {Promise}
1621 | */
1622 | GLTFParser.prototype.loadAccessor = function ( accessorIndex ) {
1623 |
1624 | var parser = this;
1625 | var json = this.json;
1626 |
1627 | var accessorDef = this.json.accessors[ accessorIndex ];
1628 |
1629 | if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {
1630 |
1631 | // Ignore empty accessors, which may be used to declare runtime
1632 | // information about attributes coming from another source (e.g. Draco
1633 | // compression extension).
1634 | return Promise.resolve( null );
1635 |
1636 | }
1637 |
1638 | var pendingBufferViews = [];
1639 |
1640 | if ( accessorDef.bufferView !== undefined ) {
1641 |
1642 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );
1643 |
1644 | } else {
1645 |
1646 | pendingBufferViews.push( null );
1647 |
1648 | }
1649 |
1650 | if ( accessorDef.sparse !== undefined ) {
1651 |
1652 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
1653 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );
1654 |
1655 | }
1656 |
1657 | return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
1658 |
1659 | var bufferView = bufferViews[ 0 ];
1660 |
1661 | var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
1662 | var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
1663 |
1664 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
1665 | var elementBytes = TypedArray.BYTES_PER_ELEMENT;
1666 | var itemBytes = elementBytes * itemSize;
1667 | var byteOffset = accessorDef.byteOffset || 0;
1668 | var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
1669 | var normalized = accessorDef.normalized === true;
1670 | var array, bufferAttribute;
1671 |
1672 | // The buffer is not interleaved if the stride is the item size in bytes.
1673 | if ( byteStride && byteStride !== itemBytes ) {
1674 |
1675 | // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
1676 | // This makes sure that IBA.count reflects accessor.count properly
1677 | var ibSlice = Math.floor( byteOffset / byteStride );
1678 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
1679 | var ib = parser.cache.get( ibCacheKey );
1680 |
1681 | if ( ! ib ) {
1682 |
1683 | array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes );
1684 |
1685 | // Integer parameters to IB/IBA are in array elements, not bytes.
1686 | ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes );
1687 |
1688 | parser.cache.add( ibCacheKey, ib );
1689 |
1690 | }
1691 |
1692 | bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized );
1693 |
1694 | } else {
1695 |
1696 | if ( bufferView === null ) {
1697 |
1698 | array = new TypedArray( accessorDef.count * itemSize );
1699 |
1700 | } else {
1701 |
1702 | array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );
1703 |
1704 | }
1705 |
1706 | bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized );
1707 |
1708 | }
1709 |
1710 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
1711 | if ( accessorDef.sparse !== undefined ) {
1712 |
1713 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
1714 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];
1715 |
1716 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
1717 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
1718 |
1719 | var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
1720 | var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );
1721 |
1722 | if ( bufferView !== null ) {
1723 |
1724 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
1725 | bufferAttribute = new THREE.BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized );
1726 |
1727 | }
1728 |
1729 | for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) {
1730 |
1731 | var index = sparseIndices[ i ];
1732 |
1733 | bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
1734 | if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
1735 | if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
1736 | if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
1737 | if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );
1738 |
1739 | }
1740 |
1741 | }
1742 |
1743 | return bufferAttribute;
1744 |
1745 | } );
1746 |
1747 | };
1748 |
1749 | /**
1750 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
1751 | * @param {number} textureIndex
1752 | * @return {Promise}
1753 | */
1754 | GLTFParser.prototype.loadTexture = function ( textureIndex ) {
1755 |
1756 | var parser = this;
1757 | var json = this.json;
1758 | var options = this.options;
1759 | var textureLoader = this.textureLoader;
1760 |
1761 | var URL = window.URL || window.webkitURL;
1762 |
1763 | var textureDef = json.textures[ textureIndex ];
1764 |
1765 | var textureExtensions = textureDef.extensions || {};
1766 |
1767 | var source;
1768 |
1769 | if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) {
1770 |
1771 | source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ];
1772 |
1773 | } else {
1774 |
1775 | source = json.images[ textureDef.source ];
1776 |
1777 | }
1778 |
1779 | var sourceURI = source.uri;
1780 | var isObjectURL = false;
1781 |
1782 | if ( source.bufferView !== undefined ) {
1783 |
1784 | // Load binary image data from bufferView, if provided.
1785 |
1786 | sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
1787 |
1788 | isObjectURL = true;
1789 | var blob = new Blob( [ bufferView ], { type: source.mimeType } );
1790 | sourceURI = URL.createObjectURL( blob );
1791 | return sourceURI;
1792 |
1793 | } );
1794 |
1795 | }
1796 |
1797 | return Promise.resolve( sourceURI ).then( function ( sourceURI ) {
1798 |
1799 | // Load Texture resource.
1800 |
1801 | var loader = options.manager.getHandler( sourceURI );
1802 |
1803 | if ( ! loader ) {
1804 |
1805 | loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ]
1806 | ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader
1807 | : textureLoader;
1808 |
1809 | }
1810 |
1811 | return new Promise( function ( resolve, reject ) {
1812 |
1813 | loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject );
1814 |
1815 | } );
1816 |
1817 | } ).then( function ( texture ) {
1818 |
1819 | // Clean up resources and configure Texture.
1820 |
1821 | if ( isObjectURL === true ) {
1822 |
1823 | URL.revokeObjectURL( sourceURI );
1824 |
1825 | }
1826 |
1827 | texture.flipY = false;
1828 |
1829 | if ( textureDef.name ) texture.name = textureDef.name;
1830 |
1831 | // Ignore unknown mime types, like DDS files.
1832 | if ( source.mimeType in MIME_TYPE_FORMATS ) {
1833 |
1834 | texture.format = MIME_TYPE_FORMATS[ source.mimeType ];
1835 |
1836 | }
1837 |
1838 | var samplers = json.samplers || {};
1839 | var sampler = samplers[ textureDef.sampler ] || {};
1840 |
1841 | texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;
1842 | texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipmapLinearFilter;
1843 | texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
1844 | texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
1845 |
1846 | return texture;
1847 |
1848 | } );
1849 |
1850 | };
1851 |
1852 | /**
1853 | * Asynchronously assigns a texture to the given material parameters.
1854 | * @param {Object} materialParams
1855 | * @param {string} mapName
1856 | * @param {Object} mapDef
1857 | * @return {Promise}
1858 | */
1859 | GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) {
1860 |
1861 | var parser = this;
1862 |
1863 | return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
1864 |
1865 | if ( ! texture.isCompressedTexture ) {
1866 |
1867 | switch ( mapName ) {
1868 |
1869 | case 'aoMap':
1870 | case 'emissiveMap':
1871 | case 'metalnessMap':
1872 | case 'normalMap':
1873 | case 'roughnessMap':
1874 | texture.format = THREE.RGBFormat;
1875 | break;
1876 |
1877 | }
1878 |
1879 | }
1880 |
1881 | // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
1882 | // However, we will copy UV set 0 to UV set 1 on demand for aoMap
1883 | if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) {
1884 |
1885 | console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' );
1886 |
1887 | }
1888 |
1889 | if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
1890 |
1891 | var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
1892 |
1893 | if ( transform ) {
1894 |
1895 | texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
1896 |
1897 | }
1898 |
1899 | }
1900 |
1901 | materialParams[ mapName ] = texture;
1902 |
1903 | } );
1904 |
1905 | };
1906 |
1907 | /**
1908 | * Assigns final material to a Mesh, Line, or Points instance. The instance
1909 | * already has a material (generated from the glTF material options alone)
1910 | * but reuse of the same glTF material may require multiple threejs materials
1911 | * to accomodate different primitive types, defines, etc. New materials will
1912 | * be created if necessary, and reused from a cache.
1913 | * @param {THREE.Object3D} mesh Mesh, Line, or Points instance.
1914 | */
1915 | GLTFParser.prototype.assignFinalMaterial = function ( mesh ) {
1916 |
1917 | var geometry = mesh.geometry;
1918 | var material = mesh.material;
1919 | var extensions = this.extensions;
1920 |
1921 | var useVertexTangents = geometry.attributes.tangent !== undefined;
1922 | var useVertexColors = geometry.attributes.color !== undefined;
1923 | var useFlatShading = geometry.attributes.normal === undefined;
1924 | var useSkinning = mesh.isSkinnedMesh === true;
1925 | var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
1926 | var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
1927 |
1928 | if ( mesh.isPoints ) {
1929 |
1930 | var cacheKey = 'PointsMaterial:' + material.uuid;
1931 |
1932 | var pointsMaterial = this.cache.get( cacheKey );
1933 |
1934 | if ( ! pointsMaterial ) {
1935 |
1936 | pointsMaterial = new THREE.PointsMaterial();
1937 | THREE.Material.prototype.copy.call( pointsMaterial, material );
1938 | pointsMaterial.color.copy( material.color );
1939 | pointsMaterial.map = material.map;
1940 | pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
1941 |
1942 | this.cache.add( cacheKey, pointsMaterial );
1943 |
1944 | }
1945 |
1946 | material = pointsMaterial;
1947 |
1948 | } else if ( mesh.isLine ) {
1949 |
1950 | var cacheKey = 'LineBasicMaterial:' + material.uuid;
1951 |
1952 | var lineMaterial = this.cache.get( cacheKey );
1953 |
1954 | if ( ! lineMaterial ) {
1955 |
1956 | lineMaterial = new THREE.LineBasicMaterial();
1957 | THREE.Material.prototype.copy.call( lineMaterial, material );
1958 | lineMaterial.color.copy( material.color );
1959 |
1960 | this.cache.add( cacheKey, lineMaterial );
1961 |
1962 | }
1963 |
1964 | material = lineMaterial;
1965 |
1966 | }
1967 |
1968 | // Clone the material if it will be modified
1969 | if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
1970 |
1971 | var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
1972 |
1973 | if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
1974 | if ( useSkinning ) cacheKey += 'skinning:';
1975 | if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
1976 | if ( useVertexColors ) cacheKey += 'vertex-colors:';
1977 | if ( useFlatShading ) cacheKey += 'flat-shading:';
1978 | if ( useMorphTargets ) cacheKey += 'morph-targets:';
1979 | if ( useMorphNormals ) cacheKey += 'morph-normals:';
1980 |
1981 | var cachedMaterial = this.cache.get( cacheKey );
1982 |
1983 | if ( ! cachedMaterial ) {
1984 |
1985 | cachedMaterial = material.clone();
1986 |
1987 | if ( useSkinning ) cachedMaterial.skinning = true;
1988 | if ( useVertexTangents ) cachedMaterial.vertexTangents = true;
1989 | if ( useVertexColors ) cachedMaterial.vertexColors = true;
1990 | if ( useFlatShading ) cachedMaterial.flatShading = true;
1991 | if ( useMorphTargets ) cachedMaterial.morphTargets = true;
1992 | if ( useMorphNormals ) cachedMaterial.morphNormals = true;
1993 |
1994 | this.cache.add( cacheKey, cachedMaterial );
1995 |
1996 | }
1997 |
1998 | material = cachedMaterial;
1999 |
2000 | }
2001 |
2002 | // workarounds for mesh and geometry
2003 |
2004 | if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
2005 |
2006 | geometry.setAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
2007 |
2008 | }
2009 |
2010 | // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
2011 | if ( material.normalScale && ! useVertexTangents ) {
2012 |
2013 | material.normalScale.y = - material.normalScale.y;
2014 |
2015 | }
2016 |
2017 | mesh.material = material;
2018 |
2019 | };
2020 |
2021 | /**
2022 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
2023 | * @param {number} materialIndex
2024 | * @return {Promise}
2025 | */
2026 | GLTFParser.prototype.loadMaterial = function ( materialIndex ) {
2027 |
2028 | var parser = this;
2029 | var json = this.json;
2030 | var extensions = this.extensions;
2031 | var materialDef = json.materials[ materialIndex ];
2032 |
2033 | var materialType;
2034 | var materialParams = {};
2035 | var materialExtensions = materialDef.extensions || {};
2036 |
2037 | var pending = [];
2038 |
2039 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) {
2040 |
2041 | var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
2042 | materialType = sgExtension.getMaterialType();
2043 | pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) );
2044 |
2045 | } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {
2046 |
2047 | var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
2048 | materialType = kmuExtension.getMaterialType();
2049 | pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );
2050 |
2051 | } else {
2052 |
2053 | // Specification:
2054 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
2055 |
2056 | materialType = THREE.MeshStandardMaterial;
2057 |
2058 | var metallicRoughness = materialDef.pbrMetallicRoughness || {};
2059 |
2060 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
2061 | materialParams.opacity = 1.0;
2062 |
2063 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
2064 |
2065 | var array = metallicRoughness.baseColorFactor;
2066 |
2067 | materialParams.color.fromArray( array );
2068 | materialParams.opacity = array[ 3 ];
2069 |
2070 | }
2071 |
2072 | if ( metallicRoughness.baseColorTexture !== undefined ) {
2073 |
2074 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
2075 |
2076 | }
2077 |
2078 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
2079 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
2080 |
2081 | if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {
2082 |
2083 | pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
2084 | pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );
2085 |
2086 | }
2087 |
2088 | }
2089 |
2090 | if ( materialDef.doubleSided === true ) {
2091 |
2092 | materialParams.side = THREE.DoubleSide;
2093 |
2094 | }
2095 |
2096 | var alphaMode = materialDef.alphaMode;
2097 |
2098 | if ( alphaMode === ALPHA_MODES.BLEND ) {
2099 |
2100 | materialParams.transparent = true;
2101 |
2102 | } else if ( alphaMode === ALPHA_MODES.MASK ) {
2103 |
2104 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
2105 |
2106 | }
2107 |
2108 | if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2109 |
2110 | pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );
2111 |
2112 | materialParams.normalScale = new THREE.Vector2( 1, 1 );
2113 |
2114 | if ( materialDef.normalTexture.scale !== undefined ) {
2115 |
2116 | materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale );
2117 |
2118 | }
2119 |
2120 | }
2121 |
2122 | if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2123 |
2124 | pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );
2125 |
2126 | if ( materialDef.occlusionTexture.strength !== undefined ) {
2127 |
2128 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
2129 |
2130 | }
2131 |
2132 | }
2133 |
2134 | if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2135 |
2136 | materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor );
2137 |
2138 | }
2139 |
2140 | if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2141 |
2142 | pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) );
2143 |
2144 | }
2145 |
2146 | return Promise.all( pending ).then( function () {
2147 |
2148 | var material;
2149 |
2150 | if ( materialType === GLTFMeshStandardSGMaterial ) {
2151 |
2152 | material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams );
2153 |
2154 | } else {
2155 |
2156 | material = new materialType( materialParams );
2157 |
2158 | }
2159 |
2160 | if ( materialDef.name ) material.name = materialDef.name;
2161 |
2162 | // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
2163 | if ( material.map ) material.map.encoding = THREE.sRGBEncoding;
2164 | if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding;
2165 |
2166 | assignExtrasToUserData( material, materialDef );
2167 |
2168 | if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
2169 |
2170 | return material;
2171 |
2172 | } );
2173 |
2174 | };
2175 |
2176 | /**
2177 | * @param {THREE.BufferGeometry} geometry
2178 | * @param {GLTF.Primitive} primitiveDef
2179 | * @param {GLTFParser} parser
2180 | */
2181 | function computeBounds( geometry, primitiveDef, parser ) {
2182 |
2183 | var attributes = primitiveDef.attributes;
2184 |
2185 | var box = new THREE.Box3();
2186 |
2187 | if ( attributes.POSITION !== undefined ) {
2188 |
2189 | var accessor = parser.json.accessors[ attributes.POSITION ];
2190 |
2191 | var min = accessor.min;
2192 | var max = accessor.max;
2193 |
2194 | // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
2195 |
2196 | if ( min !== undefined && max !== undefined ) {
2197 |
2198 | box.set(
2199 | new THREE.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ),
2200 | new THREE.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) );
2201 |
2202 | } else {
2203 |
2204 | console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
2205 |
2206 | return;
2207 |
2208 | }
2209 |
2210 | } else {
2211 |
2212 | return;
2213 |
2214 | }
2215 |
2216 | var targets = primitiveDef.targets;
2217 |
2218 | if ( targets !== undefined ) {
2219 |
2220 | var vector = new THREE.Vector3();
2221 |
2222 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
2223 |
2224 | var target = targets[ i ];
2225 |
2226 | if ( target.POSITION !== undefined ) {
2227 |
2228 | var accessor = parser.json.accessors[ target.POSITION ];
2229 | var min = accessor.min;
2230 | var max = accessor.max;
2231 |
2232 | // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
2233 |
2234 | if ( min !== undefined && max !== undefined ) {
2235 |
2236 | // we need to get max of absolute components because target weight is [-1,1]
2237 | vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) );
2238 | vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) );
2239 | vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) );
2240 |
2241 | box.expandByVector( vector );
2242 |
2243 | } else {
2244 |
2245 | console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
2246 |
2247 | }
2248 |
2249 | }
2250 |
2251 | }
2252 |
2253 | }
2254 |
2255 | geometry.boundingBox = box;
2256 |
2257 | var sphere = new THREE.Sphere();
2258 |
2259 | box.getCenter( sphere.center );
2260 | sphere.radius = box.min.distanceTo( box.max ) / 2;
2261 |
2262 | geometry.boundingSphere = sphere;
2263 |
2264 | }
2265 |
2266 | /**
2267 | * @param {THREE.BufferGeometry} geometry
2268 | * @param {GLTF.Primitive} primitiveDef
2269 | * @param {GLTFParser} parser
2270 | * @return {Promise}
2271 | */
2272 | function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
2273 |
2274 | var attributes = primitiveDef.attributes;
2275 |
2276 | var pending = [];
2277 |
2278 | function assignAttributeAccessor( accessorIndex, attributeName ) {
2279 |
2280 | return parser.getDependency( 'accessor', accessorIndex )
2281 | .then( function ( accessor ) {
2282 |
2283 | geometry.setAttribute( attributeName, accessor );
2284 |
2285 | } );
2286 |
2287 | }
2288 |
2289 | for ( var gltfAttributeName in attributes ) {
2290 |
2291 | var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
2292 |
2293 | // Skip attributes already provided by e.g. Draco extension.
2294 | if ( threeAttributeName in geometry.attributes ) continue;
2295 |
2296 | pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
2297 |
2298 | }
2299 |
2300 | if ( primitiveDef.indices !== undefined && ! geometry.index ) {
2301 |
2302 | var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
2303 |
2304 | geometry.setIndex( accessor );
2305 |
2306 | } );
2307 |
2308 | pending.push( accessor );
2309 |
2310 | }
2311 |
2312 | assignExtrasToUserData( geometry, primitiveDef );
2313 |
2314 | computeBounds( geometry, primitiveDef, parser );
2315 |
2316 | return Promise.all( pending ).then( function () {
2317 |
2318 | return primitiveDef.targets !== undefined
2319 | ? addMorphTargets( geometry, primitiveDef.targets, parser )
2320 | : geometry;
2321 |
2322 | } );
2323 |
2324 | }
2325 |
2326 | /**
2327 | * @param {THREE.BufferGeometry} geometry
2328 | * @param {Number} drawMode
2329 | * @return {THREE.BufferGeometry}
2330 | */
2331 | function toTrianglesDrawMode( geometry, drawMode ) {
2332 |
2333 | var index = geometry.getIndex();
2334 |
2335 | // generate index if not present
2336 |
2337 | if ( index === null ) {
2338 |
2339 | var indices = [];
2340 |
2341 | var position = geometry.getAttribute( 'position' );
2342 |
2343 | if ( position !== undefined ) {
2344 |
2345 | for ( var i = 0; i < position.count; i ++ ) {
2346 |
2347 | indices.push( i );
2348 |
2349 | }
2350 |
2351 | geometry.setIndex( indices );
2352 | index = geometry.getIndex();
2353 |
2354 | } else {
2355 |
2356 | console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
2357 | return geometry;
2358 |
2359 | }
2360 |
2361 | }
2362 |
2363 | //
2364 |
2365 | var numberOfTriangles = index.count - 2;
2366 | var newIndices = [];
2367 |
2368 | if ( drawMode === THREE.TriangleFanDrawMode ) {
2369 |
2370 | // gl.TRIANGLE_FAN
2371 |
2372 | for ( var i = 1; i <= numberOfTriangles; i ++ ) {
2373 |
2374 | newIndices.push( index.getX( 0 ) );
2375 | newIndices.push( index.getX( i ) );
2376 | newIndices.push( index.getX( i + 1 ) );
2377 |
2378 | }
2379 |
2380 | } else {
2381 |
2382 | // gl.TRIANGLE_STRIP
2383 |
2384 | for ( var i = 0; i < numberOfTriangles; i ++ ) {
2385 |
2386 | if ( i % 2 === 0 ) {
2387 |
2388 | newIndices.push( index.getX( i ) );
2389 | newIndices.push( index.getX( i + 1 ) );
2390 | newIndices.push( index.getX( i + 2 ) );
2391 |
2392 |
2393 | } else {
2394 |
2395 | newIndices.push( index.getX( i + 2 ) );
2396 | newIndices.push( index.getX( i + 1 ) );
2397 | newIndices.push( index.getX( i ) );
2398 |
2399 | }
2400 |
2401 | }
2402 |
2403 | }
2404 |
2405 | if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {
2406 |
2407 | console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
2408 |
2409 | }
2410 |
2411 | // build final geometry
2412 |
2413 | var newGeometry = geometry.clone();
2414 | newGeometry.setIndex( newIndices );
2415 |
2416 | return newGeometry;
2417 |
2418 | }
2419 |
2420 | /**
2421 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
2422 | *
2423 | * Creates BufferGeometries from primitives.
2424 | *
2425 | * @param {Array} primitives
2426 | * @return {Promise>}
2427 | */
2428 | GLTFParser.prototype.loadGeometries = function ( primitives ) {
2429 |
2430 | var parser = this;
2431 | var extensions = this.extensions;
2432 | var cache = this.primitiveCache;
2433 |
2434 | function createDracoPrimitive( primitive ) {
2435 |
2436 | return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
2437 | .decodePrimitive( primitive, parser )
2438 | .then( function ( geometry ) {
2439 |
2440 | return addPrimitiveAttributes( geometry, primitive, parser );
2441 |
2442 | } );
2443 |
2444 | }
2445 |
2446 | var pending = [];
2447 |
2448 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2449 |
2450 | var primitive = primitives[ i ];
2451 | var cacheKey = createPrimitiveKey( primitive );
2452 |
2453 | // See if we've already created this geometry
2454 | var cached = cache[ cacheKey ];
2455 |
2456 | if ( cached ) {
2457 |
2458 | // Use the cached geometry if it exists
2459 | pending.push( cached.promise );
2460 |
2461 | } else {
2462 |
2463 | var geometryPromise;
2464 |
2465 | if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {
2466 |
2467 | // Use DRACO geometry if available
2468 | geometryPromise = createDracoPrimitive( primitive );
2469 |
2470 | } else {
2471 |
2472 | // Otherwise create a new geometry
2473 | geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser );
2474 |
2475 | }
2476 |
2477 | // Cache this geometry
2478 | cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };
2479 |
2480 | pending.push( geometryPromise );
2481 |
2482 | }
2483 |
2484 | }
2485 |
2486 | return Promise.all( pending );
2487 |
2488 | };
2489 |
2490 | /**
2491 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
2492 | * @param {number} meshIndex
2493 | * @return {Promise}
2494 | */
2495 | GLTFParser.prototype.loadMesh = function ( meshIndex ) {
2496 |
2497 | var parser = this;
2498 | var json = this.json;
2499 |
2500 | var meshDef = json.meshes[ meshIndex ];
2501 | var primitives = meshDef.primitives;
2502 |
2503 | var pending = [];
2504 |
2505 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2506 |
2507 | var material = primitives[ i ].material === undefined
2508 | ? createDefaultMaterial( this.cache )
2509 | : this.getDependency( 'material', primitives[ i ].material );
2510 |
2511 | pending.push( material );
2512 |
2513 | }
2514 |
2515 | pending.push( parser.loadGeometries( primitives ) );
2516 |
2517 | return Promise.all( pending ).then( function ( results ) {
2518 |
2519 | var materials = results.slice( 0, results.length - 1 );
2520 | var geometries = results[ results.length - 1 ];
2521 |
2522 | var meshes = [];
2523 |
2524 | for ( var i = 0, il = geometries.length; i < il; i ++ ) {
2525 |
2526 | var geometry = geometries[ i ];
2527 | var primitive = primitives[ i ];
2528 |
2529 | // 1. create Mesh
2530 |
2531 | var mesh;
2532 |
2533 | var material = materials[ i ];
2534 |
2535 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
2536 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
2537 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
2538 | primitive.mode === undefined ) {
2539 |
2540 | // .isSkinnedMesh isn't in glTF spec. See .markDefs()
2541 | mesh = meshDef.isSkinnedMesh === true
2542 | ? new THREE.SkinnedMesh( geometry, material )
2543 | : new THREE.Mesh( geometry, material );
2544 |
2545 | if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
2546 |
2547 | // we normalize floating point skin weight array to fix malformed assets (see #15319)
2548 | // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
2549 | mesh.normalizeSkinWeights();
2550 |
2551 | }
2552 |
2553 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
2554 |
2555 | mesh.geometry = toTrianglesDrawMode( mesh.geometry, THREE.TriangleStripDrawMode );
2556 |
2557 | } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
2558 |
2559 | mesh.geometry = toTrianglesDrawMode( mesh.geometry, THREE.TriangleFanDrawMode );
2560 |
2561 | }
2562 |
2563 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
2564 |
2565 | mesh = new THREE.LineSegments( geometry, material );
2566 |
2567 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
2568 |
2569 | mesh = new THREE.Line( geometry, material );
2570 |
2571 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
2572 |
2573 | mesh = new THREE.LineLoop( geometry, material );
2574 |
2575 | } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
2576 |
2577 | mesh = new THREE.Points( geometry, material );
2578 |
2579 | } else {
2580 |
2581 | throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
2582 |
2583 | }
2584 |
2585 | if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
2586 |
2587 | updateMorphTargets( mesh, meshDef );
2588 |
2589 | }
2590 |
2591 | mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
2592 |
2593 | if ( geometries.length > 1 ) mesh.name += '_' + i;
2594 |
2595 | assignExtrasToUserData( mesh, meshDef );
2596 |
2597 | parser.assignFinalMaterial( mesh );
2598 |
2599 | meshes.push( mesh );
2600 |
2601 | }
2602 |
2603 | if ( meshes.length === 1 ) {
2604 |
2605 | return meshes[ 0 ];
2606 |
2607 | }
2608 |
2609 | var group = new THREE.Group();
2610 |
2611 | for ( var i = 0, il = meshes.length; i < il; i ++ ) {
2612 |
2613 | group.add( meshes[ i ] );
2614 |
2615 | }
2616 |
2617 | return group;
2618 |
2619 | } );
2620 |
2621 | };
2622 |
2623 | /**
2624 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
2625 | * @param {number} cameraIndex
2626 | * @return {Promise}
2627 | */
2628 | GLTFParser.prototype.loadCamera = function ( cameraIndex ) {
2629 |
2630 | var camera;
2631 | var cameraDef = this.json.cameras[ cameraIndex ];
2632 | var params = cameraDef[ cameraDef.type ];
2633 |
2634 | if ( ! params ) {
2635 |
2636 | console.warn( 'THREE.GLTFLoader: Missing camera parameters.' );
2637 | return;
2638 |
2639 | }
2640 |
2641 | if ( cameraDef.type === 'perspective' ) {
2642 |
2643 | camera = new THREE.PerspectiveCamera( THREE.MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );
2644 |
2645 | } else if ( cameraDef.type === 'orthographic' ) {
2646 |
2647 | camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar );
2648 |
2649 | }
2650 |
2651 | if ( cameraDef.name ) camera.name = cameraDef.name;
2652 |
2653 | assignExtrasToUserData( camera, cameraDef );
2654 |
2655 | return Promise.resolve( camera );
2656 |
2657 | };
2658 |
2659 | /**
2660 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
2661 | * @param {number} skinIndex
2662 | * @return {Promise