From e8422851c56501868b6c790828184949424b7c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Thu, 25 Jul 2019 21:53:38 -0700 Subject: [PATCH] Updated meshoptimizer. --- 3rdparty/meshoptimizer/.travis.yml | 5 +- 3rdparty/meshoptimizer/CMakeLists.txt | 7 +- 3rdparty/meshoptimizer/Makefile | 18 +- 3rdparty/meshoptimizer/README.md | 66 +- 3rdparty/meshoptimizer/demo/GLTFLoader.js | 3316 ++++++++++++ 3rdparty/meshoptimizer/demo/index.html | 57 +- 3rdparty/meshoptimizer/demo/main.cpp | 136 +- 3rdparty/meshoptimizer/demo/pirate.glb | Bin 0 -> 28780 bytes 3rdparty/meshoptimizer/demo/pirate.optmesh | Bin 36199 -> 0 bytes .../js/{decoder.js => meshopt_decoder.js} | 0 3rdparty/meshoptimizer/src/indexcodec.cpp | 2 +- 3rdparty/meshoptimizer/src/meshoptimizer.h | 36 +- .../meshoptimizer/src/overdrawanalyzer.cpp | 2 +- 3rdparty/meshoptimizer/src/simplifier.cpp | 42 +- 3rdparty/meshoptimizer/src/stripifier.cpp | 55 +- .../meshoptimizer/src/vcacheoptimizer.cpp | 4 +- 3rdparty/meshoptimizer/tools/OptMeshLoader.js | 179 - 3rdparty/meshoptimizer/tools/cgltf.h | 4733 +++++++++++++++++ 3rdparty/meshoptimizer/tools/fast_obj.h | 1397 +++++ 3rdparty/meshoptimizer/tools/gltfpack.cpp | 3493 ++++++++++++ 3rdparty/meshoptimizer/tools/lodviewer.cpp | 114 +- 3rdparty/meshoptimizer/tools/meshencoder.cpp | 255 - 3rdparty/meshoptimizer/tools/meshloader.cpp | 9 + 3rdparty/meshoptimizer/tools/objparser.cpp | 383 -- 3rdparty/meshoptimizer/tools/objparser.h | 42 - 3rdparty/meshoptimizer/tools/vcachetester.cpp | 40 +- 3rdparty/meshoptimizer/tools/vcachetuner.cpp | 337 +- 27 files changed, 13385 insertions(+), 1343 deletions(-) create mode 100644 3rdparty/meshoptimizer/demo/GLTFLoader.js create mode 100644 3rdparty/meshoptimizer/demo/pirate.glb delete mode 100644 3rdparty/meshoptimizer/demo/pirate.optmesh rename 3rdparty/meshoptimizer/js/{decoder.js => meshopt_decoder.js} (100%) delete mode 100644 3rdparty/meshoptimizer/tools/OptMeshLoader.js create mode 100644 3rdparty/meshoptimizer/tools/cgltf.h create mode 100644 3rdparty/meshoptimizer/tools/fast_obj.h create mode 100644 3rdparty/meshoptimizer/tools/gltfpack.cpp delete mode 100644 3rdparty/meshoptimizer/tools/meshencoder.cpp create mode 100644 3rdparty/meshoptimizer/tools/meshloader.cpp delete mode 100644 3rdparty/meshoptimizer/tools/objparser.cpp delete mode 100644 3rdparty/meshoptimizer/tools/objparser.h diff --git a/3rdparty/meshoptimizer/.travis.yml b/3rdparty/meshoptimizer/.travis.yml index eba9651eb..8ba189cbc 100644 --- a/3rdparty/meshoptimizer/.travis.yml +++ b/3rdparty/meshoptimizer/.travis.yml @@ -22,7 +22,8 @@ script: - if [[ "$TRAVIS_COMPILER" == "clang" ]]; then make config=sanitize test; fi - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=debug test; fi - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=release test; fi - - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake -G "$TARGET" -DBUILD_DEMO=ON; fi + - if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make config=release gltfpack; fi + - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake -G "$TARGET" -DBUILD_DEMO=ON -DBUILD_TOOLS=ON; fi - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Debug -verbosity:minimal; fi - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then ./Debug/demo.exe demo/pirate.obj; fi - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Release -verbosity:minimal; fi @@ -34,4 +35,4 @@ after_script: find . -type f -name '*.gcno' -exec gcov -p {} +; sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/ -\1\2/" *.gcov; bash <(curl -s https://codecov.io/bash) -f 'src#*.gcov' -X search; - fi \ No newline at end of file + fi diff --git a/3rdparty/meshoptimizer/CMakeLists.txt b/3rdparty/meshoptimizer/CMakeLists.txt index 423a610e3..cc8a512a4 100644 --- a/3rdparty/meshoptimizer/CMakeLists.txt +++ b/3rdparty/meshoptimizer/CMakeLists.txt @@ -6,6 +6,7 @@ option(BUILD_TOOLS "Build tools" OFF) set(SOURCES src/meshoptimizer.h + src/allocator.cpp src/clusterizer.cpp src/indexcodec.cpp src/indexgenerator.cpp @@ -30,11 +31,11 @@ else() endif() if(BUILD_DEMO) - add_executable(demo demo/main.cpp demo/miniz.cpp demo/tests.cpp tools/objparser.cpp) + add_executable(demo demo/main.cpp demo/miniz.cpp demo/tests.cpp tools/meshloader.cpp) target_link_libraries(demo meshoptimizer) endif() if(BUILD_TOOLS) - add_executable(meshencoder tools/meshencoder.cpp tools/objparser.cpp) - target_link_libraries(meshencoder meshoptimizer) + add_executable(gltfpack tools/gltfpack.cpp tools/meshloader.cpp) + target_link_libraries(gltfpack meshoptimizer) endif() diff --git a/3rdparty/meshoptimizer/Makefile b/3rdparty/meshoptimizer/Makefile index eb9f73bba..9febf7953 100644 --- a/3rdparty/meshoptimizer/Makefile +++ b/3rdparty/meshoptimizer/Makefile @@ -9,13 +9,13 @@ BUILD=build/$(config) LIBRARY_SOURCES=$(wildcard src/*.cpp) LIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o) -DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/objparser.cpp +DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/meshloader.cpp DEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o) -ENCODER_SOURCES=tools/meshencoder.cpp tools/objparser.cpp -ENCODER_OBJECTS=$(ENCODER_SOURCES:%=$(BUILD)/%.o) +GLTFPACK_SOURCES=tools/gltfpack.cpp tools/meshloader.cpp +GLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o) -OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(ENCODER_OBJECTS) +OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS) LIBRARY=$(BUILD)/libmeshoptimizer.a EXECUTABLE=$(BUILD)/meshoptimizer @@ -65,15 +65,15 @@ dev: $(EXECUTABLE) $(EXECUTABLE) -d $(files) format: - clang-format -i $(LIBRARY_SOURCES) $(DEMO_SOURCES) + clang-format -i $(LIBRARY_SOURCES) $(DEMO_SOURCES) $(GLTFPACK_SOURCES) -meshencoder: $(ENCODER_OBJECTS) $(LIBRARY) +gltfpack: $(GLTFPACK_OBJECTS) $(LIBRARY) $(CXX) $^ $(LDFLAGS) -o $@ -js/decoder.js: src/vertexcodec.cpp src/indexcodec.cpp +js/meshopt_decoder.js: src/vertexcodec.cpp src/indexcodec.cpp @mkdir -p build - emcc $(filter %.cpp,$^) -O3 -DNDEBUG -s EXPORTED_FUNCTIONS='["_meshopt_decodeVertexBuffer", "_meshopt_decodeIndexBuffer"]' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=32768 -s TOTAL_MEMORY=65536 -o build/decoder.wasm - sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/decoder.wasm | base64 -w 0)\";#" $@ + emcc $(filter %.cpp,$^) -O3 -DNDEBUG -s EXPORTED_FUNCTIONS='["_meshopt_decodeVertexBuffer", "_meshopt_decodeIndexBuffer"]' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=32768 -s TOTAL_MEMORY=65536 -o build/meshopt_decoder.wasm + sed -i "s#\(var wasm = \)\".*\";#\\1\"$$(cat build/meshopt_decoder.wasm | base64 -w 0)\";#" $@ $(EXECUTABLE): $(DEMO_OBJECTS) $(LIBRARY) $(CXX) $^ $(LDFLAGS) -o $@ diff --git a/3rdparty/meshoptimizer/README.md b/3rdparty/meshoptimizer/README.md index f7c4ab359..afd5afb6a 100644 --- a/3rdparty/meshoptimizer/README.md +++ b/3rdparty/meshoptimizer/README.md @@ -11,10 +11,10 @@ The library provides a C and C++ interface for all algorithms; you can use it fr meshoptimizer is hosted on GitHub; you can download the latest release using git: ``` -git clone -b v0.11 https://github.com/zeux/meshoptimizer.git +git clone -b v0.12 https://github.com/zeux/meshoptimizer.git ``` -Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.11.zip). +Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.12.zip). ## Building @@ -146,7 +146,7 @@ Note that vertex encoding assumes that vertex buffer was optimized for vertex fe Decoding functions are heavily optimized and can directly target write-combined memory; you can expect both decoders to run at 1-3 GB/s on modern desktop CPUs. Compression ratios depend on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized data), index data compression ratio is around 5-6x (compared to raw 16-bit index data). General purpose lossless compressors can further improve on these results. -Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/decoder.js`, that you can use to decode meshes that were encoded offline: +Due to a very high decoding performance and compatibility with general purpose lossless compressors, the compression is a good fit for the use on the web. To that end, meshoptimizer provides both vertex and index decoders compiled into WebAssembly and wrapped into a module with JavaScript-friendly interface, `js/meshopt_decoder.js`, that you can use to decode meshes that were encoded offline: ```js // ready is a Promise that is resolved when (asynchronous) WebAssembly compilation finishes @@ -157,7 +157,7 @@ MeshoptDecoder.decodeVertexBuffer(vertexBuffer, vertexCount, vertexSize, vertexD MeshoptDecoder.decodeIndexBuffer(indexBuffer, indexCount, indexSize, indexData); ``` -A THREE.js mesh loader is provided as an example in `tools/OptMeshLoader.js`; it loads meshes encoded using `tools/meshencoder.cpp`. [Usage example](https://zeuxcg.org/meshoptimizer/demo/) is available, with source in `demo/index.html`. +[Usage example](https://meshoptimizer.org/demo/) is available, with source in `demo/index.html`; this example uses .GLB files encoded using `gltfpack`. ## Triangle strip conversion @@ -170,10 +170,12 @@ This library provides an algorithm for converting a vertex cache optimized trian ```c++ std::vector strip(meshopt_stripifyBound(index_count)); -size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count); +unsigned int restart_index = ~0u; +size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index); ``` -Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. Note that triangle strips require restart index support for rendering; using degenerate triangles to connect strips is not supported. +Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR. +Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance. ## Deinterleaved geometry @@ -208,7 +210,7 @@ This library provides two simplification algorithms that reduce the number of tr The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully; it's recommended to preprocess the index buffer with `meshopt_generateShadowIndexBuffer` to discard any vertex attributes that aren't critical and can be rebuilt later such as normals. -``` +```c++ float threshold = 0.2f; size_t target_index_count = size_t(index_count * threshold); float target_error = 1e-2f; @@ -221,7 +223,7 @@ Target error is an approximate measure of the deviation from the original mesh u The second simplification algorithm, `meshopt_simplifySloppy`, doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter better because it can merge mesh features that are topologically disjoint but spatially close. -``` +```c++ float threshold = 0.2f; size_t target_index_count = size_t(index_count * threshold); @@ -253,12 +255,58 @@ Many algorithms allocate temporary memory to store intermediate results or accel meshopt_setAllocator(malloc, free); ``` -> Note that currently the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. +> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead. Vertex and index decoders (`meshopt_decodeVertexBuffer` and `meshopt_decodeIndexBuffer`) do not allocate memory and work completely within the buffer space provided via arguments. All functions have bounded stack usage that does not exceed 32 KB for any algorithms. +## gltfpack + +meshoptimizer provides many algorithms that can be integrated into a content pipeline or a rendering engine to improve performance. Often integration requires some conscious choices for optimal results - should we optimize for overdraw or not? what should the vertex format be? do we use triangle lists or strips? However, in some cases optimality is not a requirement. + +For engines that want a relatively simple way to load meshes, and would like the meshes to perform reasonably well on target hardware and be reasonably fast to load, meshoptimizer provides a command-line tool, `gltfpack`. `gltfpack` can take an `.obj` or `.gltf` file as an input, and produce a `.gltf` or `.glb` file that is optimized for rendering performance and download size. + +To build gltfpack on Linux/macOS, you can use make: + +``` +make config=release gltfpack +``` + +On Windows (and other platforms), you can use CMake: + +``` +cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TOOLS=ON +cmake --build . --config Release --target gltfpack +``` + +You can then run the resulting command-line binary like this (run it without arguments for a list of options): + +``` +gltfpack -i scene.gltf -o scene.glb +``` + +gltfpack substantially changes the glTF data by optimizing the meshes for vertex fetch and transform cache, quantizing the geometry to reduce the memory consumption and size, merging meshes to reduce the draw call count, quantizing and resampling animations to reduce animation size and simplify playback, and pruning the node tree by removing or collapsing redundant nodes. + +gltfpack can produce two types of output files: + +- By default gltfpack outputs regular `.glb`/`.gltf` files that have been optimized for GPU consumption using various cache optimizers and quantization. These files can be loaded by standard GLTF loaders present in frameworks such as [three.js](https://threejs.org/) (r107+) and [Babylon.js](https://www.babylonjs.com/) (4.1+). +- When using `-c` option, gltfpack outputs compressed `.glb`/`.gltf` files that use meshoptimizer codecs to reduce the download size further. Loading these files requires extending GLTF loaders with custom decompression support; `demo/GLTFLoader.js` contains a custom version of three.js loader that can be used to load them. + +> Note: files produced by gltfpack use `MESHOPT_quantized_geometry` and `MESHOPT_compression` pseudo-extensions; both of these have *not* been standardized yet but eventually will be. glTF validator doesn't recognize these extensions and produces a large number of validation errors because of this. + +When using compressed files, `js/meshopt_decoder.js` needs to be loaded to provide the WebAssembly decoder module like this: + +```js + + +... + +var loader = new THREE.GLTFLoader(); +loader.setMeshoptDecoder(MeshoptDecoder); +loader.load('pirate.glb', function (gltf) { scene.add(gltf.scene); }); +``` + ## License This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md). diff --git a/3rdparty/meshoptimizer/demo/GLTFLoader.js b/3rdparty/meshoptimizer/demo/GLTFLoader.js new file mode 100644 index 000000000..5fffcb06a --- /dev/null +++ b/3rdparty/meshoptimizer/demo/GLTFLoader.js @@ -0,0 +1,3316 @@ +/** + * @author Rich Tibbett / https://github.com/richtr + * @author mrdoob / http://mrdoob.com/ + * @author Tony Parisi / http://www.tonyparisi.com/ + * @author Takahiro / https://github.com/takahirox + * @author Don McCurdy / https://www.donmccurdy.com + */ + +THREE.GLTFLoader = ( function () { + + function GLTFLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + this.dracoLoader = null; + this.meshoptDecoder = null; + + } + + GLTFLoader.prototype = { + + constructor: GLTFLoader, + + crossOrigin: 'anonymous', + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var resourcePath; + + if ( this.resourcePath !== undefined ) { + + resourcePath = this.resourcePath; + + } else if ( this.path !== undefined ) { + + resourcePath = this.path; + + } else { + + resourcePath = THREE.LoaderUtils.extractUrlBase( url ); + + } + + // Tells the LoadingManager to track an extra item, which resolves after + // the model is fully loaded. This means the count of items loaded will + // be incorrect, but ensures manager.onLoad() does not fire early. + scope.manager.itemStart( url ); + + var _onError = function ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + }; + + var loader = new THREE.FileLoader( scope.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + + loader.load( url, function ( data ) { + + try { + + scope.parse( data, resourcePath, function ( gltf ) { + + onLoad( gltf ); + + scope.manager.itemEnd( url ); + + }, _onError ); + + } catch ( e ) { + + _onError( e ); + + } + + }, onProgress, _onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + }, + + setResourcePath: function ( value ) { + + this.resourcePath = value; + return this; + + }, + + setDRACOLoader: function ( dracoLoader ) { + + this.dracoLoader = dracoLoader; + return this; + + }, + + setMeshoptDecoder: function ( decoder ) { + + this.meshoptDecoder = decoder; + return this; + + }, + + parse: function ( data, path, onLoad, onError ) { + + var content; + var extensions = {}; + + if ( typeof data === 'string' ) { + + content = data; + + } else { + + var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); + + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { + + try { + + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + + } catch ( error ) { + + if ( onError ) onError( error ); + return; + + } + + content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + + } else { + + content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) ); + + } + + } + + var json = JSON.parse( content ); + + if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { + + if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) ); + return; + + } + + if ( json.extensionsUsed ) { + + for ( var i = 0; i < json.extensionsUsed.length; ++ i ) { + + var extensionName = json.extensionsUsed[ i ]; + var extensionsRequired = json.extensionsRequired || []; + + switch ( extensionName ) { + + case EXTENSIONS.KHR_LIGHTS_PUNCTUAL: + extensions[ extensionName ] = new GLTFLightsExtension( json ); + break; + + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension( json ); + break; + + case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: + extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension( json ); + break; + + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); + break; + + case EXTENSIONS.MSFT_TEXTURE_DDS: + extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension(); + break; + + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] = new GLTFTextureTransformExtension( json ); + break; + + case EXTENSIONS.MESHOPT_COMPRESSION: + extensions[ extensionName ] = new GLTFMeshoptCompressionExtension( json, this.meshoptDecoder ); + break; + + default: + + if ( extensionsRequired.indexOf( extensionName ) >= 0 ) { + + console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); + + } + + } + + } + + } + + var parser = new GLTFParser( json, extensions, { + + path: path || this.resourcePath || '', + crossOrigin: this.crossOrigin, + manager: this.manager + + } ); + + parser.parse( onLoad, onError ); + + } + + }; + + /* GLTFREGISTRY */ + + function GLTFRegistry() { + + var objects = {}; + + return { + + get: function ( key ) { + + return objects[ key ]; + + }, + + add: function ( key, object ) { + + objects[ key ] = object; + + }, + + remove: function ( key ) { + + delete objects[ key ]; + + }, + + removeAll: function () { + + objects = {}; + + } + + }; + + } + + /*********************************/ + /********** EXTENSIONS ***********/ + /*********************************/ + + var EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', + KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', + KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', + KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', + KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', + MSFT_TEXTURE_DDS: 'MSFT_texture_dds', + MESHOPT_COMPRESSION: 'MESHOPT_compression', + }; + + /** + * DDS Texture Extension + * + * Specification: + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds + * + */ + function GLTFTextureDDSExtension() { + + if ( ! THREE.DDSLoader ) { + + throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' ); + + } + + this.name = EXTENSIONS.MSFT_TEXTURE_DDS; + this.ddsLoader = new THREE.DDSLoader(); + + } + + /** + * Lights Extension + * + * Specification: PENDING + */ + function GLTFLightsExtension( json ) { + + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; + + var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {}; + this.lightDefs = extension.lights || []; + + } + + GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) { + + var lightDef = this.lightDefs[ lightIndex ]; + var lightNode; + + var color = new THREE.Color( 0xffffff ); + if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + + var range = lightDef.range !== undefined ? lightDef.range : 0; + + switch ( lightDef.type ) { + + case 'directional': + lightNode = new THREE.DirectionalLight( color ); + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + case 'point': + lightNode = new THREE.PointLight( color ); + lightNode.distance = range; + break; + + case 'spot': + lightNode = new THREE.SpotLight( color ); + lightNode.distance = range; + // Handle spotlight properties. + lightDef.spot = lightDef.spot || {}; + lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; + lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; + lightNode.angle = lightDef.spot.outerConeAngle; + lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + default: + throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); + + } + + // Some lights (e.g. spot) default to a position other than the origin. Reset the position + // here, because node-level parsing will only override position if explicitly specified. + lightNode.position.set( 0, 0, 0 ); + + lightNode.decay = 2; + + if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + + lightNode.name = lightDef.name || ( 'light_' + lightIndex ); + + return Promise.resolve( lightNode ); + + }; + + /** + * Unlit Materials Extension (pending) + * + * PR: https://github.com/KhronosGroup/glTF/pull/1163 + */ + function GLTFMaterialsUnlitExtension() { + + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; + + } + + GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { + + return THREE.MeshBasicMaterial; + + }; + + GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { + + var pending = []; + + materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + var metallicRoughness = materialDef.pbrMetallicRoughness; + + if ( metallicRoughness ) { + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + var array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + + } + + } + + return Promise.all( pending ); + + }; + + /* BINARY EXTENSION */ + + var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF'; + var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; + var BINARY_EXTENSION_HEADER_LENGTH = 12; + var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; + + function GLTFBinaryExtension( data ) { + + this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.content = null; + this.body = null; + + var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); + + this.header = { + magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), + version: headerView.getUint32( 4, true ), + length: headerView.getUint32( 8, true ) + }; + + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { + + throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); + + } else if ( this.header.version < 2.0 ) { + + throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' ); + + } + + var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + var chunkIndex = 0; + + while ( chunkIndex < chunkView.byteLength ) { + + var chunkLength = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + var chunkType = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { + + var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); + this.content = THREE.LoaderUtils.decodeText( contentArray ); + + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { + + var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; + this.body = data.slice( byteOffset, byteOffset + chunkLength ); + + } + + // Clients must ignore chunks with unknown types. + + chunkIndex += chunkLength; + + } + + if ( this.content === null ) { + + throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); + + } + + } + + /** + * DRACO Mesh Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/pull/874 + */ + function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { + + if ( ! dracoLoader ) { + + throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); + + } + + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; + this.json = json; + this.dracoLoader = dracoLoader; + + } + + GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { + + var json = this.json; + var dracoLoader = this.dracoLoader; + var bufferViewIndex = primitive.extensions[ this.name ].bufferView; + var gltfAttributeMap = primitive.extensions[ this.name ].attributes; + var threeAttributeMap = {}; + var attributeNormalizedMap = {}; + var attributeTypeMap = {}; + + for ( var attributeName in gltfAttributeMap ) { + + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + + } + + for ( attributeName in primitive.attributes ) { + + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + if ( gltfAttributeMap[ attributeName ] !== undefined ) { + + var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + attributeTypeMap[ threeAttributeName ] = componentType; + attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + + } + + } + + return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { + + return new Promise( function ( resolve ) { + + dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { + + for ( var attributeName in geometry.attributes ) { + + var attribute = geometry.attributes[ attributeName ]; + var normalized = attributeNormalizedMap[ attributeName ]; + + if ( normalized !== undefined ) attribute.normalized = normalized; + + } + + resolve( geometry ); + + }, threeAttributeMap, attributeTypeMap ); + + } ); + + } ); + + }; + + /** + * Texture Transform Extension + * + * Specification: + */ + function GLTFTextureTransformExtension() { + + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; + + } + + GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { + + texture = texture.clone(); + + if ( transform.offset !== undefined ) { + + texture.offset.fromArray( transform.offset ); + + } + + if ( transform.rotation !== undefined ) { + + texture.rotation = transform.rotation; + + } + + if ( transform.scale !== undefined ) { + + texture.repeat.fromArray( transform.scale ); + + } + + if ( transform.texCoord !== undefined ) { + + console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); + + } + + texture.needsUpdate = true; + + return texture; + + }; + + /** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + */ + function GLTFMaterialsPbrSpecularGlossinessExtension() { + + return { + + name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, + + specularGlossinessParams: [ + 'color', + 'map', + 'lightMap', + 'lightMapIntensity', + 'aoMap', + 'aoMapIntensity', + 'emissive', + 'emissiveIntensity', + 'emissiveMap', + 'bumpMap', + 'bumpScale', + 'normalMap', + 'displacementMap', + 'displacementScale', + 'displacementBias', + 'specularMap', + 'specular', + 'glossinessMap', + 'glossiness', + 'alphaMap', + 'envMap', + 'envMapIntensity', + 'refractionRatio', + ], + + getMaterialType: function () { + + return THREE.ShaderMaterial; + + }, + + extendParams: function ( materialParams, materialDef, parser ) { + + var pbrSpecularGlossiness = materialDef.extensions[ this.name ]; + + var shader = THREE.ShaderLib[ 'standard' ]; + + var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + var specularMapParsFragmentChunk = [ + '#ifdef USE_SPECULARMAP', + ' uniform sampler2D specularMap;', + '#endif' + ].join( '\n' ); + + var glossinessMapParsFragmentChunk = [ + '#ifdef USE_GLOSSINESSMAP', + ' uniform sampler2D glossinessMap;', + '#endif' + ].join( '\n' ); + + var specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' texelSpecular = sRGBToLinear( texelSpecular );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif' + ].join( '\n' ); + + var glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + '#endif' + ].join( '\n' ); + + var lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb;', + 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', + 'material.specularColor = specularFactor.rgb;', + ].join( '\n' ); + + var fragmentShader = shader.fragmentShader + .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) + .replace( 'uniform float metalness;', 'uniform float glossiness;' ) + .replace( '#include ', specularMapParsFragmentChunk ) + .replace( '#include ', glossinessMapParsFragmentChunk ) + .replace( '#include ', specularMapFragmentChunk ) + .replace( '#include ', glossinessMapFragmentChunk ) + .replace( '#include ', lightPhysicalFragmentChunk ); + + delete uniforms.roughness; + delete uniforms.metalness; + delete uniforms.roughnessMap; + delete uniforms.metalnessMap; + + uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) }; + uniforms.glossiness = { value: 0.5 }; + uniforms.specularMap = { value: null }; + uniforms.glossinessMap = { value: null }; + + materialParams.vertexShader = shader.vertexShader; + materialParams.fragmentShader = fragmentShader; + materialParams.uniforms = uniforms; + materialParams.defines = { 'STANDARD': '' }; + + materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + var pending = []; + + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + + var array = pbrSpecularGlossiness.diffuseFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); + + } + + materialParams.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); + materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + materialParams.specular = new THREE.Color( 1.0, 1.0, 1.0 ); + + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + + materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + + } + + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + + var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + + } + + return Promise.all( pending ); + + }, + + createMaterial: function ( params ) { + + // setup material properties based on MeshStandardMaterial for Specular-Glossiness + + var material = new THREE.ShaderMaterial( { + defines: params.defines, + vertexShader: params.vertexShader, + fragmentShader: params.fragmentShader, + uniforms: params.uniforms, + fog: true, + lights: true, + opacity: params.opacity, + transparent: params.transparent + } ); + + material.isGLTFSpecularGlossinessMaterial = true; + + material.color = params.color; + + material.map = params.map === undefined ? null : params.map; + + material.lightMap = null; + material.lightMapIntensity = 1.0; + + material.aoMap = params.aoMap === undefined ? null : params.aoMap; + material.aoMapIntensity = 1.0; + + material.emissive = params.emissive; + material.emissiveIntensity = 1.0; + material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; + + material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; + material.bumpScale = 1; + + material.normalMap = params.normalMap === undefined ? null : params.normalMap; + + if ( params.normalScale ) material.normalScale = params.normalScale; + + material.displacementMap = null; + material.displacementScale = 1; + material.displacementBias = 0; + + material.specularMap = params.specularMap === undefined ? null : params.specularMap; + material.specular = params.specular; + + material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; + material.glossiness = params.glossiness; + + material.alphaMap = null; + + material.envMap = params.envMap === undefined ? null : params.envMap; + material.envMapIntensity = 1.0; + + material.refractionRatio = 0.98; + + material.extensions.derivatives = true; + + return material; + + }, + + /** + * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can + * copy only properties it knows about or inherits, and misses many properties that would + * normally be defined by MeshStandardMaterial. + * + * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of + * loading a glTF model, but cloning later (e.g. by the user) would require these changes + * AND also updating `.onBeforeRender` on the parent mesh. + * + * @param {THREE.ShaderMaterial} source + * @return {THREE.ShaderMaterial} + */ + cloneMaterial: function ( source ) { + + var target = source.clone(); + + target.isGLTFSpecularGlossinessMaterial = true; + + var params = this.specularGlossinessParams; + + for ( var i = 0, il = params.length; i < il; i ++ ) { + + var value = source[ params[ i ] ]; + target[ params[ i ] ] = ( value && value.isColor ) ? value.clone() : value; + + } + + return target; + + }, + + // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. + refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { + + if ( material.isGLTFSpecularGlossinessMaterial !== true ) { + + return; + + } + + var uniforms = material.uniforms; + var defines = material.defines; + + uniforms.opacity.value = material.opacity; + + uniforms.diffuse.value.copy( material.color ); + uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); + + uniforms.map.value = material.map; + uniforms.specularMap.value = material.specularMap; + uniforms.alphaMap.value = material.alphaMap; + + uniforms.lightMap.value = material.lightMap; + uniforms.lightMapIntensity.value = material.lightMapIntensity; + + uniforms.aoMap.value = material.aoMap; + uniforms.aoMapIntensity.value = material.aoMapIntensity; + + // uv repeat and offset setting priorities + // 1. color map + // 2. specular map + // 3. normal map + // 4. bump map + // 5. alpha map + // 6. emissive map + + var uvScaleMap; + + if ( material.map ) { + + uvScaleMap = material.map; + + } else if ( material.specularMap ) { + + uvScaleMap = material.specularMap; + + } else if ( material.displacementMap ) { + + uvScaleMap = material.displacementMap; + + } else if ( material.normalMap ) { + + uvScaleMap = material.normalMap; + + } else if ( material.bumpMap ) { + + uvScaleMap = material.bumpMap; + + } else if ( material.glossinessMap ) { + + uvScaleMap = material.glossinessMap; + + } else if ( material.alphaMap ) { + + uvScaleMap = material.alphaMap; + + } else if ( material.emissiveMap ) { + + uvScaleMap = material.emissiveMap; + + } + + if ( uvScaleMap !== undefined ) { + + // backwards compatibility + if ( uvScaleMap.isWebGLRenderTarget ) { + + uvScaleMap = uvScaleMap.texture; + + } + + if ( uvScaleMap.matrixAutoUpdate === true ) { + + uvScaleMap.updateMatrix(); + + } + + uniforms.uvTransform.value.copy( uvScaleMap.matrix ); + + } + + if ( material.envMap ) { + + uniforms.envMap.value = material.envMap; + uniforms.envMapIntensity.value = material.envMapIntensity; + + // don't flip CubeTexture envMaps, flip everything else: + // WebGLRenderTargetCube will be flipped for backwards compatibility + // WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture + // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future + uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? - 1 : 1; + + uniforms.reflectivity.value = material.reflectivity; + uniforms.refractionRatio.value = material.refractionRatio; + + uniforms.maxMipLevel.value = renderer.properties.get( material.envMap ).__maxMipLevel; + + } + + uniforms.specular.value.copy( material.specular ); + uniforms.glossiness.value = material.glossiness; + + uniforms.glossinessMap.value = material.glossinessMap; + + uniforms.emissiveMap.value = material.emissiveMap; + uniforms.bumpMap.value = material.bumpMap; + uniforms.normalMap.value = material.normalMap; + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { + + defines.USE_GLOSSINESSMAP = ''; + // set USE_ROUGHNESSMAP to enable vUv + defines.USE_ROUGHNESSMAP = ''; + + } + + if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { + + delete defines.USE_GLOSSINESSMAP; + delete defines.USE_ROUGHNESSMAP; + + } + + } + + }; + + } + + /** + * meshoptimizer Compression Extension + */ + function GLTFMeshoptCompressionExtension( json, meshoptDecoder ) { + + if ( ! meshoptDecoder ) { + + throw new Error( 'THREE.GLTFLoader: No MeshoptDecoder instance provided.' ); + + } + + this.name = EXTENSIONS.MESHOPT_COMPRESSION; + this.json = json; + this.meshoptDecoder = meshoptDecoder; + + } + + GLTFMeshoptCompressionExtension.prototype.decodeBufferView = function ( bufferViewDef, buffer ) { + + var decoder = this.meshoptDecoder; + + return decoder.ready.then( function () { + + var extensionDef = bufferViewDef.extensions[ EXTENSIONS.MESHOPT_COMPRESSION ]; + + var byteOffset = bufferViewDef.byteOffset || 0; + var byteLength = bufferViewDef.byteLength || 0; + + var count = extensionDef.count; + var stride = extensionDef.byteStride; + + var result = new ArrayBuffer(count * stride); + var source = new Uint8Array(buffer, byteOffset, byteLength); + + switch ( extensionDef.mode ) { + + case 0: + decoder.decodeVertexBuffer(new Uint8Array(result), count, stride, source); + break; + + case 1: + decoder.decodeIndexBuffer(new Uint8Array(result), count, stride, source); + break; + + default: + throw new Error( 'THREE.GLTFLoader: Unrecognized meshopt compression mode.' ); + + } + + return result; + + } ); + + } + + /*********************************/ + /********** INTERPOLATION ********/ + /*********************************/ + + // Spline Interpolation + // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation + function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype ); + GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; + + GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) { + + // Copies a sample value to the result buffer. See description of glTF + // CUBICSPLINE values layout in interpolate_() function below. + + var result = this.resultBuffer, + values = this.sampleValues, + valueSize = this.valueSize, + offset = index * valueSize * 3 + valueSize; + + for ( var i = 0; i !== valueSize; i ++ ) { + + result[ i ] = values[ offset + i ]; + + } + + return result; + + }; + + GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + + GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + + GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { + + var result = this.resultBuffer; + var values = this.sampleValues; + var stride = this.valueSize; + + var stride2 = stride * 2; + var stride3 = stride * 3; + + var td = t1 - t0; + + var p = ( t - t0 ) / td; + var pp = p * p; + var ppp = pp * p; + + var offset1 = i1 * stride3; + var offset0 = offset1 - stride3; + + var s2 = - 2 * ppp + 3 * pp; + var s3 = ppp - pp; + var s0 = 1 - s2; + var s1 = s3 - pp + p; + + // Layout of keyframe output values for CUBICSPLINE animations: + // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] + for ( var i = 0; i !== stride; i ++ ) { + + var p0 = values[ offset0 + i + stride ]; // splineVertex_k + var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) + var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 + var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) + + result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; + + } + + return result; + + }; + + /*********************************/ + /********** INTERNALS ************/ + /*********************************/ + + /* CONSTANTS */ + + var WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + POINTS: 0, + LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123 + }; + + var WEBGL_TYPE = { + 5126: Number, + //35674: THREE.Matrix2, + 35675: THREE.Matrix3, + 35676: THREE.Matrix4, + 35664: THREE.Vector2, + 35665: THREE.Vector3, + 35666: THREE.Vector4, + 35678: THREE.Texture + }; + + var WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array + }; + + var WEBGL_FILTERS = { + 9728: THREE.NearestFilter, + 9729: THREE.LinearFilter, + 9984: THREE.NearestMipMapNearestFilter, + 9985: THREE.LinearMipMapNearestFilter, + 9986: THREE.NearestMipMapLinearFilter, + 9987: THREE.LinearMipMapLinearFilter + }; + + var WEBGL_WRAPPINGS = { + 33071: THREE.ClampToEdgeWrapping, + 33648: THREE.MirroredRepeatWrapping, + 10497: THREE.RepeatWrapping + }; + + var WEBGL_SIDES = { + 1028: THREE.BackSide, // Culling front + 1029: THREE.FrontSide // Culling back + //1032: THREE.NoSide // Culling front and back, what to do? + }; + + var WEBGL_DEPTH_FUNCS = { + 512: THREE.NeverDepth, + 513: THREE.LessDepth, + 514: THREE.EqualDepth, + 515: THREE.LessEqualDepth, + 516: THREE.GreaterEqualDepth, + 517: THREE.NotEqualDepth, + 518: THREE.GreaterEqualDepth, + 519: THREE.AlwaysDepth + }; + + var WEBGL_BLEND_EQUATIONS = { + 32774: THREE.AddEquation, + 32778: THREE.SubtractEquation, + 32779: THREE.ReverseSubtractEquation + }; + + var WEBGL_BLEND_FUNCS = { + 0: THREE.ZeroFactor, + 1: THREE.OneFactor, + 768: THREE.SrcColorFactor, + 769: THREE.OneMinusSrcColorFactor, + 770: THREE.SrcAlphaFactor, + 771: THREE.OneMinusSrcAlphaFactor, + 772: THREE.DstAlphaFactor, + 773: THREE.OneMinusDstAlphaFactor, + 774: THREE.DstColorFactor, + 775: THREE.OneMinusDstColorFactor, + 776: THREE.SrcAlphaSaturateFactor + // The followings are not supported by Three.js yet + //32769: CONSTANT_COLOR, + //32770: ONE_MINUS_CONSTANT_COLOR, + //32771: CONSTANT_ALPHA, + //32772: ONE_MINUS_CONSTANT_COLOR + }; + + var WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 + }; + + var ATTRIBUTES = { + POSITION: 'position', + NORMAL: 'normal', + TANGENT: 'tangent', + TEXCOORD_0: 'uv', + TEXCOORD_1: 'uv2', + COLOR_0: 'color', + WEIGHTS_0: 'skinWeight', + JOINTS_0: 'skinIndex', + }; + + var PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion', + weights: 'morphTargetInfluences' + }; + + var INTERPOLATION = { + CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each + // keyframe track will be initialized with a default interpolation type, then modified. + LINEAR: THREE.InterpolateLinear, + STEP: THREE.InterpolateDiscrete + }; + + var STATES_ENABLES = { + 2884: 'CULL_FACE', + 2929: 'DEPTH_TEST', + 3042: 'BLEND', + 3089: 'SCISSOR_TEST', + 32823: 'POLYGON_OFFSET_FILL', + 32926: 'SAMPLE_ALPHA_TO_COVERAGE' + }; + + var ALPHA_MODES = { + OPAQUE: 'OPAQUE', + MASK: 'MASK', + BLEND: 'BLEND' + }; + + var MIME_TYPE_FORMATS = { + 'image/png': THREE.RGBAFormat, + 'image/jpeg': THREE.RGBFormat + }; + + /* UTILITY FUNCTIONS */ + + function resolveURL( url, path ) { + + // Invalid URL + if ( typeof url !== 'string' || url === '' ) return ''; + + // Absolute URL http://,https://,// + if ( /^(https?:)?\/\//i.test( url ) ) return url; + + // Data URI + if ( /^data:.*,.*$/i.test( url ) ) return url; + + // Blob URL + if ( /^blob:.*$/i.test( url ) ) return url; + + // Relative URL + return path + url; + + } + + var defaultMaterial; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material + */ + function createDefaultMaterial() { + + defaultMaterial = defaultMaterial || new THREE.MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: THREE.FrontSide + } ); + + return defaultMaterial; + + } + + function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { + + // Add unknown glTF extensions to an object's userData. + + for ( var name in objectDef.extensions ) { + + if ( knownExtensions[ name ] === undefined ) { + + object.userData.gltfExtensions = object.userData.gltfExtensions || {}; + object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; + + } + + } + + } + + /** + * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object + * @param {GLTF.definition} gltfDef + */ + function assignExtrasToUserData( object, gltfDef ) { + + if ( gltfDef.extras !== undefined ) { + + if ( typeof gltfDef.extras === 'object' ) { + + Object.assign( object.userData, gltfDef.extras ); + + } else { + + console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); + + } + + } + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets + * + * @param {THREE.BufferGeometry} geometry + * @param {Array} targets + * @param {GLTFParser} parser + * @return {Promise} + */ + function addMorphTargets( geometry, targets, parser ) { + + var hasMorphPosition = false; + var hasMorphNormal = false; + + for ( var i = 0, il = targets.length; i < il; i ++ ) { + + var target = targets[ i ]; + + if ( target.POSITION !== undefined ) hasMorphPosition = true; + if ( target.NORMAL !== undefined ) hasMorphNormal = true; + + if ( hasMorphPosition && hasMorphNormal ) break; + + } + + if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry ); + + var pendingPositionAccessors = []; + var pendingNormalAccessors = []; + + for ( var i = 0, il = targets.length; i < il; i ++ ) { + + var target = targets[ i ]; + + if ( hasMorphPosition ) { + + var pendingAccessor = target.POSITION !== undefined + ? parser.getDependency( 'accessor', target.POSITION ) + : geometry.attributes.position; + + pendingPositionAccessors.push( pendingAccessor ); + + } + + if ( hasMorphNormal ) { + + var pendingAccessor = target.NORMAL !== undefined + ? parser.getDependency( 'accessor', target.NORMAL ) + : geometry.attributes.normal; + + pendingNormalAccessors.push( pendingAccessor ); + + } + + } + + return Promise.all( [ + Promise.all( pendingPositionAccessors ), + Promise.all( pendingNormalAccessors ) + ] ).then( function ( accessors ) { + + var morphPositions = accessors[ 0 ]; + var morphNormals = accessors[ 1 ]; + + // Clone morph target accessors before modifying them. + + for ( var i = 0, il = morphPositions.length; i < il; i ++ ) { + + if ( geometry.attributes.position === morphPositions[ i ] ) continue; + + morphPositions[ i ] = cloneBufferAttribute( morphPositions[ i ] ); + + } + + for ( var i = 0, il = morphNormals.length; i < il; i ++ ) { + + if ( geometry.attributes.normal === morphNormals[ i ] ) continue; + + morphNormals[ i ] = cloneBufferAttribute( morphNormals[ i ] ); + + } + + for ( var i = 0, il = targets.length; i < il; i ++ ) { + + var target = targets[ i ]; + var attributeName = 'morphTarget' + i; + + if ( hasMorphPosition ) { + + // Three.js morph position is absolute value. The formula is + // basePosition + // + weight0 * ( morphPosition0 - basePosition ) + // + weight1 * ( morphPosition1 - basePosition ) + // ... + // while the glTF one is relative + // basePosition + // + weight0 * glTFmorphPosition0 + // + weight1 * glTFmorphPosition1 + // ... + // then we need to convert from relative to absolute here. + + if ( target.POSITION !== undefined ) { + + var positionAttribute = morphPositions[ i ]; + positionAttribute.name = attributeName; + + var position = geometry.attributes.position; + + for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) { + + positionAttribute.setXYZ( + j, + positionAttribute.getX( j ) + position.getX( j ), + positionAttribute.getY( j ) + position.getY( j ), + positionAttribute.getZ( j ) + position.getZ( j ) + ); + + } + + } + + } + + if ( hasMorphNormal ) { + + // see target.POSITION's comment + + if ( target.NORMAL !== undefined ) { + + var normalAttribute = morphNormals[ i ]; + normalAttribute.name = attributeName; + + var normal = geometry.attributes.normal; + + for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) { + + normalAttribute.setXYZ( + j, + normalAttribute.getX( j ) + normal.getX( j ), + normalAttribute.getY( j ) + normal.getY( j ), + normalAttribute.getZ( j ) + normal.getZ( j ) + ); + + } + + } + + } + + } + + if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; + if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + + return geometry; + + } ); + + } + + /** + * @param {THREE.Mesh} mesh + * @param {GLTF.Mesh} meshDef + */ + function updateMorphTargets( mesh, meshDef ) { + + mesh.updateMorphTargets(); + + if ( meshDef.weights !== undefined ) { + + for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { + + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; + + } + + } + + // .extras has user-defined data, so check that .extras.targetNames is an array. + if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { + + var targetNames = meshDef.extras.targetNames; + + if ( mesh.morphTargetInfluences.length === targetNames.length ) { + + mesh.morphTargetDictionary = {}; + + for ( var i = 0, il = targetNames.length; i < il; i ++ ) { + + mesh.morphTargetDictionary[ targetNames[ i ] ] = i; + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); + + } + + } + + } + function isObjectEqual( a, b ) { + + if ( Object.keys( a ).length !== Object.keys( b ).length ) return false; + + for ( var key in a ) { + + if ( a[ key ] !== b[ key ] ) return false; + + } + + return true; + + } + + function createPrimitiveKey( primitiveDef ) { + + var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; + var geometryKey; + + if ( dracoExtension ) { + + geometryKey = 'draco:' + dracoExtension.bufferView + + ':' + dracoExtension.indices + + ':' + createAttributesKey( dracoExtension.attributes ); + + } else { + + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; + + } + + return geometryKey; + + } + + function createAttributesKey( attributes ) { + + var attributesKey = ''; + + var keys = Object.keys( attributes ).sort(); + + for ( var i = 0, il = keys.length; i < il; i ++ ) { + + attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; + + } + + return attributesKey; + + } + + function cloneBufferAttribute( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) { + + var count = attribute.count; + var itemSize = attribute.itemSize; + var array = attribute.array.slice( 0, count * itemSize ); + + for ( var i = 0, j = 0; i < count; ++ i ) { + + array[ j ++ ] = attribute.getX( i ); + if ( itemSize >= 2 ) array[ j ++ ] = attribute.getY( i ); + if ( itemSize >= 3 ) array[ j ++ ] = attribute.getZ( i ); + if ( itemSize >= 4 ) array[ j ++ ] = attribute.getW( i ); + + } + + return new THREE.BufferAttribute( array, itemSize, attribute.normalized ); + + } + + return attribute.clone(); + + } + + /* GLTF PARSER */ + + function GLTFParser( json, extensions, options ) { + + this.json = json || {}; + this.extensions = extensions || {}; + this.options = options || {}; + + // loader object cache + this.cache = new GLTFRegistry(); + + // BufferGeometry caching + this.primitiveCache = {}; + + this.textureLoader = new THREE.TextureLoader( this.options.manager ); + this.textureLoader.setCrossOrigin( this.options.crossOrigin ); + + this.fileLoader = new THREE.FileLoader( this.options.manager ); + this.fileLoader.setResponseType( 'arraybuffer' ); + + } + + GLTFParser.prototype.parse = function ( onLoad, onError ) { + + var parser = this; + var json = this.json; + var extensions = this.extensions; + + // Clear the loader cache + this.cache.removeAll(); + + // Mark the special nodes/meshes in json for efficient parse + this.markDefs(); + + Promise.all( [ + + this.getDependencies( 'scene' ), + this.getDependencies( 'animation' ), + this.getDependencies( 'camera' ), + + ] ).then( function ( dependencies ) { + + var result = { + scene: dependencies[ 0 ][ json.scene || 0 ], + scenes: dependencies[ 0 ], + animations: dependencies[ 1 ], + cameras: dependencies[ 2 ], + asset: json.asset, + parser: parser, + userData: {} + }; + + addUnknownExtensionsToUserData( extensions, result, json ); + + onLoad( result ); + + } ).catch( onError ); + + }; + + /** + * Marks the special nodes/meshes in json for efficient parse. + */ + GLTFParser.prototype.markDefs = function () { + + var nodeDefs = this.json.nodes || []; + var skinDefs = this.json.skins || []; + var meshDefs = this.json.meshes || []; + + var meshReferences = {}; + var meshUses = {}; + + // Nothing in the node definition indicates whether it is a Bone or an + // Object3D. Use the skins' joint references to mark bones. + for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { + + var joints = skinDefs[ skinIndex ].joints; + + for ( var i = 0, il = joints.length; i < il; i ++ ) { + + nodeDefs[ joints[ i ] ].isBone = true; + + } + + } + + // Meshes can (and should) be reused by multiple nodes in a glTF asset. To + // avoid having more than one THREE.Mesh with the same name, count + // references and rename instances below. + // + // Example: CesiumMilkTruck sample model reuses "Wheel" meshes. + for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + var nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.mesh !== undefined ) { + + if ( meshReferences[ nodeDef.mesh ] === undefined ) { + + meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0; + + } + + meshReferences[ nodeDef.mesh ] ++; + + // Nothing in the mesh definition indicates whether it is + // a SkinnedMesh or Mesh. Use the node's mesh reference + // to mark SkinnedMesh if node has skin. + if ( nodeDef.skin !== undefined ) { + + meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; + + } + + } + + } + + this.json.meshReferences = meshReferences; + this.json.meshUses = meshUses; + + }; + + /** + * Requests the specified dependency asynchronously, with caching. + * @param {string} type + * @param {number} index + * @return {Promise} + */ + GLTFParser.prototype.getDependency = function ( type, index ) { + + var cacheKey = type + ':' + index; + var dependency = this.cache.get( cacheKey ); + + if ( ! dependency ) { + + switch ( type ) { + + case 'scene': + dependency = this.loadScene( index ); + break; + + case 'node': + dependency = this.loadNode( index ); + break; + + case 'mesh': + dependency = this.loadMesh( index ); + break; + + case 'accessor': + dependency = this.loadAccessor( index ); + break; + + case 'bufferView': + dependency = this.loadBufferView( index ); + break; + + case 'buffer': + dependency = this.loadBuffer( index ); + break; + + case 'material': + dependency = this.loadMaterial( index ); + break; + + case 'texture': + dependency = this.loadTexture( index ); + break; + + case 'skin': + dependency = this.loadSkin( index ); + break; + + case 'animation': + dependency = this.loadAnimation( index ); + break; + + case 'camera': + dependency = this.loadCamera( index ); + break; + + case 'light': + dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index ); + break; + + default: + throw new Error( 'Unknown type: ' + type ); + + } + + this.cache.add( cacheKey, dependency ); + + } + + return dependency; + + }; + + /** + * Requests all dependencies of the specified type asynchronously, with caching. + * @param {string} type + * @return {Promise>} + */ + GLTFParser.prototype.getDependencies = function ( type ) { + + var dependencies = this.cache.get( type ); + + if ( ! dependencies ) { + + var parser = this; + var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; + + dependencies = Promise.all( defs.map( function ( def, index ) { + + return parser.getDependency( type, index ); + + } ) ); + + this.cache.add( type, dependencies ); + + } + + return dependencies; + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferIndex + * @return {Promise} + */ + GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { + + var bufferDef = this.json.buffers[ bufferIndex ]; + var loader = this.fileLoader; + + if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { + + throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); + + } + + // If present, GLB container is required to be the first buffer. + if ( bufferDef.uri === undefined && bufferIndex === 0 ) { + + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); + + } + + var options = this.options; + + return new Promise( function ( resolve, reject ) { + + loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { + + reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); + + } ); + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferViewIndex + * @return {Promise} + */ + GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { + + var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; + + if ( bufferViewDef.extensions && bufferViewDef.extensions[ EXTENSIONS.MESHOPT_COMPRESSION ] ) { + + var extension = this.extensions[ EXTENSIONS.MESHOPT_COMPRESSION ]; + + return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { + + return extension.decodeBufferView( bufferViewDef, buffer ); + + } ); + + } else { + + return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { + + var byteLength = bufferViewDef.byteLength || 0; + var byteOffset = bufferViewDef.byteOffset || 0; + return buffer.slice( byteOffset, byteOffset + byteLength ); + + } ); + + } + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors + * @param {number} accessorIndex + * @return {Promise} + */ + GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { + + var parser = this; + var json = this.json; + + var accessorDef = this.json.accessors[ accessorIndex ]; + + if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { + + // Ignore empty accessors, which may be used to declare runtime + // information about attributes coming from another source (e.g. Draco + // compression extension). + return Promise.resolve( null ); + + } + + var pendingBufferViews = []; + + if ( accessorDef.bufferView !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); + + } else { + + pendingBufferViews.push( null ); + + } + + if ( accessorDef.sparse !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); + + } + + return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { + + var bufferView = bufferViews[ 0 ]; + + var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. + var elementBytes = TypedArray.BYTES_PER_ELEMENT; + var itemBytes = elementBytes * itemSize; + var byteOffset = accessorDef.byteOffset || 0; + var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; + var normalized = accessorDef.normalized === true; + var array, bufferAttribute; + + // The buffer is not interleaved if the stride is the item size in bytes. + if ( byteStride && byteStride !== itemBytes ) { + + // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer + // This makes sure that IBA.count reflects accessor.count properly + var ibSlice = Math.floor( byteOffset / byteStride ); + var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; + var ib = parser.cache.get( ibCacheKey ); + + if ( ! ib ) { + + array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); + + // Integer parameters to IB/IBA are in array elements, not bytes. + ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes ); + + parser.cache.add( ibCacheKey, ib ); + + } + + bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, (byteOffset % byteStride) / elementBytes, normalized ); + + } else { + + if ( bufferView === null ) { + + array = new TypedArray( accessorDef.count * itemSize ); + + } else { + + array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); + + } + + bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized ); + + } + + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors + if ( accessorDef.sparse !== undefined ) { + + var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; + + var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; + var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; + + var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); + var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); + + if ( bufferView !== null ) { + + // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. + bufferAttribute.setArray( bufferAttribute.array.slice() ); + + } + + for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { + + var index = sparseIndices[ i ]; + + bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); + if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); + if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); + if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); + if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); + + } + + } + + return bufferAttribute; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + GLTFParser.prototype.loadTexture = function ( textureIndex ) { + + var parser = this; + var json = this.json; + var options = this.options; + var textureLoader = this.textureLoader; + + var URL = window.URL || window.webkitURL; + + var textureDef = json.textures[ textureIndex ]; + + var textureExtensions = textureDef.extensions || {}; + + var source; + + if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { + + source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ]; + + } else { + + source = json.images[ textureDef.source ]; + + } + + var sourceURI = source.uri; + var isObjectURL = false; + + if ( source.bufferView !== undefined ) { + + // Load binary image data from bufferView, if provided. + + sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { + + isObjectURL = true; + var blob = new Blob( [ bufferView ], { type: source.mimeType } ); + sourceURI = URL.createObjectURL( blob ); + return sourceURI; + + } ); + + } + + return Promise.resolve( sourceURI ).then( function ( sourceURI ) { + + // Load Texture resource. + + var loader = THREE.Loader.Handlers.get( sourceURI ); + + if ( ! loader ) { + + loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] + ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader + : textureLoader; + + } + + return new Promise( function ( resolve, reject ) { + + loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); + + } ); + + } ).then( function ( texture ) { + + // Clean up resources and configure Texture. + + if ( isObjectURL === true ) { + + URL.revokeObjectURL( sourceURI ); + + } + + texture.flipY = false; + + if ( textureDef.name !== undefined ) texture.name = textureDef.name; + + // Ignore unknown mime types, like DDS files. + if ( source.mimeType in MIME_TYPE_FORMATS ) { + + texture.format = MIME_TYPE_FORMATS[ source.mimeType ]; + + } + + var samplers = json.samplers || {}; + var sampler = samplers[ textureDef.sampler ] || {}; + + texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; + texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter; + texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; + texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; + + return texture; + + } ); + + }; + + /** + * Asynchronously assigns a texture to the given material parameters. + * @param {Object} materialParams + * @param {string} mapName + * @param {Object} mapDef + * @return {Promise} + */ + GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) { + + var parser = this; + + return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { + + if ( ! texture.isCompressedTexture ) { + + switch ( mapName ) { + + case 'aoMap': + case 'emissiveMap': + case 'metalnessMap': + case 'normalMap': + case 'roughnessMap': + texture.format = THREE.RGBFormat; + break; + + } + + } + + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { + + var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; + + if ( transform ) { + + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + + } + + } + + materialParams[ mapName ] = texture; + + } ); + + }; + + /** + * Assigns final material to a Mesh, Line, or Points instance. The instance + * already has a material (generated from the glTF material options alone) + * but reuse of the same glTF material may require multiple threejs materials + * to accomodate different primitive types, defines, etc. New materials will + * be created if necessary, and reused from a cache. + * @param {THREE.Object3D} mesh Mesh, Line, or Points instance. + */ + GLTFParser.prototype.assignFinalMaterial = function ( mesh ) { + + var geometry = mesh.geometry; + var material = mesh.material; + var extensions = this.extensions; + + var useVertexTangents = geometry.attributes.tangent !== undefined; + var useVertexColors = geometry.attributes.color !== undefined; + var useFlatShading = geometry.attributes.normal === undefined; + var useSkinning = mesh.isSkinnedMesh === true; + var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; + var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; + + if ( mesh.isPoints ) { + + var cacheKey = 'PointsMaterial:' + material.uuid; + + var pointsMaterial = this.cache.get( cacheKey ); + + if ( ! pointsMaterial ) { + + pointsMaterial = new THREE.PointsMaterial(); + THREE.Material.prototype.copy.call( pointsMaterial, material ); + pointsMaterial.color.copy( material.color ); + pointsMaterial.map = material.map; + pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet + + this.cache.add( cacheKey, pointsMaterial ); + + } + + material = pointsMaterial; + + } else if ( mesh.isLine ) { + + var cacheKey = 'LineBasicMaterial:' + material.uuid; + + var lineMaterial = this.cache.get( cacheKey ); + + if ( ! lineMaterial ) { + + lineMaterial = new THREE.LineBasicMaterial(); + THREE.Material.prototype.copy.call( lineMaterial, material ); + lineMaterial.color.copy( material.color ); + lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet + + this.cache.add( cacheKey, lineMaterial ); + + } + + material = lineMaterial; + + } + + // Clone the material if it will be modified + if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { + + var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; + + if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; + if ( useSkinning ) cacheKey += 'skinning:'; + if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; + if ( useVertexColors ) cacheKey += 'vertex-colors:'; + if ( useFlatShading ) cacheKey += 'flat-shading:'; + if ( useMorphTargets ) cacheKey += 'morph-targets:'; + if ( useMorphNormals ) cacheKey += 'morph-normals:'; + + var cachedMaterial = this.cache.get( cacheKey ); + + if ( ! cachedMaterial ) { + + cachedMaterial = material.isGLTFSpecularGlossinessMaterial + ? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material ) + : material.clone(); + + if ( useSkinning ) cachedMaterial.skinning = true; + if ( useVertexTangents ) cachedMaterial.vertexTangents = true; + if ( useVertexColors ) cachedMaterial.vertexColors = THREE.VertexColors; + if ( useFlatShading ) cachedMaterial.flatShading = true; + if ( useMorphTargets ) cachedMaterial.morphTargets = true; + if ( useMorphNormals ) cachedMaterial.morphNormals = true; + + this.cache.add( cacheKey, cachedMaterial ); + + } + + material = cachedMaterial; + + } + + // workarounds for mesh and geometry + + if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { + + console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); + geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); + + } + + if ( material.isGLTFSpecularGlossinessMaterial ) { + + // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update + mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; + + } + + mesh.material = material; + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials + * @param {number} materialIndex + * @return {Promise} + */ + GLTFParser.prototype.loadMaterial = function ( materialIndex ) { + + var parser = this; + var json = this.json; + var extensions = this.extensions; + var materialDef = json.materials[ materialIndex ]; + + var materialType; + var materialParams = {}; + var materialExtensions = materialDef.extensions || {}; + + var pending = []; + + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { + + var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + materialType = sgExtension.getMaterialType(); + pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); + + } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { + + var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; + materialType = kmuExtension.getMaterialType(); + pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); + + } else { + + // Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material + + materialType = THREE.MeshStandardMaterial; + + var metallicRoughness = materialDef.pbrMetallicRoughness || {}; + + materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + var array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + + } + + materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; + materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; + + if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); + pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); + + } + + } + + if ( materialDef.doubleSided === true ) { + + materialParams.side = THREE.DoubleSide; + + } + + var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + + if ( alphaMode === ALPHA_MODES.BLEND ) { + + materialParams.transparent = true; + + } else { + + materialParams.transparent = false; + + if ( alphaMode === ALPHA_MODES.MASK ) { + + materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; + + } + + } + + if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + + materialParams.normalScale = new THREE.Vector2( 1, 1 ); + + if ( materialDef.normalTexture.scale !== undefined ) { + + materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); + + } + + } + + if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); + + if ( materialDef.occlusionTexture.strength !== undefined ) { + + materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; + + } + + } + + if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) { + + materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor ); + + } + + if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); + + } + + return Promise.all( pending ).then( function () { + + var material; + + if ( materialType === THREE.ShaderMaterial ) { + + material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + + } else { + + material = new materialType( materialParams ); + + } + + if ( materialDef.name !== undefined ) material.name = materialDef.name; + + // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. + if ( material.map ) material.map.encoding = THREE.sRGBEncoding; + if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding; + if ( material.specularMap ) material.specularMap.encoding = THREE.sRGBEncoding; + + assignExtrasToUserData( material, materialDef ); + + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); + + return material; + + } ); + + }; + + /** + * @param {THREE.BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + * @return {Promise} + */ + function addPrimitiveAttributes( geometry, primitiveDef, parser ) { + + var attributes = primitiveDef.attributes; + + var pending = []; + + function assignAttributeAccessor( accessorIndex, attributeName ) { + + return parser.getDependency( 'accessor', accessorIndex ) + .then( function ( accessor ) { + + geometry.addAttribute( attributeName, accessor ); + + } ); + + } + + for ( var gltfAttributeName in attributes ) { + + var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + + // Skip attributes already provided by e.g. Draco extension. + if ( threeAttributeName in geometry.attributes ) continue; + + pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); + + } + + if ( primitiveDef.indices !== undefined && ! geometry.index ) { + + var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { + + geometry.setIndex( accessor ); + + } ); + + pending.push( accessor ); + + } + + assignExtrasToUserData( geometry, primitiveDef ); + + return Promise.all( pending ).then( function () { + + return primitiveDef.targets !== undefined + ? addMorphTargets( geometry, primitiveDef.targets, parser ) + : geometry; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry + * + * Creates BufferGeometries from primitives. + * + * @param {Array} primitives + * @return {Promise>} + */ + GLTFParser.prototype.loadGeometries = function ( primitives ) { + + var parser = this; + var extensions = this.extensions; + var cache = this.primitiveCache; + + function createDracoPrimitive( primitive ) { + + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] + .decodePrimitive( primitive, parser ) + .then( function ( geometry ) { + + return addPrimitiveAttributes( geometry, primitive, parser ); + + } ); + + } + + var pending = []; + + for ( var i = 0, il = primitives.length; i < il; i ++ ) { + + var primitive = primitives[ i ]; + var cacheKey = createPrimitiveKey( primitive ); + + // See if we've already created this geometry + var cached = cache[ cacheKey ]; + + if ( cached ) { + + // Use the cached geometry if it exists + pending.push( cached.promise ); + + } else { + + var geometryPromise; + + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { + + // Use DRACO geometry if available + geometryPromise = createDracoPrimitive( primitive ); + + } else { + + // Otherwise create a new geometry + geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser ); + + } + + // Cache this geometry + cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; + + pending.push( geometryPromise ); + + } + + } + + return Promise.all( pending ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes + * @param {number} meshIndex + * @return {Promise} + */ + GLTFParser.prototype.loadMesh = function ( meshIndex ) { + + var parser = this; + var json = this.json; + var extensions = this.extensions; + + var meshDef = json.meshes[ meshIndex ]; + var primitives = meshDef.primitives; + + var pending = []; + + for ( var i = 0, il = primitives.length; i < il; i ++ ) { + + var material = primitives[ i ].material === undefined + ? createDefaultMaterial() + : this.getDependency( 'material', primitives[ i ].material ); + + pending.push( material ); + + } + + return Promise.all( pending ).then( function ( originalMaterials ) { + + return parser.loadGeometries( primitives ).then( function ( geometries ) { + + var meshes = []; + + for ( var i = 0, il = geometries.length; i < il; i ++ ) { + + var geometry = geometries[ i ]; + var primitive = primitives[ i ]; + + // 1. create Mesh + + var mesh; + + var material = originalMaterials[ i ]; + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || + primitive.mode === undefined ) { + + // .isSkinnedMesh isn't in glTF spec. See .markDefs() + mesh = meshDef.isSkinnedMesh === true + ? new THREE.SkinnedMesh( geometry, material ) + : new THREE.Mesh( geometry, material ); + + if ( mesh.isSkinnedMesh === true && !mesh.geometry.attributes.skinWeight.normalized ) { + + // we normalize floating point skin weight array to fix malformed assets (see #15319) + // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs + mesh.normalizeSkinWeights(); + + } + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { + + mesh.drawMode = THREE.TriangleStripDrawMode; + + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { + + mesh.drawMode = THREE.TriangleFanDrawMode; + + } + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + + mesh = new THREE.LineSegments( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { + + mesh = new THREE.Line( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { + + mesh = new THREE.LineLoop( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { + + mesh = new THREE.Points( geometry, material ); + + } else { + + throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); + + } + + if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { + + updateMorphTargets( mesh, meshDef ); + + } + + mesh.name = meshDef.name || ( 'mesh_' + meshIndex ); + + if ( geometries.length > 1 ) mesh.name += '_' + i; + + assignExtrasToUserData( mesh, meshDef ); + + parser.assignFinalMaterial( mesh ); + + meshes.push( mesh ); + + } + + if ( meshes.length === 1 ) { + + return meshes[ 0 ]; + + } + + var group = new THREE.Group(); + + for ( var i = 0, il = meshes.length; i < il; i ++ ) { + + group.add( meshes[ i ] ); + + } + + return group; + + } ); + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras + * @param {number} cameraIndex + * @return {Promise} + */ + GLTFParser.prototype.loadCamera = function ( cameraIndex ) { + + var camera; + var cameraDef = this.json.cameras[ cameraIndex ]; + var params = cameraDef[ cameraDef.type ]; + + if ( ! params ) { + + console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); + return; + + } + + if ( cameraDef.type === 'perspective' ) { + + camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); + + } else if ( cameraDef.type === 'orthographic' ) { + + camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar ); + + } + + if ( cameraDef.name !== undefined ) camera.name = cameraDef.name; + + assignExtrasToUserData( camera, cameraDef ); + + return Promise.resolve( camera ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins + * @param {number} skinIndex + * @return {Promise} + */ + GLTFParser.prototype.loadSkin = function ( skinIndex ) { + + var skinDef = this.json.skins[ skinIndex ]; + + var skinEntry = { joints: skinDef.joints }; + + if ( skinDef.inverseBindMatrices === undefined ) { + + return Promise.resolve( skinEntry ); + + } + + return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { + + skinEntry.inverseBindMatrices = accessor; + + return skinEntry; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations + * @param {number} animationIndex + * @return {Promise} + */ + GLTFParser.prototype.loadAnimation = function ( animationIndex ) { + + var json = this.json; + + var animationDef = json.animations[ animationIndex ]; + + var pendingNodes = []; + var pendingInputAccessors = []; + var pendingOutputAccessors = []; + var pendingSamplers = []; + var pendingTargets = []; + + for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { + + var channel = animationDef.channels[ i ]; + var sampler = animationDef.samplers[ channel.sampler ]; + var target = channel.target; + var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. + var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; + var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; + + pendingNodes.push( this.getDependency( 'node', name ) ); + pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); + pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); + pendingSamplers.push( sampler ); + pendingTargets.push( target ); + + } + + return Promise.all( [ + + Promise.all( pendingNodes ), + Promise.all( pendingInputAccessors ), + Promise.all( pendingOutputAccessors ), + Promise.all( pendingSamplers ), + Promise.all( pendingTargets ) + + ] ).then( function ( dependencies ) { + + var nodes = dependencies[ 0 ]; + var inputAccessors = dependencies[ 1 ]; + var outputAccessors = dependencies[ 2 ]; + var samplers = dependencies[ 3 ]; + var targets = dependencies[ 4 ]; + + var tracks = []; + + for ( var i = 0, il = nodes.length; i < il; i ++ ) { + + var node = nodes[ i ]; + var inputAccessor = inputAccessors[ i ]; + var outputAccessor = outputAccessors[ i ]; + var sampler = samplers[ i ]; + var target = targets[ i ]; + + if ( node === undefined ) continue; + + node.updateMatrix(); + node.matrixAutoUpdate = true; + + var TypedKeyframeTrack; + + switch ( PATH_PROPERTIES[ target.path ] ) { + + case PATH_PROPERTIES.weights: + + TypedKeyframeTrack = THREE.NumberKeyframeTrack; + break; + + case PATH_PROPERTIES.rotation: + + TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; + break; + + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: + default: + + TypedKeyframeTrack = THREE.VectorKeyframeTrack; + break; + + } + + var targetName = node.name ? node.name : node.uuid; + + var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; + + var targetNames = []; + + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { + + // Node may be a THREE.Group (glTF mesh with several primitives) or a THREE.Mesh. + node.traverse( function ( object ) { + + if ( object.isMesh === true && object.morphTargetInfluences ) { + + targetNames.push( object.name ? object.name : object.uuid ); + + } + + } ); + + } else { + + targetNames.push( targetName ); + + } + + var outputArray = outputAccessor.array; + + if ( outputAccessor.normalized ) { + + var scale; + + if ( outputArray.constructor === Int8Array ) { + + scale = 1 / 127; + + } else if ( outputArray.constructor === Uint8Array ) { + + scale = 1 / 255; + + } else if ( outputArray.constructor == Int16Array ) { + + scale = 1 / 32767; + + } else if ( outputArray.constructor === Uint16Array ) { + + scale = 1 / 65535; + + } else { + + throw new Error( 'THREE.GLTFLoader: Unsupported output accessor component type.' ); + + } + + var scaled = new Float32Array( outputArray.length ); + + for ( var j = 0, jl = outputArray.length; j < jl; j ++ ) { + + scaled[j] = outputArray[j] * scale; + + } + + outputArray = scaled; + + } + + for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { + + var track = new TypedKeyframeTrack( + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], + inputAccessor.array, + outputArray, + interpolation + ); + + // Override interpolation with custom factory method. + if ( sampler.interpolation === 'CUBICSPLINE' ) { + + track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { + + // A CUBICSPLINE keyframe in glTF has three output values for each input value, + // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() + // must be divided by three to get the interpolant's sampleSize argument. + + return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result ); + + }; + + // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. + track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; + + } + + tracks.push( track ); + + } + + } + + var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex; + + return new THREE.AnimationClip( name, undefined, tracks ); + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy + * @param {number} nodeIndex + * @return {Promise} + */ + GLTFParser.prototype.loadNode = function ( nodeIndex ) { + + var json = this.json; + var extensions = this.extensions; + var parser = this; + + var meshReferences = json.meshReferences; + var meshUses = json.meshUses; + + var nodeDef = json.nodes[ nodeIndex ]; + + return ( function () { + + // .isBone isn't in glTF spec. See .markDefs + if ( nodeDef.isBone === true ) { + + return Promise.resolve( new THREE.Bone() ); + + } else if ( nodeDef.mesh !== undefined ) { + + return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { + + var node; + + if ( meshReferences[ nodeDef.mesh ] > 1 ) { + + var instanceNum = meshUses[ nodeDef.mesh ] ++; + + node = mesh.clone(); + node.name += '_instance_' + instanceNum; + + // onBeforeRender copy for Specular-Glossiness + node.onBeforeRender = mesh.onBeforeRender; + + for ( var i = 0, il = node.children.length; i < il; i ++ ) { + + node.children[ i ].name += '_instance_' + instanceNum; + node.children[ i ].onBeforeRender = mesh.children[ i ].onBeforeRender; + + } + + } else { + + node = mesh; + + } + + // if weights are provided on the node, override weights on the mesh. + if ( nodeDef.weights !== undefined ) { + + node.traverse( function ( o ) { + + if ( ! o.isMesh ) return; + + for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) { + + o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; + + } + + } ); + + } + + return node; + + } ); + + } else if ( nodeDef.camera !== undefined ) { + + return parser.getDependency( 'camera', nodeDef.camera ); + + } else if ( nodeDef.extensions + && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] + && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) { + + return parser.getDependency( 'light', nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ); + + } else { + + return Promise.resolve( new THREE.Object3D() ); + + } + + }() ).then( function ( node ) { + + if ( nodeDef.name !== undefined ) { + + node.userData.name = nodeDef.name; + node.name = THREE.PropertyBinding.sanitizeNodeName( nodeDef.name ); + + } + + assignExtrasToUserData( node, nodeDef ); + + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); + + if ( nodeDef.matrix !== undefined ) { + + var matrix = new THREE.Matrix4(); + matrix.fromArray( nodeDef.matrix ); + node.applyMatrix( matrix ); + + } else { + + if ( nodeDef.translation !== undefined ) { + + node.position.fromArray( nodeDef.translation ); + + } + + if ( nodeDef.rotation !== undefined ) { + + node.quaternion.fromArray( nodeDef.rotation ); + + } + + if ( nodeDef.scale !== undefined ) { + + node.scale.fromArray( nodeDef.scale ); + + } + + } + + return node; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes + * @param {number} sceneIndex + * @return {Promise} + */ + GLTFParser.prototype.loadScene = function () { + + // scene node hierachy builder + + function buildNodeHierachy( nodeId, parentObject, json, parser ) { + + var nodeDef = json.nodes[ nodeId ]; + + return parser.getDependency( 'node', nodeId ).then( function ( node ) { + + if ( nodeDef.skin === undefined ) return node; + + // build skeleton here as well + + var skinEntry; + + return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { + + skinEntry = skin; + + var pendingJoints = []; + + for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) { + + pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); + + } + + return Promise.all( pendingJoints ); + + } ).then( function ( jointNodes ) { + + var meshes = node.isGroup === true ? node.children : [ node ]; + + for ( var i = 0, il = meshes.length; i < il; i ++ ) { + + var mesh = meshes[ i ]; + + var bones = []; + var boneInverses = []; + + for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) { + + var jointNode = jointNodes[ j ]; + + if ( jointNode ) { + + bones.push( jointNode ); + + var mat = new THREE.Matrix4(); + + if ( skinEntry.inverseBindMatrices !== undefined ) { + + mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); + + } + + boneInverses.push( mat ); + + } else { + + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); + + } + + } + + mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld ); + + } + + return node; + + } ); + + } ).then( function ( node ) { + + // build node hierachy + + parentObject.add( node ); + + var pending = []; + + if ( nodeDef.children ) { + + var children = nodeDef.children; + + for ( var i = 0, il = children.length; i < il; i ++ ) { + + var child = children[ i ]; + pending.push( buildNodeHierachy( child, node, json, parser ) ); + + } + + } + + return Promise.all( pending ); + + } ); + + } + + return function loadScene( sceneIndex ) { + + var json = this.json; + var extensions = this.extensions; + var sceneDef = this.json.scenes[ sceneIndex ]; + var parser = this; + + var scene = new THREE.Scene(); + if ( sceneDef.name !== undefined ) scene.name = sceneDef.name; + + assignExtrasToUserData( scene, sceneDef ); + + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); + + var nodeIds = sceneDef.nodes || []; + + var pending = []; + + for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { + + pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); + + } + + return Promise.all( pending ).then( function () { + + return scene; + + } ); + + }; + + }(); + + return GLTFLoader; + +} )(); diff --git a/3rdparty/meshoptimizer/demo/index.html b/3rdparty/meshoptimizer/demo/index.html index 8cce49038..cdc21c0a3 100644 --- a/3rdparty/meshoptimizer/demo/index.html +++ b/3rdparty/meshoptimizer/demo/index.html @@ -30,30 +30,19 @@ meshoptimizer - + - - + +