@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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Color, Vector2, type Texture } from "three";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { planSlots, quantizeSlotCount } from "./SlotPlanner";
|
|
5
|
+
import type { CompositeLayer } from "./types";
|
|
6
|
+
|
|
7
|
+
const fakeTex = {} as unknown as Texture;
|
|
8
|
+
|
|
9
|
+
function rasterLayer(absSlot: number): CompositeLayer {
|
|
10
|
+
return {
|
|
11
|
+
kind: "raster",
|
|
12
|
+
region: absSlot < 24 ? "raster" : "vector",
|
|
13
|
+
absSlot,
|
|
14
|
+
texture: fakeTex,
|
|
15
|
+
uvOffset: new Vector2(0, 0),
|
|
16
|
+
uvScale: new Vector2(1, 1),
|
|
17
|
+
color: new Color(),
|
|
18
|
+
opacity: 1,
|
|
19
|
+
water: false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("quantizeSlotCount", () => {
|
|
24
|
+
it("returns 0 when n is 0 — empty bucket contributes no slots", () => {
|
|
25
|
+
expect(quantizeSlotCount(0, 32)).toBe(0);
|
|
26
|
+
expect(quantizeSlotCount(-1, 32)).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("rounds up to the next power of two", () => {
|
|
30
|
+
expect(quantizeSlotCount(1, 32)).toBe(1);
|
|
31
|
+
expect(quantizeSlotCount(2, 32)).toBe(2);
|
|
32
|
+
expect(quantizeSlotCount(3, 32)).toBe(4);
|
|
33
|
+
expect(quantizeSlotCount(5, 32)).toBe(8);
|
|
34
|
+
expect(quantizeSlotCount(9, 32)).toBe(16);
|
|
35
|
+
expect(quantizeSlotCount(17, 32)).toBe(32);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("clamps to max", () => {
|
|
39
|
+
expect(quantizeSlotCount(33, 32)).toBe(32);
|
|
40
|
+
expect(quantizeSlotCount(100, 32)).toBe(32);
|
|
41
|
+
// Non-power-of-two max: result is the bucket's quantised value capped.
|
|
42
|
+
expect(quantizeSlotCount(7, 6)).toBe(6);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("keeps exact powers of two intact", () => {
|
|
46
|
+
expect(quantizeSlotCount(4, 32)).toBe(4);
|
|
47
|
+
expect(quantizeSlotCount(16, 32)).toBe(16);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("planSlots", () => {
|
|
52
|
+
const BOUNDARY = 24;
|
|
53
|
+
const MAX = 32;
|
|
54
|
+
|
|
55
|
+
it("returns empty layout with no active layers", () => {
|
|
56
|
+
const plan = planSlots([], BOUNDARY, MAX);
|
|
57
|
+
expect(plan).toMatchObject({
|
|
58
|
+
rasterCount: 0,
|
|
59
|
+
vectorCount: 0,
|
|
60
|
+
boundary: 24,
|
|
61
|
+
});
|
|
62
|
+
expect(plan.slots).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("quantizes each bucket's high-water mark independently", () => {
|
|
66
|
+
// raster high-water at absSlot 2 → 3 → quantized 4; vector at 24 → 1.
|
|
67
|
+
const plan = planSlots(
|
|
68
|
+
[rasterLayer(0), rasterLayer(2), rasterLayer(24)],
|
|
69
|
+
BOUNDARY,
|
|
70
|
+
MAX,
|
|
71
|
+
);
|
|
72
|
+
expect(plan.rasterCount).toBe(4);
|
|
73
|
+
expect(plan.vectorCount).toBe(1);
|
|
74
|
+
expect(plan.slots).toHaveLength(5);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("maps compact slots to absolute slots and skips the raster/vector gap", () => {
|
|
78
|
+
const plan = planSlots(
|
|
79
|
+
[rasterLayer(0), rasterLayer(1), rasterLayer(24)],
|
|
80
|
+
BOUNDARY,
|
|
81
|
+
MAX,
|
|
82
|
+
);
|
|
83
|
+
// Raster compact slots map 1:1; vector compact slot 2 jumps to abs 24.
|
|
84
|
+
expect(plan.slots.map((s) => s.absSlot)).toEqual([0, 1, 24]);
|
|
85
|
+
expect(plan.slots.map((s) => s.region)).toEqual([
|
|
86
|
+
"raster",
|
|
87
|
+
"raster",
|
|
88
|
+
"vector",
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("leaves inactive prefix slots without a layer", () => {
|
|
93
|
+
// Only absSlot 2 active in the raster bucket → quantized to 4 slots,
|
|
94
|
+
// slots 0,1,3 carry no layer.
|
|
95
|
+
const layer = rasterLayer(2);
|
|
96
|
+
const plan = planSlots([layer], BOUNDARY, MAX);
|
|
97
|
+
expect(plan.rasterCount).toBe(4);
|
|
98
|
+
expect(plan.slots.map((s) => s.layer)).toEqual([
|
|
99
|
+
undefined,
|
|
100
|
+
undefined,
|
|
101
|
+
layer,
|
|
102
|
+
undefined,
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { CompositeLayer, CompositeLayerRegion } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Round `n` up to the next power of two, clamped to `[0, max]`. Bounds the
|
|
5
|
+
* number of generated shader variants to log₂(max)+1 so churn in the active
|
|
6
|
+
* count doesn't trigger a shader-compile per tile per frame. Compiles are
|
|
7
|
+
* tens of ms each — variant explosion is the worst-case stutter source here.
|
|
8
|
+
*
|
|
9
|
+
* `Math.clz32(n - 1)` gives the leading-zero count of (n-1) interpreted as a
|
|
10
|
+
* uint32. `1 << (32 - clz32(n - 1))` is then the smallest power of two ≥ n.
|
|
11
|
+
* For n=1, clz32(0)=32 so the shift is 1<<0=1 — the n=1 edge falls out of the
|
|
12
|
+
* same formula. Returns 0 when n is 0 so an empty bucket contributes no slots.
|
|
13
|
+
*/
|
|
14
|
+
export function quantizeSlotCount(n: number, max: number): number {
|
|
15
|
+
if (n <= 0) return 0;
|
|
16
|
+
return Math.min(1 << (32 - Math.clz32(n - 1)), max);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** One compact slot, mapped to its absolute slot and (optionally) a layer. */
|
|
20
|
+
export type SlotBinding = {
|
|
21
|
+
/** Compact index `[0, rasterCount + vectorCount)`. */
|
|
22
|
+
compactSlot: number;
|
|
23
|
+
/** Absolute slot index into the TileMesh main-shader per-slot uniforms. */
|
|
24
|
+
absSlot: number;
|
|
25
|
+
region: CompositeLayerRegion;
|
|
26
|
+
/**
|
|
27
|
+
* The active layer occupying `absSlot`, or undefined for an inactive prefix
|
|
28
|
+
* slot. The compact space is a prefix per bucket (so `winningSlot = absSlot`
|
|
29
|
+
* stays addressable), hence a quantized bucket can hold gaps.
|
|
30
|
+
*/
|
|
31
|
+
layer: CompositeLayer | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The compact slot layout for one tile's composite pass. `rasterCount` and
|
|
36
|
+
* `vectorCount` are power-of-two-quantized high-water marks; `slots` is the
|
|
37
|
+
* concrete compact→absolute mapping the enhancers bind against.
|
|
38
|
+
*/
|
|
39
|
+
export type SlotPlan = {
|
|
40
|
+
rasterCount: number;
|
|
41
|
+
vectorCount: number;
|
|
42
|
+
/** `texturizedSceneIndexFrom` — boundary between raster and vector regions. */
|
|
43
|
+
boundary: number;
|
|
44
|
+
slots: SlotBinding[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Flatten active layers into the compact slot layout. Raster-region layers
|
|
49
|
+
* occupy compact slots `[0, rasterCount)` (absSlot = compact); vector-region
|
|
50
|
+
* layers occupy `[rasterCount, total)` mapping to `boundary + offset`. Each
|
|
51
|
+
* bucket's count is the quantized high-water mark of its active absolute slots.
|
|
52
|
+
*
|
|
53
|
+
* Callers pass only active layers (shown AND textured), so each bucket's
|
|
54
|
+
* high-water mark is simply its highest active absolute slot.
|
|
55
|
+
*
|
|
56
|
+
* Stitching seam: a layer maps to exactly one slot today. To stitch multiple
|
|
57
|
+
* source textures onto one logical layer later, expand a layer into several
|
|
58
|
+
* consecutive SlotBindings here — the rest of the pipeline already binds per
|
|
59
|
+
* SlotBinding.
|
|
60
|
+
*/
|
|
61
|
+
export function planSlots(
|
|
62
|
+
layers: readonly CompositeLayer[],
|
|
63
|
+
boundary: number,
|
|
64
|
+
maxTextures: number,
|
|
65
|
+
): SlotPlan {
|
|
66
|
+
let rasterHigh = 0;
|
|
67
|
+
let vectorHigh = 0;
|
|
68
|
+
const byAbsSlot = new Map<number, CompositeLayer>();
|
|
69
|
+
for (const layer of layers) {
|
|
70
|
+
byAbsSlot.set(layer.absSlot, layer);
|
|
71
|
+
if (layer.region === "raster") {
|
|
72
|
+
rasterHigh = Math.max(rasterHigh, layer.absSlot + 1);
|
|
73
|
+
} else {
|
|
74
|
+
vectorHigh = Math.max(vectorHigh, layer.absSlot - boundary + 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rasterCount = quantizeSlotCount(rasterHigh, boundary);
|
|
79
|
+
const vectorCount = quantizeSlotCount(vectorHigh, maxTextures - boundary);
|
|
80
|
+
|
|
81
|
+
const slots: SlotBinding[] = [];
|
|
82
|
+
for (
|
|
83
|
+
let compactSlot = 0;
|
|
84
|
+
compactSlot < rasterCount + vectorCount;
|
|
85
|
+
compactSlot++
|
|
86
|
+
) {
|
|
87
|
+
const region: CompositeLayerRegion =
|
|
88
|
+
compactSlot < rasterCount ? "raster" : "vector";
|
|
89
|
+
const absSlot =
|
|
90
|
+
compactSlot < rasterCount
|
|
91
|
+
? compactSlot
|
|
92
|
+
: boundary + (compactSlot - rasterCount);
|
|
93
|
+
slots.push({
|
|
94
|
+
compactSlot,
|
|
95
|
+
absSlot,
|
|
96
|
+
region,
|
|
97
|
+
layer: byAbsSlot.get(absSlot),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { rasterCount, vectorCount, boundary, slots };
|
|
102
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { TileTextureCache } from "./TileTextureCache";
|
|
4
|
+
import type { AtlasFactory, CompositeAtlas } from "./types";
|
|
5
|
+
|
|
6
|
+
function makeFakeAtlas(): CompositeAtlas {
|
|
7
|
+
// Minimal stand-ins for Texture / WebGLRenderTarget — the cache only checks
|
|
8
|
+
// identity (acquire returns the same Texture instance) and lifecycle.
|
|
9
|
+
const tex = (name: string) =>
|
|
10
|
+
({ name, needsUpdate: false }) as unknown as CompositeAtlas["color"];
|
|
11
|
+
return {
|
|
12
|
+
target: { dispose: vi.fn() } as unknown as CompositeAtlas["target"],
|
|
13
|
+
color: tex("color"),
|
|
14
|
+
attr: tex("attr"),
|
|
15
|
+
normal: tex("normal"),
|
|
16
|
+
dispose: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeCache(maxPooled = 0) {
|
|
21
|
+
const factory = vi.fn<AtlasFactory>(() => makeFakeAtlas());
|
|
22
|
+
const cache = new TileTextureCache({
|
|
23
|
+
size: 512,
|
|
24
|
+
atlasFactory: factory,
|
|
25
|
+
maxPooled,
|
|
26
|
+
});
|
|
27
|
+
return { cache, factory };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("TileTextureCache.acquire", () => {
|
|
31
|
+
it("creates exactly one atlas per handle even across multiple acquires", () => {
|
|
32
|
+
const { cache, factory } = makeCache();
|
|
33
|
+
const a = cache.acquire(1n);
|
|
34
|
+
const b = cache.acquire(1n);
|
|
35
|
+
expect(factory).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(a.color).toBe(b.color);
|
|
37
|
+
expect(cache.size).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("newly-acquired handles start dirty in every reason category", () => {
|
|
41
|
+
const { cache } = makeCache();
|
|
42
|
+
cache.acquire(1n);
|
|
43
|
+
const reasons = cache.consumeDirty(1n);
|
|
44
|
+
expect(reasons).not.toBeNull();
|
|
45
|
+
// First composite must run, so all reason categories present.
|
|
46
|
+
expect(reasons?.has("material")).toBe(true);
|
|
47
|
+
expect(reasons?.has("texture-binding")).toBe(true);
|
|
48
|
+
expect(reasons?.has("vector-revision")).toBe(true);
|
|
49
|
+
expect(reasons?.has("hillshade")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("TileTextureCache.release", () => {
|
|
54
|
+
it("disposes atlas only when refCount reaches zero (pooling disabled)", () => {
|
|
55
|
+
const { cache, factory } = makeCache();
|
|
56
|
+
cache.acquire(1n);
|
|
57
|
+
cache.acquire(1n);
|
|
58
|
+
const entry = cache.getEntry(1n);
|
|
59
|
+
if (!entry) throw new Error("expected entry to exist");
|
|
60
|
+
const atlas = entry.atlas;
|
|
61
|
+
|
|
62
|
+
cache.release(1n);
|
|
63
|
+
expect(atlas.dispose).not.toHaveBeenCalled();
|
|
64
|
+
expect(cache.size).toBe(1);
|
|
65
|
+
|
|
66
|
+
cache.release(1n);
|
|
67
|
+
expect(atlas.dispose).toHaveBeenCalledTimes(1);
|
|
68
|
+
expect(cache.size).toBe(0);
|
|
69
|
+
|
|
70
|
+
// A subsequent acquire reuses the slot but spins up a fresh atlas.
|
|
71
|
+
cache.acquire(1n);
|
|
72
|
+
expect(factory).toHaveBeenCalledTimes(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("is a no-op for unknown handles", () => {
|
|
76
|
+
const { cache } = makeCache();
|
|
77
|
+
expect(() => cache.release(999n)).not.toThrow();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("TileTextureCache atlas pooling", () => {
|
|
82
|
+
it("pools a fully-released atlas and reuses it for the next acquire", () => {
|
|
83
|
+
const { cache, factory } = makeCache(2);
|
|
84
|
+
cache.acquire(1n);
|
|
85
|
+
const entry = cache.getEntry(1n);
|
|
86
|
+
if (!entry) throw new Error("expected entry to exist");
|
|
87
|
+
const atlas = entry.atlas;
|
|
88
|
+
|
|
89
|
+
cache.release(1n);
|
|
90
|
+
expect(atlas.dispose).not.toHaveBeenCalled();
|
|
91
|
+
expect(cache.pooledCount).toBe(1);
|
|
92
|
+
|
|
93
|
+
// Reused for a different handle, without a factory call.
|
|
94
|
+
const out = cache.acquire(2n);
|
|
95
|
+
expect(factory).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(out.color).toBe(atlas.color);
|
|
97
|
+
expect(cache.pooledCount).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("marks an entry backed by a pooled atlas fully dirty so it gets repainted", () => {
|
|
101
|
+
const { cache } = makeCache(2);
|
|
102
|
+
cache.acquire(1n);
|
|
103
|
+
cache.consumeDirty(1n);
|
|
104
|
+
cache.release(1n);
|
|
105
|
+
|
|
106
|
+
cache.acquire(2n);
|
|
107
|
+
const reasons = cache.consumeDirty(2n);
|
|
108
|
+
expect(reasons?.has("material")).toBe(true);
|
|
109
|
+
expect(reasons?.has("texture-binding")).toBe(true);
|
|
110
|
+
expect(reasons?.has("vector-revision")).toBe(true);
|
|
111
|
+
expect(reasons?.has("hillshade")).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("disposes released atlases once the pool is full", () => {
|
|
115
|
+
const { cache } = makeCache(1);
|
|
116
|
+
cache.acquire(1n);
|
|
117
|
+
cache.acquire(2n);
|
|
118
|
+
const e1 = cache.getEntry(1n);
|
|
119
|
+
const e2 = cache.getEntry(2n);
|
|
120
|
+
if (!e1 || !e2) throw new Error("expected both entries");
|
|
121
|
+
|
|
122
|
+
cache.release(1n);
|
|
123
|
+
expect(e1.atlas.dispose).not.toHaveBeenCalled();
|
|
124
|
+
expect(cache.pooledCount).toBe(1);
|
|
125
|
+
|
|
126
|
+
cache.release(2n);
|
|
127
|
+
expect(e2.atlas.dispose).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(cache.pooledCount).toBe(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("disposeAll drains the pool", () => {
|
|
132
|
+
const { cache } = makeCache(2);
|
|
133
|
+
cache.acquire(1n);
|
|
134
|
+
const entry = cache.getEntry(1n);
|
|
135
|
+
if (!entry) throw new Error("expected entry to exist");
|
|
136
|
+
const atlas = entry.atlas;
|
|
137
|
+
cache.release(1n);
|
|
138
|
+
expect(cache.pooledCount).toBe(1);
|
|
139
|
+
|
|
140
|
+
cache.disposeAll();
|
|
141
|
+
expect(atlas.dispose).toHaveBeenCalledTimes(1);
|
|
142
|
+
expect(cache.pooledCount).toBe(0);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("TileTextureCache.markDirty + consumeDirty", () => {
|
|
147
|
+
it("coalesces multiple markDirty calls into a single consume result", () => {
|
|
148
|
+
const { cache } = makeCache();
|
|
149
|
+
cache.acquire(1n);
|
|
150
|
+
// Drain the initial-acquire dirty set so we test markDirty in isolation.
|
|
151
|
+
cache.consumeDirty(1n);
|
|
152
|
+
|
|
153
|
+
cache.markDirty(1n, "hillshade");
|
|
154
|
+
cache.markDirty(1n, "vector-revision");
|
|
155
|
+
cache.markDirty(1n, "hillshade"); // duplicate
|
|
156
|
+
|
|
157
|
+
const reasons = cache.consumeDirty(1n);
|
|
158
|
+
expect(reasons?.size).toBe(2);
|
|
159
|
+
expect(reasons?.has("hillshade")).toBe(true);
|
|
160
|
+
expect(reasons?.has("vector-revision")).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("consumeDirty leaves the entry clean", () => {
|
|
164
|
+
const { cache } = makeCache();
|
|
165
|
+
cache.acquire(1n);
|
|
166
|
+
cache.consumeDirty(1n);
|
|
167
|
+
expect(cache.isDirty(1n)).toBe(false);
|
|
168
|
+
expect(cache.consumeDirty(1n)).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("markDirty on unknown handle is a no-op (no entry created)", () => {
|
|
172
|
+
const { cache } = makeCache();
|
|
173
|
+
cache.markDirty(42n, "hillshade");
|
|
174
|
+
expect(cache.size).toBe(0);
|
|
175
|
+
expect(cache.consumeDirty(42n)).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("TileTextureCache dev refcount diagnostics", () => {
|
|
180
|
+
it("logs an error on release without acquire", () => {
|
|
181
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
182
|
+
const { cache } = makeCache();
|
|
183
|
+
cache.release(999n);
|
|
184
|
+
expect(spy).toHaveBeenCalledWith(
|
|
185
|
+
expect.stringContaining("double release or release without acquire"),
|
|
186
|
+
);
|
|
187
|
+
spy.mockRestore();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("logs an error on double release", () => {
|
|
191
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
192
|
+
const { cache } = makeCache();
|
|
193
|
+
cache.acquire(1n);
|
|
194
|
+
cache.release(1n);
|
|
195
|
+
// Entry is gone after the first release, so this is release-without-entry.
|
|
196
|
+
cache.release(1n);
|
|
197
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
198
|
+
spy.mockRestore();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("logs an error when disposeAll finds live refcounts", () => {
|
|
202
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
203
|
+
const { cache } = makeCache();
|
|
204
|
+
cache.acquire(1n);
|
|
205
|
+
cache.disposeAll();
|
|
206
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining("leaked acquire"));
|
|
207
|
+
spy.mockRestore();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("TileTextureCache.disposeAll", () => {
|
|
212
|
+
it("disposes every atlas and clears the cache", () => {
|
|
213
|
+
const { cache } = makeCache();
|
|
214
|
+
cache.acquire(1n);
|
|
215
|
+
cache.acquire(2n);
|
|
216
|
+
const e1 = cache.getEntry(1n);
|
|
217
|
+
const e2 = cache.getEntry(2n);
|
|
218
|
+
if (!e1 || !e2) throw new Error("expected both entries");
|
|
219
|
+
const a1 = e1.atlas;
|
|
220
|
+
const a2 = e2.atlas;
|
|
221
|
+
|
|
222
|
+
cache.disposeAll();
|
|
223
|
+
expect(a1.dispose).toHaveBeenCalledTimes(1);
|
|
224
|
+
expect(a2.dispose).toHaveBeenCalledTimes(1);
|
|
225
|
+
expect(cache.size).toBe(0);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { TileHandle } from "@navaramap/core";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AtlasFactory,
|
|
5
|
+
CacheEntry,
|
|
6
|
+
CompositeAtlas,
|
|
7
|
+
CompositeOutputs,
|
|
8
|
+
DirtyReason,
|
|
9
|
+
} from "./types";
|
|
10
|
+
|
|
11
|
+
/** Dev-build-only refcount diagnostics (never throws in production). */
|
|
12
|
+
const DEV =
|
|
13
|
+
typeof import.meta !== "undefined" && !!(import.meta as ImportMeta).env?.DEV;
|
|
14
|
+
|
|
15
|
+
// Every atlas is the same size and format, but tile churn during pan/zoom
|
|
16
|
+
// used to allocate and dispose a ~3MB MRT render target per terrain tile
|
|
17
|
+
// swap. Keeping a few released atlases around lets the next acquire reuse
|
|
18
|
+
// them. Kept small on purpose: pooled atlases are GPU memory the per-tile
|
|
19
|
+
// ledger accounting does not see (each live tile is charged for its own
|
|
20
|
+
// atlas at mesh-attach time, a pooled spare belongs to no tile).
|
|
21
|
+
const DEFAULT_MAX_POOLED_ATLASES = 4;
|
|
22
|
+
|
|
23
|
+
export type TileTextureCacheOptions = {
|
|
24
|
+
/** Side length (in pixels) of the composite render targets. */
|
|
25
|
+
size: number;
|
|
26
|
+
/** Builds the MRT trio. Injected so unit tests pass fakes. */
|
|
27
|
+
atlasFactory: AtlasFactory;
|
|
28
|
+
/** Max spare atlases kept for reuse instead of disposed. 0 disables pooling. */
|
|
29
|
+
maxPooled?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pure cache layer for per-tile composite atlases.
|
|
34
|
+
*
|
|
35
|
+
* Why: TileMesh previously owned its own WebGLRenderTargets and had no way to
|
|
36
|
+
* coalesce updates from independent sources (vector revision, hillshade
|
|
37
|
+
* backfill, material change). Lifting these responsibilities here lets the
|
|
38
|
+
* compositor render N source textures into one atlas only when something
|
|
39
|
+
* actually changes, and makes the bookkeeping testable without three.js.
|
|
40
|
+
*/
|
|
41
|
+
export class TileTextureCache {
|
|
42
|
+
private readonly entries = new Map<TileHandle, CacheEntry>();
|
|
43
|
+
private readonly opts: TileTextureCacheOptions;
|
|
44
|
+
private readonly pool: CompositeAtlas[] = [];
|
|
45
|
+
private readonly maxPooled: number;
|
|
46
|
+
|
|
47
|
+
constructor(opts: TileTextureCacheOptions) {
|
|
48
|
+
this.opts = opts;
|
|
49
|
+
this.maxPooled = opts.maxPooled ?? DEFAULT_MAX_POOLED_ATLASES;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Acquire (or create) the atlas for a handle. Refcounted: a TileMesh
|
|
54
|
+
* acquires once on construction and releases once on dispose.
|
|
55
|
+
* Newly-created entries start dirty in every category so the first composite
|
|
56
|
+
* pass paints them — which also repaints any stale content in a pooled
|
|
57
|
+
* atlas, so reuse needs no explicit clear.
|
|
58
|
+
*/
|
|
59
|
+
acquire(handle: TileHandle): CompositeOutputs {
|
|
60
|
+
let entry = this.entries.get(handle);
|
|
61
|
+
if (!entry) {
|
|
62
|
+
const atlas = this.pool.pop() ?? this.opts.atlasFactory(this.opts.size);
|
|
63
|
+
entry = {
|
|
64
|
+
handle,
|
|
65
|
+
atlas,
|
|
66
|
+
refCount: 0,
|
|
67
|
+
dirty: new Set<DirtyReason>([
|
|
68
|
+
"material",
|
|
69
|
+
"texture-binding",
|
|
70
|
+
"vector-revision",
|
|
71
|
+
"hillshade",
|
|
72
|
+
]),
|
|
73
|
+
};
|
|
74
|
+
this.entries.set(handle, entry);
|
|
75
|
+
}
|
|
76
|
+
entry.refCount++;
|
|
77
|
+
return {
|
|
78
|
+
color: entry.atlas.color,
|
|
79
|
+
attr: entry.atlas.attr,
|
|
80
|
+
normal: entry.atlas.normal,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Decrement refcount. When the last holder releases, the entry is forgotten
|
|
86
|
+
* and its atlas is pooled for reuse (or disposed once the pool is full).
|
|
87
|
+
*/
|
|
88
|
+
release(handle: TileHandle): void {
|
|
89
|
+
const entry = this.entries.get(handle);
|
|
90
|
+
if (!entry) {
|
|
91
|
+
if (DEV) {
|
|
92
|
+
console.error(
|
|
93
|
+
`TileTextureCache.release: no entry for handle ${handle} (double release or release without acquire)`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
entry.refCount--;
|
|
99
|
+
if (DEV && entry.refCount < 0) {
|
|
100
|
+
console.error(
|
|
101
|
+
`TileTextureCache.release: refcount went negative for handle ${handle}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (entry.refCount <= 0) {
|
|
105
|
+
if (this.pool.length < this.maxPooled) {
|
|
106
|
+
this.pool.push(entry.atlas);
|
|
107
|
+
} else {
|
|
108
|
+
entry.atlas.dispose();
|
|
109
|
+
}
|
|
110
|
+
this.entries.delete(handle);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Mark an entry dirty for a given reason. No-op if handle isn't tracked. */
|
|
115
|
+
markDirty(handle: TileHandle, reason: DirtyReason): void {
|
|
116
|
+
const entry = this.entries.get(handle);
|
|
117
|
+
if (!entry) return;
|
|
118
|
+
entry.dirty.add(reason);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Return the set of dirty reasons (and clear them) for a handle. Returns
|
|
123
|
+
* null when the handle isn't tracked or has nothing dirty — compositor uses
|
|
124
|
+
* that to skip the offscreen pass.
|
|
125
|
+
*/
|
|
126
|
+
consumeDirty(handle: TileHandle): Set<DirtyReason> | null {
|
|
127
|
+
const entry = this.entries.get(handle);
|
|
128
|
+
if (!entry || entry.dirty.size === 0) return null;
|
|
129
|
+
const reasons = entry.dirty;
|
|
130
|
+
entry.dirty = new Set<DirtyReason>();
|
|
131
|
+
return reasons;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Peek without clearing — used by the compositor's scheduling pass. */
|
|
135
|
+
isDirty(handle: TileHandle): boolean {
|
|
136
|
+
const entry = this.entries.get(handle);
|
|
137
|
+
return !!entry && entry.dirty.size > 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Atlas accessor for the compositor's internal render path. */
|
|
141
|
+
getEntry(handle: TileHandle): CacheEntry | undefined {
|
|
142
|
+
return this.entries.get(handle);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Return the outputs for a handle that was already acquired. Unlike
|
|
147
|
+
* acquire(), does NOT touch the refcount — use this from places that just
|
|
148
|
+
* need to bind the atlas textures (e.g. TileMesh.initMaterial after the
|
|
149
|
+
* constructor already acquired the entry).
|
|
150
|
+
*/
|
|
151
|
+
getOutputs(handle: TileHandle): CompositeOutputs | undefined {
|
|
152
|
+
const entry = this.entries.get(handle);
|
|
153
|
+
if (!entry) return undefined;
|
|
154
|
+
return {
|
|
155
|
+
color: entry.atlas.color,
|
|
156
|
+
attr: entry.atlas.attr,
|
|
157
|
+
normal: entry.atlas.normal,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Iterate all live entries. Used by compositor.update(). */
|
|
162
|
+
entriesIter(): IterableIterator<CacheEntry> {
|
|
163
|
+
return this.entries.values();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Number of tracked entries (testing/debug). */
|
|
167
|
+
get size(): number {
|
|
168
|
+
return this.entries.size;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Number of pooled (unowned) spare atlases — for stats/debugging. */
|
|
172
|
+
get pooledCount(): number {
|
|
173
|
+
return this.pool.length;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Dispose all atlases (live and pooled) and clear. Called on view shutdown. */
|
|
177
|
+
disposeAll(): void {
|
|
178
|
+
for (const entry of this.entries.values()) {
|
|
179
|
+
if (DEV && entry.refCount > 0) {
|
|
180
|
+
console.error(
|
|
181
|
+
`TileTextureCache.disposeAll: handle ${entry.handle} still has refcount ${entry.refCount} (leaked acquire)`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
entry.atlas.dispose();
|
|
185
|
+
}
|
|
186
|
+
this.entries.clear();
|
|
187
|
+
for (const atlas of this.pool) {
|
|
188
|
+
atlas.dispose();
|
|
189
|
+
}
|
|
190
|
+
this.pool.length = 0;
|
|
191
|
+
}
|
|
192
|
+
}
|