@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.
Files changed (350) hide show
  1. package/LICENSE +8 -0
  2. package/dist/assets/atmosphere/README.md +1 -0
  3. package/dist/assets/atmosphere/higher_order_scattering.bin +0 -0
  4. package/dist/assets/atmosphere/higher_order_scattering.exr +0 -0
  5. package/dist/assets/atmosphere/irradiance.bin +0 -0
  6. package/dist/assets/atmosphere/irradiance.exr +0 -0
  7. package/dist/assets/atmosphere/scattering.bin +0 -0
  8. package/dist/assets/atmosphere/scattering.exr +0 -0
  9. package/dist/assets/atmosphere/single_mie_scattering.bin +0 -0
  10. package/dist/assets/atmosphere/single_mie_scattering.exr +0 -0
  11. package/dist/assets/atmosphere/stars.bin +0 -0
  12. package/dist/assets/atmosphere/transmittance.bin +0 -0
  13. package/dist/assets/atmosphere/transmittance.exr +0 -0
  14. package/dist/assets/cloud/README.md +1 -0
  15. package/dist/assets/cloud/local_weather.png +0 -0
  16. package/dist/assets/cloud/shape.bin +1 -0
  17. package/dist/assets/cloud/shape_detail.bin +1 -0
  18. package/dist/assets/cloud/turbulence.png +0 -0
  19. package/dist/assets/fontWorker-CEJEfRpb.js +2 -0
  20. package/dist/assets/fontWorker-CEJEfRpb.js.map +1 -0
  21. package/dist/assets/index-BUB-ijMO.js +12 -0
  22. package/dist/assets/index-BUB-ijMO.js.map +1 -0
  23. package/dist/assets/navara_wasm_font_worker_bg-665OLxjy.wasm +0 -0
  24. package/dist/assets/navara_wasm_worker_bg-BXhKC5bE.wasm +0 -0
  25. package/dist/assets/noise/README.md +1 -0
  26. package/dist/assets/noise/stbn.bin +0 -0
  27. package/dist/assets/water/README.md +3 -0
  28. package/dist/assets/water/waternormals.jpg +0 -0
  29. package/dist/index.d.ts +10114 -0
  30. package/dist/index.js +34481 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/index.umd.cjs +6345 -0
  33. package/dist/index.umd.cjs.map +1 -0
  34. package/package.json +95 -0
  35. package/src/Color.ts +111 -0
  36. package/src/ShadowMapViewers.ts +49 -0
  37. package/src/atmosphere.test.ts +76 -0
  38. package/src/atmosphere.ts +364 -0
  39. package/src/bufferView.ts +90 -0
  40. package/src/camera.ts +182 -0
  41. package/src/concurrency.ts +15 -0
  42. package/src/constants/assets.ts +30 -0
  43. package/src/constants/decoder.ts +7 -0
  44. package/src/constants/index.ts +2 -0
  45. package/src/core/BaseDesc.ts +142 -0
  46. package/src/core/BaseHandle.ts +126 -0
  47. package/src/core/DescRegistry.ts +88 -0
  48. package/src/core/EffectDesc.ts +343 -0
  49. package/src/core/EffectDescRegistry.ts +31 -0
  50. package/src/core/InstancedMeshDesc.ts +413 -0
  51. package/src/core/LightDesc.ts +216 -0
  52. package/src/core/LightDescRegistry.ts +32 -0
  53. package/src/core/MeshDesc.ts +457 -0
  54. package/src/core/MeshDescRegistry.ts +32 -0
  55. package/src/core/MeshDescWithSelectiveEffect.ts +129 -0
  56. package/src/core/Registries.ts +59 -0
  57. package/src/core/SelectiveEffectRegistry.ts +90 -0
  58. package/src/core/ViewContext.ts +235 -0
  59. package/src/core/errors.ts +16 -0
  60. package/src/core/index.ts +50 -0
  61. package/src/device.ts +414 -0
  62. package/src/effects/CustomEffectPass.ts +28 -0
  63. package/src/effects/copy.ts +10 -0
  64. package/src/effects/effect.ts +87 -0
  65. package/src/effects/index.ts +4 -0
  66. package/src/effects/render.ts +10 -0
  67. package/src/evaluations/FeatureEvaluator.ts +529 -0
  68. package/src/evaluations/index.ts +1 -0
  69. package/src/event/HillshadeContext.test.ts +232 -0
  70. package/src/event/HillshadeContext.ts +299 -0
  71. package/src/event/context.ts +279 -0
  72. package/src/event/feature.ts +357 -0
  73. package/src/event/featureEvent.ts +81 -0
  74. package/src/event/features/billboard.ts +30 -0
  75. package/src/event/features/model.ts +192 -0
  76. package/src/event/features/modelGpuBytes.test.ts +69 -0
  77. package/src/event/features/modelGpuBytes.ts +87 -0
  78. package/src/event/features/point.ts +25 -0
  79. package/src/event/features/polygon.ts +23 -0
  80. package/src/event/features/polygonOutline.ts +19 -0
  81. package/src/event/features/polyline.ts +19 -0
  82. package/src/event/features/text.ts +71 -0
  83. package/src/event/hillshade.test.ts +842 -0
  84. package/src/event/hillshade.ts +588 -0
  85. package/src/event/index.ts +700 -0
  86. package/src/event/loaders.ts +53 -0
  87. package/src/event/tile.ts +19 -0
  88. package/src/event/worker.ts +999 -0
  89. package/src/event/workerTaskSettlement.test.ts +243 -0
  90. package/src/event/workerTaskSettlement.ts +128 -0
  91. package/src/inMemoryBufferStore.test.ts +60 -0
  92. package/src/inMemoryBufferStore.ts +67 -0
  93. package/src/index.iife.ts +11 -0
  94. package/src/index.ts +2556 -0
  95. package/src/input.ts +144 -0
  96. package/src/layer.ts +183 -0
  97. package/src/layers/effect/FinalCopyEffectDesc.ts +29 -0
  98. package/src/layers/effect/MRTPassEffectDesc.ts +105 -0
  99. package/src/layers/effect/README.md +9 -0
  100. package/src/layers/effect/SelectiveEffectDesc.ts +113 -0
  101. package/src/layers/effect/SkyEnvMapEffectDesc.ts +58 -0
  102. package/src/layers/effect/TransparentPassEffectDesc.ts +62 -0
  103. package/src/layers/effect/index.ts +5 -0
  104. package/src/layers/index.ts +1 -0
  105. package/src/layersManager.ts +63 -0
  106. package/src/light.ts +15 -0
  107. package/src/loaders/AbortableImageLoader.ts +203 -0
  108. package/src/loaders/AbortableTextureLoader.ts +74 -0
  109. package/src/loaders/index.ts +5 -0
  110. package/src/material/enhancer/DESIGN.md +300 -0
  111. package/src/material/enhancer/MaterialEnhancer.ts +112 -0
  112. package/src/material/enhancer/ShaderReplacer.test.ts +114 -0
  113. package/src/material/enhancer/ShaderReplacer.ts +97 -0
  114. package/src/material/enhancer/batchTexture.ts +8 -0
  115. package/src/material/enhancer/index.ts +46 -0
  116. package/src/material/enhancer/instancedSprite/index.ts +7 -0
  117. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.test.ts +214 -0
  118. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.ts +134 -0
  119. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/markers.ts +21 -0
  120. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/material.ts +32 -0
  121. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.test.ts +277 -0
  122. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.ts +151 -0
  123. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.test.ts +114 -0
  124. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.ts +49 -0
  125. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.test.ts +82 -0
  126. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.ts +80 -0
  127. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/types.ts +146 -0
  128. package/src/material/enhancer/instancedSprite/instancedSpriteMaterialEnhancer.ts +105 -0
  129. package/src/material/enhancer/model/index.ts +25 -0
  130. package/src/material/enhancer/model/modelBaseEnhancer/index.test.ts +74 -0
  131. package/src/material/enhancer/model/modelBaseEnhancer/index.ts +110 -0
  132. package/src/material/enhancer/model/modelBaseEnhancer/markers.ts +27 -0
  133. package/src/material/enhancer/model/modelBaseEnhancer/material.test.ts +66 -0
  134. package/src/material/enhancer/model/modelBaseEnhancer/material.ts +44 -0
  135. package/src/material/enhancer/model/modelBaseEnhancer/mutates.test.ts +84 -0
  136. package/src/material/enhancer/model/modelBaseEnhancer/mutates.ts +47 -0
  137. package/src/material/enhancer/model/modelBaseEnhancer/shader.test.ts +24 -0
  138. package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +162 -0
  139. package/src/material/enhancer/model/modelBaseEnhancer/state.test.ts +103 -0
  140. package/src/material/enhancer/model/modelBaseEnhancer/state.ts +52 -0
  141. package/src/material/enhancer/model/modelBaseEnhancer/types.ts +89 -0
  142. package/src/material/enhancer/model/modelMaterialEnhancer.ts +91 -0
  143. package/src/material/enhancer/model/modelWaterEnhancer/index.test.ts +71 -0
  144. package/src/material/enhancer/model/modelWaterEnhancer/index.ts +175 -0
  145. package/src/material/enhancer/model/modelWaterEnhancer/material.test.ts +48 -0
  146. package/src/material/enhancer/model/modelWaterEnhancer/material.ts +25 -0
  147. package/src/material/enhancer/model/modelWaterEnhancer/mutates.test.ts +89 -0
  148. package/src/material/enhancer/model/modelWaterEnhancer/mutates.ts +89 -0
  149. package/src/material/enhancer/model/modelWaterEnhancer/shader.test.ts +34 -0
  150. package/src/material/enhancer/model/modelWaterEnhancer/shader.ts +111 -0
  151. package/src/material/enhancer/model/modelWaterEnhancer/state.test.ts +105 -0
  152. package/src/material/enhancer/model/modelWaterEnhancer/state.ts +59 -0
  153. package/src/material/enhancer/model/modelWaterEnhancer/types.ts +141 -0
  154. package/src/material/enhancer/model/pntsEnhancer/index.test.ts +96 -0
  155. package/src/material/enhancer/model/pntsEnhancer/index.ts +82 -0
  156. package/src/material/enhancer/model/pntsEnhancer/markers.ts +6 -0
  157. package/src/material/enhancer/model/pntsEnhancer/material.test.ts +37 -0
  158. package/src/material/enhancer/model/pntsEnhancer/material.ts +21 -0
  159. package/src/material/enhancer/model/pntsEnhancer/mutates.test.ts +80 -0
  160. package/src/material/enhancer/model/pntsEnhancer/mutates.ts +29 -0
  161. package/src/material/enhancer/model/pntsEnhancer/shader.test.ts +32 -0
  162. package/src/material/enhancer/model/pntsEnhancer/shader.ts +56 -0
  163. package/src/material/enhancer/model/pntsEnhancer/state.test.ts +87 -0
  164. package/src/material/enhancer/model/pntsEnhancer/state.ts +29 -0
  165. package/src/material/enhancer/model/pntsEnhancer/types.ts +38 -0
  166. package/src/material/enhancer/polygon/index.ts +22 -0
  167. package/src/material/enhancer/polygon/polygonBaseEnhancer/index.test.ts +101 -0
  168. package/src/material/enhancer/polygon/polygonBaseEnhancer/index.ts +130 -0
  169. package/src/material/enhancer/polygon/polygonBaseEnhancer/markers.ts +23 -0
  170. package/src/material/enhancer/polygon/polygonBaseEnhancer/material.test.ts +19 -0
  171. package/src/material/enhancer/polygon/polygonBaseEnhancer/material.ts +38 -0
  172. package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.test.ts +75 -0
  173. package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +122 -0
  174. package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.test.ts +25 -0
  175. package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +249 -0
  176. package/src/material/enhancer/polygon/polygonBaseEnhancer/state.test.ts +102 -0
  177. package/src/material/enhancer/polygon/polygonBaseEnhancer/state.ts +92 -0
  178. package/src/material/enhancer/polygon/polygonBaseEnhancer/types.ts +150 -0
  179. package/src/material/enhancer/polygon/polygonMaterialEnhancer.ts +97 -0
  180. package/src/material/enhancer/polygon/polygonWaterEnhancer/index.test.ts +72 -0
  181. package/src/material/enhancer/polygon/polygonWaterEnhancer/index.ts +164 -0
  182. package/src/material/enhancer/polygon/polygonWaterEnhancer/material.test.ts +48 -0
  183. package/src/material/enhancer/polygon/polygonWaterEnhancer/material.ts +35 -0
  184. package/src/material/enhancer/polygon/polygonWaterEnhancer/mutates.ts +61 -0
  185. package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.test.ts +34 -0
  186. package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.ts +96 -0
  187. package/src/material/enhancer/polygon/polygonWaterEnhancer/state.test.ts +98 -0
  188. package/src/material/enhancer/polygon/polygonWaterEnhancer/state.ts +57 -0
  189. package/src/material/enhancer/polygon/polygonWaterEnhancer/types.ts +115 -0
  190. package/src/material/enhancer/polyline/index.ts +7 -0
  191. package/src/material/enhancer/polyline/polylineBaseEnhancer/index.test.ts +157 -0
  192. package/src/material/enhancer/polyline/polylineBaseEnhancer/index.ts +154 -0
  193. package/src/material/enhancer/polyline/polylineBaseEnhancer/markers.ts +19 -0
  194. package/src/material/enhancer/polyline/polylineBaseEnhancer/material.test.ts +42 -0
  195. package/src/material/enhancer/polyline/polylineBaseEnhancer/material.ts +38 -0
  196. package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.test.ts +120 -0
  197. package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.ts +169 -0
  198. package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.test.ts +67 -0
  199. package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.ts +75 -0
  200. package/src/material/enhancer/polyline/polylineBaseEnhancer/state.test.ts +55 -0
  201. package/src/material/enhancer/polyline/polylineBaseEnhancer/state.ts +91 -0
  202. package/src/material/enhancer/polyline/polylineBaseEnhancer/types.ts +170 -0
  203. package/src/material/enhancer/polyline/polylineMaterialEnhancer.ts +102 -0
  204. package/src/material/enhancer/sdfText/index.ts +7 -0
  205. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +125 -0
  206. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/markers.ts +19 -0
  207. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.test.ts +35 -0
  208. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.ts +32 -0
  209. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +381 -0
  210. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +232 -0
  211. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +36 -0
  212. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +118 -0
  213. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +107 -0
  214. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +184 -0
  215. package/src/material/enhancer/sdfText/sdfTextMaterialEnhancer.ts +100 -0
  216. package/src/material/enhancer/shadowMap/index.ts +7 -0
  217. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/index.ts +97 -0
  218. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/markers.ts +10 -0
  219. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/material.ts +31 -0
  220. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/mutates.ts +8 -0
  221. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.test.ts +17 -0
  222. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.ts +53 -0
  223. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/state.ts +17 -0
  224. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/types.ts +26 -0
  225. package/src/material/enhancer/test-utils/shaderCompatibility.ts +124 -0
  226. package/src/material/enhancer/tileComposite/compose.test.ts +95 -0
  227. package/src/material/enhancer/tileComposite/compose.ts +112 -0
  228. package/src/material/enhancer/tileComposite/index.ts +23 -0
  229. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/index.ts +11 -0
  230. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/mutates.ts +132 -0
  231. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/shader.ts +245 -0
  232. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/types.ts +43 -0
  233. package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/index.ts +29 -0
  234. package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/mutates.ts +68 -0
  235. package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/shader.ts +24 -0
  236. package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/index.ts +29 -0
  237. package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/mutates.ts +34 -0
  238. package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/shader.ts +36 -0
  239. package/src/material/enhancer/tileComposite/tileWaterEnhancer/index.ts +23 -0
  240. package/src/material/enhancer/tileComposite/tileWaterEnhancer/mutates.ts +23 -0
  241. package/src/material/enhancer/tileComposite/tileWaterEnhancer/shader.ts +8 -0
  242. package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/index.ts +23 -0
  243. package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/mutates.ts +34 -0
  244. package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/shader.ts +13 -0
  245. package/src/material/enhancer/tileComposite/types.ts +58 -0
  246. package/src/material/index.ts +3 -0
  247. package/src/material/macro/index.ts +1 -0
  248. package/src/material/macro/tileShader.test.ts +136 -0
  249. package/src/material/macro/tileShader.ts +287 -0
  250. package/src/material/overrideMaterialsForMRT.ts +439 -0
  251. package/src/material/selectiveEffectMaterialSetup.test.ts +52 -0
  252. package/src/material/selectiveEffectMaterialSetup.ts +53 -0
  253. package/src/material/types/index.ts +1 -0
  254. package/src/material/types/uniform.ts +1 -0
  255. package/src/mesh/DrapedMesh.ts +101 -0
  256. package/src/mesh/batchTexture.test.ts +646 -0
  257. package/src/mesh/batchTexture.ts +437 -0
  258. package/src/mesh/batchedFeature.ts +186 -0
  259. package/src/mesh/batchedSdfText.ts +484 -0
  260. package/src/mesh/featureMesh.ts +43 -0
  261. package/src/mesh/index.ts +16 -0
  262. package/src/mesh/instanced.ts +117 -0
  263. package/src/mesh/model.ts +483 -0
  264. package/src/mesh/pickableMesh.ts +55 -0
  265. package/src/mesh/pickableMeshWrapper.ts +529 -0
  266. package/src/mesh/polygon.ts +699 -0
  267. package/src/mesh/polygonOutline.ts +393 -0
  268. package/src/mesh/polyline.ts +523 -0
  269. package/src/mesh/releaseGeometryArrays.ts +57 -0
  270. package/src/mesh/rtcRteHelper.ts +211 -0
  271. package/src/mesh/sdfText.test.ts +200 -0
  272. package/src/mesh/sdfText.ts +1019 -0
  273. package/src/mesh/sprite/billboardAtlas.test.ts +177 -0
  274. package/src/mesh/sprite/billboardAtlas.ts +210 -0
  275. package/src/mesh/sprite/billboardAtlasImageLoader.ts +29 -0
  276. package/src/mesh/sprite/index.ts +1 -0
  277. package/src/mesh/sprite/instancedSprite.ts +694 -0
  278. package/src/mesh/tile.test.ts +393 -0
  279. package/src/mesh/tile.ts +1790 -0
  280. package/src/object3DEvent.ts +6 -0
  281. package/src/orchestrators/RenderPassOrchestrator.ts +147 -0
  282. package/src/orchestrators/index.ts +1 -0
  283. package/src/passes/AllDepthCopyPass.ts +165 -0
  284. package/src/passes/CustomRenderPass.ts +279 -0
  285. package/src/passes/NormalCopyPass.ts +125 -0
  286. package/src/passes/RenderTargetCopyPass.ts +91 -0
  287. package/src/passes/SkyEnvMapPass.ts +58 -0
  288. package/src/passes/index.ts +5 -0
  289. package/src/pick/pickHelper.ts +273 -0
  290. package/src/pick/pickTerrain.ts +254 -0
  291. package/src/plugins/AttributionPlugin/AttributionPlugin.test.ts +210 -0
  292. package/src/plugins/AttributionPlugin/attribution.test.ts +225 -0
  293. package/src/plugins/AttributionPlugin/attribution.ts +355 -0
  294. package/src/plugins/AttributionPlugin/attributionStyles.ts +163 -0
  295. package/src/plugins/AttributionPlugin/index.ts +702 -0
  296. package/src/plugins/index.ts +1 -0
  297. package/src/quality.ts +1 -0
  298. package/src/renderOrder.ts +3 -0
  299. package/src/scene.test.ts +164 -0
  300. package/src/scene.ts +127 -0
  301. package/src/shaders/index.ts +1 -0
  302. package/src/solar.test.ts +81 -0
  303. package/src/solar.ts +101 -0
  304. package/src/source.ts +80 -0
  305. package/src/stats.ts +102 -0
  306. package/src/tasks/computeVertexNormalsAsync.ts +15 -0
  307. package/src/tasks/constructPolygonBatchedFeature.ts +34 -0
  308. package/src/tasks/constructPolylineBatchedFeature.ts +27 -0
  309. package/src/tasks/constructQuantizedMeshTerrainMesh.ts +24 -0
  310. package/src/tasks/constructTerrainMesh.ts +25 -0
  311. package/src/tasks/getImageDataFromBlob.ts +13 -0
  312. package/src/tasks/getImageDataFromImageBitmap.ts +12 -0
  313. package/src/tasks/parseMvtTile.ts +22 -0
  314. package/src/tasks/queueTask.ts +19 -0
  315. package/src/tasks/toCreasedNormalsAsync.ts +18 -0
  316. package/src/tasks/upsampleQuantizedMeshTerrainMesh.ts +44 -0
  317. package/src/tasks/upsampleTerrainMesh.ts +39 -0
  318. package/src/texture.ts +19 -0
  319. package/src/textures.ts +13 -0
  320. package/src/tileTexture/CompositeShader.snapshot.test.ts +59 -0
  321. package/src/tileTexture/CompositeShader.test.ts +168 -0
  322. package/src/tileTexture/CompositeShader.ts +53 -0
  323. package/src/tileTexture/SlotPlanner.test.ts +105 -0
  324. package/src/tileTexture/SlotPlanner.ts +102 -0
  325. package/src/tileTexture/TileTextureCache.test.ts +227 -0
  326. package/src/tileTexture/TileTextureCache.ts +192 -0
  327. package/src/tileTexture/TileTextureCompositor.test.ts +260 -0
  328. package/src/tileTexture/TileTextureCompositor.ts +421 -0
  329. package/src/tileTexture/__snapshots__/CompositeShader.snapshot.test.ts.snap +2649 -0
  330. package/src/tileTexture/index.ts +8 -0
  331. package/src/tileTexture/types.ts +150 -0
  332. package/src/type/index.ts +261 -0
  333. package/src/uniforms.ts +20 -0
  334. package/src/utils/arraysEqual.ts +9 -0
  335. package/src/utils/blendModes.ts +80 -0
  336. package/src/utils/geometry.ts +53 -0
  337. package/src/utils/hillshadeNormalMapGenerator.ts +193 -0
  338. package/src/utils/index.ts +8 -0
  339. package/src/utils/object.test.ts +14 -0
  340. package/src/utils/object.ts +7 -0
  341. package/src/utils/replacer.test.ts +42 -0
  342. package/src/utils/replacer.ts +89 -0
  343. package/src/utils/screen.ts +29 -0
  344. package/src/utils/textureFragmentIndex.test.ts +224 -0
  345. package/src/utils/textureFragmentIndex.ts +97 -0
  346. package/src/utils/worker.ts +3 -0
  347. package/src/vite-env.d.ts +2 -0
  348. package/src/worker/index.ts +13 -0
  349. package/src/worker/tasks/computeVertexNormals.ts +15 -0
  350. package/src/worker/tasks/toCreasedNormals.ts +22 -0
