@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 @@
|
|
|
1
|
+
export * from "./effect";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { EffectDesc, BaseHandle, EffectHandle } from "./core";
|
|
2
|
+
import { Layer, type LayerEvent } from "./layer";
|
|
3
|
+
|
|
4
|
+
export class LayersManager {
|
|
5
|
+
private layers = new Map<string, Layer | BaseHandle>();
|
|
6
|
+
|
|
7
|
+
add(l: Layer | BaseHandle) {
|
|
8
|
+
this.layers.set(l.id, l);
|
|
9
|
+
const deleteLayer = () => {
|
|
10
|
+
this.layers.delete(l.id);
|
|
11
|
+
};
|
|
12
|
+
if (l instanceof BaseHandle) {
|
|
13
|
+
l.on("deleted", deleteLayer);
|
|
14
|
+
} else {
|
|
15
|
+
l.on("deleted", deleteLayer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(id: string) {
|
|
20
|
+
return this.layers.get(id);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
emitById<K extends keyof LayerEvent>(
|
|
24
|
+
k: K,
|
|
25
|
+
id: string,
|
|
26
|
+
...args: Parameters<LayerEvent[K]>
|
|
27
|
+
) {
|
|
28
|
+
const l = this.layers.get(id);
|
|
29
|
+
if (!l) return;
|
|
30
|
+
if (l instanceof Layer) {
|
|
31
|
+
l.emit(k, ...args);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
emitAll<K extends keyof LayerEvent>(
|
|
36
|
+
k: K,
|
|
37
|
+
...args: Parameters<LayerEvent[K]>
|
|
38
|
+
) {
|
|
39
|
+
for (const l of this.layers.values()) {
|
|
40
|
+
if (l instanceof Layer) {
|
|
41
|
+
l.emit(k, ...args);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
*getResourceLayers() {
|
|
47
|
+
for (const l of this.layers.values()) {
|
|
48
|
+
if (!(l instanceof Layer)) continue;
|
|
49
|
+
yield l;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
*getEffectDescs(): Generator<EffectHandle> {
|
|
54
|
+
for (const handle of this.layers.values()) {
|
|
55
|
+
if (!(handle instanceof BaseHandle)) continue;
|
|
56
|
+
|
|
57
|
+
const desc = handle.ref;
|
|
58
|
+
if (!(desc instanceof EffectDesc)) continue;
|
|
59
|
+
|
|
60
|
+
yield handle as EffectHandle;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/light.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Vector3 } from "three";
|
|
2
|
+
|
|
3
|
+
export type Light = {
|
|
4
|
+
ambient?: {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
color?: number;
|
|
7
|
+
intensity?: number;
|
|
8
|
+
};
|
|
9
|
+
sun?: {
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
color?: number;
|
|
12
|
+
position?: Vector3;
|
|
13
|
+
intensity?: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Loader } from "three";
|
|
2
|
+
|
|
3
|
+
function createElementNS(name: string) {
|
|
4
|
+
return document.createElementNS("http://www.w3.org/1999/xhtml", name);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Same shape as a fetch abort rejection: `err.name === "AbortError"`. */
|
|
8
|
+
function abortError() {
|
|
9
|
+
return new DOMException("The operation was aborted.", "AbortError");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Decoded tile image. `ImageBitmap` on browsers with `createImageBitmap`
|
|
14
|
+
* (decode happens off the main thread); `HTMLImageElement` on the fallback
|
|
15
|
+
* path. An `ImageBitmap` is produced with `imageOrientation: "flipY"`, so the
|
|
16
|
+
* consumer must upload it with `texture.flipY = false` to match the
|
|
17
|
+
* `<img>`-path orientation (the GL flip flag is ignored for ImageBitmap
|
|
18
|
+
* uploads anyway).
|
|
19
|
+
*/
|
|
20
|
+
export type LoadedImage = HTMLImageElement | ImageBitmap;
|
|
21
|
+
|
|
22
|
+
// Ref: https://github.com/mrdoob/three.js/blob/beab9e845f9e5ae11d648f55b24a0e910b56a85a/src/loaders/ImageLoader.js
|
|
23
|
+
export class AbortableImageLoader extends Loader<LoadedImage> {
|
|
24
|
+
loadAsyncWithAbort(
|
|
25
|
+
url: string,
|
|
26
|
+
abort?: AbortController,
|
|
27
|
+
onProgress?: (event: ProgressEvent) => void,
|
|
28
|
+
) {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
30
|
+
const scope = this;
|
|
31
|
+
|
|
32
|
+
return new Promise<LoadedImage>(function (resolve, reject) {
|
|
33
|
+
scope.load(url, resolve, onProgress, reject, abort);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
load(
|
|
38
|
+
url: string,
|
|
39
|
+
onLoad: (data: LoadedImage) => void,
|
|
40
|
+
_onProgress?: (event: ProgressEvent) => void,
|
|
41
|
+
onError?: (err: unknown, isAborted?: boolean) => void,
|
|
42
|
+
abort?: AbortController,
|
|
43
|
+
timeout = 5000,
|
|
44
|
+
): void {
|
|
45
|
+
if (this.path !== undefined) url = this.path + url;
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
48
|
+
const scope = this;
|
|
49
|
+
|
|
50
|
+
let settled = false;
|
|
51
|
+
|
|
52
|
+
// Report the timeout before aborting: abort() dispatches its event
|
|
53
|
+
// synchronously, so the reverse order would let the fallback path's
|
|
54
|
+
// abort listener settle the load as AbortError first and swallow the
|
|
55
|
+
// TimeoutError.
|
|
56
|
+
const timeoutId = window.setTimeout(() => {
|
|
57
|
+
fail(new Error("TimeoutError"));
|
|
58
|
+
abort?.abort();
|
|
59
|
+
}, timeout);
|
|
60
|
+
|
|
61
|
+
function done(image: LoadedImage) {
|
|
62
|
+
if (settled) return;
|
|
63
|
+
settled = true;
|
|
64
|
+
window.clearTimeout(timeoutId);
|
|
65
|
+
|
|
66
|
+
onLoad(image);
|
|
67
|
+
|
|
68
|
+
scope.manager.itemEnd(url);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function fail(err: unknown, isAborted?: boolean) {
|
|
72
|
+
if (settled) return;
|
|
73
|
+
settled = true;
|
|
74
|
+
window.clearTimeout(timeoutId);
|
|
75
|
+
|
|
76
|
+
if (onError) onError(err, isAborted);
|
|
77
|
+
|
|
78
|
+
scope.manager.itemError(url);
|
|
79
|
+
scope.manager.itemEnd(url);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Before any async work, so every done/fail (which call itemEnd) has a
|
|
83
|
+
// matching itemStart even when the fetch itself rejects or times out.
|
|
84
|
+
scope.manager.itemStart(url);
|
|
85
|
+
|
|
86
|
+
fetch(url, { signal: abort?.signal })
|
|
87
|
+
.then((r) => {
|
|
88
|
+
// Unlike the <img> path (which fires "error" on a 4xx/5xx), fetch
|
|
89
|
+
// resolves on HTTP error statuses — fail here instead of trying to
|
|
90
|
+
// decode the error body as an image (e.g. a 404 that returns a
|
|
91
|
+
// placeholder image would otherwise be shown as a real tile).
|
|
92
|
+
if (!r.ok) throw new Error(`HTTP ${r.status} while loading ${url}`);
|
|
93
|
+
return r.blob();
|
|
94
|
+
})
|
|
95
|
+
.then((blob) => {
|
|
96
|
+
if (settled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (abort?.signal.aborted) {
|
|
100
|
+
fail(abortError(), true);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof createImageBitmap === "function") {
|
|
105
|
+
// Decodes off the main thread and needs no object URL.
|
|
106
|
+
// - flipY is baked in (see LoadedImage);
|
|
107
|
+
// - premultiply disabled and colorSpaceConversion "none" so the
|
|
108
|
+
// decoded bytes match the old <img> → texImage2D path, where
|
|
109
|
+
// three sets UNPACK_PREMULTIPLY_ALPHA/UNPACK_COLORSPACE_CONVERSION
|
|
110
|
+
// to NONE (critical for RGB-encoded elevation fragments, whose
|
|
111
|
+
// bytes must not be color-managed before the shader decodes them).
|
|
112
|
+
createImageBitmap(blob, {
|
|
113
|
+
imageOrientation: "flipY",
|
|
114
|
+
premultiplyAlpha: "none",
|
|
115
|
+
colorSpaceConversion: "none",
|
|
116
|
+
})
|
|
117
|
+
.then((bitmap) => {
|
|
118
|
+
// createImageBitmap cannot be cancelled; if the load already
|
|
119
|
+
// settled (abort/timeout) while it was decoding, just release
|
|
120
|
+
// the now-useless bitmap.
|
|
121
|
+
if (settled || abort?.signal.aborted) {
|
|
122
|
+
bitmap.close();
|
|
123
|
+
if (!settled) fail(abortError(), true);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
done(bitmap);
|
|
127
|
+
})
|
|
128
|
+
.catch((e) => fail(e));
|
|
129
|
+
// Settle promptly on abort instead of blocking itemEnd (and any
|
|
130
|
+
// loads gated on it) until the un-cancellable decode finishes. The
|
|
131
|
+
// decode still runs; its .then closes the orphaned bitmap above.
|
|
132
|
+
abort?.signal.addEventListener(
|
|
133
|
+
"abort",
|
|
134
|
+
() => fail(abortError(), true),
|
|
135
|
+
{ once: true },
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.loadViaImageElement(url, blob, done, fail, abort);
|
|
141
|
+
})
|
|
142
|
+
.catch((e) => {
|
|
143
|
+
fail(e, !e.name || e.name === "AbortError");
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Fallback for browsers without `createImageBitmap`. The object URL keeps
|
|
149
|
+
* the blob alive until revoked, so it must be revoked on every exit path
|
|
150
|
+
* (load, error, abort) or each fetched tile leaks its encoded bytes for the
|
|
151
|
+
* page's lifetime.
|
|
152
|
+
*/
|
|
153
|
+
private loadViaImageElement(
|
|
154
|
+
url: string,
|
|
155
|
+
blob: Blob,
|
|
156
|
+
done: (image: LoadedImage) => void,
|
|
157
|
+
fail: (err: unknown, isAborted?: boolean) => void,
|
|
158
|
+
abort?: AbortController,
|
|
159
|
+
) {
|
|
160
|
+
if (abort?.signal.aborted) {
|
|
161
|
+
fail(abortError(), true);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const objectUrl = window.URL.createObjectURL(blob);
|
|
166
|
+
const image = createElementNS("img") as HTMLImageElement;
|
|
167
|
+
|
|
168
|
+
function cleanup() {
|
|
169
|
+
window.URL.revokeObjectURL(objectUrl);
|
|
170
|
+
image.removeEventListener("load", onImageLoad, false);
|
|
171
|
+
image.removeEventListener("error", onImageError, false);
|
|
172
|
+
abort?.signal.removeEventListener("abort", onAbort, false);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onImageLoad(this: HTMLImageElement) {
|
|
176
|
+
cleanup();
|
|
177
|
+
done(this);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function onImageError(event: unknown) {
|
|
181
|
+
cleanup();
|
|
182
|
+
fail(event);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onAbort() {
|
|
186
|
+
// Abort the decode by clearing the image element.
|
|
187
|
+
image.src = "";
|
|
188
|
+
image.remove();
|
|
189
|
+
cleanup();
|
|
190
|
+
fail(abortError(), true);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
image.addEventListener("load", onImageLoad, false);
|
|
194
|
+
image.addEventListener("error", onImageError, false);
|
|
195
|
+
abort?.signal.addEventListener("abort", onAbort, false);
|
|
196
|
+
|
|
197
|
+
if (url.slice(0, 5) !== "data:") {
|
|
198
|
+
if (this.crossOrigin !== undefined) image.crossOrigin = this.crossOrigin;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
image.src = objectUrl;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Loader, Texture } from "three";
|
|
2
|
+
|
|
3
|
+
import { AbortableImageLoader } from "./AbortableImageLoader";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Dispose a texture and release its backing image. When the texture is backed
|
|
7
|
+
* by an `ImageBitmap` (the `createImageBitmap` decode path in
|
|
8
|
+
* AbortableImageLoader), `texture.dispose()` frees the GL texture but NOT the
|
|
9
|
+
* bitmap's decoded-pixel memory, which lives off-heap until `.close()` — so a
|
|
10
|
+
* plain dispose leaks it on every tile removal. Safe for any texture: images
|
|
11
|
+
* that are not ImageBitmaps (render targets, HTMLImageElement) are left alone.
|
|
12
|
+
*/
|
|
13
|
+
export function disposeTexture(texture: Texture): void {
|
|
14
|
+
const image = texture.image as unknown;
|
|
15
|
+
texture.dispose();
|
|
16
|
+
if (typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
|
|
17
|
+
image.close();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Ref: https://github.com/mrdoob/three.js/blob/beab9e845f9e5ae11d648f55b24a0e910b56a85a/src/loaders/TextureLoader.js
|
|
22
|
+
export class AbortableTextureLoader extends Loader {
|
|
23
|
+
loadAsyncWithAbort(
|
|
24
|
+
url: string,
|
|
25
|
+
abort?: AbortController,
|
|
26
|
+
onProgress?: (event: ProgressEvent) => void,
|
|
27
|
+
) {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
29
|
+
const scope = this;
|
|
30
|
+
|
|
31
|
+
return new Promise<Texture>(function (resolve, reject) {
|
|
32
|
+
scope.load(url, resolve, onProgress, reject, abort);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
load(
|
|
37
|
+
url: string,
|
|
38
|
+
onLoad: (data: Texture) => void,
|
|
39
|
+
onProgress?: (event: ProgressEvent) => void,
|
|
40
|
+
onError?: (err: unknown, isAborted?: boolean) => void,
|
|
41
|
+
abort?: AbortController,
|
|
42
|
+
): Texture {
|
|
43
|
+
const texture = new Texture();
|
|
44
|
+
|
|
45
|
+
const loader = new AbortableImageLoader(this.manager);
|
|
46
|
+
loader.setCrossOrigin(this.crossOrigin);
|
|
47
|
+
loader.setPath(this.path);
|
|
48
|
+
|
|
49
|
+
loader.load(
|
|
50
|
+
url,
|
|
51
|
+
function (image) {
|
|
52
|
+
texture.image = image;
|
|
53
|
+
if (
|
|
54
|
+
typeof ImageBitmap !== "undefined" &&
|
|
55
|
+
image instanceof ImageBitmap
|
|
56
|
+
) {
|
|
57
|
+
// The bitmap is already flipped at decode time
|
|
58
|
+
// (imageOrientation: "flipY" — see AbortableImageLoader).
|
|
59
|
+
texture.flipY = false;
|
|
60
|
+
}
|
|
61
|
+
texture.needsUpdate = true;
|
|
62
|
+
|
|
63
|
+
if (onLoad !== undefined) {
|
|
64
|
+
onLoad(texture);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
onProgress,
|
|
68
|
+
onError,
|
|
69
|
+
abort,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return texture;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Material Enhancer Pattern
|
|
2
|
+
|
|
3
|
+
A compositional pattern for Three.js materials that separates shader logic into testable, composable units.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Three.js materials with multiple shader features become monolithic — all shader modifications interleaved in a single class, making code **hard to test in isolation**. You cannot unit-test one shader feature without the entire material setup, and GPU uniform objects (`{ value: T }`) are mutable shared state that easily breaks when accessed from multiple places.
|
|
8
|
+
|
|
9
|
+
## Solution: Testable Separation of Concerns
|
|
10
|
+
|
|
11
|
+
The Material Enhancer pattern splits material logic into three layers, each testable independently:
|
|
12
|
+
|
|
13
|
+
| Layer | What it holds | Testability benefit |
|
|
14
|
+
| ----------- | --------------------------------------------------- | --------------------------------------------- |
|
|
15
|
+
| **State** | Immutable config snapshot (replaced on each update) | Assert state transitions with simple equality |
|
|
16
|
+
| **Refs** | Mutable `{ value: T }` uniform objects for the GPU | Hidden in closure — cannot leak into tests |
|
|
17
|
+
| **Mutates** | Functions that sync refs from state | Test ref-syncing logic without a real shader |
|
|
18
|
+
|
|
19
|
+
The key insight: **state and refs serve different purposes**. State is a clean, serializable snapshot for comparison and assertions. Refs are GPU implementation details that must be mutated in place. By hiding refs behind mutates functions, each layer can be tested without the others.
|
|
20
|
+
|
|
21
|
+
## API
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
type MaterialEnhancer<M, Props, States, Mutates, Shaders> = {
|
|
25
|
+
material: M;
|
|
26
|
+
availableShaders: Shaders;
|
|
27
|
+
mount(props: Props): void; // Initialize state + refs
|
|
28
|
+
update(props: Props): void; // Update state, sync refs (no recompilation)
|
|
29
|
+
transformShader(shader): void; // Inject custom GLSL + assign uniforms
|
|
30
|
+
states(): States; // Read current state (for assertions)
|
|
31
|
+
mutates(): Mutates; // Access controlled mutation functions
|
|
32
|
+
programCacheKey(): string; // Cache key for shader define changes
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Lifecycle
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
1. Create material + enhancer
|
|
40
|
+
2. mount(props) → initializes state and refs
|
|
41
|
+
3. material.onBeforeCompile = enhancer.transformShader
|
|
42
|
+
4. Three.js calls transformShader() → injects GLSL, assigns uniforms
|
|
43
|
+
5. update(props) → updates state, syncs refs (no recompilation)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Constraint**: All methods except `mount()` throw if called before `mount()` (enforced with `invariant`). The factory returns an uninitialized enhancer.
|
|
47
|
+
|
|
48
|
+
**Note**: `states()` returns the current internal state object. After calling `update()`, call `states()` again to get the latest snapshot.
|
|
49
|
+
|
|
50
|
+
## Factory Pattern
|
|
51
|
+
|
|
52
|
+
Enhancers use factory functions. State and refs are closure-private:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
function createFeatureEnhancer(material: MeshLambertMaterial) {
|
|
56
|
+
let state: FeatureState | null = null;
|
|
57
|
+
let mutates: FeatureMutates | null = null;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
material,
|
|
61
|
+
availableShaders: ["lambert"] as const,
|
|
62
|
+
|
|
63
|
+
mount: (props) => {
|
|
64
|
+
state = {
|
|
65
|
+
featureEnabled: props.featureEnabled ?? false,
|
|
66
|
+
intensity: props.intensity ?? 1.0,
|
|
67
|
+
};
|
|
68
|
+
mutates = createMutates(); // refs created inside, captured in closure
|
|
69
|
+
mutates.update(state);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
update: (props) => {
|
|
73
|
+
const prev = state.featureEnabled;
|
|
74
|
+
state = { ...state, ...props };
|
|
75
|
+
mutates.update(state); // sync refs from new state
|
|
76
|
+
if (state.featureEnabled !== prev) material.needsUpdate = true;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
transformShader: (shader) => {
|
|
80
|
+
if (state.featureEnabled) shader.defines.FEATURE_X = 1;
|
|
81
|
+
mutates.updateUniforms(shader.uniforms, state);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
states: () => state,
|
|
85
|
+
mutates: () => mutates,
|
|
86
|
+
programCacheKey: () =>
|
|
87
|
+
JSON.stringify({ featureEnabled: state?.featureEnabled }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Refs Management (inside `createMutates`)
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const createMutates = (): FeatureMutates => {
|
|
96
|
+
const refs = { uIntensity: { value: 1.0 } }; // private to closure
|
|
97
|
+
return {
|
|
98
|
+
update: (state) => {
|
|
99
|
+
refs.uIntensity.value = state.intensity;
|
|
100
|
+
},
|
|
101
|
+
updateUniforms: (uniforms, state) => {
|
|
102
|
+
uniforms.uIntensity = refs.uIntensity;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This is the core testability mechanism. Each module can be tested in isolation:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Test state transitions (state.test.ts)
|
|
112
|
+
const state = updateState({ intensity: 0.5 }, DEFAULT_STATE);
|
|
113
|
+
expect(state.intensity).toBe(0.5);
|
|
114
|
+
|
|
115
|
+
// Test ref syncing (mutates.test.ts)
|
|
116
|
+
const mutates = createMutates();
|
|
117
|
+
const state = { intensity: 0.7 };
|
|
118
|
+
mutates.update(state);
|
|
119
|
+
const uniforms: Record<string, unknown> = {};
|
|
120
|
+
mutates.updateUniforms(uniforms, state);
|
|
121
|
+
expect((uniforms.uIntensity as { value: number }).value).toBe(0.7);
|
|
122
|
+
|
|
123
|
+
// Test shader injection (shader.test.ts)
|
|
124
|
+
const mockShader = {
|
|
125
|
+
defines: {},
|
|
126
|
+
uniforms: {},
|
|
127
|
+
vertexShader: "...",
|
|
128
|
+
fragmentShader: "...",
|
|
129
|
+
};
|
|
130
|
+
transformShader(state, mutates, mockShader);
|
|
131
|
+
expect(mockShader.defines.FEATURE_X).toBe(1);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Each file (`state.ts`, `mutates.ts`, `shader.ts`) has a co-located `.test.ts` file.
|
|
135
|
+
|
|
136
|
+
### Shader Compatibility Testing
|
|
137
|
+
|
|
138
|
+
Three.js has some built-in shader types, each with different structure. An enhancer declares which shaders it supports via `availableShaders`. The shared test utility `testShaderCompatibility` automatically generates a test matrix across all supported shaders and prop configurations:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// shader.test.ts
|
|
142
|
+
testShaderCompatibility(
|
|
143
|
+
"featureEnhancer",
|
|
144
|
+
(material) => createFeatureEnhancer(material as SupportedMaterial),
|
|
145
|
+
[
|
|
146
|
+
{ name: "Feature enabled", props: { featureEnabled: true } },
|
|
147
|
+
{ name: "Feature disabled", props: { featureEnabled: false } },
|
|
148
|
+
],
|
|
149
|
+
FEATURE_SHADER_MARKERS,
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For each (shader type x props) combination, `testShaderCompatibility` verifies:
|
|
154
|
+
|
|
155
|
+
1. `mount(props)` + `transformShader()` does not throw
|
|
156
|
+
2. `update(props)` + `transformShader()` does not throw
|
|
157
|
+
3. All declared markers are present in the transformed shader output
|
|
158
|
+
|
|
159
|
+
This ensures enhancers work correctly across all Three.js shader types without writing repetitive per-shader tests.
|
|
160
|
+
|
|
161
|
+
## Composition
|
|
162
|
+
|
|
163
|
+
Enhancers compose by wrapping a base enhancer. Each layer delegates to its base:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
function createComposedEnhancer(baseEnhancer) {
|
|
167
|
+
let state = null,
|
|
168
|
+
mutates = null;
|
|
169
|
+
return {
|
|
170
|
+
mount: (props) => {
|
|
171
|
+
baseEnhancer.mount(props.base ?? {}); // delegate to base
|
|
172
|
+
state = createInitialState(props.feature);
|
|
173
|
+
mutates = createComposedMutates();
|
|
174
|
+
mutates.update(state);
|
|
175
|
+
},
|
|
176
|
+
transformShader: (shader) => {
|
|
177
|
+
baseEnhancer.transformShader(shader); // base injects first
|
|
178
|
+
if (state.featureEnabled) shader.defines.FEATURE_Y = 1;
|
|
179
|
+
mutates.updateUniforms(shader.uniforms, state);
|
|
180
|
+
},
|
|
181
|
+
update: (props) => {
|
|
182
|
+
if (props.base) baseEnhancer.update(props.base);
|
|
183
|
+
if (props.feature) {
|
|
184
|
+
const prev = state.featureEnabled;
|
|
185
|
+
state = updateState(props.feature, state);
|
|
186
|
+
mutates.update(state);
|
|
187
|
+
if (state.featureEnabled !== prev) material.needsUpdate = true;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
states: () => ({ base: baseEnhancer.states(), feature: state }),
|
|
191
|
+
mutates: () => ({ base: baseEnhancer.mutates() }),
|
|
192
|
+
programCacheKey: () =>
|
|
193
|
+
baseEnhancer.programCacheKey() +
|
|
194
|
+
JSON.stringify({ featureEnabled: state?.featureEnabled }),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Shader Modification: Replacer vs ShaderReplacer
|
|
200
|
+
|
|
201
|
+
Two utilities handle shader string manipulation. They serve different roles in the composition chain:
|
|
202
|
+
|
|
203
|
+
| Aspect | `Replacer` | `ShaderReplacer<Markers>` |
|
|
204
|
+
| --------------------- | ------------------------------------- | --------------------------------------------------- |
|
|
205
|
+
| **Used by** | Base enhancer only | Composing enhancers only |
|
|
206
|
+
| **Search by** | Arbitrary string / regex | Declared markers only (type-safe) |
|
|
207
|
+
| **Operations** | `replace()`, `replaceWithCondition()` | `insertBefore()`, `insertAfter()`, `replaceBlock()` |
|
|
208
|
+
| **Preserves markers** | N/A | Yes — markers remain after every operation |
|
|
209
|
+
|
|
210
|
+
### Base enhancer: `Replacer`
|
|
211
|
+
|
|
212
|
+
The base enhancer is the first in the chain, so there are no markers yet. It uses `Replacer` to replace Three.js `#include` directives with custom GLSL and **places markers** at insertion points for composing enhancers:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Base enhancer's shader.ts — uses Replacer to build initial shader + place markers
|
|
216
|
+
shader.fragmentShader = createReplacer(shader.fragmentShader).replace(
|
|
217
|
+
"uniform vec3 diffuse;",
|
|
218
|
+
`${MARKERS.fragment.UNIFORM_START}
|
|
219
|
+
uniform vec3 diffuse;
|
|
220
|
+
uniform bool uClampToGround;
|
|
221
|
+
${MARKERS.fragment.UNIFORM_END}`,
|
|
222
|
+
).source;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Composing enhancer: `ShaderReplacer`
|
|
226
|
+
|
|
227
|
+
Composing enhancers operate on the base enhancer's output, which already contains markers. `ShaderReplacer<Markers>` restricts operations to declared markers at the type level, preventing accidental modifications to arbitrary shader code:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Composing enhancer's shader.ts — uses ShaderReplacer, restricted to base markers
|
|
231
|
+
shader.fragmentShader = createFeatureShaderReplacer(shader.fragmentShader)
|
|
232
|
+
.insertAfter(MARKERS.fragment.UNIFORM_END, "uniform float uFoo;")
|
|
233
|
+
.replaceBlock(
|
|
234
|
+
{
|
|
235
|
+
start: MARKERS.fragment.UNIFORM_START,
|
|
236
|
+
end: MARKERS.fragment.UNIFORM_END,
|
|
237
|
+
},
|
|
238
|
+
"uniform float uBar;",
|
|
239
|
+
).source;
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Markers
|
|
243
|
+
|
|
244
|
+
Markers are GLSL comments defined in `markers.ts` and organized by shader stage:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const MARKERS = {
|
|
248
|
+
vertex: {},
|
|
249
|
+
fragment: {
|
|
250
|
+
UNIFORM_START: "// NVR_FEATURE_UNIFORM_START",
|
|
251
|
+
UNIFORM_END: "// NVR_FEATURE_UNIFORM_END",
|
|
252
|
+
},
|
|
253
|
+
} as const satisfies ShaderMarkers;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Each base enhancer exposes a typed factory (e.g., `createFeatureShaderReplacer`) that returns a `ShaderReplacer` restricted to its markers. All `ShaderReplacer` operations preserve markers and return `this` for chaining.
|
|
257
|
+
|
|
258
|
+
## External Refs
|
|
259
|
+
|
|
260
|
+
Some uniforms (e.g., shared textures) are created externally and passed via props. The enhancer must hold the same `{ value: T }` object reference to receive future external mutations:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
setTexture: (texture: UniformValue<Texture | null>) => {
|
|
264
|
+
refs.uTexture = texture; // assign the ref object, not just the value
|
|
265
|
+
},
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Shader Defines and Recompilation
|
|
269
|
+
|
|
270
|
+
State properties that affect `#define` flags follow two patterns:
|
|
271
|
+
|
|
272
|
+
- **Immutable after mount**: Cannot change after initialization (e.g., `useRTE`).
|
|
273
|
+
- **Mutable with recompilation**: Can change; triggers `material.needsUpdate = true` and must be included in `programCacheKey()`.
|
|
274
|
+
|
|
275
|
+
## Type Conventions
|
|
276
|
+
|
|
277
|
+
Each enhancer defines four types:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
type FeatureProps = { ... }; // Input (public)
|
|
281
|
+
type FeatureState = Readonly<{ ... }>; // Immutable snapshot (public)
|
|
282
|
+
type FeatureRefs = { ... }; // Uniform references (internal)
|
|
283
|
+
type FeatureUniforms = Partial<FeatureRefs>; // Uniform types (internal)
|
|
284
|
+
type FeatureMutates = Mutates<FeatureState, FeatureUniforms, { ... }>; // Mutation interface (public)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## File Organization
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
/featureEnhancer
|
|
291
|
+
index.ts - Factory function + public type re-exports
|
|
292
|
+
types.ts - Props, State, Refs, Uniforms, Mutates type definitions
|
|
293
|
+
mutates.ts - createMutates, default ref values (use structuredClone for defaults)
|
|
294
|
+
state.ts - State transitions and default values
|
|
295
|
+
shader.ts - transformShader logic
|
|
296
|
+
material.ts - Supported material types and shader compatibility
|
|
297
|
+
markers.ts - Marker constants and typed ShaderReplacer factory
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Each module has a co-located `*.test.ts` file (e.g., `state.test.ts`, `mutates.test.ts`, `shader.test.ts`) if it has a logic.
|