@navaramap/three 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/dist/assets/atmosphere/README.md +1 -0
- package/dist/assets/atmosphere/higher_order_scattering.bin +0 -0
- package/dist/assets/atmosphere/higher_order_scattering.exr +0 -0
- package/dist/assets/atmosphere/irradiance.bin +0 -0
- package/dist/assets/atmosphere/irradiance.exr +0 -0
- package/dist/assets/atmosphere/scattering.bin +0 -0
- package/dist/assets/atmosphere/scattering.exr +0 -0
- package/dist/assets/atmosphere/single_mie_scattering.bin +0 -0
- package/dist/assets/atmosphere/single_mie_scattering.exr +0 -0
- package/dist/assets/atmosphere/stars.bin +0 -0
- package/dist/assets/atmosphere/transmittance.bin +0 -0
- package/dist/assets/atmosphere/transmittance.exr +0 -0
- package/dist/assets/cloud/README.md +1 -0
- package/dist/assets/cloud/local_weather.png +0 -0
- package/dist/assets/cloud/shape.bin +1 -0
- package/dist/assets/cloud/shape_detail.bin +1 -0
- package/dist/assets/cloud/turbulence.png +0 -0
- package/dist/assets/fontWorker-CEJEfRpb.js +2 -0
- package/dist/assets/fontWorker-CEJEfRpb.js.map +1 -0
- package/dist/assets/index-BUB-ijMO.js +12 -0
- package/dist/assets/index-BUB-ijMO.js.map +1 -0
- package/dist/assets/navara_wasm_font_worker_bg-665OLxjy.wasm +0 -0
- package/dist/assets/navara_wasm_worker_bg-BXhKC5bE.wasm +0 -0
- package/dist/assets/noise/README.md +1 -0
- package/dist/assets/noise/stbn.bin +0 -0
- package/dist/assets/water/README.md +3 -0
- package/dist/assets/water/waternormals.jpg +0 -0
- package/dist/index.d.ts +10114 -0
- package/dist/index.js +34481 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.cjs +6345 -0
- package/dist/index.umd.cjs.map +1 -0
- package/package.json +95 -0
- package/src/Color.ts +111 -0
- package/src/ShadowMapViewers.ts +49 -0
- package/src/atmosphere.test.ts +76 -0
- package/src/atmosphere.ts +364 -0
- package/src/bufferView.ts +90 -0
- package/src/camera.ts +182 -0
- package/src/concurrency.ts +15 -0
- package/src/constants/assets.ts +30 -0
- package/src/constants/decoder.ts +7 -0
- package/src/constants/index.ts +2 -0
- package/src/core/BaseDesc.ts +142 -0
- package/src/core/BaseHandle.ts +126 -0
- package/src/core/DescRegistry.ts +88 -0
- package/src/core/EffectDesc.ts +343 -0
- package/src/core/EffectDescRegistry.ts +31 -0
- package/src/core/InstancedMeshDesc.ts +413 -0
- package/src/core/LightDesc.ts +216 -0
- package/src/core/LightDescRegistry.ts +32 -0
- package/src/core/MeshDesc.ts +457 -0
- package/src/core/MeshDescRegistry.ts +32 -0
- package/src/core/MeshDescWithSelectiveEffect.ts +129 -0
- package/src/core/Registries.ts +59 -0
- package/src/core/SelectiveEffectRegistry.ts +90 -0
- package/src/core/ViewContext.ts +235 -0
- package/src/core/errors.ts +16 -0
- package/src/core/index.ts +50 -0
- package/src/device.ts +414 -0
- package/src/effects/CustomEffectPass.ts +28 -0
- package/src/effects/copy.ts +10 -0
- package/src/effects/effect.ts +87 -0
- package/src/effects/index.ts +4 -0
- package/src/effects/render.ts +10 -0
- package/src/evaluations/FeatureEvaluator.ts +529 -0
- package/src/evaluations/index.ts +1 -0
- package/src/event/HillshadeContext.test.ts +232 -0
- package/src/event/HillshadeContext.ts +299 -0
- package/src/event/context.ts +279 -0
- package/src/event/feature.ts +357 -0
- package/src/event/featureEvent.ts +81 -0
- package/src/event/features/billboard.ts +30 -0
- package/src/event/features/model.ts +192 -0
- package/src/event/features/modelGpuBytes.test.ts +69 -0
- package/src/event/features/modelGpuBytes.ts +87 -0
- package/src/event/features/point.ts +25 -0
- package/src/event/features/polygon.ts +23 -0
- package/src/event/features/polygonOutline.ts +19 -0
- package/src/event/features/polyline.ts +19 -0
- package/src/event/features/text.ts +71 -0
- package/src/event/hillshade.test.ts +842 -0
- package/src/event/hillshade.ts +588 -0
- package/src/event/index.ts +700 -0
- package/src/event/loaders.ts +53 -0
- package/src/event/tile.ts +19 -0
- package/src/event/worker.ts +999 -0
- package/src/event/workerTaskSettlement.test.ts +243 -0
- package/src/event/workerTaskSettlement.ts +128 -0
- package/src/inMemoryBufferStore.test.ts +60 -0
- package/src/inMemoryBufferStore.ts +67 -0
- package/src/index.iife.ts +11 -0
- package/src/index.ts +2556 -0
- package/src/input.ts +144 -0
- package/src/layer.ts +183 -0
- package/src/layers/effect/FinalCopyEffectDesc.ts +29 -0
- package/src/layers/effect/MRTPassEffectDesc.ts +105 -0
- package/src/layers/effect/README.md +9 -0
- package/src/layers/effect/SelectiveEffectDesc.ts +113 -0
- package/src/layers/effect/SkyEnvMapEffectDesc.ts +58 -0
- package/src/layers/effect/TransparentPassEffectDesc.ts +62 -0
- package/src/layers/effect/index.ts +5 -0
- package/src/layers/index.ts +1 -0
- package/src/layersManager.ts +63 -0
- package/src/light.ts +15 -0
- package/src/loaders/AbortableImageLoader.ts +203 -0
- package/src/loaders/AbortableTextureLoader.ts +74 -0
- package/src/loaders/index.ts +5 -0
- package/src/material/enhancer/DESIGN.md +300 -0
- package/src/material/enhancer/MaterialEnhancer.ts +112 -0
- package/src/material/enhancer/ShaderReplacer.test.ts +114 -0
- package/src/material/enhancer/ShaderReplacer.ts +97 -0
- package/src/material/enhancer/batchTexture.ts +8 -0
- package/src/material/enhancer/index.ts +46 -0
- package/src/material/enhancer/instancedSprite/index.ts +7 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.test.ts +214 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.ts +134 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/markers.ts +21 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/material.ts +32 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.test.ts +277 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.ts +151 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.test.ts +114 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.ts +49 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.test.ts +82 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.ts +80 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/types.ts +146 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteMaterialEnhancer.ts +105 -0
- package/src/material/enhancer/model/index.ts +25 -0
- package/src/material/enhancer/model/modelBaseEnhancer/index.test.ts +74 -0
- package/src/material/enhancer/model/modelBaseEnhancer/index.ts +110 -0
- package/src/material/enhancer/model/modelBaseEnhancer/markers.ts +27 -0
- package/src/material/enhancer/model/modelBaseEnhancer/material.test.ts +66 -0
- package/src/material/enhancer/model/modelBaseEnhancer/material.ts +44 -0
- package/src/material/enhancer/model/modelBaseEnhancer/mutates.test.ts +84 -0
- package/src/material/enhancer/model/modelBaseEnhancer/mutates.ts +47 -0
- package/src/material/enhancer/model/modelBaseEnhancer/shader.test.ts +24 -0
- package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +162 -0
- package/src/material/enhancer/model/modelBaseEnhancer/state.test.ts +103 -0
- package/src/material/enhancer/model/modelBaseEnhancer/state.ts +52 -0
- package/src/material/enhancer/model/modelBaseEnhancer/types.ts +89 -0
- package/src/material/enhancer/model/modelMaterialEnhancer.ts +91 -0
- package/src/material/enhancer/model/modelWaterEnhancer/index.test.ts +71 -0
- package/src/material/enhancer/model/modelWaterEnhancer/index.ts +175 -0
- package/src/material/enhancer/model/modelWaterEnhancer/material.test.ts +48 -0
- package/src/material/enhancer/model/modelWaterEnhancer/material.ts +25 -0
- package/src/material/enhancer/model/modelWaterEnhancer/mutates.test.ts +89 -0
- package/src/material/enhancer/model/modelWaterEnhancer/mutates.ts +89 -0
- package/src/material/enhancer/model/modelWaterEnhancer/shader.test.ts +34 -0
- package/src/material/enhancer/model/modelWaterEnhancer/shader.ts +111 -0
- package/src/material/enhancer/model/modelWaterEnhancer/state.test.ts +105 -0
- package/src/material/enhancer/model/modelWaterEnhancer/state.ts +59 -0
- package/src/material/enhancer/model/modelWaterEnhancer/types.ts +141 -0
- package/src/material/enhancer/model/pntsEnhancer/index.test.ts +96 -0
- package/src/material/enhancer/model/pntsEnhancer/index.ts +82 -0
- package/src/material/enhancer/model/pntsEnhancer/markers.ts +6 -0
- package/src/material/enhancer/model/pntsEnhancer/material.test.ts +37 -0
- package/src/material/enhancer/model/pntsEnhancer/material.ts +21 -0
- package/src/material/enhancer/model/pntsEnhancer/mutates.test.ts +80 -0
- package/src/material/enhancer/model/pntsEnhancer/mutates.ts +29 -0
- package/src/material/enhancer/model/pntsEnhancer/shader.test.ts +32 -0
- package/src/material/enhancer/model/pntsEnhancer/shader.ts +56 -0
- package/src/material/enhancer/model/pntsEnhancer/state.test.ts +87 -0
- package/src/material/enhancer/model/pntsEnhancer/state.ts +29 -0
- package/src/material/enhancer/model/pntsEnhancer/types.ts +38 -0
- package/src/material/enhancer/polygon/index.ts +22 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/index.test.ts +101 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/index.ts +130 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/markers.ts +23 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/material.test.ts +19 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/material.ts +38 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.test.ts +75 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +122 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.test.ts +25 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +249 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/state.test.ts +102 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/state.ts +92 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/types.ts +150 -0
- package/src/material/enhancer/polygon/polygonMaterialEnhancer.ts +97 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/index.test.ts +72 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/index.ts +164 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/material.test.ts +48 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/material.ts +35 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/mutates.ts +61 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.test.ts +34 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.ts +96 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/state.test.ts +98 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/state.ts +57 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/types.ts +115 -0
- package/src/material/enhancer/polyline/index.ts +7 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/index.test.ts +157 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/index.ts +154 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/markers.ts +19 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/material.test.ts +42 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/material.ts +38 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.test.ts +120 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.ts +169 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.test.ts +67 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.ts +75 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/state.test.ts +55 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/state.ts +91 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/types.ts +170 -0
- package/src/material/enhancer/polyline/polylineMaterialEnhancer.ts +102 -0
- package/src/material/enhancer/sdfText/index.ts +7 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +125 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/markers.ts +19 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.test.ts +35 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.ts +32 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +381 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +232 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +36 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +118 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +107 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +184 -0
- package/src/material/enhancer/sdfText/sdfTextMaterialEnhancer.ts +100 -0
- package/src/material/enhancer/shadowMap/index.ts +7 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/index.ts +97 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/markers.ts +10 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/material.ts +31 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/mutates.ts +8 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.test.ts +17 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.ts +53 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/state.ts +17 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/types.ts +26 -0
- package/src/material/enhancer/test-utils/shaderCompatibility.ts +124 -0
- package/src/material/enhancer/tileComposite/compose.test.ts +95 -0
- package/src/material/enhancer/tileComposite/compose.ts +112 -0
- package/src/material/enhancer/tileComposite/index.ts +23 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/index.ts +11 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/mutates.ts +132 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/shader.ts +245 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/types.ts +43 -0
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/index.ts +29 -0
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/mutates.ts +68 -0
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/shader.ts +24 -0
- package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/index.ts +29 -0
- package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/mutates.ts +34 -0
- package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/shader.ts +36 -0
- package/src/material/enhancer/tileComposite/tileWaterEnhancer/index.ts +23 -0
- package/src/material/enhancer/tileComposite/tileWaterEnhancer/mutates.ts +23 -0
- package/src/material/enhancer/tileComposite/tileWaterEnhancer/shader.ts +8 -0
- package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/index.ts +23 -0
- package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/mutates.ts +34 -0
- package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/shader.ts +13 -0
- package/src/material/enhancer/tileComposite/types.ts +58 -0
- package/src/material/index.ts +3 -0
- package/src/material/macro/index.ts +1 -0
- package/src/material/macro/tileShader.test.ts +136 -0
- package/src/material/macro/tileShader.ts +287 -0
- package/src/material/overrideMaterialsForMRT.ts +439 -0
- package/src/material/selectiveEffectMaterialSetup.test.ts +52 -0
- package/src/material/selectiveEffectMaterialSetup.ts +53 -0
- package/src/material/types/index.ts +1 -0
- package/src/material/types/uniform.ts +1 -0
- package/src/mesh/DrapedMesh.ts +101 -0
- package/src/mesh/batchTexture.test.ts +646 -0
- package/src/mesh/batchTexture.ts +437 -0
- package/src/mesh/batchedFeature.ts +186 -0
- package/src/mesh/batchedSdfText.ts +484 -0
- package/src/mesh/featureMesh.ts +43 -0
- package/src/mesh/index.ts +16 -0
- package/src/mesh/instanced.ts +117 -0
- package/src/mesh/model.ts +483 -0
- package/src/mesh/pickableMesh.ts +55 -0
- package/src/mesh/pickableMeshWrapper.ts +529 -0
- package/src/mesh/polygon.ts +699 -0
- package/src/mesh/polygonOutline.ts +393 -0
- package/src/mesh/polyline.ts +523 -0
- package/src/mesh/releaseGeometryArrays.ts +57 -0
- package/src/mesh/rtcRteHelper.ts +211 -0
- package/src/mesh/sdfText.test.ts +200 -0
- package/src/mesh/sdfText.ts +1019 -0
- package/src/mesh/sprite/billboardAtlas.test.ts +177 -0
- package/src/mesh/sprite/billboardAtlas.ts +210 -0
- package/src/mesh/sprite/billboardAtlasImageLoader.ts +29 -0
- package/src/mesh/sprite/index.ts +1 -0
- package/src/mesh/sprite/instancedSprite.ts +694 -0
- package/src/mesh/tile.test.ts +393 -0
- package/src/mesh/tile.ts +1790 -0
- package/src/object3DEvent.ts +6 -0
- package/src/orchestrators/RenderPassOrchestrator.ts +147 -0
- package/src/orchestrators/index.ts +1 -0
- package/src/passes/AllDepthCopyPass.ts +165 -0
- package/src/passes/CustomRenderPass.ts +279 -0
- package/src/passes/NormalCopyPass.ts +125 -0
- package/src/passes/RenderTargetCopyPass.ts +91 -0
- package/src/passes/SkyEnvMapPass.ts +58 -0
- package/src/passes/index.ts +5 -0
- package/src/pick/pickHelper.ts +273 -0
- package/src/pick/pickTerrain.ts +254 -0
- package/src/plugins/AttributionPlugin/AttributionPlugin.test.ts +210 -0
- package/src/plugins/AttributionPlugin/attribution.test.ts +225 -0
- package/src/plugins/AttributionPlugin/attribution.ts +355 -0
- package/src/plugins/AttributionPlugin/attributionStyles.ts +163 -0
- package/src/plugins/AttributionPlugin/index.ts +702 -0
- package/src/plugins/index.ts +1 -0
- package/src/quality.ts +1 -0
- package/src/renderOrder.ts +3 -0
- package/src/scene.test.ts +164 -0
- package/src/scene.ts +127 -0
- package/src/shaders/index.ts +1 -0
- package/src/solar.test.ts +81 -0
- package/src/solar.ts +101 -0
- package/src/source.ts +80 -0
- package/src/stats.ts +102 -0
- package/src/tasks/computeVertexNormalsAsync.ts +15 -0
- package/src/tasks/constructPolygonBatchedFeature.ts +34 -0
- package/src/tasks/constructPolylineBatchedFeature.ts +27 -0
- package/src/tasks/constructQuantizedMeshTerrainMesh.ts +24 -0
- package/src/tasks/constructTerrainMesh.ts +25 -0
- package/src/tasks/getImageDataFromBlob.ts +13 -0
- package/src/tasks/getImageDataFromImageBitmap.ts +12 -0
- package/src/tasks/parseMvtTile.ts +22 -0
- package/src/tasks/queueTask.ts +19 -0
- package/src/tasks/toCreasedNormalsAsync.ts +18 -0
- package/src/tasks/upsampleQuantizedMeshTerrainMesh.ts +44 -0
- package/src/tasks/upsampleTerrainMesh.ts +39 -0
- package/src/texture.ts +19 -0
- package/src/textures.ts +13 -0
- package/src/tileTexture/CompositeShader.snapshot.test.ts +59 -0
- package/src/tileTexture/CompositeShader.test.ts +168 -0
- package/src/tileTexture/CompositeShader.ts +53 -0
- package/src/tileTexture/SlotPlanner.test.ts +105 -0
- package/src/tileTexture/SlotPlanner.ts +102 -0
- package/src/tileTexture/TileTextureCache.test.ts +227 -0
- package/src/tileTexture/TileTextureCache.ts +192 -0
- package/src/tileTexture/TileTextureCompositor.test.ts +260 -0
- package/src/tileTexture/TileTextureCompositor.ts +421 -0
- package/src/tileTexture/__snapshots__/CompositeShader.snapshot.test.ts.snap +2649 -0
- package/src/tileTexture/index.ts +8 -0
- package/src/tileTexture/types.ts +150 -0
- package/src/type/index.ts +261 -0
- package/src/uniforms.ts +20 -0
- package/src/utils/arraysEqual.ts +9 -0
- package/src/utils/blendModes.ts +80 -0
- package/src/utils/geometry.ts +53 -0
- package/src/utils/hillshadeNormalMapGenerator.ts +193 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/object.test.ts +14 -0
- package/src/utils/object.ts +7 -0
- package/src/utils/replacer.test.ts +42 -0
- package/src/utils/replacer.ts +89 -0
- package/src/utils/screen.ts +29 -0
- package/src/utils/textureFragmentIndex.test.ts +224 -0
- package/src/utils/textureFragmentIndex.ts +97 -0
- package/src/utils/worker.ts +3 -0
- package/src/vite-env.d.ts +2 -0
- package/src/worker/index.ts +13 -0
- package/src/worker/tasks/computeVertexNormals.ts +15 -0
- package/src/worker/tasks/toCreasedNormals.ts +22 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,2556 @@
|
|
|
1
|
+
import { EventManager, EventHandler, Globe, Plugin } from "@navaramap/core";
|
|
2
|
+
import type {
|
|
3
|
+
CameraPosition,
|
|
4
|
+
ColorMap,
|
|
5
|
+
GlobeOptions,
|
|
6
|
+
Nullable,
|
|
7
|
+
XYZ,
|
|
8
|
+
Color as CoreColor,
|
|
9
|
+
LatLngHeight,
|
|
10
|
+
LatLng,
|
|
11
|
+
} from "@navaramap/core";
|
|
12
|
+
import initCore, {
|
|
13
|
+
Core,
|
|
14
|
+
CameraDirection,
|
|
15
|
+
DynamicSse as EngineDynamicSse,
|
|
16
|
+
LLE,
|
|
17
|
+
type TerrainHeightUpdatedEvent,
|
|
18
|
+
type TextureFragmentStatus,
|
|
19
|
+
} from "@navaramap/engine";
|
|
20
|
+
import {
|
|
21
|
+
FontManager,
|
|
22
|
+
type FontFamily,
|
|
23
|
+
type FontWorkerMemoryStats,
|
|
24
|
+
} from "@navaramap/font";
|
|
25
|
+
import FontWorkerURL from "@navaramap/font/fontWorker?worker&url";
|
|
26
|
+
import { initNavaraApi } from "@navaramap/three_api";
|
|
27
|
+
import {
|
|
28
|
+
initializeWorkerPool,
|
|
29
|
+
probeWorkerPoolHeap,
|
|
30
|
+
terminateWorkerPool,
|
|
31
|
+
warmUpWorkerPool,
|
|
32
|
+
workerPoolHeapStats,
|
|
33
|
+
type WorkerPoolHeapStats,
|
|
34
|
+
} from "@navaramap/worker";
|
|
35
|
+
import {
|
|
36
|
+
Scene,
|
|
37
|
+
WebGLRenderer,
|
|
38
|
+
Vector3,
|
|
39
|
+
Texture,
|
|
40
|
+
Vector2,
|
|
41
|
+
LinearFilter,
|
|
42
|
+
ClampToEdgeWrapping,
|
|
43
|
+
RepeatWrapping,
|
|
44
|
+
Group,
|
|
45
|
+
PCFShadowMap,
|
|
46
|
+
} from "three";
|
|
47
|
+
import invariant from "tiny-invariant";
|
|
48
|
+
|
|
49
|
+
import { Atmosphere, type AtmosphereOptions } from "./atmosphere";
|
|
50
|
+
import { ThreeViewCamera } from "./camera";
|
|
51
|
+
import { Color } from "./Color";
|
|
52
|
+
import { createDefaultConcurrencyManager } from "./concurrency";
|
|
53
|
+
import { WATER_NORMAL_URL } from "./constants/assets";
|
|
54
|
+
import {
|
|
55
|
+
LightDesc,
|
|
56
|
+
EffectDesc,
|
|
57
|
+
ViewContext,
|
|
58
|
+
type MeshConfig,
|
|
59
|
+
type LightConfig,
|
|
60
|
+
type EffectConfig,
|
|
61
|
+
type MeshDescConstructor,
|
|
62
|
+
type LightDescConstructor,
|
|
63
|
+
type EffectDescConstructor,
|
|
64
|
+
UnknownTypeError,
|
|
65
|
+
} from "./core";
|
|
66
|
+
import {
|
|
67
|
+
MeshHandle,
|
|
68
|
+
LightHandle,
|
|
69
|
+
EffectHandle,
|
|
70
|
+
type AnyMeshDesc,
|
|
71
|
+
} from "./core/BaseHandle";
|
|
72
|
+
import { Registries } from "./core/Registries";
|
|
73
|
+
import {
|
|
74
|
+
getCompositeAtlasSize,
|
|
75
|
+
getDefaultDynamicSse,
|
|
76
|
+
getDefaultLodFog,
|
|
77
|
+
getDefaultMemoryBudgets,
|
|
78
|
+
getDefaultMemoryCostHints,
|
|
79
|
+
getDevicePixelRatio,
|
|
80
|
+
isMobileDevice,
|
|
81
|
+
type DynamicSseSettings,
|
|
82
|
+
type LodFogSettings,
|
|
83
|
+
} from "./device";
|
|
84
|
+
import {
|
|
85
|
+
processEvent,
|
|
86
|
+
EventContext,
|
|
87
|
+
HillshadeContext,
|
|
88
|
+
type BufferLoader,
|
|
89
|
+
type FeatureHandler,
|
|
90
|
+
type GlobeHandler,
|
|
91
|
+
type LayerHandler,
|
|
92
|
+
type MeshHandler,
|
|
93
|
+
type TextureFragmentHandler,
|
|
94
|
+
type TileHandler,
|
|
95
|
+
type WorkerTaskHandler,
|
|
96
|
+
} from "./event";
|
|
97
|
+
import { TEXTURE_LOADER } from "./event/loaders";
|
|
98
|
+
import { InMemoryBufferStore } from "./inMemoryBufferStore";
|
|
99
|
+
import { registerInputEvents } from "./input";
|
|
100
|
+
import { Layer, type LayerEvent } from "./layer";
|
|
101
|
+
import {
|
|
102
|
+
MRTPassEffectDesc,
|
|
103
|
+
SkyEnvMapEffectDesc,
|
|
104
|
+
TransparentPassEffectDesc,
|
|
105
|
+
} from "./layers/effect";
|
|
106
|
+
import { FinalCopyEffectDesc } from "./layers/effect/FinalCopyEffectDesc";
|
|
107
|
+
import { LayersManager } from "./layersManager";
|
|
108
|
+
import { disposeTexture } from "./loaders";
|
|
109
|
+
import { overrideMaterialsForMRT } from "./material";
|
|
110
|
+
import type { TileMesh } from "./mesh/tile";
|
|
111
|
+
import { RenderPassOrchestrator } from "./orchestrators/RenderPassOrchestrator";
|
|
112
|
+
import { PickHelper } from "./pick/pickHelper";
|
|
113
|
+
import { TerrainPicker } from "./pick/pickTerrain";
|
|
114
|
+
import { AttributionPlugin, type AttributionPluginOptions } from "./plugins";
|
|
115
|
+
import { TexturizedSceneByTileCoordinates, type Scenes } from "./scene";
|
|
116
|
+
import { ShadowMapViewers } from "./ShadowMapViewers";
|
|
117
|
+
import { Source } from "./source";
|
|
118
|
+
import { RendererStats } from "./stats";
|
|
119
|
+
import type { TextureOptions } from "./textures";
|
|
120
|
+
import { TileTextureCompositor } from "./tileTexture";
|
|
121
|
+
import {
|
|
122
|
+
type AbortControllers,
|
|
123
|
+
type LayerDescription,
|
|
124
|
+
type SourceDescription,
|
|
125
|
+
type BuiltInEffectDescription,
|
|
126
|
+
type Descriptions,
|
|
127
|
+
type EmptyDescriptions,
|
|
128
|
+
type OmitType,
|
|
129
|
+
type MeshCache,
|
|
130
|
+
type PickedFeature,
|
|
131
|
+
type WorkerPoolPromises,
|
|
132
|
+
type RenderFlag,
|
|
133
|
+
type TileMapByHandle,
|
|
134
|
+
} from "./type";
|
|
135
|
+
import type { CommonUniforms } from "./uniforms";
|
|
136
|
+
import { isWorker, convertScreenPos, type TextureSlot } from "./utils";
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
138
|
+
/** @ts-ignore ignore: https://v3.vitejs.dev/guide/features.html#import-with-query-suffixes */
|
|
139
|
+
import WorkerURL from "./worker?url&worker";
|
|
140
|
+
|
|
141
|
+
export type { CameraOptions, CameraEvent } from "./camera";
|
|
142
|
+
|
|
143
|
+
export { ColorMap, EventHandler, Plugin } from "@navaramap/core";
|
|
144
|
+
export type {
|
|
145
|
+
Nullable,
|
|
146
|
+
XYZ,
|
|
147
|
+
LatLng,
|
|
148
|
+
LatLngHeight,
|
|
149
|
+
CameraOrientation,
|
|
150
|
+
CameraPosition,
|
|
151
|
+
ColorTuple,
|
|
152
|
+
LUT,
|
|
153
|
+
Globe,
|
|
154
|
+
Observed,
|
|
155
|
+
ObservedEvent,
|
|
156
|
+
} from "@navaramap/core";
|
|
157
|
+
export { CameraDirection } from "@navaramap/engine";
|
|
158
|
+
// CSM exports for advanced users
|
|
159
|
+
export { CascadedShadowMaps, CSMHelper } from "@navaramap/three_csm";
|
|
160
|
+
|
|
161
|
+
export * from "./type";
|
|
162
|
+
export * from "./constants";
|
|
163
|
+
export * from "./light";
|
|
164
|
+
export * from "./mesh";
|
|
165
|
+
export * from "./layer";
|
|
166
|
+
export * from "./source";
|
|
167
|
+
export * from "./effects";
|
|
168
|
+
export * from "./shaders";
|
|
169
|
+
export * from "./material";
|
|
170
|
+
export * from "./core";
|
|
171
|
+
export { BufferView } from "./bufferView";
|
|
172
|
+
export * from "./plugins";
|
|
173
|
+
export * from "./layers";
|
|
174
|
+
export * from "./passes";
|
|
175
|
+
export * from "./evaluations";
|
|
176
|
+
export { SKY_RENDER_ORDER, STARS_RENDER_ORDER } from "./renderOrder";
|
|
177
|
+
export * from "@navaramap/three_api";
|
|
178
|
+
export * from "./Color";
|
|
179
|
+
export { type BlendMode, blendFunction, createReplacer } from "./utils";
|
|
180
|
+
export { Atmosphere, type AtmosphereOptions } from "./atmosphere";
|
|
181
|
+
export type { Quality } from "./quality";
|
|
182
|
+
export type { CustomObject3DEventMap } from "./object3DEvent";
|
|
183
|
+
export type {
|
|
184
|
+
FontFamily,
|
|
185
|
+
FontFace,
|
|
186
|
+
UnicodeRange,
|
|
187
|
+
FontWorkerMemoryStats,
|
|
188
|
+
} from "@navaramap/font";
|
|
189
|
+
export {
|
|
190
|
+
fetchFontFamilyFromCss,
|
|
191
|
+
parseCssUnicodeRange,
|
|
192
|
+
parseFontFamilyFromCss,
|
|
193
|
+
} from "@navaramap/font";
|
|
194
|
+
export type {
|
|
195
|
+
CssFontFaceFilter,
|
|
196
|
+
FetchCssFontFamilyOptions,
|
|
197
|
+
ParseCssFontFamilyOptions,
|
|
198
|
+
} from "@navaramap/font";
|
|
199
|
+
export {
|
|
200
|
+
getDefaultCacheBytes,
|
|
201
|
+
getDefaultDynamicSse,
|
|
202
|
+
getDefaultLodFog,
|
|
203
|
+
getDefaultMemoryBudgets,
|
|
204
|
+
MB,
|
|
205
|
+
type DynamicSseSettings,
|
|
206
|
+
type LodFogSettings,
|
|
207
|
+
type MemoryBudgets,
|
|
208
|
+
} from "./device";
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Snapshot of engine memory usage; see {@link ThreeView.memoryStats}. Plain
|
|
212
|
+
* object (not the WASM-generated class) per the WASM API policy: the class is
|
|
213
|
+
* copied field-by-field and freed before returning.
|
|
214
|
+
*/
|
|
215
|
+
export type MemoryStats = {
|
|
216
|
+
bufferTotalBytes: number;
|
|
217
|
+
/** Bytes held in the JS-side `InMemoryBufferStore` (WASM `External`
|
|
218
|
+
* entries): fetched MVT pbf and worker-built geometry that never enter WASM
|
|
219
|
+
* linear memory. Not part of `bufferTotalBytes`. */
|
|
220
|
+
externalBufferBytes: number;
|
|
221
|
+
bufferCount: number;
|
|
222
|
+
gpuBytesEst: number;
|
|
223
|
+
externalCpuBytes: number;
|
|
224
|
+
reservedBytes: number;
|
|
225
|
+
budgetBytes: number | undefined;
|
|
226
|
+
evictedCount: number;
|
|
227
|
+
sseMultiplier: number;
|
|
228
|
+
retainedVector: number;
|
|
229
|
+
retainedTerrain: number;
|
|
230
|
+
retainedRaster: number;
|
|
231
|
+
retainedTiles3d: number;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/** Snapshot of worker-side memory; see {@link ThreeView.workerMemoryStats}. */
|
|
235
|
+
export type WorkerMemoryStats = {
|
|
236
|
+
/** Tile-worker pool heaps (undefined per slot until first probed). */
|
|
237
|
+
tileWorkers: WorkerPoolHeapStats | undefined;
|
|
238
|
+
/** Font worker heap/cache breakdown; undefined while no font is in use. */
|
|
239
|
+
fontWorker: FontWorkerMemoryStats | undefined;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// NOTE:
|
|
243
|
+
// This overrides all materials to output a normal buffer, meaning Navara operates using MRT (Multiple Render Targets).
|
|
244
|
+
// Currently, Navara requires two buffers, so your shader must output them.
|
|
245
|
+
overrideMaterialsForMRT();
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Configuration options for initializing ThreeView.
|
|
249
|
+
*/
|
|
250
|
+
export type Options = {
|
|
251
|
+
/** Container element to append the canvas to. */
|
|
252
|
+
container?: HTMLElement;
|
|
253
|
+
/** Canvas element for rendering. If not provided, a new canvas is created. */
|
|
254
|
+
canvas?: HTMLCanvasElement | OffscreenCanvas;
|
|
255
|
+
/** Device pixel ratio override. Uses device default if not specified. */
|
|
256
|
+
pixelRatio?: number;
|
|
257
|
+
/** Disables automatic resize handling on window resize events. */
|
|
258
|
+
disableAutoResize?: boolean;
|
|
259
|
+
/** Enables debug mode with performance stats overlay. */
|
|
260
|
+
debug?: boolean;
|
|
261
|
+
/** Atmosphere rendering configuration options. */
|
|
262
|
+
atmosphere?: AtmosphereOptions;
|
|
263
|
+
/** Background color of the scene. Defaults to dark color (0x0a0a0f). */
|
|
264
|
+
backgroundColor?: CoreColor;
|
|
265
|
+
/** Feature picking configuration. */
|
|
266
|
+
picking?: boolean;
|
|
267
|
+
/** When true, renders every frame. When false, renders only on changes or when forceUpdate() is called. */
|
|
268
|
+
animation?: boolean;
|
|
269
|
+
/** Number of samples for MSAA (Multi-Sample Anti-Aliasing). 0 disables MSAA. */
|
|
270
|
+
multisampling?: number;
|
|
271
|
+
/** Uses half-float precision for post-processing. Higher quality when true. @defaultValue true */
|
|
272
|
+
halfFloat?: boolean;
|
|
273
|
+
/** Enables logarithmic depth buffer for improved depth precision at large scales. @defaultValue true */
|
|
274
|
+
logarithmicDepthBuffer?: boolean;
|
|
275
|
+
/** Enables shadow mapping. Must be set at initialization time. */
|
|
276
|
+
shadow?: boolean;
|
|
277
|
+
/** Enables mobile device optimizations such as lower pixel ratio. */
|
|
278
|
+
mobileOptimization?: boolean;
|
|
279
|
+
/**
|
|
280
|
+
* Milliseconds of inactivity (no Rust-side events) before the `idle` event fires.
|
|
281
|
+
* @defaultValue 100
|
|
282
|
+
*/
|
|
283
|
+
idleThreshold?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Memory budget in bytes for tile caches (WASM buffers + estimated GPU
|
|
286
|
+
* cost). Tiles leaving the view are retained until the budget is exceeded,
|
|
287
|
+
* then evicted least-recently-visited first — panning back is refetch-free
|
|
288
|
+
* while total usage stays capped. Naming follows Cesium3DTileset.cacheBytes.
|
|
289
|
+
* @defaultValue device-dependent, see `getDefaultCacheBytes`
|
|
290
|
+
*/
|
|
291
|
+
cacheBytes?: number;
|
|
292
|
+
/**
|
|
293
|
+
* LOD fog: distance-based screen-space-error relaxation used by tile LOD
|
|
294
|
+
* selection — far tiles tolerate a larger error and stay coarser while
|
|
295
|
+
* near tiles keep full resolution. Purely an LOD control; it never
|
|
296
|
+
* affects visual fog rendering. Partial overrides are merged over the
|
|
297
|
+
* device default.
|
|
298
|
+
* @defaultValue device-memory-dependent, see `getDefaultLodFog`
|
|
299
|
+
*/
|
|
300
|
+
lodFog?: Partial<LodFogSettings>;
|
|
301
|
+
/**
|
|
302
|
+
* Dynamic screen-space error (CesiumJS `dynamicScreenSpaceError`
|
|
303
|
+
* equivalent): tilted, street-level horizon views tolerate a larger error
|
|
304
|
+
* for far tiles, cutting the tile working set in exactly the views that
|
|
305
|
+
* over-refine. Zero effect looking straight down. Partial overrides are
|
|
306
|
+
* merged over the default.
|
|
307
|
+
* @defaultValue see `getDefaultDynamicSse`
|
|
308
|
+
*/
|
|
309
|
+
dynamicSse?: Partial<DynamicSseSettings>;
|
|
310
|
+
/**
|
|
311
|
+
* Overrides for the worker-side memory budgets (per-tile-worker WASM heap
|
|
312
|
+
* recycle threshold and the font worker's cache budget). Defaults derive
|
|
313
|
+
* from the device memory together with `cacheBytes` — see
|
|
314
|
+
* `getDefaultMemoryBudgets`. The font budget caps *further growth* of the
|
|
315
|
+
* font caches; WASM heaps never return memory to the OS.
|
|
316
|
+
*/
|
|
317
|
+
memoryBudget?: {
|
|
318
|
+
maxWorkerHeapBytes?: number;
|
|
319
|
+
fontBudgetBytes?: number;
|
|
320
|
+
/** In-flight data fetch cap per tile pipeline (see `getDefaultMemoryBudgets`). */
|
|
321
|
+
maxPendingRequests?: number;
|
|
322
|
+
/**
|
|
323
|
+
* Resting (base) memory-pressure SSE multiplier. On mobile a value > 1
|
|
324
|
+
* keeps far tiles coarser even when the budget is not exceeded. Defaults
|
|
325
|
+
* per device — see `getDefaultSseMultiplierRange`.
|
|
326
|
+
*/
|
|
327
|
+
sseMultiplierMin?: number;
|
|
328
|
+
/**
|
|
329
|
+
* Ceiling for the dynamic memory-pressure SSE degrade. A larger value lets
|
|
330
|
+
* the engine shed more of the visible set under pressure before the tab is
|
|
331
|
+
* killed. Defaults per device — see `getDefaultSseMultiplierRange`.
|
|
332
|
+
*/
|
|
333
|
+
sseMultiplierMax?: number;
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Enables shared water texture. When enabled, a single water normal texture
|
|
337
|
+
* is loaded once and shared across all meshes that have water effects enabled.
|
|
338
|
+
*/
|
|
339
|
+
waterTexture?: {
|
|
340
|
+
/** Whether to enable the shared water texture. */
|
|
341
|
+
enabled: boolean;
|
|
342
|
+
/** Custom water normal texture URL. Uses built-in texture if not specified. */
|
|
343
|
+
url?: string;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Attribution (credit) UI, on by default (accessed via
|
|
347
|
+
* {@link ThreeView.attribution}). Pass `false` to opt out (e.g. to build your
|
|
348
|
+
* own UI), or an object to configure the initial style / corner. Skipped in a
|
|
349
|
+
* worker (no DOM), so `view.attribution` is `undefined` there.
|
|
350
|
+
* @defaultValue true
|
|
351
|
+
*/
|
|
352
|
+
defaultAttribution?: boolean | AttributionPluginOptions;
|
|
353
|
+
} & GlobeOptions;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Mouse event extended with map coordinates at the event location.
|
|
357
|
+
*/
|
|
358
|
+
export type MapMouseEvent = {
|
|
359
|
+
/** World coordinates (ECEF) at the mouse position on the globe surface. */
|
|
360
|
+
map: XYZ;
|
|
361
|
+
} & MouseEvent;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Event types emitted by ThreeView. Subscribe using `view.on(eventName, callback)`.
|
|
365
|
+
*/
|
|
366
|
+
export type ViewEvents = {
|
|
367
|
+
/** Emitted when the view is resized. Receives width and height in pixels. */
|
|
368
|
+
resize: (w: number, h: number) => void;
|
|
369
|
+
/** Emitted when a feature is picked. Receives picked feature info or null. */
|
|
370
|
+
pick: (info: Nullable<PickedFeature>) => void;
|
|
371
|
+
/** Emitted when a layer event occurs. Receives event type, layer ID, and event arguments. */
|
|
372
|
+
layer: <K extends keyof LayerEvent>(
|
|
373
|
+
k: K,
|
|
374
|
+
layerId: string,
|
|
375
|
+
...args: Parameters<LayerEvent[K]>
|
|
376
|
+
) => void;
|
|
377
|
+
/** Emitted before an update process happens. Receives `DOMHighResTimeStamp` as a timestamp. */
|
|
378
|
+
preUpdate: (t: number) => void;
|
|
379
|
+
/** Emitted after an update process when state changes occurred. Receives `DOMHighResTimeStamp` as a timestamp. */
|
|
380
|
+
postUpdate: (t: number) => void;
|
|
381
|
+
/** Emitted before rendering. With `animation: true`, fires every frame. Receives `DOMHighResTimeStamp` as a timestamp. */
|
|
382
|
+
preRender: (t: number) => void;
|
|
383
|
+
/** Emitted after rendering. With `animation: true`, fires every frame. Receives `DOMHighResTimeStamp` as a timestamp. */
|
|
384
|
+
postRender: (t: number) => void;
|
|
385
|
+
/** @private Emitted when terrain height sampling completes. */
|
|
386
|
+
_sample_terrain_height_received: (ev: TerrainHeightUpdatedEvent) => void;
|
|
387
|
+
/** Emitted on mouse down with map coordinates. */
|
|
388
|
+
mousedown: (event: MapMouseEvent) => void;
|
|
389
|
+
/** Emitted when mouse enters the canvas with map coordinates. */
|
|
390
|
+
mouseenter: (event: MapMouseEvent) => void;
|
|
391
|
+
/** Emitted when mouse leaves the canvas with map coordinates. */
|
|
392
|
+
mouseleave: (event: MapMouseEvent) => void;
|
|
393
|
+
/** Emitted on mouse move with map coordinates. */
|
|
394
|
+
mousemove: (event: MapMouseEvent) => void;
|
|
395
|
+
/** Emitted on mouse up with map coordinates. */
|
|
396
|
+
mouseup: (event: MapMouseEvent) => void;
|
|
397
|
+
/** Emitted on click with map coordinates. */
|
|
398
|
+
click: (event: MapMouseEvent) => void;
|
|
399
|
+
/** Emitted when the engine becomes idle (no Rust-side events for `idleThreshold` ms). */
|
|
400
|
+
idle: () => void;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The main 3D globe view class that manages rendering, layers, camera, and user interaction.
|
|
405
|
+
* Create an instance and call `init()` to start the engine.
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* const view = new ThreeView();
|
|
410
|
+
* await view.init();
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
export default class ThreeView<
|
|
414
|
+
D extends Descriptions = EmptyDescriptions,
|
|
415
|
+
> extends EventHandler<ViewEvents> {
|
|
416
|
+
private _camera: ThreeViewCamera;
|
|
417
|
+
private _renderer: WebGLRenderer;
|
|
418
|
+
private _globe!: Globe;
|
|
419
|
+
private _atmosphere: Atmosphere;
|
|
420
|
+
|
|
421
|
+
/** Handle for the sky environment map effect descriptor. Used for sky reflections. */
|
|
422
|
+
private skyEnvMap!: EffectHandle<SkyEnvMapEffectDesc>;
|
|
423
|
+
/** Handle for the Multi-Render Target pass that outputs color and normal buffers. */
|
|
424
|
+
private mrtPass!: EffectHandle<MRTPassEffectDesc>;
|
|
425
|
+
/** Handle for the transparent objects rendering pass. */
|
|
426
|
+
private transparentPass!: EffectHandle<TransparentPassEffectDesc>;
|
|
427
|
+
/** Handle for the final compositing pass that outputs to screen. */
|
|
428
|
+
private finalPass!: EffectHandle<FinalCopyEffectDesc>;
|
|
429
|
+
|
|
430
|
+
/** The render pass orchestrator that manages the post-processing effect pipeline. */
|
|
431
|
+
private renderPassOrchestrator: RenderPassOrchestrator;
|
|
432
|
+
|
|
433
|
+
private _scenes: Scenes;
|
|
434
|
+
|
|
435
|
+
private _core: Core | undefined;
|
|
436
|
+
private _fontManager = new FontManager(FontWorkerURL);
|
|
437
|
+
private _options: Options;
|
|
438
|
+
private _stats: RendererStats | undefined;
|
|
439
|
+
private _eventDisposer: (() => void) | undefined;
|
|
440
|
+
private _disposed = false;
|
|
441
|
+
private _renderFlag: RenderFlag = {
|
|
442
|
+
forceUpdate: false,
|
|
443
|
+
animation: false,
|
|
444
|
+
};
|
|
445
|
+
private _isIdle = false;
|
|
446
|
+
private _uniforms: CommonUniforms;
|
|
447
|
+
|
|
448
|
+
private _meshes: MeshCache = new Map();
|
|
449
|
+
private _abortControllers: AbortControllers = new Map();
|
|
450
|
+
private _workerPoolPromises: WorkerPoolPromises = new Map();
|
|
451
|
+
private _loadedTexs = new Map<string, Texture>();
|
|
452
|
+
private _texturizedSceneByTileCoordinates: TexturizedSceneByTileCoordinates;
|
|
453
|
+
private _tileTextureCompositor!: TileTextureCompositor;
|
|
454
|
+
private _tileMapByHandle: TileMapByHandle = new Map();
|
|
455
|
+
// fragment id → Set of {tileMesh, slotIndex}
|
|
456
|
+
private _textureFragmentIndex: Map<string, Set<TextureSlot>> = new Map<
|
|
457
|
+
string,
|
|
458
|
+
Set<TextureSlot>
|
|
459
|
+
>();
|
|
460
|
+
// tileMesh → Set of fragment IDs it uses
|
|
461
|
+
private _tileMeshToFragmentIds: Map<TileMesh, Set<string>> = new Map<
|
|
462
|
+
TileMesh,
|
|
463
|
+
Set<string>
|
|
464
|
+
>();
|
|
465
|
+
// Hillshade processing context
|
|
466
|
+
// Scoped to this view instance to prevent cross-view contamination
|
|
467
|
+
private _hillshadeContext = new HillshadeContext();
|
|
468
|
+
private _initialized = false;
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* JS-side store for buffers WASM tracks by byte count only (`External`
|
|
472
|
+
* entries). Fetched MVT pbf and worker-built geometry live here instead of
|
|
473
|
+
* being copied into WASM linear memory, whose peak never shrinks. The
|
|
474
|
+
* `_buf` wrapper prefers this store, falling back to WASM for entries Rust
|
|
475
|
+
* still owns; handles are evicted per frame via `drainRemovedExternalHandles`.
|
|
476
|
+
*/
|
|
477
|
+
private _inMemoryBufferStore = new InMemoryBufferStore();
|
|
478
|
+
|
|
479
|
+
private _buf: BufferLoader = {
|
|
480
|
+
// Getters prefer the JS store (type-matched) and fall back to the WASM
|
|
481
|
+
// view for entries Rust still owns.
|
|
482
|
+
u8: (handle) => {
|
|
483
|
+
const js = this._inMemoryBufferStore.get(handle);
|
|
484
|
+
if (js instanceof Uint8Array) return js;
|
|
485
|
+
const b = this._core?.getBufferU8(handle);
|
|
486
|
+
return b ?? null;
|
|
487
|
+
},
|
|
488
|
+
f32: (handle) => {
|
|
489
|
+
const js = this._inMemoryBufferStore.get(handle);
|
|
490
|
+
if (js instanceof Float32Array) return js;
|
|
491
|
+
const b = this._core?.getBufferF32(handle);
|
|
492
|
+
return b ?? null;
|
|
493
|
+
},
|
|
494
|
+
f64: (handle) => {
|
|
495
|
+
const js = this._inMemoryBufferStore.get(handle);
|
|
496
|
+
if (js instanceof Float64Array) return js;
|
|
497
|
+
const b = this._core?.getBufferF64(handle);
|
|
498
|
+
return b ?? null;
|
|
499
|
+
},
|
|
500
|
+
u32: (handle) => {
|
|
501
|
+
const js = this._inMemoryBufferStore.get(handle);
|
|
502
|
+
if (js instanceof Uint32Array) return js;
|
|
503
|
+
const b = this._core?.getBufferU32(handle);
|
|
504
|
+
return b ?? null;
|
|
505
|
+
},
|
|
506
|
+
// remove* take from the JS store when present (and clear the WASM-side
|
|
507
|
+
// External accounting), else fall back to the owned-copy WASM remover.
|
|
508
|
+
removeU8: (handle) => {
|
|
509
|
+
const js = this._inMemoryBufferStore.take(handle);
|
|
510
|
+
if (js instanceof Uint8Array) {
|
|
511
|
+
this._core?.removeBuffer(handle);
|
|
512
|
+
return js;
|
|
513
|
+
}
|
|
514
|
+
const b = this._core?.removeBufferU8(handle);
|
|
515
|
+
return b ?? null;
|
|
516
|
+
},
|
|
517
|
+
removeU32: (handle) => {
|
|
518
|
+
const js = this._inMemoryBufferStore.take(handle);
|
|
519
|
+
if (js instanceof Uint32Array) {
|
|
520
|
+
this._core?.removeBuffer(handle);
|
|
521
|
+
return js;
|
|
522
|
+
}
|
|
523
|
+
const b = this._core?.removeBufferU32(handle);
|
|
524
|
+
return b ?? null;
|
|
525
|
+
},
|
|
526
|
+
removeF32: (handle) => {
|
|
527
|
+
const js = this._inMemoryBufferStore.take(handle);
|
|
528
|
+
if (js instanceof Float32Array) {
|
|
529
|
+
this._core?.removeBuffer(handle);
|
|
530
|
+
return js;
|
|
531
|
+
}
|
|
532
|
+
const b = this._core?.removeBufferF32(handle);
|
|
533
|
+
return b ?? null;
|
|
534
|
+
},
|
|
535
|
+
removeF64: (handle) => {
|
|
536
|
+
const js = this._inMemoryBufferStore.take(handle);
|
|
537
|
+
if (js instanceof Float64Array) {
|
|
538
|
+
this._core?.removeBuffer(handle);
|
|
539
|
+
return js;
|
|
540
|
+
}
|
|
541
|
+
const b = this._core?.removeBufferF64(handle);
|
|
542
|
+
return b ?? null;
|
|
543
|
+
},
|
|
544
|
+
setU8: (handle: number, bits: bigint, b: Uint8Array) => {
|
|
545
|
+
if (!this._core?.hasDataRequester(bits)) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this._core?.setBufferU8(handle, bits, b.length, (buf: Uint8Array) => {
|
|
549
|
+
buf.set(b);
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
newU8: (b: Uint8Array) => {
|
|
553
|
+
return this._core?.newBufferU8(b.length, (buf: Uint8Array) => {
|
|
554
|
+
buf.set(b);
|
|
555
|
+
});
|
|
556
|
+
// return this._core?.newBufferU8Cloned(b);
|
|
557
|
+
},
|
|
558
|
+
newU32: (b: Uint32Array) => {
|
|
559
|
+
return this._core?.newBufferU32(b.length, (buf: Uint32Array) => {
|
|
560
|
+
buf.set(b);
|
|
561
|
+
});
|
|
562
|
+
// return this._core?.newBufferU32Cloned(b);
|
|
563
|
+
},
|
|
564
|
+
newF32: (b: Float32Array) => {
|
|
565
|
+
return this._core?.newBufferF32(b.length, (buf: Float32Array) => {
|
|
566
|
+
buf.set(b);
|
|
567
|
+
});
|
|
568
|
+
// return this._core?.newBufferF32Cloned(b);
|
|
569
|
+
},
|
|
570
|
+
newF64: (b: Float64Array) => {
|
|
571
|
+
return this._core?.newBufferF64(b.length, (buf: Float64Array) => {
|
|
572
|
+
buf.set(b);
|
|
573
|
+
});
|
|
574
|
+
// return this._core?.newBufferF32Cloned(b);
|
|
575
|
+
},
|
|
576
|
+
// adopt* register a JS-owned array (zero-copy) and mint a byte-count-only
|
|
577
|
+
// External handle; the array bytes never touch WASM linear memory.
|
|
578
|
+
adoptU8: (array: Uint8Array) => {
|
|
579
|
+
const handle = this._core?.newExternalBuffer(array.byteLength);
|
|
580
|
+
if (handle != null) this._inMemoryBufferStore.set(handle, array);
|
|
581
|
+
return handle;
|
|
582
|
+
},
|
|
583
|
+
adoptU32: (array: Uint32Array) => {
|
|
584
|
+
const handle = this._core?.newExternalBuffer(array.byteLength);
|
|
585
|
+
if (handle != null) this._inMemoryBufferStore.set(handle, array);
|
|
586
|
+
return handle;
|
|
587
|
+
},
|
|
588
|
+
adoptF32: (array: Float32Array) => {
|
|
589
|
+
const handle = this._core?.newExternalBuffer(array.byteLength);
|
|
590
|
+
if (handle != null) this._inMemoryBufferStore.set(handle, array);
|
|
591
|
+
return handle;
|
|
592
|
+
},
|
|
593
|
+
adoptF64: (array: Float64Array) => {
|
|
594
|
+
const handle = this._core?.newExternalBuffer(array.byteLength);
|
|
595
|
+
if (handle != null) this._inMemoryBufferStore.set(handle, array);
|
|
596
|
+
return handle;
|
|
597
|
+
},
|
|
598
|
+
setExternal: (handle: number, bits: bigint, array: Uint8Array) => {
|
|
599
|
+
if (!this._core?.hasDataRequester(bits)) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this._core.setExternalBuffer(handle, bits, array.byteLength);
|
|
603
|
+
this._inMemoryBufferStore.set(handle, array);
|
|
604
|
+
},
|
|
605
|
+
remove: (handle: number) => {
|
|
606
|
+
// Also drop any JS-side entry so the two stores stay in step when a
|
|
607
|
+
// caller removes an External handle without going through remove*.
|
|
608
|
+
this._inMemoryBufferStore.delete(handle);
|
|
609
|
+
this._core?.removeBuffer(handle);
|
|
610
|
+
},
|
|
611
|
+
drainRemovedExternalHandles: () => {
|
|
612
|
+
const handles = this._core?.drainRemovedExternalHandles();
|
|
613
|
+
if (!handles) return;
|
|
614
|
+
for (const handle of handles) {
|
|
615
|
+
this._inMemoryBufferStore.delete(handle);
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
triggerDataRequesterLoaded: (bits: bigint, handle: number) => {
|
|
619
|
+
this._core?.triggerDataRequesterLoaded(bits, handle);
|
|
620
|
+
},
|
|
621
|
+
triggerDataRequesterFailed: (bits: bigint) => {
|
|
622
|
+
this._core?.triggerDataRequesterFailed(bits);
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
private _texFragment: TextureFragmentHandler = {
|
|
626
|
+
triggerTextureFragmentLoaded: (
|
|
627
|
+
bits: bigint,
|
|
628
|
+
status: TextureFragmentStatus,
|
|
629
|
+
) => {
|
|
630
|
+
this._core?.triggerTextureFragmentLoaded(bits, status);
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
// Per-frame snapshot of the WASM vector resolution revision, refreshed in `_render`.
|
|
634
|
+
private _vectorRevision = 0;
|
|
635
|
+
private _tileHandler: TileHandler = {
|
|
636
|
+
getMartini: (id) => {
|
|
637
|
+
return this._core?.getMartini(id);
|
|
638
|
+
},
|
|
639
|
+
getTile: (handle) => {
|
|
640
|
+
return this._core?.getTile(handle);
|
|
641
|
+
},
|
|
642
|
+
getParentTile: (handle) => {
|
|
643
|
+
return this._core?.getParentTile(handle);
|
|
644
|
+
},
|
|
645
|
+
getTileElevationDecoder: (handle) => {
|
|
646
|
+
return this._core?.getTileElevationDecoder(handle);
|
|
647
|
+
},
|
|
648
|
+
getVectorTileStates: (handle) => {
|
|
649
|
+
return this._core?.getVectorTileStates(handle);
|
|
650
|
+
},
|
|
651
|
+
// Returns the per-frame cached revision (read once in `_render`), so per-tile
|
|
652
|
+
// `onBeforeRender` gating costs no WASM-boundary call.
|
|
653
|
+
vectorRevision: () => this._vectorRevision,
|
|
654
|
+
reportDrapeGpuBytes: (handle, bytes) => {
|
|
655
|
+
this._core?.reportTerrainDrapeGpuBytes(handle, bytes);
|
|
656
|
+
},
|
|
657
|
+
calcMetersPerTexel: (tileHandle, textureZoom, textureWidth) => {
|
|
658
|
+
return (
|
|
659
|
+
this._core?.calcMetersPerTexel(tileHandle, textureZoom, textureWidth) ??
|
|
660
|
+
1.0
|
|
661
|
+
);
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
private _globeHandler: GlobeHandler = {
|
|
665
|
+
getTransparent: () => {
|
|
666
|
+
return this._core?.getGlobeTransparent();
|
|
667
|
+
},
|
|
668
|
+
getMaxSse: () => {
|
|
669
|
+
return this._core?.getGlobeMaxSse();
|
|
670
|
+
},
|
|
671
|
+
getSegments: () => {
|
|
672
|
+
return this._core?.getGlobeSegments();
|
|
673
|
+
},
|
|
674
|
+
getColor: (): CoreColor | undefined => {
|
|
675
|
+
const hexColor = this._core?.getGlobeColor();
|
|
676
|
+
return hexColor === undefined ? undefined : new Color().setHex(hexColor);
|
|
677
|
+
},
|
|
678
|
+
getHideUnderground: () => {
|
|
679
|
+
return this._core?.getGlobeHideUnderground();
|
|
680
|
+
},
|
|
681
|
+
getUseNormal: () => {
|
|
682
|
+
return this._core?.getGlobeUseNormal();
|
|
683
|
+
},
|
|
684
|
+
getOpacity: () => {
|
|
685
|
+
return this._core?.getGlobeOpacity();
|
|
686
|
+
},
|
|
687
|
+
getWireframe: () => {
|
|
688
|
+
return this._core?.getGlobeWireframe();
|
|
689
|
+
},
|
|
690
|
+
getElevationColormap: () => {
|
|
691
|
+
return this._core?.getGlobeElevationColormap();
|
|
692
|
+
},
|
|
693
|
+
setTransparent: (value: boolean) => {
|
|
694
|
+
this._core?.setGlobeTransparent(value);
|
|
695
|
+
},
|
|
696
|
+
setMaxSse: (value: number) => {
|
|
697
|
+
this._core?.setGlobeMaxSse(value);
|
|
698
|
+
},
|
|
699
|
+
setSegments: (value: number) => {
|
|
700
|
+
this._core?.setGlobeSegments(value);
|
|
701
|
+
},
|
|
702
|
+
setColor: (value: CoreColor) => {
|
|
703
|
+
this._core?.setGlobeColor(value.toHex());
|
|
704
|
+
},
|
|
705
|
+
setHideUnderground: (value: boolean) => {
|
|
706
|
+
this._core?.setGlobeHideUnderground(value);
|
|
707
|
+
},
|
|
708
|
+
setUseNormal: (value: boolean) => {
|
|
709
|
+
this._core?.setGlobeUseNormal(value);
|
|
710
|
+
},
|
|
711
|
+
setOpacity: (value: number) => {
|
|
712
|
+
this._core?.setGlobeOpacity(value);
|
|
713
|
+
},
|
|
714
|
+
setWireframe: (value: boolean) => {
|
|
715
|
+
this._core?.setGlobeWireframe(value);
|
|
716
|
+
},
|
|
717
|
+
setElevationColormap: (value: ColorMap) => {
|
|
718
|
+
this._core?.setGlobeElevationColormap(value.flatten());
|
|
719
|
+
|
|
720
|
+
const canvas = value.createImage();
|
|
721
|
+
const texture = new Texture(canvas);
|
|
722
|
+
texture.wrapS = ClampToEdgeWrapping;
|
|
723
|
+
texture.wrapT = ClampToEdgeWrapping;
|
|
724
|
+
texture.minFilter = LinearFilter;
|
|
725
|
+
texture.magFilter = LinearFilter;
|
|
726
|
+
texture.generateMipmaps = false;
|
|
727
|
+
texture.flipY = false;
|
|
728
|
+
texture.needsUpdate = true;
|
|
729
|
+
|
|
730
|
+
this._uniforms.colorMapTexture.value = texture;
|
|
731
|
+
|
|
732
|
+
// Track additional texture usage
|
|
733
|
+
this._defaultTextureOptions.additionalTexturesInUse = {
|
|
734
|
+
...this._defaultTextureOptions.additionalTexturesInUse,
|
|
735
|
+
colorMapTexture: true,
|
|
736
|
+
};
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
private _workerTaskHandler: WorkerTaskHandler = {
|
|
740
|
+
triggerWorkerTaskCompleted: (bits, result) => {
|
|
741
|
+
this._core?.triggerWorkerTaskCompleted(bits, result);
|
|
742
|
+
},
|
|
743
|
+
triggerWorkerTaskFailed: (delegator_id) => {
|
|
744
|
+
if (this._core) {
|
|
745
|
+
this._core.triggerWorkerTaskFailed(delegator_id);
|
|
746
|
+
} else {
|
|
747
|
+
// The engine is gone (teardown); still free the boundary object.
|
|
748
|
+
delegator_id.free();
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
hasWorkerTask: (bits) => {
|
|
752
|
+
return !!this._core?.hasWorkerTask(bits);
|
|
753
|
+
},
|
|
754
|
+
};
|
|
755
|
+
private _featureHandler: FeatureHandler = {
|
|
756
|
+
getTransferablePolygonBatchedFeature: (bits) => {
|
|
757
|
+
return this._core?.getTransferablePolygonBatchedFeature(bits);
|
|
758
|
+
},
|
|
759
|
+
getTransferablePolylineBatchedFeature: (bits) => {
|
|
760
|
+
return this._core?.getTransferablePolylineBatchedFeature(bits);
|
|
761
|
+
},
|
|
762
|
+
markFeatureIsRendered: (type, bits) => {
|
|
763
|
+
this._core?.markFeatureIsRendered(type, bits);
|
|
764
|
+
},
|
|
765
|
+
reportFeatureGpuBytes: (bits, bytes) => {
|
|
766
|
+
this._core?.reportFeatureGpuBytes(bits, bytes);
|
|
767
|
+
},
|
|
768
|
+
readAllBatchedProperties: (featureId, f) =>
|
|
769
|
+
this._core?.readAllBatchedProperties(featureId, f),
|
|
770
|
+
readFilteredBatchedProperties: (featureId, keys, f) =>
|
|
771
|
+
this._core?.readFilteredBatchedProperties(featureId, keys, f),
|
|
772
|
+
};
|
|
773
|
+
private _meshHandler: MeshHandler = {
|
|
774
|
+
setTileMeshPrepared: (handle: bigint) => {
|
|
775
|
+
this._core?.setTileMeshPrepared(handle);
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
private _layerHandler: LayerHandler = {
|
|
779
|
+
getLayerIndex: (layerId: string) => {
|
|
780
|
+
return this._core?.getLayerIndex(layerId);
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
private _ownedRootElement: HTMLElement | undefined;
|
|
784
|
+
private _eventManager = new EventManager();
|
|
785
|
+
private _pickHelper?: PickHelper;
|
|
786
|
+
private _terrainPicker: TerrainPicker;
|
|
787
|
+
private _defaultTextureOptions: TextureOptions;
|
|
788
|
+
private layersManager = new LayersManager();
|
|
789
|
+
private shadowMapViewers: ShadowMapViewers;
|
|
790
|
+
private eventContext!: EventContext;
|
|
791
|
+
|
|
792
|
+
// Registry support
|
|
793
|
+
private registries!: Registries;
|
|
794
|
+
private viewContext!: ViewContext;
|
|
795
|
+
private plugins: Plugin[] = [];
|
|
796
|
+
|
|
797
|
+
/** Built-in attribution UI; unset when disabled or in a worker (no DOM). */
|
|
798
|
+
private _attribution?: AttributionPlugin;
|
|
799
|
+
|
|
800
|
+
private pixelRatioMatchedMedia?: MediaQueryList;
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* The built-in attribution (credit) UI. `undefined` when disabled via
|
|
804
|
+
* `defaultAttribution: false`, or in a worker / no-DOM environment. Feed it
|
|
805
|
+
* credits with `view.attribution?.add([...])`.
|
|
806
|
+
*/
|
|
807
|
+
get attribution(): AttributionPlugin | undefined {
|
|
808
|
+
return this._attribution;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
constructor(options: Options = {}) {
|
|
812
|
+
super();
|
|
813
|
+
|
|
814
|
+
if (!options.canvas) {
|
|
815
|
+
const div = document.createElement("div");
|
|
816
|
+
div.id = "navara-root";
|
|
817
|
+
div.style.width = "100vw";
|
|
818
|
+
div.style.height = "100vh";
|
|
819
|
+
|
|
820
|
+
options.canvas = document.createElement("canvas");
|
|
821
|
+
options.canvas.id = "navara-canvas";
|
|
822
|
+
options.canvas.style.width = "100%";
|
|
823
|
+
options.canvas.style.height = "100%";
|
|
824
|
+
|
|
825
|
+
div.appendChild(options.canvas);
|
|
826
|
+
|
|
827
|
+
document.body.appendChild(div);
|
|
828
|
+
this._ownedRootElement = div;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
this._options = options;
|
|
832
|
+
|
|
833
|
+
// Attribution UI on by default; skip in a worker (no DOM) or when disabled.
|
|
834
|
+
if (options.defaultAttribution !== false && !isWorker()) {
|
|
835
|
+
const cfg =
|
|
836
|
+
typeof options.defaultAttribution === "object"
|
|
837
|
+
? options.defaultAttribution
|
|
838
|
+
: undefined;
|
|
839
|
+
this._attribution = new AttributionPlugin(cfg);
|
|
840
|
+
this.addPlugin(this._attribution);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Initialize terrain picker
|
|
844
|
+
this._terrainPicker = new TerrainPicker();
|
|
845
|
+
|
|
846
|
+
// disable right-click
|
|
847
|
+
options.canvas?.addEventListener("contextmenu", (e) => {
|
|
848
|
+
e.preventDefault();
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
const renderer = new WebGLRenderer({
|
|
852
|
+
// If it's true, some noise will happen. So use other AA algorithm instead.
|
|
853
|
+
antialias: (options.multisampling ?? 0) > 0,
|
|
854
|
+
logarithmicDepthBuffer: options.logarithmicDepthBuffer ?? true,
|
|
855
|
+
canvas: options.canvas,
|
|
856
|
+
stencil: true,
|
|
857
|
+
});
|
|
858
|
+
renderer.info.autoReset = false;
|
|
859
|
+
renderer.autoClearStencil = false;
|
|
860
|
+
renderer.autoClearColor = false;
|
|
861
|
+
renderer.autoClearDepth = false;
|
|
862
|
+
|
|
863
|
+
this._renderer = renderer;
|
|
864
|
+
|
|
865
|
+
renderer.shadowMap.enabled = !!options.shadow;
|
|
866
|
+
this._renderer.shadowMap.type = PCFShadowMap;
|
|
867
|
+
|
|
868
|
+
// Update shadow map manually in CustomRenderPass.
|
|
869
|
+
renderer.shadowMap.autoUpdate = false;
|
|
870
|
+
|
|
871
|
+
const { width, height } = this._getCanvasSize() ?? {};
|
|
872
|
+
invariant(width && height);
|
|
873
|
+
|
|
874
|
+
if (typeof options?.pixelRatio === "number" || !isWorker()) {
|
|
875
|
+
const pixelRatio = isWorker()
|
|
876
|
+
? 1
|
|
877
|
+
: getDevicePixelRatio({
|
|
878
|
+
override: options.pixelRatio,
|
|
879
|
+
mobileOptimization: options.mobileOptimization,
|
|
880
|
+
});
|
|
881
|
+
renderer.setPixelRatio(pixelRatio);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
renderer.setSize(width, height, !isWorker());
|
|
885
|
+
if (options.container) {
|
|
886
|
+
options.container.appendChild(renderer.domElement);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
this._texturizedSceneByTileCoordinates =
|
|
890
|
+
new TexturizedSceneByTileCoordinates(this._renderer);
|
|
891
|
+
|
|
892
|
+
// Compositor owns the per-tile composite atlas pool. It centralises the
|
|
893
|
+
// vector-scene offscreen render loop that previously lived in TileMesh,
|
|
894
|
+
// and is the seam through which a future MRT composite pass will reduce
|
|
895
|
+
// the main TileMesh shader's texture fetches.
|
|
896
|
+
// Atlas / drape RT side length is 512² on both desktop and mobile — the
|
|
897
|
+
// SAME source of truth `getDefaultMemoryCostHints` uses, so the ledger's
|
|
898
|
+
// per-tile GPU estimate matches the real allocation.
|
|
899
|
+
// `isMobileOptimized()` only reads `_options.mobileOptimization` (set
|
|
900
|
+
// above) and the pure `isMobileDevice()`, so it is safe here in the ctor.
|
|
901
|
+
this._tileTextureCompositor = new TileTextureCompositor({
|
|
902
|
+
renderer: this._renderer,
|
|
903
|
+
texturizedSceneByTileCoordinates: this._texturizedSceneByTileCoordinates,
|
|
904
|
+
size: getCompositeAtlasSize(this.isMobileOptimized()),
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
this._scenes = {
|
|
908
|
+
light: new Group(),
|
|
909
|
+
mrt: new Scene(),
|
|
910
|
+
globe: new Scene(),
|
|
911
|
+
draped: new Scene(),
|
|
912
|
+
opaque: new Scene(),
|
|
913
|
+
transparent: new Scene(),
|
|
914
|
+
skyEnvMap: new Scene(),
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
this._camera = new ThreeViewCamera();
|
|
918
|
+
|
|
919
|
+
// Setup render pass orchestrator
|
|
920
|
+
this.renderPassOrchestrator = new RenderPassOrchestrator(this._renderer, {
|
|
921
|
+
halfFloat: options.halfFloat ?? true,
|
|
922
|
+
multisampling: options.multisampling,
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
this.renderPassOrchestrator.setSize(width, height);
|
|
926
|
+
|
|
927
|
+
// Background color
|
|
928
|
+
const bgColor = options.backgroundColor
|
|
929
|
+
? options.backgroundColor.toHex()
|
|
930
|
+
: 0x000000;
|
|
931
|
+
this._renderer.setClearColor(bgColor);
|
|
932
|
+
|
|
933
|
+
if (!options.disableAutoResize && !isWorker()) {
|
|
934
|
+
window.addEventListener("resize", this._handleResize);
|
|
935
|
+
// Observe a change in devicePixelRatio.
|
|
936
|
+
this.pixelRatioMatchedMedia = window.matchMedia(
|
|
937
|
+
`(resolution: ${window.devicePixelRatio}dppx)`,
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
this.pixelRatioMatchedMedia.addEventListener(
|
|
941
|
+
"change",
|
|
942
|
+
this._handleResize,
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (options.debug) {
|
|
947
|
+
const t = options.container || this._renderer.domElement.parentElement;
|
|
948
|
+
if (t) {
|
|
949
|
+
this._stats = new RendererStats(
|
|
950
|
+
{
|
|
951
|
+
beginRender: () => this._renderer.info.reset(),
|
|
952
|
+
endRender: () => ({
|
|
953
|
+
...this._renderer.info.render,
|
|
954
|
+
memGeometries: this._renderer.info.memory.geometries,
|
|
955
|
+
}),
|
|
956
|
+
},
|
|
957
|
+
() => {
|
|
958
|
+
const stats = this._core?.getMemoryStats();
|
|
959
|
+
// Read the fields off the boxed wasm-bindgen struct, then free it —
|
|
960
|
+
// the sampler runs ~1/s and the struct otherwise leaks until the
|
|
961
|
+
// FinalizationRegistry runs (see fontWorker.ts for the pattern).
|
|
962
|
+
const bufferTotalBytes = stats?.bufferTotalBytes;
|
|
963
|
+
const externalBufferBytes = stats?.externalBufferBytes;
|
|
964
|
+
const budgetBytes = stats?.budgetBytes;
|
|
965
|
+
stats?.free();
|
|
966
|
+
const memory = (
|
|
967
|
+
performance as Performance & {
|
|
968
|
+
memory?: { usedJSHeapSize: number };
|
|
969
|
+
}
|
|
970
|
+
).memory;
|
|
971
|
+
const MB = 1024 * 1024;
|
|
972
|
+
return {
|
|
973
|
+
wasmMB:
|
|
974
|
+
bufferTotalBytes !== undefined
|
|
975
|
+
? bufferTotalBytes / MB
|
|
976
|
+
: undefined,
|
|
977
|
+
jsBufMB:
|
|
978
|
+
externalBufferBytes !== undefined
|
|
979
|
+
? externalBufferBytes / MB
|
|
980
|
+
: undefined,
|
|
981
|
+
jsHeapMB: memory ? memory.usedJSHeapSize / MB : undefined,
|
|
982
|
+
budgetMB:
|
|
983
|
+
budgetBytes !== undefined ? budgetBytes / MB : undefined,
|
|
984
|
+
};
|
|
985
|
+
},
|
|
986
|
+
);
|
|
987
|
+
t.appendChild(this._stats.dom);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
this._uniforms = {
|
|
992
|
+
viewportAndPixelRatio: { value: null },
|
|
993
|
+
frustumNearFar: { value: null },
|
|
994
|
+
frustumRatio: { value: null },
|
|
995
|
+
tGlobeDepth: { value: null },
|
|
996
|
+
tGlobeNormal: { value: null },
|
|
997
|
+
tSkyEnvMap: { value: null },
|
|
998
|
+
inverseProjectionMatrix: { value: null },
|
|
999
|
+
// TODO: Need to sync `fov` with WASM side
|
|
1000
|
+
fov: { value: (this._camera.raw.fov * Math.PI) / 180 },
|
|
1001
|
+
screenHeightPx: { value: height },
|
|
1002
|
+
time: { value: 0 },
|
|
1003
|
+
colorMapTexture: { value: null },
|
|
1004
|
+
waterTexture: { value: null },
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
// This is necessary to avoid attaching a texture beyond the max textures capabilities of GPU.
|
|
1008
|
+
// TODO: Allow to change this value dynamically.
|
|
1009
|
+
const NUM_CASCADED_SHADOW_MAPS = 6;
|
|
1010
|
+
|
|
1011
|
+
this._defaultTextureOptions = {
|
|
1012
|
+
maxAnisotropy: this._renderer.capabilities.getMaxAnisotropy(),
|
|
1013
|
+
magFilter: LinearFilter,
|
|
1014
|
+
minFilter: LinearFilter,
|
|
1015
|
+
useMipmaps: true,
|
|
1016
|
+
maxTextures:
|
|
1017
|
+
Math.max(this._renderer.capabilities.maxTextures, 8) -
|
|
1018
|
+
NUM_CASCADED_SHADOW_MAPS,
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// Load shared water texture if enabled
|
|
1022
|
+
if (options.waterTexture?.enabled) {
|
|
1023
|
+
const waterUrl = options.waterTexture.url ?? WATER_NORMAL_URL;
|
|
1024
|
+
this._uniforms.waterTexture.value = TEXTURE_LOADER.load(
|
|
1025
|
+
waterUrl,
|
|
1026
|
+
(texture) => {
|
|
1027
|
+
texture.wrapS = RepeatWrapping;
|
|
1028
|
+
texture.wrapT = RepeatWrapping;
|
|
1029
|
+
},
|
|
1030
|
+
);
|
|
1031
|
+
// Track additional texture usage
|
|
1032
|
+
this._defaultTextureOptions.additionalTexturesInUse = {
|
|
1033
|
+
...this._defaultTextureOptions.additionalTexturesInUse,
|
|
1034
|
+
waterTexture: true,
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
this._atmosphere = new Atmosphere(
|
|
1039
|
+
this._renderer,
|
|
1040
|
+
options.atmosphere,
|
|
1041
|
+
this._camera,
|
|
1042
|
+
);
|
|
1043
|
+
this._atmosphere.on("needsUpdate", this.forceUpdate);
|
|
1044
|
+
|
|
1045
|
+
this.on("layer", (e, id, ...args) => {
|
|
1046
|
+
this.layersManager.emitById(e, id, ...args);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
this._renderFlag.animation = !!options.animation;
|
|
1050
|
+
|
|
1051
|
+
this._camera.on("frustumChanged", () => {
|
|
1052
|
+
this.renderPassOrchestrator.effectComposer.setMainCamera(
|
|
1053
|
+
this._camera.raw,
|
|
1054
|
+
);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
this.shadowMapViewers = new ShadowMapViewers(this._scenes.light);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Convert a mouse event to a MapMouseEvent by adding map coordinates
|
|
1062
|
+
*/
|
|
1063
|
+
private convertMouseEventToMapEvent(event: MouseEvent): MapMouseEvent | null {
|
|
1064
|
+
const rect = this._renderer.domElement.getBoundingClientRect();
|
|
1065
|
+
const x = event.clientX - rect.left;
|
|
1066
|
+
const y = event.clientY - rect.top;
|
|
1067
|
+
const xyz = convertScreenPos(this, x, y);
|
|
1068
|
+
|
|
1069
|
+
if (xyz) {
|
|
1070
|
+
return Object.assign(event, {
|
|
1071
|
+
map: {
|
|
1072
|
+
x: xyz.x,
|
|
1073
|
+
y: xyz.y,
|
|
1074
|
+
z: xyz.z,
|
|
1075
|
+
},
|
|
1076
|
+
}) as MapMouseEvent;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
private initializeRenderPass() {
|
|
1083
|
+
this.skyEnvMap = this.addEffect<SkyEnvMapEffectDesc>({
|
|
1084
|
+
skyEnvMap: {},
|
|
1085
|
+
});
|
|
1086
|
+
this.mrtPass = this.addEffect<MRTPassEffectDesc>({
|
|
1087
|
+
mrt: {
|
|
1088
|
+
// debugNormal: true,
|
|
1089
|
+
},
|
|
1090
|
+
});
|
|
1091
|
+
this.transparentPass = this.addEffect<TransparentPassEffectDesc>({
|
|
1092
|
+
transparent: {},
|
|
1093
|
+
});
|
|
1094
|
+
this.finalPass = this.addEffect<FinalCopyEffectDesc>({
|
|
1095
|
+
final: {},
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
private get renderPass() {
|
|
1100
|
+
const instance = this.mrtPass.ref.raw;
|
|
1101
|
+
invariant(instance);
|
|
1102
|
+
return instance;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/** The camera controller that manages view position, orientation, and projection. */
|
|
1106
|
+
get camera(): ThreeViewCamera {
|
|
1107
|
+
return this._camera;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** The globe instance that manages terrain, imagery layers, and globe-specific settings. */
|
|
1111
|
+
get globe(): Globe {
|
|
1112
|
+
return this._globe;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/** The atmosphere renderer that handles sky, sun, and atmospheric scattering effects. */
|
|
1116
|
+
get atmosphere(): Atmosphere {
|
|
1117
|
+
return this._atmosphere;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Gets the tone mapping exposure value.
|
|
1122
|
+
*/
|
|
1123
|
+
get toneMappingExposure() {
|
|
1124
|
+
return this._renderer.toneMappingExposure;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Sets the tone mapping exposure value for HDR rendering.
|
|
1128
|
+
*/
|
|
1129
|
+
set toneMappingExposure(v: number) {
|
|
1130
|
+
this._renderer.toneMappingExposure = v;
|
|
1131
|
+
this.forceUpdate();
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* The resolved tile-cache memory budget in bytes (see `Options.cacheBytes`).
|
|
1136
|
+
* `undefined` before `init()` when no explicit option was given.
|
|
1137
|
+
*/
|
|
1138
|
+
get cacheBytes(): number | undefined {
|
|
1139
|
+
return this._options.cacheBytes;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Updates the tile-cache memory budget at runtime. Lowering it evicts
|
|
1143
|
+
* retained tiles down to the new budget over the next frames. Passing
|
|
1144
|
+
* `undefined` disables budgeting entirely, restoring the original
|
|
1145
|
+
* destroy-on-unvisited tile lifecycle.
|
|
1146
|
+
*/
|
|
1147
|
+
set cacheBytes(v: number | undefined) {
|
|
1148
|
+
this._options.cacheBytes = v;
|
|
1149
|
+
this._core?.setCacheBytes(v);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* The resolved LOD fog settings (see `Options.lodFog`). `undefined`
|
|
1154
|
+
* before `init()`.
|
|
1155
|
+
*/
|
|
1156
|
+
get lodFog(): LodFogSettings | undefined {
|
|
1157
|
+
const lodFog = this._options.lodFog;
|
|
1158
|
+
if (!lodFog) return undefined;
|
|
1159
|
+
return lodFog as LodFogSettings;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Updates the LOD fog at runtime; partial values merge over the current
|
|
1163
|
+
* settings. The next traversal re-selects tile LODs with the new curve.
|
|
1164
|
+
*/
|
|
1165
|
+
set lodFog(v: Partial<LodFogSettings>) {
|
|
1166
|
+
const lodFog = {
|
|
1167
|
+
...getDefaultLodFog({ isMobile: this.isMobileOptimized() }),
|
|
1168
|
+
...this._options.lodFog,
|
|
1169
|
+
...v,
|
|
1170
|
+
};
|
|
1171
|
+
this._options.lodFog = lodFog;
|
|
1172
|
+
this._core?.setLodFog(lodFog.enabled, lodFog.density, lodFog.sseFactor);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* The resolved dynamic-SSE settings (see `Options.dynamicSse`).
|
|
1177
|
+
* `undefined` before `init()`.
|
|
1178
|
+
*/
|
|
1179
|
+
get dynamicSse(): DynamicSseSettings | undefined {
|
|
1180
|
+
const dynamicSse = this._options.dynamicSse;
|
|
1181
|
+
if (!dynamicSse) return undefined;
|
|
1182
|
+
return dynamicSse as DynamicSseSettings;
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Updates the dynamic SSE at runtime; partial values merge over the
|
|
1186
|
+
* current settings. The next traversal re-selects tile LODs with the new
|
|
1187
|
+
* curve.
|
|
1188
|
+
*/
|
|
1189
|
+
set dynamicSse(v: Partial<DynamicSseSettings>) {
|
|
1190
|
+
const dynamicSse = {
|
|
1191
|
+
...getDefaultDynamicSse(),
|
|
1192
|
+
...this._options.dynamicSse,
|
|
1193
|
+
...v,
|
|
1194
|
+
};
|
|
1195
|
+
this._options.dynamicSse = dynamicSse;
|
|
1196
|
+
// The WASM class is constructed internally and consumed (moved) by
|
|
1197
|
+
// `setDynamicSse`, so no `.free()` is needed — per the WASM API policy.
|
|
1198
|
+
this._core?.setDynamicSse(
|
|
1199
|
+
new EngineDynamicSse(
|
|
1200
|
+
dynamicSse.enabled,
|
|
1201
|
+
dynamicSse.density,
|
|
1202
|
+
dynamicSse.sseFactor,
|
|
1203
|
+
dynamicSse.heightFalloff,
|
|
1204
|
+
dynamicSse.minHeight,
|
|
1205
|
+
dynamicSse.maxHeight,
|
|
1206
|
+
),
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Updates the memory-pressure SSE degrade range at runtime. `min` is the
|
|
1212
|
+
* resting multiplier applied even without budget pressure (> 1 coarsens far
|
|
1213
|
+
* tiles at rest); `max` is the ceiling the dynamic degrade can climb to. The
|
|
1214
|
+
* next traversal re-selects tile LODs with the new range.
|
|
1215
|
+
*/
|
|
1216
|
+
setSseMultiplierRange(min: number, max: number): void {
|
|
1217
|
+
this._options.memoryBudget = {
|
|
1218
|
+
...this._options.memoryBudget,
|
|
1219
|
+
sseMultiplierMin: min,
|
|
1220
|
+
sseMultiplierMax: max,
|
|
1221
|
+
};
|
|
1222
|
+
this._core?.setSseMultiplierRange(min, max);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Snapshot of engine memory usage (WASM buffer bytes, GPU estimates,
|
|
1227
|
+
* retained tile counts). Returns `undefined` before `init()`.
|
|
1228
|
+
*/
|
|
1229
|
+
memoryStats(): MemoryStats | undefined {
|
|
1230
|
+
const stats = this._core?.getMemoryStats();
|
|
1231
|
+
if (!stats) return undefined;
|
|
1232
|
+
// Copy every field into a plain object and free the boxed WASM struct
|
|
1233
|
+
// before returning — the public API must not expose wasm-bindgen classes
|
|
1234
|
+
// (WASM_API_POLICY.md), and returning the class would also leak it.
|
|
1235
|
+
const plain: MemoryStats = {
|
|
1236
|
+
bufferTotalBytes: stats.bufferTotalBytes,
|
|
1237
|
+
externalBufferBytes: stats.externalBufferBytes,
|
|
1238
|
+
bufferCount: stats.bufferCount,
|
|
1239
|
+
gpuBytesEst: stats.gpuBytesEst,
|
|
1240
|
+
externalCpuBytes: stats.externalCpuBytes,
|
|
1241
|
+
reservedBytes: stats.reservedBytes,
|
|
1242
|
+
budgetBytes: stats.budgetBytes,
|
|
1243
|
+
evictedCount: stats.evictedCount,
|
|
1244
|
+
sseMultiplier: stats.sseMultiplier,
|
|
1245
|
+
retainedVector: stats.retainedVector,
|
|
1246
|
+
retainedTerrain: stats.retainedTerrain,
|
|
1247
|
+
retainedRaster: stats.retainedRaster,
|
|
1248
|
+
retainedTiles3d: stats.retainedTiles3d,
|
|
1249
|
+
};
|
|
1250
|
+
stats.free();
|
|
1251
|
+
return plain;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Snapshot of worker-side memory: per-tile-worker WASM heaps (point-in-time
|
|
1256
|
+
* samples from the pool's post-task probes — this call also requests fresh
|
|
1257
|
+
* probes, whose results show up on the *next* call) and the font worker's
|
|
1258
|
+
* heap/cache breakdown (`undefined` while no font has been used). Returns
|
|
1259
|
+
* `undefined` before `init()`.
|
|
1260
|
+
*/
|
|
1261
|
+
async workerMemoryStats(): Promise<WorkerMemoryStats | undefined> {
|
|
1262
|
+
if (!this._initialized) return undefined;
|
|
1263
|
+
// Request fresh samples for the next read; probes coalesce per worker.
|
|
1264
|
+
probeWorkerPoolHeap();
|
|
1265
|
+
return {
|
|
1266
|
+
tileWorkers: workerPoolHeapStats(),
|
|
1267
|
+
fontWorker: await this._fontManager.memoryStats(),
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
private get globeDepthTexture() {
|
|
1272
|
+
return this.renderPass.globeDepthCopyPass.texture;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
private get depthTexture() {
|
|
1276
|
+
return this.renderPass.allDepthCopyPass.texture;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Returns whether mobile optimizations should be applied.
|
|
1281
|
+
* If `mobileOptimization` option is explicitly set, returns that value;
|
|
1282
|
+
* otherwise falls back to auto-detecting via `isMobileDevice()`.
|
|
1283
|
+
*/
|
|
1284
|
+
isMobileOptimized(): boolean {
|
|
1285
|
+
const opt = this._options.mobileOptimization;
|
|
1286
|
+
return opt ?? isMobileDevice();
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Forces an immediate re-render of the scene on the next frame.
|
|
1291
|
+
*/
|
|
1292
|
+
forceUpdate = () => {
|
|
1293
|
+
this._renderFlag.forceUpdate = true;
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Initializes the 3D engine, WASM modules, and starts the main render loop.
|
|
1298
|
+
* Must be called before using the view.
|
|
1299
|
+
*/
|
|
1300
|
+
async init() {
|
|
1301
|
+
if (this._core || this._initialized) return;
|
|
1302
|
+
|
|
1303
|
+
this._initialized = true;
|
|
1304
|
+
|
|
1305
|
+
const mobileOptimized = this.isMobileOptimized();
|
|
1306
|
+
const concurrencyManager = createDefaultConcurrencyManager(mobileOptimized);
|
|
1307
|
+
if (mobileOptimized) {
|
|
1308
|
+
this._hillshadeContext.normalMapScale = 0.5;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// One device-wide view of memory: the main-thread tile cache, the
|
|
1312
|
+
// tile-worker pool, and the font worker derive their budgets together.
|
|
1313
|
+
const budgets = getDefaultMemoryBudgets({
|
|
1314
|
+
isMobile: mobileOptimized,
|
|
1315
|
+
poolSize: concurrencyManager.total,
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
initializeWorkerPool(WorkerURL, concurrencyManager, {
|
|
1319
|
+
// A worker over this WASM heap budget is recycled; tighter on mobile,
|
|
1320
|
+
// where the process is killed at much lower memory pressure.
|
|
1321
|
+
maxWorkerHeapBytes:
|
|
1322
|
+
this._options.memoryBudget?.maxWorkerHeapBytes ??
|
|
1323
|
+
budgets.maxWorkerHeapBytes,
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// 1. Pre-fetch atmosphere resources. Non-blocking: consumers receive
|
|
1327
|
+
// the textures through onTexturesReady() once they arrive.
|
|
1328
|
+
this._atmosphere._init().then(
|
|
1329
|
+
() => this.forceUpdate(),
|
|
1330
|
+
(e) => console.error("Failed to load atmosphere textures:", e),
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
// 2. Fetch WASM resources.
|
|
1334
|
+
await Promise.all([initCore(), initNavaraApi()]);
|
|
1335
|
+
|
|
1336
|
+
this._core = new Core(newId());
|
|
1337
|
+
this._core.start();
|
|
1338
|
+
|
|
1339
|
+
const cacheBytes = this._options.cacheBytes ?? budgets.cacheBytes;
|
|
1340
|
+
this._options.cacheBytes = cacheBytes;
|
|
1341
|
+
this._core.setCacheBytes(cacheBytes);
|
|
1342
|
+
|
|
1343
|
+
// Cap in-flight fetches (per tile pipeline) — mobile presets shrink the
|
|
1344
|
+
// decode/upload burst a camera move can produce.
|
|
1345
|
+
this._core.setMaxPendingRequests(
|
|
1346
|
+
this._options.memoryBudget?.maxPendingRequests ??
|
|
1347
|
+
budgets.maxPendingRequests,
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
// Memory-pressure SSE degrade range. min > 1 (mobile) keeps far tiles
|
|
1351
|
+
// coarser at rest; max is the ceiling the dynamic degrade can climb to
|
|
1352
|
+
// when the visible set alone exceeds the budget.
|
|
1353
|
+
this._core.setSseMultiplierRange(
|
|
1354
|
+
this._options.memoryBudget?.sseMultiplierMin ?? budgets.sseMultiplierMin,
|
|
1355
|
+
this._options.memoryBudget?.sseMultiplierMax ?? budgets.sseMultiplierMax,
|
|
1356
|
+
);
|
|
1357
|
+
|
|
1358
|
+
// Override the Rust CostHints defaults with sizes derived from this
|
|
1359
|
+
// device's actual composite-atlas dimensions (512² with three MRT
|
|
1360
|
+
// attachments, ~3MB per tile) so the ledger's per-tile GPU estimate matches.
|
|
1361
|
+
const costHints = getDefaultMemoryCostHints({ isMobile: mobileOptimized });
|
|
1362
|
+
this._core.setMemoryCostHints(
|
|
1363
|
+
costHints.atlasTileBytes,
|
|
1364
|
+
costHints.rasterTileBytes,
|
|
1365
|
+
);
|
|
1366
|
+
|
|
1367
|
+
// LOD fog: stronger distance-based degrade on low-memory devices.
|
|
1368
|
+
const lodFog = {
|
|
1369
|
+
...getDefaultLodFog({ isMobile: mobileOptimized }),
|
|
1370
|
+
...this._options.lodFog,
|
|
1371
|
+
};
|
|
1372
|
+
this._options.lodFog = lodFog;
|
|
1373
|
+
this._core.setLodFog(lodFog.enabled, lodFog.density, lodFog.sseFactor);
|
|
1374
|
+
|
|
1375
|
+
// Dynamic SSE: tilt-scaled relaxation for street-level horizon views.
|
|
1376
|
+
const dynamicSse = {
|
|
1377
|
+
...getDefaultDynamicSse(),
|
|
1378
|
+
...this._options.dynamicSse,
|
|
1379
|
+
};
|
|
1380
|
+
this._options.dynamicSse = dynamicSse;
|
|
1381
|
+
this._core.setDynamicSse(
|
|
1382
|
+
new EngineDynamicSse(
|
|
1383
|
+
dynamicSse.enabled,
|
|
1384
|
+
dynamicSse.density,
|
|
1385
|
+
dynamicSse.sseFactor,
|
|
1386
|
+
dynamicSse.heightFalloff,
|
|
1387
|
+
dynamicSse.minHeight,
|
|
1388
|
+
dynamicSse.maxHeight,
|
|
1389
|
+
),
|
|
1390
|
+
);
|
|
1391
|
+
|
|
1392
|
+
this._fontManager.setConcurrencyManager(concurrencyManager);
|
|
1393
|
+
this._fontManager.setMemoryBudget(
|
|
1394
|
+
this._options.memoryBudget?.fontBudgetBytes ?? budgets.fontBudgetBytes,
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
this._globe = new Globe(this._globeHandler, this._options);
|
|
1398
|
+
|
|
1399
|
+
// Set up Registry
|
|
1400
|
+
this.viewContext = new ViewContext(
|
|
1401
|
+
this._scenes,
|
|
1402
|
+
this.layersManager,
|
|
1403
|
+
this.renderPassOrchestrator,
|
|
1404
|
+
concurrencyManager,
|
|
1405
|
+
this._core,
|
|
1406
|
+
this._meshes,
|
|
1407
|
+
);
|
|
1408
|
+
this.registries = new Registries(this, this.viewContext);
|
|
1409
|
+
this.eventContext = new EventContext({
|
|
1410
|
+
eventManager: this._eventManager,
|
|
1411
|
+
scenes: this._scenes,
|
|
1412
|
+
camera: this._camera,
|
|
1413
|
+
meshes: this._meshes,
|
|
1414
|
+
abortControllers: this._abortControllers,
|
|
1415
|
+
buf: this._buf,
|
|
1416
|
+
texFragment: this._texFragment,
|
|
1417
|
+
tileHandler: this._tileHandler,
|
|
1418
|
+
workerTaskHandler: this._workerTaskHandler,
|
|
1419
|
+
meshHandler: this._meshHandler,
|
|
1420
|
+
featureHandler: this._featureHandler,
|
|
1421
|
+
loadedTexs: this._loadedTexs,
|
|
1422
|
+
workerPoolPromises: this._workerPoolPromises,
|
|
1423
|
+
uniforms: this._uniforms,
|
|
1424
|
+
texturizedSceneByTileCoordinates: this._texturizedSceneByTileCoordinates,
|
|
1425
|
+
tileTextureCompositor: this._tileTextureCompositor,
|
|
1426
|
+
tileMapByHandle: this._tileMapByHandle,
|
|
1427
|
+
textureOptions: this._defaultTextureOptions,
|
|
1428
|
+
renderFlag: this._renderFlag,
|
|
1429
|
+
viewEvents: this,
|
|
1430
|
+
layersManager: this.layersManager,
|
|
1431
|
+
viewContext: this.viewContext,
|
|
1432
|
+
layerHandler: this._layerHandler,
|
|
1433
|
+
fontManager: this._fontManager,
|
|
1434
|
+
textureFragmentIndex: this._textureFragmentIndex,
|
|
1435
|
+
tileMeshToFragmentIds: this._tileMeshToFragmentIds,
|
|
1436
|
+
hillshadeContext: this._hillshadeContext,
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
// Register built-in descriptors
|
|
1440
|
+
this.registerBuiltIns();
|
|
1441
|
+
|
|
1442
|
+
if (!isWorker()) {
|
|
1443
|
+
this._eventDisposer = registerInputEvents(
|
|
1444
|
+
this._core,
|
|
1445
|
+
this._renderer.domElement,
|
|
1446
|
+
() => {
|
|
1447
|
+
const el = this._renderer.domElement;
|
|
1448
|
+
const rect = el.getBoundingClientRect();
|
|
1449
|
+
const pos = this.pickDepthPosition(
|
|
1450
|
+
rect.left + rect.width / 2,
|
|
1451
|
+
rect.top + rect.height / 2,
|
|
1452
|
+
);
|
|
1453
|
+
if (!pos) return null;
|
|
1454
|
+
return this._camera.raw.position.distanceTo(pos);
|
|
1455
|
+
},
|
|
1456
|
+
);
|
|
1457
|
+
this._pickHelper = new PickHelper(
|
|
1458
|
+
this._renderer.domElement,
|
|
1459
|
+
this._renderer,
|
|
1460
|
+
this._camera.raw,
|
|
1461
|
+
this._meshes,
|
|
1462
|
+
this.onPick.bind(this),
|
|
1463
|
+
// {
|
|
1464
|
+
// debug: true,
|
|
1465
|
+
// },
|
|
1466
|
+
);
|
|
1467
|
+
this._pickHelper.enablePick(this._options.picking ?? true);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
this.initializeRenderPass();
|
|
1471
|
+
|
|
1472
|
+
this.viewContext._setRenderPass(this.renderPass);
|
|
1473
|
+
|
|
1474
|
+
// 3. Warm up workers. Non-blocking: warms one worker, the rest follow
|
|
1475
|
+
// in the background off the HTTP cache; tasks dispatched to a still-cold
|
|
1476
|
+
// worker just await WASM inside the task.
|
|
1477
|
+
warmUpWorkerPool().catch((e) =>
|
|
1478
|
+
console.error("Failed to warm up the worker pool:", e),
|
|
1479
|
+
);
|
|
1480
|
+
|
|
1481
|
+
await Promise.all(this.plugins.map((p) => p.init(this, this.viewContext)));
|
|
1482
|
+
|
|
1483
|
+
this._startMainLoop();
|
|
1484
|
+
|
|
1485
|
+
const size = new Vector2();
|
|
1486
|
+
this._renderer.getSize(size);
|
|
1487
|
+
this.resize(size.width, size.height, this._renderer.getPixelRatio());
|
|
1488
|
+
|
|
1489
|
+
this._camera.core = this._core;
|
|
1490
|
+
|
|
1491
|
+
this._renderer.domElement.addEventListener("mousedown", (event) => {
|
|
1492
|
+
const mapEvent = this.convertMouseEventToMapEvent(event);
|
|
1493
|
+
if (mapEvent) {
|
|
1494
|
+
this.emit("mousedown", mapEvent);
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
this._renderer.domElement.addEventListener("mouseenter", (event) => {
|
|
1499
|
+
const mapEvent = this.convertMouseEventToMapEvent(event);
|
|
1500
|
+
if (mapEvent) {
|
|
1501
|
+
this.emit("mouseenter", mapEvent);
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
this._renderer.domElement.addEventListener("mouseleave", (event) => {
|
|
1506
|
+
const mapEvent = this.convertMouseEventToMapEvent(event);
|
|
1507
|
+
if (mapEvent) {
|
|
1508
|
+
this.emit("mouseleave", mapEvent);
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
this._renderer.domElement.addEventListener("mousemove", (event) => {
|
|
1513
|
+
const mapEvent = this.convertMouseEventToMapEvent(event);
|
|
1514
|
+
if (mapEvent) {
|
|
1515
|
+
this.emit("mousemove", mapEvent);
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
this._renderer.domElement.addEventListener("mouseup", (event) => {
|
|
1520
|
+
const mapEvent = this.convertMouseEventToMapEvent(event);
|
|
1521
|
+
if (mapEvent) {
|
|
1522
|
+
this.emit("mouseup", mapEvent);
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
this._renderer.domElement.addEventListener("click", (event) => {
|
|
1527
|
+
const mapEvent = this.convertMouseEventToMapEvent(event);
|
|
1528
|
+
if (mapEvent) {
|
|
1529
|
+
this.emit("click", mapEvent);
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Disposes all resources and stops the render loop.
|
|
1536
|
+
* Call this when the view is no longer needed.
|
|
1537
|
+
*/
|
|
1538
|
+
dispose() {
|
|
1539
|
+
this._disposed = true;
|
|
1540
|
+
this._initialized = false;
|
|
1541
|
+
// Dispose the view-owned attribution plugin (other plugins are the caller's).
|
|
1542
|
+
this._attribution?.dispose();
|
|
1543
|
+
this._attribution = undefined;
|
|
1544
|
+
if (!isWorker()) {
|
|
1545
|
+
window.removeEventListener("resize", this._handleResize);
|
|
1546
|
+
this.pixelRatioMatchedMedia?.removeEventListener(
|
|
1547
|
+
"change",
|
|
1548
|
+
this._handleResize,
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (this._eventDisposer) {
|
|
1553
|
+
this._eventDisposer();
|
|
1554
|
+
this._eventDisposer = undefined;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
if (this._pickHelper) {
|
|
1558
|
+
this._pickHelper.dispose();
|
|
1559
|
+
this._pickHelper = undefined;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (this._terrainPicker) {
|
|
1563
|
+
this._terrainPicker.dispose();
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
this._fontManager.dispose();
|
|
1567
|
+
this._atmosphere._dispose();
|
|
1568
|
+
|
|
1569
|
+
this.skyEnvMap?.delete();
|
|
1570
|
+
this.mrtPass?.delete();
|
|
1571
|
+
this.transparentPass?.delete();
|
|
1572
|
+
this.finalPass?.delete();
|
|
1573
|
+
|
|
1574
|
+
// Abort all pending requests
|
|
1575
|
+
for (const controller of this._abortControllers.values()) {
|
|
1576
|
+
controller.abort();
|
|
1577
|
+
}
|
|
1578
|
+
this._abortControllers.clear();
|
|
1579
|
+
|
|
1580
|
+
// Dispose loaded textures (closes any ImageBitmap backing so the decoded
|
|
1581
|
+
// pixels are freed, not just the GL texture).
|
|
1582
|
+
for (const tex of this._loadedTexs.values()) {
|
|
1583
|
+
disposeTexture(tex);
|
|
1584
|
+
}
|
|
1585
|
+
this._loadedTexs.clear();
|
|
1586
|
+
|
|
1587
|
+
// Cleanup hillshade context (temp DEMs, generator, etc.)
|
|
1588
|
+
this._hillshadeContext.dispose();
|
|
1589
|
+
|
|
1590
|
+
// Clear caches and maps
|
|
1591
|
+
this._meshes.clear();
|
|
1592
|
+
this._inMemoryBufferStore.clear();
|
|
1593
|
+
this._workerPoolPromises.clear();
|
|
1594
|
+
this._tileMapByHandle.clear();
|
|
1595
|
+
this._textureFragmentIndex.clear();
|
|
1596
|
+
this._tileMeshToFragmentIds.clear();
|
|
1597
|
+
|
|
1598
|
+
// Dispose render pipeline GPU resources
|
|
1599
|
+
this.renderPassOrchestrator.dispose();
|
|
1600
|
+
|
|
1601
|
+
// Release per-tile composite atlases, cached shader materials, placeholder
|
|
1602
|
+
// textures, and the fullscreen quad geometry owned by the compositor.
|
|
1603
|
+
this._tileTextureCompositor.dispose();
|
|
1604
|
+
|
|
1605
|
+
// Clean up WASM core
|
|
1606
|
+
this._core?.free();
|
|
1607
|
+
this._core = undefined;
|
|
1608
|
+
|
|
1609
|
+
if (this._stats) {
|
|
1610
|
+
this._stats.dom.remove();
|
|
1611
|
+
this._stats = undefined;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
this._renderer.setAnimationLoop(null);
|
|
1615
|
+
if (
|
|
1616
|
+
"dispose" in this._renderer &&
|
|
1617
|
+
typeof this._renderer.dispose === "function"
|
|
1618
|
+
) {
|
|
1619
|
+
this._renderer.dispose();
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// Remove auto-created DOM container so a new ThreeView can append its own
|
|
1623
|
+
this._ownedRootElement?.remove();
|
|
1624
|
+
this._ownedRootElement = undefined;
|
|
1625
|
+
|
|
1626
|
+
// Reset worker pool so a new ThreeView can reinitialize it
|
|
1627
|
+
terminateWorkerPool();
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* Resizes the renderer and updates the camera aspect ratio.
|
|
1632
|
+
* @param width - New width in pixels (uses canvas size if omitted)
|
|
1633
|
+
* @param height - New height in pixels (uses canvas size if omitted)
|
|
1634
|
+
* @param pixelRatio - Device pixel ratio
|
|
1635
|
+
*/
|
|
1636
|
+
resize = (width?: number, height?: number, pixelRatio?: number) => {
|
|
1637
|
+
if (this._disposed) return;
|
|
1638
|
+
|
|
1639
|
+
const canvas = this._getCanvasSize();
|
|
1640
|
+
const w = typeof width === "number" ? width : canvas?.width;
|
|
1641
|
+
const h = typeof height === "number" ? height : canvas?.height;
|
|
1642
|
+
if (typeof w !== "number" || typeof h !== "number") return;
|
|
1643
|
+
|
|
1644
|
+
this._camera.raw.aspect = w / h;
|
|
1645
|
+
this._camera.raw.updateProjectionMatrix();
|
|
1646
|
+
this._renderer.setSize(w, h, !isWorker());
|
|
1647
|
+
this.renderPassOrchestrator.setSize(w, h);
|
|
1648
|
+
if (this._options.pixelRatio == null && pixelRatio) {
|
|
1649
|
+
this._renderer.setPixelRatio(pixelRatio);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const drawingBufferSize = this._renderer.getDrawingBufferSize(
|
|
1653
|
+
new Vector2(),
|
|
1654
|
+
);
|
|
1655
|
+
this._pickHelper?.setSize(drawingBufferSize.x, drawingBufferSize.y);
|
|
1656
|
+
|
|
1657
|
+
this._core?.resize(w, h, pixelRatio ?? 1);
|
|
1658
|
+
|
|
1659
|
+
this.emit("resize", w, h);
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
private _updateUniforms() {
|
|
1663
|
+
const viewport = this._getCanvasSize();
|
|
1664
|
+
const pixelRatio = this._renderer.getPixelRatio();
|
|
1665
|
+
|
|
1666
|
+
// Ref: https://github.com/CesiumGS/cesium/blob/2cf09cb06e4f7ea767da39befabcfc3444b02c49/packages/engine/Source/Core/PerspectiveFrustum.js#L208-L218
|
|
1667
|
+
const fovY = this._camera.fovy ?? 1;
|
|
1668
|
+
const top = this._camera.raw.near * Math.tan(0.5 * fovY);
|
|
1669
|
+
const bottom = -top;
|
|
1670
|
+
const right = this._camera.raw.aspect * top;
|
|
1671
|
+
const left = -right;
|
|
1672
|
+
|
|
1673
|
+
this._uniforms.viewportAndPixelRatio.value = [
|
|
1674
|
+
viewport?.width ?? 0,
|
|
1675
|
+
viewport?.height ?? 0,
|
|
1676
|
+
pixelRatio,
|
|
1677
|
+
];
|
|
1678
|
+
this._uniforms.frustumNearFar.value = [
|
|
1679
|
+
this._camera.raw.near,
|
|
1680
|
+
this._camera.raw.far,
|
|
1681
|
+
];
|
|
1682
|
+
this._uniforms.frustumRatio.value = [top, bottom, right, left];
|
|
1683
|
+
this._uniforms.tGlobeDepth.value =
|
|
1684
|
+
this.renderPass.globeDepthCopyPass.texture;
|
|
1685
|
+
this._uniforms.tGlobeNormal.value =
|
|
1686
|
+
this.renderPass.globeNormalCopyPass.texture;
|
|
1687
|
+
this._uniforms.tSkyEnvMap.value =
|
|
1688
|
+
this.skyEnvMap.ref.raw?.getEnvMapTexture() ?? null;
|
|
1689
|
+
this._uniforms.inverseProjectionMatrix.value =
|
|
1690
|
+
this._camera.raw.projectionMatrixInverse;
|
|
1691
|
+
|
|
1692
|
+
// TODO: Need to sync `fov` with WASM side
|
|
1693
|
+
this._uniforms.fov.value = (this._camera.raw.fov * Math.PI) / 180;
|
|
1694
|
+
this._uniforms.screenHeightPx.value = viewport?.height ?? 0;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/** Returns true if the scene was updated and needs to be rendered. */
|
|
1698
|
+
private _update(updatedAt: number): boolean {
|
|
1699
|
+
this.emit("preUpdate", updatedAt);
|
|
1700
|
+
|
|
1701
|
+
this._core?.update(updatedAt);
|
|
1702
|
+
|
|
1703
|
+
const events = this._core?.readEvents();
|
|
1704
|
+
if ((!events && !this._eventManager.needsUpdate()) || !this._core) {
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
this.eventContext.updatedAt = updatedAt;
|
|
1709
|
+
|
|
1710
|
+
processEvent(this.eventContext, events);
|
|
1711
|
+
|
|
1712
|
+
events?.free();
|
|
1713
|
+
|
|
1714
|
+
// Evict JS-side `InMemoryBufferStore` entries whose WASM `External` handles
|
|
1715
|
+
// Rust freed this frame (tile destroy, DataRequester removal, ...), keeping
|
|
1716
|
+
// the two stores in step.
|
|
1717
|
+
this._buf.drainRemovedExternalHandles();
|
|
1718
|
+
|
|
1719
|
+
this._camera.raw.updateMatrixWorld();
|
|
1720
|
+
|
|
1721
|
+
this._updateUniforms();
|
|
1722
|
+
|
|
1723
|
+
this.emit("postUpdate", updatedAt);
|
|
1724
|
+
|
|
1725
|
+
return true;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Process feature updates for all descriptors
|
|
1730
|
+
* This is called after the main update loop to batch feature updates
|
|
1731
|
+
*/
|
|
1732
|
+
private _forceFeatureUpdates(updatedAt: number) {
|
|
1733
|
+
// Process updates for each layer
|
|
1734
|
+
for (const layer of this.layersManager.getResourceLayers()) {
|
|
1735
|
+
if (layer._processFeatureUpdates(updatedAt)) {
|
|
1736
|
+
this._renderFlag.forceUpdate = true;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
private _render(updatedAt: number) {
|
|
1742
|
+
// Read the vector resolution revision once per frame so each terrain tile's
|
|
1743
|
+
// `onBeforeRender` can gate its slot re-fetch against it without its own FFI call.
|
|
1744
|
+
this._vectorRevision = this._core?.vectorRevision() ?? this._vectorRevision;
|
|
1745
|
+
|
|
1746
|
+
this._uniforms.time.value = updatedAt;
|
|
1747
|
+
|
|
1748
|
+
this._atmosphere._update();
|
|
1749
|
+
|
|
1750
|
+
this.emit("preRender", updatedAt);
|
|
1751
|
+
|
|
1752
|
+
this.renderPassOrchestrator.render();
|
|
1753
|
+
this._pickHelper?.renderDebugCanvas();
|
|
1754
|
+
|
|
1755
|
+
this.shadowMapViewers.render(this._renderer);
|
|
1756
|
+
|
|
1757
|
+
this.emit("postRender", updatedAt);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
/**
|
|
1761
|
+
* Since passing Color class to WASM is tricky, converts Navara Color
|
|
1762
|
+
* objects to numbers in layer descriptions.
|
|
1763
|
+
* Handles the two-level structure: layer -> material -> color fields.
|
|
1764
|
+
*/
|
|
1765
|
+
private _convertColorsToNumbers(obj: unknown): unknown {
|
|
1766
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
1767
|
+
return obj;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// Process the object's properties (shallow copy)
|
|
1771
|
+
const result: Record<string, unknown> = { ...obj };
|
|
1772
|
+
|
|
1773
|
+
for (const [key, value] of Object.entries(result)) {
|
|
1774
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1775
|
+
// Nested object (e.g., point, billboard, text, model, etc.)
|
|
1776
|
+
const nestedResult: Record<string, unknown> = { ...value };
|
|
1777
|
+
for (const [nestedKey, nestedValue] of Object.entries(nestedResult)) {
|
|
1778
|
+
if (nestedValue instanceof Color) {
|
|
1779
|
+
nestedResult[nestedKey] = nestedValue.toHex();
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
result[key] = nestedResult;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
return result;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Adds a new resource layer (GIS data layer) to the scene.
|
|
1791
|
+
* For meshes, lights, and effects, use `addMesh()`, `addLight()`, and `addEffect()` instead.
|
|
1792
|
+
* @param l - Resource layer configuration object specifying type and options
|
|
1793
|
+
* @returns A Layer for controlling the added layer
|
|
1794
|
+
*/
|
|
1795
|
+
addLayer(l: LayerDescription): Layer {
|
|
1796
|
+
// Normalize a Source handle reference to its id before passing to Rust.
|
|
1797
|
+
const normalized =
|
|
1798
|
+
"source" in l && l.source instanceof Source
|
|
1799
|
+
? { ...l, source: l.source.id }
|
|
1800
|
+
: l;
|
|
1801
|
+
|
|
1802
|
+
// Convert all Color objects to numbers before passing to Rust
|
|
1803
|
+
const processedLayer = this._convertColorsToNumbers(
|
|
1804
|
+
normalized,
|
|
1805
|
+
) as LayerDescription;
|
|
1806
|
+
|
|
1807
|
+
// Existing resource layer process
|
|
1808
|
+
const layerId = this._core?.addLayer(processedLayer);
|
|
1809
|
+
invariant(layerId);
|
|
1810
|
+
invariant(this._core);
|
|
1811
|
+
|
|
1812
|
+
const layer = new Layer(
|
|
1813
|
+
layerId,
|
|
1814
|
+
this._core,
|
|
1815
|
+
this._convertColorsToNumbers.bind(this),
|
|
1816
|
+
);
|
|
1817
|
+
this.layersManager.add(layer);
|
|
1818
|
+
|
|
1819
|
+
return layer;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/**
|
|
1823
|
+
* Registers a data source (GeoJSON, vector tile, raster tile, raster DEM,
|
|
1824
|
+
* quantized mesh, 3D Tiles) and returns a {@link Source} handle.
|
|
1825
|
+
* Reference the returned source from layers via `addLayer({ source })`.
|
|
1826
|
+
* @param s - Source configuration object specifying type and options
|
|
1827
|
+
* @returns A Source handle for referencing, updating, and deleting the source
|
|
1828
|
+
*/
|
|
1829
|
+
addSource(s: SourceDescription): Source {
|
|
1830
|
+
invariant(this._core);
|
|
1831
|
+
invariant(s.type);
|
|
1832
|
+
const sourceId = this._core.addSource(s);
|
|
1833
|
+
|
|
1834
|
+
return new Source(sourceId, s.type, this._core);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/**
|
|
1838
|
+
* Adds a 3D mesh to the scene.
|
|
1839
|
+
* The mesh kind is determined by the nested key (e.g., `{ box: { width: 200 } }`).
|
|
1840
|
+
* @param desc - Mesh configuration object
|
|
1841
|
+
* @returns A MeshHandle for controlling the added mesh
|
|
1842
|
+
*/
|
|
1843
|
+
addMesh<L extends AnyMeshDesc = AnyMeshDesc>(
|
|
1844
|
+
config: OmitType<MeshConfig | NonNullable<D["mesh"]>>,
|
|
1845
|
+
): MeshHandle<L> {
|
|
1846
|
+
// Find which mesh type from config
|
|
1847
|
+
const meshType = this.registries.mesh.findMeshType(config);
|
|
1848
|
+
if (!meshType) {
|
|
1849
|
+
throw new UnknownTypeError("mesh", config);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// Create mesh descriptor instance
|
|
1853
|
+
const meshDesc = this.registries.mesh.create(meshType, config);
|
|
1854
|
+
|
|
1855
|
+
// Initialize the mesh
|
|
1856
|
+
meshDesc.onCreate();
|
|
1857
|
+
|
|
1858
|
+
// Set up update listener
|
|
1859
|
+
if (meshDesc.update) {
|
|
1860
|
+
this.on("preRender", meshDesc.update.bind(meshDesc));
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
if (meshDesc.onResize) {
|
|
1864
|
+
this.on("resize", meshDesc.onResize.bind(meshDesc));
|
|
1865
|
+
|
|
1866
|
+
const canvasSize = this._getCanvasSize();
|
|
1867
|
+
if (canvasSize) {
|
|
1868
|
+
meshDesc.onResize(canvasSize.width, canvasSize.height);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// Trigger re-render
|
|
1873
|
+
meshDesc.on("needsUpdate", this.forceUpdate);
|
|
1874
|
+
|
|
1875
|
+
const l = new MeshHandle(meshDesc);
|
|
1876
|
+
|
|
1877
|
+
// Store the mesh descriptor
|
|
1878
|
+
this.layersManager.add(l);
|
|
1879
|
+
|
|
1880
|
+
// Return handle for imperative access
|
|
1881
|
+
return l as MeshHandle<L>;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
/**
|
|
1885
|
+
* Adds a light to the scene.
|
|
1886
|
+
* The light kind is determined by the nested key (e.g., `{ ambient: { intensity: 0.5 } }`).
|
|
1887
|
+
* @param desc - Light configuration object
|
|
1888
|
+
* @returns A LightHandle for controlling the added light
|
|
1889
|
+
*/
|
|
1890
|
+
addLight<L extends LightDesc = LightDesc>(
|
|
1891
|
+
config: OmitType<LightConfig | NonNullable<D["light"]>>,
|
|
1892
|
+
): LightHandle<L> {
|
|
1893
|
+
// Find which light type from config
|
|
1894
|
+
const lightType = this.registries.light.findLightType(config);
|
|
1895
|
+
if (!lightType) {
|
|
1896
|
+
throw new UnknownTypeError("light", config);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// Create light descriptor instance
|
|
1900
|
+
const lightDesc = this.registries.light.create(lightType, config);
|
|
1901
|
+
|
|
1902
|
+
// Initialize the light
|
|
1903
|
+
lightDesc.onCreate();
|
|
1904
|
+
|
|
1905
|
+
// Set up update listener if the descriptor has an update method
|
|
1906
|
+
if (lightDesc.update) {
|
|
1907
|
+
this.on("preRender", lightDesc.update.bind(lightDesc));
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// Trigger re-render
|
|
1911
|
+
lightDesc.on("needsUpdate", this.forceUpdate);
|
|
1912
|
+
|
|
1913
|
+
const l = new LightHandle(lightDesc);
|
|
1914
|
+
|
|
1915
|
+
// Store the light descriptor
|
|
1916
|
+
this.layersManager.add(l);
|
|
1917
|
+
|
|
1918
|
+
// Return handle for imperative access
|
|
1919
|
+
return l as LightHandle<L>;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* Adds a post-processing effect to the scene.
|
|
1924
|
+
* The effect kind is determined by the nested key (e.g., `{ bloom: { strength: 1.0 } }`).
|
|
1925
|
+
* @param desc - Effect configuration object
|
|
1926
|
+
* @returns An EffectHandle for controlling the added effect
|
|
1927
|
+
*/
|
|
1928
|
+
addEffect<L extends EffectDesc = EffectDesc>(
|
|
1929
|
+
config: OmitType<
|
|
1930
|
+
BuiltInEffectDescription | EffectConfig | NonNullable<D["effect"]>
|
|
1931
|
+
>,
|
|
1932
|
+
): EffectHandle<L> {
|
|
1933
|
+
// Find which effect type from config
|
|
1934
|
+
const effectType = this.registries.effect.findEffectType(config);
|
|
1935
|
+
if (!effectType) {
|
|
1936
|
+
throw new UnknownTypeError("effect", config);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// Create effect descriptor instance
|
|
1940
|
+
const effectDesc = this.registries.effect.create(effectType, config);
|
|
1941
|
+
|
|
1942
|
+
// Initialize the effect
|
|
1943
|
+
effectDesc.onCreate();
|
|
1944
|
+
|
|
1945
|
+
// Set up update listener if the descriptor has an update method
|
|
1946
|
+
if (effectDesc.update) {
|
|
1947
|
+
this.on("preRender", effectDesc.update.bind(effectDesc));
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// Trigger re-render
|
|
1951
|
+
effectDesc.on("needsUpdate", this.forceUpdate);
|
|
1952
|
+
|
|
1953
|
+
const l = new EffectHandle(effectDesc);
|
|
1954
|
+
|
|
1955
|
+
// Store the effect descriptor
|
|
1956
|
+
this.layersManager.add(l);
|
|
1957
|
+
|
|
1958
|
+
// Return handle for imperative access
|
|
1959
|
+
return l as EffectHandle<L>;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* Finds a resource layer by its ID.
|
|
1964
|
+
* Only returns resource layers added via `addLayer()` (not mesh, light, or
|
|
1965
|
+
* effect descriptors).
|
|
1966
|
+
* @param id - The unique identifier of the layer to find
|
|
1967
|
+
* @returns The matching {@link Layer}, or `undefined` if none exists
|
|
1968
|
+
*/
|
|
1969
|
+
findLayerById(id: string): Layer | undefined {
|
|
1970
|
+
const target = this.layersManager.get(id);
|
|
1971
|
+
return target instanceof Layer ? target : undefined;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/**
|
|
1975
|
+
* Updates an existing resource layer's configuration by its ID.
|
|
1976
|
+
* Only works for resource layers added via `addLayer()`.
|
|
1977
|
+
* @param id - The unique identifier of the layer to update
|
|
1978
|
+
* @param l - New layer configuration
|
|
1979
|
+
*/
|
|
1980
|
+
updateLayerById(id: string, l: LayerDescription) {
|
|
1981
|
+
invariant(this._core);
|
|
1982
|
+
const target = this.layersManager.get(id);
|
|
1983
|
+
if (!target || !(target instanceof Layer)) return;
|
|
1984
|
+
target.update(l);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/**
|
|
1988
|
+
* Updates an existing mesh descriptor's configuration by its ID.
|
|
1989
|
+
* @param id - The unique identifier of the mesh to update
|
|
1990
|
+
* @param updates - Partial configuration object with properties to update
|
|
1991
|
+
*/
|
|
1992
|
+
updateMeshById(
|
|
1993
|
+
id: string,
|
|
1994
|
+
updates: OmitType<MeshConfig | NonNullable<D["mesh"]>>,
|
|
1995
|
+
) {
|
|
1996
|
+
const target = this.layersManager.get(id);
|
|
1997
|
+
if (!target || !(target instanceof MeshHandle)) return;
|
|
1998
|
+
target.update(updates);
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Updates an existing light descriptor's configuration by its ID.
|
|
2003
|
+
* @param id - The unique identifier of the light to update
|
|
2004
|
+
* @param updates - Partial configuration object with properties to update
|
|
2005
|
+
*/
|
|
2006
|
+
updateLightById(
|
|
2007
|
+
id: string,
|
|
2008
|
+
updates: OmitType<LightConfig | NonNullable<D["light"]>>,
|
|
2009
|
+
) {
|
|
2010
|
+
const target = this.layersManager.get(id);
|
|
2011
|
+
if (!target || !(target instanceof LightHandle)) return;
|
|
2012
|
+
target.update(updates);
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* Updates an existing effect descriptor's configuration by its ID.
|
|
2017
|
+
* @param id - The unique identifier of the effect to update
|
|
2018
|
+
* @param updates - Partial configuration object with properties to update
|
|
2019
|
+
*/
|
|
2020
|
+
updateEffectById(
|
|
2021
|
+
id: string,
|
|
2022
|
+
updates: OmitType<
|
|
2023
|
+
BuiltInEffectDescription | EffectConfig | NonNullable<D["effect"]>
|
|
2024
|
+
>,
|
|
2025
|
+
) {
|
|
2026
|
+
const target = this.layersManager.get(id);
|
|
2027
|
+
if (!target || !(target instanceof EffectHandle)) return;
|
|
2028
|
+
target.update(updates);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* Deletes a resource layer from the scene by its ID.
|
|
2033
|
+
* @param id - The unique identifier of the layer to delete
|
|
2034
|
+
* @returns `true` if the layer was found and deleted, `false` otherwise
|
|
2035
|
+
*/
|
|
2036
|
+
deleteLayerById(id: string): boolean {
|
|
2037
|
+
invariant(this._core);
|
|
2038
|
+
const target = this.layersManager.get(id);
|
|
2039
|
+
if (!target || !(target instanceof Layer)) return false;
|
|
2040
|
+
target.delete();
|
|
2041
|
+
return true;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/**
|
|
2045
|
+
* Deletes a mesh descriptor from the scene by its ID.
|
|
2046
|
+
* @param id - The unique identifier of the mesh to delete
|
|
2047
|
+
* @returns `true` if the mesh was found and deleted, `false` otherwise
|
|
2048
|
+
*/
|
|
2049
|
+
deleteMeshById(id: string): boolean {
|
|
2050
|
+
const target = this.layersManager.get(id);
|
|
2051
|
+
if (!target || !(target instanceof MeshHandle)) return false;
|
|
2052
|
+
target.delete();
|
|
2053
|
+
return true;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
/**
|
|
2057
|
+
* Deletes a light descriptor from the scene by its ID.
|
|
2058
|
+
* @param id - The unique identifier of the light to delete
|
|
2059
|
+
* @returns `true` if the light was found and deleted, `false` otherwise
|
|
2060
|
+
*/
|
|
2061
|
+
deleteLightById(id: string): boolean {
|
|
2062
|
+
const target = this.layersManager.get(id);
|
|
2063
|
+
if (!target || !(target instanceof LightHandle)) return false;
|
|
2064
|
+
target.delete();
|
|
2065
|
+
return true;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
/**
|
|
2069
|
+
* Deletes an effect descriptor from the scene by its ID.
|
|
2070
|
+
* @param id - The unique identifier of the effect to delete
|
|
2071
|
+
* @returns `true` if the effect was found and deleted, `false` otherwise
|
|
2072
|
+
*/
|
|
2073
|
+
deleteEffectById(id: string): boolean {
|
|
2074
|
+
const target = this.layersManager.get(id);
|
|
2075
|
+
if (!target || !(target instanceof EffectHandle)) return false;
|
|
2076
|
+
target.delete();
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
private registerBuiltIns(): void {
|
|
2081
|
+
this.registerBuiltInEffects();
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
private registerBuiltInEffects(): void {
|
|
2085
|
+
this.registerEffect("skyEnvMap", SkyEnvMapEffectDesc);
|
|
2086
|
+
this.registerEffect("mrt", MRTPassEffectDesc);
|
|
2087
|
+
this.registerEffect("transparent", TransparentPassEffectDesc);
|
|
2088
|
+
|
|
2089
|
+
this.registerEffect("final", FinalCopyEffectDesc);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* Registers a custom mesh descriptor type for use with addMesh().
|
|
2094
|
+
* @param name - Unique name to identify this mesh type in descriptor configurations
|
|
2095
|
+
* @param meshClass - The mesh descriptor class constructor
|
|
2096
|
+
*/
|
|
2097
|
+
registerMesh(name: string, meshClass: MeshDescConstructor): void {
|
|
2098
|
+
this.registries.mesh.register(name, meshClass);
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
/**
|
|
2102
|
+
* Registers a custom light descriptor type for use with addLight().
|
|
2103
|
+
* @param name - Unique name to identify this light type in descriptor configurations
|
|
2104
|
+
* @param lightClass - The light descriptor class constructor
|
|
2105
|
+
*/
|
|
2106
|
+
registerLight(name: string, lightClass: LightDescConstructor): void {
|
|
2107
|
+
this.registries.light.register(name, lightClass);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/**
|
|
2111
|
+
* Registers a custom post-processing effect descriptor type for use with addEffect().
|
|
2112
|
+
* @param name - Unique name to identify this effect type in descriptor configurations
|
|
2113
|
+
* @param effectClass - The effect descriptor class constructor
|
|
2114
|
+
*/
|
|
2115
|
+
registerEffect(name: string, effectClass: EffectDescConstructor): void {
|
|
2116
|
+
this.registries.effect.register(name, effectClass);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
/**
|
|
2120
|
+
* Adds a plugin to the view. Plugins are initialized during `init()`.
|
|
2121
|
+
* Must be called before `init()`.
|
|
2122
|
+
* @param plugin - The plugin instance to add
|
|
2123
|
+
* @returns This view instance for chaining
|
|
2124
|
+
*/
|
|
2125
|
+
addPlugin(plugin: Plugin): this {
|
|
2126
|
+
if (this._initialized)
|
|
2127
|
+
throw new Error("Plugin must be added before `view.init()`.");
|
|
2128
|
+
this.plugins.push(plugin);
|
|
2129
|
+
return this;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/**
|
|
2133
|
+
* Register a font family with multiple faces.
|
|
2134
|
+
* Each face covers a set of unicode ranges and points to a separate font file URL.
|
|
2135
|
+
* When a text label uses this family name as its font, only the face files
|
|
2136
|
+
* whose unicode ranges cover the label's characters are loaded.
|
|
2137
|
+
*
|
|
2138
|
+
* Face priority and fallback:
|
|
2139
|
+
* - Faces are evaluated in the order given in `faces`. For each codepoint,
|
|
2140
|
+
* the first face whose `unicodeRanges` includes that codepoint is used,
|
|
2141
|
+
* so earlier entries win when ranges overlap.
|
|
2142
|
+
* - Codepoints not covered by any face fall back to the first face
|
|
2143
|
+
* (`faces[0]`), which may therefore be downloaded for uncovered characters
|
|
2144
|
+
* even if its declared `unicodeRanges` do not include them.
|
|
2145
|
+
*
|
|
2146
|
+
* Put the intended fallback face at index `0`: it is both the fallback for
|
|
2147
|
+
* uncovered codepoints and the highest-priority face when ranges overlap.
|
|
2148
|
+
* Order the remaining faces after it by priority, and avoid overlapping
|
|
2149
|
+
* ranges unless that precedence is intentional.
|
|
2150
|
+
*
|
|
2151
|
+
* @example
|
|
2152
|
+
* ```ts
|
|
2153
|
+
* view.addFontFamily({
|
|
2154
|
+
* family: "MapFont",
|
|
2155
|
+
* faces: [
|
|
2156
|
+
* {
|
|
2157
|
+
* url: "/fonts/latin.woff2",
|
|
2158
|
+
* unicodeRanges: [{ from: 0x0000, to: 0x024f }],
|
|
2159
|
+
* },
|
|
2160
|
+
* {
|
|
2161
|
+
* url: "/fonts/cjk.woff2",
|
|
2162
|
+
* unicodeRanges: [{ from: 0x4e00, to: 0x9fff }],
|
|
2163
|
+
* },
|
|
2164
|
+
* ],
|
|
2165
|
+
* });
|
|
2166
|
+
* ```
|
|
2167
|
+
*
|
|
2168
|
+
* Instead of writing faces and ranges by hand, they can be derived from a
|
|
2169
|
+
* stylesheet's `@font-face` rules (e.g. the Google Fonts CSS API) with
|
|
2170
|
+
* {@link fetchFontFamilyFromCss} / {@link parseFontFamilyFromCss}:
|
|
2171
|
+
*
|
|
2172
|
+
* @example
|
|
2173
|
+
* ```ts
|
|
2174
|
+
* view.addFontFamily(
|
|
2175
|
+
* await fetchFontFamilyFromCss(
|
|
2176
|
+
* "MapFont",
|
|
2177
|
+
* "https://fonts.googleapis.com/css2?family=Noto+Sans&family=Noto+Sans+JP",
|
|
2178
|
+
* ),
|
|
2179
|
+
* );
|
|
2180
|
+
* ```
|
|
2181
|
+
*/
|
|
2182
|
+
addFontFamily(family: FontFamily): this {
|
|
2183
|
+
this._fontManager.registerFontFamily(family);
|
|
2184
|
+
return this;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/** Remove a previously registered font family by name. */
|
|
2188
|
+
removeFontFamily(family: string): this {
|
|
2189
|
+
this._fontManager.unregisterFontFamily(family);
|
|
2190
|
+
return this;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
/**
|
|
2194
|
+
* Sets the camera position and orientation instantly.
|
|
2195
|
+
* @param camPos - Camera position with lng (degrees), lat (degrees), height (meters), and optional pitch, heading, roll (degrees)
|
|
2196
|
+
*/
|
|
2197
|
+
setCamera(camPos: CameraPosition) {
|
|
2198
|
+
function checkFinite(value: number | undefined): value is number {
|
|
2199
|
+
return Number.isFinite(value) && value != null;
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
const lng = camPos.lng;
|
|
2203
|
+
const lat = camPos.lat;
|
|
2204
|
+
const position =
|
|
2205
|
+
checkFinite(lng) && checkFinite(lat)
|
|
2206
|
+
? new Float64Array([lng, lat, camPos.height ?? 0])
|
|
2207
|
+
: null;
|
|
2208
|
+
|
|
2209
|
+
this._core?.changeCamera(
|
|
2210
|
+
position,
|
|
2211
|
+
camPos.pitch,
|
|
2212
|
+
camPos.heading,
|
|
2213
|
+
camPos.roll,
|
|
2214
|
+
camPos.distance,
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/**
|
|
2219
|
+
* Moves the camera in a specified direction.
|
|
2220
|
+
* @param move - Direction: `CameraDirection`
|
|
2221
|
+
* @param amount - Distance to move in meters
|
|
2222
|
+
*/
|
|
2223
|
+
moveCamera(move: CameraDirection, amount: number) {
|
|
2224
|
+
switch (move) {
|
|
2225
|
+
case CameraDirection.Forward:
|
|
2226
|
+
this._core?.moveCamera(CameraDirection.Forward, amount);
|
|
2227
|
+
break;
|
|
2228
|
+
case CameraDirection.Backward:
|
|
2229
|
+
this._core?.moveCamera(CameraDirection.Backward, amount);
|
|
2230
|
+
break;
|
|
2231
|
+
case CameraDirection.Up:
|
|
2232
|
+
this._core?.moveCamera(CameraDirection.Up, amount);
|
|
2233
|
+
break;
|
|
2234
|
+
case CameraDirection.Down:
|
|
2235
|
+
this._core?.moveCamera(CameraDirection.Down, amount);
|
|
2236
|
+
break;
|
|
2237
|
+
case CameraDirection.Left:
|
|
2238
|
+
this._core?.moveCamera(CameraDirection.Left, amount);
|
|
2239
|
+
break;
|
|
2240
|
+
case CameraDirection.Right:
|
|
2241
|
+
this._core?.moveCamera(CameraDirection.Right, amount);
|
|
2242
|
+
break;
|
|
2243
|
+
default:
|
|
2244
|
+
break;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
/**
|
|
2249
|
+
* Moves the camera along a custom direction vector.
|
|
2250
|
+
* @param dir - Direction vector as [x, y, z] array
|
|
2251
|
+
* @param amount - Distance to move in meters
|
|
2252
|
+
*/
|
|
2253
|
+
moveCameraWithDirection(dir: number[], amount: number) {
|
|
2254
|
+
if (dir.length !== 3) {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
this._core?.moveCameraWithDirection(new Float64Array(dir), amount);
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/**
|
|
2262
|
+
* Animates the camera to fly to a target position.
|
|
2263
|
+
* @param camPos - Target position with required lng/lat (degrees). Provide either `height` (meters above the ellipsoid)
|
|
2264
|
+
* or `distance` (meters from the target point along the camera forward direction). Optional pitch, heading, roll (degrees).
|
|
2265
|
+
* @param duration - Animation duration in milliseconds
|
|
2266
|
+
* @param maxHeight - Maximum height during the flight arc in meters
|
|
2267
|
+
*/
|
|
2268
|
+
flyTo(
|
|
2269
|
+
camPos: CameraPosition &
|
|
2270
|
+
Required<Pick<CameraPosition, "lng" | "lat">> &
|
|
2271
|
+
({ height: number } | { distance: number }),
|
|
2272
|
+
duration?: number,
|
|
2273
|
+
maxHeight?: number,
|
|
2274
|
+
) {
|
|
2275
|
+
const pos = camPos as CameraPosition & { lng: number; lat: number };
|
|
2276
|
+
const position = new Float64Array([pos.lng, pos.lat, pos.height ?? 0]);
|
|
2277
|
+
|
|
2278
|
+
this._core?.flyTo(
|
|
2279
|
+
position,
|
|
2280
|
+
pos.pitch,
|
|
2281
|
+
pos.heading,
|
|
2282
|
+
pos.roll,
|
|
2283
|
+
duration,
|
|
2284
|
+
maxHeight,
|
|
2285
|
+
pos.distance,
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
/**
|
|
2290
|
+
* Makes the camera look at a target position with an offset.
|
|
2291
|
+
* @param target - Target geodetic position (lng in degrees, lat in degrees, height in meters)
|
|
2292
|
+
* @param offset - Offset from the target in East-North-Up (ENU) coordinates (meters)
|
|
2293
|
+
*/
|
|
2294
|
+
lookAt(target: LatLngHeight, offset: Vector3) {
|
|
2295
|
+
this._core?.lookAt(
|
|
2296
|
+
new Float64Array([target.lng, target.lat, target.height]),
|
|
2297
|
+
new Float64Array([offset.x, offset.y, offset.z]),
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
/**
|
|
2302
|
+
* Enables or disables camera following mode.
|
|
2303
|
+
* @param enabled - Whether to enable camera following
|
|
2304
|
+
* @param target - Target geodetic position to follow (lng in degrees, lat in degrees, height in meters)
|
|
2305
|
+
* @param offset - Offset from the target in East-North-Up (ENU) coordinates (meters)
|
|
2306
|
+
*/
|
|
2307
|
+
cameraFollow(enabled: boolean, target?: LatLngHeight, offset?: Vector3) {
|
|
2308
|
+
const targetArray = target
|
|
2309
|
+
? new Float64Array([target.lng, target.lat, target.height])
|
|
2310
|
+
: undefined;
|
|
2311
|
+
const offsetArray = offset
|
|
2312
|
+
? new Float64Array([offset.x, offset.y, offset.z])
|
|
2313
|
+
: undefined;
|
|
2314
|
+
|
|
2315
|
+
this._core?.cameraFollow(enabled, targetArray, offsetArray);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Enables position-locked free-look. The camera stays at the target
|
|
2320
|
+
* position while mouse drag rotates orientation in place. Use for
|
|
2321
|
+
* first-person-view "look around" controls.
|
|
2322
|
+
* @param enabled - Whether to enable free-look mode
|
|
2323
|
+
* @param target - Target geodetic position the camera is locked to (lng/lat degrees, height meters)
|
|
2324
|
+
*/
|
|
2325
|
+
cameraFreeLook(enabled: boolean, target?: LatLngHeight) {
|
|
2326
|
+
const targetArray = target
|
|
2327
|
+
? new Float64Array([target.lng, target.lat, target.height])
|
|
2328
|
+
: undefined;
|
|
2329
|
+
|
|
2330
|
+
this._core?.cameraFreeLook(enabled, targetArray);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* Samples the terrain height at a given geodetic position synchronously.
|
|
2335
|
+
* @param pos - Geodetic position (lat in radians, lng in radians; height is ignored)
|
|
2336
|
+
* @returns Terrain height in meters, or undefined if terrain data not loaded
|
|
2337
|
+
*/
|
|
2338
|
+
sampleTerrainHeight(pos: LatLngHeight): number | undefined {
|
|
2339
|
+
const lle = new LLE(pos.lat, pos.lng, 0);
|
|
2340
|
+
return this._core?.sampleTerrainHeight(lle);
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
/**
|
|
2344
|
+
* Observes terrain height changes at a position. Callback is invoked each time terrain data updates.
|
|
2345
|
+
* @param pos - Geodetic position to observe (lat in radians, lng in radians)
|
|
2346
|
+
* @param cb - Callback function receiving the terrain height in meters
|
|
2347
|
+
* @returns Cleanup function to stop observing
|
|
2348
|
+
*/
|
|
2349
|
+
observeTerrainHeightAt(
|
|
2350
|
+
pos: LatLng,
|
|
2351
|
+
cb: (height: number) => void,
|
|
2352
|
+
): () => void {
|
|
2353
|
+
if (!this._core) {
|
|
2354
|
+
return () => {};
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
const lle = new LLE(pos.lat, pos.lng, 0);
|
|
2358
|
+
const entityBits = this._core.registerSampleTerrainHeightEvent(lle);
|
|
2359
|
+
|
|
2360
|
+
const callFunc = (ev: TerrainHeightUpdatedEvent) => {
|
|
2361
|
+
if (ev.bits === entityBits && ev.height) {
|
|
2362
|
+
cb(ev.height);
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
|
|
2366
|
+
this.on("_sample_terrain_height_received", callFunc);
|
|
2367
|
+
|
|
2368
|
+
return () => {
|
|
2369
|
+
this._core?.unregisterSampleTerrainHeightEvent(entityBits);
|
|
2370
|
+
this.off("_sample_terrain_height_received", callFunc);
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
/**
|
|
2375
|
+
* Rotates the camera around an axis.
|
|
2376
|
+
* @param axis - Axis to rotate around (zero vector uses default)
|
|
2377
|
+
* @param angle - Rotation angle in radians
|
|
2378
|
+
*/
|
|
2379
|
+
rotateAroundAxis(axis: Vector3, angle: number) {
|
|
2380
|
+
this._core?.rotateAroundAxis(
|
|
2381
|
+
new Float64Array([axis.x, axis.y, axis.z]),
|
|
2382
|
+
angle,
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
/**
|
|
2387
|
+
* Rotates the camera around the current look-at point or center of view.
|
|
2388
|
+
* @param angle - Rotation angle in radians
|
|
2389
|
+
*/
|
|
2390
|
+
rotateAround(angle: number) {
|
|
2391
|
+
this._core?.rotateAroundAxis(null, angle);
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
private _startMainLoop() {
|
|
2395
|
+
const idleThreshold = this._options.idleThreshold ?? 100;
|
|
2396
|
+
const loop: XRFrameRequestCallback = (time) => {
|
|
2397
|
+
if (this._disposed) return;
|
|
2398
|
+
this._stats?.begin();
|
|
2399
|
+
|
|
2400
|
+
this._forceFeatureUpdates(time);
|
|
2401
|
+
|
|
2402
|
+
const updated = this._update(time);
|
|
2403
|
+
if (updated || this._renderFlag.forceUpdate || this._renderFlag.animation)
|
|
2404
|
+
this._render(time);
|
|
2405
|
+
this._renderFlag.forceUpdate = false;
|
|
2406
|
+
|
|
2407
|
+
if (updated) {
|
|
2408
|
+
this._isIdle = false;
|
|
2409
|
+
} else if (
|
|
2410
|
+
!this._isIdle &&
|
|
2411
|
+
this.eventContext.updatedAt > 0 &&
|
|
2412
|
+
time - this.eventContext.updatedAt >= idleThreshold
|
|
2413
|
+
) {
|
|
2414
|
+
this._isIdle = true;
|
|
2415
|
+
this.emit("idle");
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
this._stats?.end();
|
|
2419
|
+
};
|
|
2420
|
+
this._renderer.setAnimationLoop(loop);
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
private _getCanvasSize(): { width: number; height: number } | undefined {
|
|
2424
|
+
const element =
|
|
2425
|
+
this._options.container ??
|
|
2426
|
+
this._renderer.domElement?.parentElement ??
|
|
2427
|
+
this._renderer.domElement;
|
|
2428
|
+
if (!element) return;
|
|
2429
|
+
|
|
2430
|
+
const width = element.offsetWidth;
|
|
2431
|
+
const height = element.offsetHeight;
|
|
2432
|
+
if (typeof width !== "number" && typeof height !== "number") {
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
return { width, height };
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
private _handleResize = () => {
|
|
2440
|
+
const { width, height } = this._getCanvasSize() ?? {};
|
|
2441
|
+
if (!width || !height) return;
|
|
2442
|
+
|
|
2443
|
+
const pixelRatio = isWorker()
|
|
2444
|
+
? (this._options.pixelRatio ?? 1)
|
|
2445
|
+
: window.devicePixelRatio;
|
|
2446
|
+
this.resize(width, height, pixelRatio);
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
/**
|
|
2450
|
+
* Handles pick events and emits the picked feature information.
|
|
2451
|
+
* @param pickArr - Array of picked batch IDs
|
|
2452
|
+
*/
|
|
2453
|
+
private onPick(pickArr: number[]) {
|
|
2454
|
+
this._renderFlag.forceUpdate = true;
|
|
2455
|
+
|
|
2456
|
+
if (pickArr.length > 0) {
|
|
2457
|
+
const batchId = pickArr[0];
|
|
2458
|
+
const prop = this._core?.readPropertyByGlobalBatchId(batchId);
|
|
2459
|
+
if (prop) {
|
|
2460
|
+
const pickedFeature: PickedFeature = {
|
|
2461
|
+
properties: prop.properties,
|
|
2462
|
+
batchId,
|
|
2463
|
+
layerId: prop.layerId,
|
|
2464
|
+
};
|
|
2465
|
+
this.emit("pick", pickedFeature);
|
|
2466
|
+
} else {
|
|
2467
|
+
const emptyFeature: PickedFeature = {
|
|
2468
|
+
properties: {},
|
|
2469
|
+
batchId,
|
|
2470
|
+
layerId: undefined,
|
|
2471
|
+
};
|
|
2472
|
+
this.emit("pick", emptyFeature);
|
|
2473
|
+
}
|
|
2474
|
+
} else {
|
|
2475
|
+
this.emit("pick", null);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/**
|
|
2480
|
+
* Gets whether continuous animation mode is enabled.
|
|
2481
|
+
*/
|
|
2482
|
+
get animation() {
|
|
2483
|
+
return this._renderFlag.animation;
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Sets whether to render every frame continuously (true) or only on changes (false).
|
|
2487
|
+
*/
|
|
2488
|
+
set animation(v: boolean) {
|
|
2489
|
+
this._renderFlag.animation = v;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
/**
|
|
2493
|
+
* Gets the current screen size in pixels.
|
|
2494
|
+
*/
|
|
2495
|
+
get screenSize() {
|
|
2496
|
+
const size = new Vector2();
|
|
2497
|
+
this._renderer.getSize(size);
|
|
2498
|
+
return size;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
/**
|
|
2502
|
+
* Gets the current device pixel ratio.
|
|
2503
|
+
*/
|
|
2504
|
+
get pixelRatio() {
|
|
2505
|
+
return this._renderer.getPixelRatio();
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
/**
|
|
2509
|
+
* Gets whether shadow map debug viewers are displayed.
|
|
2510
|
+
*/
|
|
2511
|
+
get shadowMapViewersEnabled() {
|
|
2512
|
+
return this.shadowMapViewers.enabled;
|
|
2513
|
+
}
|
|
2514
|
+
/**
|
|
2515
|
+
* Enables or disables shadow map debug viewers on screen.
|
|
2516
|
+
*/
|
|
2517
|
+
set shadowMapViewersEnabled(v: boolean) {
|
|
2518
|
+
this.shadowMapViewers.enabled = v;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/**
|
|
2522
|
+
* Picks the terrain position at screen coordinates.
|
|
2523
|
+
* @param x - Screen X coordinate in CSS pixels (same as MouseEvent.clientX)
|
|
2524
|
+
* @param y - Screen Y coordinate in CSS pixels (same as MouseEvent.clientY)
|
|
2525
|
+
* @returns World position Vector3 in ECEF coordinates, or null if no terrain is hit
|
|
2526
|
+
*/
|
|
2527
|
+
pickTerrainPosition(x: number, y: number): Nullable<Vector3> {
|
|
2528
|
+
return this._terrainPicker.pick(
|
|
2529
|
+
x,
|
|
2530
|
+
y,
|
|
2531
|
+
this._renderer,
|
|
2532
|
+
this.globeDepthTexture,
|
|
2533
|
+
this._camera.raw,
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
/**
|
|
2538
|
+
* Picks the position at screen coordinates from the depth.
|
|
2539
|
+
* @param x - Screen X coordinate in CSS pixels (same as MouseEvent.clientX)
|
|
2540
|
+
* @param y - Screen Y coordinate in CSS pixels (same as MouseEvent.clientY)
|
|
2541
|
+
* @returns World position Vector3 in ECEF coordinates, or null if nothing is hit
|
|
2542
|
+
*/
|
|
2543
|
+
pickDepthPosition(x: number, y: number): Nullable<Vector3> {
|
|
2544
|
+
return this._terrainPicker.pick(
|
|
2545
|
+
x,
|
|
2546
|
+
y,
|
|
2547
|
+
this._renderer,
|
|
2548
|
+
this.depthTexture,
|
|
2549
|
+
this._camera.raw,
|
|
2550
|
+
);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
function newId() {
|
|
2555
|
+
return Math.random().toString(36).slice(2);
|
|
2556
|
+
}
|