From 101f5f3817b5755686e2428183d164094c056b2b Mon Sep 17 00:00:00 2001 From: FengFengmomo <12838106+FengFengmomo@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:02:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=90=83=E4=BD=93=E6=8E=A9?= =?UTF-8?q?=E8=86=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/wegeo.cjs | 6537 ++-------------- build/wegeo.js | 6539 ++--------------- build/wegeo.module.js | 6538 ++-------------- .../js/THREEi_ONLY_SphereWithSomeHoles.js | 1694 +++++ examples/js/createhole.js | 1663 +++++ examples/main.html | 8 + examples/screenshots/mask.png | Bin 0 -> 500854 bytes examples/sphereEarth/mask/mask.html | 141 + examples/wegeo.html | 91 +- src/WegeoMap.js | 12 +- src/animation/Animate.js | 2 +- src/effect/outline.js | 12 +- src/examples/3dtiles.js | 6 +- src/examples/basic.js | 2 +- src/layers/3DTilesLayer.js | 4 +- src/layers/Layer.js | 8 +- src/loader/ModelLoader.js | 8 +- src/loader/Water1.js | 2 +- src/loader/Water2.js | 2 +- src/main.js | 1 + src/sky/Skybox.js | 24 +- src/utils/AngleUtils.js | 20 + 22 files changed, 5614 insertions(+), 17700 deletions(-) create mode 100644 examples/js/THREEi_ONLY_SphereWithSomeHoles.js create mode 100644 examples/js/createhole.js create mode 100644 examples/screenshots/mask.png create mode 100644 examples/sphereEarth/mask/mask.html create mode 100644 src/utils/AngleUtils.js diff --git a/build/wegeo.cjs b/build/wegeo.cjs index 0b3afaf..0a61719 100644 --- a/build/wegeo.cjs +++ b/build/wegeo.cjs @@ -5009,6 +5009,26 @@ class Element { } } +class AngleUtils { + /** + * 弧度转角度 + * @param {*} rad + * @returns + */ + static radToDeg(rad) { + return rad * (180 / Math.PI); + } + /** + * 角度转弧度 + * @param {*} deg + * @returns + */ + static degToRad(deg) { + return deg * (Math.PI / 180); + } + +} + // OrbitControls performs orbiting, dollying (zooming), and panning. // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). // @@ -9173,7 +9193,7 @@ class EffectOutline { * @param {number} drawMode * @return {BufferGeometry} */ -function toTrianglesDrawMode$1( geometry, drawMode ) { +function toTrianglesDrawMode( geometry, drawMode ) { if ( drawMode === three.TrianglesDrawMode ) { return geometry; @@ -9268,7 +9288,7 @@ function toTrianglesDrawMode$1( geometry, drawMode ) { } -let GLTFLoader$1 = class GLTFLoader extends three.Loader { +class GLTFLoader extends three.Loader { constructor( manager ) { @@ -9282,97 +9302,97 @@ let GLTFLoader$1 = class GLTFLoader extends three.Loader { this.register( function ( parser ) { - return new GLTFMaterialsClearcoatExtension$1( parser ); + return new GLTFMaterialsClearcoatExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureBasisUExtension$1( parser ); + return new GLTFTextureBasisUExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureWebPExtension$1( parser ); + return new GLTFTextureWebPExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureAVIFExtension$1( parser ); + return new GLTFTextureAVIFExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsSheenExtension$1( parser ); + return new GLTFMaterialsSheenExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsTransmissionExtension$1( parser ); + return new GLTFMaterialsTransmissionExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsVolumeExtension$1( parser ); + return new GLTFMaterialsVolumeExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsIorExtension$1( parser ); + return new GLTFMaterialsIorExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsEmissiveStrengthExtension$1( parser ); + return new GLTFMaterialsEmissiveStrengthExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsSpecularExtension$1( parser ); + return new GLTFMaterialsSpecularExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsIridescenceExtension$1( parser ); + return new GLTFMaterialsIridescenceExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsAnisotropyExtension$1( parser ); + return new GLTFMaterialsAnisotropyExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsBumpExtension$1( parser ); + return new GLTFMaterialsBumpExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFLightsExtension$1( parser ); + return new GLTFLightsExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMeshoptCompression$1( parser ); + return new GLTFMeshoptCompression( parser ); } ); this.register( function ( parser ) { - return new GLTFMeshGpuInstancing$1( parser ); + return new GLTFMeshGpuInstancing( parser ); } ); @@ -9521,11 +9541,11 @@ let GLTFLoader$1 = class GLTFLoader extends three.Loader { const magic = textDecoder.decode( new Uint8Array( data, 0, 4 ) ); - if ( magic === BINARY_EXTENSION_HEADER_MAGIC$1 ) { + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { try { - extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ] = new GLTFBinaryExtension$1( data ); + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); } catch ( error ) { @@ -9534,7 +9554,7 @@ let GLTFLoader$1 = class GLTFLoader extends three.Loader { } - json = JSON.parse( extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ].content ); + json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); } else { @@ -9555,7 +9575,7 @@ let GLTFLoader$1 = class GLTFLoader extends three.Loader { } - const parser = new GLTFParser$1( json, { + const parser = new GLTFParser( json, { path: path || this.resourcePath || '', crossOrigin: this.crossOrigin, @@ -9593,20 +9613,20 @@ let GLTFLoader$1 = class GLTFLoader extends three.Loader { switch ( extensionName ) { - case EXTENSIONS$1.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension$1(); + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); break; - case EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension$1( json, this.dracoLoader ); + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); break; - case EXTENSIONS$1.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension$1(); + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); break; - case EXTENSIONS$1.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension$1(); + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); break; default: @@ -9637,11 +9657,11 @@ let GLTFLoader$1 = class GLTFLoader extends three.Loader { } -}; +} /* GLTFREGISTRY */ -function GLTFRegistry$1() { +function GLTFRegistry() { let objects = {}; @@ -9679,7 +9699,7 @@ function GLTFRegistry$1() { /********** EXTENSIONS ***********/ /*********************************/ -const EXTENSIONS$1 = { +const EXTENSIONS = { KHR_BINARY_GLTF: 'KHR_binary_glTF', KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', @@ -9708,12 +9728,12 @@ const EXTENSIONS$1 = { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ -let GLTFLightsExtension$1 = class GLTFLightsExtension { +class GLTFLightsExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_LIGHTS_PUNCTUAL; + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // Object3D instance caches this.cache = { refs: {}, uses: {} }; @@ -9798,7 +9818,7 @@ let GLTFLightsExtension$1 = class GLTFLightsExtension { lightNode.decay = 2; - assignExtrasToUserData$1( lightNode, lightDef ); + assignExtrasToUserData( lightNode, lightDef ); if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; @@ -9839,18 +9859,18 @@ let GLTFLightsExtension$1 = class GLTFLightsExtension { } -}; +} /** * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ -let GLTFMaterialsUnlitExtension$1 = class GLTFMaterialsUnlitExtension { +class GLTFMaterialsUnlitExtension { constructor() { - this.name = EXTENSIONS$1.KHR_MATERIALS_UNLIT; + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; } @@ -9892,19 +9912,19 @@ let GLTFMaterialsUnlitExtension$1 = class GLTFMaterialsUnlitExtension { } -}; +} /** * Materials Emissive Strength Extension * * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md */ -let GLTFMaterialsEmissiveStrengthExtension$1 = class GLTFMaterialsEmissiveStrengthExtension { +class GLTFMaterialsEmissiveStrengthExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_EMISSIVE_STRENGTH; + this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; } @@ -9931,19 +9951,19 @@ let GLTFMaterialsEmissiveStrengthExtension$1 = class GLTFMaterialsEmissiveStreng } -}; +} /** * Clearcoat Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat */ -let GLTFMaterialsClearcoatExtension$1 = class GLTFMaterialsClearcoatExtension { +class GLTFMaterialsClearcoatExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_CLEARCOAT; + this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; } @@ -10015,19 +10035,19 @@ let GLTFMaterialsClearcoatExtension$1 = class GLTFMaterialsClearcoatExtension { } -}; +} /** * Iridescence Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence */ -let GLTFMaterialsIridescenceExtension$1 = class GLTFMaterialsIridescenceExtension { +class GLTFMaterialsIridescenceExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_IRIDESCENCE; + this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; } @@ -10103,19 +10123,19 @@ let GLTFMaterialsIridescenceExtension$1 = class GLTFMaterialsIridescenceExtensio } -}; +} /** * Sheen Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen */ -let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { +class GLTFMaterialsSheenExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_SHEEN; + this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; } @@ -10178,7 +10198,7 @@ let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { } -}; +} /** * Transmission Materials Extension @@ -10186,12 +10206,12 @@ let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission * Draft: https://github.com/KhronosGroup/glTF/pull/1698 */ -let GLTFMaterialsTransmissionExtension$1 = class GLTFMaterialsTransmissionExtension { +class GLTFMaterialsTransmissionExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_TRANSMISSION; + this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; } @@ -10237,19 +10257,19 @@ let GLTFMaterialsTransmissionExtension$1 = class GLTFMaterialsTransmissionExtens } -}; +} /** * Materials Volume Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume */ -let GLTFMaterialsVolumeExtension$1 = class GLTFMaterialsVolumeExtension { +class GLTFMaterialsVolumeExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_VOLUME; + this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; } @@ -10296,19 +10316,19 @@ let GLTFMaterialsVolumeExtension$1 = class GLTFMaterialsVolumeExtension { } -}; +} /** * Materials ior Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior */ -let GLTFMaterialsIorExtension$1 = class GLTFMaterialsIorExtension { +class GLTFMaterialsIorExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_IOR; + this.name = EXTENSIONS.KHR_MATERIALS_IOR; } @@ -10342,19 +10362,19 @@ let GLTFMaterialsIorExtension$1 = class GLTFMaterialsIorExtension { } -}; +} /** * Materials specular Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular */ -let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { +class GLTFMaterialsSpecularExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_SPECULAR; + this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; } @@ -10405,7 +10425,7 @@ let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { } -}; +} /** @@ -10413,12 +10433,12 @@ let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump */ -let GLTFMaterialsBumpExtension$1 = class GLTFMaterialsBumpExtension { +class GLTFMaterialsBumpExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_MATERIALS_BUMP; + this.name = EXTENSIONS.EXT_MATERIALS_BUMP; } @@ -10460,19 +10480,19 @@ let GLTFMaterialsBumpExtension$1 = class GLTFMaterialsBumpExtension { } -}; +} /** * Materials anisotropy Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy */ -let GLTFMaterialsAnisotropyExtension$1 = class GLTFMaterialsAnisotropyExtension { +class GLTFMaterialsAnisotropyExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_ANISOTROPY; + this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; } @@ -10524,19 +10544,19 @@ let GLTFMaterialsAnisotropyExtension$1 = class GLTFMaterialsAnisotropyExtension } -}; +} /** * BasisU Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu */ -let GLTFTextureBasisUExtension$1 = class GLTFTextureBasisUExtension { +class GLTFTextureBasisUExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_TEXTURE_BASISU; + this.name = EXTENSIONS.KHR_TEXTURE_BASISU; } @@ -10575,19 +10595,19 @@ let GLTFTextureBasisUExtension$1 = class GLTFTextureBasisUExtension { } -}; +} /** * WebP Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp */ -let GLTFTextureWebPExtension$1 = class GLTFTextureWebPExtension { +class GLTFTextureWebPExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_TEXTURE_WEBP; + this.name = EXTENSIONS.EXT_TEXTURE_WEBP; this.isSupported = null; } @@ -10660,19 +10680,19 @@ let GLTFTextureWebPExtension$1 = class GLTFTextureWebPExtension { } -}; +} /** * AVIF Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif */ -let GLTFTextureAVIFExtension$1 = class GLTFTextureAVIFExtension { +class GLTFTextureAVIFExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_TEXTURE_AVIF; + this.name = EXTENSIONS.EXT_TEXTURE_AVIF; this.isSupported = null; } @@ -10743,18 +10763,18 @@ let GLTFTextureAVIFExtension$1 = class GLTFTextureAVIFExtension { } -}; +} /** * meshopt BufferView Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression */ -let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { +class GLTFMeshoptCompression { constructor( parser ) { - this.name = EXTENSIONS$1.EXT_MESHOPT_COMPRESSION; + this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; this.parser = parser; } @@ -10827,7 +10847,7 @@ let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { } -}; +} /** * GPU Instancing Extension @@ -10835,11 +10855,11 @@ let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing * */ -let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { +class GLTFMeshGpuInstancing { constructor( parser ) { - this.name = EXTENSIONS$1.EXT_MESH_GPU_INSTANCING; + this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; this.parser = parser; } @@ -10862,9 +10882,9 @@ let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { for ( const primitive of meshDef.primitives ) { - if ( primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLE_FAN && + if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && + primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && + primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && primitive.mode !== undefined ) { return null; @@ -10984,22 +11004,22 @@ let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { } -}; +} /* BINARY EXTENSION */ -const BINARY_EXTENSION_HEADER_MAGIC$1 = 'glTF'; -const BINARY_EXTENSION_HEADER_LENGTH$1 = 12; -const BINARY_EXTENSION_CHUNK_TYPES$1 = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; +const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; +const BINARY_EXTENSION_HEADER_LENGTH = 12; +const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; -let GLTFBinaryExtension$1 = class GLTFBinaryExtension { +class GLTFBinaryExtension { constructor( data ) { - this.name = EXTENSIONS$1.KHR_BINARY_GLTF; + this.name = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH$1 ); + const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); const textDecoder = new TextDecoder(); this.header = { @@ -11008,7 +11028,7 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { length: headerView.getUint32( 8, true ) }; - if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC$1 ) { + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); @@ -11018,8 +11038,8 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { } - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH$1; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH$1 ); + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); let chunkIndex = 0; while ( chunkIndex < chunkContentsLength ) { @@ -11030,14 +11050,14 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { const chunkType = chunkView.getUint32( chunkIndex, true ); chunkIndex += 4; - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES$1.JSON ) { + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH$1 + chunkIndex, chunkLength ); + const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); this.content = textDecoder.decode( contentArray ); - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES$1.BIN ) { + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - const byteOffset = BINARY_EXTENSION_HEADER_LENGTH$1 + chunkIndex; + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; this.body = data.slice( byteOffset, byteOffset + chunkLength ); } @@ -11056,14 +11076,14 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { } -}; +} /** * DRACO Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ -let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtension { +class GLTFDracoMeshCompressionExtension { constructor( json, dracoLoader ) { @@ -11073,7 +11093,7 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio } - this.name = EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION; + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; this.json = json; this.dracoLoader = dracoLoader; this.dracoLoader.preload(); @@ -11092,7 +11112,7 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio for ( const attributeName in gltfAttributeMap ) { - const threeAttributeName = ATTRIBUTES$1[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; @@ -11100,12 +11120,12 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio for ( const attributeName in primitive.attributes ) { - const threeAttributeName = ATTRIBUTES$1[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); if ( gltfAttributeMap[ attributeName ] !== undefined ) { const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; attributeTypeMap[ threeAttributeName ] = componentType.name; attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; @@ -11139,18 +11159,18 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio } -}; +} /** * Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform */ -let GLTFTextureTransformExtension$1 = class GLTFTextureTransformExtension { +class GLTFTextureTransformExtension { constructor() { - this.name = EXTENSIONS$1.KHR_TEXTURE_TRANSFORM; + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } @@ -11198,22 +11218,22 @@ let GLTFTextureTransformExtension$1 = class GLTFTextureTransformExtension { } -}; +} /** * Mesh Quantization Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization */ -let GLTFMeshQuantizationExtension$1 = class GLTFMeshQuantizationExtension { +class GLTFMeshQuantizationExtension { constructor() { - this.name = EXTENSIONS$1.KHR_MESH_QUANTIZATION; + this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; } -}; +} /*********************************/ /********** INTERPOLATION ********/ @@ -11221,7 +11241,7 @@ let GLTFMeshQuantizationExtension$1 = class GLTFMeshQuantizationExtension { // Spline Interpolation // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation -let GLTFCubicSplineInterpolant$1 = class GLTFCubicSplineInterpolant extends three.Interpolant { +class GLTFCubicSplineInterpolant extends three.Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { @@ -11289,23 +11309,23 @@ let GLTFCubicSplineInterpolant$1 = class GLTFCubicSplineInterpolant extends thre } -}; +} -const _q$1 = new three.Quaternion(); +const _q = new three.Quaternion(); -let GLTFCubicSplineQuaternionInterpolant$1 = class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant$1 { +class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { interpolate_( i1, t0, t, t1 ) { const result = super.interpolate_( i1, t0, t, t1 ); - _q$1.fromArray( result ).normalize().toArray( result ); + _q.fromArray( result ).normalize().toArray( result ); return result; } -}; +} /*********************************/ @@ -11314,7 +11334,7 @@ let GLTFCubicSplineQuaternionInterpolant$1 = class GLTFCubicSplineQuaternionInte /* CONSTANTS */ -const WEBGL_CONSTANTS$1 = { +const WEBGL_CONSTANTS = { FLOAT: 5126, //FLOAT_MAT2: 35674, FLOAT_MAT3: 35675, @@ -11336,7 +11356,7 @@ const WEBGL_CONSTANTS$1 = { UNSIGNED_SHORT: 5123 }; -const WEBGL_COMPONENT_TYPES$1 = { +const WEBGL_COMPONENT_TYPES = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, @@ -11345,7 +11365,7 @@ const WEBGL_COMPONENT_TYPES$1 = { 5126: Float32Array }; -const WEBGL_FILTERS$1 = { +const WEBGL_FILTERS = { 9728: three.NearestFilter, 9729: three.LinearFilter, 9984: three.NearestMipmapNearestFilter, @@ -11354,13 +11374,13 @@ const WEBGL_FILTERS$1 = { 9987: three.LinearMipmapLinearFilter }; -const WEBGL_WRAPPINGS$1 = { +const WEBGL_WRAPPINGS = { 33071: three.ClampToEdgeWrapping, 33648: three.MirroredRepeatWrapping, 10497: three.RepeatWrapping }; -const WEBGL_TYPE_SIZES$1 = { +const WEBGL_TYPE_SIZES = { 'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, @@ -11370,7 +11390,7 @@ const WEBGL_TYPE_SIZES$1 = { 'MAT4': 16 }; -const ATTRIBUTES$1 = { +const ATTRIBUTES = { POSITION: 'position', NORMAL: 'normal', TANGENT: 'tangent', @@ -11383,21 +11403,21 @@ const ATTRIBUTES$1 = { JOINTS_0: 'skinIndex', }; -const PATH_PROPERTIES$1 = { +const PATH_PROPERTIES = { scale: 'scale', translation: 'position', rotation: 'quaternion', weights: 'morphTargetInfluences' }; -const INTERPOLATION$1 = { +const 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 }; -const ALPHA_MODES$1 = { +const ALPHA_MODES = { OPAQUE: 'OPAQUE', MASK: 'MASK', BLEND: 'BLEND' @@ -11406,7 +11426,7 @@ const ALPHA_MODES$1 = { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material */ -function createDefaultMaterial$1( cache ) { +function createDefaultMaterial( cache ) { if ( cache[ 'DefaultMaterial' ] === undefined ) { @@ -11426,7 +11446,7 @@ function createDefaultMaterial$1( cache ) { } -function addUnknownExtensionsToUserData$1( knownExtensions, object, objectDef ) { +function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { // Add unknown glTF extensions to an object's userData. @@ -11447,7 +11467,7 @@ function addUnknownExtensionsToUserData$1( knownExtensions, object, objectDef ) * @param {Object3D|Material|BufferGeometry} object * @param {GLTF.definition} gltfDef */ -function assignExtrasToUserData$1( object, gltfDef ) { +function assignExtrasToUserData( object, gltfDef ) { if ( gltfDef.extras !== undefined ) { @@ -11469,7 +11489,7 @@ function assignExtrasToUserData$1( object, gltfDef ) { * @param {GLTFParser} parser * @return {Promise} */ -function addMorphTargets$1( geometry, targets, parser ) { +function addMorphTargets( geometry, targets, parser ) { let hasMorphPosition = false; let hasMorphNormal = false; @@ -11554,7 +11574,7 @@ function addMorphTargets$1( geometry, targets, parser ) { * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ -function updateMorphTargets$1( mesh, meshDef ) { +function updateMorphTargets( mesh, meshDef ) { mesh.updateMorphTargets(); @@ -11589,21 +11609,21 @@ function updateMorphTargets$1( mesh, meshDef ) { } -function createPrimitiveKey$1( primitiveDef ) { +function createPrimitiveKey( primitiveDef ) { let geometryKey; - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ]; + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; if ( dracoExtension ) { geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices - + ':' + createAttributesKey$1( dracoExtension.attributes ); + + ':' + createAttributesKey( dracoExtension.attributes ); } else { - geometryKey = primitiveDef.indices + ':' + createAttributesKey$1( primitiveDef.attributes ) + ':' + primitiveDef.mode; + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; } @@ -11611,7 +11631,7 @@ function createPrimitiveKey$1( primitiveDef ) { for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - geometryKey += ':' + createAttributesKey$1( primitiveDef.targets[ i ] ); + geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); } @@ -11621,7 +11641,7 @@ function createPrimitiveKey$1( primitiveDef ) { } -function createAttributesKey$1( attributes ) { +function createAttributesKey( attributes ) { let attributesKey = ''; @@ -11637,7 +11657,7 @@ function createAttributesKey$1( attributes ) { } -function getNormalizedComponentScale$1( constructor ) { +function getNormalizedComponentScale( constructor ) { // Reference: // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data @@ -11663,7 +11683,7 @@ function getNormalizedComponentScale$1( constructor ) { } -function getImageURIMimeType$1( uri ) { +function getImageURIMimeType( uri ) { if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; @@ -11672,11 +11692,11 @@ function getImageURIMimeType$1( uri ) { } -const _identityMatrix$1 = new three.Matrix4(); +const _identityMatrix = new three.Matrix4(); /* GLTF PARSER */ -let GLTFParser$1 = class GLTFParser { +class GLTFParser { constructor( json = {}, options = {} ) { @@ -11686,7 +11706,7 @@ let GLTFParser$1 = class GLTFParser { this.options = options; // loader object cache - this.cache = new GLTFRegistry$1(); + this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements this.associations = new Map(); @@ -11802,9 +11822,9 @@ let GLTFParser$1 = class GLTFParser { userData: {} }; - addUnknownExtensionsToUserData$1( extensions, result, json ); + addUnknownExtensionsToUserData( extensions, result, json ); - assignExtrasToUserData$1( result, json ); + assignExtrasToUserData( result, json ); return Promise.all( parser._invokeAll( function ( ext ) { @@ -12122,7 +12142,7 @@ let GLTFParser$1 = class GLTFParser { // If present, GLB container is required to be the first buffer. if ( bufferDef.uri === undefined && bufferIndex === 0 ) { - return Promise.resolve( this.extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ].body ); + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); } @@ -12173,8 +12193,8 @@ let GLTFParser$1 = class GLTFParser { if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - const itemSize = WEBGL_TYPE_SIZES$1[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; const normalized = accessorDef.normalized === true; const array = new TypedArray( accessorDef.count * itemSize ); @@ -12205,8 +12225,8 @@ let GLTFParser$1 = class GLTFParser { const bufferView = bufferViews[ 0 ]; - const itemSize = WEBGL_TYPE_SIZES$1[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. const elementBytes = TypedArray.BYTES_PER_ELEMENT; @@ -12257,8 +12277,8 @@ let GLTFParser$1 = class GLTFParser { // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors if ( accessorDef.sparse !== undefined ) { - const itemSizeIndices = WEBGL_TYPE_SIZES$1.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES$1[ accessorDef.sparse.indices.componentType ]; + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; @@ -12351,10 +12371,10 @@ let GLTFParser$1 = class GLTFParser { const samplers = json.samplers || {}; const sampler = samplers[ textureDef.sampler ] || {}; - texture.magFilter = WEBGL_FILTERS$1[ sampler.magFilter ] || three.LinearFilter; - texture.minFilter = WEBGL_FILTERS$1[ sampler.minFilter ] || three.LinearMipmapLinearFilter; - texture.wrapS = WEBGL_WRAPPINGS$1[ sampler.wrapS ] || three.RepeatWrapping; - texture.wrapT = WEBGL_WRAPPINGS$1[ sampler.wrapT ] || three.RepeatWrapping; + 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; parser.associations.set( texture, { textures: textureIndex } ); @@ -12443,7 +12463,7 @@ let GLTFParser$1 = class GLTFParser { } - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType$1( sourceDef.uri ); + texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); return texture; @@ -12479,14 +12499,14 @@ let GLTFParser$1 = class GLTFParser { } - if ( parser.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ] ) { + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ] : undefined; + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; if ( transform ) { const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); parser.associations.set( texture, gltfReference ); } @@ -12629,9 +12649,9 @@ let GLTFParser$1 = class GLTFParser { const pending = []; - if ( materialExtensions[ EXTENSIONS$1.KHR_MATERIALS_UNLIT ] ) { + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - const kmuExtension = extensions[ EXTENSIONS$1.KHR_MATERIALS_UNLIT ]; + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; materialType = kmuExtension.getMaterialType(); pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); @@ -12690,9 +12710,9 @@ let GLTFParser$1 = class GLTFParser { } - const alphaMode = materialDef.alphaMode || ALPHA_MODES$1.OPAQUE; + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - if ( alphaMode === ALPHA_MODES$1.BLEND ) { + if ( alphaMode === ALPHA_MODES.BLEND ) { materialParams.transparent = true; @@ -12703,7 +12723,7 @@ let GLTFParser$1 = class GLTFParser { materialParams.transparent = false; - if ( alphaMode === ALPHA_MODES$1.MASK ) { + if ( alphaMode === ALPHA_MODES.MASK ) { materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; @@ -12758,11 +12778,11 @@ let GLTFParser$1 = class GLTFParser { if ( materialDef.name ) material.name = materialDef.name; - assignExtrasToUserData$1( material, materialDef ); + assignExtrasToUserData( material, materialDef ); parser.associations.set( material, { materials: materialIndex } ); - if ( materialDef.extensions ) addUnknownExtensionsToUserData$1( extensions, material, materialDef ); + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); return material; @@ -12805,11 +12825,11 @@ let GLTFParser$1 = class GLTFParser { function createDracoPrimitive( primitive ) { - return extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ] + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] .decodePrimitive( primitive, parser ) .then( function ( geometry ) { - return addPrimitiveAttributes$1( geometry, primitive, parser ); + return addPrimitiveAttributes( geometry, primitive, parser ); } ); @@ -12820,7 +12840,7 @@ let GLTFParser$1 = class GLTFParser { for ( let i = 0, il = primitives.length; i < il; i ++ ) { const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey$1( primitive ); + const cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry const cached = cache[ cacheKey ]; @@ -12834,7 +12854,7 @@ let GLTFParser$1 = class GLTFParser { let geometryPromise; - if ( primitive.extensions && primitive.extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ] ) { + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { // Use DRACO geometry if available geometryPromise = createDracoPrimitive( primitive ); @@ -12842,7 +12862,7 @@ let GLTFParser$1 = class GLTFParser { } else { // Otherwise create a new geometry - geometryPromise = addPrimitiveAttributes$1( new three.BufferGeometry(), primitive, parser ); + geometryPromise = addPrimitiveAttributes( new three.BufferGeometry(), primitive, parser ); } @@ -12878,7 +12898,7 @@ let GLTFParser$1 = class GLTFParser { for ( let i = 0, il = primitives.length; i < il; i ++ ) { const material = primitives[ i ].material === undefined - ? createDefaultMaterial$1( this.cache ) + ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material ); pending.push( material ); @@ -12905,9 +12925,9 @@ let GLTFParser$1 = class GLTFParser { const material = materials[ i ]; - if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLES || - primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_STRIP || - primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_FAN || + 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() @@ -12922,29 +12942,29 @@ let GLTFParser$1 = class GLTFParser { } - if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_STRIP ) { + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - mesh.geometry = toTrianglesDrawMode$1( mesh.geometry, three.TriangleStripDrawMode ); + mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleStripDrawMode ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_FAN ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - mesh.geometry = toTrianglesDrawMode$1( mesh.geometry, three.TriangleFanDrawMode ); + mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleFanDrawMode ); } - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINES ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { mesh = new three.LineSegments( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINE_STRIP ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { mesh = new three.Line( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINE_LOOP ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { mesh = new three.LineLoop( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.POINTS ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { mesh = new three.Points( geometry, material ); @@ -12956,15 +12976,15 @@ let GLTFParser$1 = class GLTFParser { if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { - updateMorphTargets$1( mesh, meshDef ); + updateMorphTargets( mesh, meshDef ); } mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - assignExtrasToUserData$1( mesh, meshDef ); + assignExtrasToUserData( mesh, meshDef ); - if ( primitive.extensions ) addUnknownExtensionsToUserData$1( extensions, mesh, primitive ); + if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); parser.assignFinalMaterial( mesh ); @@ -12983,7 +13003,7 @@ let GLTFParser$1 = class GLTFParser { if ( meshes.length === 1 ) { - if ( meshDef.extensions ) addUnknownExtensionsToUserData$1( extensions, meshes[ 0 ], meshDef ); + if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); return meshes[ 0 ]; @@ -12991,7 +13011,7 @@ let GLTFParser$1 = class GLTFParser { const group = new three.Group(); - if ( meshDef.extensions ) addUnknownExtensionsToUserData$1( extensions, group, meshDef ); + if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); parser.associations.set( group, { meshes: meshIndex } ); @@ -13035,7 +13055,7 @@ let GLTFParser$1 = class GLTFParser { if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); - assignExtrasToUserData$1( camera, cameraDef ); + assignExtrasToUserData( camera, cameraDef ); return Promise.resolve( camera ); @@ -13279,7 +13299,7 @@ let GLTFParser$1 = class GLTFParser { if ( ! mesh.isSkinnedMesh ) return; - mesh.bind( skeleton, _identityMatrix$1 ); + mesh.bind( skeleton, _identityMatrix ); } ); @@ -13393,9 +13413,9 @@ let GLTFParser$1 = class GLTFParser { } - assignExtrasToUserData$1( node, nodeDef ); + assignExtrasToUserData( node, nodeDef ); - if ( nodeDef.extensions ) addUnknownExtensionsToUserData$1( extensions, node, nodeDef ); + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); if ( nodeDef.matrix !== undefined ) { @@ -13457,9 +13477,9 @@ let GLTFParser$1 = class GLTFParser { const scene = new three.Group(); if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - assignExtrasToUserData$1( scene, sceneDef ); + assignExtrasToUserData( scene, sceneDef ); - if ( sceneDef.extensions ) addUnknownExtensionsToUserData$1( extensions, scene, sceneDef ); + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); const nodeIds = sceneDef.nodes || []; @@ -13526,7 +13546,7 @@ let GLTFParser$1 = class GLTFParser { const targetName = node.name ? node.name : node.uuid; const targetNames = []; - if ( PATH_PROPERTIES$1[ target.path ] === PATH_PROPERTIES$1.weights ) { + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { node.traverse( function ( object ) { @@ -13546,20 +13566,20 @@ let GLTFParser$1 = class GLTFParser { let TypedKeyframeTrack; - switch ( PATH_PROPERTIES$1[ target.path ] ) { + switch ( PATH_PROPERTIES[ target.path ] ) { - case PATH_PROPERTIES$1.weights: + case PATH_PROPERTIES.weights: TypedKeyframeTrack = three.NumberKeyframeTrack; break; - case PATH_PROPERTIES$1.rotation: + case PATH_PROPERTIES.rotation: TypedKeyframeTrack = three.QuaternionKeyframeTrack; break; - case PATH_PROPERTIES$1.position: - case PATH_PROPERTIES$1.scale: + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: TypedKeyframeTrack = three.VectorKeyframeTrack; break; @@ -13583,7 +13603,7 @@ let GLTFParser$1 = class GLTFParser { } - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION$1[ sampler.interpolation ] : three.InterpolateLinear; + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : three.InterpolateLinear; const outputArray = this._getArrayFromAccessor( outputAccessor ); @@ -13591,7 +13611,7 @@ let GLTFParser$1 = class GLTFParser { for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES$1[ target.path ], + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], inputAccessor.array, outputArray, interpolation @@ -13618,7 +13638,7 @@ let GLTFParser$1 = class GLTFParser { if ( accessor.normalized ) { - const scale = getNormalizedComponentScale$1( outputArray.constructor ); + const scale = getNormalizedComponentScale( outputArray.constructor ); const scaled = new Float32Array( outputArray.length ); for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { @@ -13643,7 +13663,7 @@ let GLTFParser$1 = class GLTFParser { // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() // must be divided by three to get the interpolant's sampleSize argument. - const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant$1 : GLTFCubicSplineInterpolant$1; + const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); @@ -13654,14 +13674,14 @@ let GLTFParser$1 = class GLTFParser { } -}; +} /** * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser */ -function computeBounds$1( geometry, primitiveDef, parser ) { +function computeBounds( geometry, primitiveDef, parser ) { const attributes = primitiveDef.attributes; @@ -13685,7 +13705,7 @@ function computeBounds$1( geometry, primitiveDef, parser ) { if ( accessor.normalized ) { - const boxScale = getNormalizedComponentScale$1( WEBGL_COMPONENT_TYPES$1[ accessor.componentType ] ); + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); box.min.multiplyScalar( boxScale ); box.max.multiplyScalar( boxScale ); @@ -13732,7 +13752,7 @@ function computeBounds$1( geometry, primitiveDef, parser ) { if ( accessor.normalized ) { - const boxScale = getNormalizedComponentScale$1( WEBGL_COMPONENT_TYPES$1[ accessor.componentType ] ); + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); vector.multiplyScalar( boxScale ); } @@ -13771,7 +13791,7 @@ function computeBounds$1( geometry, primitiveDef, parser ) { * @param {GLTFParser} parser * @return {Promise} */ -function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { +function addPrimitiveAttributes( geometry, primitiveDef, parser ) { const attributes = primitiveDef.attributes; @@ -13790,7 +13810,7 @@ function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { for ( const gltfAttributeName in attributes ) { - const threeAttributeName = ATTRIBUTES$1[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension. if ( threeAttributeName in geometry.attributes ) continue; @@ -13813,23 +13833,23 @@ function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { if ( three.ColorManagement.workingColorSpace !== three.LinearSRGBColorSpace && 'COLOR_0' in attributes ) ; - assignExtrasToUserData$1( geometry, primitiveDef ); + assignExtrasToUserData( geometry, primitiveDef ); - computeBounds$1( geometry, primitiveDef, parser ); + computeBounds( geometry, primitiveDef, parser ); return Promise.all( pending ).then( function () { return primitiveDef.targets !== undefined - ? addMorphTargets$1( geometry, primitiveDef.targets, parser ) + ? addMorphTargets( geometry, primitiveDef.targets, parser ) : geometry; } ); } -const _taskCache$2 = new WeakMap(); +const _taskCache$1 = new WeakMap(); -let DRACOLoader$1 = class DRACOLoader extends three.Loader { +class DRACOLoader extends three.Loader { constructor( manager ) { @@ -13927,9 +13947,9 @@ let DRACOLoader$1 = class DRACOLoader extends three.Loader { // Check for an existing task using this buffer. A transferred buffer cannot be transferred // again from this thread. - if ( _taskCache$2.has( buffer ) ) { + if ( _taskCache$1.has( buffer ) ) { - const cachedTask = _taskCache$2.get( buffer ); + const cachedTask = _taskCache$1.get( buffer ); if ( cachedTask.key === taskKey ) { @@ -13995,7 +14015,7 @@ let DRACOLoader$1 = class DRACOLoader extends three.Loader { } ); // Cache the task result. - _taskCache$2.set( buffer, { + _taskCache$1.set( buffer, { key: taskKey, promise: geometryPending @@ -14113,7 +14133,7 @@ let DRACOLoader$1 = class DRACOLoader extends three.Loader { } - const fn = DRACOWorker$1.toString(); + const fn = DRACOWorker.toString(); const body = [ '/* draco decoder */', @@ -14216,11 +14236,11 @@ let DRACOLoader$1 = class DRACOLoader extends three.Loader { } -}; +} /* WEB WORKER */ -function DRACOWorker$1() { +function DRACOWorker() { let decoderConfig; let decoderPending; @@ -18439,10 +18459,10 @@ class ModelLoader { } class Lorder{ constructor() { - this.gltfLoader = new GLTFLoader$1(); + this.gltfLoader = new GLTFLoader(); // Optional: Provide a DRACOLoader instance to decode compressed mesh data - const dracoLoader = new DRACOLoader$1(); + const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( Config.DRACOPath ); this.gltfLoader.setDRACOLoader( dracoLoader ); this.objLoader = new OBJLoader(); // obj模型 @@ -18688,5725 +18708,471 @@ Sky.SkyShader = { uniform float mieCoefficient; uniform vec3 up; - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; - - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); - } - - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } - - void main() { - - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far - - vSunDirection = normalize( sunPosition ); - - vSunE = sunIntensity( dot( vSunDirection, up ) ); - - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; - - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; - - }`, - - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - uniform float mieDirectionalG; - uniform vec3 up; - - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; - - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; - - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } - - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } - - void main() { - - vec3 direction = normalize( vWorldPosition - cameraPosition ); - - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; - - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - - // in scattering - float cosTheta = dot( direction, vSunDirection ); - - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; - - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; - - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; - - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - - gl_FragColor = vec4( retColor, 1.0 ); - - #include - #include - - }` - -}; - -// 多个canvas并没有id,只有父节点 - - -class Layer extends BasLayer{ - id; // 唯一标识 - layerContainer; // div#layer 容器 - zIndex=1;//默认为1 - opacity=1;//默认为1 - canvas;//canvas - dispose = false; - renderer;//canvas上下文 - scene;//场景 - visible=true;//是否可见 - mapView;//地图视图 - camera;//相机 - controls;//控件 - animateId;//动画事件id - base = false; // 是否为底图 - ambientLight; // 环境光 - directionalLight; // 方向光 - modelLayer = false; // 模型图层 - imageLayer = false; // 影像图层 - vectorLayer = false; // 矢量图层,如路网、行政区划,地名等图层 - waters = []; // 水面集合 - constructor(id, layerContainer, canvas, mapView, plane = true, camera = new three.PerspectiveCamera(80, 1, 0.1, 1e12)) { - super(); - this.id = id; - this.layerContainer = layerContainer; - this.canvas = canvas; - this.renderer = new three.WebGLRenderer({ - canvas: this.canvas, - antialias: true, - alpha: true, - logarithmicDepthBuffer: true, - precision: "highp", - }); - this.renderer.sortObjects = true; - this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setClearColor(0xFFFFFF, 0.0); - this.scene = new three.Scene(); - this.mapView = mapView; - this.camera = camera; - if(this.mapView){ - this.scene.add(this.mapView); - this.mapView.updateMatrixWorld(true); - } - if (plane){ - this.controls = new MapControls(this.camera, this.canvas); - this.controls.minDistance = 1e1; - this.controls.zoomSpeed = 2.0; - } else { - this.controls = new OrbitControls(this.camera, this.canvas); - this.controls.enablePan = false; - this.controls.minDistance = UnitsUtils.EARTH_RADIUS + 2; - this.controls.maxDistance = UnitsUtils.EARTH_RADIUS * 1e1; - } - this._raycaster = new three.Raycaster(); - if(Config.outLine.on){ - this.effectOutline = new EffectOutline(this.renderer, this.scene, this.camera, this.canvas.width, this.canvas.height); - } - if (Config.layer.map.ambientLight.add){ - this.scene.add(new three.AmbientLight(Config.layer.map.ambientLight.color, Config.layer.map.ambientLight.intensity)); - } - if (Config.layer.map.directionalLight.add){ - this.scene.add(new three.DirectionalLight(Config.layer.map.directionalLight.color, Config.layer.map.directionalLight.intensity)); - } - if (Config.layer.map.pointLight.add){ - let pointLight = new three.PointLight(Config.layer.map.pointLight.color, Config.layer.map.pointLight.intensity, Config.layer.map.pointLight.distance); - pointLight.position.set(...Config.layer.map.pointLight.position); - this.scene.add(pointLight); - } - } - - moveTo(lat, lon, height = 38472.48763833733){ - // var coords = UnitsUtils.datumsToSpherical(44.266119,90.139228); - var coords = UnitsUtils.datumsToSpherical(lat,lon); - this.camera.position.set(coords.x, height, -coords.y); - this.controls.target.set(this.camera.position.x, 0, this.camera.position.z); - } - - moveToByCoords(coords){ - let offset = 50; - this.camera.position.set(coords.x, coords.y+offset, coords.z); - this.controls.target.set(coords.x, coords.y, coords.z); - } - - moveToByLL(lat, lon, distance = 384720){ - let dir = UnitsUtils.datumsToVector(lat, lon); - dir.multiplyScalar(UnitsUtils.EARTH_RADIUS + distance); - this.camera.position.copy(dir); - } - - - on(eventName, callback){ - this.listener.on(eventName, callback); - } - - setSceneBackground(color) { - this.scene.background = color; - } - - clearSceneBackground() { - this.scene.background = null; - } - - // 可用于添加灯光mesh等元素 - add(Object3D) { - if(Object3D ==null || Object3D ==undefined){ - return; - } - this.scene.add(Object3D); - } - - - remove(Object3D) { - if(Object3D ==null || Object3D ==undefined){ - return; - } - this.scene.remove(Object3D); - } - - openWaterConfig(){ - /** - * 打开渲染水系配置 - */ - // this.renderer.setPixelRatio( window.devicePixelRatio ); - this.renderer.toneMapping = three.ACESFilmicToneMapping; - this.renderer.toneMappingExposure = 0.5; - - // 添加天空 - this.sky = new Sky(); - this.sky.translateX = true; - this.sky.translateY = true; - this.sky.translateZ = true; - this.sky.rotateX = Math.PI / 2; - this.sky.scale.setScalar( Config.EARTH_RADIUS * 2 * Math.PI ); // 天空放大倍数 - this.scene.add( this.sky ); - const skyUniforms = this.sky.material.uniforms; - // 天空的配置 - skyUniforms[ 'turbidity' ].value = 10; - skyUniforms[ 'rayleigh' ].value = 2; - skyUniforms[ 'mieCoefficient' ].value = 0.005; - skyUniforms[ 'mieDirectionalG' ].value = 0.8; - // 旋转 设置为y朝上 - // sky.material.uniforms["up"].value = new THREE.Vector3(0, 1, 0); - - // 天空映射, 更新太阳位置 - this.pmremGenerator = new three.PMREMGenerator( this.renderer ); - this.sceneEnv = new three.Scene(); - this.renderTarget = null; - this.sun = new three.Vector3(); - this.updateSun(Config.SUNDEGREE, Config.SUNAZIMUTH); - } - - updateSun(elevation, azimuth) { - - const phi = three.MathUtils.degToRad( 90 - elevation ); - const theta = three.MathUtils.degToRad( azimuth ); - - this.sun.setFromSphericalCoords( 1, phi, theta ); - - this.sky.material.uniforms[ 'sunPosition' ].value.copy( this.sun ); - for (let water of this.waters){ - water.material.uniforms[ 'sunDirection' ].value.copy( this.sun ).normalize(); - } - if ( this.renderTarget !== null ) this.renderTarget.dispose(); - - this.sceneEnv.add( this.sky ); - this.renderTarget = this.pmremGenerator.fromScene( this.sceneEnv ); - this.scene.add( this.sky ); - - this.scene.environment = this.renderTarget.texture; - - - } - - /** - * 添加水系 - * @param {*} water - * @returns - */ - addWater(water) { - if(water ==null || water ==undefined){ - return; - } - this.scene.add(water); - this.waters.push(water); - } - - removeWater(water) { - if(water ==null || water ==undefined){ - return; - } - this.scene.remove(water); - let index = this.waters.indexOf(water); - if (index > -1) { - this.waters.splice(index, 1); - } - } - - setVisible(visible) { - this.visible = this.visible; - this.layerContainer.style.display = visible ? 'block' : 'none'; - } - /** - * @deprecated 不建议用 - * @param {number} opacity - */ - setOpacity(opacity) { - this.opacity = opacity; - this.layerContainer.style.opacity = opacity; - } - - /** - * 修改显示层级,默认越靠下的dom元素,显示上越靠上 - * @param {number} zIndex - */ - setZIndex(zIndex) { - this.zIndex = zIndex; - this.layerContainer.style.zIndex = this.zIndex; - } - - dispose() { - Element.removeLayer(id); - this.dispose = true; - this.animateId && cancelAnimationFrame(this.animateId); - this.mapView.root.dispose(); - this.mapView.dispose(); - } - - selectModel(insect){ - this.effectOutline.selectModel(insect); - } - - resize(){ - var width = window.innerWidth; - var height = window.innerHeight; - this.renderer.setSize(width, height); - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - if(Config.outLine.on){ - this.effectOutline.resize(width, height); - } - } - - animate(){ - this.animateId = requestAnimationFrame(this.animate.bind(this)); - if(this.base){ - this.controls.update(); - } - if (this.base){ - update(); //目前只有在基础地图中添加相机移动的动画,所以暂且只在base地图中进行渲染,后期可改成每个图层都进行渲染,或者必要时进行渲染。 - } - for(let water of this.waters){ - water.material.uniforms[ 'time' ].value += 1.0 / 60.0; - } - if (Config.outLine.on){ - this.effectOutline.render(); // 合成器渲染 - } else { - this.renderer.autoClear = true; - } - this.renderer.render(this.scene, this.camera); - } - - _raycast(meshes, recursive, faceExclude) { - const isects = this._raycaster.intersectObjects(meshes, recursive); - if (faceExclude) { - for (let i = 0; i < isects.length; i++) { - if (isects[i].face !== faceExclude) { - return isects[i]; - } - } - return null; - } - return isects.length > 0 ? isects[0] : null; - } - - _raycastFromMouse(mx, my, width, height, cam, meshes, recursive=false) { - const mouse = new three.Vector2( // normalized (-1 to +1) - (mx / width) * 2 - 1, - - (my / height) * 2 + 1); - // https://threejs.org/docs/#api/core/Raycaster - // update the picking ray with the camera and mouse position - this._raycaster.setFromCamera(mouse, cam); - return this._raycast(meshes, recursive, null); - } - - /** - * - * @param {*} mx 屏幕坐标x - * @param {*} my 屏幕坐标y - * @param {boolean} recursive 是否检查子节点,true 递归检查 - * @returns mesh - */ - raycastFromMouse(mx, my, recursive=false) { - //---- NG: 2x when starting with Chrome's inspector mobile - // const {width, height} = this.renderer.domElement; - // const {width, height} = this.canvas; - //---- OK - const {clientWidth, clientHeight} = this.canvas; - - return this._raycastFromMouse( - mx, my, clientWidth, clientHeight, this.camera, - this.mapView.children, recursive); - } - - insectALL(mx, my, recursive=false) { - const {clientWidth, clientHeight} = this.canvas; - - return this._raycastFromMouse( - mx, my, clientWidth, clientHeight, this.camera, - this.scene.children, recursive); - } - -} - -/** - * @param {BufferGeometry} geometry - * @param {number} drawMode - * @return {BufferGeometry} - */ -function toTrianglesDrawMode( geometry, drawMode ) { - - if ( drawMode === three.TrianglesDrawMode ) { - return geometry; - - } - - if ( drawMode === three.TriangleFanDrawMode || drawMode === three.TriangleStripDrawMode ) { - - let index = geometry.getIndex(); - - // generate index if not present - - if ( index === null ) { - - const indices = []; - - const position = geometry.getAttribute( 'position' ); - - if ( position !== undefined ) { - - for ( let i = 0; i < position.count; i ++ ) { - - indices.push( i ); - - } - - geometry.setIndex( indices ); - index = geometry.getIndex(); - - } else { - return geometry; - - } - - } - - // - - const numberOfTriangles = index.count - 2; - const newIndices = []; - - if ( drawMode === three.TriangleFanDrawMode ) { - - // gl.TRIANGLE_FAN - - for ( let i = 1; i <= numberOfTriangles; i ++ ) { - - newIndices.push( index.getX( 0 ) ); - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - - } - - } else { - - // gl.TRIANGLE_STRIP - - for ( let i = 0; i < numberOfTriangles; i ++ ) { - - if ( i % 2 === 0 ) { - - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i + 2 ) ); - - } else { - - newIndices.push( index.getX( i + 2 ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i ) ); - - } - - } - - } - - if ( ( newIndices.length / 3 ) !== numberOfTriangles ) ; - - // build final geometry - - const newGeometry = geometry.clone(); - newGeometry.setIndex( newIndices ); - newGeometry.clearGroups(); - - return newGeometry; - - } else { - return geometry; - - } - -} - -class GLTFLoader extends three.Loader { - - constructor( manager ) { - - super( manager ); - - this.dracoLoader = null; - this.ktx2Loader = null; - this.meshoptDecoder = null; - - this.pluginCallbacks = []; - - this.register( function ( parser ) { - - return new GLTFMaterialsClearcoatExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureBasisUExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureWebPExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureAVIFExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSheenExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsTransmissionExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsVolumeExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIorExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsEmissiveStrengthExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSpecularExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIridescenceExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsAnisotropyExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsBumpExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFLightsExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshoptCompression( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshGpuInstancing( parser ); - - } ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - let resourcePath; - - if ( this.resourcePath !== '' ) { - - resourcePath = this.resourcePath; - - } else if ( this.path !== '' ) { - - // If a base path is set, resources will be relative paths from that plus the relative path of the gltf file - // Example path = 'https://my-cnd-server.com/', url = 'assets/models/model.gltf' - // resourcePath = 'https://my-cnd-server.com/assets/models/' - // referenced resource 'model.bin' will be loaded from 'https://my-cnd-server.com/assets/models/model.bin' - // referenced resource '../textures/texture.png' will be loaded from 'https://my-cnd-server.com/assets/textures/texture.png' - const relativeUrl = three.LoaderUtils.extractUrlBase( url ); - resourcePath = three.LoaderUtils.resolveURL( relativeUrl, 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. - this.manager.itemStart( url ); - - const _onError = function ( e ) { - - if ( onError ) { - - onError( e ); - - } - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - }; - - const loader = new three.FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - 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 ); - - } - - setDRACOLoader( dracoLoader ) { - - this.dracoLoader = dracoLoader; - return this; - - } - - setDDSLoader() { - - throw new Error( - - 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' - - ); - - } - - setKTX2Loader( ktx2Loader ) { - - this.ktx2Loader = ktx2Loader; - return this; - - } - - setMeshoptDecoder( meshoptDecoder ) { - - this.meshoptDecoder = meshoptDecoder; - return this; - - } - - register( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - } - - unregister( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - } - - parse( data, path, onLoad, onError ) { - - let json; - const extensions = {}; - const plugins = {}; - const textDecoder = new TextDecoder(); - - if ( typeof data === 'string' ) { - - json = JSON.parse( data ); - - } else if ( data instanceof ArrayBuffer ) { - - const magic = textDecoder.decode( 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; - - } - - json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); - - } else { - - json = JSON.parse( textDecoder.decode( data ) ); - - } - - } else { - - json = data; - - } - - if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { - - if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); - return; - - } - - const parser = new GLTFParser( json, { - - path: path || this.resourcePath || '', - crossOrigin: this.crossOrigin, - requestHeader: this.requestHeader, - manager: this.manager, - ktx2Loader: this.ktx2Loader, - meshoptDecoder: this.meshoptDecoder - - } ); - - parser.fileLoader.setRequestHeader( this.requestHeader ); - - for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { - - const plugin = this.pluginCallbacks[ i ]( parser ); - - if ( ! plugin.name ) ; - - plugins[ plugin.name ] = plugin; - - // Workaround to avoid determining as unknown extension - // in addUnknownExtensionsToUserData(). - // Remove this workaround if we move all the existing - // extension handlers to plugin system - extensions[ plugin.name ] = true; - - } - - if ( json.extensionsUsed ) { - - for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { - - const extensionName = json.extensionsUsed[ i ]; - const extensionsRequired = json.extensionsRequired || []; - - switch ( extensionName ) { - - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; - - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; - - case EXTENSIONS.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension(); - break; - - case EXTENSIONS.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); - break; - - default: - - if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) ; - - } - - } - - } - - parser.setExtensions( extensions ); - parser.setPlugins( plugins ); - parser.parse( onLoad, onError ); - - } - - parseAsync( data, path ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.parse( data, path, resolve, reject ); - - } ); - - } - -} - -/* GLTFREGISTRY */ - -function GLTFRegistry() { - - let 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 ***********/ -/*********************************/ - -const EXTENSIONS = { - KHR_BINARY_GLTF: 'KHR_binary_glTF', - KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', - KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', - KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', - KHR_MATERIALS_IOR: 'KHR_materials_ior', - KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', - KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', - KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', - KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', - KHR_MATERIALS_ANISOTROPY: 'KHR_materials_anisotropy', - KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', - KHR_MATERIALS_VOLUME: 'KHR_materials_volume', - KHR_TEXTURE_BASISU: 'KHR_texture_basisu', - KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', - KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', - KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength', - EXT_MATERIALS_BUMP: 'EXT_materials_bump', - EXT_TEXTURE_WEBP: 'EXT_texture_webp', - EXT_TEXTURE_AVIF: 'EXT_texture_avif', - EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', - EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing' -}; - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ -class GLTFLightsExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; - - // Object3D instance caches - this.cache = { refs: {}, uses: {} }; - - } - - _markDefs() { - - const parser = this.parser; - const nodeDefs = this.parser.json.nodes || []; - - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.extensions - && nodeDef.extensions[ this.name ] - && nodeDef.extensions[ this.name ].light !== undefined ) { - - parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); - - } - - } - - } - - _loadLight( lightIndex ) { - - const parser = this.parser; - const cacheKey = 'light:' + lightIndex; - let dependency = parser.cache.get( cacheKey ); - - if ( dependency ) return dependency; - - const json = parser.json; - const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; - const lightDefs = extensions.lights || []; - const lightDef = lightDefs[ lightIndex ]; - let lightNode; - - const color = new three.Color( 0xffffff ); - - if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], three.LinearSRGBColorSpace ); - - const 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; - - assignExtrasToUserData( lightNode, lightDef ); - - if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; - - lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); - - dependency = Promise.resolve( lightNode ); - - parser.cache.add( cacheKey, dependency ); - - return dependency; - - } - - getDependency( type, index ) { - - if ( type !== 'light' ) return; - - return this._loadLight( index ); - - } - - createNodeAttachment( nodeIndex ) { - - const self = this; - const parser = this.parser; - const json = parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; - const lightIndex = lightDef.light; - - if ( lightIndex === undefined ) return null; - - return this._loadLight( lightIndex ).then( function ( light ) { - - return parser._getNodeRef( self.cache, lightIndex, light ); - - } ); - - } - -} - -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ -class GLTFMaterialsUnlitExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; - - } - - getMaterialType() { - - return three.MeshBasicMaterial; - - } - - extendParams( materialParams, materialDef, parser ) { - - const pending = []; - - materialParams.color = new three.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - const metallicRoughness = materialDef.pbrMetallicRoughness; - - if ( metallicRoughness ) { - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], three.LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, three.SRGBColorSpace ) ); - - } - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - */ -class GLTFMaterialsEmissiveStrengthExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const emissiveStrength = materialDef.extensions[ this.name ].emissiveStrength; - - if ( emissiveStrength !== undefined ) { - - materialParams.emissiveIntensity = emissiveStrength; - - } - - return Promise.resolve(); - - } - -} - -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - */ -class GLTFMaterialsClearcoatExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.clearcoatFactor !== undefined ) { - - materialParams.clearcoat = extension.clearcoatFactor; - - } - - if ( extension.clearcoatTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); - - } - - if ( extension.clearcoatRoughnessFactor !== undefined ) { - - materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; - - } - - if ( extension.clearcoatRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); - - } - - if ( extension.clearcoatNormalTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); - - if ( extension.clearcoatNormalTexture.scale !== undefined ) { - - const scale = extension.clearcoatNormalTexture.scale; - - materialParams.clearcoatNormalScale = new three.Vector2( scale, scale ); - - } - - } - - return Promise.all( pending ); - - } - -} - -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - */ -class GLTFMaterialsIridescenceExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.iridescenceFactor !== undefined ) { - - materialParams.iridescence = extension.iridescenceFactor; - - } - - if ( extension.iridescenceTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); - - } - - if ( extension.iridescenceIor !== undefined ) { - - materialParams.iridescenceIOR = extension.iridescenceIor; - - } - - if ( materialParams.iridescenceThicknessRange === undefined ) { - - materialParams.iridescenceThicknessRange = [ 100, 400 ]; - - } - - if ( extension.iridescenceThicknessMinimum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; - - } - - if ( extension.iridescenceThicknessMaximum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; - - } - - if ( extension.iridescenceThicknessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - */ -class GLTFMaterialsSheenExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - materialParams.sheenColor = new three.Color( 0, 0, 0 ); - materialParams.sheenRoughness = 0; - materialParams.sheen = 1; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.sheenColorFactor !== undefined ) { - - const colorFactor = extension.sheenColorFactor; - materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], three.LinearSRGBColorSpace ); - - } - - if ( extension.sheenRoughnessFactor !== undefined ) { - - materialParams.sheenRoughness = extension.sheenRoughnessFactor; - - } - - if ( extension.sheenColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, three.SRGBColorSpace ) ); - - } - - if ( extension.sheenRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - * Draft: https://github.com/KhronosGroup/glTF/pull/1698 - */ -class GLTFMaterialsTransmissionExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.transmissionFactor !== undefined ) { - - materialParams.transmission = extension.transmissionFactor; - - } - - if ( extension.transmissionTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ -class GLTFMaterialsVolumeExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; - - if ( extension.thicknessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); - - } - - materialParams.attenuationDistance = extension.attenuationDistance || Infinity; - - const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; - materialParams.attenuationColor = new three.Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], three.LinearSRGBColorSpace ); - - return Promise.all( pending ); - - } - -} - -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - */ -class GLTFMaterialsIorExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IOR; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const extension = materialDef.extensions[ this.name ]; - - materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; - - return Promise.resolve(); - - } - -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - */ -class GLTFMaterialsSpecularExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; - - if ( extension.specularTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); - - } - - const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; - materialParams.specularColor = new three.Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], three.LinearSRGBColorSpace ); - - if ( extension.specularColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, three.SRGBColorSpace ) ); - - } - - return Promise.all( pending ); - - } - -} - - -/** - * Materials bump Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump - */ -class GLTFMaterialsBumpExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_MATERIALS_BUMP; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.bumpScale = extension.bumpFactor !== undefined ? extension.bumpFactor : 1.0; - - if ( extension.bumpTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'bumpMap', extension.bumpTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials anisotropy Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy - */ -class GLTFMaterialsAnisotropyExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.anisotropyStrength !== undefined ) { - - materialParams.anisotropy = extension.anisotropyStrength; - - } - - if ( extension.anisotropyRotation !== undefined ) { - - materialParams.anisotropyRotation = extension.anisotropyRotation; - - } - - if ( extension.anisotropyTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'anisotropyMap', extension.anisotropyTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * BasisU Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu - */ -class GLTFTextureBasisUExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_TEXTURE_BASISU; - - } - - loadTexture( textureIndex ) { - - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ this.name ]; - const loader = parser.options.ktx2Loader; - - if ( ! loader ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); - - } else { - - // Assumes that the extension is optional and that a fallback texture is present - return null; - - } - - } - - return parser.loadTextureImage( textureIndex, extension.source, loader ); - - } - -} - -/** - * WebP Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp - */ -class GLTFTextureWebPExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_WEBP; - this.isSupported = null; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. Support for lossy images doesn't guarantee support for all - // WebP images, unfortunately. - image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; - - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; - - } - -} - -/** - * AVIF Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif - */ -class GLTFTextureAVIFExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_AVIF; - this.isSupported = null; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: AVIF required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. - image.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkQAAAAB8dSLfI='; - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; - - } - -} - -/** - * meshopt BufferView Compression Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression - */ -class GLTFMeshoptCompression { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; - this.parser = parser; - - } - - loadBufferView( index ) { - - const json = this.parser.json; - const bufferView = json.bufferViews[ index ]; - - if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { - - const extensionDef = bufferView.extensions[ this.name ]; - - const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); - const decoder = this.parser.options.meshoptDecoder; - - if ( ! decoder || ! decoder.supported ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); - - } else { - - // Assumes that the extension is optional and that fallback buffer data is present - return null; - - } - - } - - return buffer.then( function ( res ) { - - const byteOffset = extensionDef.byteOffset || 0; - const byteLength = extensionDef.byteLength || 0; - - const count = extensionDef.count; - const stride = extensionDef.byteStride; - - const source = new Uint8Array( res, byteOffset, byteLength ); - - if ( decoder.decodeGltfBufferAsync ) { - - return decoder.decodeGltfBufferAsync( count, stride, source, extensionDef.mode, extensionDef.filter ).then( function ( res ) { - - return res.buffer; - - } ); - - } else { - - // Support for MeshoptDecoder 0.18 or earlier, without decodeGltfBufferAsync - return decoder.ready.then( function () { - - const result = new ArrayBuffer( count * stride ); - decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); - return result; - - } ); - - } - - } ); - - } else { - - return null; - - } - - } - -} - -/** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - * - */ -class GLTFMeshGpuInstancing { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; - this.parser = parser; - - } - - createNodeMesh( nodeIndex ) { - - const json = this.parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] || - nodeDef.mesh === undefined ) { - - return null; - - } - - const meshDef = json.meshes[ nodeDef.mesh ]; - - // No Points or Lines + Instancing support yet - - for ( const primitive of meshDef.primitives ) { - - if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && - primitive.mode !== undefined ) { - - return null; - - } - - } - - const extensionDef = nodeDef.extensions[ this.name ]; - const attributesDef = extensionDef.attributes; - - // @TODO: Can we support InstancedMesh + SkinnedMesh? - - const pending = []; - const attributes = {}; - - for ( const key in attributesDef ) { - - pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => { - - attributes[ key ] = accessor; - return attributes[ key ]; - - } ) ); - - } - - if ( pending.length < 1 ) { - - return null; - - } - - pending.push( this.parser.createNodeMesh( nodeIndex ) ); - - return Promise.all( pending ).then( results => { - - const nodeObject = results.pop(); - const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ]; - const count = results[ 0 ].count; // All attribute counts should be same - const instancedMeshes = []; - - for ( const mesh of meshes ) { - - // Temporal variables - const m = new three.Matrix4(); - const p = new three.Vector3(); - const q = new three.Quaternion(); - const s = new three.Vector3( 1, 1, 1 ); - - const instancedMesh = new three.InstancedMesh( mesh.geometry, mesh.material, count ); - - for ( let i = 0; i < count; i ++ ) { - - if ( attributes.TRANSLATION ) { - - p.fromBufferAttribute( attributes.TRANSLATION, i ); - - } - - if ( attributes.ROTATION ) { - - q.fromBufferAttribute( attributes.ROTATION, i ); - - } - - if ( attributes.SCALE ) { - - s.fromBufferAttribute( attributes.SCALE, i ); - - } - - instancedMesh.setMatrixAt( i, m.compose( p, q, s ) ); - - } - - // Add instance attributes to the geometry, excluding TRS. - for ( const attributeName in attributes ) { - - if ( attributeName === '_COLOR_0' ) { - - const attr = attributes[ attributeName ]; - instancedMesh.instanceColor = new three.InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ); - - } else if ( attributeName !== 'TRANSLATION' && - attributeName !== 'ROTATION' && - attributeName !== 'SCALE' ) { - - mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] ); - - } - - } - - // Just in case - three.Object3D.prototype.copy.call( instancedMesh, mesh ); - - this.parser.assignFinalMaterial( instancedMesh ); - - instancedMeshes.push( instancedMesh ); - - } - - if ( nodeObject.isGroup ) { - - nodeObject.clear(); - - nodeObject.add( ... instancedMeshes ); - - return nodeObject; - - } - - return instancedMeshes[ 0 ]; - - } ); - - } - -} - -/* BINARY EXTENSION */ -const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; -const BINARY_EXTENSION_HEADER_LENGTH = 12; -const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; - -class GLTFBinaryExtension { - - constructor( data ) { - - this.name = EXTENSIONS.KHR_BINARY_GLTF; - this.content = null; - this.body = null; - - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); - const textDecoder = new TextDecoder(); - - this.header = { - magic: textDecoder.decode( 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.' ); - - } - - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - let chunkIndex = 0; - - while ( chunkIndex < chunkContentsLength ) { - - const chunkLength = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - const chunkType = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); - this.content = textDecoder.decode( contentArray ); - - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - - const 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/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression - */ -class GLTFDracoMeshCompressionExtension { - - constructor( 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; - this.dracoLoader.preload(); - - } - - decodePrimitive( primitive, parser ) { - - const json = this.json; - const dracoLoader = this.dracoLoader; - const bufferViewIndex = primitive.extensions[ this.name ].bufferView; - const gltfAttributeMap = primitive.extensions[ this.name ].attributes; - const threeAttributeMap = {}; - const attributeNormalizedMap = {}; - const attributeTypeMap = {}; - - for ( const attributeName in gltfAttributeMap ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; - - } - - for ( const attributeName in primitive.attributes ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - if ( gltfAttributeMap[ attributeName ] !== undefined ) { - - const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - attributeTypeMap[ threeAttributeName ] = componentType.name; - attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; - - } - - } - - return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - - return new Promise( function ( resolve, reject ) { - - dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - - for ( const attributeName in geometry.attributes ) { - - const attribute = geometry.attributes[ attributeName ]; - const normalized = attributeNormalizedMap[ attributeName ]; - - if ( normalized !== undefined ) attribute.normalized = normalized; - - } - - resolve( geometry ); - - }, threeAttributeMap, attributeTypeMap, three.LinearSRGBColorSpace, reject ); - - } ); - - } ); - - } - -} - -/** - * Texture Transform Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform - */ -class GLTFTextureTransformExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; - - } - - extendTexture( texture, transform ) { - - if ( ( transform.texCoord === undefined || transform.texCoord === texture.channel ) - && transform.offset === undefined - && transform.rotation === undefined - && transform.scale === undefined ) { - - // See https://github.com/mrdoob/three.js/issues/21819. - return texture; - - } - - texture = texture.clone(); - - if ( transform.texCoord !== undefined ) { - - texture.channel = transform.texCoord; - - } - - 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 ); - - } - - texture.needsUpdate = true; - - return texture; - - } - -} - -/** - * Mesh Quantization Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization - */ -class GLTFMeshQuantizationExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; - - } - -} - -/*********************************/ -/********** INTERPOLATION ********/ -/*********************************/ - -// Spline Interpolation -// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation -class GLTFCubicSplineInterpolant extends three.Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - copySampleValue_( index ) { - - // Copies a sample value to the result buffer. See description of glTF - // CUBICSPLINE values layout in interpolate_() function below. - - const result = this.resultBuffer, - values = this.sampleValues, - valueSize = this.valueSize, - offset = index * valueSize * 3 + valueSize; - - for ( let i = 0; i !== valueSize; i ++ ) { - - result[ i ] = values[ offset + i ]; - - } - - return result; - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer; - const values = this.sampleValues; - const stride = this.valueSize; - - const stride2 = stride * 2; - const stride3 = stride * 3; - - const td = t1 - t0; - - const p = ( t - t0 ) / td; - const pp = p * p; - const ppp = pp * p; - - const offset1 = i1 * stride3; - const offset0 = offset1 - stride3; - - const s2 = - 2 * ppp + 3 * pp; - const s3 = ppp - pp; - const s0 = 1 - s2; - const s1 = s3 - pp + p; - - // Layout of keyframe output values for CUBICSPLINE animations: - // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] - for ( let i = 0; i !== stride; i ++ ) { - - const p0 = values[ offset0 + i + stride ]; // splineVertex_k - const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) - const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 - const 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; - - } - -} - -const _q = new three.Quaternion(); - -class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { - - interpolate_( i1, t0, t, t1 ) { - - const result = super.interpolate_( i1, t0, t, t1 ); - - _q.fromArray( result ).normalize().toArray( result ); - - return result; - - } - -} - - -/*********************************/ -/********** INTERNALS ************/ -/*********************************/ - -/* CONSTANTS */ - -const 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 -}; - -const WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array -}; - -const WEBGL_FILTERS = { - 9728: three.NearestFilter, - 9729: three.LinearFilter, - 9984: three.NearestMipmapNearestFilter, - 9985: three.LinearMipmapNearestFilter, - 9986: three.NearestMipmapLinearFilter, - 9987: three.LinearMipmapLinearFilter -}; - -const WEBGL_WRAPPINGS = { - 33071: three.ClampToEdgeWrapping, - 33648: three.MirroredRepeatWrapping, - 10497: three.RepeatWrapping -}; - -const WEBGL_TYPE_SIZES = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 -}; - -const ATTRIBUTES = { - POSITION: 'position', - NORMAL: 'normal', - TANGENT: 'tangent', - TEXCOORD_0: 'uv', - TEXCOORD_1: 'uv1', - TEXCOORD_2: 'uv2', - TEXCOORD_3: 'uv3', - COLOR_0: 'color', - WEIGHTS_0: 'skinWeight', - JOINTS_0: 'skinIndex', -}; - -const PATH_PROPERTIES = { - scale: 'scale', - translation: 'position', - rotation: 'quaternion', - weights: 'morphTargetInfluences' -}; - -const 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 -}; - -const ALPHA_MODES = { - OPAQUE: 'OPAQUE', - MASK: 'MASK', - BLEND: 'BLEND' -}; - -/** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material - */ -function createDefaultMaterial( cache ) { - - if ( cache[ 'DefaultMaterial' ] === undefined ) { - - cache[ 'DefaultMaterial' ] = new three.MeshStandardMaterial( { - color: 0xFFFFFF, - emissive: 0x000000, - metalness: 1, - roughness: 1, - transparent: false, - depthTest: true, - side: three.FrontSide - } ); - - } - - return cache[ 'DefaultMaterial' ]; - -} - -function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { - - // Add unknown glTF extensions to an object's userData. - - for ( const name in objectDef.extensions ) { - - if ( knownExtensions[ name ] === undefined ) { - - object.userData.gltfExtensions = object.userData.gltfExtensions || {}; - object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; - - } - - } - -} - -/** - * @param {Object3D|Material|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 ); - - } - - } - -} - -/** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets - * - * @param {BufferGeometry} geometry - * @param {Array} targets - * @param {GLTFParser} parser - * @return {Promise} - */ -function addMorphTargets( geometry, targets, parser ) { - - let hasMorphPosition = false; - let hasMorphNormal = false; - let hasMorphColor = false; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) hasMorphPosition = true; - if ( target.NORMAL !== undefined ) hasMorphNormal = true; - if ( target.COLOR_0 !== undefined ) hasMorphColor = true; - - if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; - - } - - if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); - - const pendingPositionAccessors = []; - const pendingNormalAccessors = []; - const pendingColorAccessors = []; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( hasMorphPosition ) { - - const pendingAccessor = target.POSITION !== undefined - ? parser.getDependency( 'accessor', target.POSITION ) - : geometry.attributes.position; - - pendingPositionAccessors.push( pendingAccessor ); - - } - - if ( hasMorphNormal ) { - - const pendingAccessor = target.NORMAL !== undefined - ? parser.getDependency( 'accessor', target.NORMAL ) - : geometry.attributes.normal; - - pendingNormalAccessors.push( pendingAccessor ); - - } - - if ( hasMorphColor ) { - - const pendingAccessor = target.COLOR_0 !== undefined - ? parser.getDependency( 'accessor', target.COLOR_0 ) - : geometry.attributes.color; - - pendingColorAccessors.push( pendingAccessor ); - - } - - } - - return Promise.all( [ - Promise.all( pendingPositionAccessors ), - Promise.all( pendingNormalAccessors ), - Promise.all( pendingColorAccessors ) - ] ).then( function ( accessors ) { - - const morphPositions = accessors[ 0 ]; - const morphNormals = accessors[ 1 ]; - const morphColors = accessors[ 2 ]; - - if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; - if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; - if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; - geometry.morphTargetsRelative = true; - - return geometry; - - } ); - -} - -/** - * @param {Mesh} mesh - * @param {GLTF.Mesh} meshDef - */ -function updateMorphTargets( mesh, meshDef ) { - - mesh.updateMorphTargets(); - - if ( meshDef.weights !== undefined ) { - - for ( let 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 ) ) { - - const targetNames = meshDef.extras.targetNames; - - if ( mesh.morphTargetInfluences.length === targetNames.length ) { - - mesh.morphTargetDictionary = {}; - - for ( let i = 0, il = targetNames.length; i < il; i ++ ) { - - mesh.morphTargetDictionary[ targetNames[ i ] ] = i; - - } - - } - - } - -} - -function createPrimitiveKey( primitiveDef ) { - - let geometryKey; - - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; - - if ( dracoExtension ) { - - geometryKey = 'draco:' + dracoExtension.bufferView - + ':' + dracoExtension.indices - + ':' + createAttributesKey( dracoExtension.attributes ); - - } else { - - geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; - - } - - if ( primitiveDef.targets !== undefined ) { - - for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - - geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); - - } - - } - - return geometryKey; - -} - -function createAttributesKey( attributes ) { - - let attributesKey = ''; - - const keys = Object.keys( attributes ).sort(); - - for ( let i = 0, il = keys.length; i < il; i ++ ) { - - attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; - - } - - return attributesKey; - -} - -function getNormalizedComponentScale( constructor ) { - - // Reference: - // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data - - switch ( constructor ) { - - case Int8Array: - return 1 / 127; - - case Uint8Array: - return 1 / 255; - - case Int16Array: - return 1 / 32767; - - case Uint16Array: - return 1 / 65535; - - default: - throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); - - } - -} - -function getImageURIMimeType( uri ) { - - if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; - if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; - - return 'image/png'; - -} - -const _identityMatrix = new three.Matrix4(); - -/* GLTF PARSER */ - -class GLTFParser { - - constructor( json = {}, options = {} ) { - - this.json = json; - this.extensions = {}; - this.plugins = {}; - this.options = options; - - // loader object cache - this.cache = new GLTFRegistry(); - - // associations between Three.js objects and glTF elements - this.associations = new Map(); - - // BufferGeometry caching - this.primitiveCache = {}; - - // Node cache - this.nodeCache = {}; - - // Object3D instance caches - this.meshCache = { refs: {}, uses: {} }; - this.cameraCache = { refs: {}, uses: {} }; - this.lightCache = { refs: {}, uses: {} }; - - this.sourceCache = {}; - this.textureCache = {}; - - // Track node names, to ensure no duplicates - this.nodeNamesUsed = {}; - - // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the - // expensive work of uploading a texture to the GPU off the main thread. - - let isSafari = false; - let isFirefox = false; - let firefoxVersion = - 1; - - if ( typeof navigator !== 'undefined' ) { - - isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true; - isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1; - firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; - - } - - if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) { - - this.textureLoader = new three.TextureLoader( this.options.manager ); - - } else { - - this.textureLoader = new three.ImageBitmapLoader( this.options.manager ); - - } - - this.textureLoader.setCrossOrigin( this.options.crossOrigin ); - this.textureLoader.setRequestHeader( this.options.requestHeader ); - - this.fileLoader = new three.FileLoader( this.options.manager ); - this.fileLoader.setResponseType( 'arraybuffer' ); - - if ( this.options.crossOrigin === 'use-credentials' ) { - - this.fileLoader.setWithCredentials( true ); - - } - - } - - setExtensions( extensions ) { - - this.extensions = extensions; - - } - - setPlugins( plugins ) { - - this.plugins = plugins; - - } - - parse( onLoad, onError ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - // Clear the loader cache - this.cache.removeAll(); - this.nodeCache = {}; - - // Mark the special nodes/meshes in json for efficient parse - this._invokeAll( function ( ext ) { - - return ext._markDefs && ext._markDefs(); - - } ); - - Promise.all( this._invokeAll( function ( ext ) { - - return ext.beforeRoot && ext.beforeRoot(); - - } ) ).then( function () { - - return Promise.all( [ - - parser.getDependencies( 'scene' ), - parser.getDependencies( 'animation' ), - parser.getDependencies( 'camera' ), - - ] ); - - } ).then( function ( dependencies ) { - - const 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 ); - - assignExtrasToUserData( result, json ); - - return Promise.all( parser._invokeAll( function ( ext ) { - - return ext.afterRoot && ext.afterRoot( result ); - - } ) ).then( function () { - - onLoad( result ); - - } ); - - } ).catch( onError ); - - } - - /** - * Marks the special nodes/meshes in json for efficient parse. - */ - _markDefs() { - - const nodeDefs = this.json.nodes || []; - const skinDefs = this.json.skins || []; - const meshDefs = this.json.meshes || []; - - // Nothing in the node definition indicates whether it is a Bone or an - // Object3D. Use the skins' joint references to mark bones. - for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { - - const joints = skinDefs[ skinIndex ].joints; - - for ( let i = 0, il = joints.length; i < il; i ++ ) { - - nodeDefs[ joints[ i ] ].isBone = true; - - } - - } - - // Iterate over all nodes, marking references to shared resources, - // as well as skeleton joints. - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.mesh !== undefined ) { - - this._addNodeRef( this.meshCache, 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; - - } - - } - - if ( nodeDef.camera !== undefined ) { - - this._addNodeRef( this.cameraCache, nodeDef.camera ); - - } - - } - - } - - /** - * Counts references to shared node / Object3D resources. These resources - * can be reused, or "instantiated", at multiple nodes in the scene - * hierarchy. Mesh, Camera, and Light instances are instantiated and must - * be marked. Non-scenegraph resources (like Materials, Geometries, and - * Textures) can be reused directly and are not marked here. - * - * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. - */ - _addNodeRef( cache, index ) { - - if ( index === undefined ) return; - - if ( cache.refs[ index ] === undefined ) { - - cache.refs[ index ] = cache.uses[ index ] = 0; - - } - - cache.refs[ index ] ++; - - } - - /** Returns a reference to a shared resource, cloning it if necessary. */ - _getNodeRef( cache, index, object ) { - - if ( cache.refs[ index ] <= 1 ) return object; - - const ref = object.clone(); - - // Propagates mappings to the cloned object, prevents mappings on the - // original object from being lost. - const updateMappings = ( original, clone ) => { - - const mappings = this.associations.get( original ); - if ( mappings != null ) { - - this.associations.set( clone, mappings ); - - } - - for ( const [ i, child ] of original.children.entries() ) { - - updateMappings( child, clone.children[ i ] ); - - } - - }; - - updateMappings( object, ref ); - - ref.name += '_instance_' + ( cache.uses[ index ] ++ ); - - return ref; - - } - - _invokeOne( func ) { - - const extensions = Object.values( this.plugins ); - extensions.push( this ); - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) return result; - - } - - return null; - - } - - _invokeAll( func ) { - - const extensions = Object.values( this.plugins ); - extensions.unshift( this ); - - const pending = []; - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) pending.push( result ); - - } - - return pending; - - } - - /** - * Requests the specified dependency asynchronously, with caching. - * @param {string} type - * @param {number} index - * @return {Promise} - */ - getDependency( type, index ) { - - const cacheKey = type + ':' + index; - let dependency = this.cache.get( cacheKey ); - - if ( ! dependency ) { - - switch ( type ) { - - case 'scene': - dependency = this.loadScene( index ); - break; - - case 'node': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadNode && ext.loadNode( index ); - - } ); - break; - - case 'mesh': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMesh && ext.loadMesh( index ); - - } ); - break; - - case 'accessor': - dependency = this.loadAccessor( index ); - break; - - case 'bufferView': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadBufferView && ext.loadBufferView( index ); - - } ); - break; - - case 'buffer': - dependency = this.loadBuffer( index ); - break; - - case 'material': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMaterial && ext.loadMaterial( index ); - - } ); - break; - - case 'texture': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadTexture && ext.loadTexture( index ); - - } ); - break; - - case 'skin': - dependency = this.loadSkin( index ); - break; - - case 'animation': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadAnimation && ext.loadAnimation( index ); - - } ); - break; - - case 'camera': - dependency = this.loadCamera( index ); - break; - - default: - dependency = this._invokeOne( function ( ext ) { - - return ext != this && ext.getDependency && ext.getDependency( type, index ); - - } ); - - if ( ! dependency ) { - - throw new Error( 'Unknown type: ' + type ); - - } - - break; - - } - - this.cache.add( cacheKey, dependency ); - - } - - return dependency; - - } - - /** - * Requests all dependencies of the specified type asynchronously, with caching. - * @param {string} type - * @return {Promise>} - */ - getDependencies( type ) { - - let dependencies = this.cache.get( type ); - - if ( ! dependencies ) { - - const parser = this; - const 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} - */ - loadBuffer( bufferIndex ) { - - const bufferDef = this.json.buffers[ bufferIndex ]; - const 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 ); - - } - - const options = this.options; - - return new Promise( function ( resolve, reject ) { - - loader.load( three.LoaderUtils.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} - */ - loadBufferView( bufferViewIndex ) { - - const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; - - return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - - const byteLength = bufferViewDef.byteLength || 0; - const 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} - */ - loadAccessor( accessorIndex ) { - - const parser = this; - const json = this.json; - - const accessorDef = this.json.accessors[ accessorIndex ]; - - if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - const normalized = accessorDef.normalized === true; - - const array = new TypedArray( accessorDef.count * itemSize ); - return Promise.resolve( new three.BufferAttribute( array, itemSize, normalized ) ); - - } - - const 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 ) { - - const bufferView = bufferViews[ 0 ]; - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - const elementBytes = TypedArray.BYTES_PER_ELEMENT; - const itemBytes = elementBytes * itemSize; - const byteOffset = accessorDef.byteOffset || 0; - const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; - const normalized = accessorDef.normalized === true; - let 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 - const ibSlice = Math.floor( byteOffset / byteStride ); - const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; - let 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 ) { - - const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; - - const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; - const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; - - const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); - const 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 = new three.BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); - - } - - for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { - - const 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} - */ - loadTexture( textureIndex ) { - - const json = this.json; - const options = this.options; - const textureDef = json.textures[ textureIndex ]; - const sourceIndex = textureDef.source; - const sourceDef = json.images[ sourceIndex ]; - - let loader = this.textureLoader; - - if ( sourceDef.uri ) { - - const handler = options.manager.getHandler( sourceDef.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.loadTextureImage( textureIndex, sourceIndex, loader ); - - } - - loadTextureImage( textureIndex, sourceIndex, loader ) { - - const parser = this; - const json = this.json; - - const textureDef = json.textures[ textureIndex ]; - const sourceDef = json.images[ sourceIndex ]; - - const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; - - if ( this.textureCache[ cacheKey ] ) { - - // See https://github.com/mrdoob/three.js/issues/21559. - return this.textureCache[ cacheKey ]; - - } - - const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { - - texture.flipY = false; - - texture.name = textureDef.name || sourceDef.name || ''; - - if ( texture.name === '' && typeof sourceDef.uri === 'string' && sourceDef.uri.startsWith( 'data:image/' ) === false ) { - - texture.name = sourceDef.uri; - - } - - const samplers = json.samplers || {}; - const 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; - - parser.associations.set( texture, { textures: textureIndex } ); - - return texture; - - } ).catch( function () { - - return null; - - } ); - - this.textureCache[ cacheKey ] = promise; - - return promise; - - } - - loadImageSource( sourceIndex, loader ) { - - const parser = this; - const json = this.json; - const options = this.options; - - if ( this.sourceCache[ sourceIndex ] !== undefined ) { - - return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); - - } - - const sourceDef = json.images[ sourceIndex ]; - - const URL = self.URL || self.webkitURL; - - let sourceURI = sourceDef.uri || ''; - let isObjectURL = false; - - if ( sourceDef.bufferView !== undefined ) { - - // Load binary image data from bufferView, if provided. - - sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { - - isObjectURL = true; - const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; - - } ); - - } else if ( sourceDef.uri === undefined ) { - - throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); - - } - - const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { - - return new Promise( function ( resolve, reject ) { - - let onLoad = resolve; - - if ( loader.isImageBitmapLoader === true ) { - - onLoad = function ( imageBitmap ) { - - const texture = new three.Texture( imageBitmap ); - texture.needsUpdate = true; - - resolve( texture ); - - }; - - } - - loader.load( three.LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); - - } ); - - } ).then( function ( texture ) { - - // Clean up resources and configure Texture. - - if ( isObjectURL === true ) { - - URL.revokeObjectURL( sourceURI ); - - } - - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); - - return texture; - - } ).catch( function ( error ) { - throw error; - - } ); - - this.sourceCache[ sourceIndex ] = promise; - return promise; - - } - - /** - * Asynchronously assigns a texture to the given material parameters. - * @param {Object} materialParams - * @param {string} mapName - * @param {Object} mapDef - * @return {Promise} - */ - assignTexture( materialParams, mapName, mapDef, colorSpace ) { - - const parser = this; - - return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { - - if ( ! texture ) return null; - - if ( mapDef.texCoord !== undefined && mapDef.texCoord > 0 ) { - - texture = texture.clone(); - texture.channel = mapDef.texCoord; - - } - - if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; - - if ( transform ) { - - const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); - parser.associations.set( texture, gltfReference ); - - } - - } - - if ( colorSpace !== undefined ) { - - texture.colorSpace = colorSpace; - - } - - materialParams[ mapName ] = texture; - - return 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 accommodate different primitive types, defines, etc. New materials will - * be created if necessary, and reused from a cache. - * @param {Object3D} mesh Mesh, Line, or Points instance. - */ - assignFinalMaterial( mesh ) { - - const geometry = mesh.geometry; - let material = mesh.material; - - const useDerivativeTangents = geometry.attributes.tangent === undefined; - const useVertexColors = geometry.attributes.color !== undefined; - const useFlatShading = geometry.attributes.normal === undefined; - - if ( mesh.isPoints ) { - - const cacheKey = 'PointsMaterial:' + material.uuid; - - let 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.sizeAttenuation = false; // glTF spec says points should be 1px - - this.cache.add( cacheKey, pointsMaterial ); - - } - - material = pointsMaterial; - - } else if ( mesh.isLine ) { - - const cacheKey = 'LineBasicMaterial:' + material.uuid; - - let lineMaterial = this.cache.get( cacheKey ); - - if ( ! lineMaterial ) { - - lineMaterial = new three.LineBasicMaterial(); - three.Material.prototype.copy.call( lineMaterial, material ); - lineMaterial.color.copy( material.color ); - lineMaterial.map = material.map; - - this.cache.add( cacheKey, lineMaterial ); - - } - - material = lineMaterial; - - } - - // Clone the material if it will be modified - if ( useDerivativeTangents || useVertexColors || useFlatShading ) { - - let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; - - if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; - if ( useVertexColors ) cacheKey += 'vertex-colors:'; - if ( useFlatShading ) cacheKey += 'flat-shading:'; - - let cachedMaterial = this.cache.get( cacheKey ); - - if ( ! cachedMaterial ) { - - cachedMaterial = material.clone(); - - if ( useVertexColors ) cachedMaterial.vertexColors = true; - if ( useFlatShading ) cachedMaterial.flatShading = true; - - if ( useDerivativeTangents ) { - - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; - if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; - - } - - this.cache.add( cacheKey, cachedMaterial ); - - this.associations.set( cachedMaterial, this.associations.get( material ) ); - - } - - material = cachedMaterial; - - } - - mesh.material = material; - - } - - getMaterialType( /* materialIndex */ ) { - - return three.MeshStandardMaterial; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials - * @param {number} materialIndex - * @return {Promise} - */ - loadMaterial( materialIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - const materialDef = json.materials[ materialIndex ]; - - let materialType; - const materialParams = {}; - const materialExtensions = materialDef.extensions || {}; - - const pending = []; - - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - - const 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 - - const metallicRoughness = materialDef.pbrMetallicRoughness || {}; - - materialParams.color = new three.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], three.LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, three.SRGBColorSpace ) ); - - } - - 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 ) ); - - } - - materialType = this._invokeOne( function ( ext ) { - - return ext.getMaterialType && ext.getMaterialType( materialIndex ); - - } ); - - pending.push( Promise.all( this._invokeAll( function ( ext ) { - - return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); - - } ) ) ); - - } - - if ( materialDef.doubleSided === true ) { - - materialParams.side = three.DoubleSide; - - } - - const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - - if ( alphaMode === ALPHA_MODES.BLEND ) { - - materialParams.transparent = true; - - // See: https://github.com/mrdoob/three.js/issues/17706 - materialParams.depthWrite = false; - - } 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 ) { - - const scale = materialDef.normalTexture.scale; - - materialParams.normalScale.set( scale, 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 ) { - - const emissiveFactor = materialDef.emissiveFactor; - materialParams.emissive = new three.Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], three.LinearSRGBColorSpace ); - - } - - if ( materialDef.emissiveTexture !== undefined && materialType !== three.MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, three.SRGBColorSpace ) ); - - } - - return Promise.all( pending ).then( function () { - - const material = new materialType( materialParams ); - - if ( materialDef.name ) material.name = materialDef.name; - - assignExtrasToUserData( material, materialDef ); - - parser.associations.set( material, { materials: materialIndex } ); - - if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); - - return material; - - } ); - - } - - /** When Object3D instances are targeted by animation, they need unique names. */ - createUniqueName( originalName ) { - - const sanitizedName = three.PropertyBinding.sanitizeNodeName( originalName || '' ); - - if ( sanitizedName in this.nodeNamesUsed ) { - - return sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] ); - - } else { - - this.nodeNamesUsed[ sanitizedName ] = 0; - - return sanitizedName; - - } - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry - * - * Creates BufferGeometries from primitives. - * - * @param {Array} primitives - * @return {Promise>} - */ - loadGeometries( primitives ) { - - const parser = this; - const extensions = this.extensions; - const cache = this.primitiveCache; - - function createDracoPrimitive( primitive ) { - - return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] - .decodePrimitive( primitive, parser ) - .then( function ( geometry ) { - - return addPrimitiveAttributes( geometry, primitive, parser ); - - } ); - - } - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey( primitive ); - - // See if we've already created this geometry - const cached = cache[ cacheKey ]; - - if ( cached ) { - - // Use the cached geometry if it exists - pending.push( cached.promise ); - - } else { - - let 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} - */ - loadMesh( meshIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - const meshDef = json.meshes[ meshIndex ]; - const primitives = meshDef.primitives; - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const material = primitives[ i ].material === undefined - ? createDefaultMaterial( this.cache ) - : this.getDependency( 'material', primitives[ i ].material ); - - pending.push( material ); - - } - - pending.push( parser.loadGeometries( primitives ) ); - - return Promise.all( pending ).then( function ( results ) { - - const materials = results.slice( 0, results.length - 1 ); - const geometries = results[ results.length - 1 ]; - - const meshes = []; - - for ( let i = 0, il = geometries.length; i < il; i ++ ) { - - const geometry = geometries[ i ]; - const primitive = primitives[ i ]; - - // 1. create Mesh - - let mesh; - - const material = materials[ 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 ) { - - // normalize skin weights to fix malformed assets (see #15319) - mesh.normalizeSkinWeights(); - - } - - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - - mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleStripDrawMode ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - - mesh.geometry = toTrianglesDrawMode( mesh.geometry, 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 = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - - assignExtrasToUserData( mesh, meshDef ); - - if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); - - parser.assignFinalMaterial( mesh ); - - meshes.push( mesh ); - - } - - for ( let i = 0, il = meshes.length; i < il; i ++ ) { - - parser.associations.set( meshes[ i ], { - meshes: meshIndex, - primitives: i - } ); - - } - - if ( meshes.length === 1 ) { - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); - - return meshes[ 0 ]; - - } - - const group = new three.Group(); - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); - - parser.associations.set( group, { meshes: meshIndex } ); - - for ( let 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} - */ - loadCamera( cameraIndex ) { - - let camera; - const cameraDef = this.json.cameras[ cameraIndex ]; - const params = cameraDef[ cameraDef.type ]; - - if ( ! params ) { - return; - - } - - if ( cameraDef.type === 'perspective' ) { - - camera = new three.PerspectiveCamera( three.MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); - - } else if ( cameraDef.type === 'orthographic' ) { - - camera = new three.OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); - - } - - if ( cameraDef.name ) camera.name = this.createUniqueName( 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} - */ - loadSkin( skinIndex ) { - - const skinDef = this.json.skins[ skinIndex ]; - - const pending = []; - - for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) { - - pending.push( this._loadNodeShallow( skinDef.joints[ i ] ) ); - - } - - if ( skinDef.inverseBindMatrices !== undefined ) { - - pending.push( this.getDependency( 'accessor', skinDef.inverseBindMatrices ) ); - - } else { - - pending.push( null ); - - } - - return Promise.all( pending ).then( function ( results ) { - - const inverseBindMatrices = results.pop(); - const jointNodes = results; - - // Note that bones (joint nodes) may or may not be in the - // scene graph at this time. - - const bones = []; - const boneInverses = []; - - for ( let i = 0, il = jointNodes.length; i < il; i ++ ) { - - const jointNode = jointNodes[ i ]; - - if ( jointNode ) { - - bones.push( jointNode ); - - const mat = new three.Matrix4(); - - if ( inverseBindMatrices !== null ) { - - mat.fromArray( inverseBindMatrices.array, i * 16 ); - - } - - boneInverses.push( mat ); - - } - - } - - return new three.Skeleton( bones, boneInverses ); - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations - * @param {number} animationIndex - * @return {Promise} - */ - loadAnimation( animationIndex ) { - - const json = this.json; - const parser = this; - - const animationDef = json.animations[ animationIndex ]; - const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex; - - const pendingNodes = []; - const pendingInputAccessors = []; - const pendingOutputAccessors = []; - const pendingSamplers = []; - const pendingTargets = []; - - for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { - - const channel = animationDef.channels[ i ]; - const sampler = animationDef.samplers[ channel.sampler ]; - const target = channel.target; - const name = target.node; - const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; - const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; - - if ( target.node === undefined ) continue; - - 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 ) { - - const nodes = dependencies[ 0 ]; - const inputAccessors = dependencies[ 1 ]; - const outputAccessors = dependencies[ 2 ]; - const samplers = dependencies[ 3 ]; - const targets = dependencies[ 4 ]; - - const tracks = []; - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - const node = nodes[ i ]; - const inputAccessor = inputAccessors[ i ]; - const outputAccessor = outputAccessors[ i ]; - const sampler = samplers[ i ]; - const target = targets[ i ]; - - if ( node === undefined ) continue; - - if ( node.updateMatrix ) { - - node.updateMatrix(); - - } - - const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ); - - if ( createdTracks ) { - - for ( let k = 0; k < createdTracks.length; k ++ ) { - - tracks.push( createdTracks[ k ] ); - - } - - } - - } - - return new three.AnimationClip( animationName, undefined, tracks ); - - } ); - - } - - createNodeMesh( nodeIndex ) { - - const json = this.json; - const parser = this; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( nodeDef.mesh === undefined ) return null; - - return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { - - const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, 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 ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { - - o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; - - } - - } ); - - } - - return node; - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy - * @param {number} nodeIndex - * @return {Promise} - */ - loadNode( nodeIndex ) { - - const json = this.json; - const parser = this; - - const nodeDef = json.nodes[ nodeIndex ]; - - const nodePending = parser._loadNodeShallow( nodeIndex ); - - const childPending = []; - const childrenDef = nodeDef.children || []; - - for ( let i = 0, il = childrenDef.length; i < il; i ++ ) { - - childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) ); - - } - - const skeletonPending = nodeDef.skin === undefined - ? Promise.resolve( null ) - : parser.getDependency( 'skin', nodeDef.skin ); - - return Promise.all( [ - nodePending, - Promise.all( childPending ), - skeletonPending - ] ).then( function ( results ) { - - const node = results[ 0 ]; - const children = results[ 1 ]; - const skeleton = results[ 2 ]; - - if ( skeleton !== null ) { - - // This full traverse should be fine because - // child glTF nodes have not been added to this node yet. - node.traverse( function ( mesh ) { - - if ( ! mesh.isSkinnedMesh ) return; - - mesh.bind( skeleton, _identityMatrix ); - - } ); - - } - - for ( let i = 0, il = children.length; i < il; i ++ ) { - - node.add( children[ i ] ); - - } - - return node; - - } ); - - } - - // ._loadNodeShallow() parses a single node. - // skin and child nodes are created and added in .loadNode() (no '_' prefix). - _loadNodeShallow( nodeIndex ) { - - const json = this.json; - const extensions = this.extensions; - const parser = this; - - // This method is called from .loadNode() and .loadSkin(). - // Cache a node to avoid duplication. - - if ( this.nodeCache[ nodeIndex ] !== undefined ) { - - return this.nodeCache[ nodeIndex ]; - - } - - const nodeDef = json.nodes[ nodeIndex ]; - - // reserve node's name before its dependencies, so the root has the intended name. - const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; - - const pending = []; - - const meshPromise = parser._invokeOne( function ( ext ) { - - return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); - - } ); - - if ( meshPromise ) { - - pending.push( meshPromise ); - - } - - if ( nodeDef.camera !== undefined ) { - - pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { - - return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); - - } ) ); - - } - - parser._invokeAll( function ( ext ) { - - return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); - - } ).forEach( function ( promise ) { - - pending.push( promise ); - - } ); - - this.nodeCache[ nodeIndex ] = Promise.all( pending ).then( function ( objects ) { - - let node; - - // .isBone isn't in glTF spec. See ._markDefs - if ( nodeDef.isBone === true ) { - - node = new three.Bone(); - - } else if ( objects.length > 1 ) { - - node = new three.Group(); - - } else if ( objects.length === 1 ) { - - node = objects[ 0 ]; - - } else { - - node = new three.Object3D(); - - } - - if ( node !== objects[ 0 ] ) { - - for ( let i = 0, il = objects.length; i < il; i ++ ) { - - node.add( objects[ i ] ); - - } - - } - - if ( nodeDef.name ) { - - node.userData.name = nodeDef.name; - node.name = nodeName; - - } - - assignExtrasToUserData( node, nodeDef ); - - if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); - - if ( nodeDef.matrix !== undefined ) { - - const matrix = new three.Matrix4(); - matrix.fromArray( nodeDef.matrix ); - node.applyMatrix4( 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 ); - - } - - } - - if ( ! parser.associations.has( node ) ) { - - parser.associations.set( node, {} ); - - } - - parser.associations.get( node ).nodes = nodeIndex; - - return node; - - } ); - - return this.nodeCache[ nodeIndex ]; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes - * @param {number} sceneIndex - * @return {Promise} - */ - loadScene( sceneIndex ) { - - const extensions = this.extensions; - const sceneDef = this.json.scenes[ sceneIndex ]; - const parser = this; - - // Loader returns Group, not Scene. - // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 - const scene = new three.Group(); - if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - - assignExtrasToUserData( scene, sceneDef ); - - if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); - - const nodeIds = sceneDef.nodes || []; - - const pending = []; - - for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - - pending.push( parser.getDependency( 'node', nodeIds[ i ] ) ); - - } - - return Promise.all( pending ).then( function ( nodes ) { - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - scene.add( nodes[ i ] ); - - } - - // Removes dangling associations, associations that reference a node that - // didn't make it into the scene. - const reduceAssociations = ( node ) => { - - const reducedAssociations = new Map(); - - for ( const [ key, value ] of parser.associations ) { - - if ( key instanceof three.Material || key instanceof three.Texture ) { - - reducedAssociations.set( key, value ); - - } - - } - - node.traverse( ( node ) => { - - const mappings = parser.associations.get( node ); - - if ( mappings != null ) { - - reducedAssociations.set( node, mappings ); - - } - - } ); - - return reducedAssociations; - - }; - - parser.associations = reduceAssociations( scene ); - - return scene; - - } ); - - } - - _createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) { - - const tracks = []; - - const targetName = node.name ? node.name : node.uuid; - const targetNames = []; - - if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { - - node.traverse( function ( object ) { - - if ( object.morphTargetInfluences ) { - - targetNames.push( object.name ? object.name : object.uuid ); - - } - - } ); - - } else { - - targetNames.push( targetName ); - - } - - let 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: - - TypedKeyframeTrack = three.VectorKeyframeTrack; - break; - - default: - - switch ( outputAccessor.itemSize ) { - - case 1: - TypedKeyframeTrack = three.NumberKeyframeTrack; - break; - case 2: - case 3: - default: - TypedKeyframeTrack = three.VectorKeyframeTrack; - break; - - } - - break; - - } - - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : three.InterpolateLinear; - - - const outputArray = this._getArrayFromAccessor( outputAccessor ); - - for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { - - const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], - inputAccessor.array, - outputArray, - interpolation - ); - - // Override interpolation with custom factory method. - if ( sampler.interpolation === 'CUBICSPLINE' ) { - - this._createCubicSplineTrackInterpolant( track ); - - } - - tracks.push( track ); - - } - - return tracks; - - } - - _getArrayFromAccessor( accessor ) { - - let outputArray = accessor.array; - - if ( accessor.normalized ) { - - const scale = getNormalizedComponentScale( outputArray.constructor ); - const scaled = new Float32Array( outputArray.length ); - - for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { - - scaled[ j ] = outputArray[ j ] * scale; - - } - - outputArray = scaled; - - } - - return outputArray; - - } - - _createCubicSplineTrackInterpolant( track ) { - - 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. - - const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; - - return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); - - }; - - // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. - track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; - - } - -} - -/** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - */ -function computeBounds( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const box = new three.Box3(); - - if ( attributes.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ attributes.POSITION ]; - - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - box.set( - new three.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), - new three.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) - ); - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - box.min.multiplyScalar( boxScale ); - box.max.multiplyScalar( boxScale ); - - } - - } else { - - return; - - } - - } else { - - return; - - } - - const targets = primitiveDef.targets; - - if ( targets !== undefined ) { - - const maxDisplacement = new three.Vector3(); - const vector = new three.Vector3(); - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ target.POSITION ]; - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - // we need to get max of absolute components because target weight is [-1,1] - vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); - vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); - vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); - - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - vector.multiplyScalar( boxScale ); - - } - - // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative - // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets - // are used to implement key-frame animations and as such only two are active at a time - this results in very large - // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. - maxDisplacement.max( vector ); - - } - - } - - } - - // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. - box.expandByVector( maxDisplacement ); - - } - - geometry.boundingBox = box; - - const sphere = new three.Sphere(); - - box.getCenter( sphere.center ); - sphere.radius = box.min.distanceTo( box.max ) / 2; - - geometry.boundingSphere = sphere; - -} - -/** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - * @return {Promise} - */ -function addPrimitiveAttributes( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const pending = []; - - function assignAttributeAccessor( accessorIndex, attributeName ) { - - return parser.getDependency( 'accessor', accessorIndex ) - .then( function ( accessor ) { - - geometry.setAttribute( attributeName, accessor ); - - } ); - - } - - for ( const gltfAttributeName in attributes ) { - - const 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 ) { - - const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { - - geometry.setIndex( accessor ); - - } ); - - pending.push( accessor ); - - } - - if ( three.ColorManagement.workingColorSpace !== three.LinearSRGBColorSpace && 'COLOR_0' in attributes ) ; - - assignExtrasToUserData( geometry, primitiveDef ); - - computeBounds( geometry, primitiveDef, parser ); - - return Promise.all( pending ).then( function () { - - return primitiveDef.targets !== undefined - ? addMorphTargets( geometry, primitiveDef.targets, parser ) - : geometry; - - } ); - -} - -const _taskCache$1 = new WeakMap(); - -class DRACOLoader extends three.Loader { - - constructor( manager ) { - - super( manager ); - - this.decoderPath = ''; - this.decoderConfig = {}; - this.decoderBinary = null; - this.decoderPending = null; - - this.workerLimit = 4; - this.workerPool = []; - this.workerNextTaskID = 1; - this.workerSourceURL = ''; - - this.defaultAttributeIDs = { - position: 'POSITION', - normal: 'NORMAL', - color: 'COLOR', - uv: 'TEX_COORD' - }; - this.defaultAttributeTypes = { - position: 'Float32Array', - normal: 'Float32Array', - color: 'Float32Array', - uv: 'Float32Array' - }; - - } - - setDecoderPath( path ) { - - this.decoderPath = path; - - return this; - - } - - setDecoderConfig( config ) { - - this.decoderConfig = config; - - return this; - - } - - setWorkerLimit( workerLimit ) { - - this.workerLimit = workerLimit; - - return this; - - } - - load( url, onLoad, onProgress, onError ) { - - const loader = new three.FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - loader.load( url, ( buffer ) => { - - this.parse( buffer, onLoad, onError ); - - }, onProgress, onError ); - - } - - - parse( buffer, onLoad, onError = ()=>{} ) { - - this.decodeDracoFile( buffer, onLoad, null, null, three.SRGBColorSpace ).catch( onError ); - - } - - decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = three.LinearSRGBColorSpace, onError = () => {} ) { - - const taskConfig = { - attributeIDs: attributeIDs || this.defaultAttributeIDs, - attributeTypes: attributeTypes || this.defaultAttributeTypes, - useUniqueIDs: !! attributeIDs, - vertexColorSpace: vertexColorSpace, - }; - - return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError ); - - } - - decodeGeometry( buffer, taskConfig ) { - - const taskKey = JSON.stringify( taskConfig ); - - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache$1.has( buffer ) ) { - - const cachedTask = _taskCache$1.get( buffer ); - - if ( cachedTask.key === taskKey ) { - - return cachedTask.promise; - - } else if ( buffer.byteLength === 0 ) { - - // Technically, it would be possible to wait for the previous task to complete, - // transfer the buffer back, and decode again with the second configuration. That - // is complex, and I don't know of any reason to decode a Draco buffer twice in - // different ways, so this is left unimplemented. - throw new Error( - - 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + - 'settings. Buffer has already been transferred.' - - ); - - } - - } - - // - - let worker; - const taskID = this.workerNextTaskID ++; - const taskCost = buffer.byteLength; - - // Obtain a worker and assign a task, and construct a geometry instance - // when the task completes. - const geometryPending = this._getWorker( taskID, taskCost ) - .then( ( _worker ) => { - - worker = _worker; - - return new Promise( ( resolve, reject ) => { - - worker._callbacks[ taskID ] = { resolve, reject }; - - worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); - - // this.debug(); - - } ); - - } ) - .then( ( message ) => this._createGeometry( message.geometry ) ); - - // Remove task from the task list. - // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) - geometryPending - .catch( () => true ) - .then( () => { - - if ( worker && taskID ) { - - this._releaseTask( worker, taskID ); - - // this.debug(); - - } - - } ); - - // Cache the task result. - _taskCache$1.set( buffer, { - - key: taskKey, - promise: geometryPending - - } ); - - return geometryPending; - - } - - _createGeometry( geometryData ) { - - const geometry = new three.BufferGeometry(); - - if ( geometryData.index ) { - - geometry.setIndex( new three.BufferAttribute( geometryData.index.array, 1 ) ); - - } - - for ( let i = 0; i < geometryData.attributes.length; i ++ ) { - - const result = geometryData.attributes[ i ]; - const name = result.name; - const array = result.array; - const itemSize = result.itemSize; - - const attribute = new three.BufferAttribute( array, itemSize ); - - if ( name === 'color' ) { - - this._assignVertexColorSpace( attribute, result.vertexColorSpace ); - - attribute.normalized = ( array instanceof Float32Array ) === false; - - } - - geometry.setAttribute( name, attribute ); - - } - - return geometry; - - } - - _assignVertexColorSpace( attribute, inputColorSpace ) { - - // While .drc files do not specify colorspace, the only 'official' tooling - // is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc - // file is passed into .load() or .parse(). GLTFLoader uses internal APIs - // to decode geometry, and vertex colors are already Linear-sRGB in there. - - if ( inputColorSpace !== three.SRGBColorSpace ) return; - - const _color = new three.Color(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); - attribute.setXYZ( i, _color.r, _color.g, _color.b ); - - } - - } - - _loadLibrary( url, responseType ) { - - const loader = new three.FileLoader( this.manager ); - loader.setPath( this.decoderPath ); - loader.setResponseType( responseType ); - loader.setWithCredentials( this.withCredentials ); - - return new Promise( ( resolve, reject ) => { - - loader.load( url, resolve, undefined, reject ); - - } ); - - } - - preload() { - - this._initDecoder(); - - return this; - - } - - _initDecoder() { - - if ( this.decoderPending ) return this.decoderPending; - - const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; - const librariesPending = []; - - if ( useJS ) { - - librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); - - } else { - - librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); - librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); - - } - - this.decoderPending = Promise.all( librariesPending ) - .then( ( libraries ) => { - - const jsContent = libraries[ 0 ]; - - if ( ! useJS ) { - - this.decoderConfig.wasmBinary = libraries[ 1 ]; - - } - - const fn = DRACOWorker.toString(); - - const body = [ - '/* draco decoder */', - jsContent, - '', - '/* worker */', - fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) - ].join( '\n' ); - - this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); - - } ); - - return this.decoderPending; - - } - - _getWorker( taskID, taskCost ) { - - return this._initDecoder().then( () => { - - if ( this.workerPool.length < this.workerLimit ) { - - const worker = new Worker( this.workerSourceURL ); - - worker._callbacks = {}; - worker._taskCosts = {}; - worker._taskLoad = 0; - - worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); - - worker.onmessage = function ( e ) { - - const message = e.data; - - switch ( message.type ) { - - case 'decode': - worker._callbacks[ message.id ].resolve( message ); - break; - - case 'error': - worker._callbacks[ message.id ].reject( message ); - break; - - } - - }; - - this.workerPool.push( worker ); - - } else { - - this.workerPool.sort( function ( a, b ) { - - return a._taskLoad > b._taskLoad ? - 1 : 1; - - } ); - - } - - const worker = this.workerPool[ this.workerPool.length - 1 ]; - worker._taskCosts[ taskID ] = taskCost; - worker._taskLoad += taskCost; - return worker; - - } ); - - } - - _releaseTask( worker, taskID ) { - - worker._taskLoad -= worker._taskCosts[ taskID ]; - delete worker._callbacks[ taskID ]; - delete worker._taskCosts[ taskID ]; - - } - - debug() { - - } - - dispose() { - - for ( let i = 0; i < this.workerPool.length; ++ i ) { - - this.workerPool[ i ].terminate(); - - } - - this.workerPool.length = 0; - - if ( this.workerSourceURL !== '' ) { - - URL.revokeObjectURL( this.workerSourceURL ); - - } - - return this; - - } - -} - -/* WEB WORKER */ - -function DRACOWorker() { - - let decoderConfig; - let decoderPending; + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - onmessage = function ( e ) { + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; - const message = e.data; + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - switch ( message.type ) { + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - case 'init': - decoderConfig = message.decoderConfig; - decoderPending = new Promise( function ( resolve/*, reject*/ ) { + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; - decoderConfig.onModuleLoaded = function ( draco ) { + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } - // Module is Promise-like. Wrap before resolving to avoid loop. - resolve( { draco: draco } ); + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } - }; + void main() { - DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - } ); - break; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - case 'decode': - const buffer = message.buffer; - const taskConfig = message.taskConfig; - decoderPending.then( ( module ) => { + vSunDirection = normalize( sunPosition ); - const draco = module.draco; - const decoder = new draco.Decoder(); + vSunE = sunIntensity( dot( vSunDirection, up ) ); - try { + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - const geometry = decodeGeometry( draco, decoder, new Int8Array( buffer ), taskConfig ); + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + }`, - } catch ( error ) { + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + uniform float mieDirectionalG; + uniform vec3 up; - } finally { + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; - draco.destroy( decoder ); + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - } + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - } ); - break; + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); } - }; + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - function decodeGeometry( draco, decoder, array, taskConfig ) { + void main() { - const attributeIDs = taskConfig.attributeIDs; - const attributeTypes = taskConfig.attributeTypes; + vec3 direction = normalize( vWorldPosition - cameraPosition ); - let dracoGeometry; - let decodingStatus; + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - const geometryType = decoder.GetEncodedGeometryType( array ); + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - if ( geometryType === draco.TRIANGULAR_MESH ) { + // in scattering + float cosTheta = dot( direction, vSunDirection ); - dracoGeometry = new draco.Mesh(); - decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry ); + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - } else if ( geometryType === draco.POINT_CLOUD ) { + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - dracoGeometry = new draco.PointCloud(); - decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, dracoGeometry ); + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - } else { + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - } + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + gl_FragColor = vec4( retColor, 1.0 ); - } + #include + #include - const geometry = { index: null, attributes: [] }; + }` - // Gather all vertex attributes. - for ( const attributeName in attributeIDs ) { +}; - const attributeType = self[ attributeTypes[ attributeName ] ]; +// 多个canvas并没有id,只有父节点 - let attribute; - let attributeID; - // A Draco file may be created with default vertex attributes, whose attribute IDs - // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, - // a Draco file may contain a custom set of attributes, identified by known unique - // IDs. glTF files always do the latter, and `.drc` files typically do the former. - if ( taskConfig.useUniqueIDs ) { +class Layer extends BasLayer{ + id; // 唯一标识 + layerContainer; // div#layer 容器 + zIndex=1;//默认为1 + opacity=1;//默认为1 + canvas;//canvas + dispose = false; + renderer;//canvas上下文 + scene;//场景 + visible=true;//是否可见 + mapView;//地图视图 + camera;//相机 + controls;//控件 + animateId;//动画事件id + base = false; // 是否为底图 + ambientLight; // 环境光 + directionalLight; // 方向光 + modelLayer = false; // 模型图层 + imageLayer = false; // 影像图层 + vectorLayer = false; // 矢量图层,如路网、行政区划,地名等图层 + waters = []; // 水面集合 + constructor(id, layerContainer, canvas, mapView, plane = true, camera = new three.PerspectiveCamera(80, 1, 0.1, 1e12)) { + super(); + this.id = id; + this.layerContainer = layerContainer; + this.canvas = canvas; + this.renderer = new three.WebGLRenderer({ + canvas: this.canvas, + antialias: true, + alpha: true, + logarithmicDepthBuffer: true, + precision: "highp", + }); + this.renderer.sortObjects = true; + this.renderer.setPixelRatio(window.devicePixelRatio); + this.renderer.setClearColor(0xFFFFFF, 0.0); + this.scene = new three.Scene(); + this.mapView = mapView; + this.camera = camera; + if(this.mapView){ + this.scene.add(this.mapView); + this.mapView.updateMatrixWorld(true); + } + if (plane){ + this.controls = new MapControls(this.camera, this.canvas); + this.controls.minDistance = 1e1; + this.controls.zoomSpeed = 2.0; + } else { + this.controls = new OrbitControls(this.camera, this.canvas); + this.controls.enablePan = false; + this.controls.minDistance = UnitsUtils.EARTH_RADIUS + 2; + this.controls.maxDistance = UnitsUtils.EARTH_RADIUS * 1e1; + } + this._raycaster = new three.Raycaster(); + if(Config.outLine.on){ + this.effectOutline = new EffectOutline(this.renderer, this.scene, this.camera, this.canvas.width, this.canvas.height); + } + if (Config.layer.map.ambientLight.add){ + this.scene.add(new three.AmbientLight(Config.layer.map.ambientLight.color, Config.layer.map.ambientLight.intensity)); + } + if (Config.layer.map.directionalLight.add){ + this.scene.add(new three.DirectionalLight(Config.layer.map.directionalLight.color, Config.layer.map.directionalLight.intensity)); + } + if (Config.layer.map.pointLight.add){ + let pointLight = new three.PointLight(Config.layer.map.pointLight.color, Config.layer.map.pointLight.intensity, Config.layer.map.pointLight.distance); + pointLight.position.set(...Config.layer.map.pointLight.position); + this.scene.add(pointLight); + } + } - attributeID = attributeIDs[ attributeName ]; - attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + moveTo(lat, lon, height = 38472.48763833733){ + // var coords = UnitsUtils.datumsToSpherical(44.266119,90.139228); + var coords = UnitsUtils.datumsToSpherical(lat,lon); + this.camera.position.set(coords.x, height, -coords.y); + this.controls.target.set(this.camera.position.x, 0, this.camera.position.z); + } - } else { + moveToByCoords(coords){ + let offset = 50; + this.camera.position.set(coords.x, coords.y+offset, coords.z); + this.controls.target.set(coords.x, coords.y, coords.z); + } - attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + moveToByLL(lat, lon, distance = 384720){ + let dir = UnitsUtils.datumsToVector(lat, lon); + dir.multiplyScalar(UnitsUtils.EARTH_RADIUS + distance); + this.camera.position.copy(dir); + } - if ( attributeID === - 1 ) continue; - attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + on(eventName, callback){ + this.listener.on(eventName, callback); + } - } + setSceneBackground(color) { + this.scene.background = color; + } - const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ); + clearSceneBackground() { + this.scene.background = null; + } - if ( attributeName === 'color' ) { + // 可用于添加灯光mesh等元素 + add(Object3D) { + if(Object3D ==null || Object3D ==undefined){ + return; + } + this.scene.add(Object3D); + } - attributeResult.vertexColorSpace = taskConfig.vertexColorSpace; - } + remove(Object3D) { + if(Object3D ==null || Object3D ==undefined){ + return; + } + this.scene.remove(Object3D); + } - geometry.attributes.push( attributeResult ); + openWaterConfig(){ + /** + * 打开渲染水系配置 + */ + // this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.toneMapping = three.ACESFilmicToneMapping; + this.renderer.toneMappingExposure = 0.5; + + // 添加天空 + this.sky = new Sky(); + this.sky.translateX = true; + this.sky.translateY = true; + this.sky.translateZ = true; + this.sky.rotateX = Math.PI / 2; + this.sky.scale.setScalar( Config.EARTH_RADIUS * 2 * Math.PI ); // 天空放大倍数 + this.scene.add( this.sky ); + const skyUniforms = this.sky.material.uniforms; + // 天空的配置 + skyUniforms[ 'turbidity' ].value = 10; + skyUniforms[ 'rayleigh' ].value = 2; + skyUniforms[ 'mieCoefficient' ].value = 0.005; + skyUniforms[ 'mieDirectionalG' ].value = 0.8; + // 旋转 设置为y朝上 + // sky.material.uniforms["up"].value = new THREE.Vector3(0, 1, 0); + + // 天空映射, 更新太阳位置 + this.pmremGenerator = new three.PMREMGenerator( this.renderer ); + this.sceneEnv = new three.Scene(); + this.renderTarget = null; + this.sun = new three.Vector3(); + this.updateSun(Config.SUNDEGREE, Config.SUNAZIMUTH); + } - } + updateSun(elevation, azimuth) { - // Add index. - if ( geometryType === draco.TRIANGULAR_MESH ) { + const phi = three.MathUtils.degToRad( 90 - elevation ); + const theta = three.MathUtils.degToRad( azimuth ); - geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + this.sun.setFromSphericalCoords( 1, phi, theta ); - } + this.sky.material.uniforms[ 'sunPosition' ].value.copy( this.sun ); + for (let water of this.waters){ + water.material.uniforms[ 'sunDirection' ].value.copy( this.sun ).normalize(); + } + if ( this.renderTarget !== null ) this.renderTarget.dispose(); - draco.destroy( dracoGeometry ); + this.sceneEnv.add( this.sky ); + this.renderTarget = this.pmremGenerator.fromScene( this.sceneEnv ); + this.scene.add( this.sky ); - return geometry; + this.scene.environment = this.renderTarget.texture; - } - function decodeIndex( draco, decoder, dracoGeometry ) { + } - const numFaces = dracoGeometry.num_faces(); - const numIndices = numFaces * 3; - const byteLength = numIndices * 4; + /** + * 添加水系 + * @param {*} water + * @returns + */ + addWater(water) { + if(water ==null || water ==undefined){ + return; + } + this.scene.add(water); + this.waters.push(water); + } - const ptr = draco._malloc( byteLength ); - decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); - const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); - draco._free( ptr ); + removeWater(water) { + if(water ==null || water ==undefined){ + return; + } + this.scene.remove(water); + let index = this.waters.indexOf(water); + if (index > -1) { + this.waters.splice(index, 1); + } + } - return { array: index, itemSize: 1 }; + setVisible(visible) { + this.visible = this.visible; + this.layerContainer.style.display = visible ? 'block' : 'none'; + } + /** + * @deprecated 不建议用 + * @param {number} opacity + */ + setOpacity(opacity) { + this.opacity = opacity; + this.layerContainer.style.opacity = opacity; + } - } + /** + * 修改显示层级,默认越靠下的dom元素,显示上越靠上 + * @param {number} zIndex + */ + setZIndex(zIndex) { + this.zIndex = zIndex; + this.layerContainer.style.zIndex = this.zIndex; + } - function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + dispose() { + Element.removeLayer(id); + this.dispose = true; + this.animateId && cancelAnimationFrame(this.animateId); + this.mapView.root.dispose(); + this.mapView.dispose(); + } - const numComponents = attribute.num_components(); - const numPoints = dracoGeometry.num_points(); - const numValues = numPoints * numComponents; - const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; - const dataType = getDracoDataType( draco, attributeType ); + selectModel(insect){ + this.effectOutline.selectModel(insect); + } - const ptr = draco._malloc( byteLength ); - decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); - const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); - draco._free( ptr ); + resize(){ + var width = window.innerWidth; + var height = window.innerHeight; + this.renderer.setSize(width, height); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + if(Config.outLine.on){ + this.effectOutline.resize(width, height); + } + } - return { - name: attributeName, - array: array, - itemSize: numComponents - }; + animate(){ + this.animateId = requestAnimationFrame(this.animate.bind(this)); + if(this.base){ + this.controls.update(); + } + if (this.base){ + update(); //目前只有在基础地图中添加相机移动的动画,所以暂且只在base地图中进行渲染,后期可改成每个图层都进行渲染,或者必要时进行渲染。 + } + for(let water of this.waters){ + water.material.uniforms[ 'time' ].value += 1.0 / 60.0; + } + if (Config.outLine.on){ + this.effectOutline.render(); // 合成器渲染 + } else { + this.renderer.autoClear = true; + } + this.renderer.render(this.scene, this.camera); + } - } + _raycast(meshes, recursive, faceExclude) { + const isects = this._raycaster.intersectObjects(meshes, recursive); + if (faceExclude) { + for (let i = 0; i < isects.length; i++) { + if (isects[i].face !== faceExclude) { + return isects[i]; + } + } + return null; + } + return isects.length > 0 ? isects[0] : null; + } - function getDracoDataType( draco, attributeType ) { + _raycastFromMouse(mx, my, width, height, cam, meshes, recursive=false) { + const mouse = new three.Vector2( // normalized (-1 to +1) + (mx / width) * 2 - 1, + - (my / height) * 2 + 1); + // https://threejs.org/docs/#api/core/Raycaster + // update the picking ray with the camera and mouse position + this._raycaster.setFromCamera(mouse, cam); + return this._raycast(meshes, recursive, null); + } - switch ( attributeType ) { + /** + * + * @param {*} mx 屏幕坐标x + * @param {*} my 屏幕坐标y + * @param {boolean} recursive 是否检查子节点,true 递归检查 + * @returns mesh + */ + raycastFromMouse(mx, my, recursive=false) { + //---- NG: 2x when starting with Chrome's inspector mobile + // const {width, height} = this.renderer.domElement; + // const {width, height} = this.canvas; + //---- OK + const {clientWidth, clientHeight} = this.canvas; - case Float32Array: return draco.DT_FLOAT32; - case Int8Array: return draco.DT_INT8; - case Int16Array: return draco.DT_INT16; - case Int32Array: return draco.DT_INT32; - case Uint8Array: return draco.DT_UINT8; - case Uint16Array: return draco.DT_UINT16; - case Uint32Array: return draco.DT_UINT32; + return this._raycastFromMouse( + mx, my, clientWidth, clientHeight, this.camera, + this.mapView.children, recursive); + } - } + insectALL(mx, my, recursive=false) { + const {clientWidth, clientHeight} = this.canvas; - } + return this._raycastFromMouse( + mx, my, clientWidth, clientHeight, this.camera, + this.scene.children, recursive); + } } @@ -41527,11 +36293,17 @@ class WegeoMap { this.baseMap.moveToByCoords(coords); } - moveToByLL(lat, lon){ + /** + * 跳转到指定位置,用于球形地图 + * @param {*} lat + * @param {*} lon + * @returns + */ + moveToByLL(lat, lon, distance = 384720){ if(!this.baseMap){ return; } - this.baseMap.moveToByLL(lat, lon); + this.baseMap.moveToByLL(lat, lon, distance); } // 鼠标点击获取模型 @@ -41895,12 +36667,12 @@ class Skybox { loadSkyBox(scale) { var aCubeMap = new three.CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); aCubeMap.format = three.RGBAFormat; @@ -41923,17 +36695,18 @@ class Skybox { } loadBox(){ var cube = new three.CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); return cube; } } +exports.AngleUtils = AngleUtils; exports.Animate = Animate; exports.BingMapsProvider = BingMapsProvider; exports.CancelablePromise = CancelablePromise; diff --git a/build/wegeo.js b/build/wegeo.js index fb795a6..3c6a8ff 100644 --- a/build/wegeo.js +++ b/build/wegeo.js @@ -5011,6 +5011,26 @@ } } + class AngleUtils { + /** + * 弧度转角度 + * @param {*} rad + * @returns + */ + static radToDeg(rad) { + return rad * (180 / Math.PI); + } + /** + * 角度转弧度 + * @param {*} deg + * @returns + */ + static degToRad(deg) { + return deg * (Math.PI / 180); + } + + } + // OrbitControls performs orbiting, dollying (zooming), and panning. // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). // @@ -9175,7 +9195,7 @@ * @param {number} drawMode * @return {BufferGeometry} */ - function toTrianglesDrawMode$1( geometry, drawMode ) { + function toTrianglesDrawMode( geometry, drawMode ) { if ( drawMode === three.TrianglesDrawMode ) { return geometry; @@ -9270,7 +9290,7 @@ } - let GLTFLoader$1 = class GLTFLoader extends three.Loader { + class GLTFLoader extends three.Loader { constructor( manager ) { @@ -9284,97 +9304,97 @@ this.register( function ( parser ) { - return new GLTFMaterialsClearcoatExtension$1( parser ); + return new GLTFMaterialsClearcoatExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureBasisUExtension$1( parser ); + return new GLTFTextureBasisUExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureWebPExtension$1( parser ); + return new GLTFTextureWebPExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureAVIFExtension$1( parser ); + return new GLTFTextureAVIFExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsSheenExtension$1( parser ); + return new GLTFMaterialsSheenExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsTransmissionExtension$1( parser ); + return new GLTFMaterialsTransmissionExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsVolumeExtension$1( parser ); + return new GLTFMaterialsVolumeExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsIorExtension$1( parser ); + return new GLTFMaterialsIorExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsEmissiveStrengthExtension$1( parser ); + return new GLTFMaterialsEmissiveStrengthExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsSpecularExtension$1( parser ); + return new GLTFMaterialsSpecularExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsIridescenceExtension$1( parser ); + return new GLTFMaterialsIridescenceExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsAnisotropyExtension$1( parser ); + return new GLTFMaterialsAnisotropyExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsBumpExtension$1( parser ); + return new GLTFMaterialsBumpExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFLightsExtension$1( parser ); + return new GLTFLightsExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMeshoptCompression$1( parser ); + return new GLTFMeshoptCompression( parser ); } ); this.register( function ( parser ) { - return new GLTFMeshGpuInstancing$1( parser ); + return new GLTFMeshGpuInstancing( parser ); } ); @@ -9523,11 +9543,11 @@ const magic = textDecoder.decode( new Uint8Array( data, 0, 4 ) ); - if ( magic === BINARY_EXTENSION_HEADER_MAGIC$1 ) { + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { try { - extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ] = new GLTFBinaryExtension$1( data ); + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); } catch ( error ) { @@ -9536,7 +9556,7 @@ } - json = JSON.parse( extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ].content ); + json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); } else { @@ -9557,7 +9577,7 @@ } - const parser = new GLTFParser$1( json, { + const parser = new GLTFParser( json, { path: path || this.resourcePath || '', crossOrigin: this.crossOrigin, @@ -9595,20 +9615,20 @@ switch ( extensionName ) { - case EXTENSIONS$1.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension$1(); + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); break; - case EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension$1( json, this.dracoLoader ); + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); break; - case EXTENSIONS$1.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension$1(); + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); break; - case EXTENSIONS$1.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension$1(); + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); break; default: @@ -9639,11 +9659,11 @@ } - }; + } /* GLTFREGISTRY */ - function GLTFRegistry$1() { + function GLTFRegistry() { let objects = {}; @@ -9681,7 +9701,7 @@ /********** EXTENSIONS ***********/ /*********************************/ - const EXTENSIONS$1 = { + const EXTENSIONS = { KHR_BINARY_GLTF: 'KHR_binary_glTF', KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', @@ -9710,12 +9730,12 @@ * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ - let GLTFLightsExtension$1 = class GLTFLightsExtension { + class GLTFLightsExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_LIGHTS_PUNCTUAL; + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // Object3D instance caches this.cache = { refs: {}, uses: {} }; @@ -9800,7 +9820,7 @@ lightNode.decay = 2; - assignExtrasToUserData$1( lightNode, lightDef ); + assignExtrasToUserData( lightNode, lightDef ); if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; @@ -9841,18 +9861,18 @@ } - }; + } /** * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ - let GLTFMaterialsUnlitExtension$1 = class GLTFMaterialsUnlitExtension { + class GLTFMaterialsUnlitExtension { constructor() { - this.name = EXTENSIONS$1.KHR_MATERIALS_UNLIT; + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; } @@ -9894,19 +9914,19 @@ } - }; + } /** * Materials Emissive Strength Extension * * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md */ - let GLTFMaterialsEmissiveStrengthExtension$1 = class GLTFMaterialsEmissiveStrengthExtension { + class GLTFMaterialsEmissiveStrengthExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_EMISSIVE_STRENGTH; + this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; } @@ -9933,19 +9953,19 @@ } - }; + } /** * Clearcoat Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat */ - let GLTFMaterialsClearcoatExtension$1 = class GLTFMaterialsClearcoatExtension { + class GLTFMaterialsClearcoatExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_CLEARCOAT; + this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; } @@ -10017,19 +10037,19 @@ } - }; + } /** * Iridescence Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence */ - let GLTFMaterialsIridescenceExtension$1 = class GLTFMaterialsIridescenceExtension { + class GLTFMaterialsIridescenceExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_IRIDESCENCE; + this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; } @@ -10105,19 +10125,19 @@ } - }; + } /** * Sheen Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen */ - let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { + class GLTFMaterialsSheenExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_SHEEN; + this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; } @@ -10180,7 +10200,7 @@ } - }; + } /** * Transmission Materials Extension @@ -10188,12 +10208,12 @@ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission * Draft: https://github.com/KhronosGroup/glTF/pull/1698 */ - let GLTFMaterialsTransmissionExtension$1 = class GLTFMaterialsTransmissionExtension { + class GLTFMaterialsTransmissionExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_TRANSMISSION; + this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; } @@ -10239,19 +10259,19 @@ } - }; + } /** * Materials Volume Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume */ - let GLTFMaterialsVolumeExtension$1 = class GLTFMaterialsVolumeExtension { + class GLTFMaterialsVolumeExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_VOLUME; + this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; } @@ -10298,19 +10318,19 @@ } - }; + } /** * Materials ior Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior */ - let GLTFMaterialsIorExtension$1 = class GLTFMaterialsIorExtension { + class GLTFMaterialsIorExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_IOR; + this.name = EXTENSIONS.KHR_MATERIALS_IOR; } @@ -10344,19 +10364,19 @@ } - }; + } /** * Materials specular Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular */ - let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { + class GLTFMaterialsSpecularExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_SPECULAR; + this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; } @@ -10407,7 +10427,7 @@ } - }; + } /** @@ -10415,12 +10435,12 @@ * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump */ - let GLTFMaterialsBumpExtension$1 = class GLTFMaterialsBumpExtension { + class GLTFMaterialsBumpExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_MATERIALS_BUMP; + this.name = EXTENSIONS.EXT_MATERIALS_BUMP; } @@ -10462,19 +10482,19 @@ } - }; + } /** * Materials anisotropy Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy */ - let GLTFMaterialsAnisotropyExtension$1 = class GLTFMaterialsAnisotropyExtension { + class GLTFMaterialsAnisotropyExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_ANISOTROPY; + this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; } @@ -10526,19 +10546,19 @@ } - }; + } /** * BasisU Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu */ - let GLTFTextureBasisUExtension$1 = class GLTFTextureBasisUExtension { + class GLTFTextureBasisUExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_TEXTURE_BASISU; + this.name = EXTENSIONS.KHR_TEXTURE_BASISU; } @@ -10577,19 +10597,19 @@ } - }; + } /** * WebP Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp */ - let GLTFTextureWebPExtension$1 = class GLTFTextureWebPExtension { + class GLTFTextureWebPExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_TEXTURE_WEBP; + this.name = EXTENSIONS.EXT_TEXTURE_WEBP; this.isSupported = null; } @@ -10662,19 +10682,19 @@ } - }; + } /** * AVIF Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif */ - let GLTFTextureAVIFExtension$1 = class GLTFTextureAVIFExtension { + class GLTFTextureAVIFExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_TEXTURE_AVIF; + this.name = EXTENSIONS.EXT_TEXTURE_AVIF; this.isSupported = null; } @@ -10745,18 +10765,18 @@ } - }; + } /** * meshopt BufferView Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression */ - let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { + class GLTFMeshoptCompression { constructor( parser ) { - this.name = EXTENSIONS$1.EXT_MESHOPT_COMPRESSION; + this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; this.parser = parser; } @@ -10829,7 +10849,7 @@ } - }; + } /** * GPU Instancing Extension @@ -10837,11 +10857,11 @@ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing * */ - let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { + class GLTFMeshGpuInstancing { constructor( parser ) { - this.name = EXTENSIONS$1.EXT_MESH_GPU_INSTANCING; + this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; this.parser = parser; } @@ -10864,9 +10884,9 @@ for ( const primitive of meshDef.primitives ) { - if ( primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLE_FAN && + if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && + primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && + primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && primitive.mode !== undefined ) { return null; @@ -10986,22 +11006,22 @@ } - }; + } /* BINARY EXTENSION */ - const BINARY_EXTENSION_HEADER_MAGIC$1 = 'glTF'; - const BINARY_EXTENSION_HEADER_LENGTH$1 = 12; - const BINARY_EXTENSION_CHUNK_TYPES$1 = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; + const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; + const BINARY_EXTENSION_HEADER_LENGTH = 12; + const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; - let GLTFBinaryExtension$1 = class GLTFBinaryExtension { + class GLTFBinaryExtension { constructor( data ) { - this.name = EXTENSIONS$1.KHR_BINARY_GLTF; + this.name = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH$1 ); + const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); const textDecoder = new TextDecoder(); this.header = { @@ -11010,7 +11030,7 @@ length: headerView.getUint32( 8, true ) }; - if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC$1 ) { + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); @@ -11020,8 +11040,8 @@ } - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH$1; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH$1 ); + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); let chunkIndex = 0; while ( chunkIndex < chunkContentsLength ) { @@ -11032,14 +11052,14 @@ const chunkType = chunkView.getUint32( chunkIndex, true ); chunkIndex += 4; - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES$1.JSON ) { + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH$1 + chunkIndex, chunkLength ); + const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); this.content = textDecoder.decode( contentArray ); - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES$1.BIN ) { + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - const byteOffset = BINARY_EXTENSION_HEADER_LENGTH$1 + chunkIndex; + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; this.body = data.slice( byteOffset, byteOffset + chunkLength ); } @@ -11058,14 +11078,14 @@ } - }; + } /** * DRACO Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ - let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtension { + class GLTFDracoMeshCompressionExtension { constructor( json, dracoLoader ) { @@ -11075,7 +11095,7 @@ } - this.name = EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION; + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; this.json = json; this.dracoLoader = dracoLoader; this.dracoLoader.preload(); @@ -11094,7 +11114,7 @@ for ( const attributeName in gltfAttributeMap ) { - const threeAttributeName = ATTRIBUTES$1[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; @@ -11102,12 +11122,12 @@ for ( const attributeName in primitive.attributes ) { - const threeAttributeName = ATTRIBUTES$1[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); if ( gltfAttributeMap[ attributeName ] !== undefined ) { const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; attributeTypeMap[ threeAttributeName ] = componentType.name; attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; @@ -11141,18 +11161,18 @@ } - }; + } /** * Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform */ - let GLTFTextureTransformExtension$1 = class GLTFTextureTransformExtension { + class GLTFTextureTransformExtension { constructor() { - this.name = EXTENSIONS$1.KHR_TEXTURE_TRANSFORM; + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } @@ -11200,22 +11220,22 @@ } - }; + } /** * Mesh Quantization Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization */ - let GLTFMeshQuantizationExtension$1 = class GLTFMeshQuantizationExtension { + class GLTFMeshQuantizationExtension { constructor() { - this.name = EXTENSIONS$1.KHR_MESH_QUANTIZATION; + this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; } - }; + } /*********************************/ /********** INTERPOLATION ********/ @@ -11223,7 +11243,7 @@ // Spline Interpolation // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation - let GLTFCubicSplineInterpolant$1 = class GLTFCubicSplineInterpolant extends three.Interpolant { + class GLTFCubicSplineInterpolant extends three.Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { @@ -11291,23 +11311,23 @@ } - }; + } - const _q$1 = new three.Quaternion(); + const _q = new three.Quaternion(); - let GLTFCubicSplineQuaternionInterpolant$1 = class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant$1 { + class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { interpolate_( i1, t0, t, t1 ) { const result = super.interpolate_( i1, t0, t, t1 ); - _q$1.fromArray( result ).normalize().toArray( result ); + _q.fromArray( result ).normalize().toArray( result ); return result; } - }; + } /*********************************/ @@ -11316,7 +11336,7 @@ /* CONSTANTS */ - const WEBGL_CONSTANTS$1 = { + const WEBGL_CONSTANTS = { FLOAT: 5126, //FLOAT_MAT2: 35674, FLOAT_MAT3: 35675, @@ -11338,7 +11358,7 @@ UNSIGNED_SHORT: 5123 }; - const WEBGL_COMPONENT_TYPES$1 = { + const WEBGL_COMPONENT_TYPES = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, @@ -11347,7 +11367,7 @@ 5126: Float32Array }; - const WEBGL_FILTERS$1 = { + const WEBGL_FILTERS = { 9728: three.NearestFilter, 9729: three.LinearFilter, 9984: three.NearestMipmapNearestFilter, @@ -11356,13 +11376,13 @@ 9987: three.LinearMipmapLinearFilter }; - const WEBGL_WRAPPINGS$1 = { + const WEBGL_WRAPPINGS = { 33071: three.ClampToEdgeWrapping, 33648: three.MirroredRepeatWrapping, 10497: three.RepeatWrapping }; - const WEBGL_TYPE_SIZES$1 = { + const WEBGL_TYPE_SIZES = { 'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, @@ -11372,7 +11392,7 @@ 'MAT4': 16 }; - const ATTRIBUTES$1 = { + const ATTRIBUTES = { POSITION: 'position', NORMAL: 'normal', TANGENT: 'tangent', @@ -11385,21 +11405,21 @@ JOINTS_0: 'skinIndex', }; - const PATH_PROPERTIES$1 = { + const PATH_PROPERTIES = { scale: 'scale', translation: 'position', rotation: 'quaternion', weights: 'morphTargetInfluences' }; - const INTERPOLATION$1 = { + const 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 }; - const ALPHA_MODES$1 = { + const ALPHA_MODES = { OPAQUE: 'OPAQUE', MASK: 'MASK', BLEND: 'BLEND' @@ -11408,7 +11428,7 @@ /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material */ - function createDefaultMaterial$1( cache ) { + function createDefaultMaterial( cache ) { if ( cache[ 'DefaultMaterial' ] === undefined ) { @@ -11428,7 +11448,7 @@ } - function addUnknownExtensionsToUserData$1( knownExtensions, object, objectDef ) { + function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { // Add unknown glTF extensions to an object's userData. @@ -11449,7 +11469,7 @@ * @param {Object3D|Material|BufferGeometry} object * @param {GLTF.definition} gltfDef */ - function assignExtrasToUserData$1( object, gltfDef ) { + function assignExtrasToUserData( object, gltfDef ) { if ( gltfDef.extras !== undefined ) { @@ -11471,7 +11491,7 @@ * @param {GLTFParser} parser * @return {Promise} */ - function addMorphTargets$1( geometry, targets, parser ) { + function addMorphTargets( geometry, targets, parser ) { let hasMorphPosition = false; let hasMorphNormal = false; @@ -11556,7 +11576,7 @@ * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ - function updateMorphTargets$1( mesh, meshDef ) { + function updateMorphTargets( mesh, meshDef ) { mesh.updateMorphTargets(); @@ -11591,21 +11611,21 @@ } - function createPrimitiveKey$1( primitiveDef ) { + function createPrimitiveKey( primitiveDef ) { let geometryKey; - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ]; + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; if ( dracoExtension ) { geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices - + ':' + createAttributesKey$1( dracoExtension.attributes ); + + ':' + createAttributesKey( dracoExtension.attributes ); } else { - geometryKey = primitiveDef.indices + ':' + createAttributesKey$1( primitiveDef.attributes ) + ':' + primitiveDef.mode; + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; } @@ -11613,7 +11633,7 @@ for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - geometryKey += ':' + createAttributesKey$1( primitiveDef.targets[ i ] ); + geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); } @@ -11623,7 +11643,7 @@ } - function createAttributesKey$1( attributes ) { + function createAttributesKey( attributes ) { let attributesKey = ''; @@ -11639,7 +11659,7 @@ } - function getNormalizedComponentScale$1( constructor ) { + function getNormalizedComponentScale( constructor ) { // Reference: // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data @@ -11665,7 +11685,7 @@ } - function getImageURIMimeType$1( uri ) { + function getImageURIMimeType( uri ) { if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; @@ -11674,11 +11694,11 @@ } - const _identityMatrix$1 = new three.Matrix4(); + const _identityMatrix = new three.Matrix4(); /* GLTF PARSER */ - let GLTFParser$1 = class GLTFParser { + class GLTFParser { constructor( json = {}, options = {} ) { @@ -11688,7 +11708,7 @@ this.options = options; // loader object cache - this.cache = new GLTFRegistry$1(); + this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements this.associations = new Map(); @@ -11804,9 +11824,9 @@ userData: {} }; - addUnknownExtensionsToUserData$1( extensions, result, json ); + addUnknownExtensionsToUserData( extensions, result, json ); - assignExtrasToUserData$1( result, json ); + assignExtrasToUserData( result, json ); return Promise.all( parser._invokeAll( function ( ext ) { @@ -12124,7 +12144,7 @@ // If present, GLB container is required to be the first buffer. if ( bufferDef.uri === undefined && bufferIndex === 0 ) { - return Promise.resolve( this.extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ].body ); + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); } @@ -12175,8 +12195,8 @@ if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - const itemSize = WEBGL_TYPE_SIZES$1[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; const normalized = accessorDef.normalized === true; const array = new TypedArray( accessorDef.count * itemSize ); @@ -12207,8 +12227,8 @@ const bufferView = bufferViews[ 0 ]; - const itemSize = WEBGL_TYPE_SIZES$1[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. const elementBytes = TypedArray.BYTES_PER_ELEMENT; @@ -12259,8 +12279,8 @@ // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors if ( accessorDef.sparse !== undefined ) { - const itemSizeIndices = WEBGL_TYPE_SIZES$1.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES$1[ accessorDef.sparse.indices.componentType ]; + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; @@ -12353,10 +12373,10 @@ const samplers = json.samplers || {}; const sampler = samplers[ textureDef.sampler ] || {}; - texture.magFilter = WEBGL_FILTERS$1[ sampler.magFilter ] || three.LinearFilter; - texture.minFilter = WEBGL_FILTERS$1[ sampler.minFilter ] || three.LinearMipmapLinearFilter; - texture.wrapS = WEBGL_WRAPPINGS$1[ sampler.wrapS ] || three.RepeatWrapping; - texture.wrapT = WEBGL_WRAPPINGS$1[ sampler.wrapT ] || three.RepeatWrapping; + 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; parser.associations.set( texture, { textures: textureIndex } ); @@ -12445,7 +12465,7 @@ } - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType$1( sourceDef.uri ); + texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); return texture; @@ -12481,14 +12501,14 @@ } - if ( parser.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ] ) { + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ] : undefined; + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; if ( transform ) { const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); parser.associations.set( texture, gltfReference ); } @@ -12631,9 +12651,9 @@ const pending = []; - if ( materialExtensions[ EXTENSIONS$1.KHR_MATERIALS_UNLIT ] ) { + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - const kmuExtension = extensions[ EXTENSIONS$1.KHR_MATERIALS_UNLIT ]; + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; materialType = kmuExtension.getMaterialType(); pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); @@ -12692,9 +12712,9 @@ } - const alphaMode = materialDef.alphaMode || ALPHA_MODES$1.OPAQUE; + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - if ( alphaMode === ALPHA_MODES$1.BLEND ) { + if ( alphaMode === ALPHA_MODES.BLEND ) { materialParams.transparent = true; @@ -12705,7 +12725,7 @@ materialParams.transparent = false; - if ( alphaMode === ALPHA_MODES$1.MASK ) { + if ( alphaMode === ALPHA_MODES.MASK ) { materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; @@ -12760,11 +12780,11 @@ if ( materialDef.name ) material.name = materialDef.name; - assignExtrasToUserData$1( material, materialDef ); + assignExtrasToUserData( material, materialDef ); parser.associations.set( material, { materials: materialIndex } ); - if ( materialDef.extensions ) addUnknownExtensionsToUserData$1( extensions, material, materialDef ); + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); return material; @@ -12807,11 +12827,11 @@ function createDracoPrimitive( primitive ) { - return extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ] + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] .decodePrimitive( primitive, parser ) .then( function ( geometry ) { - return addPrimitiveAttributes$1( geometry, primitive, parser ); + return addPrimitiveAttributes( geometry, primitive, parser ); } ); @@ -12822,7 +12842,7 @@ for ( let i = 0, il = primitives.length; i < il; i ++ ) { const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey$1( primitive ); + const cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry const cached = cache[ cacheKey ]; @@ -12836,7 +12856,7 @@ let geometryPromise; - if ( primitive.extensions && primitive.extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ] ) { + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { // Use DRACO geometry if available geometryPromise = createDracoPrimitive( primitive ); @@ -12844,7 +12864,7 @@ } else { // Otherwise create a new geometry - geometryPromise = addPrimitiveAttributes$1( new three.BufferGeometry(), primitive, parser ); + geometryPromise = addPrimitiveAttributes( new three.BufferGeometry(), primitive, parser ); } @@ -12880,7 +12900,7 @@ for ( let i = 0, il = primitives.length; i < il; i ++ ) { const material = primitives[ i ].material === undefined - ? createDefaultMaterial$1( this.cache ) + ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material ); pending.push( material ); @@ -12907,9 +12927,9 @@ const material = materials[ i ]; - if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLES || - primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_STRIP || - primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_FAN || + 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() @@ -12924,29 +12944,29 @@ } - if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_STRIP ) { + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - mesh.geometry = toTrianglesDrawMode$1( mesh.geometry, three.TriangleStripDrawMode ); + mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleStripDrawMode ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_FAN ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - mesh.geometry = toTrianglesDrawMode$1( mesh.geometry, three.TriangleFanDrawMode ); + mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleFanDrawMode ); } - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINES ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { mesh = new three.LineSegments( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINE_STRIP ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { mesh = new three.Line( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINE_LOOP ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { mesh = new three.LineLoop( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.POINTS ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { mesh = new three.Points( geometry, material ); @@ -12958,15 +12978,15 @@ if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { - updateMorphTargets$1( mesh, meshDef ); + updateMorphTargets( mesh, meshDef ); } mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - assignExtrasToUserData$1( mesh, meshDef ); + assignExtrasToUserData( mesh, meshDef ); - if ( primitive.extensions ) addUnknownExtensionsToUserData$1( extensions, mesh, primitive ); + if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); parser.assignFinalMaterial( mesh ); @@ -12985,7 +13005,7 @@ if ( meshes.length === 1 ) { - if ( meshDef.extensions ) addUnknownExtensionsToUserData$1( extensions, meshes[ 0 ], meshDef ); + if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); return meshes[ 0 ]; @@ -12993,7 +13013,7 @@ const group = new three.Group(); - if ( meshDef.extensions ) addUnknownExtensionsToUserData$1( extensions, group, meshDef ); + if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); parser.associations.set( group, { meshes: meshIndex } ); @@ -13037,7 +13057,7 @@ if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); - assignExtrasToUserData$1( camera, cameraDef ); + assignExtrasToUserData( camera, cameraDef ); return Promise.resolve( camera ); @@ -13281,7 +13301,7 @@ if ( ! mesh.isSkinnedMesh ) return; - mesh.bind( skeleton, _identityMatrix$1 ); + mesh.bind( skeleton, _identityMatrix ); } ); @@ -13395,9 +13415,9 @@ } - assignExtrasToUserData$1( node, nodeDef ); + assignExtrasToUserData( node, nodeDef ); - if ( nodeDef.extensions ) addUnknownExtensionsToUserData$1( extensions, node, nodeDef ); + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); if ( nodeDef.matrix !== undefined ) { @@ -13459,9 +13479,9 @@ const scene = new three.Group(); if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - assignExtrasToUserData$1( scene, sceneDef ); + assignExtrasToUserData( scene, sceneDef ); - if ( sceneDef.extensions ) addUnknownExtensionsToUserData$1( extensions, scene, sceneDef ); + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); const nodeIds = sceneDef.nodes || []; @@ -13528,7 +13548,7 @@ const targetName = node.name ? node.name : node.uuid; const targetNames = []; - if ( PATH_PROPERTIES$1[ target.path ] === PATH_PROPERTIES$1.weights ) { + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { node.traverse( function ( object ) { @@ -13548,20 +13568,20 @@ let TypedKeyframeTrack; - switch ( PATH_PROPERTIES$1[ target.path ] ) { + switch ( PATH_PROPERTIES[ target.path ] ) { - case PATH_PROPERTIES$1.weights: + case PATH_PROPERTIES.weights: TypedKeyframeTrack = three.NumberKeyframeTrack; break; - case PATH_PROPERTIES$1.rotation: + case PATH_PROPERTIES.rotation: TypedKeyframeTrack = three.QuaternionKeyframeTrack; break; - case PATH_PROPERTIES$1.position: - case PATH_PROPERTIES$1.scale: + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: TypedKeyframeTrack = three.VectorKeyframeTrack; break; @@ -13585,7 +13605,7 @@ } - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION$1[ sampler.interpolation ] : three.InterpolateLinear; + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : three.InterpolateLinear; const outputArray = this._getArrayFromAccessor( outputAccessor ); @@ -13593,7 +13613,7 @@ for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES$1[ target.path ], + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], inputAccessor.array, outputArray, interpolation @@ -13620,7 +13640,7 @@ if ( accessor.normalized ) { - const scale = getNormalizedComponentScale$1( outputArray.constructor ); + const scale = getNormalizedComponentScale( outputArray.constructor ); const scaled = new Float32Array( outputArray.length ); for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { @@ -13645,7 +13665,7 @@ // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() // must be divided by three to get the interpolant's sampleSize argument. - const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant$1 : GLTFCubicSplineInterpolant$1; + const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); @@ -13656,14 +13676,14 @@ } - }; + } /** * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser */ - function computeBounds$1( geometry, primitiveDef, parser ) { + function computeBounds( geometry, primitiveDef, parser ) { const attributes = primitiveDef.attributes; @@ -13687,7 +13707,7 @@ if ( accessor.normalized ) { - const boxScale = getNormalizedComponentScale$1( WEBGL_COMPONENT_TYPES$1[ accessor.componentType ] ); + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); box.min.multiplyScalar( boxScale ); box.max.multiplyScalar( boxScale ); @@ -13734,7 +13754,7 @@ if ( accessor.normalized ) { - const boxScale = getNormalizedComponentScale$1( WEBGL_COMPONENT_TYPES$1[ accessor.componentType ] ); + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); vector.multiplyScalar( boxScale ); } @@ -13773,7 +13793,7 @@ * @param {GLTFParser} parser * @return {Promise} */ - function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { + function addPrimitiveAttributes( geometry, primitiveDef, parser ) { const attributes = primitiveDef.attributes; @@ -13792,7 +13812,7 @@ for ( const gltfAttributeName in attributes ) { - const threeAttributeName = ATTRIBUTES$1[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension. if ( threeAttributeName in geometry.attributes ) continue; @@ -13815,23 +13835,23 @@ if ( three.ColorManagement.workingColorSpace !== three.LinearSRGBColorSpace && 'COLOR_0' in attributes ) ; - assignExtrasToUserData$1( geometry, primitiveDef ); + assignExtrasToUserData( geometry, primitiveDef ); - computeBounds$1( geometry, primitiveDef, parser ); + computeBounds( geometry, primitiveDef, parser ); return Promise.all( pending ).then( function () { return primitiveDef.targets !== undefined - ? addMorphTargets$1( geometry, primitiveDef.targets, parser ) + ? addMorphTargets( geometry, primitiveDef.targets, parser ) : geometry; } ); } - const _taskCache$2 = new WeakMap(); + const _taskCache$1 = new WeakMap(); - let DRACOLoader$1 = class DRACOLoader extends three.Loader { + class DRACOLoader extends three.Loader { constructor( manager ) { @@ -13929,9 +13949,9 @@ // Check for an existing task using this buffer. A transferred buffer cannot be transferred // again from this thread. - if ( _taskCache$2.has( buffer ) ) { + if ( _taskCache$1.has( buffer ) ) { - const cachedTask = _taskCache$2.get( buffer ); + const cachedTask = _taskCache$1.get( buffer ); if ( cachedTask.key === taskKey ) { @@ -13997,7 +14017,7 @@ } ); // Cache the task result. - _taskCache$2.set( buffer, { + _taskCache$1.set( buffer, { key: taskKey, promise: geometryPending @@ -14115,7 +14135,7 @@ } - const fn = DRACOWorker$1.toString(); + const fn = DRACOWorker.toString(); const body = [ '/* draco decoder */', @@ -14218,11 +14238,11 @@ } - }; + } /* WEB WORKER */ - function DRACOWorker$1() { + function DRACOWorker() { let decoderConfig; let decoderPending; @@ -18441,10 +18461,10 @@ } class Lorder{ constructor() { - this.gltfLoader = new GLTFLoader$1(); + this.gltfLoader = new GLTFLoader(); // Optional: Provide a DRACOLoader instance to decode compressed mesh data - const dracoLoader = new DRACOLoader$1(); + const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( Config.DRACOPath ); this.gltfLoader.setDRACOLoader( dracoLoader ); this.objLoader = new OBJLoader(); // obj模型 @@ -18690,5725 +18710,471 @@ uniform float mieCoefficient; uniform vec3 up; - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; - - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); - } - - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } - - void main() { - - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far - - vSunDirection = normalize( sunPosition ); - - vSunE = sunIntensity( dot( vSunDirection, up ) ); - - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; - - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; - - }`, - - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - uniform float mieDirectionalG; - uniform vec3 up; - - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; - - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; - - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } - - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } - - void main() { - - vec3 direction = normalize( vWorldPosition - cameraPosition ); - - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; - - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - - // in scattering - float cosTheta = dot( direction, vSunDirection ); - - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; - - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; - - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; - - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - - gl_FragColor = vec4( retColor, 1.0 ); - - #include - #include - - }` - - }; - - // 多个canvas并没有id,只有父节点 - - - class Layer extends BasLayer{ - id; // 唯一标识 - layerContainer; // div#layer 容器 - zIndex=1;//默认为1 - opacity=1;//默认为1 - canvas;//canvas - dispose = false; - renderer;//canvas上下文 - scene;//场景 - visible=true;//是否可见 - mapView;//地图视图 - camera;//相机 - controls;//控件 - animateId;//动画事件id - base = false; // 是否为底图 - ambientLight; // 环境光 - directionalLight; // 方向光 - modelLayer = false; // 模型图层 - imageLayer = false; // 影像图层 - vectorLayer = false; // 矢量图层,如路网、行政区划,地名等图层 - waters = []; // 水面集合 - constructor(id, layerContainer, canvas, mapView, plane = true, camera = new three.PerspectiveCamera(80, 1, 0.1, 1e12)) { - super(); - this.id = id; - this.layerContainer = layerContainer; - this.canvas = canvas; - this.renderer = new three.WebGLRenderer({ - canvas: this.canvas, - antialias: true, - alpha: true, - logarithmicDepthBuffer: true, - precision: "highp", - }); - this.renderer.sortObjects = true; - this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setClearColor(0xFFFFFF, 0.0); - this.scene = new three.Scene(); - this.mapView = mapView; - this.camera = camera; - if(this.mapView){ - this.scene.add(this.mapView); - this.mapView.updateMatrixWorld(true); - } - if (plane){ - this.controls = new MapControls(this.camera, this.canvas); - this.controls.minDistance = 1e1; - this.controls.zoomSpeed = 2.0; - } else { - this.controls = new OrbitControls(this.camera, this.canvas); - this.controls.enablePan = false; - this.controls.minDistance = UnitsUtils.EARTH_RADIUS + 2; - this.controls.maxDistance = UnitsUtils.EARTH_RADIUS * 1e1; - } - this._raycaster = new three.Raycaster(); - if(Config.outLine.on){ - this.effectOutline = new EffectOutline(this.renderer, this.scene, this.camera, this.canvas.width, this.canvas.height); - } - if (Config.layer.map.ambientLight.add){ - this.scene.add(new three.AmbientLight(Config.layer.map.ambientLight.color, Config.layer.map.ambientLight.intensity)); - } - if (Config.layer.map.directionalLight.add){ - this.scene.add(new three.DirectionalLight(Config.layer.map.directionalLight.color, Config.layer.map.directionalLight.intensity)); - } - if (Config.layer.map.pointLight.add){ - let pointLight = new three.PointLight(Config.layer.map.pointLight.color, Config.layer.map.pointLight.intensity, Config.layer.map.pointLight.distance); - pointLight.position.set(...Config.layer.map.pointLight.position); - this.scene.add(pointLight); - } - } - - moveTo(lat, lon, height = 38472.48763833733){ - // var coords = UnitsUtils.datumsToSpherical(44.266119,90.139228); - var coords = UnitsUtils.datumsToSpherical(lat,lon); - this.camera.position.set(coords.x, height, -coords.y); - this.controls.target.set(this.camera.position.x, 0, this.camera.position.z); - } - - moveToByCoords(coords){ - let offset = 50; - this.camera.position.set(coords.x, coords.y+offset, coords.z); - this.controls.target.set(coords.x, coords.y, coords.z); - } - - moveToByLL(lat, lon, distance = 384720){ - let dir = UnitsUtils.datumsToVector(lat, lon); - dir.multiplyScalar(UnitsUtils.EARTH_RADIUS + distance); - this.camera.position.copy(dir); - } - - - on(eventName, callback){ - this.listener.on(eventName, callback); - } - - setSceneBackground(color) { - this.scene.background = color; - } - - clearSceneBackground() { - this.scene.background = null; - } - - // 可用于添加灯光mesh等元素 - add(Object3D) { - if(Object3D ==null || Object3D ==undefined){ - return; - } - this.scene.add(Object3D); - } - - - remove(Object3D) { - if(Object3D ==null || Object3D ==undefined){ - return; - } - this.scene.remove(Object3D); - } - - openWaterConfig(){ - /** - * 打开渲染水系配置 - */ - // this.renderer.setPixelRatio( window.devicePixelRatio ); - this.renderer.toneMapping = three.ACESFilmicToneMapping; - this.renderer.toneMappingExposure = 0.5; - - // 添加天空 - this.sky = new Sky(); - this.sky.translateX = true; - this.sky.translateY = true; - this.sky.translateZ = true; - this.sky.rotateX = Math.PI / 2; - this.sky.scale.setScalar( Config.EARTH_RADIUS * 2 * Math.PI ); // 天空放大倍数 - this.scene.add( this.sky ); - const skyUniforms = this.sky.material.uniforms; - // 天空的配置 - skyUniforms[ 'turbidity' ].value = 10; - skyUniforms[ 'rayleigh' ].value = 2; - skyUniforms[ 'mieCoefficient' ].value = 0.005; - skyUniforms[ 'mieDirectionalG' ].value = 0.8; - // 旋转 设置为y朝上 - // sky.material.uniforms["up"].value = new THREE.Vector3(0, 1, 0); - - // 天空映射, 更新太阳位置 - this.pmremGenerator = new three.PMREMGenerator( this.renderer ); - this.sceneEnv = new three.Scene(); - this.renderTarget = null; - this.sun = new three.Vector3(); - this.updateSun(Config.SUNDEGREE, Config.SUNAZIMUTH); - } - - updateSun(elevation, azimuth) { - - const phi = three.MathUtils.degToRad( 90 - elevation ); - const theta = three.MathUtils.degToRad( azimuth ); - - this.sun.setFromSphericalCoords( 1, phi, theta ); - - this.sky.material.uniforms[ 'sunPosition' ].value.copy( this.sun ); - for (let water of this.waters){ - water.material.uniforms[ 'sunDirection' ].value.copy( this.sun ).normalize(); - } - if ( this.renderTarget !== null ) this.renderTarget.dispose(); - - this.sceneEnv.add( this.sky ); - this.renderTarget = this.pmremGenerator.fromScene( this.sceneEnv ); - this.scene.add( this.sky ); - - this.scene.environment = this.renderTarget.texture; - - - } - - /** - * 添加水系 - * @param {*} water - * @returns - */ - addWater(water) { - if(water ==null || water ==undefined){ - return; - } - this.scene.add(water); - this.waters.push(water); - } - - removeWater(water) { - if(water ==null || water ==undefined){ - return; - } - this.scene.remove(water); - let index = this.waters.indexOf(water); - if (index > -1) { - this.waters.splice(index, 1); - } - } - - setVisible(visible) { - this.visible = this.visible; - this.layerContainer.style.display = visible ? 'block' : 'none'; - } - /** - * @deprecated 不建议用 - * @param {number} opacity - */ - setOpacity(opacity) { - this.opacity = opacity; - this.layerContainer.style.opacity = opacity; - } - - /** - * 修改显示层级,默认越靠下的dom元素,显示上越靠上 - * @param {number} zIndex - */ - setZIndex(zIndex) { - this.zIndex = zIndex; - this.layerContainer.style.zIndex = this.zIndex; - } - - dispose() { - Element.removeLayer(id); - this.dispose = true; - this.animateId && cancelAnimationFrame(this.animateId); - this.mapView.root.dispose(); - this.mapView.dispose(); - } - - selectModel(insect){ - this.effectOutline.selectModel(insect); - } - - resize(){ - var width = window.innerWidth; - var height = window.innerHeight; - this.renderer.setSize(width, height); - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - if(Config.outLine.on){ - this.effectOutline.resize(width, height); - } - } - - animate(){ - this.animateId = requestAnimationFrame(this.animate.bind(this)); - if(this.base){ - this.controls.update(); - } - if (this.base){ - update(); //目前只有在基础地图中添加相机移动的动画,所以暂且只在base地图中进行渲染,后期可改成每个图层都进行渲染,或者必要时进行渲染。 - } - for(let water of this.waters){ - water.material.uniforms[ 'time' ].value += 1.0 / 60.0; - } - if (Config.outLine.on){ - this.effectOutline.render(); // 合成器渲染 - } else { - this.renderer.autoClear = true; - } - this.renderer.render(this.scene, this.camera); - } - - _raycast(meshes, recursive, faceExclude) { - const isects = this._raycaster.intersectObjects(meshes, recursive); - if (faceExclude) { - for (let i = 0; i < isects.length; i++) { - if (isects[i].face !== faceExclude) { - return isects[i]; - } - } - return null; - } - return isects.length > 0 ? isects[0] : null; - } - - _raycastFromMouse(mx, my, width, height, cam, meshes, recursive=false) { - const mouse = new three.Vector2( // normalized (-1 to +1) - (mx / width) * 2 - 1, - - (my / height) * 2 + 1); - // https://threejs.org/docs/#api/core/Raycaster - // update the picking ray with the camera and mouse position - this._raycaster.setFromCamera(mouse, cam); - return this._raycast(meshes, recursive, null); - } - - /** - * - * @param {*} mx 屏幕坐标x - * @param {*} my 屏幕坐标y - * @param {boolean} recursive 是否检查子节点,true 递归检查 - * @returns mesh - */ - raycastFromMouse(mx, my, recursive=false) { - //---- NG: 2x when starting with Chrome's inspector mobile - // const {width, height} = this.renderer.domElement; - // const {width, height} = this.canvas; - //---- OK - const {clientWidth, clientHeight} = this.canvas; - - return this._raycastFromMouse( - mx, my, clientWidth, clientHeight, this.camera, - this.mapView.children, recursive); - } - - insectALL(mx, my, recursive=false) { - const {clientWidth, clientHeight} = this.canvas; - - return this._raycastFromMouse( - mx, my, clientWidth, clientHeight, this.camera, - this.scene.children, recursive); - } - - } - - /** - * @param {BufferGeometry} geometry - * @param {number} drawMode - * @return {BufferGeometry} - */ - function toTrianglesDrawMode( geometry, drawMode ) { - - if ( drawMode === three.TrianglesDrawMode ) { - return geometry; - - } - - if ( drawMode === three.TriangleFanDrawMode || drawMode === three.TriangleStripDrawMode ) { - - let index = geometry.getIndex(); - - // generate index if not present - - if ( index === null ) { - - const indices = []; - - const position = geometry.getAttribute( 'position' ); - - if ( position !== undefined ) { - - for ( let i = 0; i < position.count; i ++ ) { - - indices.push( i ); - - } - - geometry.setIndex( indices ); - index = geometry.getIndex(); - - } else { - return geometry; - - } - - } - - // - - const numberOfTriangles = index.count - 2; - const newIndices = []; - - if ( drawMode === three.TriangleFanDrawMode ) { - - // gl.TRIANGLE_FAN - - for ( let i = 1; i <= numberOfTriangles; i ++ ) { - - newIndices.push( index.getX( 0 ) ); - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - - } - - } else { - - // gl.TRIANGLE_STRIP - - for ( let i = 0; i < numberOfTriangles; i ++ ) { - - if ( i % 2 === 0 ) { - - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i + 2 ) ); - - } else { - - newIndices.push( index.getX( i + 2 ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i ) ); - - } - - } - - } - - if ( ( newIndices.length / 3 ) !== numberOfTriangles ) ; - - // build final geometry - - const newGeometry = geometry.clone(); - newGeometry.setIndex( newIndices ); - newGeometry.clearGroups(); - - return newGeometry; - - } else { - return geometry; - - } - - } - - class GLTFLoader extends three.Loader { - - constructor( manager ) { - - super( manager ); - - this.dracoLoader = null; - this.ktx2Loader = null; - this.meshoptDecoder = null; - - this.pluginCallbacks = []; - - this.register( function ( parser ) { - - return new GLTFMaterialsClearcoatExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureBasisUExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureWebPExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureAVIFExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSheenExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsTransmissionExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsVolumeExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIorExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsEmissiveStrengthExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSpecularExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIridescenceExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsAnisotropyExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsBumpExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFLightsExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshoptCompression( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshGpuInstancing( parser ); - - } ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - let resourcePath; - - if ( this.resourcePath !== '' ) { - - resourcePath = this.resourcePath; - - } else if ( this.path !== '' ) { - - // If a base path is set, resources will be relative paths from that plus the relative path of the gltf file - // Example path = 'https://my-cnd-server.com/', url = 'assets/models/model.gltf' - // resourcePath = 'https://my-cnd-server.com/assets/models/' - // referenced resource 'model.bin' will be loaded from 'https://my-cnd-server.com/assets/models/model.bin' - // referenced resource '../textures/texture.png' will be loaded from 'https://my-cnd-server.com/assets/textures/texture.png' - const relativeUrl = three.LoaderUtils.extractUrlBase( url ); - resourcePath = three.LoaderUtils.resolveURL( relativeUrl, 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. - this.manager.itemStart( url ); - - const _onError = function ( e ) { - - if ( onError ) { - - onError( e ); - - } - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - }; - - const loader = new three.FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - 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 ); - - } - - setDRACOLoader( dracoLoader ) { - - this.dracoLoader = dracoLoader; - return this; - - } - - setDDSLoader() { - - throw new Error( - - 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' - - ); - - } - - setKTX2Loader( ktx2Loader ) { - - this.ktx2Loader = ktx2Loader; - return this; - - } - - setMeshoptDecoder( meshoptDecoder ) { - - this.meshoptDecoder = meshoptDecoder; - return this; - - } - - register( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - } - - unregister( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - } - - parse( data, path, onLoad, onError ) { - - let json; - const extensions = {}; - const plugins = {}; - const textDecoder = new TextDecoder(); - - if ( typeof data === 'string' ) { - - json = JSON.parse( data ); - - } else if ( data instanceof ArrayBuffer ) { - - const magic = textDecoder.decode( 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; - - } - - json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); - - } else { - - json = JSON.parse( textDecoder.decode( data ) ); - - } - - } else { - - json = data; - - } - - if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { - - if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); - return; - - } - - const parser = new GLTFParser( json, { - - path: path || this.resourcePath || '', - crossOrigin: this.crossOrigin, - requestHeader: this.requestHeader, - manager: this.manager, - ktx2Loader: this.ktx2Loader, - meshoptDecoder: this.meshoptDecoder - - } ); - - parser.fileLoader.setRequestHeader( this.requestHeader ); - - for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { - - const plugin = this.pluginCallbacks[ i ]( parser ); - - if ( ! plugin.name ) ; - - plugins[ plugin.name ] = plugin; - - // Workaround to avoid determining as unknown extension - // in addUnknownExtensionsToUserData(). - // Remove this workaround if we move all the existing - // extension handlers to plugin system - extensions[ plugin.name ] = true; - - } - - if ( json.extensionsUsed ) { - - for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { - - const extensionName = json.extensionsUsed[ i ]; - const extensionsRequired = json.extensionsRequired || []; - - switch ( extensionName ) { - - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; - - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; - - case EXTENSIONS.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension(); - break; - - case EXTENSIONS.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); - break; - - default: - - if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) ; - - } - - } - - } - - parser.setExtensions( extensions ); - parser.setPlugins( plugins ); - parser.parse( onLoad, onError ); - - } - - parseAsync( data, path ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.parse( data, path, resolve, reject ); - - } ); - - } - - } - - /* GLTFREGISTRY */ - - function GLTFRegistry() { - - let 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 ***********/ - /*********************************/ - - const EXTENSIONS = { - KHR_BINARY_GLTF: 'KHR_binary_glTF', - KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', - KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', - KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', - KHR_MATERIALS_IOR: 'KHR_materials_ior', - KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', - KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', - KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', - KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', - KHR_MATERIALS_ANISOTROPY: 'KHR_materials_anisotropy', - KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', - KHR_MATERIALS_VOLUME: 'KHR_materials_volume', - KHR_TEXTURE_BASISU: 'KHR_texture_basisu', - KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', - KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', - KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength', - EXT_MATERIALS_BUMP: 'EXT_materials_bump', - EXT_TEXTURE_WEBP: 'EXT_texture_webp', - EXT_TEXTURE_AVIF: 'EXT_texture_avif', - EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', - EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing' - }; - - /** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ - class GLTFLightsExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; - - // Object3D instance caches - this.cache = { refs: {}, uses: {} }; - - } - - _markDefs() { - - const parser = this.parser; - const nodeDefs = this.parser.json.nodes || []; - - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.extensions - && nodeDef.extensions[ this.name ] - && nodeDef.extensions[ this.name ].light !== undefined ) { - - parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); - - } - - } - - } - - _loadLight( lightIndex ) { - - const parser = this.parser; - const cacheKey = 'light:' + lightIndex; - let dependency = parser.cache.get( cacheKey ); - - if ( dependency ) return dependency; - - const json = parser.json; - const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; - const lightDefs = extensions.lights || []; - const lightDef = lightDefs[ lightIndex ]; - let lightNode; - - const color = new three.Color( 0xffffff ); - - if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], three.LinearSRGBColorSpace ); - - const 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; - - assignExtrasToUserData( lightNode, lightDef ); - - if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; - - lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); - - dependency = Promise.resolve( lightNode ); - - parser.cache.add( cacheKey, dependency ); - - return dependency; - - } - - getDependency( type, index ) { - - if ( type !== 'light' ) return; - - return this._loadLight( index ); - - } - - createNodeAttachment( nodeIndex ) { - - const self = this; - const parser = this.parser; - const json = parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; - const lightIndex = lightDef.light; - - if ( lightIndex === undefined ) return null; - - return this._loadLight( lightIndex ).then( function ( light ) { - - return parser._getNodeRef( self.cache, lightIndex, light ); - - } ); - - } - - } - - /** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ - class GLTFMaterialsUnlitExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; - - } - - getMaterialType() { - - return three.MeshBasicMaterial; - - } - - extendParams( materialParams, materialDef, parser ) { - - const pending = []; - - materialParams.color = new three.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - const metallicRoughness = materialDef.pbrMetallicRoughness; - - if ( metallicRoughness ) { - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], three.LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, three.SRGBColorSpace ) ); - - } - - } - - return Promise.all( pending ); - - } - - } - - /** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - */ - class GLTFMaterialsEmissiveStrengthExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const emissiveStrength = materialDef.extensions[ this.name ].emissiveStrength; - - if ( emissiveStrength !== undefined ) { - - materialParams.emissiveIntensity = emissiveStrength; - - } - - return Promise.resolve(); - - } - - } - - /** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - */ - class GLTFMaterialsClearcoatExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.clearcoatFactor !== undefined ) { - - materialParams.clearcoat = extension.clearcoatFactor; - - } - - if ( extension.clearcoatTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); - - } - - if ( extension.clearcoatRoughnessFactor !== undefined ) { - - materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; - - } - - if ( extension.clearcoatRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); - - } - - if ( extension.clearcoatNormalTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); - - if ( extension.clearcoatNormalTexture.scale !== undefined ) { - - const scale = extension.clearcoatNormalTexture.scale; - - materialParams.clearcoatNormalScale = new three.Vector2( scale, scale ); - - } - - } - - return Promise.all( pending ); - - } - - } - - /** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - */ - class GLTFMaterialsIridescenceExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.iridescenceFactor !== undefined ) { - - materialParams.iridescence = extension.iridescenceFactor; - - } - - if ( extension.iridescenceTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); - - } - - if ( extension.iridescenceIor !== undefined ) { - - materialParams.iridescenceIOR = extension.iridescenceIor; - - } - - if ( materialParams.iridescenceThicknessRange === undefined ) { - - materialParams.iridescenceThicknessRange = [ 100, 400 ]; - - } - - if ( extension.iridescenceThicknessMinimum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; - - } - - if ( extension.iridescenceThicknessMaximum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; - - } - - if ( extension.iridescenceThicknessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); - - } - - return Promise.all( pending ); - - } - - } - - /** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - */ - class GLTFMaterialsSheenExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - materialParams.sheenColor = new three.Color( 0, 0, 0 ); - materialParams.sheenRoughness = 0; - materialParams.sheen = 1; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.sheenColorFactor !== undefined ) { - - const colorFactor = extension.sheenColorFactor; - materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], three.LinearSRGBColorSpace ); - - } - - if ( extension.sheenRoughnessFactor !== undefined ) { - - materialParams.sheenRoughness = extension.sheenRoughnessFactor; - - } - - if ( extension.sheenColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, three.SRGBColorSpace ) ); - - } - - if ( extension.sheenRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); - - } - - return Promise.all( pending ); - - } - - } - - /** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - * Draft: https://github.com/KhronosGroup/glTF/pull/1698 - */ - class GLTFMaterialsTransmissionExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.transmissionFactor !== undefined ) { - - materialParams.transmission = extension.transmissionFactor; - - } - - if ( extension.transmissionTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); - - } - - return Promise.all( pending ); - - } - - } - - /** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ - class GLTFMaterialsVolumeExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; - - if ( extension.thicknessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); - - } - - materialParams.attenuationDistance = extension.attenuationDistance || Infinity; - - const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; - materialParams.attenuationColor = new three.Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], three.LinearSRGBColorSpace ); - - return Promise.all( pending ); - - } - - } - - /** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - */ - class GLTFMaterialsIorExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IOR; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const extension = materialDef.extensions[ this.name ]; - - materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; - - return Promise.resolve(); - - } - - } - - /** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - */ - class GLTFMaterialsSpecularExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; - - if ( extension.specularTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); - - } - - const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; - materialParams.specularColor = new three.Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], three.LinearSRGBColorSpace ); - - if ( extension.specularColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, three.SRGBColorSpace ) ); - - } - - return Promise.all( pending ); - - } - - } - - - /** - * Materials bump Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump - */ - class GLTFMaterialsBumpExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_MATERIALS_BUMP; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.bumpScale = extension.bumpFactor !== undefined ? extension.bumpFactor : 1.0; - - if ( extension.bumpTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'bumpMap', extension.bumpTexture ) ); - - } - - return Promise.all( pending ); - - } - - } - - /** - * Materials anisotropy Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy - */ - class GLTFMaterialsAnisotropyExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return three.MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.anisotropyStrength !== undefined ) { - - materialParams.anisotropy = extension.anisotropyStrength; - - } - - if ( extension.anisotropyRotation !== undefined ) { - - materialParams.anisotropyRotation = extension.anisotropyRotation; - - } - - if ( extension.anisotropyTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'anisotropyMap', extension.anisotropyTexture ) ); - - } - - return Promise.all( pending ); - - } - - } - - /** - * BasisU Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu - */ - class GLTFTextureBasisUExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_TEXTURE_BASISU; - - } - - loadTexture( textureIndex ) { - - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ this.name ]; - const loader = parser.options.ktx2Loader; - - if ( ! loader ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); - - } else { - - // Assumes that the extension is optional and that a fallback texture is present - return null; - - } - - } - - return parser.loadTextureImage( textureIndex, extension.source, loader ); - - } - - } - - /** - * WebP Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp - */ - class GLTFTextureWebPExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_WEBP; - this.isSupported = null; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. Support for lossy images doesn't guarantee support for all - // WebP images, unfortunately. - image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; - - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; - - } - - } - - /** - * AVIF Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif - */ - class GLTFTextureAVIFExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_AVIF; - this.isSupported = null; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: AVIF required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. - image.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkQAAAAB8dSLfI='; - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; - - } - - } - - /** - * meshopt BufferView Compression Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression - */ - class GLTFMeshoptCompression { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; - this.parser = parser; - - } - - loadBufferView( index ) { - - const json = this.parser.json; - const bufferView = json.bufferViews[ index ]; - - if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { - - const extensionDef = bufferView.extensions[ this.name ]; - - const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); - const decoder = this.parser.options.meshoptDecoder; - - if ( ! decoder || ! decoder.supported ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); - - } else { - - // Assumes that the extension is optional and that fallback buffer data is present - return null; - - } - - } - - return buffer.then( function ( res ) { - - const byteOffset = extensionDef.byteOffset || 0; - const byteLength = extensionDef.byteLength || 0; - - const count = extensionDef.count; - const stride = extensionDef.byteStride; - - const source = new Uint8Array( res, byteOffset, byteLength ); - - if ( decoder.decodeGltfBufferAsync ) { - - return decoder.decodeGltfBufferAsync( count, stride, source, extensionDef.mode, extensionDef.filter ).then( function ( res ) { - - return res.buffer; - - } ); - - } else { - - // Support for MeshoptDecoder 0.18 or earlier, without decodeGltfBufferAsync - return decoder.ready.then( function () { - - const result = new ArrayBuffer( count * stride ); - decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); - return result; - - } ); - - } - - } ); - - } else { - - return null; - - } - - } - - } - - /** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - * - */ - class GLTFMeshGpuInstancing { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; - this.parser = parser; - - } - - createNodeMesh( nodeIndex ) { - - const json = this.parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] || - nodeDef.mesh === undefined ) { - - return null; - - } - - const meshDef = json.meshes[ nodeDef.mesh ]; - - // No Points or Lines + Instancing support yet - - for ( const primitive of meshDef.primitives ) { - - if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && - primitive.mode !== undefined ) { - - return null; - - } - - } - - const extensionDef = nodeDef.extensions[ this.name ]; - const attributesDef = extensionDef.attributes; - - // @TODO: Can we support InstancedMesh + SkinnedMesh? - - const pending = []; - const attributes = {}; - - for ( const key in attributesDef ) { - - pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => { - - attributes[ key ] = accessor; - return attributes[ key ]; - - } ) ); - - } - - if ( pending.length < 1 ) { - - return null; - - } - - pending.push( this.parser.createNodeMesh( nodeIndex ) ); - - return Promise.all( pending ).then( results => { - - const nodeObject = results.pop(); - const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ]; - const count = results[ 0 ].count; // All attribute counts should be same - const instancedMeshes = []; - - for ( const mesh of meshes ) { - - // Temporal variables - const m = new three.Matrix4(); - const p = new three.Vector3(); - const q = new three.Quaternion(); - const s = new three.Vector3( 1, 1, 1 ); - - const instancedMesh = new three.InstancedMesh( mesh.geometry, mesh.material, count ); - - for ( let i = 0; i < count; i ++ ) { - - if ( attributes.TRANSLATION ) { - - p.fromBufferAttribute( attributes.TRANSLATION, i ); - - } - - if ( attributes.ROTATION ) { - - q.fromBufferAttribute( attributes.ROTATION, i ); - - } - - if ( attributes.SCALE ) { - - s.fromBufferAttribute( attributes.SCALE, i ); - - } - - instancedMesh.setMatrixAt( i, m.compose( p, q, s ) ); - - } - - // Add instance attributes to the geometry, excluding TRS. - for ( const attributeName in attributes ) { - - if ( attributeName === '_COLOR_0' ) { - - const attr = attributes[ attributeName ]; - instancedMesh.instanceColor = new three.InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ); - - } else if ( attributeName !== 'TRANSLATION' && - attributeName !== 'ROTATION' && - attributeName !== 'SCALE' ) { - - mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] ); - - } - - } - - // Just in case - three.Object3D.prototype.copy.call( instancedMesh, mesh ); - - this.parser.assignFinalMaterial( instancedMesh ); - - instancedMeshes.push( instancedMesh ); - - } - - if ( nodeObject.isGroup ) { - - nodeObject.clear(); - - nodeObject.add( ... instancedMeshes ); - - return nodeObject; - - } - - return instancedMeshes[ 0 ]; - - } ); - - } - - } - - /* BINARY EXTENSION */ - const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; - const BINARY_EXTENSION_HEADER_LENGTH = 12; - const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; - - class GLTFBinaryExtension { - - constructor( data ) { - - this.name = EXTENSIONS.KHR_BINARY_GLTF; - this.content = null; - this.body = null; - - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); - const textDecoder = new TextDecoder(); - - this.header = { - magic: textDecoder.decode( 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.' ); - - } - - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - let chunkIndex = 0; - - while ( chunkIndex < chunkContentsLength ) { - - const chunkLength = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - const chunkType = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); - this.content = textDecoder.decode( contentArray ); - - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - - const 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/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression - */ - class GLTFDracoMeshCompressionExtension { - - constructor( 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; - this.dracoLoader.preload(); - - } - - decodePrimitive( primitive, parser ) { - - const json = this.json; - const dracoLoader = this.dracoLoader; - const bufferViewIndex = primitive.extensions[ this.name ].bufferView; - const gltfAttributeMap = primitive.extensions[ this.name ].attributes; - const threeAttributeMap = {}; - const attributeNormalizedMap = {}; - const attributeTypeMap = {}; - - for ( const attributeName in gltfAttributeMap ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; - - } - - for ( const attributeName in primitive.attributes ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - if ( gltfAttributeMap[ attributeName ] !== undefined ) { - - const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - attributeTypeMap[ threeAttributeName ] = componentType.name; - attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; - - } - - } - - return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - - return new Promise( function ( resolve, reject ) { - - dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - - for ( const attributeName in geometry.attributes ) { - - const attribute = geometry.attributes[ attributeName ]; - const normalized = attributeNormalizedMap[ attributeName ]; - - if ( normalized !== undefined ) attribute.normalized = normalized; - - } - - resolve( geometry ); - - }, threeAttributeMap, attributeTypeMap, three.LinearSRGBColorSpace, reject ); - - } ); - - } ); - - } - - } - - /** - * Texture Transform Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform - */ - class GLTFTextureTransformExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; - - } - - extendTexture( texture, transform ) { - - if ( ( transform.texCoord === undefined || transform.texCoord === texture.channel ) - && transform.offset === undefined - && transform.rotation === undefined - && transform.scale === undefined ) { - - // See https://github.com/mrdoob/three.js/issues/21819. - return texture; - - } - - texture = texture.clone(); - - if ( transform.texCoord !== undefined ) { - - texture.channel = transform.texCoord; - - } - - 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 ); - - } - - texture.needsUpdate = true; - - return texture; - - } - - } - - /** - * Mesh Quantization Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization - */ - class GLTFMeshQuantizationExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; - - } - - } - - /*********************************/ - /********** INTERPOLATION ********/ - /*********************************/ - - // Spline Interpolation - // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation - class GLTFCubicSplineInterpolant extends three.Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - copySampleValue_( index ) { - - // Copies a sample value to the result buffer. See description of glTF - // CUBICSPLINE values layout in interpolate_() function below. - - const result = this.resultBuffer, - values = this.sampleValues, - valueSize = this.valueSize, - offset = index * valueSize * 3 + valueSize; - - for ( let i = 0; i !== valueSize; i ++ ) { - - result[ i ] = values[ offset + i ]; - - } - - return result; - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer; - const values = this.sampleValues; - const stride = this.valueSize; - - const stride2 = stride * 2; - const stride3 = stride * 3; - - const td = t1 - t0; - - const p = ( t - t0 ) / td; - const pp = p * p; - const ppp = pp * p; - - const offset1 = i1 * stride3; - const offset0 = offset1 - stride3; - - const s2 = - 2 * ppp + 3 * pp; - const s3 = ppp - pp; - const s0 = 1 - s2; - const s1 = s3 - pp + p; - - // Layout of keyframe output values for CUBICSPLINE animations: - // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] - for ( let i = 0; i !== stride; i ++ ) { - - const p0 = values[ offset0 + i + stride ]; // splineVertex_k - const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) - const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 - const 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; - - } - - } - - const _q = new three.Quaternion(); - - class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { - - interpolate_( i1, t0, t, t1 ) { - - const result = super.interpolate_( i1, t0, t, t1 ); - - _q.fromArray( result ).normalize().toArray( result ); - - return result; - - } - - } - - - /*********************************/ - /********** INTERNALS ************/ - /*********************************/ - - /* CONSTANTS */ - - const 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 - }; - - const WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array - }; - - const WEBGL_FILTERS = { - 9728: three.NearestFilter, - 9729: three.LinearFilter, - 9984: three.NearestMipmapNearestFilter, - 9985: three.LinearMipmapNearestFilter, - 9986: three.NearestMipmapLinearFilter, - 9987: three.LinearMipmapLinearFilter - }; - - const WEBGL_WRAPPINGS = { - 33071: three.ClampToEdgeWrapping, - 33648: three.MirroredRepeatWrapping, - 10497: three.RepeatWrapping - }; - - const WEBGL_TYPE_SIZES = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 - }; - - const ATTRIBUTES = { - POSITION: 'position', - NORMAL: 'normal', - TANGENT: 'tangent', - TEXCOORD_0: 'uv', - TEXCOORD_1: 'uv1', - TEXCOORD_2: 'uv2', - TEXCOORD_3: 'uv3', - COLOR_0: 'color', - WEIGHTS_0: 'skinWeight', - JOINTS_0: 'skinIndex', - }; - - const PATH_PROPERTIES = { - scale: 'scale', - translation: 'position', - rotation: 'quaternion', - weights: 'morphTargetInfluences' - }; - - const 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 - }; - - const ALPHA_MODES = { - OPAQUE: 'OPAQUE', - MASK: 'MASK', - BLEND: 'BLEND' - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material - */ - function createDefaultMaterial( cache ) { - - if ( cache[ 'DefaultMaterial' ] === undefined ) { - - cache[ 'DefaultMaterial' ] = new three.MeshStandardMaterial( { - color: 0xFFFFFF, - emissive: 0x000000, - metalness: 1, - roughness: 1, - transparent: false, - depthTest: true, - side: three.FrontSide - } ); - - } - - return cache[ 'DefaultMaterial' ]; - - } - - function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { - - // Add unknown glTF extensions to an object's userData. - - for ( const name in objectDef.extensions ) { - - if ( knownExtensions[ name ] === undefined ) { - - object.userData.gltfExtensions = object.userData.gltfExtensions || {}; - object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; - - } - - } - - } - - /** - * @param {Object3D|Material|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 ); - - } - - } - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets - * - * @param {BufferGeometry} geometry - * @param {Array} targets - * @param {GLTFParser} parser - * @return {Promise} - */ - function addMorphTargets( geometry, targets, parser ) { - - let hasMorphPosition = false; - let hasMorphNormal = false; - let hasMorphColor = false; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) hasMorphPosition = true; - if ( target.NORMAL !== undefined ) hasMorphNormal = true; - if ( target.COLOR_0 !== undefined ) hasMorphColor = true; - - if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; - - } - - if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); - - const pendingPositionAccessors = []; - const pendingNormalAccessors = []; - const pendingColorAccessors = []; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( hasMorphPosition ) { - - const pendingAccessor = target.POSITION !== undefined - ? parser.getDependency( 'accessor', target.POSITION ) - : geometry.attributes.position; - - pendingPositionAccessors.push( pendingAccessor ); - - } - - if ( hasMorphNormal ) { - - const pendingAccessor = target.NORMAL !== undefined - ? parser.getDependency( 'accessor', target.NORMAL ) - : geometry.attributes.normal; - - pendingNormalAccessors.push( pendingAccessor ); - - } - - if ( hasMorphColor ) { - - const pendingAccessor = target.COLOR_0 !== undefined - ? parser.getDependency( 'accessor', target.COLOR_0 ) - : geometry.attributes.color; - - pendingColorAccessors.push( pendingAccessor ); - - } - - } - - return Promise.all( [ - Promise.all( pendingPositionAccessors ), - Promise.all( pendingNormalAccessors ), - Promise.all( pendingColorAccessors ) - ] ).then( function ( accessors ) { - - const morphPositions = accessors[ 0 ]; - const morphNormals = accessors[ 1 ]; - const morphColors = accessors[ 2 ]; - - if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; - if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; - if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; - geometry.morphTargetsRelative = true; - - return geometry; - - } ); - - } - - /** - * @param {Mesh} mesh - * @param {GLTF.Mesh} meshDef - */ - function updateMorphTargets( mesh, meshDef ) { - - mesh.updateMorphTargets(); - - if ( meshDef.weights !== undefined ) { - - for ( let 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 ) ) { - - const targetNames = meshDef.extras.targetNames; - - if ( mesh.morphTargetInfluences.length === targetNames.length ) { - - mesh.morphTargetDictionary = {}; - - for ( let i = 0, il = targetNames.length; i < il; i ++ ) { - - mesh.morphTargetDictionary[ targetNames[ i ] ] = i; - - } - - } - - } - - } - - function createPrimitiveKey( primitiveDef ) { - - let geometryKey; - - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; - - if ( dracoExtension ) { - - geometryKey = 'draco:' + dracoExtension.bufferView - + ':' + dracoExtension.indices - + ':' + createAttributesKey( dracoExtension.attributes ); - - } else { - - geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; - - } - - if ( primitiveDef.targets !== undefined ) { - - for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - - geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); - - } - - } - - return geometryKey; - - } - - function createAttributesKey( attributes ) { - - let attributesKey = ''; - - const keys = Object.keys( attributes ).sort(); - - for ( let i = 0, il = keys.length; i < il; i ++ ) { - - attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; - - } - - return attributesKey; - - } - - function getNormalizedComponentScale( constructor ) { - - // Reference: - // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data - - switch ( constructor ) { - - case Int8Array: - return 1 / 127; - - case Uint8Array: - return 1 / 255; - - case Int16Array: - return 1 / 32767; - - case Uint16Array: - return 1 / 65535; - - default: - throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); - - } - - } - - function getImageURIMimeType( uri ) { - - if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; - if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; - - return 'image/png'; - - } - - const _identityMatrix = new three.Matrix4(); - - /* GLTF PARSER */ - - class GLTFParser { - - constructor( json = {}, options = {} ) { - - this.json = json; - this.extensions = {}; - this.plugins = {}; - this.options = options; - - // loader object cache - this.cache = new GLTFRegistry(); - - // associations between Three.js objects and glTF elements - this.associations = new Map(); - - // BufferGeometry caching - this.primitiveCache = {}; - - // Node cache - this.nodeCache = {}; - - // Object3D instance caches - this.meshCache = { refs: {}, uses: {} }; - this.cameraCache = { refs: {}, uses: {} }; - this.lightCache = { refs: {}, uses: {} }; - - this.sourceCache = {}; - this.textureCache = {}; - - // Track node names, to ensure no duplicates - this.nodeNamesUsed = {}; - - // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the - // expensive work of uploading a texture to the GPU off the main thread. - - let isSafari = false; - let isFirefox = false; - let firefoxVersion = - 1; - - if ( typeof navigator !== 'undefined' ) { - - isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true; - isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1; - firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; - - } - - if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) { - - this.textureLoader = new three.TextureLoader( this.options.manager ); - - } else { - - this.textureLoader = new three.ImageBitmapLoader( this.options.manager ); - - } - - this.textureLoader.setCrossOrigin( this.options.crossOrigin ); - this.textureLoader.setRequestHeader( this.options.requestHeader ); - - this.fileLoader = new three.FileLoader( this.options.manager ); - this.fileLoader.setResponseType( 'arraybuffer' ); - - if ( this.options.crossOrigin === 'use-credentials' ) { - - this.fileLoader.setWithCredentials( true ); - - } - - } - - setExtensions( extensions ) { - - this.extensions = extensions; - - } - - setPlugins( plugins ) { - - this.plugins = plugins; - - } - - parse( onLoad, onError ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - // Clear the loader cache - this.cache.removeAll(); - this.nodeCache = {}; - - // Mark the special nodes/meshes in json for efficient parse - this._invokeAll( function ( ext ) { - - return ext._markDefs && ext._markDefs(); - - } ); - - Promise.all( this._invokeAll( function ( ext ) { - - return ext.beforeRoot && ext.beforeRoot(); - - } ) ).then( function () { - - return Promise.all( [ - - parser.getDependencies( 'scene' ), - parser.getDependencies( 'animation' ), - parser.getDependencies( 'camera' ), - - ] ); - - } ).then( function ( dependencies ) { - - const 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 ); - - assignExtrasToUserData( result, json ); - - return Promise.all( parser._invokeAll( function ( ext ) { - - return ext.afterRoot && ext.afterRoot( result ); - - } ) ).then( function () { - - onLoad( result ); - - } ); - - } ).catch( onError ); - - } - - /** - * Marks the special nodes/meshes in json for efficient parse. - */ - _markDefs() { - - const nodeDefs = this.json.nodes || []; - const skinDefs = this.json.skins || []; - const meshDefs = this.json.meshes || []; - - // Nothing in the node definition indicates whether it is a Bone or an - // Object3D. Use the skins' joint references to mark bones. - for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { - - const joints = skinDefs[ skinIndex ].joints; - - for ( let i = 0, il = joints.length; i < il; i ++ ) { - - nodeDefs[ joints[ i ] ].isBone = true; - - } - - } - - // Iterate over all nodes, marking references to shared resources, - // as well as skeleton joints. - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.mesh !== undefined ) { - - this._addNodeRef( this.meshCache, 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; - - } - - } - - if ( nodeDef.camera !== undefined ) { - - this._addNodeRef( this.cameraCache, nodeDef.camera ); - - } - - } - - } - - /** - * Counts references to shared node / Object3D resources. These resources - * can be reused, or "instantiated", at multiple nodes in the scene - * hierarchy. Mesh, Camera, and Light instances are instantiated and must - * be marked. Non-scenegraph resources (like Materials, Geometries, and - * Textures) can be reused directly and are not marked here. - * - * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. - */ - _addNodeRef( cache, index ) { - - if ( index === undefined ) return; - - if ( cache.refs[ index ] === undefined ) { - - cache.refs[ index ] = cache.uses[ index ] = 0; - - } - - cache.refs[ index ] ++; - - } - - /** Returns a reference to a shared resource, cloning it if necessary. */ - _getNodeRef( cache, index, object ) { - - if ( cache.refs[ index ] <= 1 ) return object; - - const ref = object.clone(); - - // Propagates mappings to the cloned object, prevents mappings on the - // original object from being lost. - const updateMappings = ( original, clone ) => { - - const mappings = this.associations.get( original ); - if ( mappings != null ) { - - this.associations.set( clone, mappings ); - - } - - for ( const [ i, child ] of original.children.entries() ) { - - updateMappings( child, clone.children[ i ] ); - - } - - }; - - updateMappings( object, ref ); - - ref.name += '_instance_' + ( cache.uses[ index ] ++ ); - - return ref; - - } - - _invokeOne( func ) { - - const extensions = Object.values( this.plugins ); - extensions.push( this ); - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) return result; - - } - - return null; - - } - - _invokeAll( func ) { - - const extensions = Object.values( this.plugins ); - extensions.unshift( this ); - - const pending = []; - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) pending.push( result ); - - } - - return pending; - - } - - /** - * Requests the specified dependency asynchronously, with caching. - * @param {string} type - * @param {number} index - * @return {Promise} - */ - getDependency( type, index ) { - - const cacheKey = type + ':' + index; - let dependency = this.cache.get( cacheKey ); - - if ( ! dependency ) { - - switch ( type ) { - - case 'scene': - dependency = this.loadScene( index ); - break; - - case 'node': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadNode && ext.loadNode( index ); - - } ); - break; - - case 'mesh': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMesh && ext.loadMesh( index ); - - } ); - break; - - case 'accessor': - dependency = this.loadAccessor( index ); - break; - - case 'bufferView': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadBufferView && ext.loadBufferView( index ); - - } ); - break; - - case 'buffer': - dependency = this.loadBuffer( index ); - break; - - case 'material': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMaterial && ext.loadMaterial( index ); - - } ); - break; - - case 'texture': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadTexture && ext.loadTexture( index ); - - } ); - break; - - case 'skin': - dependency = this.loadSkin( index ); - break; - - case 'animation': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadAnimation && ext.loadAnimation( index ); - - } ); - break; - - case 'camera': - dependency = this.loadCamera( index ); - break; - - default: - dependency = this._invokeOne( function ( ext ) { - - return ext != this && ext.getDependency && ext.getDependency( type, index ); - - } ); - - if ( ! dependency ) { - - throw new Error( 'Unknown type: ' + type ); - - } - - break; - - } - - this.cache.add( cacheKey, dependency ); - - } - - return dependency; - - } - - /** - * Requests all dependencies of the specified type asynchronously, with caching. - * @param {string} type - * @return {Promise>} - */ - getDependencies( type ) { - - let dependencies = this.cache.get( type ); - - if ( ! dependencies ) { - - const parser = this; - const 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} - */ - loadBuffer( bufferIndex ) { - - const bufferDef = this.json.buffers[ bufferIndex ]; - const 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 ); - - } - - const options = this.options; - - return new Promise( function ( resolve, reject ) { - - loader.load( three.LoaderUtils.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} - */ - loadBufferView( bufferViewIndex ) { - - const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; - - return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - - const byteLength = bufferViewDef.byteLength || 0; - const 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} - */ - loadAccessor( accessorIndex ) { - - const parser = this; - const json = this.json; - - const accessorDef = this.json.accessors[ accessorIndex ]; - - if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - const normalized = accessorDef.normalized === true; - - const array = new TypedArray( accessorDef.count * itemSize ); - return Promise.resolve( new three.BufferAttribute( array, itemSize, normalized ) ); - - } - - const 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 ) { - - const bufferView = bufferViews[ 0 ]; - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - const elementBytes = TypedArray.BYTES_PER_ELEMENT; - const itemBytes = elementBytes * itemSize; - const byteOffset = accessorDef.byteOffset || 0; - const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; - const normalized = accessorDef.normalized === true; - let 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 - const ibSlice = Math.floor( byteOffset / byteStride ); - const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; - let 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 ) { - - const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; - - const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; - const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; - - const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); - const 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 = new three.BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); - - } - - for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { - - const 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} - */ - loadTexture( textureIndex ) { - - const json = this.json; - const options = this.options; - const textureDef = json.textures[ textureIndex ]; - const sourceIndex = textureDef.source; - const sourceDef = json.images[ sourceIndex ]; - - let loader = this.textureLoader; - - if ( sourceDef.uri ) { - - const handler = options.manager.getHandler( sourceDef.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.loadTextureImage( textureIndex, sourceIndex, loader ); - - } - - loadTextureImage( textureIndex, sourceIndex, loader ) { - - const parser = this; - const json = this.json; - - const textureDef = json.textures[ textureIndex ]; - const sourceDef = json.images[ sourceIndex ]; - - const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; - - if ( this.textureCache[ cacheKey ] ) { - - // See https://github.com/mrdoob/three.js/issues/21559. - return this.textureCache[ cacheKey ]; - - } - - const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { - - texture.flipY = false; - - texture.name = textureDef.name || sourceDef.name || ''; - - if ( texture.name === '' && typeof sourceDef.uri === 'string' && sourceDef.uri.startsWith( 'data:image/' ) === false ) { - - texture.name = sourceDef.uri; - - } - - const samplers = json.samplers || {}; - const 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; - - parser.associations.set( texture, { textures: textureIndex } ); - - return texture; - - } ).catch( function () { - - return null; - - } ); - - this.textureCache[ cacheKey ] = promise; - - return promise; - - } - - loadImageSource( sourceIndex, loader ) { - - const parser = this; - const json = this.json; - const options = this.options; - - if ( this.sourceCache[ sourceIndex ] !== undefined ) { - - return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); - - } - - const sourceDef = json.images[ sourceIndex ]; - - const URL = self.URL || self.webkitURL; - - let sourceURI = sourceDef.uri || ''; - let isObjectURL = false; - - if ( sourceDef.bufferView !== undefined ) { - - // Load binary image data from bufferView, if provided. - - sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { - - isObjectURL = true; - const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; - - } ); - - } else if ( sourceDef.uri === undefined ) { - - throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); - - } - - const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { - - return new Promise( function ( resolve, reject ) { - - let onLoad = resolve; - - if ( loader.isImageBitmapLoader === true ) { - - onLoad = function ( imageBitmap ) { - - const texture = new three.Texture( imageBitmap ); - texture.needsUpdate = true; - - resolve( texture ); - - }; - - } - - loader.load( three.LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); - - } ); - - } ).then( function ( texture ) { - - // Clean up resources and configure Texture. - - if ( isObjectURL === true ) { - - URL.revokeObjectURL( sourceURI ); - - } - - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); - - return texture; - - } ).catch( function ( error ) { - throw error; - - } ); - - this.sourceCache[ sourceIndex ] = promise; - return promise; - - } - - /** - * Asynchronously assigns a texture to the given material parameters. - * @param {Object} materialParams - * @param {string} mapName - * @param {Object} mapDef - * @return {Promise} - */ - assignTexture( materialParams, mapName, mapDef, colorSpace ) { - - const parser = this; - - return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { - - if ( ! texture ) return null; - - if ( mapDef.texCoord !== undefined && mapDef.texCoord > 0 ) { - - texture = texture.clone(); - texture.channel = mapDef.texCoord; - - } - - if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; - - if ( transform ) { - - const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); - parser.associations.set( texture, gltfReference ); - - } - - } - - if ( colorSpace !== undefined ) { - - texture.colorSpace = colorSpace; - - } - - materialParams[ mapName ] = texture; - - return 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 accommodate different primitive types, defines, etc. New materials will - * be created if necessary, and reused from a cache. - * @param {Object3D} mesh Mesh, Line, or Points instance. - */ - assignFinalMaterial( mesh ) { - - const geometry = mesh.geometry; - let material = mesh.material; - - const useDerivativeTangents = geometry.attributes.tangent === undefined; - const useVertexColors = geometry.attributes.color !== undefined; - const useFlatShading = geometry.attributes.normal === undefined; - - if ( mesh.isPoints ) { - - const cacheKey = 'PointsMaterial:' + material.uuid; - - let 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.sizeAttenuation = false; // glTF spec says points should be 1px - - this.cache.add( cacheKey, pointsMaterial ); - - } - - material = pointsMaterial; - - } else if ( mesh.isLine ) { - - const cacheKey = 'LineBasicMaterial:' + material.uuid; - - let lineMaterial = this.cache.get( cacheKey ); - - if ( ! lineMaterial ) { - - lineMaterial = new three.LineBasicMaterial(); - three.Material.prototype.copy.call( lineMaterial, material ); - lineMaterial.color.copy( material.color ); - lineMaterial.map = material.map; - - this.cache.add( cacheKey, lineMaterial ); - - } - - material = lineMaterial; - - } - - // Clone the material if it will be modified - if ( useDerivativeTangents || useVertexColors || useFlatShading ) { - - let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; - - if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; - if ( useVertexColors ) cacheKey += 'vertex-colors:'; - if ( useFlatShading ) cacheKey += 'flat-shading:'; - - let cachedMaterial = this.cache.get( cacheKey ); - - if ( ! cachedMaterial ) { - - cachedMaterial = material.clone(); - - if ( useVertexColors ) cachedMaterial.vertexColors = true; - if ( useFlatShading ) cachedMaterial.flatShading = true; - - if ( useDerivativeTangents ) { - - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; - if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; - - } - - this.cache.add( cacheKey, cachedMaterial ); - - this.associations.set( cachedMaterial, this.associations.get( material ) ); - - } - - material = cachedMaterial; - - } - - mesh.material = material; - - } - - getMaterialType( /* materialIndex */ ) { - - return three.MeshStandardMaterial; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials - * @param {number} materialIndex - * @return {Promise} - */ - loadMaterial( materialIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - const materialDef = json.materials[ materialIndex ]; - - let materialType; - const materialParams = {}; - const materialExtensions = materialDef.extensions || {}; - - const pending = []; - - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - - const 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 - - const metallicRoughness = materialDef.pbrMetallicRoughness || {}; - - materialParams.color = new three.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], three.LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, three.SRGBColorSpace ) ); - - } - - 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 ) ); - - } - - materialType = this._invokeOne( function ( ext ) { - - return ext.getMaterialType && ext.getMaterialType( materialIndex ); - - } ); - - pending.push( Promise.all( this._invokeAll( function ( ext ) { - - return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); - - } ) ) ); - - } - - if ( materialDef.doubleSided === true ) { - - materialParams.side = three.DoubleSide; - - } - - const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - - if ( alphaMode === ALPHA_MODES.BLEND ) { - - materialParams.transparent = true; - - // See: https://github.com/mrdoob/three.js/issues/17706 - materialParams.depthWrite = false; - - } 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 ) { - - const scale = materialDef.normalTexture.scale; - - materialParams.normalScale.set( scale, 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 ) { - - const emissiveFactor = materialDef.emissiveFactor; - materialParams.emissive = new three.Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], three.LinearSRGBColorSpace ); - - } - - if ( materialDef.emissiveTexture !== undefined && materialType !== three.MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, three.SRGBColorSpace ) ); - - } - - return Promise.all( pending ).then( function () { - - const material = new materialType( materialParams ); - - if ( materialDef.name ) material.name = materialDef.name; - - assignExtrasToUserData( material, materialDef ); - - parser.associations.set( material, { materials: materialIndex } ); - - if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); - - return material; - - } ); - - } - - /** When Object3D instances are targeted by animation, they need unique names. */ - createUniqueName( originalName ) { - - const sanitizedName = three.PropertyBinding.sanitizeNodeName( originalName || '' ); - - if ( sanitizedName in this.nodeNamesUsed ) { - - return sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] ); - - } else { - - this.nodeNamesUsed[ sanitizedName ] = 0; - - return sanitizedName; - - } - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry - * - * Creates BufferGeometries from primitives. - * - * @param {Array} primitives - * @return {Promise>} - */ - loadGeometries( primitives ) { - - const parser = this; - const extensions = this.extensions; - const cache = this.primitiveCache; - - function createDracoPrimitive( primitive ) { - - return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] - .decodePrimitive( primitive, parser ) - .then( function ( geometry ) { - - return addPrimitiveAttributes( geometry, primitive, parser ); - - } ); - - } - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey( primitive ); - - // See if we've already created this geometry - const cached = cache[ cacheKey ]; - - if ( cached ) { - - // Use the cached geometry if it exists - pending.push( cached.promise ); - - } else { - - let 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} - */ - loadMesh( meshIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - const meshDef = json.meshes[ meshIndex ]; - const primitives = meshDef.primitives; - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const material = primitives[ i ].material === undefined - ? createDefaultMaterial( this.cache ) - : this.getDependency( 'material', primitives[ i ].material ); - - pending.push( material ); - - } - - pending.push( parser.loadGeometries( primitives ) ); - - return Promise.all( pending ).then( function ( results ) { - - const materials = results.slice( 0, results.length - 1 ); - const geometries = results[ results.length - 1 ]; - - const meshes = []; - - for ( let i = 0, il = geometries.length; i < il; i ++ ) { - - const geometry = geometries[ i ]; - const primitive = primitives[ i ]; - - // 1. create Mesh - - let mesh; - - const material = materials[ 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 ) { - - // normalize skin weights to fix malformed assets (see #15319) - mesh.normalizeSkinWeights(); - - } - - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - - mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleStripDrawMode ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - - mesh.geometry = toTrianglesDrawMode( mesh.geometry, 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 = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - - assignExtrasToUserData( mesh, meshDef ); - - if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); - - parser.assignFinalMaterial( mesh ); - - meshes.push( mesh ); - - } - - for ( let i = 0, il = meshes.length; i < il; i ++ ) { - - parser.associations.set( meshes[ i ], { - meshes: meshIndex, - primitives: i - } ); - - } - - if ( meshes.length === 1 ) { - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); - - return meshes[ 0 ]; - - } - - const group = new three.Group(); - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); - - parser.associations.set( group, { meshes: meshIndex } ); - - for ( let 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} - */ - loadCamera( cameraIndex ) { - - let camera; - const cameraDef = this.json.cameras[ cameraIndex ]; - const params = cameraDef[ cameraDef.type ]; - - if ( ! params ) { - return; - - } - - if ( cameraDef.type === 'perspective' ) { - - camera = new three.PerspectiveCamera( three.MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); - - } else if ( cameraDef.type === 'orthographic' ) { - - camera = new three.OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); - - } - - if ( cameraDef.name ) camera.name = this.createUniqueName( 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} - */ - loadSkin( skinIndex ) { - - const skinDef = this.json.skins[ skinIndex ]; - - const pending = []; - - for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) { - - pending.push( this._loadNodeShallow( skinDef.joints[ i ] ) ); - - } - - if ( skinDef.inverseBindMatrices !== undefined ) { - - pending.push( this.getDependency( 'accessor', skinDef.inverseBindMatrices ) ); - - } else { - - pending.push( null ); - - } - - return Promise.all( pending ).then( function ( results ) { - - const inverseBindMatrices = results.pop(); - const jointNodes = results; - - // Note that bones (joint nodes) may or may not be in the - // scene graph at this time. - - const bones = []; - const boneInverses = []; - - for ( let i = 0, il = jointNodes.length; i < il; i ++ ) { - - const jointNode = jointNodes[ i ]; - - if ( jointNode ) { - - bones.push( jointNode ); - - const mat = new three.Matrix4(); - - if ( inverseBindMatrices !== null ) { - - mat.fromArray( inverseBindMatrices.array, i * 16 ); - - } - - boneInverses.push( mat ); - - } - - } - - return new three.Skeleton( bones, boneInverses ); - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations - * @param {number} animationIndex - * @return {Promise} - */ - loadAnimation( animationIndex ) { - - const json = this.json; - const parser = this; - - const animationDef = json.animations[ animationIndex ]; - const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex; - - const pendingNodes = []; - const pendingInputAccessors = []; - const pendingOutputAccessors = []; - const pendingSamplers = []; - const pendingTargets = []; - - for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { - - const channel = animationDef.channels[ i ]; - const sampler = animationDef.samplers[ channel.sampler ]; - const target = channel.target; - const name = target.node; - const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; - const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; - - if ( target.node === undefined ) continue; - - 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 ) { - - const nodes = dependencies[ 0 ]; - const inputAccessors = dependencies[ 1 ]; - const outputAccessors = dependencies[ 2 ]; - const samplers = dependencies[ 3 ]; - const targets = dependencies[ 4 ]; - - const tracks = []; - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - const node = nodes[ i ]; - const inputAccessor = inputAccessors[ i ]; - const outputAccessor = outputAccessors[ i ]; - const sampler = samplers[ i ]; - const target = targets[ i ]; - - if ( node === undefined ) continue; - - if ( node.updateMatrix ) { - - node.updateMatrix(); - - } - - const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ); - - if ( createdTracks ) { - - for ( let k = 0; k < createdTracks.length; k ++ ) { - - tracks.push( createdTracks[ k ] ); - - } - - } - - } - - return new three.AnimationClip( animationName, undefined, tracks ); - - } ); - - } - - createNodeMesh( nodeIndex ) { - - const json = this.json; - const parser = this; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( nodeDef.mesh === undefined ) return null; - - return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { - - const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, 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 ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { - - o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; - - } - - } ); - - } - - return node; - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy - * @param {number} nodeIndex - * @return {Promise} - */ - loadNode( nodeIndex ) { - - const json = this.json; - const parser = this; - - const nodeDef = json.nodes[ nodeIndex ]; - - const nodePending = parser._loadNodeShallow( nodeIndex ); - - const childPending = []; - const childrenDef = nodeDef.children || []; - - for ( let i = 0, il = childrenDef.length; i < il; i ++ ) { - - childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) ); - - } - - const skeletonPending = nodeDef.skin === undefined - ? Promise.resolve( null ) - : parser.getDependency( 'skin', nodeDef.skin ); - - return Promise.all( [ - nodePending, - Promise.all( childPending ), - skeletonPending - ] ).then( function ( results ) { - - const node = results[ 0 ]; - const children = results[ 1 ]; - const skeleton = results[ 2 ]; - - if ( skeleton !== null ) { - - // This full traverse should be fine because - // child glTF nodes have not been added to this node yet. - node.traverse( function ( mesh ) { - - if ( ! mesh.isSkinnedMesh ) return; - - mesh.bind( skeleton, _identityMatrix ); - - } ); - - } - - for ( let i = 0, il = children.length; i < il; i ++ ) { - - node.add( children[ i ] ); - - } - - return node; - - } ); - - } - - // ._loadNodeShallow() parses a single node. - // skin and child nodes are created and added in .loadNode() (no '_' prefix). - _loadNodeShallow( nodeIndex ) { - - const json = this.json; - const extensions = this.extensions; - const parser = this; - - // This method is called from .loadNode() and .loadSkin(). - // Cache a node to avoid duplication. - - if ( this.nodeCache[ nodeIndex ] !== undefined ) { - - return this.nodeCache[ nodeIndex ]; - - } - - const nodeDef = json.nodes[ nodeIndex ]; - - // reserve node's name before its dependencies, so the root has the intended name. - const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; - - const pending = []; - - const meshPromise = parser._invokeOne( function ( ext ) { - - return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); - - } ); - - if ( meshPromise ) { - - pending.push( meshPromise ); - - } - - if ( nodeDef.camera !== undefined ) { - - pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { - - return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); - - } ) ); - - } - - parser._invokeAll( function ( ext ) { - - return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); - - } ).forEach( function ( promise ) { - - pending.push( promise ); - - } ); - - this.nodeCache[ nodeIndex ] = Promise.all( pending ).then( function ( objects ) { - - let node; - - // .isBone isn't in glTF spec. See ._markDefs - if ( nodeDef.isBone === true ) { - - node = new three.Bone(); - - } else if ( objects.length > 1 ) { - - node = new three.Group(); - - } else if ( objects.length === 1 ) { - - node = objects[ 0 ]; - - } else { - - node = new three.Object3D(); - - } - - if ( node !== objects[ 0 ] ) { - - for ( let i = 0, il = objects.length; i < il; i ++ ) { - - node.add( objects[ i ] ); - - } - - } - - if ( nodeDef.name ) { - - node.userData.name = nodeDef.name; - node.name = nodeName; - - } - - assignExtrasToUserData( node, nodeDef ); - - if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); - - if ( nodeDef.matrix !== undefined ) { - - const matrix = new three.Matrix4(); - matrix.fromArray( nodeDef.matrix ); - node.applyMatrix4( 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 ); - - } - - } - - if ( ! parser.associations.has( node ) ) { - - parser.associations.set( node, {} ); - - } - - parser.associations.get( node ).nodes = nodeIndex; - - return node; - - } ); - - return this.nodeCache[ nodeIndex ]; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes - * @param {number} sceneIndex - * @return {Promise} - */ - loadScene( sceneIndex ) { - - const extensions = this.extensions; - const sceneDef = this.json.scenes[ sceneIndex ]; - const parser = this; - - // Loader returns Group, not Scene. - // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 - const scene = new three.Group(); - if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - - assignExtrasToUserData( scene, sceneDef ); - - if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); - - const nodeIds = sceneDef.nodes || []; - - const pending = []; - - for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - - pending.push( parser.getDependency( 'node', nodeIds[ i ] ) ); - - } - - return Promise.all( pending ).then( function ( nodes ) { - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - scene.add( nodes[ i ] ); - - } - - // Removes dangling associations, associations that reference a node that - // didn't make it into the scene. - const reduceAssociations = ( node ) => { - - const reducedAssociations = new Map(); - - for ( const [ key, value ] of parser.associations ) { - - if ( key instanceof three.Material || key instanceof three.Texture ) { - - reducedAssociations.set( key, value ); - - } - - } - - node.traverse( ( node ) => { - - const mappings = parser.associations.get( node ); - - if ( mappings != null ) { - - reducedAssociations.set( node, mappings ); - - } - - } ); - - return reducedAssociations; - - }; - - parser.associations = reduceAssociations( scene ); - - return scene; - - } ); - - } - - _createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) { - - const tracks = []; - - const targetName = node.name ? node.name : node.uuid; - const targetNames = []; - - if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { - - node.traverse( function ( object ) { - - if ( object.morphTargetInfluences ) { - - targetNames.push( object.name ? object.name : object.uuid ); - - } - - } ); - - } else { - - targetNames.push( targetName ); - - } - - let 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: - - TypedKeyframeTrack = three.VectorKeyframeTrack; - break; - - default: - - switch ( outputAccessor.itemSize ) { - - case 1: - TypedKeyframeTrack = three.NumberKeyframeTrack; - break; - case 2: - case 3: - default: - TypedKeyframeTrack = three.VectorKeyframeTrack; - break; - - } - - break; - - } - - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : three.InterpolateLinear; - - - const outputArray = this._getArrayFromAccessor( outputAccessor ); - - for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { - - const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], - inputAccessor.array, - outputArray, - interpolation - ); - - // Override interpolation with custom factory method. - if ( sampler.interpolation === 'CUBICSPLINE' ) { - - this._createCubicSplineTrackInterpolant( track ); - - } - - tracks.push( track ); - - } - - return tracks; - - } - - _getArrayFromAccessor( accessor ) { - - let outputArray = accessor.array; - - if ( accessor.normalized ) { - - const scale = getNormalizedComponentScale( outputArray.constructor ); - const scaled = new Float32Array( outputArray.length ); - - for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { - - scaled[ j ] = outputArray[ j ] * scale; - - } - - outputArray = scaled; - - } - - return outputArray; - - } - - _createCubicSplineTrackInterpolant( track ) { - - 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. - - const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; - - return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); - - }; - - // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. - track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; - - } - - } - - /** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - */ - function computeBounds( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const box = new three.Box3(); - - if ( attributes.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ attributes.POSITION ]; - - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - box.set( - new three.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), - new three.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) - ); - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - box.min.multiplyScalar( boxScale ); - box.max.multiplyScalar( boxScale ); - - } - - } else { - - return; - - } - - } else { - - return; - - } - - const targets = primitiveDef.targets; - - if ( targets !== undefined ) { - - const maxDisplacement = new three.Vector3(); - const vector = new three.Vector3(); - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ target.POSITION ]; - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - // we need to get max of absolute components because target weight is [-1,1] - vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); - vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); - vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); - - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - vector.multiplyScalar( boxScale ); - - } - - // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative - // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets - // are used to implement key-frame animations and as such only two are active at a time - this results in very large - // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. - maxDisplacement.max( vector ); - - } - - } - - } - - // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. - box.expandByVector( maxDisplacement ); - - } - - geometry.boundingBox = box; - - const sphere = new three.Sphere(); - - box.getCenter( sphere.center ); - sphere.radius = box.min.distanceTo( box.max ) / 2; - - geometry.boundingSphere = sphere; - - } - - /** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - * @return {Promise} - */ - function addPrimitiveAttributes( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const pending = []; - - function assignAttributeAccessor( accessorIndex, attributeName ) { - - return parser.getDependency( 'accessor', accessorIndex ) - .then( function ( accessor ) { - - geometry.setAttribute( attributeName, accessor ); - - } ); - - } - - for ( const gltfAttributeName in attributes ) { - - const 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 ) { - - const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { - - geometry.setIndex( accessor ); - - } ); - - pending.push( accessor ); - - } - - if ( three.ColorManagement.workingColorSpace !== three.LinearSRGBColorSpace && 'COLOR_0' in attributes ) ; - - assignExtrasToUserData( geometry, primitiveDef ); - - computeBounds( geometry, primitiveDef, parser ); - - return Promise.all( pending ).then( function () { - - return primitiveDef.targets !== undefined - ? addMorphTargets( geometry, primitiveDef.targets, parser ) - : geometry; - - } ); - - } - - const _taskCache$1 = new WeakMap(); - - class DRACOLoader extends three.Loader { - - constructor( manager ) { - - super( manager ); - - this.decoderPath = ''; - this.decoderConfig = {}; - this.decoderBinary = null; - this.decoderPending = null; - - this.workerLimit = 4; - this.workerPool = []; - this.workerNextTaskID = 1; - this.workerSourceURL = ''; - - this.defaultAttributeIDs = { - position: 'POSITION', - normal: 'NORMAL', - color: 'COLOR', - uv: 'TEX_COORD' - }; - this.defaultAttributeTypes = { - position: 'Float32Array', - normal: 'Float32Array', - color: 'Float32Array', - uv: 'Float32Array' - }; - - } - - setDecoderPath( path ) { - - this.decoderPath = path; - - return this; - - } - - setDecoderConfig( config ) { - - this.decoderConfig = config; - - return this; - - } - - setWorkerLimit( workerLimit ) { - - this.workerLimit = workerLimit; - - return this; - - } - - load( url, onLoad, onProgress, onError ) { - - const loader = new three.FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - loader.load( url, ( buffer ) => { - - this.parse( buffer, onLoad, onError ); - - }, onProgress, onError ); - - } - - - parse( buffer, onLoad, onError = ()=>{} ) { - - this.decodeDracoFile( buffer, onLoad, null, null, three.SRGBColorSpace ).catch( onError ); - - } - - decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = three.LinearSRGBColorSpace, onError = () => {} ) { - - const taskConfig = { - attributeIDs: attributeIDs || this.defaultAttributeIDs, - attributeTypes: attributeTypes || this.defaultAttributeTypes, - useUniqueIDs: !! attributeIDs, - vertexColorSpace: vertexColorSpace, - }; - - return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError ); - - } - - decodeGeometry( buffer, taskConfig ) { - - const taskKey = JSON.stringify( taskConfig ); - - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache$1.has( buffer ) ) { - - const cachedTask = _taskCache$1.get( buffer ); - - if ( cachedTask.key === taskKey ) { - - return cachedTask.promise; - - } else if ( buffer.byteLength === 0 ) { - - // Technically, it would be possible to wait for the previous task to complete, - // transfer the buffer back, and decode again with the second configuration. That - // is complex, and I don't know of any reason to decode a Draco buffer twice in - // different ways, so this is left unimplemented. - throw new Error( - - 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + - 'settings. Buffer has already been transferred.' - - ); - - } - - } - - // - - let worker; - const taskID = this.workerNextTaskID ++; - const taskCost = buffer.byteLength; - - // Obtain a worker and assign a task, and construct a geometry instance - // when the task completes. - const geometryPending = this._getWorker( taskID, taskCost ) - .then( ( _worker ) => { - - worker = _worker; - - return new Promise( ( resolve, reject ) => { - - worker._callbacks[ taskID ] = { resolve, reject }; - - worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); - - // this.debug(); - - } ); - - } ) - .then( ( message ) => this._createGeometry( message.geometry ) ); - - // Remove task from the task list. - // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) - geometryPending - .catch( () => true ) - .then( () => { - - if ( worker && taskID ) { - - this._releaseTask( worker, taskID ); - - // this.debug(); - - } - - } ); - - // Cache the task result. - _taskCache$1.set( buffer, { - - key: taskKey, - promise: geometryPending - - } ); - - return geometryPending; - - } - - _createGeometry( geometryData ) { - - const geometry = new three.BufferGeometry(); - - if ( geometryData.index ) { - - geometry.setIndex( new three.BufferAttribute( geometryData.index.array, 1 ) ); - - } - - for ( let i = 0; i < geometryData.attributes.length; i ++ ) { - - const result = geometryData.attributes[ i ]; - const name = result.name; - const array = result.array; - const itemSize = result.itemSize; - - const attribute = new three.BufferAttribute( array, itemSize ); - - if ( name === 'color' ) { - - this._assignVertexColorSpace( attribute, result.vertexColorSpace ); - - attribute.normalized = ( array instanceof Float32Array ) === false; - - } - - geometry.setAttribute( name, attribute ); - - } - - return geometry; - - } - - _assignVertexColorSpace( attribute, inputColorSpace ) { - - // While .drc files do not specify colorspace, the only 'official' tooling - // is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc - // file is passed into .load() or .parse(). GLTFLoader uses internal APIs - // to decode geometry, and vertex colors are already Linear-sRGB in there. - - if ( inputColorSpace !== three.SRGBColorSpace ) return; - - const _color = new three.Color(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); - attribute.setXYZ( i, _color.r, _color.g, _color.b ); - - } - - } - - _loadLibrary( url, responseType ) { - - const loader = new three.FileLoader( this.manager ); - loader.setPath( this.decoderPath ); - loader.setResponseType( responseType ); - loader.setWithCredentials( this.withCredentials ); - - return new Promise( ( resolve, reject ) => { - - loader.load( url, resolve, undefined, reject ); - - } ); - - } - - preload() { - - this._initDecoder(); - - return this; - - } - - _initDecoder() { - - if ( this.decoderPending ) return this.decoderPending; - - const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; - const librariesPending = []; - - if ( useJS ) { - - librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); - - } else { - - librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); - librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); - - } - - this.decoderPending = Promise.all( librariesPending ) - .then( ( libraries ) => { - - const jsContent = libraries[ 0 ]; - - if ( ! useJS ) { - - this.decoderConfig.wasmBinary = libraries[ 1 ]; - - } - - const fn = DRACOWorker.toString(); - - const body = [ - '/* draco decoder */', - jsContent, - '', - '/* worker */', - fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) - ].join( '\n' ); - - this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); - - } ); - - return this.decoderPending; - - } - - _getWorker( taskID, taskCost ) { - - return this._initDecoder().then( () => { - - if ( this.workerPool.length < this.workerLimit ) { - - const worker = new Worker( this.workerSourceURL ); - - worker._callbacks = {}; - worker._taskCosts = {}; - worker._taskLoad = 0; - - worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); - - worker.onmessage = function ( e ) { - - const message = e.data; - - switch ( message.type ) { - - case 'decode': - worker._callbacks[ message.id ].resolve( message ); - break; - - case 'error': - worker._callbacks[ message.id ].reject( message ); - break; - - } - - }; - - this.workerPool.push( worker ); - - } else { - - this.workerPool.sort( function ( a, b ) { - - return a._taskLoad > b._taskLoad ? - 1 : 1; - - } ); - - } - - const worker = this.workerPool[ this.workerPool.length - 1 ]; - worker._taskCosts[ taskID ] = taskCost; - worker._taskLoad += taskCost; - return worker; - - } ); - - } - - _releaseTask( worker, taskID ) { - - worker._taskLoad -= worker._taskCosts[ taskID ]; - delete worker._callbacks[ taskID ]; - delete worker._taskCosts[ taskID ]; - - } - - debug() { - - } - - dispose() { - - for ( let i = 0; i < this.workerPool.length; ++ i ) { - - this.workerPool[ i ].terminate(); - - } - - this.workerPool.length = 0; - - if ( this.workerSourceURL !== '' ) { - - URL.revokeObjectURL( this.workerSourceURL ); - - } - - return this; - - } - - } - - /* WEB WORKER */ - - function DRACOWorker() { - - let decoderConfig; - let decoderPending; + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - onmessage = function ( e ) { + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; - const message = e.data; + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - switch ( message.type ) { + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - case 'init': - decoderConfig = message.decoderConfig; - decoderPending = new Promise( function ( resolve/*, reject*/ ) { + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; - decoderConfig.onModuleLoaded = function ( draco ) { + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } - // Module is Promise-like. Wrap before resolving to avoid loop. - resolve( { draco: draco } ); + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } - }; + void main() { - DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - } ); - break; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - case 'decode': - const buffer = message.buffer; - const taskConfig = message.taskConfig; - decoderPending.then( ( module ) => { + vSunDirection = normalize( sunPosition ); - const draco = module.draco; - const decoder = new draco.Decoder(); + vSunE = sunIntensity( dot( vSunDirection, up ) ); - try { + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - const geometry = decodeGeometry( draco, decoder, new Int8Array( buffer ), taskConfig ); + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + }`, - } catch ( error ) { + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + uniform float mieDirectionalG; + uniform vec3 up; - } finally { + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; - draco.destroy( decoder ); + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - } + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - } ); - break; + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; - } + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); + } - }; + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - function decodeGeometry( draco, decoder, array, taskConfig ) { + void main() { - const attributeIDs = taskConfig.attributeIDs; - const attributeTypes = taskConfig.attributeTypes; + vec3 direction = normalize( vWorldPosition - cameraPosition ); - let dracoGeometry; - let decodingStatus; + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - const geometryType = decoder.GetEncodedGeometryType( array ); + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - if ( geometryType === draco.TRIANGULAR_MESH ) { + // in scattering + float cosTheta = dot( direction, vSunDirection ); - dracoGeometry = new draco.Mesh(); - decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry ); + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - } else if ( geometryType === draco.POINT_CLOUD ) { + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - dracoGeometry = new draco.PointCloud(); - decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, dracoGeometry ); + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - } else { + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - } + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + gl_FragColor = vec4( retColor, 1.0 ); - } + #include + #include - const geometry = { index: null, attributes: [] }; + }` - // Gather all vertex attributes. - for ( const attributeName in attributeIDs ) { + }; - const attributeType = self[ attributeTypes[ attributeName ] ]; + // 多个canvas并没有id,只有父节点 - let attribute; - let attributeID; - // A Draco file may be created with default vertex attributes, whose attribute IDs - // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, - // a Draco file may contain a custom set of attributes, identified by known unique - // IDs. glTF files always do the latter, and `.drc` files typically do the former. - if ( taskConfig.useUniqueIDs ) { + class Layer extends BasLayer{ + id; // 唯一标识 + layerContainer; // div#layer 容器 + zIndex=1;//默认为1 + opacity=1;//默认为1 + canvas;//canvas + dispose = false; + renderer;//canvas上下文 + scene;//场景 + visible=true;//是否可见 + mapView;//地图视图 + camera;//相机 + controls;//控件 + animateId;//动画事件id + base = false; // 是否为底图 + ambientLight; // 环境光 + directionalLight; // 方向光 + modelLayer = false; // 模型图层 + imageLayer = false; // 影像图层 + vectorLayer = false; // 矢量图层,如路网、行政区划,地名等图层 + waters = []; // 水面集合 + constructor(id, layerContainer, canvas, mapView, plane = true, camera = new three.PerspectiveCamera(80, 1, 0.1, 1e12)) { + super(); + this.id = id; + this.layerContainer = layerContainer; + this.canvas = canvas; + this.renderer = new three.WebGLRenderer({ + canvas: this.canvas, + antialias: true, + alpha: true, + logarithmicDepthBuffer: true, + precision: "highp", + }); + this.renderer.sortObjects = true; + this.renderer.setPixelRatio(window.devicePixelRatio); + this.renderer.setClearColor(0xFFFFFF, 0.0); + this.scene = new three.Scene(); + this.mapView = mapView; + this.camera = camera; + if(this.mapView){ + this.scene.add(this.mapView); + this.mapView.updateMatrixWorld(true); + } + if (plane){ + this.controls = new MapControls(this.camera, this.canvas); + this.controls.minDistance = 1e1; + this.controls.zoomSpeed = 2.0; + } else { + this.controls = new OrbitControls(this.camera, this.canvas); + this.controls.enablePan = false; + this.controls.minDistance = UnitsUtils.EARTH_RADIUS + 2; + this.controls.maxDistance = UnitsUtils.EARTH_RADIUS * 1e1; + } + this._raycaster = new three.Raycaster(); + if(Config.outLine.on){ + this.effectOutline = new EffectOutline(this.renderer, this.scene, this.camera, this.canvas.width, this.canvas.height); + } + if (Config.layer.map.ambientLight.add){ + this.scene.add(new three.AmbientLight(Config.layer.map.ambientLight.color, Config.layer.map.ambientLight.intensity)); + } + if (Config.layer.map.directionalLight.add){ + this.scene.add(new three.DirectionalLight(Config.layer.map.directionalLight.color, Config.layer.map.directionalLight.intensity)); + } + if (Config.layer.map.pointLight.add){ + let pointLight = new three.PointLight(Config.layer.map.pointLight.color, Config.layer.map.pointLight.intensity, Config.layer.map.pointLight.distance); + pointLight.position.set(...Config.layer.map.pointLight.position); + this.scene.add(pointLight); + } + } - attributeID = attributeIDs[ attributeName ]; - attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + moveTo(lat, lon, height = 38472.48763833733){ + // var coords = UnitsUtils.datumsToSpherical(44.266119,90.139228); + var coords = UnitsUtils.datumsToSpherical(lat,lon); + this.camera.position.set(coords.x, height, -coords.y); + this.controls.target.set(this.camera.position.x, 0, this.camera.position.z); + } - } else { + moveToByCoords(coords){ + let offset = 50; + this.camera.position.set(coords.x, coords.y+offset, coords.z); + this.controls.target.set(coords.x, coords.y, coords.z); + } - attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + moveToByLL(lat, lon, distance = 384720){ + let dir = UnitsUtils.datumsToVector(lat, lon); + dir.multiplyScalar(UnitsUtils.EARTH_RADIUS + distance); + this.camera.position.copy(dir); + } - if ( attributeID === - 1 ) continue; - attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + on(eventName, callback){ + this.listener.on(eventName, callback); + } - } + setSceneBackground(color) { + this.scene.background = color; + } - const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ); + clearSceneBackground() { + this.scene.background = null; + } - if ( attributeName === 'color' ) { + // 可用于添加灯光mesh等元素 + add(Object3D) { + if(Object3D ==null || Object3D ==undefined){ + return; + } + this.scene.add(Object3D); + } - attributeResult.vertexColorSpace = taskConfig.vertexColorSpace; - } + remove(Object3D) { + if(Object3D ==null || Object3D ==undefined){ + return; + } + this.scene.remove(Object3D); + } - geometry.attributes.push( attributeResult ); + openWaterConfig(){ + /** + * 打开渲染水系配置 + */ + // this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.toneMapping = three.ACESFilmicToneMapping; + this.renderer.toneMappingExposure = 0.5; + + // 添加天空 + this.sky = new Sky(); + this.sky.translateX = true; + this.sky.translateY = true; + this.sky.translateZ = true; + this.sky.rotateX = Math.PI / 2; + this.sky.scale.setScalar( Config.EARTH_RADIUS * 2 * Math.PI ); // 天空放大倍数 + this.scene.add( this.sky ); + const skyUniforms = this.sky.material.uniforms; + // 天空的配置 + skyUniforms[ 'turbidity' ].value = 10; + skyUniforms[ 'rayleigh' ].value = 2; + skyUniforms[ 'mieCoefficient' ].value = 0.005; + skyUniforms[ 'mieDirectionalG' ].value = 0.8; + // 旋转 设置为y朝上 + // sky.material.uniforms["up"].value = new THREE.Vector3(0, 1, 0); + + // 天空映射, 更新太阳位置 + this.pmremGenerator = new three.PMREMGenerator( this.renderer ); + this.sceneEnv = new three.Scene(); + this.renderTarget = null; + this.sun = new three.Vector3(); + this.updateSun(Config.SUNDEGREE, Config.SUNAZIMUTH); + } - } + updateSun(elevation, azimuth) { - // Add index. - if ( geometryType === draco.TRIANGULAR_MESH ) { + const phi = three.MathUtils.degToRad( 90 - elevation ); + const theta = three.MathUtils.degToRad( azimuth ); - geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + this.sun.setFromSphericalCoords( 1, phi, theta ); - } + this.sky.material.uniforms[ 'sunPosition' ].value.copy( this.sun ); + for (let water of this.waters){ + water.material.uniforms[ 'sunDirection' ].value.copy( this.sun ).normalize(); + } + if ( this.renderTarget !== null ) this.renderTarget.dispose(); - draco.destroy( dracoGeometry ); + this.sceneEnv.add( this.sky ); + this.renderTarget = this.pmremGenerator.fromScene( this.sceneEnv ); + this.scene.add( this.sky ); - return geometry; + this.scene.environment = this.renderTarget.texture; - } - function decodeIndex( draco, decoder, dracoGeometry ) { + } - const numFaces = dracoGeometry.num_faces(); - const numIndices = numFaces * 3; - const byteLength = numIndices * 4; + /** + * 添加水系 + * @param {*} water + * @returns + */ + addWater(water) { + if(water ==null || water ==undefined){ + return; + } + this.scene.add(water); + this.waters.push(water); + } - const ptr = draco._malloc( byteLength ); - decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); - const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); - draco._free( ptr ); + removeWater(water) { + if(water ==null || water ==undefined){ + return; + } + this.scene.remove(water); + let index = this.waters.indexOf(water); + if (index > -1) { + this.waters.splice(index, 1); + } + } - return { array: index, itemSize: 1 }; + setVisible(visible) { + this.visible = this.visible; + this.layerContainer.style.display = visible ? 'block' : 'none'; + } + /** + * @deprecated 不建议用 + * @param {number} opacity + */ + setOpacity(opacity) { + this.opacity = opacity; + this.layerContainer.style.opacity = opacity; + } - } + /** + * 修改显示层级,默认越靠下的dom元素,显示上越靠上 + * @param {number} zIndex + */ + setZIndex(zIndex) { + this.zIndex = zIndex; + this.layerContainer.style.zIndex = this.zIndex; + } - function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + dispose() { + Element.removeLayer(id); + this.dispose = true; + this.animateId && cancelAnimationFrame(this.animateId); + this.mapView.root.dispose(); + this.mapView.dispose(); + } - const numComponents = attribute.num_components(); - const numPoints = dracoGeometry.num_points(); - const numValues = numPoints * numComponents; - const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; - const dataType = getDracoDataType( draco, attributeType ); + selectModel(insect){ + this.effectOutline.selectModel(insect); + } - const ptr = draco._malloc( byteLength ); - decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); - const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); - draco._free( ptr ); + resize(){ + var width = window.innerWidth; + var height = window.innerHeight; + this.renderer.setSize(width, height); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + if(Config.outLine.on){ + this.effectOutline.resize(width, height); + } + } - return { - name: attributeName, - array: array, - itemSize: numComponents - }; + animate(){ + this.animateId = requestAnimationFrame(this.animate.bind(this)); + if(this.base){ + this.controls.update(); + } + if (this.base){ + update(); //目前只有在基础地图中添加相机移动的动画,所以暂且只在base地图中进行渲染,后期可改成每个图层都进行渲染,或者必要时进行渲染。 + } + for(let water of this.waters){ + water.material.uniforms[ 'time' ].value += 1.0 / 60.0; + } + if (Config.outLine.on){ + this.effectOutline.render(); // 合成器渲染 + } else { + this.renderer.autoClear = true; + } + this.renderer.render(this.scene, this.camera); + } - } + _raycast(meshes, recursive, faceExclude) { + const isects = this._raycaster.intersectObjects(meshes, recursive); + if (faceExclude) { + for (let i = 0; i < isects.length; i++) { + if (isects[i].face !== faceExclude) { + return isects[i]; + } + } + return null; + } + return isects.length > 0 ? isects[0] : null; + } - function getDracoDataType( draco, attributeType ) { + _raycastFromMouse(mx, my, width, height, cam, meshes, recursive=false) { + const mouse = new three.Vector2( // normalized (-1 to +1) + (mx / width) * 2 - 1, + - (my / height) * 2 + 1); + // https://threejs.org/docs/#api/core/Raycaster + // update the picking ray with the camera and mouse position + this._raycaster.setFromCamera(mouse, cam); + return this._raycast(meshes, recursive, null); + } - switch ( attributeType ) { + /** + * + * @param {*} mx 屏幕坐标x + * @param {*} my 屏幕坐标y + * @param {boolean} recursive 是否检查子节点,true 递归检查 + * @returns mesh + */ + raycastFromMouse(mx, my, recursive=false) { + //---- NG: 2x when starting with Chrome's inspector mobile + // const {width, height} = this.renderer.domElement; + // const {width, height} = this.canvas; + //---- OK + const {clientWidth, clientHeight} = this.canvas; - case Float32Array: return draco.DT_FLOAT32; - case Int8Array: return draco.DT_INT8; - case Int16Array: return draco.DT_INT16; - case Int32Array: return draco.DT_INT32; - case Uint8Array: return draco.DT_UINT8; - case Uint16Array: return draco.DT_UINT16; - case Uint32Array: return draco.DT_UINT32; + return this._raycastFromMouse( + mx, my, clientWidth, clientHeight, this.camera, + this.mapView.children, recursive); + } - } + insectALL(mx, my, recursive=false) { + const {clientWidth, clientHeight} = this.canvas; - } + return this._raycastFromMouse( + mx, my, clientWidth, clientHeight, this.camera, + this.scene.children, recursive); + } } @@ -41529,11 +36295,17 @@ Char: ${this.c}`; this.baseMap.moveToByCoords(coords); } - moveToByLL(lat, lon){ + /** + * 跳转到指定位置,用于球形地图 + * @param {*} lat + * @param {*} lon + * @returns + */ + moveToByLL(lat, lon, distance = 384720){ if(!this.baseMap){ return; } - this.baseMap.moveToByLL(lat, lon); + this.baseMap.moveToByLL(lat, lon, distance); } // 鼠标点击获取模型 @@ -41897,12 +36669,12 @@ Char: ${this.c}`; loadSkyBox(scale) { var aCubeMap = new three.CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); aCubeMap.format = three.RGBAFormat; @@ -41925,17 +36697,18 @@ Char: ${this.c}`; } loadBox(){ var cube = new three.CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); return cube; } } + exports.AngleUtils = AngleUtils; exports.Animate = Animate; exports.BingMapsProvider = BingMapsProvider; exports.CancelablePromise = CancelablePromise; diff --git a/build/wegeo.module.js b/build/wegeo.module.js index 364bef3..136bd6a 100644 --- a/build/wegeo.module.js +++ b/build/wegeo.module.js @@ -5007,6 +5007,26 @@ class Element { } } +class AngleUtils { + /** + * 弧度转角度 + * @param {*} rad + * @returns + */ + static radToDeg(rad) { + return rad * (180 / Math.PI); + } + /** + * 角度转弧度 + * @param {*} deg + * @returns + */ + static degToRad(deg) { + return deg * (Math.PI / 180); + } + +} + // OrbitControls performs orbiting, dollying (zooming), and panning. // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). // @@ -9171,7 +9191,7 @@ class EffectOutline { * @param {number} drawMode * @return {BufferGeometry} */ -function toTrianglesDrawMode$1( geometry, drawMode ) { +function toTrianglesDrawMode( geometry, drawMode ) { if ( drawMode === TrianglesDrawMode ) { return geometry; @@ -9266,7 +9286,7 @@ function toTrianglesDrawMode$1( geometry, drawMode ) { } -let GLTFLoader$1 = class GLTFLoader extends Loader { +class GLTFLoader extends Loader { constructor( manager ) { @@ -9280,97 +9300,97 @@ let GLTFLoader$1 = class GLTFLoader extends Loader { this.register( function ( parser ) { - return new GLTFMaterialsClearcoatExtension$1( parser ); + return new GLTFMaterialsClearcoatExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureBasisUExtension$1( parser ); + return new GLTFTextureBasisUExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureWebPExtension$1( parser ); + return new GLTFTextureWebPExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFTextureAVIFExtension$1( parser ); + return new GLTFTextureAVIFExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsSheenExtension$1( parser ); + return new GLTFMaterialsSheenExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsTransmissionExtension$1( parser ); + return new GLTFMaterialsTransmissionExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsVolumeExtension$1( parser ); + return new GLTFMaterialsVolumeExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsIorExtension$1( parser ); + return new GLTFMaterialsIorExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsEmissiveStrengthExtension$1( parser ); + return new GLTFMaterialsEmissiveStrengthExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsSpecularExtension$1( parser ); + return new GLTFMaterialsSpecularExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsIridescenceExtension$1( parser ); + return new GLTFMaterialsIridescenceExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsAnisotropyExtension$1( parser ); + return new GLTFMaterialsAnisotropyExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMaterialsBumpExtension$1( parser ); + return new GLTFMaterialsBumpExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFLightsExtension$1( parser ); + return new GLTFLightsExtension( parser ); } ); this.register( function ( parser ) { - return new GLTFMeshoptCompression$1( parser ); + return new GLTFMeshoptCompression( parser ); } ); this.register( function ( parser ) { - return new GLTFMeshGpuInstancing$1( parser ); + return new GLTFMeshGpuInstancing( parser ); } ); @@ -9519,11 +9539,11 @@ let GLTFLoader$1 = class GLTFLoader extends Loader { const magic = textDecoder.decode( new Uint8Array( data, 0, 4 ) ); - if ( magic === BINARY_EXTENSION_HEADER_MAGIC$1 ) { + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { try { - extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ] = new GLTFBinaryExtension$1( data ); + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); } catch ( error ) { @@ -9532,7 +9552,7 @@ let GLTFLoader$1 = class GLTFLoader extends Loader { } - json = JSON.parse( extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ].content ); + json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); } else { @@ -9553,7 +9573,7 @@ let GLTFLoader$1 = class GLTFLoader extends Loader { } - const parser = new GLTFParser$1( json, { + const parser = new GLTFParser( json, { path: path || this.resourcePath || '', crossOrigin: this.crossOrigin, @@ -9591,20 +9611,20 @@ let GLTFLoader$1 = class GLTFLoader extends Loader { switch ( extensionName ) { - case EXTENSIONS$1.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension$1(); + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); break; - case EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension$1( json, this.dracoLoader ); + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); break; - case EXTENSIONS$1.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension$1(); + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); break; - case EXTENSIONS$1.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension$1(); + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); break; default: @@ -9635,11 +9655,11 @@ let GLTFLoader$1 = class GLTFLoader extends Loader { } -}; +} /* GLTFREGISTRY */ -function GLTFRegistry$1() { +function GLTFRegistry() { let objects = {}; @@ -9677,7 +9697,7 @@ function GLTFRegistry$1() { /********** EXTENSIONS ***********/ /*********************************/ -const EXTENSIONS$1 = { +const EXTENSIONS = { KHR_BINARY_GLTF: 'KHR_binary_glTF', KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', @@ -9706,12 +9726,12 @@ const EXTENSIONS$1 = { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ -let GLTFLightsExtension$1 = class GLTFLightsExtension { +class GLTFLightsExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_LIGHTS_PUNCTUAL; + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // Object3D instance caches this.cache = { refs: {}, uses: {} }; @@ -9796,7 +9816,7 @@ let GLTFLightsExtension$1 = class GLTFLightsExtension { lightNode.decay = 2; - assignExtrasToUserData$1( lightNode, lightDef ); + assignExtrasToUserData( lightNode, lightDef ); if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; @@ -9837,18 +9857,18 @@ let GLTFLightsExtension$1 = class GLTFLightsExtension { } -}; +} /** * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ -let GLTFMaterialsUnlitExtension$1 = class GLTFMaterialsUnlitExtension { +class GLTFMaterialsUnlitExtension { constructor() { - this.name = EXTENSIONS$1.KHR_MATERIALS_UNLIT; + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; } @@ -9890,19 +9910,19 @@ let GLTFMaterialsUnlitExtension$1 = class GLTFMaterialsUnlitExtension { } -}; +} /** * Materials Emissive Strength Extension * * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md */ -let GLTFMaterialsEmissiveStrengthExtension$1 = class GLTFMaterialsEmissiveStrengthExtension { +class GLTFMaterialsEmissiveStrengthExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_EMISSIVE_STRENGTH; + this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; } @@ -9929,19 +9949,19 @@ let GLTFMaterialsEmissiveStrengthExtension$1 = class GLTFMaterialsEmissiveStreng } -}; +} /** * Clearcoat Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat */ -let GLTFMaterialsClearcoatExtension$1 = class GLTFMaterialsClearcoatExtension { +class GLTFMaterialsClearcoatExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_CLEARCOAT; + this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; } @@ -10013,19 +10033,19 @@ let GLTFMaterialsClearcoatExtension$1 = class GLTFMaterialsClearcoatExtension { } -}; +} /** * Iridescence Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence */ -let GLTFMaterialsIridescenceExtension$1 = class GLTFMaterialsIridescenceExtension { +class GLTFMaterialsIridescenceExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_IRIDESCENCE; + this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; } @@ -10101,19 +10121,19 @@ let GLTFMaterialsIridescenceExtension$1 = class GLTFMaterialsIridescenceExtensio } -}; +} /** * Sheen Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen */ -let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { +class GLTFMaterialsSheenExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_SHEEN; + this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; } @@ -10176,7 +10196,7 @@ let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { } -}; +} /** * Transmission Materials Extension @@ -10184,12 +10204,12 @@ let GLTFMaterialsSheenExtension$1 = class GLTFMaterialsSheenExtension { * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission * Draft: https://github.com/KhronosGroup/glTF/pull/1698 */ -let GLTFMaterialsTransmissionExtension$1 = class GLTFMaterialsTransmissionExtension { +class GLTFMaterialsTransmissionExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_TRANSMISSION; + this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; } @@ -10235,19 +10255,19 @@ let GLTFMaterialsTransmissionExtension$1 = class GLTFMaterialsTransmissionExtens } -}; +} /** * Materials Volume Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume */ -let GLTFMaterialsVolumeExtension$1 = class GLTFMaterialsVolumeExtension { +class GLTFMaterialsVolumeExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_VOLUME; + this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; } @@ -10294,19 +10314,19 @@ let GLTFMaterialsVolumeExtension$1 = class GLTFMaterialsVolumeExtension { } -}; +} /** * Materials ior Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior */ -let GLTFMaterialsIorExtension$1 = class GLTFMaterialsIorExtension { +class GLTFMaterialsIorExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_IOR; + this.name = EXTENSIONS.KHR_MATERIALS_IOR; } @@ -10340,19 +10360,19 @@ let GLTFMaterialsIorExtension$1 = class GLTFMaterialsIorExtension { } -}; +} /** * Materials specular Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular */ -let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { +class GLTFMaterialsSpecularExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_SPECULAR; + this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; } @@ -10403,7 +10423,7 @@ let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { } -}; +} /** @@ -10411,12 +10431,12 @@ let GLTFMaterialsSpecularExtension$1 = class GLTFMaterialsSpecularExtension { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump */ -let GLTFMaterialsBumpExtension$1 = class GLTFMaterialsBumpExtension { +class GLTFMaterialsBumpExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_MATERIALS_BUMP; + this.name = EXTENSIONS.EXT_MATERIALS_BUMP; } @@ -10458,19 +10478,19 @@ let GLTFMaterialsBumpExtension$1 = class GLTFMaterialsBumpExtension { } -}; +} /** * Materials anisotropy Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy */ -let GLTFMaterialsAnisotropyExtension$1 = class GLTFMaterialsAnisotropyExtension { +class GLTFMaterialsAnisotropyExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_MATERIALS_ANISOTROPY; + this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; } @@ -10522,19 +10542,19 @@ let GLTFMaterialsAnisotropyExtension$1 = class GLTFMaterialsAnisotropyExtension } -}; +} /** * BasisU Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu */ -let GLTFTextureBasisUExtension$1 = class GLTFTextureBasisUExtension { +class GLTFTextureBasisUExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.KHR_TEXTURE_BASISU; + this.name = EXTENSIONS.KHR_TEXTURE_BASISU; } @@ -10573,19 +10593,19 @@ let GLTFTextureBasisUExtension$1 = class GLTFTextureBasisUExtension { } -}; +} /** * WebP Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp */ -let GLTFTextureWebPExtension$1 = class GLTFTextureWebPExtension { +class GLTFTextureWebPExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_TEXTURE_WEBP; + this.name = EXTENSIONS.EXT_TEXTURE_WEBP; this.isSupported = null; } @@ -10658,19 +10678,19 @@ let GLTFTextureWebPExtension$1 = class GLTFTextureWebPExtension { } -}; +} /** * AVIF Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif */ -let GLTFTextureAVIFExtension$1 = class GLTFTextureAVIFExtension { +class GLTFTextureAVIFExtension { constructor( parser ) { this.parser = parser; - this.name = EXTENSIONS$1.EXT_TEXTURE_AVIF; + this.name = EXTENSIONS.EXT_TEXTURE_AVIF; this.isSupported = null; } @@ -10741,18 +10761,18 @@ let GLTFTextureAVIFExtension$1 = class GLTFTextureAVIFExtension { } -}; +} /** * meshopt BufferView Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression */ -let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { +class GLTFMeshoptCompression { constructor( parser ) { - this.name = EXTENSIONS$1.EXT_MESHOPT_COMPRESSION; + this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; this.parser = parser; } @@ -10825,7 +10845,7 @@ let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { } -}; +} /** * GPU Instancing Extension @@ -10833,11 +10853,11 @@ let GLTFMeshoptCompression$1 = class GLTFMeshoptCompression { * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing * */ -let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { +class GLTFMeshGpuInstancing { constructor( parser ) { - this.name = EXTENSIONS$1.EXT_MESH_GPU_INSTANCING; + this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; this.parser = parser; } @@ -10860,9 +10880,9 @@ let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { for ( const primitive of meshDef.primitives ) { - if ( primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS$1.TRIANGLE_FAN && + if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && + primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && + primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && primitive.mode !== undefined ) { return null; @@ -10982,22 +11002,22 @@ let GLTFMeshGpuInstancing$1 = class GLTFMeshGpuInstancing { } -}; +} /* BINARY EXTENSION */ -const BINARY_EXTENSION_HEADER_MAGIC$1 = 'glTF'; -const BINARY_EXTENSION_HEADER_LENGTH$1 = 12; -const BINARY_EXTENSION_CHUNK_TYPES$1 = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; +const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; +const BINARY_EXTENSION_HEADER_LENGTH = 12; +const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; -let GLTFBinaryExtension$1 = class GLTFBinaryExtension { +class GLTFBinaryExtension { constructor( data ) { - this.name = EXTENSIONS$1.KHR_BINARY_GLTF; + this.name = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH$1 ); + const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); const textDecoder = new TextDecoder(); this.header = { @@ -11006,7 +11026,7 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { length: headerView.getUint32( 8, true ) }; - if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC$1 ) { + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); @@ -11016,8 +11036,8 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { } - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH$1; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH$1 ); + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); let chunkIndex = 0; while ( chunkIndex < chunkContentsLength ) { @@ -11028,14 +11048,14 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { const chunkType = chunkView.getUint32( chunkIndex, true ); chunkIndex += 4; - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES$1.JSON ) { + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH$1 + chunkIndex, chunkLength ); + const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); this.content = textDecoder.decode( contentArray ); - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES$1.BIN ) { + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - const byteOffset = BINARY_EXTENSION_HEADER_LENGTH$1 + chunkIndex; + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; this.body = data.slice( byteOffset, byteOffset + chunkLength ); } @@ -11054,14 +11074,14 @@ let GLTFBinaryExtension$1 = class GLTFBinaryExtension { } -}; +} /** * DRACO Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ -let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtension { +class GLTFDracoMeshCompressionExtension { constructor( json, dracoLoader ) { @@ -11071,7 +11091,7 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio } - this.name = EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION; + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; this.json = json; this.dracoLoader = dracoLoader; this.dracoLoader.preload(); @@ -11090,7 +11110,7 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio for ( const attributeName in gltfAttributeMap ) { - const threeAttributeName = ATTRIBUTES$1[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; @@ -11098,12 +11118,12 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio for ( const attributeName in primitive.attributes ) { - const threeAttributeName = ATTRIBUTES$1[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); if ( gltfAttributeMap[ attributeName ] !== undefined ) { const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; attributeTypeMap[ threeAttributeName ] = componentType.name; attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; @@ -11137,18 +11157,18 @@ let GLTFDracoMeshCompressionExtension$1 = class GLTFDracoMeshCompressionExtensio } -}; +} /** * Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform */ -let GLTFTextureTransformExtension$1 = class GLTFTextureTransformExtension { +class GLTFTextureTransformExtension { constructor() { - this.name = EXTENSIONS$1.KHR_TEXTURE_TRANSFORM; + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } @@ -11196,22 +11216,22 @@ let GLTFTextureTransformExtension$1 = class GLTFTextureTransformExtension { } -}; +} /** * Mesh Quantization Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization */ -let GLTFMeshQuantizationExtension$1 = class GLTFMeshQuantizationExtension { +class GLTFMeshQuantizationExtension { constructor() { - this.name = EXTENSIONS$1.KHR_MESH_QUANTIZATION; + this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; } -}; +} /*********************************/ /********** INTERPOLATION ********/ @@ -11219,7 +11239,7 @@ let GLTFMeshQuantizationExtension$1 = class GLTFMeshQuantizationExtension { // Spline Interpolation // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation -let GLTFCubicSplineInterpolant$1 = class GLTFCubicSplineInterpolant extends Interpolant { +class GLTFCubicSplineInterpolant extends Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { @@ -11287,23 +11307,23 @@ let GLTFCubicSplineInterpolant$1 = class GLTFCubicSplineInterpolant extends Inte } -}; +} -const _q$1 = new Quaternion(); +const _q = new Quaternion(); -let GLTFCubicSplineQuaternionInterpolant$1 = class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant$1 { +class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { interpolate_( i1, t0, t, t1 ) { const result = super.interpolate_( i1, t0, t, t1 ); - _q$1.fromArray( result ).normalize().toArray( result ); + _q.fromArray( result ).normalize().toArray( result ); return result; } -}; +} /*********************************/ @@ -11312,7 +11332,7 @@ let GLTFCubicSplineQuaternionInterpolant$1 = class GLTFCubicSplineQuaternionInte /* CONSTANTS */ -const WEBGL_CONSTANTS$1 = { +const WEBGL_CONSTANTS = { FLOAT: 5126, //FLOAT_MAT2: 35674, FLOAT_MAT3: 35675, @@ -11334,7 +11354,7 @@ const WEBGL_CONSTANTS$1 = { UNSIGNED_SHORT: 5123 }; -const WEBGL_COMPONENT_TYPES$1 = { +const WEBGL_COMPONENT_TYPES = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, @@ -11343,7 +11363,7 @@ const WEBGL_COMPONENT_TYPES$1 = { 5126: Float32Array }; -const WEBGL_FILTERS$1 = { +const WEBGL_FILTERS = { 9728: NearestFilter, 9729: LinearFilter, 9984: NearestMipmapNearestFilter, @@ -11352,13 +11372,13 @@ const WEBGL_FILTERS$1 = { 9987: LinearMipmapLinearFilter }; -const WEBGL_WRAPPINGS$1 = { +const WEBGL_WRAPPINGS = { 33071: ClampToEdgeWrapping, 33648: MirroredRepeatWrapping, 10497: RepeatWrapping }; -const WEBGL_TYPE_SIZES$1 = { +const WEBGL_TYPE_SIZES = { 'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, @@ -11368,7 +11388,7 @@ const WEBGL_TYPE_SIZES$1 = { 'MAT4': 16 }; -const ATTRIBUTES$1 = { +const ATTRIBUTES = { POSITION: 'position', NORMAL: 'normal', TANGENT: 'tangent', @@ -11381,21 +11401,21 @@ const ATTRIBUTES$1 = { JOINTS_0: 'skinIndex', }; -const PATH_PROPERTIES$1 = { +const PATH_PROPERTIES = { scale: 'scale', translation: 'position', rotation: 'quaternion', weights: 'morphTargetInfluences' }; -const INTERPOLATION$1 = { +const 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: InterpolateLinear, STEP: InterpolateDiscrete }; -const ALPHA_MODES$1 = { +const ALPHA_MODES = { OPAQUE: 'OPAQUE', MASK: 'MASK', BLEND: 'BLEND' @@ -11404,7 +11424,7 @@ const ALPHA_MODES$1 = { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material */ -function createDefaultMaterial$1( cache ) { +function createDefaultMaterial( cache ) { if ( cache[ 'DefaultMaterial' ] === undefined ) { @@ -11424,7 +11444,7 @@ function createDefaultMaterial$1( cache ) { } -function addUnknownExtensionsToUserData$1( knownExtensions, object, objectDef ) { +function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { // Add unknown glTF extensions to an object's userData. @@ -11445,7 +11465,7 @@ function addUnknownExtensionsToUserData$1( knownExtensions, object, objectDef ) * @param {Object3D|Material|BufferGeometry} object * @param {GLTF.definition} gltfDef */ -function assignExtrasToUserData$1( object, gltfDef ) { +function assignExtrasToUserData( object, gltfDef ) { if ( gltfDef.extras !== undefined ) { @@ -11467,7 +11487,7 @@ function assignExtrasToUserData$1( object, gltfDef ) { * @param {GLTFParser} parser * @return {Promise} */ -function addMorphTargets$1( geometry, targets, parser ) { +function addMorphTargets( geometry, targets, parser ) { let hasMorphPosition = false; let hasMorphNormal = false; @@ -11552,7 +11572,7 @@ function addMorphTargets$1( geometry, targets, parser ) { * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ -function updateMorphTargets$1( mesh, meshDef ) { +function updateMorphTargets( mesh, meshDef ) { mesh.updateMorphTargets(); @@ -11587,21 +11607,21 @@ function updateMorphTargets$1( mesh, meshDef ) { } -function createPrimitiveKey$1( primitiveDef ) { +function createPrimitiveKey( primitiveDef ) { let geometryKey; - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ]; + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; if ( dracoExtension ) { geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices - + ':' + createAttributesKey$1( dracoExtension.attributes ); + + ':' + createAttributesKey( dracoExtension.attributes ); } else { - geometryKey = primitiveDef.indices + ':' + createAttributesKey$1( primitiveDef.attributes ) + ':' + primitiveDef.mode; + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; } @@ -11609,7 +11629,7 @@ function createPrimitiveKey$1( primitiveDef ) { for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - geometryKey += ':' + createAttributesKey$1( primitiveDef.targets[ i ] ); + geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); } @@ -11619,7 +11639,7 @@ function createPrimitiveKey$1( primitiveDef ) { } -function createAttributesKey$1( attributes ) { +function createAttributesKey( attributes ) { let attributesKey = ''; @@ -11635,7 +11655,7 @@ function createAttributesKey$1( attributes ) { } -function getNormalizedComponentScale$1( constructor ) { +function getNormalizedComponentScale( constructor ) { // Reference: // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data @@ -11661,7 +11681,7 @@ function getNormalizedComponentScale$1( constructor ) { } -function getImageURIMimeType$1( uri ) { +function getImageURIMimeType( uri ) { if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; @@ -11670,11 +11690,11 @@ function getImageURIMimeType$1( uri ) { } -const _identityMatrix$1 = new Matrix4(); +const _identityMatrix = new Matrix4(); /* GLTF PARSER */ -let GLTFParser$1 = class GLTFParser { +class GLTFParser { constructor( json = {}, options = {} ) { @@ -11684,7 +11704,7 @@ let GLTFParser$1 = class GLTFParser { this.options = options; // loader object cache - this.cache = new GLTFRegistry$1(); + this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements this.associations = new Map(); @@ -11800,9 +11820,9 @@ let GLTFParser$1 = class GLTFParser { userData: {} }; - addUnknownExtensionsToUserData$1( extensions, result, json ); + addUnknownExtensionsToUserData( extensions, result, json ); - assignExtrasToUserData$1( result, json ); + assignExtrasToUserData( result, json ); return Promise.all( parser._invokeAll( function ( ext ) { @@ -12120,7 +12140,7 @@ let GLTFParser$1 = class GLTFParser { // If present, GLB container is required to be the first buffer. if ( bufferDef.uri === undefined && bufferIndex === 0 ) { - return Promise.resolve( this.extensions[ EXTENSIONS$1.KHR_BINARY_GLTF ].body ); + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); } @@ -12171,8 +12191,8 @@ let GLTFParser$1 = class GLTFParser { if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - const itemSize = WEBGL_TYPE_SIZES$1[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; const normalized = accessorDef.normalized === true; const array = new TypedArray( accessorDef.count * itemSize ); @@ -12203,8 +12223,8 @@ let GLTFParser$1 = class GLTFParser { const bufferView = bufferViews[ 0 ]; - const itemSize = WEBGL_TYPE_SIZES$1[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES$1[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. const elementBytes = TypedArray.BYTES_PER_ELEMENT; @@ -12255,8 +12275,8 @@ let GLTFParser$1 = class GLTFParser { // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors if ( accessorDef.sparse !== undefined ) { - const itemSizeIndices = WEBGL_TYPE_SIZES$1.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES$1[ accessorDef.sparse.indices.componentType ]; + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; @@ -12349,10 +12369,10 @@ let GLTFParser$1 = class GLTFParser { const samplers = json.samplers || {}; const sampler = samplers[ textureDef.sampler ] || {}; - texture.magFilter = WEBGL_FILTERS$1[ sampler.magFilter ] || LinearFilter; - texture.minFilter = WEBGL_FILTERS$1[ sampler.minFilter ] || LinearMipmapLinearFilter; - texture.wrapS = WEBGL_WRAPPINGS$1[ sampler.wrapS ] || RepeatWrapping; - texture.wrapT = WEBGL_WRAPPINGS$1[ sampler.wrapT ] || RepeatWrapping; + texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; + texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; + texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; + texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; parser.associations.set( texture, { textures: textureIndex } ); @@ -12441,7 +12461,7 @@ let GLTFParser$1 = class GLTFParser { } - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType$1( sourceDef.uri ); + texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); return texture; @@ -12477,14 +12497,14 @@ let GLTFParser$1 = class GLTFParser { } - if ( parser.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ] ) { + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ] : undefined; + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; if ( transform ) { const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS$1.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); parser.associations.set( texture, gltfReference ); } @@ -12627,9 +12647,9 @@ let GLTFParser$1 = class GLTFParser { const pending = []; - if ( materialExtensions[ EXTENSIONS$1.KHR_MATERIALS_UNLIT ] ) { + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - const kmuExtension = extensions[ EXTENSIONS$1.KHR_MATERIALS_UNLIT ]; + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; materialType = kmuExtension.getMaterialType(); pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); @@ -12688,9 +12708,9 @@ let GLTFParser$1 = class GLTFParser { } - const alphaMode = materialDef.alphaMode || ALPHA_MODES$1.OPAQUE; + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - if ( alphaMode === ALPHA_MODES$1.BLEND ) { + if ( alphaMode === ALPHA_MODES.BLEND ) { materialParams.transparent = true; @@ -12701,7 +12721,7 @@ let GLTFParser$1 = class GLTFParser { materialParams.transparent = false; - if ( alphaMode === ALPHA_MODES$1.MASK ) { + if ( alphaMode === ALPHA_MODES.MASK ) { materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; @@ -12756,11 +12776,11 @@ let GLTFParser$1 = class GLTFParser { if ( materialDef.name ) material.name = materialDef.name; - assignExtrasToUserData$1( material, materialDef ); + assignExtrasToUserData( material, materialDef ); parser.associations.set( material, { materials: materialIndex } ); - if ( materialDef.extensions ) addUnknownExtensionsToUserData$1( extensions, material, materialDef ); + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); return material; @@ -12803,11 +12823,11 @@ let GLTFParser$1 = class GLTFParser { function createDracoPrimitive( primitive ) { - return extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ] + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] .decodePrimitive( primitive, parser ) .then( function ( geometry ) { - return addPrimitiveAttributes$1( geometry, primitive, parser ); + return addPrimitiveAttributes( geometry, primitive, parser ); } ); @@ -12818,7 +12838,7 @@ let GLTFParser$1 = class GLTFParser { for ( let i = 0, il = primitives.length; i < il; i ++ ) { const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey$1( primitive ); + const cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry const cached = cache[ cacheKey ]; @@ -12832,7 +12852,7 @@ let GLTFParser$1 = class GLTFParser { let geometryPromise; - if ( primitive.extensions && primitive.extensions[ EXTENSIONS$1.KHR_DRACO_MESH_COMPRESSION ] ) { + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { // Use DRACO geometry if available geometryPromise = createDracoPrimitive( primitive ); @@ -12840,7 +12860,7 @@ let GLTFParser$1 = class GLTFParser { } else { // Otherwise create a new geometry - geometryPromise = addPrimitiveAttributes$1( new BufferGeometry(), primitive, parser ); + geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); } @@ -12876,7 +12896,7 @@ let GLTFParser$1 = class GLTFParser { for ( let i = 0, il = primitives.length; i < il; i ++ ) { const material = primitives[ i ].material === undefined - ? createDefaultMaterial$1( this.cache ) + ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material ); pending.push( material ); @@ -12903,9 +12923,9 @@ let GLTFParser$1 = class GLTFParser { const material = materials[ i ]; - if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLES || - primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_STRIP || - primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_FAN || + 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() @@ -12920,29 +12940,29 @@ let GLTFParser$1 = class GLTFParser { } - if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_STRIP ) { + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - mesh.geometry = toTrianglesDrawMode$1( mesh.geometry, TriangleStripDrawMode ); + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.TRIANGLE_FAN ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - mesh.geometry = toTrianglesDrawMode$1( mesh.geometry, TriangleFanDrawMode ); + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode ); } - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINES ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { mesh = new LineSegments( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINE_STRIP ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { mesh = new Line( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.LINE_LOOP ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { mesh = new LineLoop( geometry, material ); - } else if ( primitive.mode === WEBGL_CONSTANTS$1.POINTS ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { mesh = new Points( geometry, material ); @@ -12954,15 +12974,15 @@ let GLTFParser$1 = class GLTFParser { if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { - updateMorphTargets$1( mesh, meshDef ); + updateMorphTargets( mesh, meshDef ); } mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - assignExtrasToUserData$1( mesh, meshDef ); + assignExtrasToUserData( mesh, meshDef ); - if ( primitive.extensions ) addUnknownExtensionsToUserData$1( extensions, mesh, primitive ); + if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); parser.assignFinalMaterial( mesh ); @@ -12981,7 +13001,7 @@ let GLTFParser$1 = class GLTFParser { if ( meshes.length === 1 ) { - if ( meshDef.extensions ) addUnknownExtensionsToUserData$1( extensions, meshes[ 0 ], meshDef ); + if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); return meshes[ 0 ]; @@ -12989,7 +13009,7 @@ let GLTFParser$1 = class GLTFParser { const group = new Group$1(); - if ( meshDef.extensions ) addUnknownExtensionsToUserData$1( extensions, group, meshDef ); + if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); parser.associations.set( group, { meshes: meshIndex } ); @@ -13033,7 +13053,7 @@ let GLTFParser$1 = class GLTFParser { if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); - assignExtrasToUserData$1( camera, cameraDef ); + assignExtrasToUserData( camera, cameraDef ); return Promise.resolve( camera ); @@ -13277,7 +13297,7 @@ let GLTFParser$1 = class GLTFParser { if ( ! mesh.isSkinnedMesh ) return; - mesh.bind( skeleton, _identityMatrix$1 ); + mesh.bind( skeleton, _identityMatrix ); } ); @@ -13391,9 +13411,9 @@ let GLTFParser$1 = class GLTFParser { } - assignExtrasToUserData$1( node, nodeDef ); + assignExtrasToUserData( node, nodeDef ); - if ( nodeDef.extensions ) addUnknownExtensionsToUserData$1( extensions, node, nodeDef ); + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); if ( nodeDef.matrix !== undefined ) { @@ -13455,9 +13475,9 @@ let GLTFParser$1 = class GLTFParser { const scene = new Group$1(); if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - assignExtrasToUserData$1( scene, sceneDef ); + assignExtrasToUserData( scene, sceneDef ); - if ( sceneDef.extensions ) addUnknownExtensionsToUserData$1( extensions, scene, sceneDef ); + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); const nodeIds = sceneDef.nodes || []; @@ -13524,7 +13544,7 @@ let GLTFParser$1 = class GLTFParser { const targetName = node.name ? node.name : node.uuid; const targetNames = []; - if ( PATH_PROPERTIES$1[ target.path ] === PATH_PROPERTIES$1.weights ) { + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { node.traverse( function ( object ) { @@ -13544,20 +13564,20 @@ let GLTFParser$1 = class GLTFParser { let TypedKeyframeTrack; - switch ( PATH_PROPERTIES$1[ target.path ] ) { + switch ( PATH_PROPERTIES[ target.path ] ) { - case PATH_PROPERTIES$1.weights: + case PATH_PROPERTIES.weights: TypedKeyframeTrack = NumberKeyframeTrack; break; - case PATH_PROPERTIES$1.rotation: + case PATH_PROPERTIES.rotation: TypedKeyframeTrack = QuaternionKeyframeTrack; break; - case PATH_PROPERTIES$1.position: - case PATH_PROPERTIES$1.scale: + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: TypedKeyframeTrack = VectorKeyframeTrack; break; @@ -13581,7 +13601,7 @@ let GLTFParser$1 = class GLTFParser { } - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION$1[ sampler.interpolation ] : InterpolateLinear; + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; const outputArray = this._getArrayFromAccessor( outputAccessor ); @@ -13589,7 +13609,7 @@ let GLTFParser$1 = class GLTFParser { for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES$1[ target.path ], + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], inputAccessor.array, outputArray, interpolation @@ -13616,7 +13636,7 @@ let GLTFParser$1 = class GLTFParser { if ( accessor.normalized ) { - const scale = getNormalizedComponentScale$1( outputArray.constructor ); + const scale = getNormalizedComponentScale( outputArray.constructor ); const scaled = new Float32Array( outputArray.length ); for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { @@ -13641,7 +13661,7 @@ let GLTFParser$1 = class GLTFParser { // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() // must be divided by three to get the interpolant's sampleSize argument. - const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant$1 : GLTFCubicSplineInterpolant$1; + const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); @@ -13652,14 +13672,14 @@ let GLTFParser$1 = class GLTFParser { } -}; +} /** * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser */ -function computeBounds$1( geometry, primitiveDef, parser ) { +function computeBounds( geometry, primitiveDef, parser ) { const attributes = primitiveDef.attributes; @@ -13683,7 +13703,7 @@ function computeBounds$1( geometry, primitiveDef, parser ) { if ( accessor.normalized ) { - const boxScale = getNormalizedComponentScale$1( WEBGL_COMPONENT_TYPES$1[ accessor.componentType ] ); + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); box.min.multiplyScalar( boxScale ); box.max.multiplyScalar( boxScale ); @@ -13730,7 +13750,7 @@ function computeBounds$1( geometry, primitiveDef, parser ) { if ( accessor.normalized ) { - const boxScale = getNormalizedComponentScale$1( WEBGL_COMPONENT_TYPES$1[ accessor.componentType ] ); + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); vector.multiplyScalar( boxScale ); } @@ -13769,7 +13789,7 @@ function computeBounds$1( geometry, primitiveDef, parser ) { * @param {GLTFParser} parser * @return {Promise} */ -function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { +function addPrimitiveAttributes( geometry, primitiveDef, parser ) { const attributes = primitiveDef.attributes; @@ -13788,7 +13808,7 @@ function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { for ( const gltfAttributeName in attributes ) { - const threeAttributeName = ATTRIBUTES$1[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension. if ( threeAttributeName in geometry.attributes ) continue; @@ -13811,23 +13831,23 @@ function addPrimitiveAttributes$1( geometry, primitiveDef, parser ) { if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) ; - assignExtrasToUserData$1( geometry, primitiveDef ); + assignExtrasToUserData( geometry, primitiveDef ); - computeBounds$1( geometry, primitiveDef, parser ); + computeBounds( geometry, primitiveDef, parser ); return Promise.all( pending ).then( function () { return primitiveDef.targets !== undefined - ? addMorphTargets$1( geometry, primitiveDef.targets, parser ) + ? addMorphTargets( geometry, primitiveDef.targets, parser ) : geometry; } ); } -const _taskCache$2 = new WeakMap(); +const _taskCache$1 = new WeakMap(); -let DRACOLoader$1 = class DRACOLoader extends Loader { +class DRACOLoader extends Loader { constructor( manager ) { @@ -13925,9 +13945,9 @@ let DRACOLoader$1 = class DRACOLoader extends Loader { // Check for an existing task using this buffer. A transferred buffer cannot be transferred // again from this thread. - if ( _taskCache$2.has( buffer ) ) { + if ( _taskCache$1.has( buffer ) ) { - const cachedTask = _taskCache$2.get( buffer ); + const cachedTask = _taskCache$1.get( buffer ); if ( cachedTask.key === taskKey ) { @@ -13993,7 +14013,7 @@ let DRACOLoader$1 = class DRACOLoader extends Loader { } ); // Cache the task result. - _taskCache$2.set( buffer, { + _taskCache$1.set( buffer, { key: taskKey, promise: geometryPending @@ -14111,7 +14131,7 @@ let DRACOLoader$1 = class DRACOLoader extends Loader { } - const fn = DRACOWorker$1.toString(); + const fn = DRACOWorker.toString(); const body = [ '/* draco decoder */', @@ -14214,11 +14234,11 @@ let DRACOLoader$1 = class DRACOLoader extends Loader { } -}; +} /* WEB WORKER */ -function DRACOWorker$1() { +function DRACOWorker() { let decoderConfig; let decoderPending; @@ -18437,10 +18457,10 @@ class ModelLoader { } class Lorder{ constructor() { - this.gltfLoader = new GLTFLoader$1(); + this.gltfLoader = new GLTFLoader(); // Optional: Provide a DRACOLoader instance to decode compressed mesh data - const dracoLoader = new DRACOLoader$1(); + const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( Config.DRACOPath ); this.gltfLoader.setDRACOLoader( dracoLoader ); this.objLoader = new OBJLoader(); // obj模型 @@ -18686,5725 +18706,471 @@ Sky.SkyShader = { uniform float mieCoefficient; uniform vec3 up; - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - // constants for atmospheric scattering - const float e = 2.71828182845904523536028747135266249775724709369995957; - const float pi = 3.141592653589793238462643383279502884197169; - - // wavelength of used primaries, according to preetham - const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: - // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - - // mie stuff - // K coefficient for the primaries - const float v = 4.0; - const vec3 K = vec3( 0.686, 0.678, 0.666 ); - // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - - // earth shadow hack - // cutoffAngle = pi / 1.95; - const float cutoffAngle = 1.6110731556870734; - const float steepness = 1.5; - const float EE = 1000.0; - - float sunIntensity( float zenithAngleCos ) { - zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); - return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); - } - - vec3 totalMie( float T ) { - float c = ( 0.2 * T ) * 10E-18; - return 0.434 * c * MieConst; - } - - void main() { - - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vWorldPosition = worldPosition.xyz; - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - gl_Position.z = gl_Position.w; // set z to camera.far - - vSunDirection = normalize( sunPosition ); - - vSunE = sunIntensity( dot( vSunDirection, up ) ); - - vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - - float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - - // extinction (absorbtion + out scattering) - // rayleigh coefficients - vBetaR = totalRayleigh * rayleighCoefficient; - - // mie coefficients - vBetaM = totalMie( turbidity ) * mieCoefficient; - - }`, - - fragmentShader: /* glsl */` - varying vec3 vWorldPosition; - varying vec3 vSunDirection; - varying float vSunfade; - varying vec3 vBetaR; - varying vec3 vBetaM; - varying float vSunE; - - uniform float mieDirectionalG; - uniform vec3 up; - - // constants for atmospheric scattering - const float pi = 3.141592653589793238462643383279502884197169; - - const float n = 1.0003; // refractive index of air - const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - - // optical length at zenith for molecules - const float rayleighZenithLength = 8.4E3; - const float mieZenithLength = 1.25E3; - // 66 arc seconds -> degrees, and the cosine of that - const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - - // 3.0 / ( 16.0 * pi ) - const float THREE_OVER_SIXTEENPI = 0.05968310365946075; - // 1.0 / ( 4.0 * pi ) - const float ONE_OVER_FOURPI = 0.07957747154594767; - - float rayleighPhase( float cosTheta ) { - return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); - } - - float hgPhase( float cosTheta, float g ) { - float g2 = pow( g, 2.0 ); - float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); - return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); - } - - void main() { - - vec3 direction = normalize( vWorldPosition - cameraPosition ); - - // optical length - // cutoff angle at 90 to avoid singularity in next formula. - float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); - float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); - float sR = rayleighZenithLength * inverse; - float sM = mieZenithLength * inverse; - - // combined extinction factor - vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - - // in scattering - float cosTheta = dot( direction, vSunDirection ); - - float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); - vec3 betaRTheta = vBetaR * rPhase; - - float mPhase = hgPhase( cosTheta, mieDirectionalG ); - vec3 betaMTheta = vBetaM * mPhase; - - vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); - Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - - // nightsky - float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] - float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] - vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); - vec3 L0 = vec3( 0.1 ) * Fex; - - // composition + solar disc - float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); - L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - - vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - - vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - - gl_FragColor = vec4( retColor, 1.0 ); - - #include - #include - - }` - -}; - -// 多个canvas并没有id,只有父节点 - - -class Layer extends BasLayer{ - id; // 唯一标识 - layerContainer; // div#layer 容器 - zIndex=1;//默认为1 - opacity=1;//默认为1 - canvas;//canvas - dispose = false; - renderer;//canvas上下文 - scene;//场景 - visible=true;//是否可见 - mapView;//地图视图 - camera;//相机 - controls;//控件 - animateId;//动画事件id - base = false; // 是否为底图 - ambientLight; // 环境光 - directionalLight; // 方向光 - modelLayer = false; // 模型图层 - imageLayer = false; // 影像图层 - vectorLayer = false; // 矢量图层,如路网、行政区划,地名等图层 - waters = []; // 水面集合 - constructor(id, layerContainer, canvas, mapView, plane = true, camera = new PerspectiveCamera(80, 1, 0.1, 1e12)) { - super(); - this.id = id; - this.layerContainer = layerContainer; - this.canvas = canvas; - this.renderer = new WebGLRenderer({ - canvas: this.canvas, - antialias: true, - alpha: true, - logarithmicDepthBuffer: true, - precision: "highp", - }); - this.renderer.sortObjects = true; - this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setClearColor(0xFFFFFF, 0.0); - this.scene = new Scene(); - this.mapView = mapView; - this.camera = camera; - if(this.mapView){ - this.scene.add(this.mapView); - this.mapView.updateMatrixWorld(true); - } - if (plane){ - this.controls = new MapControls(this.camera, this.canvas); - this.controls.minDistance = 1e1; - this.controls.zoomSpeed = 2.0; - } else { - this.controls = new OrbitControls(this.camera, this.canvas); - this.controls.enablePan = false; - this.controls.minDistance = UnitsUtils.EARTH_RADIUS + 2; - this.controls.maxDistance = UnitsUtils.EARTH_RADIUS * 1e1; - } - this._raycaster = new Raycaster(); - if(Config.outLine.on){ - this.effectOutline = new EffectOutline(this.renderer, this.scene, this.camera, this.canvas.width, this.canvas.height); - } - if (Config.layer.map.ambientLight.add){ - this.scene.add(new AmbientLight(Config.layer.map.ambientLight.color, Config.layer.map.ambientLight.intensity)); - } - if (Config.layer.map.directionalLight.add){ - this.scene.add(new DirectionalLight(Config.layer.map.directionalLight.color, Config.layer.map.directionalLight.intensity)); - } - if (Config.layer.map.pointLight.add){ - let pointLight = new PointLight(Config.layer.map.pointLight.color, Config.layer.map.pointLight.intensity, Config.layer.map.pointLight.distance); - pointLight.position.set(...Config.layer.map.pointLight.position); - this.scene.add(pointLight); - } - } - - moveTo(lat, lon, height = 38472.48763833733){ - // var coords = UnitsUtils.datumsToSpherical(44.266119,90.139228); - var coords = UnitsUtils.datumsToSpherical(lat,lon); - this.camera.position.set(coords.x, height, -coords.y); - this.controls.target.set(this.camera.position.x, 0, this.camera.position.z); - } - - moveToByCoords(coords){ - let offset = 50; - this.camera.position.set(coords.x, coords.y+offset, coords.z); - this.controls.target.set(coords.x, coords.y, coords.z); - } - - moveToByLL(lat, lon, distance = 384720){ - let dir = UnitsUtils.datumsToVector(lat, lon); - dir.multiplyScalar(UnitsUtils.EARTH_RADIUS + distance); - this.camera.position.copy(dir); - } - - - on(eventName, callback){ - this.listener.on(eventName, callback); - } - - setSceneBackground(color) { - this.scene.background = color; - } - - clearSceneBackground() { - this.scene.background = null; - } - - // 可用于添加灯光mesh等元素 - add(Object3D) { - if(Object3D ==null || Object3D ==undefined){ - return; - } - this.scene.add(Object3D); - } - - - remove(Object3D) { - if(Object3D ==null || Object3D ==undefined){ - return; - } - this.scene.remove(Object3D); - } - - openWaterConfig(){ - /** - * 打开渲染水系配置 - */ - // this.renderer.setPixelRatio( window.devicePixelRatio ); - this.renderer.toneMapping = ACESFilmicToneMapping; - this.renderer.toneMappingExposure = 0.5; - - // 添加天空 - this.sky = new Sky(); - this.sky.translateX = true; - this.sky.translateY = true; - this.sky.translateZ = true; - this.sky.rotateX = Math.PI / 2; - this.sky.scale.setScalar( Config.EARTH_RADIUS * 2 * Math.PI ); // 天空放大倍数 - this.scene.add( this.sky ); - const skyUniforms = this.sky.material.uniforms; - // 天空的配置 - skyUniforms[ 'turbidity' ].value = 10; - skyUniforms[ 'rayleigh' ].value = 2; - skyUniforms[ 'mieCoefficient' ].value = 0.005; - skyUniforms[ 'mieDirectionalG' ].value = 0.8; - // 旋转 设置为y朝上 - // sky.material.uniforms["up"].value = new THREE.Vector3(0, 1, 0); - - // 天空映射, 更新太阳位置 - this.pmremGenerator = new PMREMGenerator( this.renderer ); - this.sceneEnv = new Scene(); - this.renderTarget = null; - this.sun = new Vector3(); - this.updateSun(Config.SUNDEGREE, Config.SUNAZIMUTH); - } - - updateSun(elevation, azimuth) { - - const phi = MathUtils.degToRad( 90 - elevation ); - const theta = MathUtils.degToRad( azimuth ); - - this.sun.setFromSphericalCoords( 1, phi, theta ); - - this.sky.material.uniforms[ 'sunPosition' ].value.copy( this.sun ); - for (let water of this.waters){ - water.material.uniforms[ 'sunDirection' ].value.copy( this.sun ).normalize(); - } - if ( this.renderTarget !== null ) this.renderTarget.dispose(); - - this.sceneEnv.add( this.sky ); - this.renderTarget = this.pmremGenerator.fromScene( this.sceneEnv ); - this.scene.add( this.sky ); - - this.scene.environment = this.renderTarget.texture; - - - } - - /** - * 添加水系 - * @param {*} water - * @returns - */ - addWater(water) { - if(water ==null || water ==undefined){ - return; - } - this.scene.add(water); - this.waters.push(water); - } - - removeWater(water) { - if(water ==null || water ==undefined){ - return; - } - this.scene.remove(water); - let index = this.waters.indexOf(water); - if (index > -1) { - this.waters.splice(index, 1); - } - } - - setVisible(visible) { - this.visible = this.visible; - this.layerContainer.style.display = visible ? 'block' : 'none'; - } - /** - * @deprecated 不建议用 - * @param {number} opacity - */ - setOpacity(opacity) { - this.opacity = opacity; - this.layerContainer.style.opacity = opacity; - } - - /** - * 修改显示层级,默认越靠下的dom元素,显示上越靠上 - * @param {number} zIndex - */ - setZIndex(zIndex) { - this.zIndex = zIndex; - this.layerContainer.style.zIndex = this.zIndex; - } - - dispose() { - Element.removeLayer(id); - this.dispose = true; - this.animateId && cancelAnimationFrame(this.animateId); - this.mapView.root.dispose(); - this.mapView.dispose(); - } - - selectModel(insect){ - this.effectOutline.selectModel(insect); - } - - resize(){ - var width = window.innerWidth; - var height = window.innerHeight; - this.renderer.setSize(width, height); - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - if(Config.outLine.on){ - this.effectOutline.resize(width, height); - } - } - - animate(){ - this.animateId = requestAnimationFrame(this.animate.bind(this)); - if(this.base){ - this.controls.update(); - } - if (this.base){ - update(); //目前只有在基础地图中添加相机移动的动画,所以暂且只在base地图中进行渲染,后期可改成每个图层都进行渲染,或者必要时进行渲染。 - } - for(let water of this.waters){ - water.material.uniforms[ 'time' ].value += 1.0 / 60.0; - } - if (Config.outLine.on){ - this.effectOutline.render(); // 合成器渲染 - } else { - this.renderer.autoClear = true; - } - this.renderer.render(this.scene, this.camera); - } - - _raycast(meshes, recursive, faceExclude) { - const isects = this._raycaster.intersectObjects(meshes, recursive); - if (faceExclude) { - for (let i = 0; i < isects.length; i++) { - if (isects[i].face !== faceExclude) { - return isects[i]; - } - } - return null; - } - return isects.length > 0 ? isects[0] : null; - } - - _raycastFromMouse(mx, my, width, height, cam, meshes, recursive=false) { - const mouse = new Vector2( // normalized (-1 to +1) - (mx / width) * 2 - 1, - - (my / height) * 2 + 1); - // https://threejs.org/docs/#api/core/Raycaster - // update the picking ray with the camera and mouse position - this._raycaster.setFromCamera(mouse, cam); - return this._raycast(meshes, recursive, null); - } - - /** - * - * @param {*} mx 屏幕坐标x - * @param {*} my 屏幕坐标y - * @param {boolean} recursive 是否检查子节点,true 递归检查 - * @returns mesh - */ - raycastFromMouse(mx, my, recursive=false) { - //---- NG: 2x when starting with Chrome's inspector mobile - // const {width, height} = this.renderer.domElement; - // const {width, height} = this.canvas; - //---- OK - const {clientWidth, clientHeight} = this.canvas; - - return this._raycastFromMouse( - mx, my, clientWidth, clientHeight, this.camera, - this.mapView.children, recursive); - } - - insectALL(mx, my, recursive=false) { - const {clientWidth, clientHeight} = this.canvas; - - return this._raycastFromMouse( - mx, my, clientWidth, clientHeight, this.camera, - this.scene.children, recursive); - } - -} - -/** - * @param {BufferGeometry} geometry - * @param {number} drawMode - * @return {BufferGeometry} - */ -function toTrianglesDrawMode( geometry, drawMode ) { - - if ( drawMode === TrianglesDrawMode ) { - return geometry; - - } - - if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) { - - let index = geometry.getIndex(); - - // generate index if not present - - if ( index === null ) { - - const indices = []; - - const position = geometry.getAttribute( 'position' ); - - if ( position !== undefined ) { - - for ( let i = 0; i < position.count; i ++ ) { - - indices.push( i ); - - } - - geometry.setIndex( indices ); - index = geometry.getIndex(); - - } else { - return geometry; - - } - - } - - // - - const numberOfTriangles = index.count - 2; - const newIndices = []; - - if ( drawMode === TriangleFanDrawMode ) { - - // gl.TRIANGLE_FAN - - for ( let i = 1; i <= numberOfTriangles; i ++ ) { - - newIndices.push( index.getX( 0 ) ); - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - - } - - } else { - - // gl.TRIANGLE_STRIP - - for ( let i = 0; i < numberOfTriangles; i ++ ) { - - if ( i % 2 === 0 ) { - - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i + 2 ) ); - - } else { - - newIndices.push( index.getX( i + 2 ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i ) ); - - } - - } - - } - - if ( ( newIndices.length / 3 ) !== numberOfTriangles ) ; - - // build final geometry - - const newGeometry = geometry.clone(); - newGeometry.setIndex( newIndices ); - newGeometry.clearGroups(); - - return newGeometry; - - } else { - return geometry; - - } - -} - -class GLTFLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - this.dracoLoader = null; - this.ktx2Loader = null; - this.meshoptDecoder = null; - - this.pluginCallbacks = []; - - this.register( function ( parser ) { - - return new GLTFMaterialsClearcoatExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureBasisUExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureWebPExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureAVIFExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSheenExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsTransmissionExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsVolumeExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIorExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsEmissiveStrengthExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSpecularExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIridescenceExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsAnisotropyExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsBumpExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFLightsExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshoptCompression( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshGpuInstancing( parser ); - - } ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - let resourcePath; - - if ( this.resourcePath !== '' ) { - - resourcePath = this.resourcePath; - - } else if ( this.path !== '' ) { - - // If a base path is set, resources will be relative paths from that plus the relative path of the gltf file - // Example path = 'https://my-cnd-server.com/', url = 'assets/models/model.gltf' - // resourcePath = 'https://my-cnd-server.com/assets/models/' - // referenced resource 'model.bin' will be loaded from 'https://my-cnd-server.com/assets/models/model.bin' - // referenced resource '../textures/texture.png' will be loaded from 'https://my-cnd-server.com/assets/textures/texture.png' - const relativeUrl = LoaderUtils.extractUrlBase( url ); - resourcePath = LoaderUtils.resolveURL( relativeUrl, this.path ); - - } else { - - resourcePath = 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. - this.manager.itemStart( url ); - - const _onError = function ( e ) { - - if ( onError ) { - - onError( e ); - - } - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - }; - - const loader = new FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - 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 ); - - } - - setDRACOLoader( dracoLoader ) { - - this.dracoLoader = dracoLoader; - return this; - - } - - setDDSLoader() { - - throw new Error( - - 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' - - ); - - } - - setKTX2Loader( ktx2Loader ) { - - this.ktx2Loader = ktx2Loader; - return this; - - } - - setMeshoptDecoder( meshoptDecoder ) { - - this.meshoptDecoder = meshoptDecoder; - return this; - - } - - register( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - } - - unregister( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - } - - parse( data, path, onLoad, onError ) { - - let json; - const extensions = {}; - const plugins = {}; - const textDecoder = new TextDecoder(); - - if ( typeof data === 'string' ) { - - json = JSON.parse( data ); - - } else if ( data instanceof ArrayBuffer ) { - - const magic = textDecoder.decode( 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; - - } - - json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); - - } else { - - json = JSON.parse( textDecoder.decode( data ) ); - - } - - } else { - - json = data; - - } - - if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { - - if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); - return; - - } - - const parser = new GLTFParser( json, { - - path: path || this.resourcePath || '', - crossOrigin: this.crossOrigin, - requestHeader: this.requestHeader, - manager: this.manager, - ktx2Loader: this.ktx2Loader, - meshoptDecoder: this.meshoptDecoder - - } ); - - parser.fileLoader.setRequestHeader( this.requestHeader ); - - for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { - - const plugin = this.pluginCallbacks[ i ]( parser ); - - if ( ! plugin.name ) ; - - plugins[ plugin.name ] = plugin; - - // Workaround to avoid determining as unknown extension - // in addUnknownExtensionsToUserData(). - // Remove this workaround if we move all the existing - // extension handlers to plugin system - extensions[ plugin.name ] = true; - - } - - if ( json.extensionsUsed ) { - - for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { - - const extensionName = json.extensionsUsed[ i ]; - const extensionsRequired = json.extensionsRequired || []; - - switch ( extensionName ) { - - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; - - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; - - case EXTENSIONS.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension(); - break; - - case EXTENSIONS.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); - break; - - default: - - if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) ; - - } - - } - - } - - parser.setExtensions( extensions ); - parser.setPlugins( plugins ); - parser.parse( onLoad, onError ); - - } - - parseAsync( data, path ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.parse( data, path, resolve, reject ); - - } ); - - } - -} - -/* GLTFREGISTRY */ - -function GLTFRegistry() { - - let 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 ***********/ -/*********************************/ - -const EXTENSIONS = { - KHR_BINARY_GLTF: 'KHR_binary_glTF', - KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', - KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', - KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', - KHR_MATERIALS_IOR: 'KHR_materials_ior', - KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', - KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', - KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', - KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', - KHR_MATERIALS_ANISOTROPY: 'KHR_materials_anisotropy', - KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', - KHR_MATERIALS_VOLUME: 'KHR_materials_volume', - KHR_TEXTURE_BASISU: 'KHR_texture_basisu', - KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', - KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', - KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength', - EXT_MATERIALS_BUMP: 'EXT_materials_bump', - EXT_TEXTURE_WEBP: 'EXT_texture_webp', - EXT_TEXTURE_AVIF: 'EXT_texture_avif', - EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', - EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing' -}; - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ -class GLTFLightsExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; - - // Object3D instance caches - this.cache = { refs: {}, uses: {} }; - - } - - _markDefs() { - - const parser = this.parser; - const nodeDefs = this.parser.json.nodes || []; - - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.extensions - && nodeDef.extensions[ this.name ] - && nodeDef.extensions[ this.name ].light !== undefined ) { - - parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); - - } - - } - - } - - _loadLight( lightIndex ) { - - const parser = this.parser; - const cacheKey = 'light:' + lightIndex; - let dependency = parser.cache.get( cacheKey ); - - if ( dependency ) return dependency; - - const json = parser.json; - const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; - const lightDefs = extensions.lights || []; - const lightDef = lightDefs[ lightIndex ]; - let lightNode; - - const color = new Color( 0xffffff ); - - if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], LinearSRGBColorSpace ); - - const range = lightDef.range !== undefined ? lightDef.range : 0; - - switch ( lightDef.type ) { - - case 'directional': - lightNode = new DirectionalLight( color ); - lightNode.target.position.set( 0, 0, - 1 ); - lightNode.add( lightNode.target ); - break; - - case 'point': - lightNode = new PointLight( color ); - lightNode.distance = range; - break; - - case 'spot': - lightNode = new 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; - - assignExtrasToUserData( lightNode, lightDef ); - - if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; - - lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); - - dependency = Promise.resolve( lightNode ); - - parser.cache.add( cacheKey, dependency ); - - return dependency; - - } - - getDependency( type, index ) { - - if ( type !== 'light' ) return; - - return this._loadLight( index ); - - } - - createNodeAttachment( nodeIndex ) { - - const self = this; - const parser = this.parser; - const json = parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; - const lightIndex = lightDef.light; - - if ( lightIndex === undefined ) return null; - - return this._loadLight( lightIndex ).then( function ( light ) { - - return parser._getNodeRef( self.cache, lightIndex, light ); - - } ); - - } - -} - -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ -class GLTFMaterialsUnlitExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; - - } - - getMaterialType() { - - return MeshBasicMaterial; - - } - - extendParams( materialParams, materialDef, parser ) { - - const pending = []; - - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - const metallicRoughness = materialDef.pbrMetallicRoughness; - - if ( metallicRoughness ) { - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, SRGBColorSpace ) ); - - } - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - */ -class GLTFMaterialsEmissiveStrengthExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const emissiveStrength = materialDef.extensions[ this.name ].emissiveStrength; - - if ( emissiveStrength !== undefined ) { - - materialParams.emissiveIntensity = emissiveStrength; - - } - - return Promise.resolve(); - - } - -} - -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - */ -class GLTFMaterialsClearcoatExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.clearcoatFactor !== undefined ) { - - materialParams.clearcoat = extension.clearcoatFactor; - - } - - if ( extension.clearcoatTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); - - } - - if ( extension.clearcoatRoughnessFactor !== undefined ) { - - materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; - - } - - if ( extension.clearcoatRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); - - } - - if ( extension.clearcoatNormalTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); - - if ( extension.clearcoatNormalTexture.scale !== undefined ) { - - const scale = extension.clearcoatNormalTexture.scale; - - materialParams.clearcoatNormalScale = new Vector2( scale, scale ); - - } - - } - - return Promise.all( pending ); - - } - -} - -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - */ -class GLTFMaterialsIridescenceExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.iridescenceFactor !== undefined ) { - - materialParams.iridescence = extension.iridescenceFactor; - - } - - if ( extension.iridescenceTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); - - } - - if ( extension.iridescenceIor !== undefined ) { - - materialParams.iridescenceIOR = extension.iridescenceIor; - - } - - if ( materialParams.iridescenceThicknessRange === undefined ) { - - materialParams.iridescenceThicknessRange = [ 100, 400 ]; - - } - - if ( extension.iridescenceThicknessMinimum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; - - } - - if ( extension.iridescenceThicknessMaximum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; - - } - - if ( extension.iridescenceThicknessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - */ -class GLTFMaterialsSheenExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - materialParams.sheenColor = new Color( 0, 0, 0 ); - materialParams.sheenRoughness = 0; - materialParams.sheen = 1; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.sheenColorFactor !== undefined ) { - - const colorFactor = extension.sheenColorFactor; - materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], LinearSRGBColorSpace ); - - } - - if ( extension.sheenRoughnessFactor !== undefined ) { - - materialParams.sheenRoughness = extension.sheenRoughnessFactor; - - } - - if ( extension.sheenColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, SRGBColorSpace ) ); - - } - - if ( extension.sheenRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - * Draft: https://github.com/KhronosGroup/glTF/pull/1698 - */ -class GLTFMaterialsTransmissionExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.transmissionFactor !== undefined ) { - - materialParams.transmission = extension.transmissionFactor; - - } - - if ( extension.transmissionTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ -class GLTFMaterialsVolumeExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; - - if ( extension.thicknessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); - - } - - materialParams.attenuationDistance = extension.attenuationDistance || Infinity; - - const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; - materialParams.attenuationColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); - - return Promise.all( pending ); - - } - -} - -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - */ -class GLTFMaterialsIorExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IOR; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const extension = materialDef.extensions[ this.name ]; - - materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; - - return Promise.resolve(); - - } - -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - */ -class GLTFMaterialsSpecularExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; - - if ( extension.specularTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); - - } - - const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; - materialParams.specularColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); - - if ( extension.specularColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, SRGBColorSpace ) ); - - } - - return Promise.all( pending ); - - } - -} - - -/** - * Materials bump Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump - */ -class GLTFMaterialsBumpExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_MATERIALS_BUMP; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - materialParams.bumpScale = extension.bumpFactor !== undefined ? extension.bumpFactor : 1.0; - - if ( extension.bumpTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'bumpMap', extension.bumpTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials anisotropy Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy - */ -class GLTFMaterialsAnisotropyExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; - - } - - getMaterialType( materialIndex ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; - - return MeshPhysicalMaterial; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const parser = this.parser; - const materialDef = parser.json.materials[ materialIndex ]; - - if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { - - return Promise.resolve(); - - } - - const pending = []; - - const extension = materialDef.extensions[ this.name ]; - - if ( extension.anisotropyStrength !== undefined ) { - - materialParams.anisotropy = extension.anisotropyStrength; - - } - - if ( extension.anisotropyRotation !== undefined ) { - - materialParams.anisotropyRotation = extension.anisotropyRotation; - - } - - if ( extension.anisotropyTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'anisotropyMap', extension.anisotropyTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * BasisU Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu - */ -class GLTFTextureBasisUExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_TEXTURE_BASISU; - - } - - loadTexture( textureIndex ) { - - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ this.name ]; - const loader = parser.options.ktx2Loader; - - if ( ! loader ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); - - } else { - - // Assumes that the extension is optional and that a fallback texture is present - return null; - - } - - } - - return parser.loadTextureImage( textureIndex, extension.source, loader ); - - } - -} - -/** - * WebP Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp - */ -class GLTFTextureWebPExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_WEBP; - this.isSupported = null; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. Support for lossy images doesn't guarantee support for all - // WebP images, unfortunately. - image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; - - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; - - } - -} - -/** - * AVIF Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif - */ -class GLTFTextureAVIFExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_AVIF; - this.isSupported = null; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: AVIF required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. - image.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkQAAAAB8dSLfI='; - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; - - } - -} - -/** - * meshopt BufferView Compression Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression - */ -class GLTFMeshoptCompression { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; - this.parser = parser; - - } - - loadBufferView( index ) { - - const json = this.parser.json; - const bufferView = json.bufferViews[ index ]; - - if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { - - const extensionDef = bufferView.extensions[ this.name ]; - - const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); - const decoder = this.parser.options.meshoptDecoder; - - if ( ! decoder || ! decoder.supported ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); - - } else { - - // Assumes that the extension is optional and that fallback buffer data is present - return null; - - } - - } - - return buffer.then( function ( res ) { - - const byteOffset = extensionDef.byteOffset || 0; - const byteLength = extensionDef.byteLength || 0; - - const count = extensionDef.count; - const stride = extensionDef.byteStride; - - const source = new Uint8Array( res, byteOffset, byteLength ); - - if ( decoder.decodeGltfBufferAsync ) { - - return decoder.decodeGltfBufferAsync( count, stride, source, extensionDef.mode, extensionDef.filter ).then( function ( res ) { - - return res.buffer; - - } ); - - } else { - - // Support for MeshoptDecoder 0.18 or earlier, without decodeGltfBufferAsync - return decoder.ready.then( function () { - - const result = new ArrayBuffer( count * stride ); - decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); - return result; - - } ); - - } - - } ); - - } else { - - return null; - - } - - } - -} - -/** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - * - */ -class GLTFMeshGpuInstancing { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; - this.parser = parser; - - } - - createNodeMesh( nodeIndex ) { - - const json = this.parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] || - nodeDef.mesh === undefined ) { - - return null; - - } - - const meshDef = json.meshes[ nodeDef.mesh ]; - - // No Points or Lines + Instancing support yet - - for ( const primitive of meshDef.primitives ) { - - if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && - primitive.mode !== undefined ) { - - return null; - - } - - } - - const extensionDef = nodeDef.extensions[ this.name ]; - const attributesDef = extensionDef.attributes; - - // @TODO: Can we support InstancedMesh + SkinnedMesh? - - const pending = []; - const attributes = {}; - - for ( const key in attributesDef ) { - - pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => { - - attributes[ key ] = accessor; - return attributes[ key ]; - - } ) ); - - } - - if ( pending.length < 1 ) { - - return null; - - } - - pending.push( this.parser.createNodeMesh( nodeIndex ) ); - - return Promise.all( pending ).then( results => { - - const nodeObject = results.pop(); - const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ]; - const count = results[ 0 ].count; // All attribute counts should be same - const instancedMeshes = []; - - for ( const mesh of meshes ) { - - // Temporal variables - const m = new Matrix4(); - const p = new Vector3(); - const q = new Quaternion(); - const s = new Vector3( 1, 1, 1 ); - - const instancedMesh = new InstancedMesh( mesh.geometry, mesh.material, count ); - - for ( let i = 0; i < count; i ++ ) { - - if ( attributes.TRANSLATION ) { - - p.fromBufferAttribute( attributes.TRANSLATION, i ); - - } - - if ( attributes.ROTATION ) { - - q.fromBufferAttribute( attributes.ROTATION, i ); - - } - - if ( attributes.SCALE ) { - - s.fromBufferAttribute( attributes.SCALE, i ); - - } - - instancedMesh.setMatrixAt( i, m.compose( p, q, s ) ); - - } - - // Add instance attributes to the geometry, excluding TRS. - for ( const attributeName in attributes ) { - - if ( attributeName === '_COLOR_0' ) { - - const attr = attributes[ attributeName ]; - instancedMesh.instanceColor = new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ); - - } else if ( attributeName !== 'TRANSLATION' && - attributeName !== 'ROTATION' && - attributeName !== 'SCALE' ) { - - mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] ); - - } - - } - - // Just in case - Object3D.prototype.copy.call( instancedMesh, mesh ); - - this.parser.assignFinalMaterial( instancedMesh ); - - instancedMeshes.push( instancedMesh ); - - } - - if ( nodeObject.isGroup ) { - - nodeObject.clear(); - - nodeObject.add( ... instancedMeshes ); - - return nodeObject; - - } - - return instancedMeshes[ 0 ]; - - } ); - - } - -} - -/* BINARY EXTENSION */ -const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; -const BINARY_EXTENSION_HEADER_LENGTH = 12; -const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; - -class GLTFBinaryExtension { - - constructor( data ) { - - this.name = EXTENSIONS.KHR_BINARY_GLTF; - this.content = null; - this.body = null; - - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); - const textDecoder = new TextDecoder(); - - this.header = { - magic: textDecoder.decode( 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.' ); - - } - - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - let chunkIndex = 0; - - while ( chunkIndex < chunkContentsLength ) { - - const chunkLength = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - const chunkType = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); - this.content = textDecoder.decode( contentArray ); - - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - - const 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/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression - */ -class GLTFDracoMeshCompressionExtension { - - constructor( 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; - this.dracoLoader.preload(); - - } - - decodePrimitive( primitive, parser ) { - - const json = this.json; - const dracoLoader = this.dracoLoader; - const bufferViewIndex = primitive.extensions[ this.name ].bufferView; - const gltfAttributeMap = primitive.extensions[ this.name ].attributes; - const threeAttributeMap = {}; - const attributeNormalizedMap = {}; - const attributeTypeMap = {}; - - for ( const attributeName in gltfAttributeMap ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; - - } - - for ( const attributeName in primitive.attributes ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - if ( gltfAttributeMap[ attributeName ] !== undefined ) { - - const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - attributeTypeMap[ threeAttributeName ] = componentType.name; - attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; - - } - - } - - return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - - return new Promise( function ( resolve, reject ) { - - dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - - for ( const attributeName in geometry.attributes ) { - - const attribute = geometry.attributes[ attributeName ]; - const normalized = attributeNormalizedMap[ attributeName ]; - - if ( normalized !== undefined ) attribute.normalized = normalized; - - } - - resolve( geometry ); - - }, threeAttributeMap, attributeTypeMap, LinearSRGBColorSpace, reject ); - - } ); - - } ); - - } - -} - -/** - * Texture Transform Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform - */ -class GLTFTextureTransformExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; - - } - - extendTexture( texture, transform ) { - - if ( ( transform.texCoord === undefined || transform.texCoord === texture.channel ) - && transform.offset === undefined - && transform.rotation === undefined - && transform.scale === undefined ) { - - // See https://github.com/mrdoob/three.js/issues/21819. - return texture; - - } - - texture = texture.clone(); - - if ( transform.texCoord !== undefined ) { - - texture.channel = transform.texCoord; - - } - - 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 ); - - } - - texture.needsUpdate = true; - - return texture; - - } - -} - -/** - * Mesh Quantization Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization - */ -class GLTFMeshQuantizationExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; - - } - -} - -/*********************************/ -/********** INTERPOLATION ********/ -/*********************************/ - -// Spline Interpolation -// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation -class GLTFCubicSplineInterpolant extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - copySampleValue_( index ) { - - // Copies a sample value to the result buffer. See description of glTF - // CUBICSPLINE values layout in interpolate_() function below. - - const result = this.resultBuffer, - values = this.sampleValues, - valueSize = this.valueSize, - offset = index * valueSize * 3 + valueSize; - - for ( let i = 0; i !== valueSize; i ++ ) { - - result[ i ] = values[ offset + i ]; - - } - - return result; - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer; - const values = this.sampleValues; - const stride = this.valueSize; - - const stride2 = stride * 2; - const stride3 = stride * 3; - - const td = t1 - t0; - - const p = ( t - t0 ) / td; - const pp = p * p; - const ppp = pp * p; - - const offset1 = i1 * stride3; - const offset0 = offset1 - stride3; - - const s2 = - 2 * ppp + 3 * pp; - const s3 = ppp - pp; - const s0 = 1 - s2; - const s1 = s3 - pp + p; - - // Layout of keyframe output values for CUBICSPLINE animations: - // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] - for ( let i = 0; i !== stride; i ++ ) { - - const p0 = values[ offset0 + i + stride ]; // splineVertex_k - const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) - const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 - const 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; - - } - -} - -const _q = new Quaternion(); - -class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { - - interpolate_( i1, t0, t, t1 ) { - - const result = super.interpolate_( i1, t0, t, t1 ); - - _q.fromArray( result ).normalize().toArray( result ); - - return result; - - } - -} - - -/*********************************/ -/********** INTERNALS ************/ -/*********************************/ - -/* CONSTANTS */ - -const 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 -}; - -const WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array -}; - -const WEBGL_FILTERS = { - 9728: NearestFilter, - 9729: LinearFilter, - 9984: NearestMipmapNearestFilter, - 9985: LinearMipmapNearestFilter, - 9986: NearestMipmapLinearFilter, - 9987: LinearMipmapLinearFilter -}; - -const WEBGL_WRAPPINGS = { - 33071: ClampToEdgeWrapping, - 33648: MirroredRepeatWrapping, - 10497: RepeatWrapping -}; - -const WEBGL_TYPE_SIZES = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 -}; - -const ATTRIBUTES = { - POSITION: 'position', - NORMAL: 'normal', - TANGENT: 'tangent', - TEXCOORD_0: 'uv', - TEXCOORD_1: 'uv1', - TEXCOORD_2: 'uv2', - TEXCOORD_3: 'uv3', - COLOR_0: 'color', - WEIGHTS_0: 'skinWeight', - JOINTS_0: 'skinIndex', -}; - -const PATH_PROPERTIES = { - scale: 'scale', - translation: 'position', - rotation: 'quaternion', - weights: 'morphTargetInfluences' -}; - -const 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: InterpolateLinear, - STEP: InterpolateDiscrete -}; - -const ALPHA_MODES = { - OPAQUE: 'OPAQUE', - MASK: 'MASK', - BLEND: 'BLEND' -}; - -/** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material - */ -function createDefaultMaterial( cache ) { - - if ( cache[ 'DefaultMaterial' ] === undefined ) { - - cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { - color: 0xFFFFFF, - emissive: 0x000000, - metalness: 1, - roughness: 1, - transparent: false, - depthTest: true, - side: FrontSide - } ); - - } - - return cache[ 'DefaultMaterial' ]; - -} - -function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { - - // Add unknown glTF extensions to an object's userData. - - for ( const name in objectDef.extensions ) { - - if ( knownExtensions[ name ] === undefined ) { - - object.userData.gltfExtensions = object.userData.gltfExtensions || {}; - object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; - - } - - } - -} - -/** - * @param {Object3D|Material|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 ); - - } - - } - -} - -/** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets - * - * @param {BufferGeometry} geometry - * @param {Array} targets - * @param {GLTFParser} parser - * @return {Promise} - */ -function addMorphTargets( geometry, targets, parser ) { - - let hasMorphPosition = false; - let hasMorphNormal = false; - let hasMorphColor = false; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) hasMorphPosition = true; - if ( target.NORMAL !== undefined ) hasMorphNormal = true; - if ( target.COLOR_0 !== undefined ) hasMorphColor = true; - - if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; - - } - - if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); - - const pendingPositionAccessors = []; - const pendingNormalAccessors = []; - const pendingColorAccessors = []; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( hasMorphPosition ) { - - const pendingAccessor = target.POSITION !== undefined - ? parser.getDependency( 'accessor', target.POSITION ) - : geometry.attributes.position; - - pendingPositionAccessors.push( pendingAccessor ); - - } - - if ( hasMorphNormal ) { - - const pendingAccessor = target.NORMAL !== undefined - ? parser.getDependency( 'accessor', target.NORMAL ) - : geometry.attributes.normal; - - pendingNormalAccessors.push( pendingAccessor ); - - } - - if ( hasMorphColor ) { - - const pendingAccessor = target.COLOR_0 !== undefined - ? parser.getDependency( 'accessor', target.COLOR_0 ) - : geometry.attributes.color; - - pendingColorAccessors.push( pendingAccessor ); - - } - - } - - return Promise.all( [ - Promise.all( pendingPositionAccessors ), - Promise.all( pendingNormalAccessors ), - Promise.all( pendingColorAccessors ) - ] ).then( function ( accessors ) { - - const morphPositions = accessors[ 0 ]; - const morphNormals = accessors[ 1 ]; - const morphColors = accessors[ 2 ]; - - if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; - if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; - if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; - geometry.morphTargetsRelative = true; - - return geometry; - - } ); - -} - -/** - * @param {Mesh} mesh - * @param {GLTF.Mesh} meshDef - */ -function updateMorphTargets( mesh, meshDef ) { - - mesh.updateMorphTargets(); - - if ( meshDef.weights !== undefined ) { - - for ( let 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 ) ) { - - const targetNames = meshDef.extras.targetNames; - - if ( mesh.morphTargetInfluences.length === targetNames.length ) { - - mesh.morphTargetDictionary = {}; - - for ( let i = 0, il = targetNames.length; i < il; i ++ ) { - - mesh.morphTargetDictionary[ targetNames[ i ] ] = i; - - } - - } - - } - -} - -function createPrimitiveKey( primitiveDef ) { - - let geometryKey; - - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; - - if ( dracoExtension ) { - - geometryKey = 'draco:' + dracoExtension.bufferView - + ':' + dracoExtension.indices - + ':' + createAttributesKey( dracoExtension.attributes ); - - } else { - - geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; - - } - - if ( primitiveDef.targets !== undefined ) { - - for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - - geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); - - } - - } - - return geometryKey; - -} - -function createAttributesKey( attributes ) { - - let attributesKey = ''; - - const keys = Object.keys( attributes ).sort(); - - for ( let i = 0, il = keys.length; i < il; i ++ ) { - - attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; - - } - - return attributesKey; - -} - -function getNormalizedComponentScale( constructor ) { - - // Reference: - // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data - - switch ( constructor ) { - - case Int8Array: - return 1 / 127; - - case Uint8Array: - return 1 / 255; - - case Int16Array: - return 1 / 32767; - - case Uint16Array: - return 1 / 65535; - - default: - throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); - - } - -} - -function getImageURIMimeType( uri ) { - - if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; - if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; - - return 'image/png'; - -} - -const _identityMatrix = new Matrix4(); - -/* GLTF PARSER */ - -class GLTFParser { - - constructor( json = {}, options = {} ) { - - this.json = json; - this.extensions = {}; - this.plugins = {}; - this.options = options; - - // loader object cache - this.cache = new GLTFRegistry(); - - // associations between Three.js objects and glTF elements - this.associations = new Map(); - - // BufferGeometry caching - this.primitiveCache = {}; - - // Node cache - this.nodeCache = {}; - - // Object3D instance caches - this.meshCache = { refs: {}, uses: {} }; - this.cameraCache = { refs: {}, uses: {} }; - this.lightCache = { refs: {}, uses: {} }; - - this.sourceCache = {}; - this.textureCache = {}; - - // Track node names, to ensure no duplicates - this.nodeNamesUsed = {}; - - // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the - // expensive work of uploading a texture to the GPU off the main thread. - - let isSafari = false; - let isFirefox = false; - let firefoxVersion = - 1; - - if ( typeof navigator !== 'undefined' ) { - - isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true; - isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1; - firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; - - } - - if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) { - - this.textureLoader = new TextureLoader( this.options.manager ); - - } else { - - this.textureLoader = new ImageBitmapLoader( this.options.manager ); - - } - - this.textureLoader.setCrossOrigin( this.options.crossOrigin ); - this.textureLoader.setRequestHeader( this.options.requestHeader ); - - this.fileLoader = new FileLoader( this.options.manager ); - this.fileLoader.setResponseType( 'arraybuffer' ); - - if ( this.options.crossOrigin === 'use-credentials' ) { - - this.fileLoader.setWithCredentials( true ); - - } - - } - - setExtensions( extensions ) { - - this.extensions = extensions; - - } - - setPlugins( plugins ) { - - this.plugins = plugins; - - } - - parse( onLoad, onError ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - // Clear the loader cache - this.cache.removeAll(); - this.nodeCache = {}; - - // Mark the special nodes/meshes in json for efficient parse - this._invokeAll( function ( ext ) { - - return ext._markDefs && ext._markDefs(); - - } ); - - Promise.all( this._invokeAll( function ( ext ) { - - return ext.beforeRoot && ext.beforeRoot(); - - } ) ).then( function () { - - return Promise.all( [ - - parser.getDependencies( 'scene' ), - parser.getDependencies( 'animation' ), - parser.getDependencies( 'camera' ), - - ] ); - - } ).then( function ( dependencies ) { - - const 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 ); - - assignExtrasToUserData( result, json ); - - return Promise.all( parser._invokeAll( function ( ext ) { - - return ext.afterRoot && ext.afterRoot( result ); - - } ) ).then( function () { - - onLoad( result ); - - } ); - - } ).catch( onError ); - - } - - /** - * Marks the special nodes/meshes in json for efficient parse. - */ - _markDefs() { - - const nodeDefs = this.json.nodes || []; - const skinDefs = this.json.skins || []; - const meshDefs = this.json.meshes || []; - - // Nothing in the node definition indicates whether it is a Bone or an - // Object3D. Use the skins' joint references to mark bones. - for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { - - const joints = skinDefs[ skinIndex ].joints; - - for ( let i = 0, il = joints.length; i < il; i ++ ) { - - nodeDefs[ joints[ i ] ].isBone = true; - - } - - } - - // Iterate over all nodes, marking references to shared resources, - // as well as skeleton joints. - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.mesh !== undefined ) { - - this._addNodeRef( this.meshCache, 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; - - } - - } - - if ( nodeDef.camera !== undefined ) { - - this._addNodeRef( this.cameraCache, nodeDef.camera ); - - } - - } - - } - - /** - * Counts references to shared node / Object3D resources. These resources - * can be reused, or "instantiated", at multiple nodes in the scene - * hierarchy. Mesh, Camera, and Light instances are instantiated and must - * be marked. Non-scenegraph resources (like Materials, Geometries, and - * Textures) can be reused directly and are not marked here. - * - * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. - */ - _addNodeRef( cache, index ) { - - if ( index === undefined ) return; - - if ( cache.refs[ index ] === undefined ) { - - cache.refs[ index ] = cache.uses[ index ] = 0; - - } - - cache.refs[ index ] ++; - - } - - /** Returns a reference to a shared resource, cloning it if necessary. */ - _getNodeRef( cache, index, object ) { - - if ( cache.refs[ index ] <= 1 ) return object; - - const ref = object.clone(); - - // Propagates mappings to the cloned object, prevents mappings on the - // original object from being lost. - const updateMappings = ( original, clone ) => { - - const mappings = this.associations.get( original ); - if ( mappings != null ) { - - this.associations.set( clone, mappings ); - - } - - for ( const [ i, child ] of original.children.entries() ) { - - updateMappings( child, clone.children[ i ] ); - - } - - }; - - updateMappings( object, ref ); - - ref.name += '_instance_' + ( cache.uses[ index ] ++ ); - - return ref; - - } - - _invokeOne( func ) { - - const extensions = Object.values( this.plugins ); - extensions.push( this ); - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) return result; - - } - - return null; - - } - - _invokeAll( func ) { - - const extensions = Object.values( this.plugins ); - extensions.unshift( this ); - - const pending = []; - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) pending.push( result ); - - } - - return pending; - - } - - /** - * Requests the specified dependency asynchronously, with caching. - * @param {string} type - * @param {number} index - * @return {Promise} - */ - getDependency( type, index ) { - - const cacheKey = type + ':' + index; - let dependency = this.cache.get( cacheKey ); - - if ( ! dependency ) { - - switch ( type ) { - - case 'scene': - dependency = this.loadScene( index ); - break; - - case 'node': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadNode && ext.loadNode( index ); - - } ); - break; - - case 'mesh': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMesh && ext.loadMesh( index ); - - } ); - break; - - case 'accessor': - dependency = this.loadAccessor( index ); - break; - - case 'bufferView': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadBufferView && ext.loadBufferView( index ); - - } ); - break; - - case 'buffer': - dependency = this.loadBuffer( index ); - break; - - case 'material': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMaterial && ext.loadMaterial( index ); - - } ); - break; - - case 'texture': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadTexture && ext.loadTexture( index ); - - } ); - break; - - case 'skin': - dependency = this.loadSkin( index ); - break; - - case 'animation': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadAnimation && ext.loadAnimation( index ); - - } ); - break; - - case 'camera': - dependency = this.loadCamera( index ); - break; - - default: - dependency = this._invokeOne( function ( ext ) { - - return ext != this && ext.getDependency && ext.getDependency( type, index ); - - } ); - - if ( ! dependency ) { - - throw new Error( 'Unknown type: ' + type ); - - } - - break; - - } - - this.cache.add( cacheKey, dependency ); - - } - - return dependency; - - } - - /** - * Requests all dependencies of the specified type asynchronously, with caching. - * @param {string} type - * @return {Promise>} - */ - getDependencies( type ) { - - let dependencies = this.cache.get( type ); - - if ( ! dependencies ) { - - const parser = this; - const 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} - */ - loadBuffer( bufferIndex ) { - - const bufferDef = this.json.buffers[ bufferIndex ]; - const 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 ); - - } - - const options = this.options; - - return new Promise( function ( resolve, reject ) { - - loader.load( LoaderUtils.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} - */ - loadBufferView( bufferViewIndex ) { - - const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; - - return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - - const byteLength = bufferViewDef.byteLength || 0; - const 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} - */ - loadAccessor( accessorIndex ) { - - const parser = this; - const json = this.json; - - const accessorDef = this.json.accessors[ accessorIndex ]; - - if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - const normalized = accessorDef.normalized === true; - - const array = new TypedArray( accessorDef.count * itemSize ); - return Promise.resolve( new BufferAttribute( array, itemSize, normalized ) ); - - } - - const 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 ) { - - const bufferView = bufferViews[ 0 ]; - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - const elementBytes = TypedArray.BYTES_PER_ELEMENT; - const itemBytes = elementBytes * itemSize; - const byteOffset = accessorDef.byteOffset || 0; - const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; - const normalized = accessorDef.normalized === true; - let 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 - const ibSlice = Math.floor( byteOffset / byteStride ); - const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; - let 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 InterleavedBuffer( array, byteStride / elementBytes ); - - parser.cache.add( ibCacheKey, ib ); - - } - - bufferAttribute = new 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 BufferAttribute( array, itemSize, normalized ); - - } - - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors - if ( accessorDef.sparse !== undefined ) { - - const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; - - const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; - const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; - - const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); - const 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 = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); - - } - - for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { - - const 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} - */ - loadTexture( textureIndex ) { - - const json = this.json; - const options = this.options; - const textureDef = json.textures[ textureIndex ]; - const sourceIndex = textureDef.source; - const sourceDef = json.images[ sourceIndex ]; - - let loader = this.textureLoader; - - if ( sourceDef.uri ) { - - const handler = options.manager.getHandler( sourceDef.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.loadTextureImage( textureIndex, sourceIndex, loader ); - - } - - loadTextureImage( textureIndex, sourceIndex, loader ) { - - const parser = this; - const json = this.json; - - const textureDef = json.textures[ textureIndex ]; - const sourceDef = json.images[ sourceIndex ]; - - const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; - - if ( this.textureCache[ cacheKey ] ) { - - // See https://github.com/mrdoob/three.js/issues/21559. - return this.textureCache[ cacheKey ]; - - } - - const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { - - texture.flipY = false; - - texture.name = textureDef.name || sourceDef.name || ''; - - if ( texture.name === '' && typeof sourceDef.uri === 'string' && sourceDef.uri.startsWith( 'data:image/' ) === false ) { - - texture.name = sourceDef.uri; - - } - - const samplers = json.samplers || {}; - const sampler = samplers[ textureDef.sampler ] || {}; - - texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; - texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; - texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; - texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; - - parser.associations.set( texture, { textures: textureIndex } ); - - return texture; - - } ).catch( function () { - - return null; - - } ); - - this.textureCache[ cacheKey ] = promise; - - return promise; - - } - - loadImageSource( sourceIndex, loader ) { - - const parser = this; - const json = this.json; - const options = this.options; - - if ( this.sourceCache[ sourceIndex ] !== undefined ) { - - return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); - - } - - const sourceDef = json.images[ sourceIndex ]; - - const URL = self.URL || self.webkitURL; - - let sourceURI = sourceDef.uri || ''; - let isObjectURL = false; - - if ( sourceDef.bufferView !== undefined ) { - - // Load binary image data from bufferView, if provided. - - sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { - - isObjectURL = true; - const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; - - } ); - - } else if ( sourceDef.uri === undefined ) { - - throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); - - } - - const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { - - return new Promise( function ( resolve, reject ) { - - let onLoad = resolve; - - if ( loader.isImageBitmapLoader === true ) { - - onLoad = function ( imageBitmap ) { - - const texture = new Texture( imageBitmap ); - texture.needsUpdate = true; - - resolve( texture ); - - }; - - } - - loader.load( LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); - - } ); - - } ).then( function ( texture ) { - - // Clean up resources and configure Texture. - - if ( isObjectURL === true ) { - - URL.revokeObjectURL( sourceURI ); - - } - - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); - - return texture; - - } ).catch( function ( error ) { - throw error; - - } ); - - this.sourceCache[ sourceIndex ] = promise; - return promise; - - } - - /** - * Asynchronously assigns a texture to the given material parameters. - * @param {Object} materialParams - * @param {string} mapName - * @param {Object} mapDef - * @return {Promise} - */ - assignTexture( materialParams, mapName, mapDef, colorSpace ) { - - const parser = this; - - return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { - - if ( ! texture ) return null; - - if ( mapDef.texCoord !== undefined && mapDef.texCoord > 0 ) { - - texture = texture.clone(); - texture.channel = mapDef.texCoord; - - } - - if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; - - if ( transform ) { - - const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); - parser.associations.set( texture, gltfReference ); - - } - - } - - if ( colorSpace !== undefined ) { - - texture.colorSpace = colorSpace; - - } - - materialParams[ mapName ] = texture; - - return 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 accommodate different primitive types, defines, etc. New materials will - * be created if necessary, and reused from a cache. - * @param {Object3D} mesh Mesh, Line, or Points instance. - */ - assignFinalMaterial( mesh ) { - - const geometry = mesh.geometry; - let material = mesh.material; - - const useDerivativeTangents = geometry.attributes.tangent === undefined; - const useVertexColors = geometry.attributes.color !== undefined; - const useFlatShading = geometry.attributes.normal === undefined; - - if ( mesh.isPoints ) { - - const cacheKey = 'PointsMaterial:' + material.uuid; - - let pointsMaterial = this.cache.get( cacheKey ); - - if ( ! pointsMaterial ) { - - pointsMaterial = new PointsMaterial(); - Material.prototype.copy.call( pointsMaterial, material ); - pointsMaterial.color.copy( material.color ); - pointsMaterial.map = material.map; - pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px - - this.cache.add( cacheKey, pointsMaterial ); - - } - - material = pointsMaterial; - - } else if ( mesh.isLine ) { - - const cacheKey = 'LineBasicMaterial:' + material.uuid; - - let lineMaterial = this.cache.get( cacheKey ); - - if ( ! lineMaterial ) { - - lineMaterial = new LineBasicMaterial$1(); - Material.prototype.copy.call( lineMaterial, material ); - lineMaterial.color.copy( material.color ); - lineMaterial.map = material.map; - - this.cache.add( cacheKey, lineMaterial ); - - } - - material = lineMaterial; - - } - - // Clone the material if it will be modified - if ( useDerivativeTangents || useVertexColors || useFlatShading ) { - - let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; - - if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; - if ( useVertexColors ) cacheKey += 'vertex-colors:'; - if ( useFlatShading ) cacheKey += 'flat-shading:'; - - let cachedMaterial = this.cache.get( cacheKey ); - - if ( ! cachedMaterial ) { - - cachedMaterial = material.clone(); - - if ( useVertexColors ) cachedMaterial.vertexColors = true; - if ( useFlatShading ) cachedMaterial.flatShading = true; - - if ( useDerivativeTangents ) { - - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; - if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; - - } - - this.cache.add( cacheKey, cachedMaterial ); - - this.associations.set( cachedMaterial, this.associations.get( material ) ); - - } - - material = cachedMaterial; - - } - - mesh.material = material; - - } - - getMaterialType( /* materialIndex */ ) { - - return MeshStandardMaterial; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials - * @param {number} materialIndex - * @return {Promise} - */ - loadMaterial( materialIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - const materialDef = json.materials[ materialIndex ]; - - let materialType; - const materialParams = {}; - const materialExtensions = materialDef.extensions || {}; - - const pending = []; - - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - - const 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 - - const metallicRoughness = materialDef.pbrMetallicRoughness || {}; - - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, SRGBColorSpace ) ); - - } - - 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 ) ); - - } - - materialType = this._invokeOne( function ( ext ) { - - return ext.getMaterialType && ext.getMaterialType( materialIndex ); - - } ); - - pending.push( Promise.all( this._invokeAll( function ( ext ) { - - return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); - - } ) ) ); - - } - - if ( materialDef.doubleSided === true ) { - - materialParams.side = DoubleSide; - - } - - const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - - if ( alphaMode === ALPHA_MODES.BLEND ) { - - materialParams.transparent = true; - - // See: https://github.com/mrdoob/three.js/issues/17706 - materialParams.depthWrite = false; - - } else { - - materialParams.transparent = false; - - if ( alphaMode === ALPHA_MODES.MASK ) { - - materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; - - } - - } - - if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); - - materialParams.normalScale = new Vector2( 1, 1 ); - - if ( materialDef.normalTexture.scale !== undefined ) { - - const scale = materialDef.normalTexture.scale; - - materialParams.normalScale.set( scale, scale ); - - } - - } - - if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); - - if ( materialDef.occlusionTexture.strength !== undefined ) { - - materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; - - } - - } - - if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { - - const emissiveFactor = materialDef.emissiveFactor; - materialParams.emissive = new Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], LinearSRGBColorSpace ); - - } - - if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, SRGBColorSpace ) ); - - } - - return Promise.all( pending ).then( function () { - - const material = new materialType( materialParams ); - - if ( materialDef.name ) material.name = materialDef.name; - - assignExtrasToUserData( material, materialDef ); - - parser.associations.set( material, { materials: materialIndex } ); - - if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); - - return material; - - } ); - - } - - /** When Object3D instances are targeted by animation, they need unique names. */ - createUniqueName( originalName ) { - - const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); - - if ( sanitizedName in this.nodeNamesUsed ) { - - return sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] ); - - } else { - - this.nodeNamesUsed[ sanitizedName ] = 0; - - return sanitizedName; - - } - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry - * - * Creates BufferGeometries from primitives. - * - * @param {Array} primitives - * @return {Promise>} - */ - loadGeometries( primitives ) { - - const parser = this; - const extensions = this.extensions; - const cache = this.primitiveCache; - - function createDracoPrimitive( primitive ) { - - return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] - .decodePrimitive( primitive, parser ) - .then( function ( geometry ) { - - return addPrimitiveAttributes( geometry, primitive, parser ); - - } ); - - } - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey( primitive ); - - // See if we've already created this geometry - const cached = cache[ cacheKey ]; - - if ( cached ) { - - // Use the cached geometry if it exists - pending.push( cached.promise ); - - } else { - - let 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 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} - */ - loadMesh( meshIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - const meshDef = json.meshes[ meshIndex ]; - const primitives = meshDef.primitives; - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const material = primitives[ i ].material === undefined - ? createDefaultMaterial( this.cache ) - : this.getDependency( 'material', primitives[ i ].material ); - - pending.push( material ); - - } - - pending.push( parser.loadGeometries( primitives ) ); - - return Promise.all( pending ).then( function ( results ) { - - const materials = results.slice( 0, results.length - 1 ); - const geometries = results[ results.length - 1 ]; - - const meshes = []; - - for ( let i = 0, il = geometries.length; i < il; i ++ ) { - - const geometry = geometries[ i ]; - const primitive = primitives[ i ]; - - // 1. create Mesh - - let mesh; - - const material = materials[ 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 SkinnedMesh( geometry, material ) - : new Mesh( geometry, material ); - - if ( mesh.isSkinnedMesh === true ) { - - // normalize skin weights to fix malformed assets (see #15319) - mesh.normalizeSkinWeights(); - - } - - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - - mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - - mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode ); - - } - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { - - mesh = new LineSegments( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { - - mesh = new Line( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { - - mesh = new LineLoop( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { - - mesh = new 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 = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - - assignExtrasToUserData( mesh, meshDef ); - - if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); - - parser.assignFinalMaterial( mesh ); - - meshes.push( mesh ); - - } - - for ( let i = 0, il = meshes.length; i < il; i ++ ) { - - parser.associations.set( meshes[ i ], { - meshes: meshIndex, - primitives: i - } ); - - } - - if ( meshes.length === 1 ) { - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); - - return meshes[ 0 ]; - - } - - const group = new Group$1(); - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); - - parser.associations.set( group, { meshes: meshIndex } ); - - for ( let 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} - */ - loadCamera( cameraIndex ) { - - let camera; - const cameraDef = this.json.cameras[ cameraIndex ]; - const params = cameraDef[ cameraDef.type ]; - - if ( ! params ) { - return; - - } - - if ( cameraDef.type === 'perspective' ) { - - camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); - - } else if ( cameraDef.type === 'orthographic' ) { - - camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); - - } - - if ( cameraDef.name ) camera.name = this.createUniqueName( 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} - */ - loadSkin( skinIndex ) { - - const skinDef = this.json.skins[ skinIndex ]; - - const pending = []; - - for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) { - - pending.push( this._loadNodeShallow( skinDef.joints[ i ] ) ); - - } - - if ( skinDef.inverseBindMatrices !== undefined ) { - - pending.push( this.getDependency( 'accessor', skinDef.inverseBindMatrices ) ); - - } else { - - pending.push( null ); - - } - - return Promise.all( pending ).then( function ( results ) { - - const inverseBindMatrices = results.pop(); - const jointNodes = results; - - // Note that bones (joint nodes) may or may not be in the - // scene graph at this time. - - const bones = []; - const boneInverses = []; - - for ( let i = 0, il = jointNodes.length; i < il; i ++ ) { - - const jointNode = jointNodes[ i ]; - - if ( jointNode ) { - - bones.push( jointNode ); - - const mat = new Matrix4(); - - if ( inverseBindMatrices !== null ) { - - mat.fromArray( inverseBindMatrices.array, i * 16 ); - - } - - boneInverses.push( mat ); - - } - - } - - return new Skeleton( bones, boneInverses ); - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations - * @param {number} animationIndex - * @return {Promise} - */ - loadAnimation( animationIndex ) { - - const json = this.json; - const parser = this; - - const animationDef = json.animations[ animationIndex ]; - const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex; - - const pendingNodes = []; - const pendingInputAccessors = []; - const pendingOutputAccessors = []; - const pendingSamplers = []; - const pendingTargets = []; - - for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { - - const channel = animationDef.channels[ i ]; - const sampler = animationDef.samplers[ channel.sampler ]; - const target = channel.target; - const name = target.node; - const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; - const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; - - if ( target.node === undefined ) continue; - - 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 ) { - - const nodes = dependencies[ 0 ]; - const inputAccessors = dependencies[ 1 ]; - const outputAccessors = dependencies[ 2 ]; - const samplers = dependencies[ 3 ]; - const targets = dependencies[ 4 ]; - - const tracks = []; - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - const node = nodes[ i ]; - const inputAccessor = inputAccessors[ i ]; - const outputAccessor = outputAccessors[ i ]; - const sampler = samplers[ i ]; - const target = targets[ i ]; - - if ( node === undefined ) continue; - - if ( node.updateMatrix ) { - - node.updateMatrix(); - - } - - const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ); - - if ( createdTracks ) { - - for ( let k = 0; k < createdTracks.length; k ++ ) { - - tracks.push( createdTracks[ k ] ); - - } - - } - - } - - return new AnimationClip( animationName, undefined, tracks ); - - } ); - - } - - createNodeMesh( nodeIndex ) { - - const json = this.json; - const parser = this; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( nodeDef.mesh === undefined ) return null; - - return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { - - const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, 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 ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { - - o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; - - } - - } ); - - } - - return node; - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy - * @param {number} nodeIndex - * @return {Promise} - */ - loadNode( nodeIndex ) { - - const json = this.json; - const parser = this; - - const nodeDef = json.nodes[ nodeIndex ]; - - const nodePending = parser._loadNodeShallow( nodeIndex ); - - const childPending = []; - const childrenDef = nodeDef.children || []; - - for ( let i = 0, il = childrenDef.length; i < il; i ++ ) { - - childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) ); - - } - - const skeletonPending = nodeDef.skin === undefined - ? Promise.resolve( null ) - : parser.getDependency( 'skin', nodeDef.skin ); - - return Promise.all( [ - nodePending, - Promise.all( childPending ), - skeletonPending - ] ).then( function ( results ) { - - const node = results[ 0 ]; - const children = results[ 1 ]; - const skeleton = results[ 2 ]; - - if ( skeleton !== null ) { - - // This full traverse should be fine because - // child glTF nodes have not been added to this node yet. - node.traverse( function ( mesh ) { - - if ( ! mesh.isSkinnedMesh ) return; - - mesh.bind( skeleton, _identityMatrix ); - - } ); - - } - - for ( let i = 0, il = children.length; i < il; i ++ ) { - - node.add( children[ i ] ); - - } - - return node; - - } ); - - } - - // ._loadNodeShallow() parses a single node. - // skin and child nodes are created and added in .loadNode() (no '_' prefix). - _loadNodeShallow( nodeIndex ) { - - const json = this.json; - const extensions = this.extensions; - const parser = this; - - // This method is called from .loadNode() and .loadSkin(). - // Cache a node to avoid duplication. - - if ( this.nodeCache[ nodeIndex ] !== undefined ) { - - return this.nodeCache[ nodeIndex ]; - - } - - const nodeDef = json.nodes[ nodeIndex ]; - - // reserve node's name before its dependencies, so the root has the intended name. - const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; - - const pending = []; - - const meshPromise = parser._invokeOne( function ( ext ) { - - return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); - - } ); - - if ( meshPromise ) { - - pending.push( meshPromise ); - - } - - if ( nodeDef.camera !== undefined ) { - - pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { - - return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); - - } ) ); - - } - - parser._invokeAll( function ( ext ) { - - return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); - - } ).forEach( function ( promise ) { - - pending.push( promise ); - - } ); - - this.nodeCache[ nodeIndex ] = Promise.all( pending ).then( function ( objects ) { - - let node; - - // .isBone isn't in glTF spec. See ._markDefs - if ( nodeDef.isBone === true ) { - - node = new Bone(); - - } else if ( objects.length > 1 ) { - - node = new Group$1(); - - } else if ( objects.length === 1 ) { - - node = objects[ 0 ]; - - } else { - - node = new Object3D(); - - } - - if ( node !== objects[ 0 ] ) { - - for ( let i = 0, il = objects.length; i < il; i ++ ) { - - node.add( objects[ i ] ); - - } - - } - - if ( nodeDef.name ) { - - node.userData.name = nodeDef.name; - node.name = nodeName; - - } - - assignExtrasToUserData( node, nodeDef ); - - if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); - - if ( nodeDef.matrix !== undefined ) { - - const matrix = new Matrix4(); - matrix.fromArray( nodeDef.matrix ); - node.applyMatrix4( 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 ); - - } - - } - - if ( ! parser.associations.has( node ) ) { - - parser.associations.set( node, {} ); - - } - - parser.associations.get( node ).nodes = nodeIndex; - - return node; - - } ); - - return this.nodeCache[ nodeIndex ]; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes - * @param {number} sceneIndex - * @return {Promise} - */ - loadScene( sceneIndex ) { - - const extensions = this.extensions; - const sceneDef = this.json.scenes[ sceneIndex ]; - const parser = this; - - // Loader returns Group, not Scene. - // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 - const scene = new Group$1(); - if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - - assignExtrasToUserData( scene, sceneDef ); - - if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); - - const nodeIds = sceneDef.nodes || []; - - const pending = []; - - for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - - pending.push( parser.getDependency( 'node', nodeIds[ i ] ) ); - - } - - return Promise.all( pending ).then( function ( nodes ) { - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - scene.add( nodes[ i ] ); - - } - - // Removes dangling associations, associations that reference a node that - // didn't make it into the scene. - const reduceAssociations = ( node ) => { - - const reducedAssociations = new Map(); - - for ( const [ key, value ] of parser.associations ) { - - if ( key instanceof Material || key instanceof Texture ) { - - reducedAssociations.set( key, value ); - - } - - } - - node.traverse( ( node ) => { - - const mappings = parser.associations.get( node ); - - if ( mappings != null ) { - - reducedAssociations.set( node, mappings ); - - } - - } ); - - return reducedAssociations; - - }; - - parser.associations = reduceAssociations( scene ); - - return scene; - - } ); - - } - - _createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) { - - const tracks = []; - - const targetName = node.name ? node.name : node.uuid; - const targetNames = []; - - if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { - - node.traverse( function ( object ) { - - if ( object.morphTargetInfluences ) { - - targetNames.push( object.name ? object.name : object.uuid ); - - } - - } ); - - } else { - - targetNames.push( targetName ); - - } - - let TypedKeyframeTrack; - - switch ( PATH_PROPERTIES[ target.path ] ) { - - case PATH_PROPERTIES.weights: - - TypedKeyframeTrack = NumberKeyframeTrack; - break; - - case PATH_PROPERTIES.rotation: - - TypedKeyframeTrack = QuaternionKeyframeTrack; - break; - - case PATH_PROPERTIES.position: - case PATH_PROPERTIES.scale: - - TypedKeyframeTrack = VectorKeyframeTrack; - break; - - default: - - switch ( outputAccessor.itemSize ) { - - case 1: - TypedKeyframeTrack = NumberKeyframeTrack; - break; - case 2: - case 3: - default: - TypedKeyframeTrack = VectorKeyframeTrack; - break; - - } - - break; - - } - - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; - - - const outputArray = this._getArrayFromAccessor( outputAccessor ); - - for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { - - const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], - inputAccessor.array, - outputArray, - interpolation - ); - - // Override interpolation with custom factory method. - if ( sampler.interpolation === 'CUBICSPLINE' ) { - - this._createCubicSplineTrackInterpolant( track ); - - } - - tracks.push( track ); - - } - - return tracks; - - } - - _getArrayFromAccessor( accessor ) { - - let outputArray = accessor.array; - - if ( accessor.normalized ) { - - const scale = getNormalizedComponentScale( outputArray.constructor ); - const scaled = new Float32Array( outputArray.length ); - - for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { - - scaled[ j ] = outputArray[ j ] * scale; - - } - - outputArray = scaled; - - } - - return outputArray; - - } - - _createCubicSplineTrackInterpolant( track ) { - - 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. - - const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; - - return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); - - }; - - // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. - track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; - - } - -} - -/** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - */ -function computeBounds( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const box = new Box3(); - - if ( attributes.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ attributes.POSITION ]; - - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - box.set( - new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), - new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) - ); - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - box.min.multiplyScalar( boxScale ); - box.max.multiplyScalar( boxScale ); - - } - - } else { - - return; - - } - - } else { - - return; - - } - - const targets = primitiveDef.targets; - - if ( targets !== undefined ) { - - const maxDisplacement = new Vector3(); - const vector = new Vector3(); - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ target.POSITION ]; - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - // we need to get max of absolute components because target weight is [-1,1] - vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); - vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); - vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); - - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - vector.multiplyScalar( boxScale ); - - } - - // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative - // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets - // are used to implement key-frame animations and as such only two are active at a time - this results in very large - // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. - maxDisplacement.max( vector ); - - } - - } - - } - - // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. - box.expandByVector( maxDisplacement ); - - } - - geometry.boundingBox = box; - - const sphere = new Sphere(); - - box.getCenter( sphere.center ); - sphere.radius = box.min.distanceTo( box.max ) / 2; - - geometry.boundingSphere = sphere; - -} - -/** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - * @return {Promise} - */ -function addPrimitiveAttributes( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const pending = []; - - function assignAttributeAccessor( accessorIndex, attributeName ) { - - return parser.getDependency( 'accessor', accessorIndex ) - .then( function ( accessor ) { - - geometry.setAttribute( attributeName, accessor ); - - } ); - - } - - for ( const gltfAttributeName in attributes ) { - - const 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 ) { - - const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { - - geometry.setIndex( accessor ); - - } ); - - pending.push( accessor ); - - } - - if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) ; - - assignExtrasToUserData( geometry, primitiveDef ); - - computeBounds( geometry, primitiveDef, parser ); - - return Promise.all( pending ).then( function () { - - return primitiveDef.targets !== undefined - ? addMorphTargets( geometry, primitiveDef.targets, parser ) - : geometry; - - } ); - -} - -const _taskCache$1 = new WeakMap(); - -class DRACOLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - this.decoderPath = ''; - this.decoderConfig = {}; - this.decoderBinary = null; - this.decoderPending = null; - - this.workerLimit = 4; - this.workerPool = []; - this.workerNextTaskID = 1; - this.workerSourceURL = ''; - - this.defaultAttributeIDs = { - position: 'POSITION', - normal: 'NORMAL', - color: 'COLOR', - uv: 'TEX_COORD' - }; - this.defaultAttributeTypes = { - position: 'Float32Array', - normal: 'Float32Array', - color: 'Float32Array', - uv: 'Float32Array' - }; - - } - - setDecoderPath( path ) { - - this.decoderPath = path; - - return this; - - } - - setDecoderConfig( config ) { - - this.decoderConfig = config; - - return this; - - } - - setWorkerLimit( workerLimit ) { - - this.workerLimit = workerLimit; - - return this; - - } - - load( url, onLoad, onProgress, onError ) { - - const loader = new FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - loader.load( url, ( buffer ) => { - - this.parse( buffer, onLoad, onError ); - - }, onProgress, onError ); - - } - - - parse( buffer, onLoad, onError = ()=>{} ) { - - this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError ); - - } - - decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, onError = () => {} ) { - - const taskConfig = { - attributeIDs: attributeIDs || this.defaultAttributeIDs, - attributeTypes: attributeTypes || this.defaultAttributeTypes, - useUniqueIDs: !! attributeIDs, - vertexColorSpace: vertexColorSpace, - }; - - return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError ); - - } - - decodeGeometry( buffer, taskConfig ) { - - const taskKey = JSON.stringify( taskConfig ); - - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache$1.has( buffer ) ) { - - const cachedTask = _taskCache$1.get( buffer ); - - if ( cachedTask.key === taskKey ) { - - return cachedTask.promise; - - } else if ( buffer.byteLength === 0 ) { - - // Technically, it would be possible to wait for the previous task to complete, - // transfer the buffer back, and decode again with the second configuration. That - // is complex, and I don't know of any reason to decode a Draco buffer twice in - // different ways, so this is left unimplemented. - throw new Error( - - 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + - 'settings. Buffer has already been transferred.' - - ); - - } - - } - - // - - let worker; - const taskID = this.workerNextTaskID ++; - const taskCost = buffer.byteLength; - - // Obtain a worker and assign a task, and construct a geometry instance - // when the task completes. - const geometryPending = this._getWorker( taskID, taskCost ) - .then( ( _worker ) => { - - worker = _worker; - - return new Promise( ( resolve, reject ) => { - - worker._callbacks[ taskID ] = { resolve, reject }; - - worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); - - // this.debug(); - - } ); - - } ) - .then( ( message ) => this._createGeometry( message.geometry ) ); - - // Remove task from the task list. - // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) - geometryPending - .catch( () => true ) - .then( () => { - - if ( worker && taskID ) { - - this._releaseTask( worker, taskID ); - - // this.debug(); - - } - - } ); - - // Cache the task result. - _taskCache$1.set( buffer, { - - key: taskKey, - promise: geometryPending - - } ); - - return geometryPending; - - } - - _createGeometry( geometryData ) { - - const geometry = new BufferGeometry(); - - if ( geometryData.index ) { - - geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) ); - - } - - for ( let i = 0; i < geometryData.attributes.length; i ++ ) { - - const result = geometryData.attributes[ i ]; - const name = result.name; - const array = result.array; - const itemSize = result.itemSize; - - const attribute = new BufferAttribute( array, itemSize ); - - if ( name === 'color' ) { - - this._assignVertexColorSpace( attribute, result.vertexColorSpace ); - - attribute.normalized = ( array instanceof Float32Array ) === false; - - } - - geometry.setAttribute( name, attribute ); - - } - - return geometry; - - } - - _assignVertexColorSpace( attribute, inputColorSpace ) { - - // While .drc files do not specify colorspace, the only 'official' tooling - // is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc - // file is passed into .load() or .parse(). GLTFLoader uses internal APIs - // to decode geometry, and vertex colors are already Linear-sRGB in there. - - if ( inputColorSpace !== SRGBColorSpace ) return; - - const _color = new Color(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); - attribute.setXYZ( i, _color.r, _color.g, _color.b ); - - } - - } - - _loadLibrary( url, responseType ) { - - const loader = new FileLoader( this.manager ); - loader.setPath( this.decoderPath ); - loader.setResponseType( responseType ); - loader.setWithCredentials( this.withCredentials ); - - return new Promise( ( resolve, reject ) => { - - loader.load( url, resolve, undefined, reject ); - - } ); - - } - - preload() { - - this._initDecoder(); - - return this; - - } - - _initDecoder() { - - if ( this.decoderPending ) return this.decoderPending; - - const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; - const librariesPending = []; - - if ( useJS ) { - - librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); - - } else { - - librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); - librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); - - } - - this.decoderPending = Promise.all( librariesPending ) - .then( ( libraries ) => { - - const jsContent = libraries[ 0 ]; - - if ( ! useJS ) { - - this.decoderConfig.wasmBinary = libraries[ 1 ]; - - } - - const fn = DRACOWorker.toString(); - - const body = [ - '/* draco decoder */', - jsContent, - '', - '/* worker */', - fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) - ].join( '\n' ); - - this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); - - } ); - - return this.decoderPending; - - } - - _getWorker( taskID, taskCost ) { - - return this._initDecoder().then( () => { - - if ( this.workerPool.length < this.workerLimit ) { - - const worker = new Worker( this.workerSourceURL ); - - worker._callbacks = {}; - worker._taskCosts = {}; - worker._taskLoad = 0; - - worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); - - worker.onmessage = function ( e ) { - - const message = e.data; - - switch ( message.type ) { - - case 'decode': - worker._callbacks[ message.id ].resolve( message ); - break; - - case 'error': - worker._callbacks[ message.id ].reject( message ); - break; - - } - - }; - - this.workerPool.push( worker ); - - } else { - - this.workerPool.sort( function ( a, b ) { - - return a._taskLoad > b._taskLoad ? - 1 : 1; - - } ); - - } - - const worker = this.workerPool[ this.workerPool.length - 1 ]; - worker._taskCosts[ taskID ] = taskCost; - worker._taskLoad += taskCost; - return worker; - - } ); - - } - - _releaseTask( worker, taskID ) { - - worker._taskLoad -= worker._taskCosts[ taskID ]; - delete worker._callbacks[ taskID ]; - delete worker._taskCosts[ taskID ]; - - } - - debug() { - - } - - dispose() { - - for ( let i = 0; i < this.workerPool.length; ++ i ) { - - this.workerPool[ i ].terminate(); - - } - - this.workerPool.length = 0; - - if ( this.workerSourceURL !== '' ) { - - URL.revokeObjectURL( this.workerSourceURL ); - - } - - return this; - - } - -} - -/* WEB WORKER */ - -function DRACOWorker() { - - let decoderConfig; - let decoderPending; + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - onmessage = function ( e ) { + // constants for atmospheric scattering + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; - const message = e.data; + // wavelength of used primaries, according to preetham + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); - switch ( message.type ) { + // mie stuff + // K coefficient for the primaries + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); - case 'init': - decoderConfig = message.decoderConfig; - decoderPending = new Promise( function ( resolve/*, reject*/ ) { + // earth shadow hack + // cutoffAngle = pi / 1.95; + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; - decoderConfig.onModuleLoaded = function ( draco ) { + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } - // Module is Promise-like. Wrap before resolving to avoid loop. - resolve( { draco: draco } ); + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } - }; + void main() { - DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - } ); - break; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - case 'decode': - const buffer = message.buffer; - const taskConfig = message.taskConfig; - decoderPending.then( ( module ) => { + vSunDirection = normalize( sunPosition ); - const draco = module.draco; - const decoder = new draco.Decoder(); + vSunE = sunIntensity( dot( vSunDirection, up ) ); - try { + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - const geometry = decodeGeometry( draco, decoder, new Int8Array( buffer ), taskConfig ); + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + // extinction (absorbtion + out scattering) + // rayleigh coefficients + vBetaR = totalRayleigh * rayleighCoefficient; - if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + // mie coefficients + vBetaM = totalMie( turbidity ) * mieCoefficient; - self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + }`, - } catch ( error ) { + fragmentShader: /* glsl */` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - self.postMessage( { type: 'error', id: message.id, error: error.message } ); + uniform float mieDirectionalG; + uniform vec3 up; - } finally { + // constants for atmospheric scattering + const float pi = 3.141592653589793238462643383279502884197169; - draco.destroy( decoder ); + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) - } + // optical length at zenith for molecules + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; + // 66 arc seconds -> degrees, and the cosine of that + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; - } ); - break; + // 3.0 / ( 16.0 * pi ) + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; + // 1.0 / ( 4.0 * pi ) + const float ONE_OVER_FOURPI = 0.07957747154594767; + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); } - }; + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - function decodeGeometry( draco, decoder, array, taskConfig ) { + void main() { - const attributeIDs = taskConfig.attributeIDs; - const attributeTypes = taskConfig.attributeTypes; + vec3 direction = normalize( vWorldPosition - cameraPosition ); - let dracoGeometry; - let decodingStatus; + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; - const geometryType = decoder.GetEncodedGeometryType( array ); + // combined extinction factor + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); - if ( geometryType === draco.TRIANGULAR_MESH ) { + // in scattering + float cosTheta = dot( direction, vSunDirection ); - dracoGeometry = new draco.Mesh(); - decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry ); + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - } else if ( geometryType === draco.POINT_CLOUD ) { + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - dracoGeometry = new draco.PointCloud(); - decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, dracoGeometry ); + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); - } else { + // nightsky + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; - throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + // composition + solar disc + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - } + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + gl_FragColor = vec4( retColor, 1.0 ); - } + #include + #include - const geometry = { index: null, attributes: [] }; + }` - // Gather all vertex attributes. - for ( const attributeName in attributeIDs ) { +}; - const attributeType = self[ attributeTypes[ attributeName ] ]; +// 多个canvas并没有id,只有父节点 - let attribute; - let attributeID; - // A Draco file may be created with default vertex attributes, whose attribute IDs - // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, - // a Draco file may contain a custom set of attributes, identified by known unique - // IDs. glTF files always do the latter, and `.drc` files typically do the former. - if ( taskConfig.useUniqueIDs ) { +class Layer extends BasLayer{ + id; // 唯一标识 + layerContainer; // div#layer 容器 + zIndex=1;//默认为1 + opacity=1;//默认为1 + canvas;//canvas + dispose = false; + renderer;//canvas上下文 + scene;//场景 + visible=true;//是否可见 + mapView;//地图视图 + camera;//相机 + controls;//控件 + animateId;//动画事件id + base = false; // 是否为底图 + ambientLight; // 环境光 + directionalLight; // 方向光 + modelLayer = false; // 模型图层 + imageLayer = false; // 影像图层 + vectorLayer = false; // 矢量图层,如路网、行政区划,地名等图层 + waters = []; // 水面集合 + constructor(id, layerContainer, canvas, mapView, plane = true, camera = new PerspectiveCamera(80, 1, 0.1, 1e12)) { + super(); + this.id = id; + this.layerContainer = layerContainer; + this.canvas = canvas; + this.renderer = new WebGLRenderer({ + canvas: this.canvas, + antialias: true, + alpha: true, + logarithmicDepthBuffer: true, + precision: "highp", + }); + this.renderer.sortObjects = true; + this.renderer.setPixelRatio(window.devicePixelRatio); + this.renderer.setClearColor(0xFFFFFF, 0.0); + this.scene = new Scene(); + this.mapView = mapView; + this.camera = camera; + if(this.mapView){ + this.scene.add(this.mapView); + this.mapView.updateMatrixWorld(true); + } + if (plane){ + this.controls = new MapControls(this.camera, this.canvas); + this.controls.minDistance = 1e1; + this.controls.zoomSpeed = 2.0; + } else { + this.controls = new OrbitControls(this.camera, this.canvas); + this.controls.enablePan = false; + this.controls.minDistance = UnitsUtils.EARTH_RADIUS + 2; + this.controls.maxDistance = UnitsUtils.EARTH_RADIUS * 1e1; + } + this._raycaster = new Raycaster(); + if(Config.outLine.on){ + this.effectOutline = new EffectOutline(this.renderer, this.scene, this.camera, this.canvas.width, this.canvas.height); + } + if (Config.layer.map.ambientLight.add){ + this.scene.add(new AmbientLight(Config.layer.map.ambientLight.color, Config.layer.map.ambientLight.intensity)); + } + if (Config.layer.map.directionalLight.add){ + this.scene.add(new DirectionalLight(Config.layer.map.directionalLight.color, Config.layer.map.directionalLight.intensity)); + } + if (Config.layer.map.pointLight.add){ + let pointLight = new PointLight(Config.layer.map.pointLight.color, Config.layer.map.pointLight.intensity, Config.layer.map.pointLight.distance); + pointLight.position.set(...Config.layer.map.pointLight.position); + this.scene.add(pointLight); + } + } - attributeID = attributeIDs[ attributeName ]; - attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + moveTo(lat, lon, height = 38472.48763833733){ + // var coords = UnitsUtils.datumsToSpherical(44.266119,90.139228); + var coords = UnitsUtils.datumsToSpherical(lat,lon); + this.camera.position.set(coords.x, height, -coords.y); + this.controls.target.set(this.camera.position.x, 0, this.camera.position.z); + } - } else { + moveToByCoords(coords){ + let offset = 50; + this.camera.position.set(coords.x, coords.y+offset, coords.z); + this.controls.target.set(coords.x, coords.y, coords.z); + } - attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + moveToByLL(lat, lon, distance = 384720){ + let dir = UnitsUtils.datumsToVector(lat, lon); + dir.multiplyScalar(UnitsUtils.EARTH_RADIUS + distance); + this.camera.position.copy(dir); + } - if ( attributeID === - 1 ) continue; - attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + on(eventName, callback){ + this.listener.on(eventName, callback); + } - } + setSceneBackground(color) { + this.scene.background = color; + } - const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ); + clearSceneBackground() { + this.scene.background = null; + } - if ( attributeName === 'color' ) { + // 可用于添加灯光mesh等元素 + add(Object3D) { + if(Object3D ==null || Object3D ==undefined){ + return; + } + this.scene.add(Object3D); + } - attributeResult.vertexColorSpace = taskConfig.vertexColorSpace; - } + remove(Object3D) { + if(Object3D ==null || Object3D ==undefined){ + return; + } + this.scene.remove(Object3D); + } - geometry.attributes.push( attributeResult ); + openWaterConfig(){ + /** + * 打开渲染水系配置 + */ + // this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.toneMapping = ACESFilmicToneMapping; + this.renderer.toneMappingExposure = 0.5; + + // 添加天空 + this.sky = new Sky(); + this.sky.translateX = true; + this.sky.translateY = true; + this.sky.translateZ = true; + this.sky.rotateX = Math.PI / 2; + this.sky.scale.setScalar( Config.EARTH_RADIUS * 2 * Math.PI ); // 天空放大倍数 + this.scene.add( this.sky ); + const skyUniforms = this.sky.material.uniforms; + // 天空的配置 + skyUniforms[ 'turbidity' ].value = 10; + skyUniforms[ 'rayleigh' ].value = 2; + skyUniforms[ 'mieCoefficient' ].value = 0.005; + skyUniforms[ 'mieDirectionalG' ].value = 0.8; + // 旋转 设置为y朝上 + // sky.material.uniforms["up"].value = new THREE.Vector3(0, 1, 0); + + // 天空映射, 更新太阳位置 + this.pmremGenerator = new PMREMGenerator( this.renderer ); + this.sceneEnv = new Scene(); + this.renderTarget = null; + this.sun = new Vector3(); + this.updateSun(Config.SUNDEGREE, Config.SUNAZIMUTH); + } - } + updateSun(elevation, azimuth) { - // Add index. - if ( geometryType === draco.TRIANGULAR_MESH ) { + const phi = MathUtils.degToRad( 90 - elevation ); + const theta = MathUtils.degToRad( azimuth ); - geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + this.sun.setFromSphericalCoords( 1, phi, theta ); - } + this.sky.material.uniforms[ 'sunPosition' ].value.copy( this.sun ); + for (let water of this.waters){ + water.material.uniforms[ 'sunDirection' ].value.copy( this.sun ).normalize(); + } + if ( this.renderTarget !== null ) this.renderTarget.dispose(); - draco.destroy( dracoGeometry ); + this.sceneEnv.add( this.sky ); + this.renderTarget = this.pmremGenerator.fromScene( this.sceneEnv ); + this.scene.add( this.sky ); - return geometry; + this.scene.environment = this.renderTarget.texture; - } - function decodeIndex( draco, decoder, dracoGeometry ) { + } - const numFaces = dracoGeometry.num_faces(); - const numIndices = numFaces * 3; - const byteLength = numIndices * 4; + /** + * 添加水系 + * @param {*} water + * @returns + */ + addWater(water) { + if(water ==null || water ==undefined){ + return; + } + this.scene.add(water); + this.waters.push(water); + } - const ptr = draco._malloc( byteLength ); - decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); - const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); - draco._free( ptr ); + removeWater(water) { + if(water ==null || water ==undefined){ + return; + } + this.scene.remove(water); + let index = this.waters.indexOf(water); + if (index > -1) { + this.waters.splice(index, 1); + } + } - return { array: index, itemSize: 1 }; + setVisible(visible) { + this.visible = this.visible; + this.layerContainer.style.display = visible ? 'block' : 'none'; + } + /** + * @deprecated 不建议用 + * @param {number} opacity + */ + setOpacity(opacity) { + this.opacity = opacity; + this.layerContainer.style.opacity = opacity; + } - } + /** + * 修改显示层级,默认越靠下的dom元素,显示上越靠上 + * @param {number} zIndex + */ + setZIndex(zIndex) { + this.zIndex = zIndex; + this.layerContainer.style.zIndex = this.zIndex; + } - function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + dispose() { + Element.removeLayer(id); + this.dispose = true; + this.animateId && cancelAnimationFrame(this.animateId); + this.mapView.root.dispose(); + this.mapView.dispose(); + } - const numComponents = attribute.num_components(); - const numPoints = dracoGeometry.num_points(); - const numValues = numPoints * numComponents; - const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; - const dataType = getDracoDataType( draco, attributeType ); + selectModel(insect){ + this.effectOutline.selectModel(insect); + } - const ptr = draco._malloc( byteLength ); - decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); - const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); - draco._free( ptr ); + resize(){ + var width = window.innerWidth; + var height = window.innerHeight; + this.renderer.setSize(width, height); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + if(Config.outLine.on){ + this.effectOutline.resize(width, height); + } + } - return { - name: attributeName, - array: array, - itemSize: numComponents - }; + animate(){ + this.animateId = requestAnimationFrame(this.animate.bind(this)); + if(this.base){ + this.controls.update(); + } + if (this.base){ + update(); //目前只有在基础地图中添加相机移动的动画,所以暂且只在base地图中进行渲染,后期可改成每个图层都进行渲染,或者必要时进行渲染。 + } + for(let water of this.waters){ + water.material.uniforms[ 'time' ].value += 1.0 / 60.0; + } + if (Config.outLine.on){ + this.effectOutline.render(); // 合成器渲染 + } else { + this.renderer.autoClear = true; + } + this.renderer.render(this.scene, this.camera); + } - } + _raycast(meshes, recursive, faceExclude) { + const isects = this._raycaster.intersectObjects(meshes, recursive); + if (faceExclude) { + for (let i = 0; i < isects.length; i++) { + if (isects[i].face !== faceExclude) { + return isects[i]; + } + } + return null; + } + return isects.length > 0 ? isects[0] : null; + } - function getDracoDataType( draco, attributeType ) { + _raycastFromMouse(mx, my, width, height, cam, meshes, recursive=false) { + const mouse = new Vector2( // normalized (-1 to +1) + (mx / width) * 2 - 1, + - (my / height) * 2 + 1); + // https://threejs.org/docs/#api/core/Raycaster + // update the picking ray with the camera and mouse position + this._raycaster.setFromCamera(mouse, cam); + return this._raycast(meshes, recursive, null); + } - switch ( attributeType ) { + /** + * + * @param {*} mx 屏幕坐标x + * @param {*} my 屏幕坐标y + * @param {boolean} recursive 是否检查子节点,true 递归检查 + * @returns mesh + */ + raycastFromMouse(mx, my, recursive=false) { + //---- NG: 2x when starting with Chrome's inspector mobile + // const {width, height} = this.renderer.domElement; + // const {width, height} = this.canvas; + //---- OK + const {clientWidth, clientHeight} = this.canvas; - case Float32Array: return draco.DT_FLOAT32; - case Int8Array: return draco.DT_INT8; - case Int16Array: return draco.DT_INT16; - case Int32Array: return draco.DT_INT32; - case Uint8Array: return draco.DT_UINT8; - case Uint16Array: return draco.DT_UINT16; - case Uint32Array: return draco.DT_UINT32; + return this._raycastFromMouse( + mx, my, clientWidth, clientHeight, this.camera, + this.mapView.children, recursive); + } - } + insectALL(mx, my, recursive=false) { + const {clientWidth, clientHeight} = this.canvas; - } + return this._raycastFromMouse( + mx, my, clientWidth, clientHeight, this.camera, + this.scene.children, recursive); + } } @@ -41525,11 +36291,17 @@ class WegeoMap { this.baseMap.moveToByCoords(coords); } - moveToByLL(lat, lon){ + /** + * 跳转到指定位置,用于球形地图 + * @param {*} lat + * @param {*} lon + * @returns + */ + moveToByLL(lat, lon, distance = 384720){ if(!this.baseMap){ return; } - this.baseMap.moveToByLL(lat, lon); + this.baseMap.moveToByLL(lat, lon, distance); } // 鼠标点击获取模型 @@ -41893,12 +36665,12 @@ class Skybox { loadSkyBox(scale) { var aCubeMap = new CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); aCubeMap.format = RGBAFormat; @@ -41921,15 +36693,15 @@ class Skybox { } loadBox(){ var cube = new CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); return cube; } } -export { Animate, BingMapsProvider, CancelablePromise, CanvasUtils, Config, DebugProvider, Element, Geolocation, GeolocationUtils, GeoserverWMSProvider, GeoserverWMTSProvider, GoogleMapsProvider, HeightDebugProvider, HereMapsProvider, LODControl, LODFrustum, LODRadial, LODRaycast, LODSphere, Layer, MapBoxProvider, MapHeightNode, MapHeightNodeShader, MapNode, MapNodeGeometry, MapNodeHeightGeometry, MapPlaneNode, MapProvider, MapSphereNode, MapSphereNodeGeometry, MapTilerProvider, MapView, OpenMapTilesProvider, OpenStreetMapsProvider, QuadTreePosition, RoadImageProvider, Skybox, TextureUtils, TianDiTuHeightProvider, TianDiTuProvider, UnitsUtils, WegeoMap, XHRUtils }; +export { AngleUtils, Animate, BingMapsProvider, CancelablePromise, CanvasUtils, Config, DebugProvider, Element, Geolocation, GeolocationUtils, GeoserverWMSProvider, GeoserverWMTSProvider, GoogleMapsProvider, HeightDebugProvider, HereMapsProvider, LODControl, LODFrustum, LODRadial, LODRaycast, LODSphere, Layer, MapBoxProvider, MapHeightNode, MapHeightNodeShader, MapNode, MapNodeGeometry, MapNodeHeightGeometry, MapPlaneNode, MapProvider, MapSphereNode, MapSphereNodeGeometry, MapTilerProvider, MapView, OpenMapTilesProvider, OpenStreetMapsProvider, QuadTreePosition, RoadImageProvider, Skybox, TextureUtils, TianDiTuHeightProvider, TianDiTuProvider, UnitsUtils, WegeoMap, XHRUtils }; diff --git a/examples/js/THREEi_ONLY_SphereWithSomeHoles.js b/examples/js/THREEi_ONLY_SphereWithSomeHoles.js new file mode 100644 index 0000000..95a3f5c --- /dev/null +++ b/examples/js/THREEi_ONLY_SphereWithSomeHoles.js @@ -0,0 +1,1694 @@ +// THREEi_ONLY_SphereWithSomeHoles.js ( rev 109.0 ) + +// NOTE! This version contains only the older, simplified functions + +// * buildSphereWithHolesObj, buildSphereWithHoles( ), +// * this requires less effort in code and execution, +// but +// * does not check whether the current front overlaps, +// * in very many cases with few holes this is not a problem, +// * it can lead to errors in more complicated cases + +/** + * @author hofk / http://threejs.hofk.de/ +*/ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.THREEi = global.THREEi || {}))); +}(this, (function (exports) { + +'use strict'; + +var g; // THREE.BufferGeometry + +//################################################################################################# + +// Algorithm based on (simplified for sphere with holes) +// de: https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdg0/cdg0n.pdf +// en: https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdgen0104.pdf +// + +// ............................ Sphere with Holes (Triangulation) ................................. + +function createSphereWithHoles( arg1, arg2 ) { + + g = this; // THREE.BufferGeometry() - geometry object from three.js + + g.buildSphereWithHolesObj = buildSphereWithHolesObj; + g.buildSphereWithHoles = buildSphereWithHoles; + + if( typeof arg1 === 'number' ) { // variant detail, holes are optional, radius = 1 is fixed - use three.js .scale + // Variant with less effort in the algorithm! Other angle calculation too. + + g.detail = arg1; // count of triangles for half a great circle + g.holes = arg2 !== undefined ? arg2 : []; // optional + /* holes, example: + holes = [ + // circular hole, 3 elements: [ theta, phi, count ] + [ 2.44, 0.41, 12 ], + [ 0.72, 2.55, 19 ], + // points hole,: array of points theta, phi, ... (last point is connected to first) + [ 0,0, 0.5,-0.8, 0.25,-0.27, 0.4,0.3, 0.3,0.72 ] + ]; + */ + + g.buildSphereWithHoles( ); + + } else { // variant parameters object { d: div4: holes: } all elements optional + + g.d = arg1.d !== undefined ? arg1.d : 2 * Math.sin( Math.PI / 24 ); // to g.div4 default + g.div4 = arg1.div4 !== undefined ? arg1.div4 : 6; // 6 * 4 = 24 great circle divisions + g.holes = arg1.holes !== undefined ? arg1.holes : []; + + g.detail = g.div4 * 4; // division of the great circle + g.radius = g.d / Math.sin( Math.PI / g.detail ) / 2; // sphere radius, for external use as well + + /* holes, example: + holes: [ + // circular hole, 3 elements: [ theta, phi, div4Hole ], div4Hole <= div4 + [ 1.82, 0.41, 12 ], + // points hole,: array of points theta, phi, ... (last point is connected to first) + [ 0,0, 0.5,-0.8, 0.25,-0.27, 0.4,0.3, 0.3,0.72 ] + ] + */ + + g.buildSphereWithHolesObj( ); + + } + +} + +function buildSphereWithHolesObj( ) { + + const dd = g.d * g.d; + + const squareLength = ( x,y,z ) => ( x*x + y*y + z*z ); + const length = ( x, y, z ) => ( Math.sqrt( x * x + y * y + z * z ) ); + const prevFront = ( i ) => ( i !== 0 ? i - 1 : front.length - 1 ); + const nextFront = ( i ) => ( i !== front.length - 1 ? i + 1 : 0 ); + const determinant = ( xa,ya,za, xb,yb,zb, xc,yc,zc ) => ( xa*yb*zc + ya*zb*xc + za*xb*yc - za*yb*xc - xa*zb*yc - ya*xb*zc ); + + let m; // index of the current front point + let n; // number of new points + let nT; // number of new triangles + let nIns; // number of new points (after union or split) + let dAng; // partial angle + let len, d1, d2, d12; // lengths + let iSplit, jSplit; // split front indices + let iUnite, jUnite, fUnite; // unite front indices, front number (to unite) + + // points and vectors: + let x, y, z, xp, yp, zp; // coordinates point and actual point p + let x1, y1, z1, x2, y2, z2; // previous and next point to p in front + let xn, yn, zn; // normal, gradient (sphere: normalized point) + let xt1, yt1, zt1, xt2, yt2, zt2; // tangents + let xs1, ys1, xs2, ys2; // p in tangential system (only x, y required) + let xc, yc, zc; // actual point as center point for new points + + // preparation + + const faceCount = g.detail * g.detail * 8 ; + const posCount = g.detail * g.detail * 6 ; + + g.indices = new Uint32Array( faceCount * 3 ); + g.positions = new Float32Array( posCount * 3 ); + //g.normals = new Float32Array( posCount * 3 ); + + g.setIndex( new THREE.BufferAttribute( g.indices, 1 ) ); + g.addAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) ); + + let posIdx = 0; + let indIdx = 0; + let frontPosIdx, unionIdxA, unionIdxB, splitIdx; + + let front = []; // active front // front[ i ]: object { idx: 0, ang: 0 } + let partFront = []; // separated part of the active front (to split) + let insertFront = []; // new front points to insert into active front + let fronts = []; // all fronts + let partBounds = []; // bounding box of partFront [ xmin, xmax, ymin, ymax, zmin, zmax ] + let boundings = []; // fronts bounding boxes + let smallAngles = []; // new angles < 1.5 + + let frontNo, frontStock; + let unite = false; + let split = false; + + frontNo = 0; // active front number + frontStock = 0; // number of fronts still to be processed + + // define holes fronts + + if ( g.holes.length === 0 ) { + + makeFirstTriangle( ); + + } else { + + g.circles = []; // array of arrays [ xc, yc, zc, rHole, div4Hole ], values for external use + + for ( let i = 0; i < g.holes.length; i ++ ) { + + if ( g.holes[ i ].length === 3 ) { + + makeCircularHole( i ); // [ theta, phi, div4Hole ] + + } else { + + makePointsHole( i ); // points: [ theta, phi, ... ] + + } + + } + + } + + frontNo = 0; + front = fronts[ frontNo ]; + + //////////////// DEBUG triangles ////////////////////////////////////// + // let stp = 0; + /////////////////////////////////////////////////////////////////////// + + // ------ triangulation cycle ------------- + + while ( frontStock > 0 ) { + + if ( !unite ) { // triangulation on the front + + smallAngles = []; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang === 0 ) calculateFrontAngle( i ); // is to be recalculated (angle was set to zero) + + } + + m = getMinimalAngleIndex( ); // front angle + makeNewTriangles( m ); + + if ( front.length > 9 && smallAngles.length === 0 ) { + + checkDistancesToUnite( m ); + + } + + if ( front.length === 3 ) { + + makeLastTriangle( ); // last triangle closes the front + chooseNextFront( ); // if aviable + + } + + } else { // unite the active front to another front + + uniteFront( m, iUnite, fUnite, jUnite ); + trianglesAtUnionPoints( ); + unite = false; + + } + + } + + // ..... detail functions ..... + + function makeFirstTriangle ( ) { + + fronts[ frontNo ] = []; + boundings[ frontNo ] = []; + + storePoint( 0, 0 ); // ( theta, phi ) + storePoint( Math.PI / 2 / g.div4, -Math.PI / 6 ); + storePoint( Math.PI / 2 / g.div4, Math.PI / 6 ); + + g.indices[ 0 ] = 0; + g.indices[ 1 ] = 1; + g.indices[ 2 ] = 2; + + indIdx += 3; + + fronts[ frontNo ].push( { idx: 0, ang: 0 }, { idx: 1, ang: 0 }, { idx: 2, ang: 0 } ); + + frontNo ++; + frontStock ++; + + } + + function makePointsHole( i ) { + + let theta, phi, count, xmin, ymin, zmin, xmax, ymax, zmax, xv2, yv2, zv2; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + fronts[ frontNo ] = []; + boundings[ frontNo ] = []; + + theta = g.holes[ i ][ 0 ]; + phi = g.holes[ i ][ 1 ]; + + x1 = g.radius * Math.sin( theta ) * Math.cos( phi ); + y1 = g.radius * Math.cos( theta ); + z1 = -g.radius * Math.sin( theta ) * Math.sin( phi ); + + for ( let j = 1; j < g.holes[ i ].length / 2 + 1; j ++ ) { + + g.positions[ posIdx ] = x1; + g.positions[ posIdx + 1 ] = y1; + g.positions[ posIdx + 2 ] = z1; + + fronts[ frontNo ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x1 < xmin ? x1 : xmin; + ymin = y1 < ymin ? y1 : ymin; + zmin = z1 < zmin ? z1 : zmin; + + xmax = x1 > xmax ? x1 : xmax; + ymax = y1 > ymax ? y1 : ymax; + zmax = z1 > zmax ? z1 : zmax; + + posIdx += 3; + + theta = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 : 0 ]; // 0 => connect to start + phi = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 + 1 : 1 ]; // 1 => connect to start + + x2 = g.radius * Math.sin( theta ) * Math.cos( phi ); + y2 = g.radius * Math.cos( theta ); + z2 = -g.radius * Math.sin( theta ) * Math.sin( phi ); + + xv2 = x2 - x1; + yv2 = y2 - y1; + zv2 = z2 - z1; + + len = length( xv2, yv2, zv2 ); + + if ( len > g.d ) { + + count = Math.ceil( len / g.d ); + + for ( let k = 1; k < count; k ++ ) { + + x = x1 + k * xv2 / count; + y = y1 + k * yv2 / count; + z = z1 + k * zv2 / count; + + len = length( x, y, z ); // to bring the point to the surface (g.radius * ..) + + g.positions[ posIdx ] = g.radius * x / len; + g.positions[ posIdx + 1 ] = g.radius * y / len; + g.positions[ posIdx + 2 ] = g.radius * z / len; + + fronts[ frontNo ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + } + + x1 = x2; + y1 = y2; + z1 = z2; + + } + + boundings[ frontNo ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + frontNo ++; + frontStock ++; + + } + + function makeCircularHole( i ) { + + let xa, ya, za, xb, yb; // for rotation around z, y + + const theta = g.holes[ i ][ 0 ]; + const phi = g.holes[ i ][ 1 ]; + const div4Hole = g.holes[ i ][ 2 ]; + const countH = div4Hole * 4; + + let xmin, ymin, zmin, xmax, ymax, zmax; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + const rHole = g.d / ( Math.sin( Math.PI / countH ) * 2 ); // radius cutting circle + const h = Math.sqrt( g.radius * g.radius - rHole * rHole ); // distance: sphere center to cutting circle + + // ... hole values for external use + xp = g.radius * Math.sin( theta ) * Math.cos( phi ); + yp = g.radius * Math.cos( theta ); + zp = -g.radius * -Math.sin( theta ) * Math.sin( phi ); + + xc = h / g.radius * xp; + yc = h / g.radius * yp; + zc = h / g.radius * zp; + + g.circles.push( [ xc, yc, zc, rHole, div4Hole ] ); // values for external use + + fronts[ frontNo ] = []; + boundings[ frontNo ] = []; + + ya = h; + + for ( let i = 0, alpha = 0; i < countH; i ++, alpha += 2 * Math.PI / countH ) { + + // cutting circle on top + xa = rHole * Math.cos( alpha ); + za = rHole * Math.sin( alpha ); + + // rotate around z axis + xb = xa * Math.cos( theta ) - ya * Math.sin( theta ); + yb = xa * Math.sin( theta ) + ya * Math.cos( theta ); + + // rotate around y axis + x = -xb * Math.cos( phi ) + za * Math.sin( phi ); + z = xb * Math.sin( phi ) + za * Math.cos( phi ); + + y = yb; // for storing and checking bounds + + g.positions[ posIdx ] = x; + g.positions[ posIdx + 1 ] = y; + g.positions[ posIdx + 2 ] = z; + + fronts[ frontNo ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + boundings[ frontNo ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + frontNo ++; + frontStock ++; + + } + + function checkDistancesToUnite( m ) { // for new active front points + + let idxJ, xChk, yChk, zChk, ddUnite; + let ddUniteMin = Infinity; + unite = false; + + for ( let i = 0; i < insertFront.length; i ++ ) { + + getPoint( m + i ); + + for ( let f = 0; f < fronts.length; f ++ ) { + + if ( f !== frontNo ) { + + xChk = ( xp > boundings[ f ][ 0 ] - g.d ) && ( xp < boundings[ f ][ 3 ] + g.d ); + yChk = ( yp > boundings[ f ][ 1 ] - g.d ) && ( yp < boundings[ f ][ 4 ] + g.d ); + zChk = ( zp > boundings[ f ][ 2 ] - g.d ) && ( zp < boundings[ f ][ 5 ] + g.d ); + + if ( xChk || yChk || zChk ) { + + for ( let j = 0; j < fronts[ f ].length; j ++ ) { + + idxJ = fronts[ f ][ j ].idx * 3; + + // Hint: here (2) is exceptionally point in other front! + x2 = g.positions[ idxJ ]; + y2 = g.positions[ idxJ + 1 ]; + z2 = g.positions[ idxJ + 2 ]; + + ddUnite = squareLength ( x2 - xp, y2 - yp, z2 - zp ); + + if ( ddUnite < dd && ddUnite < ddUniteMin ) { + + ddUniteMin = ddUnite; + iUnite = i; + jUnite = j; + fUnite = f; + unite = true; + + } + + } + + } + + } + + } + + } + + } + + function uniteFront( m, i, f, j ) { + + let tmp = []; + + tmp[ 0 ] = front.slice( 0, m + i + 1 ); + tmp[ 1 ] = fronts[ f ].slice( j , fronts[ f ].length ); + tmp[ 2 ] = fronts[ f ].slice( 0 , j + 1 ); + tmp[ 3 ] = front.slice( m + i, front.length ); + + unionIdxA = m + i; + unionIdxB = m + i + 1 + fronts[ f ].length + + front = []; + + for ( let t = 0; t < 4; t ++ ) { + + for ( let k = 0; k < tmp[ t ].length ; k ++ ) { + + front.push( tmp[ t ][ k ] ); + + } + + } + + fronts[ f ] = []; // empty united front + + frontStock -= 1; // front is eliminated + + } + + function trianglesAtUnionPoints( ) { + + nIns = 0; // count inserted points + + calculateFrontAngle( unionIdxA ); + calculateFrontAngle( unionIdxA + 1 ); + + if ( front[ unionIdxA ].ang < front[ unionIdxA + 1 ].ang ) { + + makeNewTriangles( unionIdxA ); + nIns += n - 1; + calculateFrontAngle( unionIdxA + 1 + nIns ); + makeNewTriangles( unionIdxA + 1 + nIns ); + nIns += n - 1; + + } else { + + makeNewTriangles( unionIdxA + 1 ); + nIns += n - 1; + calculateFrontAngle( unionIdxA ); + makeNewTriangles( unionIdxA ); + nIns += n - 1; + } + + calculateFrontAngle( unionIdxB + nIns ); + calculateFrontAngle( unionIdxB + 1 + nIns ); + + if ( front[ unionIdxB + nIns ].ang < front[ unionIdxB + 1 + nIns ].ang ) { + + makeNewTriangles( unionIdxB + nIns ); + nIns += n - 1; + calculateFrontAngle( unionIdxB + 1 + nIns ); + makeNewTriangles( unionIdxB + 1 + nIns ); + + } else { + + makeNewTriangles( unionIdxB + 1 + nIns ); + calculateFrontAngle( unionIdxB + nIns ); + makeNewTriangles( unionIdxB + nIns ); + + } + + } + + function getMinimalAngleIndex( ) { + + let angle = Infinity; + let m; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang < angle ) { + + angle = front[ i ].ang ; + m = i; + + } + + } + + return m; + + } + + function makeNewTriangles( m ) { + + // m: minimal angle (index) + + insertFront = []; // new front points + + nT = Math.floor( 3 * front[ m ].ang / Math.PI ) + 1; // number of new triangles + + dAng = front[ m ].ang / nT; + + getSystemAtPoint( m ); + getNextPoint( m ); + + d1 = length( x1 - xp, y1 - yp, z1 - zp ); + d2 = length( x2 - xp, y2 - yp, z2 - zp ); + d12 = length( x2 - x1, y2 - y1, z2 - z1 ); + + // correction of dAng, nT in extreme cases + + if ( dAng < 0.8 && nT > 1 ) { + + nT --; + dAng = front[ m ].ang / nT; + + } + + if ( dAng > 0.8 && nT === 1 && d12 > 1.25 * g.d ) { + + nT = 2; + dAng = front[ m ].ang / nT; + + } + + if ( d1 * d1 < 0.2 * dd || d2 * d2 < 0.2 * dd ) { + + nT = 1; + + } + + n = nT - 1; // n number of new points + + if ( n === 0 ) { // one triangle + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + front[ prevFront( m ) ].ang = 0; + front[ nextFront( m ) ].ang = 0; + + front.splice( m, 1 ); // delete point with index m from the front + + } else { // more then one triangle + + xc = xp; + yc = yp; + zc = zp; + + for ( let i = 0, phi = dAng; i < n; i ++, phi += dAng ) { + + xp = xc + Math.cos( phi ) * g.d * xt1 + Math.sin( phi ) * g.d * xt2; + yp = yc + Math.cos( phi ) * g.d * yt1 + Math.sin( phi ) * g.d * yt2; + zp = zc + Math.cos( phi ) * g.d * zt1 + Math.sin( phi ) * g.d * zt2; + + len = length( xp, yp, zp ); // to bring the point to the surface (g.radius * ..) + + g.positions[ posIdx ] = g.radius * xp / len; + g.positions[ posIdx + 1 ] = g.radius * yp / len; + g.positions[ posIdx + 2 ] = g.radius * zp / len; + + insertFront.push( { idx: posIdx / 3, ang: 0 } ); + + posIdx += 3; + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx + g.indices[ indIdx + 2 ] = insertFront[ 0 ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + front[ prevFront( m ) ].ang = 0; + + for ( let i = 0; i < n - 1; i ++ ) { + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ i ].idx; + g.indices[ indIdx + 2 ] = insertFront[ i + 1 ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ n - 1 ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + front[ nextFront( m ) ].ang = 0; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + replaceFront( m, insertFront ); // replaces front[ m ] with new points + + } + + } + + function makeLastTriangle( ) { + + g.indices[ indIdx ] = front[ 2 ].idx; + g.indices[ indIdx + 1 ] = front[ 1 ].idx + g.indices[ indIdx + 2 ] = front[ 0 ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + front = []; + + fronts[ frontNo ] = []; + + frontStock -= 1; // close front + + } + + function chooseNextFront( ) { + + if ( frontStock > 0 ) { + + for ( let i = 0; i < fronts.length; i ++ ) { + + if ( fronts[ i ].length > 0 ) { + + frontNo = i; + break; + + } + + } + + front = fronts[ frontNo ]; + + smallAngles = []; + + for ( let i = 0; i < front.length; i ++ ) { + + calculateFrontAngle( i ); // recalculate angles of next front + + } + + } + + } + + function atan2PI( x, y ) { + + let phi = Math.atan2( y, x ); + + if ( phi < 0 ) phi = phi + Math.PI * 2; + + return phi; + + } + + function coordTangentialSystem( ) { + + let det = determinant( xt1, yt1, zt1, xt2, yt2, zt2, xn, yn, zn ); + + xs1 = determinant( x1 - xp, y1 - yp, z1 - zp, xt2, yt2, zt2, xn, yn, zn ) / det; + ys1 = determinant( xt1, yt1, zt1, x1 - xp, y1 - yp, z1 - zp, xn, yn, zn ) / det; + //zs1 = determinant( xt1, yt1, zt1, xt2, yt2, zt2, x1 - xp, y1 - yp, z1 - zp ) / det; // not needed + + xs2 = determinant( x2 - xp, y2 - yp, z2 - zp, xt2, yt2, zt2, xn, yn, zn ) / det; + ys2 = determinant( xt1, yt1, zt1, x2 - xp, y2 - yp, z2 - zp, xn, yn, zn ) / det; + //zs2 = determinant( xt1, yt1, zt1, xt2, yt2, zt2, x2 - xp, y2 - yp, z2 - zp ) / det; // not needed + + } + + function calculateFrontAngle( i ) { + + let ang1, ang2; + + getSystemAtPoint( i ); + getNextPoint( i ); + + coordTangentialSystem( ); + + ang1 = atan2PI( xs1, ys1 ); + ang2 = atan2PI( xs2, ys2 ); + + if ( ang2 < ang1 ) ang2 += Math.PI * 2; + + front[ i ].ang = ang2 - ang1; + + if ( front[ i ].ang < 1.5 ) smallAngles.push( i ); + + } + + function partFrontBounds( ) { + + let idx, xmin, ymin, zmin, xmax, ymax, zmax; + + partBounds = []; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + for( let i = 0; i < partFront.length; i ++ ) { + + idx = partFront[ i ].idx * 3; + + x = g.positions[ idx ]; + y = g.positions[ idx + 1 ]; + z = g.positions[ idx + 2 ]; + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + } + + partBounds.push( xmin, ymin, zmin, xmax, ymax, zmax ); + + boundings.push( partBounds ); + + } + + function replaceFront( m, fNew ) { + + let rear = front.splice( m, front.length - m ); + + for ( let i = 0; i < fNew.length; i ++ ) { + + front.push( fNew[ i ] ); // new front points + + } + + for ( let i = 1; i < rear.length; i ++ ) { // i = 1: without old front point m + + front.push( rear[ i ] ); + + } + + } + + function getSystemAtPoint( i ) { + + getPrevPoint( i ); + getPoint( i ); + + len = length( xp, yp, zp ); // to normalize + + xn = xp / len; + yn = yp / len + zn = zp / len; + + // centerAngle = Math.acos( Math.abs( x1 * xp + y1 * yp + z1 * zp ) / ( g.radius * g.radius ) ); + const h = Math.abs( x1 * xp + y1 * yp + z1 * zp ) / g.radius; // distance: sphere center to cutting circle + + // center cutting circle (refers to previous point) + xc = h / g.radius * xp; + yc = h / g.radius * yp; + zc = h / g.radius * zp; + + // first tangent + xt1 = x1 - xc; + yt1 = y1 - yc; + zt1 = z1 - zc; + + len = length( xt1, yt1, zt1 ); // to normalize + + xt1 = xt1 / len; + yt1 = yt1 / len; + zt1 = zt1 / len; + + // cross, second tangent + + xt2 = yn * zt1 - zn * yt1; + yt2 = zn * xt1 - xn * zt1; + zt2 = xn * yt1 - yn * xt1; + + } + + function storePoint( theta, phi ) { + + g.positions[ posIdx ] = g.radius * Math.sin( theta ) * Math.cos( phi ); + g.positions[ posIdx + 1 ] = g.radius * Math.cos( theta ); + g.positions[ posIdx + 2 ] = -g.radius * Math.sin( theta ) * Math.sin( phi ); + + posIdx += 3; + + } + + function getPrevPoint( i ) { + + frontPosIdx = front[ prevFront( i ) ].idx * 3; + + x1 = g.positions[ frontPosIdx ]; + y1 = g.positions[ frontPosIdx + 1 ]; + z1 = g.positions[ frontPosIdx + 2 ]; + + } + + function getPoint( i ) { + + frontPosIdx = front[ i ].idx * 3; + + xp = g.positions[ frontPosIdx ]; + yp = g.positions[ frontPosIdx + 1 ]; + zp = g.positions[ frontPosIdx + 2 ]; + + } + + function getNextPoint( i ) { + + frontPosIdx = front[ nextFront( i ) ].idx * 3; + + x2 = g.positions[ frontPosIdx ]; + y2 = g.positions[ frontPosIdx + 1 ]; + z2 = g.positions[ frontPosIdx + 2 ]; + + } + +} + +function buildSphereWithHoles( ) { + + const length = ( x, y, z ) => ( Math.sqrt( x * x + y * y + z * z ) ); + const prevFront = ( i ) => ( i !== 0 ? i - 1 : front.length - 1 ); + const nextFront = ( i ) => ( i !== front.length - 1 ? i + 1 : 0 ); + + let d; // rough edge length of the triangles + let m; // index of the current front point + let n; // number of new points + let nT; // number of new triangles + let nUnion; // number of new points (after union) + let dAng; // partial angle + let len, d1, d2, d12, dd1, dd2, dd12; // lengths and their squares + let h; // distance center to circle + let acute, concave; // front angle properties + + // points and vectors: + let x, y, z, xp, yp, zp, xc, yc, zc, x1, y1, z1, x2, y2, z2, xt1, yt1, zt1, xt2, yt2, zt2, xv1, yv1, zv1, xv2, yv2, zv2; + + // preparation + + const faceCount = g.detail * g.detail * 4; + const posCount = g.detail * g.detail * 3; + + g.indices = new Uint32Array( faceCount * 3 ); + g.positions = new Float32Array( posCount * 3 ); + //g.normals = new Float32Array( posCount * 3 ); + + g.setIndex( new THREE.BufferAttribute( g.indices, 1 ) ); + g.addAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) ); + + d = Math.PI / g.detail; // rough side length of the triangles + + let posIdx = 0; + let indIdx = 0; + let frontPosIdx, unionIdxA, unionIdxB; + + let front = []; // active front // front[ i ]: object { idx: 0, ang: 0 } + let partFront = []; // separated part of the active front + let insertFront = []; // new front points to insert into active front + let fronts = []; // all fronts + let partBounds = []; // bounding box of partFront [ xmin, xmax, ymin, ymax, zmin, zmax ] + let boundings = []; // fronts bounding boxes + let smallAngles = []; // new angles < 1.5 + + let start = true; + let united = false; + + // define holes + + let holeNumber; + + if ( g.holes.length === 0 ) { + + makeFirstTriangle( ); + + } else { + + g.circles = []; // [ center, r, count ] of holes for external use + + holeNumber = 0; + + for ( let i = 0; i < g.holes.length; i ++ ) { + + if ( g.holes[ i ].length === 3 ) { + + makeCircularHole( i ); // [ theta, phi, count ] + + } else { + + makePointsHole( i ); // points: [ theta, phi, ... ] + + } + + } + + } + + let activeFrontNo = 0; + front = fronts[ activeFrontNo ]; + + // ------ triangulation cycle ------------- + + while ( front.length > 3 || start ) { + + if ( start ) start = false; + + if ( front.length > 9 && smallAngles.length === 0 ) { + + // checkDistancesInFront( ); + checkDistancesToFronts( m ); + + } + + if ( united ) { + + trianglesAtUnionPoints( ); + + } else { + + calculateFrontAngles( ); + m = getMinimalAngleIndex( ); // front angle + newTriangles( m ); + + } + + } // end while + + makeLastTriangle( ); + + // ..... main detail functions ..... + + function checkDistancesToFronts( m ) { + + let idx, idxJ, xChk, yChk, zChk; + + for ( let i = 0; i < insertFront.length; i ++ ) { + + idx = front[ m + i ].idx * 3 + + xp = g.positions[ idx ]; + yp = g.positions[ idx + 1 ]; + zp = g.positions[ idx + 2 ]; + + for ( let f = 0; f < fronts.length; f ++ ) { + + if ( f !== activeFrontNo ) { + + xChk = ( xp > boundings[ f ][ 0 ] - d ) && ( xp < boundings[ f ][ 3 ] + d ); + yChk = ( yp > boundings[ f ][ 1 ] - d ) && ( yp < boundings[ f ][ 4 ] + d ); + zChk = ( zp > boundings[ f ][ 2 ] - d ) && ( zp < boundings[ f ][ 5 ] + d ); + + if ( xChk || yChk || zChk ) { + + for ( let j = 0; j < fronts[ f ].length; j ++ ) { + + idxJ = fronts[ f ][ j ].idx * 3; + x2 = g.positions[ idxJ ]; + y2 = g.positions[ idxJ + 1 ]; + z2 = g.positions[ idxJ + 2 ]; + + if ( length( x2 - xp, y2 - yp, z2 - zp ) < d ) { + + uniteFront( m, i, f, j ); + + } + + } + + } + + } + + } + + } + + } + + function calculateFrontAngles( ) { + + smallAngles = []; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang === 0 ) { + + frontAngle( i ); + + } + + } + + } + + function getMinimalAngleIndex( ) { + + let angle = Infinity; + let m; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang < angle ) { + + angle = front[ i ].ang ; + m = i; + + } + + } + + return m; + + } + + function newTriangles( m ) { + + // m: minimal angle (index) + + insertFront = []; + + nT = Math.floor( 3 * front[ m ].ang / Math.PI ) + 1; // number of new triangles + + dAng = front[ m ].ang / nT; + + getSystemAtPoint( m ); + getNextPoint( m ); + + d1 = length( x1 - xp, y1 - yp, z1 - zp ); + d2 = length( x2 - xp, y2 - yp, z2 - zp ); + d12 = length( x2 - x1, y2 - y1, z2 - z1 ); + + // correction of dAng, nT in extreme cases + + if ( dAng < 0.8 && nT > 1 ) { + + nT --; + dAng = front[ m ].ang / nT; + + } + + if ( dAng > 0.8 && nT === 1 && d12 > 1.25 * d ) { + + nT = 2; + dAng = front[ m ].ang / nT; + + } + + if ( d1 * d1 < 0.2 * d * d || d2 * d2 < 0.2 * d * d ) { + + nT = 1; + + } + + n = nT - 1; // n number of new points + + if ( n === 0 ) { // one triangle + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + indIdx += 3; + + front[ prevFront( m ) ].ang = 0; + front[ nextFront( m ) ].ang = 0; + + } else { // more then one triangle + + for ( let i = 0, phi = dAng; i < n; i ++, phi += dAng ) { + + xp = xc + Math.cos( phi ) * d * xt1 + Math.sin( phi ) * d * xt2; + yp = yc + Math.cos( phi ) * d * yt1 + Math.sin( phi ) * d * yt2; + zp = zc + Math.cos( phi ) * d * zt1 + Math.sin( phi ) * d * zt2; + + len = length( xp, yp, zp ); // to normalize + + g.positions[ posIdx ] = xp / len; + g.positions[ posIdx + 1 ] = yp / len; + g.positions[ posIdx + 2 ] = zp / len; + + insertFront.push( { idx: posIdx / 3, ang: 0 } ); + + posIdx += 3; + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx + g.indices[ indIdx + 2 ] = insertFront[ 0 ].idx; + + indIdx += 3; + + front[ prevFront( m ) ].ang = 0; + + for ( let i = 0; i < n - 1; i ++ ) { + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ i ].idx; + g.indices[ indIdx + 2 ] = insertFront[ i + 1 ].idx; + + indIdx += 3; + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ n - 1 ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + front[ nextFront( m ) ].ang = 0; + + indIdx += 3; + + } + + replaceFront( m, insertFront ); // replaces front[ m ] with new points + + } + + function trianglesAtUnionPoints( ) { + + nUnion = 0; // count inserted points + + frontAngle( unionIdxA ); + frontAngle( unionIdxA + 1 ); + + if ( front[ unionIdxA ].ang < front[ unionIdxA + 1 ].ang ) { + + newTriangles( unionIdxA ); + nUnion += n - 1; + frontAngle( unionIdxA + 1 + nUnion ); + newTriangles( unionIdxA + 1 + nUnion ); + nUnion += n - 1; + + } else { + + newTriangles( unionIdxA + 1 ); + nUnion += n - 1; + frontAngle( unionIdxA ); + newTriangles( unionIdxA ); + nUnion += n - 1; + } + + frontAngle( unionIdxB + nUnion ); + frontAngle( unionIdxB + 1 + nUnion ); + + if ( front[ unionIdxB + nUnion ].ang < front[ unionIdxB + 1 + nUnion ].ang ) { + + newTriangles( unionIdxB + nUnion ); + nUnion += n - 1; + frontAngle( unionIdxB + 1 + nUnion ); + newTriangles( unionIdxB + 1 + nUnion ); + + } else { + + newTriangles( unionIdxB + 1 + nUnion ); + frontAngle( unionIdxB + nUnion ); + newTriangles( unionIdxB + nUnion ); + + } + + united = false; + + } + + // ..... help functions ..... + + function frontAngle( i ) { + + getPrevPoint( i ); // (1) + getPoint( i ); + getNextPoint( i ); // (2) + + // centerAngle = Math.acos( Math.abs( x1 * xp + y1 * yp + z1 * zp ) ); + // r = Math.sin( centerAngle ); // radius circle + // h = Math.cos( centerAngle ); // distance center to circle + + h = Math.abs( x1 * xp + y1 * yp + z1 * zp ); + + // center cutting circle (refers to previous point) + xc = h * xp; + yc = h * yp; + zc = h * zp; + + xv1 = xc - x1; + yv1 = yc - y1; + zv1 = zc - z1; + + len = length( xv1, yv1, zv1 ); // to normalize + + xv1 = xv1 / len; + yv1 = yv1 / len; + zv1 = zv1 / len; + + xv2 = x2 - xc; + yv2 = y2 - yc; + zv2 = z2 - zc; + + len = length( xv2, yv2, zv2 ); // to normalize + + xv2 = xv2 / len; + yv2 = yv2 / len; + zv2 = zv2 / len; + + front[ i ].ang = Math.acos( Math.abs( xv1 * xv2 + yv1 * yv2 + zv1 * zv2 ) ); + + // cross, to detect curvature + x = yv1 * zv2 - zv1 * yv2; + y = zv1 * xv2 - xv1 * zv2; + z = xv1 * yv2 - yv1 * xv2; + + len = length( x, y, z ); // to normalize + + x = xp + x / len; + y = yp + y / len; + z = zp + z / len; + + concave = ( length( x, y, z ) < 1 ); + + d1 = length( x1 - xp, y1 - yp, z1 - zp ); + d2 = length( x2 - xp, y2 - yp, z2 - zp ); + d12 = length( x2 - x1, y2 - y1, z2 - z1 ); + + dd1 = d1 * d1; + dd2 = d2 * d2; + dd12 = d12 * d12; + + acute = ( dd12 < ( dd1 + dd2) ); + + // if ( concave && acute ) front[ i ].ang += 0; + if ( concave && !acute ) front[ i ].ang = Math.PI - front[ i ].ang ; + if ( !concave && acute ) front[ i ].ang = 2 * Math.PI - front[ i ].ang ; + if ( !concave && !acute ) front[ i ].ang = Math.PI + front[ i ].ang ; + + if ( front[ i ].ang < 1.5 ) smallAngles.push( i ); + + } + + function uniteFront( m, i, f, j ) { + + let tmp = []; + + tmp[ 0 ] = front.slice( 0, m + i + 1 ); + tmp[ 1 ] = fronts[ f ].slice( j , fronts[ f ].length ); + tmp[ 2 ] = fronts[ f ].slice( 0 , j + 1 ); + tmp[ 3 ] = front.slice( m + i, front.length ); + + unionIdxA = m + i; + unionIdxB = m + i + 1 + fronts[ f ].length + + front = []; + + for ( let t = 0; t < 4; t ++ ) { + + for ( let k = 0; k < tmp[ t ].length ; k ++ ) { + + front.push( tmp[ t ][ k ] ); + + } + + } + + fronts[ f ] = []; // empty united front + + united = true; + + } + + function partFrontBounds( ) { + + let idx, xmin, ymin, zmin, xmax, ymax, zmax; + + partBounds = []; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + for( let i = 0; i < partFront.length; i ++ ) { + + idx = partFront[ i ].idx * 3; + + x = g.positions[ idx ]; + y = g.positions[ idx + 1 ]; + z = g.positions[ idx + 2 ]; + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + } + + partBounds.push( xmin, ymin, zmin, xmax, ymax, zmax ); + + boundings.push( partBounds ); + + } + + function replaceFront( m, fNew ) { + + let rear = front.splice( m, front.length - m ) + + for ( let i = 0; i < fNew.length; i ++ ) { + + front.push( fNew[ i ] ); // new front points + + } + + for ( let i = 1; i < rear.length; i ++ ) { // 1: without old front point m + + front.push( rear[ i ] ); + + } + + } + + function storePoint( theta, phi ) { + + g.positions[ posIdx ] = Math.sin( theta ) * Math.cos( phi ); + g.positions[ posIdx + 1 ] = Math.cos( theta ); + g.positions[ posIdx + 2 ] = -Math.sin( theta ) * Math.sin( phi ); + + posIdx += 3; + + } + + function makeFirstTriangle ( ) { + + storePoint( 0, 0 ); // ( theta, phi ) + storePoint( d, -Math.PI / 6 ); + storePoint( d, Math.PI / 6 ); + + g.indices[ 0 ] = 0; + g.indices[ 1 ] = 1; + g.indices[ 2 ] = 2; + + indIdx += 3; + + front = []; + + front.push( { idx: 0, ang: 0 }, { idx: 1, ang: 0 }, { idx: 2, ang: 0 } ); + fronts.push( front ) + + } + + function makeLastTriangle( ) { + + g.indices[ indIdx ] = front[ 2 ].idx; + g.indices[ indIdx + 1 ] = front[ 1 ].idx + g.indices[ indIdx + 2 ] = front[ 0 ].idx; + + } + + function makePointsHole( i ) { + + let theta, phi, count, xmin, ymin, zmin, xmax, ymax, zmax; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + fronts[ holeNumber ] = []; + boundings[ holeNumber ] = []; + + theta = g.holes[ i ][ 0 ]; + phi = g.holes[ i ][ 1 ]; + + x1 = Math.sin( theta ) * Math.cos( phi ); + y1 = Math.cos( theta ); + z1 = -Math.sin( theta ) * Math.sin( phi ); + + for ( let j = 1; j < g.holes[ i ].length / 2 + 1; j ++ ) { + + g.positions[ posIdx ] = x1; + g.positions[ posIdx + 1 ] = y1; + g.positions[ posIdx + 2 ] = z1; + + fronts[ holeNumber ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x1 < xmin ? x1 : xmin; + ymin = y1 < ymin ? y1 : ymin; + zmin = z1 < zmin ? z1 : zmin; + + xmax = x1 > xmax ? x1 : xmax; + ymax = y1 > ymax ? y1 : ymax; + zmax = z1 > zmax ? z1 : zmax; + + posIdx += 3; + + theta = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 : 0 ]; // 0 => connect to start + phi = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 + 1 : 1 ]; // 1 => connect to start + + x2 = Math.sin( theta ) * Math.cos( phi ); + y2 = Math.cos( theta ); + z2 = -Math.sin( theta ) * Math.sin( phi ); + + xv2 = x2 - x1; + yv2 = y2 - y1; + zv2 = z2 - z1; + + len = length( xv2, yv2, zv2 ); + + if ( len > d ) { + + count = Math.ceil( len / d ); + + for ( let k = 1; k < count; k ++ ) { + + x = x1 + k * xv2 / count; + y = y1 + k * yv2 / count; + z = z1 + k * zv2 / count; + + len = length( x, y, z ); + + g.positions[ posIdx ] = x / len; + g.positions[ posIdx + 1 ] = y / len; + g.positions[ posIdx + 2 ] = z / len; + + fronts[ holeNumber ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + } + + x1 = x2; + y1 = y2; + z1 = z2; + + } + + boundings[ holeNumber ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + holeNumber ++; + + } + + function makeCircularHole( i ) { + + let theta = g.holes[ i ][ 0 ]; + let phi = g.holes[ i ][ 1 ]; + let count = g.holes[ i ][ 2 ]; + + let xmin, ymin, zmin, xmax, ymax, zmax; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + xp = Math.sin( theta ) * Math.cos( phi ); + yp = Math.cos( theta ); + zp = -Math.sin( theta ) * Math.sin( phi ); + + let r = count / detail / 2; // radius cutting circle + + h = Math.sqrt( 1 - r * r ); + + if ( !(xp === 0 && yp === 0 ) ) { + + xt1 = -yp; + yt1 = xp; + zt1 = 0; + + } else { + + xt1 = 0; + yt1 = 1; + zt1 = 0; + + } + + // cross + + xt2 = yp * zt1 - zp * yt1; + yt2 = zp * xt1 - xp * zt1; + zt2 = xp * yt1 - yp * xt1; + + len = length( xt1, yt1, zt1 ); // to normalize + + xt1 = xt1 / len; + yt1 = yt1 / len; + zt1 = zt1 / len; + + len = length( xt2, yt2, zt2 ); // to normalize + + xt2 = xt2 / len; + yt2 = yt2 / len; + zt2 = zt2 / len; + + xc = h * xp; + yc = h * yp; + zc = h * zp; + + g.circles.push( [ xc, yc, zc, r, count ] ); // for external use + + fronts[ holeNumber ] = []; + boundings[ holeNumber ] = []; + + for ( let i = 0, phi = 0; i < count; i ++, phi += 2 * Math.PI / count ) { + + x = xc + Math.cos( phi ) * r * xt1 + Math.sin( phi ) * r * xt2; + y = yc + Math.cos( phi ) * r * yt1 + Math.sin( phi ) * r * yt2; + z = zc + Math.cos( phi ) * r * zt1 + Math.sin( phi ) * r * zt2; + + g.positions[ posIdx ] = x; + g.positions[ posIdx + 1 ] = y; + g.positions[ posIdx + 2 ] = z; + + fronts[ holeNumber ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + boundings[ holeNumber ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + holeNumber ++; + + } + + function getSystemAtPoint( i ) { + + getPrevPoint( i ); + getPoint( i ); + + // centerAngle = Math.acos( Math.abs( x1 * xp + y1 * yp + z1 * zp ) ); + // r = Math.sin( centerAngle ); // radius cutting circle + // h = Math.cos( centerAngle ); // distance center to cutting circle + + h = Math.abs( x1 * xp + y1 * yp + z1 * zp ); + + // center cutting circle (refers to previous point) + xc = h * xp; + yc = h * yp; + zc = h * zp; + + // first tangent + xt1 = x1 - xc; + yt1 = y1 - yc; + zt1 = z1 - zc; + + len = length( xt1, yt1, zt1 ); // to normalize + + xt1 = xt1 / len; + yt1 = yt1 / len; + zt1 = zt1 / len; + + // cross, second tangent (sphere radius 1: p equals normal) + + xt2 = yp * zt1 - zp * yt1; + yt2 = zp * xt1 - xp * zt1; + zt2 = xp * yt1 - yp * xt1; + + } + + function getPrevPoint( i ) { + + frontPosIdx = front[ prevFront( i ) ].idx * 3 ; + x1 = g.positions[ frontPosIdx ]; + y1 = g.positions[ frontPosIdx + 1 ]; + z1 = g.positions[ frontPosIdx + 2 ]; + + } + + function getPoint( i ) { + + frontPosIdx = front[ i ].idx * 3; + xp = g.positions[ frontPosIdx ]; + yp = g.positions[ frontPosIdx + 1 ]; + zp = g.positions[ frontPosIdx + 2 ]; + + } + + function getNextPoint( i ) { + + frontPosIdx = front[ nextFront( i ) ].idx * 3; + x2 = g.positions[ frontPosIdx ]; + y2 = g.positions[ frontPosIdx + 1 ]; + z2 = g.positions[ frontPosIdx + 2 ]; + + } + +} + +exports.createSphereWithHoles = createSphereWithHoles; +exports.buildSphereWithHolesObj = buildSphereWithHolesObj; +exports.buildSphereWithHoles = buildSphereWithHoles; + +// ...................................... - .................................................. + +//################################################################################################# + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); \ No newline at end of file diff --git a/examples/js/createhole.js b/examples/js/createhole.js new file mode 100644 index 0000000..6d8a31d --- /dev/null +++ b/examples/js/createhole.js @@ -0,0 +1,1663 @@ +import * as THREE from 'three'; + +var g; // THREE.BufferGeometry + +//################################################################################################# + +// Algorithm based on (simplified for sphere with holes) +// de: https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdg0/cdg0n.pdf +// en: https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdgen0104.pdf +// + +// ............................ Sphere with Holes (Triangulation) ................................. + +function createSphereWithHoles( arg1, arg2 ) { + + g = this; // THREE.BufferGeometry() - geometry object from three.js + + g.buildSphereWithHolesObj = buildSphereWithHolesObj; + g.buildSphereWithHoles = buildSphereWithHoles; + + if( typeof arg1 === 'number' ) { // variant detail, holes are optional, radius = 1 is fixed - use three.js .scale + // Variant with less effort in the algorithm! Other angle calculation too. + + g.detail = arg1; // count of triangles for half a great circle + g.holes = arg2 !== undefined ? arg2 : []; // optional + /* holes, example: + holes = [ + // circular hole, 3 elements: [ theta, phi, count ] + [ 2.44, 0.41, 12 ], + [ 0.72, 2.55, 19 ], + // points hole,: array of points theta, phi, ... (last point is connected to first) + [ 0,0, 0.5,-0.8, 0.25,-0.27, 0.4,0.3, 0.3,0.72 ] + ]; + */ + + g.buildSphereWithHoles( ); + + } else { // variant parameters object { d: div4: holes: } all elements optional + + g.d = arg1.d !== undefined ? arg1.d : 2 * Math.sin( Math.PI / 24 ); // to g.div4 default + g.div4 = arg1.div4 !== undefined ? arg1.div4 : 6; // 6 * 4 = 24 great circle divisions + g.holes = arg1.holes !== undefined ? arg1.holes : []; + + g.detail = g.div4 * 4; // division of the great circle + g.radius = g.d / Math.sin( Math.PI / g.detail ) / 2; // sphere radius, for external use as well + + /* holes, example: + holes: [ + // circular hole, 3 elements: [ theta, phi, div4Hole ], div4Hole <= div4 + [ 1.82, 0.41, 12 ], + // points hole,: array of points theta, phi, ... (last point is connected to first) + [ 0,0, 0.5,-0.8, 0.25,-0.27, 0.4,0.3, 0.3,0.72 ] + ] + */ + + g.buildSphereWithHolesObj( ); + + } + +} + +function buildSphereWithHolesObj( ) { + + const dd = g.d * g.d; + + const squareLength = ( x,y,z ) => ( x*x + y*y + z*z ); + const length = ( x, y, z ) => ( Math.sqrt( x * x + y * y + z * z ) ); + const prevFront = ( i ) => ( i !== 0 ? i - 1 : front.length - 1 ); + const nextFront = ( i ) => ( i !== front.length - 1 ? i + 1 : 0 ); + const determinant = ( xa,ya,za, xb,yb,zb, xc,yc,zc ) => ( xa*yb*zc + ya*zb*xc + za*xb*yc - za*yb*xc - xa*zb*yc - ya*xb*zc ); + + let m; // index of the current front point + let n; // number of new points + let nT; // number of new triangles + let nIns; // number of new points (after union or split) + let dAng; // partial angle + let len, d1, d2, d12; // lengths + let iSplit, jSplit; // split front indices + let iUnite, jUnite, fUnite; // unite front indices, front number (to unite) + + // points and vectors: + let x, y, z, xp, yp, zp; // coordinates point and actual point p + let x1, y1, z1, x2, y2, z2; // previous and next point to p in front + let xn, yn, zn; // normal, gradient (sphere: normalized point) + let xt1, yt1, zt1, xt2, yt2, zt2; // tangents + let xs1, ys1, xs2, ys2; // p in tangential system (only x, y required) + let xc, yc, zc; // actual point as center point for new points + + // preparation + + const faceCount = g.detail * g.detail * 8 ; + const posCount = g.detail * g.detail * 6 ; + + g.indices = new Uint32Array( faceCount * 3 ); + g.positions = new Float32Array( posCount * 3 ); + //g.normals = new Float32Array( posCount * 3 ); + + g.setIndex( new THREE.BufferAttribute( g.indices, 1 ) ); + g.setAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) ); + + let posIdx = 0; + let indIdx = 0; + let frontPosIdx, unionIdxA, unionIdxB, splitIdx; + + let front = []; // active front // front[ i ]: object { idx: 0, ang: 0 } + let partFront = []; // separated part of the active front (to split) + let insertFront = []; // new front points to insert into active front + let fronts = []; // all fronts + let partBounds = []; // bounding box of partFront [ xmin, xmax, ymin, ymax, zmin, zmax ] + let boundings = []; // fronts bounding boxes + let smallAngles = []; // new angles < 1.5 + + let frontNo, frontStock; + let unite = false; + let split = false; + + frontNo = 0; // active front number + frontStock = 0; // number of fronts still to be processed + + // define holes fronts + + if ( g.holes.length === 0 ) { + + makeFirstTriangle( ); + + } else { + + g.circles = []; // array of arrays [ xc, yc, zc, rHole, div4Hole ], values for external use + + for ( let i = 0; i < g.holes.length; i ++ ) { + + if ( g.holes[ i ].length === 3 ) { + + makeCircularHole( i ); // [ theta, phi, div4Hole ] + + } else { + + makePointsHole( i ); // points: [ theta, phi, ... ] + + } + + } + + } + + frontNo = 0; + front = fronts[ frontNo ]; + + //////////////// DEBUG triangles ////////////////////////////////////// + // let stp = 0; + /////////////////////////////////////////////////////////////////////// + + // ------ triangulation cycle ------------- + + while ( frontStock > 0 ) { + + if ( !unite ) { // triangulation on the front + + smallAngles = []; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang === 0 ) calculateFrontAngle( i ); // is to be recalculated (angle was set to zero) + + } + + m = getMinimalAngleIndex( ); // front angle + makeNewTriangles( m ); + + if ( front.length > 9 && smallAngles.length === 0 ) { + + checkDistancesToUnite( m ); + + } + + if ( front.length === 3 ) { + + makeLastTriangle( ); // last triangle closes the front + chooseNextFront( ); // if aviable + + } + + } else { // unite the active front to another front + + uniteFront( m, iUnite, fUnite, jUnite ); + trianglesAtUnionPoints( ); + unite = false; + + } + + } + + // ..... detail functions ..... + + function makeFirstTriangle ( ) { + + fronts[ frontNo ] = []; + boundings[ frontNo ] = []; + + storePoint( 0, 0 ); // ( theta, phi ) + storePoint( Math.PI / 2 / g.div4, -Math.PI / 6 ); + storePoint( Math.PI / 2 / g.div4, Math.PI / 6 ); + + g.indices[ 0 ] = 0; + g.indices[ 1 ] = 1; + g.indices[ 2 ] = 2; + + indIdx += 3; + + fronts[ frontNo ].push( { idx: 0, ang: 0 }, { idx: 1, ang: 0 }, { idx: 2, ang: 0 } ); + + frontNo ++; + frontStock ++; + + } + + function makePointsHole( i ) { + + let theta, phi, count, xmin, ymin, zmin, xmax, ymax, zmax, xv2, yv2, zv2; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + fronts[ frontNo ] = []; + boundings[ frontNo ] = []; + + theta = g.holes[ i ][ 0 ]; + phi = g.holes[ i ][ 1 ]; + + x1 = g.radius * Math.sin( theta ) * Math.cos( phi ); + y1 = g.radius * Math.cos( theta ); + z1 = -g.radius * Math.sin( theta ) * Math.sin( phi ); + + for ( let j = 1; j < g.holes[ i ].length / 2 + 1; j ++ ) { + + g.positions[ posIdx ] = x1; + g.positions[ posIdx + 1 ] = y1; + g.positions[ posIdx + 2 ] = z1; + + fronts[ frontNo ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x1 < xmin ? x1 : xmin; + ymin = y1 < ymin ? y1 : ymin; + zmin = z1 < zmin ? z1 : zmin; + + xmax = x1 > xmax ? x1 : xmax; + ymax = y1 > ymax ? y1 : ymax; + zmax = z1 > zmax ? z1 : zmax; + + posIdx += 3; + + theta = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 : 0 ]; // 0 => connect to start + phi = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 + 1 : 1 ]; // 1 => connect to start + + x2 = g.radius * Math.sin( theta ) * Math.cos( phi ); + y2 = g.radius * Math.cos( theta ); + z2 = -g.radius * Math.sin( theta ) * Math.sin( phi ); + + xv2 = x2 - x1; + yv2 = y2 - y1; + zv2 = z2 - z1; + + len = length( xv2, yv2, zv2 ); + + if ( len > g.d ) { + + count = Math.ceil( len / g.d ); + + for ( let k = 1; k < count; k ++ ) { + + x = x1 + k * xv2 / count; + y = y1 + k * yv2 / count; + z = z1 + k * zv2 / count; + + len = length( x, y, z ); // to bring the point to the surface (g.radius * ..) + + g.positions[ posIdx ] = g.radius * x / len; + g.positions[ posIdx + 1 ] = g.radius * y / len; + g.positions[ posIdx + 2 ] = g.radius * z / len; + + fronts[ frontNo ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + } + + x1 = x2; + y1 = y2; + z1 = z2; + + } + + boundings[ frontNo ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + frontNo ++; + frontStock ++; + + } + + function makeCircularHole( i ) { + + let xa, ya, za, xb, yb; // for rotation around z, y + + const theta = g.holes[ i ][ 0 ]; + const phi = g.holes[ i ][ 1 ]; + const div4Hole = g.holes[ i ][ 2 ]; + const countH = div4Hole * 4; + + let xmin, ymin, zmin, xmax, ymax, zmax; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + const rHole = g.d / ( Math.sin( Math.PI / countH ) * 2 ); // radius cutting circle + const h = Math.sqrt( g.radius * g.radius - rHole * rHole ); // distance: sphere center to cutting circle + + // ... hole values for external use + xp = g.radius * Math.sin( theta ) * Math.cos( phi ); + yp = g.radius * Math.cos( theta ); + zp = -g.radius * -Math.sin( theta ) * Math.sin( phi ); + + xc = h / g.radius * xp; + yc = h / g.radius * yp; + zc = h / g.radius * zp; + + g.circles.push( [ xc, yc, zc, rHole, div4Hole ] ); // values for external use + + fronts[ frontNo ] = []; + boundings[ frontNo ] = []; + + ya = h; + + for ( let i = 0, alpha = 0; i < countH; i ++, alpha += 2 * Math.PI / countH ) { + + // cutting circle on top + xa = rHole * Math.cos( alpha ); + za = rHole * Math.sin( alpha ); + + // rotate around z axis + xb = xa * Math.cos( theta ) - ya * Math.sin( theta ); + yb = xa * Math.sin( theta ) + ya * Math.cos( theta ); + + // rotate around y axis + x = -xb * Math.cos( phi ) + za * Math.sin( phi ); + z = xb * Math.sin( phi ) + za * Math.cos( phi ); + + y = yb; // for storing and checking bounds + + g.positions[ posIdx ] = x; + g.positions[ posIdx + 1 ] = y; + g.positions[ posIdx + 2 ] = z; + + fronts[ frontNo ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + boundings[ frontNo ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + frontNo ++; + frontStock ++; + + } + + function checkDistancesToUnite( m ) { // for new active front points + + let idxJ, xChk, yChk, zChk, ddUnite; + let ddUniteMin = Infinity; + unite = false; + + for ( let i = 0; i < insertFront.length; i ++ ) { + + getPoint( m + i ); + + for ( let f = 0; f < fronts.length; f ++ ) { + + if ( f !== frontNo ) { + + xChk = ( xp > boundings[ f ][ 0 ] - g.d ) && ( xp < boundings[ f ][ 3 ] + g.d ); + yChk = ( yp > boundings[ f ][ 1 ] - g.d ) && ( yp < boundings[ f ][ 4 ] + g.d ); + zChk = ( zp > boundings[ f ][ 2 ] - g.d ) && ( zp < boundings[ f ][ 5 ] + g.d ); + + if ( xChk || yChk || zChk ) { + + for ( let j = 0; j < fronts[ f ].length; j ++ ) { + + idxJ = fronts[ f ][ j ].idx * 3; + + // Hint: here (2) is exceptionally point in other front! + x2 = g.positions[ idxJ ]; + y2 = g.positions[ idxJ + 1 ]; + z2 = g.positions[ idxJ + 2 ]; + + ddUnite = squareLength ( x2 - xp, y2 - yp, z2 - zp ); + + if ( ddUnite < dd && ddUnite < ddUniteMin ) { + + ddUniteMin = ddUnite; + iUnite = i; + jUnite = j; + fUnite = f; + unite = true; + + } + + } + + } + + } + + } + + } + + } + + function uniteFront( m, i, f, j ) { + + let tmp = []; + + tmp[ 0 ] = front.slice( 0, m + i + 1 ); + tmp[ 1 ] = fronts[ f ].slice( j , fronts[ f ].length ); + tmp[ 2 ] = fronts[ f ].slice( 0 , j + 1 ); + tmp[ 3 ] = front.slice( m + i, front.length ); + + unionIdxA = m + i; + unionIdxB = m + i + 1 + fronts[ f ].length + + front = []; + + for ( let t = 0; t < 4; t ++ ) { + + for ( let k = 0; k < tmp[ t ].length ; k ++ ) { + + front.push( tmp[ t ][ k ] ); + + } + + } + + fronts[ f ] = []; // empty united front + + frontStock -= 1; // front is eliminated + + } + + function trianglesAtUnionPoints( ) { + + nIns = 0; // count inserted points + + calculateFrontAngle( unionIdxA ); + calculateFrontAngle( unionIdxA + 1 ); + + if ( front[ unionIdxA ].ang < front[ unionIdxA + 1 ].ang ) { + + makeNewTriangles( unionIdxA ); + nIns += n - 1; + calculateFrontAngle( unionIdxA + 1 + nIns ); + makeNewTriangles( unionIdxA + 1 + nIns ); + nIns += n - 1; + + } else { + + makeNewTriangles( unionIdxA + 1 ); + nIns += n - 1; + calculateFrontAngle( unionIdxA ); + makeNewTriangles( unionIdxA ); + nIns += n - 1; + } + + calculateFrontAngle( unionIdxB + nIns ); + calculateFrontAngle( unionIdxB + 1 + nIns ); + + if ( front[ unionIdxB + nIns ].ang < front[ unionIdxB + 1 + nIns ].ang ) { + + makeNewTriangles( unionIdxB + nIns ); + nIns += n - 1; + calculateFrontAngle( unionIdxB + 1 + nIns ); + makeNewTriangles( unionIdxB + 1 + nIns ); + + } else { + + makeNewTriangles( unionIdxB + 1 + nIns ); + calculateFrontAngle( unionIdxB + nIns ); + makeNewTriangles( unionIdxB + nIns ); + + } + + } + + function getMinimalAngleIndex( ) { + + let angle = Infinity; + let m; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang < angle ) { + + angle = front[ i ].ang ; + m = i; + + } + + } + + return m; + + } + + function makeNewTriangles( m ) { + + // m: minimal angle (index) + + insertFront = []; // new front points + + nT = Math.floor( 3 * front[ m ].ang / Math.PI ) + 1; // number of new triangles + + dAng = front[ m ].ang / nT; + + getSystemAtPoint( m ); + getNextPoint( m ); + + d1 = length( x1 - xp, y1 - yp, z1 - zp ); + d2 = length( x2 - xp, y2 - yp, z2 - zp ); + d12 = length( x2 - x1, y2 - y1, z2 - z1 ); + + // correction of dAng, nT in extreme cases + + if ( dAng < 0.8 && nT > 1 ) { + + nT --; + dAng = front[ m ].ang / nT; + + } + + if ( dAng > 0.8 && nT === 1 && d12 > 1.25 * g.d ) { + + nT = 2; + dAng = front[ m ].ang / nT; + + } + + if ( d1 * d1 < 0.2 * dd || d2 * d2 < 0.2 * dd ) { + + nT = 1; + + } + + n = nT - 1; // n number of new points + + if ( n === 0 ) { // one triangle + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + front[ prevFront( m ) ].ang = 0; + front[ nextFront( m ) ].ang = 0; + + front.splice( m, 1 ); // delete point with index m from the front + + } else { // more then one triangle + + xc = xp; + yc = yp; + zc = zp; + + for ( let i = 0, phi = dAng; i < n; i ++, phi += dAng ) { + + xp = xc + Math.cos( phi ) * g.d * xt1 + Math.sin( phi ) * g.d * xt2; + yp = yc + Math.cos( phi ) * g.d * yt1 + Math.sin( phi ) * g.d * yt2; + zp = zc + Math.cos( phi ) * g.d * zt1 + Math.sin( phi ) * g.d * zt2; + + len = length( xp, yp, zp ); // to bring the point to the surface (g.radius * ..) + + g.positions[ posIdx ] = g.radius * xp / len; + g.positions[ posIdx + 1 ] = g.radius * yp / len; + g.positions[ posIdx + 2 ] = g.radius * zp / len; + + insertFront.push( { idx: posIdx / 3, ang: 0 } ); + + posIdx += 3; + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx + g.indices[ indIdx + 2 ] = insertFront[ 0 ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + front[ prevFront( m ) ].ang = 0; + + for ( let i = 0; i < n - 1; i ++ ) { + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ i ].idx; + g.indices[ indIdx + 2 ] = insertFront[ i + 1 ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ n - 1 ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + front[ nextFront( m ) ].ang = 0; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + replaceFront( m, insertFront ); // replaces front[ m ] with new points + + } + + } + + function makeLastTriangle( ) { + + g.indices[ indIdx ] = front[ 2 ].idx; + g.indices[ indIdx + 1 ] = front[ 1 ].idx + g.indices[ indIdx + 2 ] = front[ 0 ].idx; + + indIdx += 3; + + /////////////// DEBUG triangles ////////////////////// + // stp ++; + //////////////////////////////////////////////////////// + + front = []; + + fronts[ frontNo ] = []; + + frontStock -= 1; // close front + + } + + function chooseNextFront( ) { + + if ( frontStock > 0 ) { + + for ( let i = 0; i < fronts.length; i ++ ) { + + if ( fronts[ i ].length > 0 ) { + + frontNo = i; + break; + + } + + } + + front = fronts[ frontNo ]; + + smallAngles = []; + + for ( let i = 0; i < front.length; i ++ ) { + + calculateFrontAngle( i ); // recalculate angles of next front + + } + + } + + } + + function atan2PI( x, y ) { + + let phi = Math.atan2( y, x ); + + if ( phi < 0 ) phi = phi + Math.PI * 2; + + return phi; + + } + + function coordTangentialSystem( ) { + + let det = determinant( xt1, yt1, zt1, xt2, yt2, zt2, xn, yn, zn ); + + xs1 = determinant( x1 - xp, y1 - yp, z1 - zp, xt2, yt2, zt2, xn, yn, zn ) / det; + ys1 = determinant( xt1, yt1, zt1, x1 - xp, y1 - yp, z1 - zp, xn, yn, zn ) / det; + //zs1 = determinant( xt1, yt1, zt1, xt2, yt2, zt2, x1 - xp, y1 - yp, z1 - zp ) / det; // not needed + + xs2 = determinant( x2 - xp, y2 - yp, z2 - zp, xt2, yt2, zt2, xn, yn, zn ) / det; + ys2 = determinant( xt1, yt1, zt1, x2 - xp, y2 - yp, z2 - zp, xn, yn, zn ) / det; + //zs2 = determinant( xt1, yt1, zt1, xt2, yt2, zt2, x2 - xp, y2 - yp, z2 - zp ) / det; // not needed + + } + + function calculateFrontAngle( i ) { + + let ang1, ang2; + + getSystemAtPoint( i ); + getNextPoint( i ); + + coordTangentialSystem( ); + + ang1 = atan2PI( xs1, ys1 ); + ang2 = atan2PI( xs2, ys2 ); + + if ( ang2 < ang1 ) ang2 += Math.PI * 2; + + front[ i ].ang = ang2 - ang1; + + if ( front[ i ].ang < 1.5 ) smallAngles.push( i ); + + } + + function partFrontBounds( ) { + + let idx, xmin, ymin, zmin, xmax, ymax, zmax; + + partBounds = []; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + for( let i = 0; i < partFront.length; i ++ ) { + + idx = partFront[ i ].idx * 3; + + x = g.positions[ idx ]; + y = g.positions[ idx + 1 ]; + z = g.positions[ idx + 2 ]; + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + } + + partBounds.push( xmin, ymin, zmin, xmax, ymax, zmax ); + + boundings.push( partBounds ); + + } + + function replaceFront( m, fNew ) { + + let rear = front.splice( m, front.length - m ); + + for ( let i = 0; i < fNew.length; i ++ ) { + + front.push( fNew[ i ] ); // new front points + + } + + for ( let i = 1; i < rear.length; i ++ ) { // i = 1: without old front point m + + front.push( rear[ i ] ); + + } + + } + + function getSystemAtPoint( i ) { + + getPrevPoint( i ); + getPoint( i ); + + len = length( xp, yp, zp ); // to normalize + + xn = xp / len; + yn = yp / len + zn = zp / len; + + // centerAngle = Math.acos( Math.abs( x1 * xp + y1 * yp + z1 * zp ) / ( g.radius * g.radius ) ); + const h = Math.abs( x1 * xp + y1 * yp + z1 * zp ) / g.radius; // distance: sphere center to cutting circle + + // center cutting circle (refers to previous point) + xc = h / g.radius * xp; + yc = h / g.radius * yp; + zc = h / g.radius * zp; + + // first tangent + xt1 = x1 - xc; + yt1 = y1 - yc; + zt1 = z1 - zc; + + len = length( xt1, yt1, zt1 ); // to normalize + + xt1 = xt1 / len; + yt1 = yt1 / len; + zt1 = zt1 / len; + + // cross, second tangent + + xt2 = yn * zt1 - zn * yt1; + yt2 = zn * xt1 - xn * zt1; + zt2 = xn * yt1 - yn * xt1; + + } + + function storePoint( theta, phi ) { + + g.positions[ posIdx ] = g.radius * Math.sin( theta ) * Math.cos( phi ); + g.positions[ posIdx + 1 ] = g.radius * Math.cos( theta ); + g.positions[ posIdx + 2 ] = -g.radius * Math.sin( theta ) * Math.sin( phi ); + + posIdx += 3; + + } + + function getPrevPoint( i ) { + + frontPosIdx = front[ prevFront( i ) ].idx * 3; + + x1 = g.positions[ frontPosIdx ]; + y1 = g.positions[ frontPosIdx + 1 ]; + z1 = g.positions[ frontPosIdx + 2 ]; + + } + + function getPoint( i ) { + + frontPosIdx = front[ i ].idx * 3; + + xp = g.positions[ frontPosIdx ]; + yp = g.positions[ frontPosIdx + 1 ]; + zp = g.positions[ frontPosIdx + 2 ]; + + } + + function getNextPoint( i ) { + + frontPosIdx = front[ nextFront( i ) ].idx * 3; + + x2 = g.positions[ frontPosIdx ]; + y2 = g.positions[ frontPosIdx + 1 ]; + z2 = g.positions[ frontPosIdx + 2 ]; + + } + +} + +function buildSphereWithHoles( ) { + + const length = ( x, y, z ) => ( Math.sqrt( x * x + y * y + z * z ) ); + const prevFront = ( i ) => ( i !== 0 ? i - 1 : front.length - 1 ); + const nextFront = ( i ) => ( i !== front.length - 1 ? i + 1 : 0 ); + + let d; // rough edge length of the triangles + let m; // index of the current front point + let n; // number of new points + let nT; // number of new triangles + let nUnion; // number of new points (after union) + let dAng; // partial angle + let len, d1, d2, d12, dd1, dd2, dd12; // lengths and their squares + let h; // distance center to circle + let acute, concave; // front angle properties + + // points and vectors: + let x, y, z, xp, yp, zp, xc, yc, zc, x1, y1, z1, x2, y2, z2, xt1, yt1, zt1, xt2, yt2, zt2, xv1, yv1, zv1, xv2, yv2, zv2; + + // preparation + + const faceCount = g.detail * g.detail * 4; + const posCount = g.detail * g.detail * 3; + + g.indices = new Uint32Array( faceCount * 3 ); + g.positions = new Float32Array( posCount * 3 ); + //g.normals = new Float32Array( posCount * 3 ); + + g.setIndex( new THREE.BufferAttribute( g.indices, 1 ) ); + g.addAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) ); + + d = Math.PI / g.detail; // rough side length of the triangles + + let posIdx = 0; + let indIdx = 0; + let frontPosIdx, unionIdxA, unionIdxB; + + let front = []; // active front // front[ i ]: object { idx: 0, ang: 0 } + let partFront = []; // separated part of the active front + let insertFront = []; // new front points to insert into active front + let fronts = []; // all fronts + let partBounds = []; // bounding box of partFront [ xmin, xmax, ymin, ymax, zmin, zmax ] + let boundings = []; // fronts bounding boxes + let smallAngles = []; // new angles < 1.5 + + let start = true; + let united = false; + + // define holes + + let holeNumber; + + if ( g.holes.length === 0 ) { + + makeFirstTriangle( ); + + } else { + + g.circles = []; // [ center, r, count ] of holes for external use + + holeNumber = 0; + + for ( let i = 0; i < g.holes.length; i ++ ) { + + if ( g.holes[ i ].length === 3 ) { + + makeCircularHole( i ); // [ theta, phi, count ] + + } else { + + makePointsHole( i ); // points: [ theta, phi, ... ] + + } + + } + + } + + let activeFrontNo = 0; + front = fronts[ activeFrontNo ]; + + // ------ triangulation cycle ------------- + + while ( front.length > 3 || start ) { + + if ( start ) start = false; + + if ( front.length > 9 && smallAngles.length === 0 ) { + + // checkDistancesInFront( ); + checkDistancesToFronts( m ); + + } + + if ( united ) { + + trianglesAtUnionPoints( ); + + } else { + + calculateFrontAngles( ); + m = getMinimalAngleIndex( ); // front angle + newTriangles( m ); + + } + + } // end while + + makeLastTriangle( ); + + // ..... main detail functions ..... + + function checkDistancesToFronts( m ) { + + let idx, idxJ, xChk, yChk, zChk; + + for ( let i = 0; i < insertFront.length; i ++ ) { + + idx = front[ m + i ].idx * 3 + + xp = g.positions[ idx ]; + yp = g.positions[ idx + 1 ]; + zp = g.positions[ idx + 2 ]; + + for ( let f = 0; f < fronts.length; f ++ ) { + + if ( f !== activeFrontNo ) { + + xChk = ( xp > boundings[ f ][ 0 ] - d ) && ( xp < boundings[ f ][ 3 ] + d ); + yChk = ( yp > boundings[ f ][ 1 ] - d ) && ( yp < boundings[ f ][ 4 ] + d ); + zChk = ( zp > boundings[ f ][ 2 ] - d ) && ( zp < boundings[ f ][ 5 ] + d ); + + if ( xChk || yChk || zChk ) { + + for ( let j = 0; j < fronts[ f ].length; j ++ ) { + + idxJ = fronts[ f ][ j ].idx * 3; + x2 = g.positions[ idxJ ]; + y2 = g.positions[ idxJ + 1 ]; + z2 = g.positions[ idxJ + 2 ]; + + if ( length( x2 - xp, y2 - yp, z2 - zp ) < d ) { + + uniteFront( m, i, f, j ); + + } + + } + + } + + } + + } + + } + + } + + function calculateFrontAngles( ) { + + smallAngles = []; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang === 0 ) { + + frontAngle( i ); + + } + + } + + } + + function getMinimalAngleIndex( ) { + + let angle = Infinity; + let m; + + for ( let i = 0; i < front.length; i ++ ) { + + if( front[ i ].ang < angle ) { + + angle = front[ i ].ang ; + m = i; + + } + + } + + return m; + + } + + function newTriangles( m ) { + + // m: minimal angle (index) + + insertFront = []; + + nT = Math.floor( 3 * front[ m ].ang / Math.PI ) + 1; // number of new triangles + + dAng = front[ m ].ang / nT; + + getSystemAtPoint( m ); + getNextPoint( m ); + + d1 = length( x1 - xp, y1 - yp, z1 - zp ); + d2 = length( x2 - xp, y2 - yp, z2 - zp ); + d12 = length( x2 - x1, y2 - y1, z2 - z1 ); + + // correction of dAng, nT in extreme cases + + if ( dAng < 0.8 && nT > 1 ) { + + nT --; + dAng = front[ m ].ang / nT; + + } + + if ( dAng > 0.8 && nT === 1 && d12 > 1.25 * d ) { + + nT = 2; + dAng = front[ m ].ang / nT; + + } + + if ( d1 * d1 < 0.2 * d * d || d2 * d2 < 0.2 * d * d ) { + + nT = 1; + + } + + n = nT - 1; // n number of new points + + if ( n === 0 ) { // one triangle + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + indIdx += 3; + + front[ prevFront( m ) ].ang = 0; + front[ nextFront( m ) ].ang = 0; + + } else { // more then one triangle + + for ( let i = 0, phi = dAng; i < n; i ++, phi += dAng ) { + + xp = xc + Math.cos( phi ) * d * xt1 + Math.sin( phi ) * d * xt2; + yp = yc + Math.cos( phi ) * d * yt1 + Math.sin( phi ) * d * yt2; + zp = zc + Math.cos( phi ) * d * zt1 + Math.sin( phi ) * d * zt2; + + len = length( xp, yp, zp ); // to normalize + + g.positions[ posIdx ] = xp / len; + g.positions[ posIdx + 1 ] = yp / len; + g.positions[ posIdx + 2 ] = zp / len; + + insertFront.push( { idx: posIdx / 3, ang: 0 } ); + + posIdx += 3; + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = front[ prevFront( m ) ].idx + g.indices[ indIdx + 2 ] = insertFront[ 0 ].idx; + + indIdx += 3; + + front[ prevFront( m ) ].ang = 0; + + for ( let i = 0; i < n - 1; i ++ ) { + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ i ].idx; + g.indices[ indIdx + 2 ] = insertFront[ i + 1 ].idx; + + indIdx += 3; + + } + + g.indices[ indIdx ] = front[ m ].idx; + g.indices[ indIdx + 1 ] = insertFront[ n - 1 ].idx; + g.indices[ indIdx + 2 ] = front[ nextFront( m ) ].idx; + + front[ nextFront( m ) ].ang = 0; + + indIdx += 3; + + } + + replaceFront( m, insertFront ); // replaces front[ m ] with new points + + } + + function trianglesAtUnionPoints( ) { + + nUnion = 0; // count inserted points + + frontAngle( unionIdxA ); + frontAngle( unionIdxA + 1 ); + + if ( front[ unionIdxA ].ang < front[ unionIdxA + 1 ].ang ) { + + newTriangles( unionIdxA ); + nUnion += n - 1; + frontAngle( unionIdxA + 1 + nUnion ); + newTriangles( unionIdxA + 1 + nUnion ); + nUnion += n - 1; + + } else { + + newTriangles( unionIdxA + 1 ); + nUnion += n - 1; + frontAngle( unionIdxA ); + newTriangles( unionIdxA ); + nUnion += n - 1; + } + + frontAngle( unionIdxB + nUnion ); + frontAngle( unionIdxB + 1 + nUnion ); + + if ( front[ unionIdxB + nUnion ].ang < front[ unionIdxB + 1 + nUnion ].ang ) { + + newTriangles( unionIdxB + nUnion ); + nUnion += n - 1; + frontAngle( unionIdxB + 1 + nUnion ); + newTriangles( unionIdxB + 1 + nUnion ); + + } else { + + newTriangles( unionIdxB + 1 + nUnion ); + frontAngle( unionIdxB + nUnion ); + newTriangles( unionIdxB + nUnion ); + + } + + united = false; + + } + + // ..... help functions ..... + + function frontAngle( i ) { + + getPrevPoint( i ); // (1) + getPoint( i ); + getNextPoint( i ); // (2) + + // centerAngle = Math.acos( Math.abs( x1 * xp + y1 * yp + z1 * zp ) ); + // r = Math.sin( centerAngle ); // radius circle + // h = Math.cos( centerAngle ); // distance center to circle + + h = Math.abs( x1 * xp + y1 * yp + z1 * zp ); + + // center cutting circle (refers to previous point) + xc = h * xp; + yc = h * yp; + zc = h * zp; + + xv1 = xc - x1; + yv1 = yc - y1; + zv1 = zc - z1; + + len = length( xv1, yv1, zv1 ); // to normalize + + xv1 = xv1 / len; + yv1 = yv1 / len; + zv1 = zv1 / len; + + xv2 = x2 - xc; + yv2 = y2 - yc; + zv2 = z2 - zc; + + len = length( xv2, yv2, zv2 ); // to normalize + + xv2 = xv2 / len; + yv2 = yv2 / len; + zv2 = zv2 / len; + + front[ i ].ang = Math.acos( Math.abs( xv1 * xv2 + yv1 * yv2 + zv1 * zv2 ) ); + + // cross, to detect curvature + x = yv1 * zv2 - zv1 * yv2; + y = zv1 * xv2 - xv1 * zv2; + z = xv1 * yv2 - yv1 * xv2; + + len = length( x, y, z ); // to normalize + + x = xp + x / len; + y = yp + y / len; + z = zp + z / len; + + concave = ( length( x, y, z ) < 1 ); + + d1 = length( x1 - xp, y1 - yp, z1 - zp ); + d2 = length( x2 - xp, y2 - yp, z2 - zp ); + d12 = length( x2 - x1, y2 - y1, z2 - z1 ); + + dd1 = d1 * d1; + dd2 = d2 * d2; + dd12 = d12 * d12; + + acute = ( dd12 < ( dd1 + dd2) ); + + // if ( concave && acute ) front[ i ].ang += 0; + if ( concave && !acute ) front[ i ].ang = Math.PI - front[ i ].ang ; + if ( !concave && acute ) front[ i ].ang = 2 * Math.PI - front[ i ].ang ; + if ( !concave && !acute ) front[ i ].ang = Math.PI + front[ i ].ang ; + + if ( front[ i ].ang < 1.5 ) smallAngles.push( i ); + + } + + function uniteFront( m, i, f, j ) { + + let tmp = []; + + tmp[ 0 ] = front.slice( 0, m + i + 1 ); + tmp[ 1 ] = fronts[ f ].slice( j , fronts[ f ].length ); + tmp[ 2 ] = fronts[ f ].slice( 0 , j + 1 ); + tmp[ 3 ] = front.slice( m + i, front.length ); + + unionIdxA = m + i; + unionIdxB = m + i + 1 + fronts[ f ].length + + front = []; + + for ( let t = 0; t < 4; t ++ ) { + + for ( let k = 0; k < tmp[ t ].length ; k ++ ) { + + front.push( tmp[ t ][ k ] ); + + } + + } + + fronts[ f ] = []; // empty united front + + united = true; + + } + + function partFrontBounds( ) { + + let idx, xmin, ymin, zmin, xmax, ymax, zmax; + + partBounds = []; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + for( let i = 0; i < partFront.length; i ++ ) { + + idx = partFront[ i ].idx * 3; + + x = g.positions[ idx ]; + y = g.positions[ idx + 1 ]; + z = g.positions[ idx + 2 ]; + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + } + + partBounds.push( xmin, ymin, zmin, xmax, ymax, zmax ); + + boundings.push( partBounds ); + + } + + function replaceFront( m, fNew ) { + + let rear = front.splice( m, front.length - m ) + + for ( let i = 0; i < fNew.length; i ++ ) { + + front.push( fNew[ i ] ); // new front points + + } + + for ( let i = 1; i < rear.length; i ++ ) { // 1: without old front point m + + front.push( rear[ i ] ); + + } + + } + + function storePoint( theta, phi ) { + + g.positions[ posIdx ] = Math.sin( theta ) * Math.cos( phi ); + g.positions[ posIdx + 1 ] = Math.cos( theta ); + g.positions[ posIdx + 2 ] = -Math.sin( theta ) * Math.sin( phi ); + + posIdx += 3; + + } + + function makeFirstTriangle ( ) { + + storePoint( 0, 0 ); // ( theta, phi ) + storePoint( d, -Math.PI / 6 ); + storePoint( d, Math.PI / 6 ); + + g.indices[ 0 ] = 0; + g.indices[ 1 ] = 1; + g.indices[ 2 ] = 2; + + indIdx += 3; + + front = []; + + front.push( { idx: 0, ang: 0 }, { idx: 1, ang: 0 }, { idx: 2, ang: 0 } ); + fronts.push( front ) + + } + + function makeLastTriangle( ) { + + g.indices[ indIdx ] = front[ 2 ].idx; + g.indices[ indIdx + 1 ] = front[ 1 ].idx + g.indices[ indIdx + 2 ] = front[ 0 ].idx; + + } + + function makePointsHole( i ) { + + let theta, phi, count, xmin, ymin, zmin, xmax, ymax, zmax; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + fronts[ holeNumber ] = []; + boundings[ holeNumber ] = []; + + theta = g.holes[ i ][ 0 ]; + phi = g.holes[ i ][ 1 ]; + + x1 = Math.sin( theta ) * Math.cos( phi ); + y1 = Math.cos( theta ); + z1 = -Math.sin( theta ) * Math.sin( phi ); + + for ( let j = 1; j < g.holes[ i ].length / 2 + 1; j ++ ) { + + g.positions[ posIdx ] = x1; + g.positions[ posIdx + 1 ] = y1; + g.positions[ posIdx + 2 ] = z1; + + fronts[ holeNumber ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x1 < xmin ? x1 : xmin; + ymin = y1 < ymin ? y1 : ymin; + zmin = z1 < zmin ? z1 : zmin; + + xmax = x1 > xmax ? x1 : xmax; + ymax = y1 > ymax ? y1 : ymax; + zmax = z1 > zmax ? z1 : zmax; + + posIdx += 3; + + theta = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 : 0 ]; // 0 => connect to start + phi = g.holes[ i ][ j < g.holes[ i ].length / 2 ? j * 2 + 1 : 1 ]; // 1 => connect to start + + x2 = Math.sin( theta ) * Math.cos( phi ); + y2 = Math.cos( theta ); + z2 = -Math.sin( theta ) * Math.sin( phi ); + + xv2 = x2 - x1; + yv2 = y2 - y1; + zv2 = z2 - z1; + + len = length( xv2, yv2, zv2 ); + + if ( len > d ) { + + count = Math.ceil( len / d ); + + for ( let k = 1; k < count; k ++ ) { + + x = x1 + k * xv2 / count; + y = y1 + k * yv2 / count; + z = z1 + k * zv2 / count; + + len = length( x, y, z ); + + g.positions[ posIdx ] = x / len; + g.positions[ posIdx + 1 ] = y / len; + g.positions[ posIdx + 2 ] = z / len; + + fronts[ holeNumber ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + } + + x1 = x2; + y1 = y2; + z1 = z2; + + } + + boundings[ holeNumber ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + holeNumber ++; + + } + + function makeCircularHole( i ) { + + let theta = g.holes[ i ][ 0 ]; + let phi = g.holes[ i ][ 1 ]; + let count = g.holes[ i ][ 2 ]; + + let xmin, ymin, zmin, xmax, ymax, zmax; + + xmin = ymin = zmin = Infinity; + xmax = ymax = zmax = -Infinity; + + xp = Math.sin( theta ) * Math.cos( phi ); + yp = Math.cos( theta ); + zp = -Math.sin( theta ) * Math.sin( phi ); + + let r = count / detail / 2; // radius cutting circle + + h = Math.sqrt( 1 - r * r ); + + if ( !(xp === 0 && yp === 0 ) ) { + + xt1 = -yp; + yt1 = xp; + zt1 = 0; + + } else { + + xt1 = 0; + yt1 = 1; + zt1 = 0; + + } + + // cross + + xt2 = yp * zt1 - zp * yt1; + yt2 = zp * xt1 - xp * zt1; + zt2 = xp * yt1 - yp * xt1; + + len = length( xt1, yt1, zt1 ); // to normalize + + xt1 = xt1 / len; + yt1 = yt1 / len; + zt1 = zt1 / len; + + len = length( xt2, yt2, zt2 ); // to normalize + + xt2 = xt2 / len; + yt2 = yt2 / len; + zt2 = zt2 / len; + + xc = h * xp; + yc = h * yp; + zc = h * zp; + + g.circles.push( [ xc, yc, zc, r, count ] ); // for external use + + fronts[ holeNumber ] = []; + boundings[ holeNumber ] = []; + + for ( let i = 0, phi = 0; i < count; i ++, phi += 2 * Math.PI / count ) { + + x = xc + Math.cos( phi ) * r * xt1 + Math.sin( phi ) * r * xt2; + y = yc + Math.cos( phi ) * r * yt1 + Math.sin( phi ) * r * yt2; + z = zc + Math.cos( phi ) * r * zt1 + Math.sin( phi ) * r * zt2; + + g.positions[ posIdx ] = x; + g.positions[ posIdx + 1 ] = y; + g.positions[ posIdx + 2 ] = z; + + fronts[ holeNumber ].push( { idx: posIdx / 3, ang: 0 } ); + + xmin = x < xmin ? x : xmin; + ymin = y < ymin ? y : ymin; + zmin = z < zmin ? z : zmin; + + xmax = x > xmax ? x : xmax; + ymax = y > ymax ? y : ymax; + zmax = z > zmax ? z : zmax; + + posIdx += 3; + + } + + boundings[ holeNumber ].push( xmin, xmax, ymin, ymax, zmin, zmax ); + + holeNumber ++; + + } + + function getSystemAtPoint( i ) { + + getPrevPoint( i ); + getPoint( i ); + + // centerAngle = Math.acos( Math.abs( x1 * xp + y1 * yp + z1 * zp ) ); + // r = Math.sin( centerAngle ); // radius cutting circle + // h = Math.cos( centerAngle ); // distance center to cutting circle + + h = Math.abs( x1 * xp + y1 * yp + z1 * zp ); + + // center cutting circle (refers to previous point) + xc = h * xp; + yc = h * yp; + zc = h * zp; + + // first tangent + xt1 = x1 - xc; + yt1 = y1 - yc; + zt1 = z1 - zc; + + len = length( xt1, yt1, zt1 ); // to normalize + + xt1 = xt1 / len; + yt1 = yt1 / len; + zt1 = zt1 / len; + + // cross, second tangent (sphere radius 1: p equals normal) + + xt2 = yp * zt1 - zp * yt1; + yt2 = zp * xt1 - xp * zt1; + zt2 = xp * yt1 - yp * xt1; + + } + + function getPrevPoint( i ) { + + frontPosIdx = front[ prevFront( i ) ].idx * 3 ; + x1 = g.positions[ frontPosIdx ]; + y1 = g.positions[ frontPosIdx + 1 ]; + z1 = g.positions[ frontPosIdx + 2 ]; + + } + + function getPoint( i ) { + + frontPosIdx = front[ i ].idx * 3; + xp = g.positions[ frontPosIdx ]; + yp = g.positions[ frontPosIdx + 1 ]; + zp = g.positions[ frontPosIdx + 2 ]; + + } + + function getNextPoint( i ) { + + frontPosIdx = front[ nextFront( i ) ].idx * 3; + x2 = g.positions[ frontPosIdx ]; + y2 = g.positions[ frontPosIdx + 1 ]; + z2 = g.positions[ frontPosIdx + 2 ]; + + } + +} + +export {createSphereWithHoles, buildSphereWithHolesObj, buildSphereWithHoles}; \ No newline at end of file diff --git a/examples/main.html b/examples/main.html index 231d51c..c489799 100644 --- a/examples/main.html +++ b/examples/main.html @@ -61,6 +61,14 @@

wegeo基于threejs

特效/边框线
+ diff --git a/examples/screenshots/mask.png b/examples/screenshots/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..4ab9192dfec12c581cd48fb35855a1e0f9085416 GIT binary patch literal 500854 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

V$Ia2Pr-;EOT;Swou%UBKOEG?I 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 z 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 LPP|Vfeti31)1-`= literal 0 HcmV?d00001 diff --git a/examples/sphereEarth/mask/mask.html b/examples/sphereEarth/mask/mask.html new file mode 100644 index 0000000..02e5445 --- /dev/null +++ b/examples/sphereEarth/mask/mask.html @@ -0,0 +1,141 @@ + + + + + + + +

+ 掩膜的设计主要是参考了https://github.com/hofk/THREEi.js这个代码,我将代码改成es module的形式了。 +

其他在几何体添加孔洞的方法:

+

https://github.com/manthrax/THREE-CSGMesh 用于物体求交、减、加。

+

https://threejs.org/examples/#webgl_geometry_convex 和这个示例。

+

思路:通过境界经纬度构造ConvexGeometry几何体,然后再使用csg对球体和ConvexGeometry求减。

+

cesium也有相应的构造掩膜的方法,但我不知道他们怎么做的,哎!汗颜~~~!

+

如果有其他优雅的方式欢迎各位大佬讨论交流,或者提交pr。

+

境界服务的数据来自阿里的datav,如果境界有误请及时联系我进行修改删除,谢谢!

+
+
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/wegeo.html b/examples/wegeo.html index 4e8be91..f875ac9 100644 --- a/examples/wegeo.html +++ b/examples/wegeo.html @@ -27,40 +27,103 @@ - + diff --git a/src/WegeoMap.js b/src/WegeoMap.js index 81e116b..8833b68 100644 --- a/src/WegeoMap.js +++ b/src/WegeoMap.js @@ -5,7 +5,7 @@ import { Layer } from './layers/Layer'; import {D3TilesLayer} from './layers/3DTilesLayer'; import { Element } from './utils/Element'; import {GUI} from 'three/examples/jsm/libs/lil-gui.module.min.js' -import { OrbitControls } from './jsm/controls/OrbitControls'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import {BingMapsProvider} from './providers/BingMapsProvider'; import { Listener } from './listener/listener'; import { RaycasterUtils } from './raycaster/utils'; @@ -184,11 +184,17 @@ export class WegeoMap { this.baseMap.moveToByCoords(coords); } - moveToByLL(lat, lon){ + /** + * 跳转到指定位置,用于球形地图 + * @param {*} lat + * @param {*} lon + * @returns + */ + moveToByLL(lat, lon, distance = 384720){ if(!this.baseMap){ return; } - this.baseMap.moveToByLL(lat, lon); + this.baseMap.moveToByLL(lat, lon, distance); } // 鼠标点击获取模型 diff --git a/src/animation/Animate.js b/src/animation/Animate.js index 29e4ff4..8813503 100644 --- a/src/animation/Animate.js +++ b/src/animation/Animate.js @@ -1,4 +1,4 @@ -import * as TWEEN from '../jsm/libs/tween.module.js'; +import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'; // import TWEEN from '@tweenjs/tween.js'; /** * 相机移动控制 diff --git a/src/effect/outline.js b/src/effect/outline.js index bde57a9..8169358 100644 --- a/src/effect/outline.js +++ b/src/effect/outline.js @@ -1,9 +1,9 @@ -import { EffectComposer } from '../jsm/postprocessing/EffectComposer.js'; -import { RenderPass } from '../jsm/postprocessing/RenderPass.js'; -import { ShaderPass } from '../jsm/postprocessing/ShaderPass.js'; -import { OutlinePass } from '../jsm/postprocessing/OutlinePass.js'; -import { OutputPass } from '../jsm/postprocessing/OutputPass.js'; -import { FXAAShader } from '../jsm/shaders/FXAAShader.js'; +import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; +import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; +import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; +import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'; +import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'; +import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; import { Vector2 } from 'three'; import { Config} from '../environment/config.js'; // 上述几个包是做outline效果必须的几个包 diff --git a/src/examples/3dtiles.js b/src/examples/3dtiles.js index 1d7a542..b7d15d1 100644 --- a/src/examples/3dtiles.js +++ b/src/examples/3dtiles.js @@ -12,12 +12,12 @@ import { MeshBasicMaterial } from 'three'; - import { OrbitControls } from '../jsm/controls/OrbitControls'; + import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { Loader3DTiles } from 'three-loader-3dtiles'; import { TilesRenderer } from '3d-tiles-renderer'; - import { GLTFLoader } from '../jsm/loaders/GLTFLoader.js'; - import Stats from '../jsm/libs/stats.module.js'; + import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; + import Stats from 'three/examples/jsm/libs/stats.module.js'; const queryParams = new URLSearchParams(document.location.search); diff --git a/src/examples/basic.js b/src/examples/basic.js index 2492933..640df58 100644 --- a/src/examples/basic.js +++ b/src/examples/basic.js @@ -1,7 +1,7 @@ // @ts-nocheck import {WebGLRenderer, Scene, Color, AmbientLight, PerspectiveCamera, LinearSRGBColorSpace} from 'three'; -import {MapControls} from '../jsm/controls/MapControls.js'; +import {MapControls} from 'three/examples/jsm/controls/MapControls.js'; import {MapView, BingMapsProvider, UnitsUtils} from '../main'; var canvas = document.getElementById('canvas'); diff --git a/src/layers/3DTilesLayer.js b/src/layers/3DTilesLayer.js index 5c4b2e0..0acaec5 100644 --- a/src/layers/3DTilesLayer.js +++ b/src/layers/3DTilesLayer.js @@ -1,9 +1,9 @@ // 多个canvas并没有id,只有父节点 import { Element } from "../utils/Element"; -import {MapControls} from '../jsm/controls/MapControls.js'; +import {MapControls} from 'three/examples/jsm/controls/MapControls.js'; import {UnitsUtils} from '../utils/UnitsUtils.js'; import { PerspectiveCamera, WebGLRenderer, Scene, Color, Raycaster, Vector3, Vector2, Clock } from 'three'; -import * as TWEEN from '../jsm/libs/tween.module.js'; +import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'; import {EffectOutline} from '../effect/outline'; import {Config} from '../environment/config'; import { Loader3DTiles } from 'three-loader-3dtiles'; diff --git a/src/layers/Layer.js b/src/layers/Layer.js index ea6ce47..f29f2a2 100644 --- a/src/layers/Layer.js +++ b/src/layers/Layer.js @@ -1,16 +1,16 @@ // 多个canvas并没有id,只有父节点 import { Element } from "../utils/Element"; -import {MapControls} from '../jsm/controls/MapControls.js'; -import { OrbitControls } from '../jsm/controls/OrbitControls'; +import {MapControls} from 'three/examples/jsm/controls/MapControls.js'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import {UnitsUtils} from '../utils/UnitsUtils.js'; import { PerspectiveCamera, WebGLRenderer, Scene, Color, Raycaster, Vector3, Vector2, ACESFilmicToneMapping, BoxGeometry, MeshBasicMaterial , Mesh, TextureLoader, PMREMGenerator, MathUtils, AmbientLight, DirectionalLight, PointLight, MOUSE } from 'three'; -import * as TWEEN from '../jsm/libs/tween.module.js'; +import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'; import {EffectOutline} from '../effect/outline'; import {Config} from '../environment/config' import BasLayer from "./basLayer"; -import { Sky } from '../jsm/objects/Sky.js'; +import { Sky } from 'three/examples/jsm/objects/Sky.js'; export class Layer extends BasLayer{ diff --git a/src/loader/ModelLoader.js b/src/loader/ModelLoader.js index 151b476..a79b247 100644 --- a/src/loader/ModelLoader.js +++ b/src/loader/ModelLoader.js @@ -1,7 +1,7 @@ -import { GLTFLoader } from '../jsm/loaders/GLTFLoader.js'; -import { DRACOLoader } from '../jsm/loaders/DRACOLoader.js'; -import { OBJLoader } from '../jsm/loaders/OBJLoader.js'; -import { SVGLoader } from '../jsm/loaders/SVGLoader.js'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; +import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; +import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js'; import { Config } from '../environment/config'; import { ObjectLoader } from 'three'; diff --git a/src/loader/Water1.js b/src/loader/Water1.js index 00541aa..66a4d98 100644 --- a/src/loader/Water1.js +++ b/src/loader/Water1.js @@ -1,4 +1,4 @@ -import {Water} from '../jsm/objects/Water.js'; +import {Water} from 'three/examples/jsm/objects/Water.js'; import { TextureLoader, Vector3 , RepeatWrapping, DoubleSide, FrontSide, BackSide} from 'three'; export class Water1 { diff --git a/src/loader/Water2.js b/src/loader/Water2.js index a84768c..52d25b4 100644 --- a/src/loader/Water2.js +++ b/src/loader/Water2.js @@ -1,5 +1,5 @@ -import {Water} from '../jsm/objects/Water2'; +import {Water} from 'three/examples/jsm/objects/Water2'; import { Colors } from '../utils/Colors'; import { Vector2, TextureLoader } from 'three'; diff --git a/src/main.js b/src/main.js index 87306c7..8122eaa 100644 --- a/src/main.js +++ b/src/main.js @@ -40,6 +40,7 @@ export {CancelablePromise} from './utils/CancelablePromise'; export {XHRUtils} from './utils/XHRUtils'; export {TextureUtils} from './utils/TextureUtils'; export {Element} from './utils/Element'; +export {AngleUtils} from './utils/AngleUtils'; export {Layer} from './layers/Layer'; export {WegeoMap} from './WegeoMap'; diff --git a/src/sky/Skybox.js b/src/sky/Skybox.js index d7be7be..2749ac7 100644 --- a/src/sky/Skybox.js +++ b/src/sky/Skybox.js @@ -4,12 +4,12 @@ export class Skybox { loadSkyBox(scale) { var aCubeMap = new CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); aCubeMap.format = RGBAFormat; @@ -33,12 +33,12 @@ export class Skybox { } loadBox(){ var cube = new CubeTextureLoader().load([ - 'png/sky/px.jpg', - 'png/sky/nx.jpg', - 'png/sky/py.jpg', - 'png/sky/ny.jpg', - 'png/sky/pz.jpg', - 'png/sky/nz.jpg' + '/examples/png/sky/px.jpg', + '/examples/png/sky/nx.jpg', + '/examples/png/sky/py.jpg', + '/examples/png/sky/ny.jpg', + '/examples/png/sky/pz.jpg', + '/examples/png/sky/nz.jpg' ]); return cube; } diff --git a/src/utils/AngleUtils.js b/src/utils/AngleUtils.js new file mode 100644 index 0000000..fd68263 --- /dev/null +++ b/src/utils/AngleUtils.js @@ -0,0 +1,20 @@ + +export class AngleUtils { + /** + * 弧度转角度 + * @param {*} rad + * @returns + */ + static radToDeg(rad) { + return rad * (180 / Math.PI); + } + /** + * 角度转弧度 + * @param {*} deg + * @returns + */ + static degToRad(deg) { + return deg * (Math.PI / 180); + } + +} \ No newline at end of file