@@ -0,0 +1,260 @@
1
+ import { Mesh, BoxGeometry, MeshBasicMaterial } from "three";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ import { TexturizedSceneByTileCoordinates } from "../scene";
5
+
6
+ import { TileTextureCompositor } from "./TileTextureCompositor";
7
+ import type { AtlasFactory, CompositeAtlas } from "./types";
8
+
9
+ // Minimal renderer mock — only the methods the compositor touches need to
10
+ // exist. We track `render` so tests can assert which scenes were drawn.
11
+ function makeRenderer() {
12
+ return {
13
+ autoClear: true,
14
+ getRenderTarget: vi.fn().mockReturnValue(null),
15
+ setRenderTarget: vi.fn(),
16
+ getClearColor: vi.fn().mockImplementation((c) => c),
17
+ getClearAlpha: vi.fn().mockReturnValue(1),
18
+ setClearColor: vi.fn(),
19
+ clear: vi.fn(),
20
+ render: vi.fn(),
21
+ };
22
+ }
23
+
24
+ function makeFakeAtlas(): CompositeAtlas {
25
+ const tex = (n: string) =>
26
+ ({ name: n, needsUpdate: false }) as unknown as CompositeAtlas["color"];
27
+ return {
28
+ target: { dispose: vi.fn() } as unknown as CompositeAtlas["target"],
29
+ color: tex("color"),
30
+ attr: tex("attr"),
31
+ normal: tex("normal"),
32
+ dispose: vi.fn(),
33
+ };
34
+ }
35
+
36
+ const fakeFactory: AtlasFactory = () => makeFakeAtlas();
37
+
38
+ function setup() {
39
+ const renderer = makeRenderer();
40
+ const texturizedScenes = new TexturizedSceneByTileCoordinates(
41
+ renderer as unknown as ConstructorParameters<
42
+ typeof TexturizedSceneByTileCoordinates
43
+ >[0],
44
+ );
45
+ const compositor = new TileTextureCompositor({
46
+ renderer: renderer as unknown as ConstructorParameters<
47
+ typeof TileTextureCompositor
48
+ >[0]["renderer"],
49
+ texturizedSceneByTileCoordinates: texturizedScenes,
50
+ atlasFactory: fakeFactory,
51
+ });
52
+ return { compositor, renderer, texturizedScenes };
53
+ }
54
+
55
+ function mesh() {
56
+ return new Mesh(new BoxGeometry(1, 1, 1), new MeshBasicMaterial());
57
+ }
58
+
59
+ describe("TileTextureCompositor.acquire/release", () => {
60
+ it("acquire returns CompositeOutputs and dedupes by handle", () => {
61
+ const { compositor } = setup();
62
+ const out = compositor.acquire(1n);
63
+ expect(out.color).toBeDefined();
64
+ expect(out.attr).toBeDefined();
65
+ expect(out.normal).toBeDefined();
66
+ // Re-acquire returns same outputs (cache dedupes).
67
+ const again = compositor.acquire(1n);
68
+ expect(again.color).toBe(out.color);
69
+ });
70
+
71
+ it("release pools the atlas once refCount drops to zero", () => {
72
+ const { compositor } = setup();
73
+ compositor.acquire(1n);
74
+ compositor.acquire(1n);
75
+ const entry = compositor.cache.getEntry(1n);
76
+ if (!entry) throw new Error("expected entry");
77
+ const atlas = entry.atlas;
78
+
79
+ compositor.release(1n);
80
+ expect(atlas.dispose).not.toHaveBeenCalled();
81
+ expect(compositor.cache.pooledCount).toBe(0);
82
+ compositor.release(1n);
83
+ // The entry is gone but its atlas goes to the reuse pool, not dispose.
84
+ expect(compositor.cache.getEntry(1n)).toBeUndefined();
85
+ expect(atlas.dispose).not.toHaveBeenCalled();
86
+ expect(compositor.cache.pooledCount).toBe(1);
87
+ });
88
+ });
89
+
90
+ describe("TileTextureCompositor.renderVectorScenes", () => {
91
+ const fakeRT = (): unknown => ({ texture: { needsUpdate: false } });
92
+
93
+ it("bakes each layer's resolved source into its render target", () => {
94
+ const { compositor, renderer, texturizedScenes } = setup();
95
+ texturizedScenes.add(1n, "layer-a", mesh(), 0);
96
+ texturizedScenes.add(2n, "layer-b", mesh(), 1);
97
+
98
+ const rt0 = fakeRT();
99
+ const rt1 = fakeRT();
100
+ compositor.renderVectorScenes(
101
+ [
102
+ {
103
+ layerId: "layer-a",
104
+ sources: [{ tileHandle: 1n, uvOffset: [0, 0], uvScale: [1, 1] }],
105
+ },
106
+ {
107
+ layerId: "layer-b",
108
+ sources: [{ tileHandle: 2n, uvOffset: [0, 0], uvScale: [1, 1] }],
109
+ },
110
+ ],
111
+ [rt0 as never, rt1 as never],
112
+ );
113
+
114
+ // One render per populated slot; every target cleared and flagged.
115
+ expect(renderer.render).toHaveBeenCalledTimes(2);
116
+ expect(renderer.clear).toHaveBeenCalledTimes(2);
117
+ expect(
118
+ (rt0 as { texture: { needsUpdate: boolean } }).texture.needsUpdate,
119
+ ).toBe(true);
120
+ expect(
121
+ (rt1 as { texture: { needsUpdate: boolean } }).texture.needsUpdate,
122
+ ).toBe(true);
123
+ });
124
+
125
+ it("accumulates a layer's N:M sources into one render target, clearing once", () => {
126
+ const { compositor, renderer, texturizedScenes } = setup();
127
+ // Two WM source tiles overlapping one Geographic terrain tile, both backing
128
+ // the same layer (west half and east half).
129
+ texturizedScenes.add(1n, "layer-a", mesh(), 0);
130
+ texturizedScenes.add(2n, "layer-a", mesh(), 0);
131
+
132
+ compositor.renderVectorScenes(
133
+ [
134
+ {
135
+ layerId: "layer-a",
136
+ sources: [
137
+ { tileHandle: 1n, uvOffset: [0, 0], uvScale: [0.5, 1] },
138
+ { tileHandle: 2n, uvOffset: [0.5, 0], uvScale: [0.5, 1] },
139
+ ],
140
+ },
141
+ ],
142
+ [fakeRT() as never],
143
+ );
144
+
145
+ // Both sources drawn into the single RT, which was cleared exactly once so
146
+ // the second source mosaics with the first instead of wiping it. autoClear
147
+ // is disabled across the draws and restored afterwards.
148
+ expect(renderer.render).toHaveBeenCalledTimes(2);
149
+ expect(renderer.clear).toHaveBeenCalledTimes(1);
150
+ expect(renderer.autoClear).toBe(true);
151
+ });
152
+
153
+ it("frames the terrain sub-rect when the source resolves to a coarser ancestor", () => {
154
+ const { compositor, renderer, texturizedScenes } = setup();
155
+ texturizedScenes.add(1n, "layer-a", mesh(), 0);
156
+
157
+ // NW quadrant of the ancestor: uvOffset=(0,0.5), uvScale=(0.5,0.5).
158
+ compositor.renderVectorScenes(
159
+ [
160
+ {
161
+ layerId: "layer-a",
162
+ sources: [
163
+ { tileHandle: 1n, uvOffset: [0, 0.5], uvScale: [0.5, 0.5] },
164
+ ],
165
+ },
166
+ ],
167
+ [fakeRT() as never],
168
+ );
169
+
170
+ expect(renderer.render).toHaveBeenCalledTimes(1);
171
+ const cam = renderer.render.mock.calls[0][1] as {
172
+ left: number;
173
+ right: number;
174
+ bottom: number;
175
+ top: number;
176
+ };
177
+ // Matches ortho_camera_transform for the NW sub-tile: [-1,0]×[0,1].
178
+ expect(cam.left).toBe(-1);
179
+ expect(cam.right).toBe(0);
180
+ expect(cam.bottom).toBe(0);
181
+ expect(cam.top).toBe(1);
182
+ });
183
+
184
+ it("clears but does not render a source whose scene was removed", () => {
185
+ const { compositor, renderer, texturizedScenes } = setup();
186
+ const m = mesh();
187
+ texturizedScenes.add(1n, "layer-a", m, 0);
188
+ texturizedScenes.removeMesh(1n, "layer-a", m);
189
+
190
+ const rt = fakeRT();
191
+ compositor.renderVectorScenes(
192
+ [
193
+ {
194
+ layerId: "layer-a",
195
+ sources: [{ tileHandle: 1n, uvOffset: [0, 0], uvScale: [1, 1] }],
196
+ },
197
+ ],
198
+ [rt as never],
199
+ );
200
+
201
+ expect(renderer.render).not.toHaveBeenCalled();
202
+ expect(renderer.clear).toHaveBeenCalledTimes(1);
203
+ });
204
+
205
+ it("skips a never-touched render target that has no slot (no GPU allocation)", () => {
206
+ const { compositor, renderer } = setup();
207
+
208
+ const rt = fakeRT();
209
+ compositor.renderVectorScenes([], [rt as never]);
210
+
211
+ // Render-targeting the RT would create its GL framebuffer; an empty slot
212
+ // whose target was never baked must not pay that cost.
213
+ expect(renderer.setRenderTarget).not.toHaveBeenCalledWith(rt);
214
+ expect(renderer.render).not.toHaveBeenCalled();
215
+ expect(renderer.clear).not.toHaveBeenCalled();
216
+ });
217
+
218
+ it("clears a previously-baked render target whose slot went away", () => {
219
+ const { compositor, renderer, texturizedScenes } = setup();
220
+ texturizedScenes.add(1n, "layer-a", mesh(), 0);
221
+
222
+ const rt = fakeRT();
223
+ compositor.renderVectorScenes(
224
+ [
225
+ {
226
+ layerId: "layer-a",
227
+ sources: [{ tileHandle: 1n, uvOffset: [0, 0], uvScale: [1, 1] }],
228
+ },
229
+ ],
230
+ [rt as never],
231
+ );
232
+ expect(renderer.clear).toHaveBeenCalledTimes(1);
233
+
234
+ // The slot disappeared: the stale bake must be wiped so the atlas doesn't
235
+ // composite outdated content.
236
+ compositor.renderVectorScenes([], [rt as never]);
237
+ expect(renderer.render).toHaveBeenCalledTimes(1);
238
+ expect(renderer.clear).toHaveBeenCalledTimes(2);
239
+ });
240
+
241
+ it("clears (does not render) a source whose scene hasn't reached the cache yet", () => {
242
+ const { compositor, renderer } = setup();
243
+
244
+ // The sub-rect stays transparent during the transition window; a coarser
245
+ // ancestor backs the gap via the Rust scene-ready walk-up.
246
+ const rt = fakeRT();
247
+ compositor.renderVectorScenes(
248
+ [
249
+ {
250
+ layerId: "pending",
251
+ sources: [{ tileHandle: 99n, uvOffset: [0, 0], uvScale: [1, 1] }],
252
+ },
253
+ ],
254
+ [rt as never],
255
+ );
256
+
257
+ expect(renderer.render).not.toHaveBeenCalled();
258
+ expect(renderer.clear).toHaveBeenCalledTimes(1);
259
+ });
260
+ });
@@ -0,0 +1,421 @@
1
+ import { type TileHandle } from "@navaramap/core";
2
+ import {
3
+ Color,
4
+ DataTexture,
5
+ GLSL3,
6
+ Mesh,
7
+ OrthographicCamera,
8
+ PlaneGeometry,
9
+ RGBAFormat,
10
+ Scene,
11
+ ShaderMaterial,
12
+ type Texture,
13
+ UnsignedByteType,
14
+ WebGLRenderTarget,
15
+ type WebGLRenderer,
16
+ } from "three";
17
+
18
+ import {
19
+ buildCompositeFragmentShader,
20
+ composeCompositeContributions,
21
+ compositeFeatureKey,
22
+ COMPOSITE_VERTEX_SHADER,
23
+ createCoreUniformMutates,
24
+ createCompositeLayerEnhancers,
25
+ type CompositeLayerEnhancer,
26
+ type CompositeUniformTarget,
27
+ type CoreUniformMutates,
28
+ } from "../material/enhancer/tileComposite";
29
+ import { type TexturizedSceneByTileCoordinates } from "../scene";
30
+
31
+ import type { SlotPlan } from "./SlotPlanner";
32
+ import { TileTextureCache } from "./TileTextureCache";
33
+ import type {
34
+ AtlasFactory,
35
+ CompositeAtlas,
36
+ CompositeFeatures,
37
+ CompositeGlobals,
38
+ CompositeOutputs,
39
+ DirtyReason,
40
+ } from "./types";
41
+
42
+ const PREV_CLEAR_COLOR = new Color();
43
+
44
+ /**
45
+ * Default atlas factory: a single MRT WebGLRenderTarget (count=3) sized
46
+ * `size × size` with RGBA8 textures. All three attachments share format —
47
+ * three.js's MRT API doesn't allow per-attachment formats — so we trade some
48
+ * attr-channel precision for one draw call to populate color/attr/normal.
49
+ */
50
+ export const defaultAtlasFactory =
51
+ (renderer: WebGLRenderer | null): AtlasFactory =>
52
+ (size: number): CompositeAtlas => {
53
+ void renderer;
54
+ const target = new WebGLRenderTarget(size, size, {
55
+ count: 3,
56
+ format: RGBAFormat,
57
+ type: UnsignedByteType,
58
+ depthBuffer: false,
59
+ stencilBuffer: false,
60
+ });
61
+ const [color, attr, normal] = target.textures;
62
+ return {
63
+ target,
64
+ color,
65
+ attr,
66
+ normal,
67
+ dispose: () => target.dispose(),
68
+ };
69
+ };
70
+
71
+ export type TileTextureCompositorOptions = {
72
+ renderer: WebGLRenderer;
73
+ texturizedSceneByTileCoordinates: TexturizedSceneByTileCoordinates;
74
+ /** Atlas RT side length (defaults to 512). */
75
+ size?: number;
76
+ /** Test seam: replace MRT RT creation. */
77
+ atlasFactory?: AtlasFactory;
78
+ };
79
+
80
+ type CachedMaterial = {
81
+ material: ShaderMaterial;
82
+ /** Number of compact slots (rasterCount + vectorCount). */
83
+ numTextures: number;
84
+ /** Owns the core per-slot uniforms (shows/colors/opacities/textures/uv). */
85
+ core: CoreUniformMutates;
86
+ /** Active composite layer enhancers — each owns its own uniforms. */
87
+ chain: CompositeLayerEnhancer[];
88
+ /** 1×1 fallback so unbound texture array slots stay valid samplers. */
89
+ placeholderTexture: Texture;
90
+ };
91
+
92
+ /**
93
+ * Integration layer for per-tile texture composition.
94
+ *
95
+ * Responsibilities:
96
+ * - Owns the per-tile composite atlas via TileTextureCache.
97
+ * - Runs the per-layer vector-scene offscreen render (ported wholesale from
98
+ * the previous TileMesh._onBeforeRender so behaviour is unchanged).
99
+ * - Runs the MRT composite pass that bakes N source textures (raster + vector
100
+ * + hillshade) into the atlas (color + attr + normal). The TileMesh main
101
+ * shader then samples each atlas attachment once instead of looping over N
102
+ * slots per fragment.
103
+ * - Tracks dirty handles so the composite pass only runs when something
104
+ * actually changes (tile add, layer update, hillshade backfill, vector
105
+ * revision bump, …).
106
+ */
107
+ export class TileTextureCompositor {
108
+ readonly renderer: WebGLRenderer;
109
+ readonly cache: TileTextureCache;
110
+ /** Atlas / drape RT side length in texels (device-dependent). TileMesh reads
111
+ * this to size its lazily-allocated drape render targets and to compute the
112
+ * per-tile GPU byte cost it reports to the memory ledger. */
113
+ readonly size: number;
114
+ private readonly texturizedScenes: TexturizedSceneByTileCoordinates;
115
+ // Single fixed [-1, 1] camera shared by every vector-scene bake. Ancestor
116
+ // fallback is resolved in Rust, so no per-tile camera transform is needed.
117
+ private readonly vectorCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
118
+
119
+ // Composite-pass machinery (lazily created on first use so tests that
120
+ // don't render don't pay the allocation).
121
+ private readonly quadScene = new Scene();
122
+ private readonly quadCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
123
+ private readonly quadMesh: Mesh;
124
+ private readonly materialCache = new Map<string, CachedMaterial>();
125
+ // Vector render targets this compositor has ever rendered to. three.js
126
+ // allocates a render target's GL framebuffer on first setRenderTarget, so
127
+ // an empty slot whose target was never touched must be skipped entirely —
128
+ // clearing it would allocate size² × 4 bytes of GPU memory for nothing.
129
+ private readonly touchedVectorTargets = new WeakSet<WebGLRenderTarget>();
130
+
131
+ constructor(opts: TileTextureCompositorOptions) {
132
+ this.renderer = opts.renderer;
133
+ this.texturizedScenes = opts.texturizedSceneByTileCoordinates;
134
+ this.size = opts.size ?? 512;
135
+ this.cache = new TileTextureCache({
136
+ size: this.size,
137
+ atlasFactory: opts.atlasFactory ?? defaultAtlasFactory(opts.renderer),
138
+ });
139
+
140
+ // Single shared fullscreen quad — material is swapped per render.
141
+ this.quadMesh = new Mesh(new PlaneGeometry(2, 2));
142
+ this.quadScene.add(this.quadMesh);
143
+ this.quadCamera.position.z = 1;
144
+ this.vectorCamera.position.z = 1;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------
148
+ // Cache-facing API
149
+ // ---------------------------------------------------------------------
150
+
151
+ acquire(handle: TileHandle): CompositeOutputs {
152
+ return this.cache.acquire(handle);
153
+ }
154
+
155
+ release(handle: TileHandle): void {
156
+ this.cache.release(handle);
157
+ }
158
+
159
+ /** Refcount-neutral accessor for an already-acquired handle's atlas. */
160
+ acquireOutputs(handle: TileHandle): CompositeOutputs {
161
+ const out = this.cache.getOutputs(handle);
162
+ if (!out) {
163
+ throw new Error(
164
+ `TileTextureCompositor: handle not acquired before requesting outputs`,
165
+ );
166
+ }
167
+ return out;
168
+ }
169
+
170
+ markDirty(handle: TileHandle, reason: DirtyReason): void {
171
+ this.cache.markDirty(handle, reason);
172
+ }
173
+
174
+ // ---------------------------------------------------------------------
175
+ // Vector-scene render
176
+ // ---------------------------------------------------------------------
177
+
178
+ /**
179
+ * Bake each resolved vector layer's WM source scenes into its per-layer render
180
+ * target (slot `i` → `renderTargets[i]`). A layer can be backed by several WM
181
+ * vector tiles when the terrain is Geographic (N:M); every source is drawn into
182
+ * the one render target, each framed by the Rust-supplied mercator affine
183
+ * `uvOffset`/`uvScale` so it lands in its sub-rect. Since the affine maps the
184
+ * terrain tile's `[0, 1]` UV into the source frame, the render-target UV ends up
185
+ * equal to the terrain UV — i.e. the RT spans the terrain tile's extent, ready
186
+ * for the composite paste's latitude reprojection. `(0,0)/(1,1)` (an exact
187
+ * same-tile drape, WebMercator terrain) maps a single source to the full RT.
188
+ *
189
+ * The render target is cleared once, then each source is drawn additively
190
+ * (autoClear off) so the sources mosaic instead of overwriting one another. A
191
+ * source whose scene hasn't reached the cache yet is skipped; its sub-rect stays
192
+ * transparent until it arrives (no flashing — coarser ancestors back the gaps
193
+ * via the Rust scene-ready walk-up). The caller owns the dirty gate and only
194
+ * calls this when the resolved slots or scenes change.
195
+ */
196
+ renderVectorScenes(
197
+ slots: {
198
+ layerId: string;
199
+ sources: {
200
+ tileHandle: TileHandle;
201
+ uvOffset: [number, number];
202
+ uvScale: [number, number];
203
+ }[];
204
+ }[],
205
+ renderTargets: WebGLRenderTarget[],
206
+ ): void {
207
+ const camera = this.vectorCamera;
208
+ const prevTarget = this.renderer.getRenderTarget();
209
+ const prevClear = this.renderer.getClearColor(PREV_CLEAR_COLOR);
210
+ const prevClearAlpha = this.renderer.getClearAlpha();
211
+ const prevAutoClear = this.renderer.autoClear;
212
+ // Accumulate multiple sources into one RT: clear once, never per render.
213
+ this.renderer.autoClear = false;
214
+
215
+ for (let i = 0; i < renderTargets.length; i++) {
216
+ const renderTarget = renderTargets[i];
217
+ if (!renderTarget) continue;
218
+
219
+ const slot = slots[i];
220
+ // An empty slot only needs a clear when the target holds stale content
221
+ // from a previous bake; a never-touched target has no GL storage yet
222
+ // and must not be render-targeted (that would allocate it).
223
+ if (!slot && !this.touchedVectorTargets.has(renderTarget)) continue;
224
+ this.touchedVectorTargets.add(renderTarget);
225
+
226
+ this.renderer.setRenderTarget(renderTarget);
227
+ this.renderer.setClearColor(0x000, 0);
228
+ this.renderer.clear();
229
+
230
+ if (slot) {
231
+ for (const source of slot.sources) {
232
+ const scene = this.texturizedScenes.findSceneByLayerId(
233
+ source.tileHandle,
234
+ slot.layerId,
235
+ );
236
+ if (!scene || scene.removed || !scene.children.length) continue;
237
+ // Frame the terrain tile's sub-rect of this source tile from the
238
+ // mercator uvOffset/uvScale: meshUv ∈ [0,1] → vectorUv = uvOffset +
239
+ // meshUv·uvScale, source scene NDC = 2·vectorUv − 1. A source finer than
240
+ // the terrain tile yields a camera wider than [-1, 1], so it draws into
241
+ // only its sub-rect and leaves the rest of the RT transparent.
242
+ const [ox, oy] = source.uvOffset;
243
+ const [sx, sy] = source.uvScale;
244
+ camera.left = 2 * ox - 1;
245
+ camera.right = 2 * (ox + sx) - 1;
246
+ camera.bottom = 2 * oy - 1;
247
+ camera.top = 2 * (oy + sy) - 1;
248
+ camera.updateProjectionMatrix();
249
+ this.renderer.render(scene, camera);
250
+ }
251
+ }
252
+
253
+ renderTarget.texture.needsUpdate = true;
254
+ }
255
+
256
+ this.renderer.autoClear = prevAutoClear;
257
+ this.renderer.setRenderTarget(prevTarget);
258
+ this.renderer.setClearColor(prevClear, prevClearAlpha);
259
+ }
260
+
261
+ // ---------------------------------------------------------------------
262
+ // MRT composite pass
263
+ // ---------------------------------------------------------------------
264
+
265
+ /**
266
+ * Run the composite MRT pass for a handle if (and only if) its atlas is
267
+ * dirty. Consumes the dirty flags on success so the next frame skips work
268
+ * unless something marks it dirty again.
269
+ *
270
+ * The pass writes:
271
+ * color attachment = alpha-composited diffuse over N slots
272
+ * attr attachment = water + texturized-layer flags + winning slot idx
273
+ * normal attachment = hillshade normal (or neutral)
274
+ *
275
+ * Returns true when a render happened.
276
+ */
277
+ runCompositePassIfDirty(
278
+ handle: TileHandle,
279
+ plan: SlotPlan,
280
+ globals: CompositeGlobals,
281
+ features: CompositeFeatures,
282
+ ): boolean {
283
+ if (!this.cache.isDirty(handle)) return false;
284
+ const entry = this.cache.getEntry(handle);
285
+ if (!entry) return false;
286
+
287
+ const prevTarget = this.renderer.getRenderTarget();
288
+ const prevClear = this.renderer.getClearColor(PREV_CLEAR_COLOR);
289
+ const prevClearAlpha = this.renderer.getClearAlpha();
290
+
291
+ // No active slots → either clear (nothing to bake) or fall through to the
292
+ // shader path when a slot-independent feature still needs to write the
293
+ // atlas. Currently that's watermask only: it samples a per-tile texture
294
+ // and bakes the result into attr.r so water reflection works on tiles
295
+ // that have no raster/vector layers (e.g. open-ocean quantized-mesh).
296
+ const noSlots = plan.rasterCount + plan.vectorCount === 0;
297
+ if (noSlots && !features.hasWatermask) {
298
+ this.renderer.setRenderTarget(entry.atlas.target);
299
+ this.renderer.setClearColor(0x000000, 0);
300
+ this.renderer.clear();
301
+ this.renderer.setRenderTarget(prevTarget);
302
+ this.renderer.setClearColor(prevClear, prevClearAlpha);
303
+ entry.atlas.color.needsUpdate = true;
304
+ entry.atlas.attr.needsUpdate = true;
305
+ entry.atlas.normal.needsUpdate = true;
306
+ this.cache.consumeDirty(handle);
307
+ return true;
308
+ }
309
+
310
+ const mat = this.getOrCreateMaterial(plan, features);
311
+
312
+ this.bindUniforms(mat, plan, globals);
313
+ this.quadMesh.material = mat.material;
314
+
315
+ this.renderer.setRenderTarget(entry.atlas.target);
316
+ this.renderer.setClearColor(0x000000, 0);
317
+ this.renderer.clear();
318
+ this.renderer.render(this.quadScene, this.quadCamera);
319
+
320
+ this.renderer.setRenderTarget(prevTarget);
321
+ this.renderer.setClearColor(prevClear, prevClearAlpha);
322
+
323
+ entry.atlas.color.needsUpdate = true;
324
+ entry.atlas.attr.needsUpdate = true;
325
+ entry.atlas.normal.needsUpdate = true;
326
+
327
+ this.cache.consumeDirty(handle);
328
+ return true;
329
+ }
330
+
331
+ private getOrCreateMaterial(
332
+ plan: SlotPlan,
333
+ features: CompositeFeatures,
334
+ ): CachedMaterial {
335
+ const { rasterCount, vectorCount, boundary } = plan;
336
+ const numTextures = rasterCount + vectorCount;
337
+ const key = `${rasterCount}|${vectorCount}|${boundary}|${compositeFeatureKey(features)}`;
338
+ const cached = this.materialCache.get(key);
339
+ if (cached) return cached;
340
+
341
+ // 1×1 transparent placeholder. A bare `new Texture()` has no image and
342
+ // triggers a "Texture marked for update but no image data found" warning
343
+ // when three.js tries to upload it.
344
+ const placeholderTexture = new DataTexture(
345
+ new Uint8Array([0, 0, 0, 0]),
346
+ 1,
347
+ 1,
348
+ RGBAFormat,
349
+ UnsignedByteType,
350
+ );
351
+ placeholderTexture.needsUpdate = true;
352
+
353
+ // Build the enhancer chain once and use it for both shader generation and
354
+ // uniform ownership: each active enhancer attaches its own uniform refs and
355
+ // contributes the matching GLSL, so a new expression touches only its module.
356
+ const chain = createCompositeLayerEnhancers(features);
357
+ const core = createCoreUniformMutates();
358
+
359
+ const uniforms: CompositeUniformTarget = {};
360
+ core.attachUniforms(uniforms, numTextures, placeholderTexture);
361
+ let defines: Record<string, number> = {};
362
+ for (const enhancer of chain) {
363
+ enhancer.attachUniforms?.(uniforms, numTextures, placeholderTexture);
364
+ if (enhancer.defines) defines = { ...defines, ...enhancer.defines };
365
+ }
366
+
367
+ const material = new ShaderMaterial({
368
+ glslVersion: GLSL3,
369
+ defines,
370
+ uniforms,
371
+ vertexShader: COMPOSITE_VERTEX_SHADER,
372
+ fragmentShader: buildCompositeFragmentShader(
373
+ rasterCount,
374
+ vectorCount,
375
+ boundary,
376
+ composeCompositeContributions(chain, numTextures),
377
+ ),
378
+ depthTest: false,
379
+ depthWrite: false,
380
+ });
381
+
382
+ const entry: CachedMaterial = {
383
+ material,
384
+ numTextures,
385
+ core,
386
+ chain,
387
+ placeholderTexture,
388
+ };
389
+ this.materialCache.set(key, entry);
390
+ return entry;
391
+ }
392
+
393
+ private bindUniforms(
394
+ mat: CachedMaterial,
395
+ plan: SlotPlan,
396
+ globals: CompositeGlobals,
397
+ ): void {
398
+ // Single pass over the compact slots: the base binds the core uniforms and
399
+ // each active enhancer fills its own per-slot ref for the same slot.
400
+ for (let k = 0; k < mat.numTextures; k++) {
401
+ const layer = plan.slots[k]?.layer;
402
+ mat.core.bindSlot(k, layer);
403
+ for (const enhancer of mat.chain) enhancer.bindSlot?.(k, layer);
404
+ }
405
+ for (const enhancer of mat.chain) enhancer.bindGlobal?.(globals);
406
+ }
407
+
408
+ // ---------------------------------------------------------------------
409
+ // Lifecycle
410
+ // ---------------------------------------------------------------------
411
+
412
+ dispose(): void {
413
+ this.cache.disposeAll();
414
+ for (const m of this.materialCache.values()) {
415
+ m.material.dispose();
416
+ m.placeholderTexture.dispose();
417
+ }
418
+ this.materialCache.clear();
419
+ this.quadMesh.geometry.dispose();
420
+ }
421
+ }