@driftengine/core 4.4.2 → 4.6.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 (339) hide show
  1. package/CHANGELOG.json +97 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +28 -3
  23. package/dist/index.js +20 -1
  24. package/dist/render/backend/api.d.ts +34 -14
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  30. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  31. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  32. package/dist/render/backend/webgl2/instanced.js +72 -15
  33. package/dist/render/backend/webgl2/renderer.d.ts +201 -16
  34. package/dist/render/backend/webgl2/renderer.js +596 -224
  35. package/dist/render/backend/webgpu/buffers.d.ts +29 -1
  36. package/dist/render/backend/webgpu/buffers.js +61 -4
  37. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  38. package/dist/render/backend/webgpu/depthPass.js +7 -2
  39. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  40. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  41. package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
  42. package/dist/render/backend/webgpu/flatPass.js +57 -9
  43. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  44. package/dist/render/backend/webgpu/glassTintPass.js +206 -0
  45. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  46. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  47. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  48. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  49. package/dist/render/backend/webgpu/instanced.d.ts +23 -2
  50. package/dist/render/backend/webgpu/instanced.js +41 -4
  51. package/dist/render/backend/webgpu/latePass.d.ts +30 -0
  52. package/dist/render/backend/webgpu/latePass.js +140 -0
  53. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  54. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  55. package/dist/render/backend/webgpu/motionPass.js +491 -0
  56. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  57. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  58. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  59. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  60. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  61. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  62. package/dist/render/backend/webgpu/postPass.d.ts +3 -13
  63. package/dist/render/backend/webgpu/renderer.d.ts +271 -64
  64. package/dist/render/backend/webgpu/renderer.js +1395 -580
  65. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  66. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  67. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  68. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  69. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  70. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  71. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  72. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  73. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
  74. package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
  75. package/dist/render/driftLight/bake.d.ts +7 -0
  76. package/dist/render/driftLight/bake.js +61 -56
  77. package/dist/render/driftLight/denseField.d.ts +46 -0
  78. package/dist/render/driftLight/denseField.js +109 -0
  79. package/dist/render/driftLight/lightField.d.ts +10 -5
  80. package/dist/render/driftLight/lightField.js +25 -19
  81. package/dist/render/driftLight/presence.d.ts +50 -0
  82. package/dist/render/driftLight/presence.js +43 -0
  83. package/dist/render/driftLight/uniforms.d.ts +3 -3
  84. package/dist/render/driftLight/uniforms.js +5 -6
  85. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  86. package/dist/render/driftLight/worldLightField.js +66 -0
  87. package/dist/render/fixtureAtlas.d.ts +20 -0
  88. package/dist/render/fixtureAtlas.js +49 -0
  89. package/dist/render/glass.d.ts +45 -0
  90. package/dist/render/glass.js +39 -0
  91. package/dist/render/glassCasters.d.ts +40 -0
  92. package/dist/render/glassCasters.js +84 -0
  93. package/dist/render/glassShadow.d.ts +78 -0
  94. package/dist/render/glassShadow.js +125 -0
  95. package/dist/render/globalMediumPass.d.ts +2 -3
  96. package/dist/render/globalMediumPass.js +2 -8
  97. package/dist/render/gpudriven/lit.d.ts +12 -0
  98. package/dist/render/gpudriven/lit.js +23 -0
  99. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  100. package/dist/render/gpudriven/materialTable.js +26 -6
  101. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  102. package/dist/render/gpudriven/pipeline.js +4 -3
  103. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  104. package/dist/render/gpudriven/sunGlass.js +89 -0
  105. package/dist/render/instanceCull.d.ts +63 -0
  106. package/dist/render/instanceCull.js +160 -0
  107. package/dist/render/instances.d.ts +15 -0
  108. package/dist/render/lightBudget.d.ts +36 -35
  109. package/dist/render/lightBudget.js +38 -37
  110. package/dist/render/lightGrid.d.ts +42 -0
  111. package/dist/render/lightGrid.js +87 -0
  112. package/dist/render/mesh.d.ts +18 -24
  113. package/dist/render/mesh.js +51 -68
  114. package/dist/render/occlusion.d.ts +10 -0
  115. package/dist/render/occlusion.js +36 -7
  116. package/dist/render/pointGlassTint.d.ts +59 -0
  117. package/dist/render/pointGlassTint.js +198 -0
  118. package/dist/render/pointLightSelection.d.ts +7 -0
  119. package/dist/render/pointLightSelection.js +29 -4
  120. package/dist/render/pointShadowArray.d.ts +56 -3
  121. package/dist/render/pointShadowArray.js +127 -16
  122. package/dist/render/pointShadowMap.d.ts +23 -1
  123. package/dist/render/pointShadowMap.js +27 -1
  124. package/dist/render/rainField.d.ts +13 -0
  125. package/dist/render/rainField.js +27 -5
  126. package/dist/render/recon/changeFrames.d.ts +15 -0
  127. package/dist/render/recon/changeFrames.js +21 -0
  128. package/dist/render/recon/lateDepth.d.ts +11 -0
  129. package/dist/render/recon/lateDepth.js +70 -0
  130. package/dist/render/recon/lateRouting.d.ts +14 -0
  131. package/dist/render/recon/lateRouting.js +18 -0
  132. package/dist/render/recon/mover.d.ts +57 -0
  133. package/dist/render/recon/mover.js +86 -0
  134. package/dist/render/recon/resolve.d.ts +34 -4
  135. package/dist/render/recon/resolve.js +161 -20
  136. package/dist/render/renderQuality.d.ts +35 -0
  137. package/dist/render/renderQuality.js +7 -0
  138. package/dist/render/rendererHarness.js +16 -0
  139. package/dist/render/sceneTarget.d.ts +9 -1
  140. package/dist/render/sceneTarget.js +60 -12
  141. package/dist/render/shaders/depth.d.ts +16 -1
  142. package/dist/render/shaders/depth.js +94 -15
  143. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  144. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  145. package/dist/render/shaders/flat/driftLight.js +32 -7
  146. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  147. package/dist/render/shaders/flat/glassTint.js +25 -0
  148. package/dist/render/shaders/flat/index.d.ts +13 -0
  149. package/dist/render/shaders/flat/index.js +9 -3
  150. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  151. package/dist/render/shaders/flat/lampGlass.js +183 -0
  152. package/dist/render/shaders/flat/main.d.ts +1 -1
  153. package/dist/render/shaders/flat/main.js +190 -38
  154. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  155. package/dist/render/shaders/flat/pointShadow.js +2 -0
  156. package/dist/render/shaders/flat/preamble.js +96 -26
  157. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  158. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  159. package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
  160. package/dist/render/shaders/generated/depth.wgsl.js +230 -7
  161. package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
  162. package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
  163. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  164. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  165. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  166. package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
  167. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  168. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  169. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  170. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  171. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  172. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  173. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  174. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  175. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  176. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  177. package/dist/render/shaders/globalMedium.js +6 -6
  178. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  179. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  180. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  181. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  182. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  183. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  184. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  185. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  186. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  187. package/dist/render/shaders/lightVolume.js +6 -6
  188. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  189. package/dist/render/shaders/octahedralResolve.js +48 -17
  190. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  191. package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
  192. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  193. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  194. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  195. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  196. package/dist/render/shaders/rush.js +11 -1
  197. package/dist/render/shaders/sdfText.d.ts +1 -1
  198. package/dist/render/shaders/sdfText.js +3 -2
  199. package/dist/render/shaders/text.d.ts +1 -1
  200. package/dist/render/shaders/text.js +18 -9
  201. package/dist/render/shadowCasters.d.ts +20 -1
  202. package/dist/render/shadowCasters.js +15 -1
  203. package/dist/render/shadowMap.d.ts +54 -5
  204. package/dist/render/shadowMap.js +190 -36
  205. package/dist/render/sunGlassTint.d.ts +25 -0
  206. package/dist/render/sunGlassTint.js +79 -0
  207. package/dist/render/surfaceEffects.d.ts +83 -0
  208. package/dist/render/surfaceEffects.js +75 -0
  209. package/dist/render/surfaceScene.d.ts +8 -0
  210. package/dist/render/surfaceScene.js +8 -0
  211. package/dist/render/surfaceTexture.d.ts +18 -2
  212. package/dist/render/surfaceTexture.js +68 -15
  213. package/dist/render/temporalAa.d.ts +25 -6
  214. package/dist/render/temporalAa.js +45 -15
  215. package/dist/render/textureSource.d.ts +30 -0
  216. package/dist/render/textureSource.js +53 -0
  217. package/dist/render/translucentQueue.d.ts +6 -0
  218. package/dist/render/translucentQueue.js +15 -1
  219. package/dist/render/vertexDefaults.js +2 -0
  220. package/dist/world/hlod.d.ts +104 -0
  221. package/dist/world/hlod.js +214 -0
  222. package/package.json +6 -6
  223. package/src/geometry/boxCsg.ts +186 -0
  224. package/src/geometry/csg.ts +384 -0
  225. package/src/geometry/meshBuilder.ts +15 -0
  226. package/src/geometry/meshConcat.ts +1 -0
  227. package/src/geometry/pixelFont.ts +2 -0
  228. package/src/geometry/solid.ts +189 -0
  229. package/src/geometry/solidFlat.ts +522 -0
  230. package/src/geometry/solidHarness.ts +83 -0
  231. package/src/geometry/solidNormals.ts +103 -0
  232. package/src/geometry/solidRound.ts +443 -0
  233. package/src/geometry/solidWriter.ts +58 -0
  234. package/src/index.ts +61 -1
  235. package/src/render/backend/api.ts +36 -11
  236. package/src/render/backend/drawFog.ts +31 -0
  237. package/src/render/backend/materialChanges.ts +9 -2
  238. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  239. package/src/render/backend/webgl2/instanced.ts +80 -14
  240. package/src/render/backend/webgl2/renderer.ts +700 -250
  241. package/src/render/backend/webgpu/buffers.ts +92 -5
  242. package/src/render/backend/webgpu/depthPass.ts +7 -2
  243. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  244. package/src/render/backend/webgpu/flatPass.ts +62 -11
  245. package/src/render/backend/webgpu/glassTintPass.ts +245 -0
  246. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  247. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  248. package/src/render/backend/webgpu/instanced.ts +42 -4
  249. package/src/render/backend/webgpu/latePass.ts +161 -0
  250. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  251. package/src/render/backend/webgpu/motionPass.ts +535 -0
  252. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  253. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  254. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  255. package/src/render/backend/webgpu/renderer.ts +1671 -670
  256. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  257. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  258. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  259. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  260. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  261. package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
  262. package/src/render/driftLight/bake.ts +85 -62
  263. package/src/render/driftLight/denseField.ts +149 -0
  264. package/src/render/driftLight/lightField.ts +33 -18
  265. package/src/render/driftLight/presence.ts +69 -0
  266. package/src/render/driftLight/uniforms.ts +8 -9
  267. package/src/render/driftLight/worldLightField.ts +83 -0
  268. package/src/render/fixtureAtlas.ts +63 -0
  269. package/src/render/glass.ts +74 -0
  270. package/src/render/glassCasters.ts +164 -0
  271. package/src/render/glassShadow.ts +153 -0
  272. package/src/render/globalMediumPass.ts +4 -11
  273. package/src/render/gpudriven/lit.ts +31 -0
  274. package/src/render/gpudriven/materialTable.ts +28 -6
  275. package/src/render/gpudriven/pipeline.ts +4 -3
  276. package/src/render/gpudriven/sunGlass.ts +117 -0
  277. package/src/render/instanceCull.ts +205 -0
  278. package/src/render/instances.ts +16 -0
  279. package/src/render/lightBudget.ts +41 -38
  280. package/src/render/lightGrid.ts +130 -0
  281. package/src/render/mesh.ts +59 -72
  282. package/src/render/occlusion.ts +51 -7
  283. package/src/render/pointGlassTint.ts +250 -0
  284. package/src/render/pointLightSelection.ts +72 -4
  285. package/src/render/pointShadowArray.ts +188 -22
  286. package/src/render/pointShadowMap.ts +41 -0
  287. package/src/render/rainField.ts +39 -5
  288. package/src/render/recon/changeFrames.ts +24 -0
  289. package/src/render/recon/lateDepth.ts +83 -0
  290. package/src/render/recon/lateRouting.ts +29 -0
  291. package/src/render/recon/mover.ts +118 -0
  292. package/src/render/recon/resolve.ts +206 -20
  293. package/src/render/renderQuality.ts +47 -0
  294. package/src/render/rendererHarness.ts +16 -0
  295. package/src/render/sceneTarget.ts +62 -11
  296. package/src/render/shaders/depth.ts +118 -15
  297. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  298. package/src/render/shaders/flat/driftLight.ts +32 -7
  299. package/src/render/shaders/flat/glassTint.ts +25 -0
  300. package/src/render/shaders/flat/index.ts +22 -3
  301. package/src/render/shaders/flat/lampGlass.ts +185 -0
  302. package/src/render/shaders/flat/main.ts +190 -38
  303. package/src/render/shaders/flat/pointShadow.ts +2 -0
  304. package/src/render/shaders/flat/preamble.ts +96 -26
  305. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  306. package/src/render/shaders/generated/depth.wgsl.ts +237 -7
  307. package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
  308. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  309. package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
  310. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  311. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  312. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  313. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  314. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  315. package/src/render/shaders/globalMedium.ts +6 -6
  316. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  317. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  318. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  319. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  320. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  321. package/src/render/shaders/lightVolume.ts +6 -6
  322. package/src/render/shaders/octahedralResolve.ts +50 -17
  323. package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
  324. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  325. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  326. package/src/render/shaders/rush.ts +11 -1
  327. package/src/render/shaders/sdfText.ts +3 -2
  328. package/src/render/shaders/text.ts +18 -9
  329. package/src/render/shadowCasters.ts +28 -1
  330. package/src/render/shadowMap.ts +231 -37
  331. package/src/render/sunGlassTint.ts +86 -0
  332. package/src/render/surfaceEffects.ts +132 -0
  333. package/src/render/surfaceScene.ts +16 -0
  334. package/src/render/surfaceTexture.ts +110 -19
  335. package/src/render/temporalAa.ts +78 -14
  336. package/src/render/textureSource.ts +72 -0
  337. package/src/render/translucentQueue.ts +20 -1
  338. package/src/render/vertexDefaults.ts +2 -0
  339. package/src/world/hlod.ts +290 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * DriftLight for a whole world: every fixed light summed into one dense grid, baked offline.
3
+ *
4
+ * **Dense, where the courtyard field is sparse, because a city is lit nearly everywhere.** A brick
5
+ * earns its index and its shared faces where light is rare; street lamps reaching thirty metres
6
+ * forty metres apart leave almost no cell dark, so the sparseness buys nothing and the shared
7
+ * faces cost 2.4× the samples. At 8 m a sample, a two-kilometre city 128 m tall is 256 × 16 × 256
8
+ * samples, two half-float texels each: 16 MB on the GPU, and the renderer's lookup is one filtered
9
+ * fetch for the light and one for its direction.
10
+ *
11
+ * **The same sample as every other bake.** `sampleFieldLight` is shared with `bakeBrick`, so a dense
12
+ * volume and a brick agree bit for bit wherever both have a sample. The order a sample's lights are
13
+ * summed in does not have to match for that: the sum runs in double precision and is stored in
14
+ * single, which the last bits of a few terms in another order do not reach.
15
+ *
16
+ * **Lights are bucketed first**, four samples a bucket on every axis, so a sample sums only the
17
+ * lights whose reach touches its bucket rather than all of them: a city of ten thousand lamps is a
18
+ * few dozen candidates a sample. Offline and allocation-free per sample; it may take seconds.
19
+ *
20
+ * What it gives up is resolution: at 8 m a lamp's pool is a few samples across, which is why this
21
+ * stands in only past the exact lights, where a pool is a few dozen pixels.
22
+ */
23
+ import { sampleFieldLight } from './bake.js';
24
+ /** The most samples along x or z: WebGL2 promises 256 on every axis of a 3D texture. */
25
+ export const DENSE_MAX_AXIS = 256;
26
+ /** The most along y, which holds the light and then the direction: two layers in 256. */
27
+ export const DENSE_MAX_HEIGHT = 128;
28
+ /** Samples a bucket spans on each axis. */
29
+ const BUCKET = 4;
30
+ /**
31
+ * Sum `lights` over the box `bounds` (min xyz, max xyz) at `spacing` metres. `distance` occludes, or
32
+ * null for none. The first sample stands on the box's min corner.
33
+ */
34
+ export function bakeDenseField(lights, falloff, distance, bounds, spacing) {
35
+ if (!(spacing > 0))
36
+ throw new Error(`bakeDenseField: spacing is ${String(spacing)}`);
37
+ const origin = [
38
+ bounds[0],
39
+ bounds[1],
40
+ bounds[2],
41
+ ];
42
+ const dims = [0, 0, 0];
43
+ for (let axis = 0; axis < 3; axis++) {
44
+ const extent = bounds[axis + 3] - origin[axis];
45
+ dims[axis] = Math.max(1, Math.floor(extent / spacing + 1e-9) + 1);
46
+ }
47
+ if (dims[0] > DENSE_MAX_AXIS || dims[2] > DENSE_MAX_AXIS || dims[1] > DENSE_MAX_HEIGHT) {
48
+ throw new Error(`bakeDenseField: ${dims.join(' × ')} samples, and a volume holds ${DENSE_MAX_AXIS} across, ` +
49
+ `${DENSE_MAX_HEIGHT} up and ${DENSE_MAX_AXIS} deep; a coarser spacing fits it.`);
50
+ }
51
+ /* Each light listed in every bucket its reach touches, in the order the scene listed them. */
52
+ const buckets = [
53
+ Math.ceil(dims[0] / BUCKET),
54
+ Math.ceil(dims[1] / BUCKET),
55
+ Math.ceil(dims[2] / BUCKET),
56
+ ];
57
+ const bucketCount = buckets[0] * buckets[1] * buckets[2];
58
+ const span = spacing * BUCKET;
59
+ const range = (light, axis, out) => {
60
+ const at = axis === 0 ? light.x : axis === 1 ? light.y : light.z;
61
+ const lo = Math.floor((at - light.radius - origin[axis]) / span);
62
+ const hi = Math.floor((at + light.radius - origin[axis]) / span);
63
+ out[axis * 2] = Math.max(0, lo);
64
+ out[axis * 2 + 1] = Math.min(buckets[axis] - 1, hi);
65
+ return out[axis * 2] <= out[axis * 2 + 1];
66
+ };
67
+ const cells = new Int32Array(6);
68
+ const start = new Uint32Array(bucketCount + 1);
69
+ const each = (fill) => {
70
+ for (let index = 0; index < lights.length; index++) {
71
+ const light = lights[index];
72
+ if (!(light.radius > 0))
73
+ continue;
74
+ if (!range(light, 0, cells) || !range(light, 1, cells) || !range(light, 2, cells))
75
+ continue;
76
+ for (let z = cells[4]; z <= cells[5]; z++) {
77
+ for (let y = cells[2]; y <= cells[3]; y++) {
78
+ for (let x = cells[0]; x <= cells[1]; x++) {
79
+ fill(x + buckets[0] * (y + buckets[1] * z), index);
80
+ }
81
+ }
82
+ }
83
+ }
84
+ };
85
+ each((bucket) => {
86
+ start[bucket + 1] = start[bucket + 1] + 1;
87
+ });
88
+ for (let b = 0; b < bucketCount; b++)
89
+ start[b + 1] = start[b + 1] + start[b];
90
+ const listed = new Uint32Array(start[bucketCount]);
91
+ const cursor = start.slice(0, bucketCount);
92
+ each((bucket, light) => {
93
+ listed[cursor[bucket]] = light;
94
+ cursor[bucket] = cursor[bucket] + 1;
95
+ });
96
+ const count = dims[0] * dims[1] * dims[2];
97
+ const light = new Float32Array(count * 4);
98
+ const direction = new Float32Array(count * 4);
99
+ for (let k = 0; k < dims[2]; k++) {
100
+ for (let j = 0; j < dims[1]; j++) {
101
+ for (let i = 0; i < dims[0]; i++) {
102
+ const bucket = Math.floor(i / BUCKET) +
103
+ buckets[0] * (Math.floor(j / BUCKET) + buckets[1] * Math.floor(k / BUCKET));
104
+ sampleFieldLight(origin[0] + i * spacing, origin[1] + j * spacing, origin[2] + k * spacing, lights, listed, start[bucket], start[bucket + 1], falloff, distance, light, direction, (i + dims[0] * (j + dims[1] * k)) * 4);
105
+ }
106
+ }
107
+ }
108
+ return { origin, spacing, dims, light, direction };
109
+ }
@@ -29,6 +29,7 @@ import type { GlobalFieldInstance } from '../gi/globalField.ts';
29
29
  import type { FieldLight } from './bake.ts';
30
30
  import type { PointLightFalloff } from './falloff.ts';
31
31
  import type { LightFieldLayout } from './layout.ts';
32
+ import type { DriftLightVolumes } from './presence.ts';
32
33
  /** Seconds the summed light takes to fade in once the field is whole. */
33
34
  export declare const FIELD_FADE_SEC = 0.5;
34
35
  /** A light a field sums: a `PointLightSource`, whose `inLightField` the field sets. */
@@ -51,7 +52,7 @@ export interface LightFieldOptions {
51
52
  */
52
53
  readonly fadeSec?: number;
53
54
  }
54
- export declare class LightField {
55
+ export declare class LightField implements DriftLightVolumes {
55
56
  readonly layout: LightFieldLayout;
56
57
  /** Texels on each axis of the atlas. */
57
58
  readonly atlasSize: readonly [number, number, number];
@@ -67,17 +68,14 @@ export declare class LightField {
67
68
  readonly fadeSec: number;
68
69
  /** The summed light's brightness, for a scene that dims all its lamps at once: dawn, a switch. */
69
70
  scale: number;
70
- /** Where the frame's choice was centred, which the shader measures the radius from. */
71
- readonly centre: Float32Array<ArrayBuffer>;
72
71
  private readonly lights;
73
72
  private readonly falloff;
74
73
  private readonly distance;
75
74
  private readonly lightBlock;
76
75
  private readonly directionBlock;
76
+ private readonly showing;
77
77
  private baked;
78
78
  private taken;
79
- private eased;
80
- private shown;
81
79
  constructor(lights: readonly LightFieldSource[], options: LightFieldOptions);
82
80
  /** Whether every brick has been baked. */
83
81
  get ready(): boolean;
@@ -87,6 +85,13 @@ export declare class LightField {
87
85
  get radius(): number;
88
86
  /** How much of the summed light is in the frame, 0 until the field is whole, then up to 1. */
89
87
  get presence(): number;
88
+ /** Where the frame's choice was centred, which the shader measures the radius from. */
89
+ get centre(): Float32Array;
90
+ get empty(): boolean;
91
+ get indexDims(): readonly [number, number, number];
92
+ get index(): Uint32Array;
93
+ get sampleOrigin(): readonly [number, number, number];
94
+ get signedSpacing(): number;
90
95
  /** The texel at which brick `brick`'s light block starts; its direction block is 4 along x. */
91
96
  atlasCorner(brick: number): [number, number, number];
92
97
  /** Bake up to `bricks` more bricks into the atlas, and answer how many were baked. */
@@ -29,10 +29,9 @@ import { distanceToInstances } from '../gi/globalField.js';
29
29
  import { halfBits } from '../halfFloat.js';
30
30
  import { BRICK_TEXELS, bakeBrick } from './bake.js';
31
31
  import { BRICK_SAMPLES, layoutLightField } from './layout.js';
32
+ import { FieldPresence } from './presence.js';
32
33
  /** Seconds the summed light takes to fade in once the field is whole. */
33
34
  export const FIELD_FADE_SEC = 0.5;
34
- /** How fast the shading radius grows back, per second, when the choice reaches further again. */
35
- const RADIUS_GROWTH_PER_SEC = 2;
36
35
  /** Bricks along each axis of the atlas: 256 texels a side, the least 3D texture WebGL2 promises. */
37
36
  const ATLAS_BRICKS = [32, 64, 64];
38
37
  /** A brick's footprint in the atlas: its light block and its direction block side by side. */
@@ -53,17 +52,14 @@ export class LightField {
53
52
  fadeSec;
54
53
  /** The summed light's brightness, for a scene that dims all its lamps at once: dawn, a switch. */
55
54
  scale = 1;
56
- /** Where the frame's choice was centred, which the shader measures the radius from. */
57
- centre = new Float32Array(3);
58
55
  lights;
59
56
  falloff;
60
57
  distance;
61
58
  lightBlock = new Float32Array(BRICK_TEXELS * 4);
62
59
  directionBlock = new Float32Array(BRICK_TEXELS * 4);
60
+ showing;
63
61
  baked = 0;
64
62
  taken = 0;
65
- eased = 0;
66
- shown = 0;
67
63
  constructor(lights, options) {
68
64
  for (const light of lights)
69
65
  light.inLightField = true;
@@ -80,6 +76,7 @@ export class LightField {
80
76
  this.falloff = options.falloff;
81
77
  this.band = options.band ?? 2;
82
78
  this.fadeSec = Math.max(0, options.fadeSec ?? FIELD_FADE_SEC);
79
+ this.showing = new FieldPresence(this.fadeSec);
83
80
  const fields = options.fields ?? [];
84
81
  this.occluded = fields.length > 0;
85
82
  this.distance = this.occluded ? distanceToInstances(fields) : null;
@@ -107,11 +104,30 @@ export class LightField {
107
104
  }
108
105
  /** The radius, in metres from `centre`, inside which lights are shaded exactly. */
109
106
  get radius() {
110
- return this.eased;
107
+ return this.showing.radius;
111
108
  }
112
109
  /** How much of the summed light is in the frame, 0 until the field is whole, then up to 1. */
113
110
  get presence() {
114
- return this.shown;
111
+ return this.showing.presence;
112
+ }
113
+ /** Where the frame's choice was centred, which the shader measures the radius from. */
114
+ get centre() {
115
+ return this.showing.centre;
116
+ }
117
+ get empty() {
118
+ return this.layout.count === 0;
119
+ }
120
+ get indexDims() {
121
+ return this.layout.dims;
122
+ }
123
+ get index() {
124
+ return this.layout.index;
125
+ }
126
+ get sampleOrigin() {
127
+ return this.layout.origin;
128
+ }
129
+ get signedSpacing() {
130
+ return this.layout.spacing;
115
131
  }
116
132
  /** The texel at which brick `brick`'s light block starts; its direction block is 4 along x. */
117
133
  atlasCorner(brick) {
@@ -146,17 +162,7 @@ export class LightField {
146
162
  * which is only less exact. While nothing is summed the radius simply follows.
147
163
  */
148
164
  follow(complete, x, y, z, dtSec) {
149
- this.centre[0] = x;
150
- this.centre[1] = y;
151
- this.centre[2] = z;
152
- const showing = this.shown > 0;
153
- if (!showing || complete < this.eased)
154
- this.eased = complete;
155
- else
156
- this.eased += (complete - this.eased) * (1 - Math.exp(-RADIUS_GROWTH_PER_SEC * dtSec));
157
- if (this.ready) {
158
- this.shown = this.fadeSec > 0 ? Math.min(1, this.shown + dtSec / this.fadeSec) : 1;
159
- }
165
+ this.showing.follow(this.ready, complete, x, y, z, dtSec);
160
166
  }
161
167
  /** Write the brick just baked into its place in the atlas, as half floats. */
162
168
  place(brick) {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * How much of a DriftLight field is in the frame, and the radius inside which the frame's exact
3
+ * lights take over from it — the part every kind of field shares, kept once.
4
+ *
5
+ * **The radius shrinks at once and grows back slowly.** A pixel inside it that a left-out light
6
+ * reaches would be shaded short, so it cannot lag a shrink; a pixel it has not reached yet takes the
7
+ * summed light, which is only less exact, so a grow may ease. While nothing is summed the radius
8
+ * simply follows. The presence fades in over `fadeSec` once the field is whole, so a field
9
+ * completing in view does not pop — and a held clock never finishes a fade, so a field behind a
10
+ * loading screen asks for none.
11
+ */
12
+ export declare class FieldPresence {
13
+ readonly fadeSec: number;
14
+ /** Where the frame's choice was centred, which the shader measures the radius from. */
15
+ readonly centre: Float32Array<ArrayBuffer>;
16
+ private eased;
17
+ private shown;
18
+ constructor(fadeSec: number);
19
+ /** The radius, in metres from `centre`, inside which lights are shaded exactly. */
20
+ get radius(): number;
21
+ /** How much of the summed light is in the frame, 0 until the field is whole, then up to 1. */
22
+ get presence(): number;
23
+ /** Follow the frame's choice: `complete` from the selection, centred at (x, y, z). */
24
+ follow(ready: boolean, complete: number, x: number, y: number, z: number, dtSec: number): void;
25
+ }
26
+ /**
27
+ * What a backend uploads and binds for a field of either kind, and what the uniforms are made of.
28
+ *
29
+ * A sparse field's index finds its bricks; a dense one brings a 1×1×1 index of nothing, so the
30
+ * upload and the bind are one code path, and says it is dense by a negative spacing.
31
+ */
32
+ export interface DriftLightVolumes {
33
+ readonly ready: boolean;
34
+ /** Nothing summed at all: nothing to upload. */
35
+ readonly empty: boolean;
36
+ readonly indexDims: readonly [number, number, number];
37
+ readonly index: Uint32Array;
38
+ readonly atlasSize: readonly [number, number, number];
39
+ /** Half floats, four channels a texel, x fastest. */
40
+ readonly atlas: Uint16Array;
41
+ readonly presence: number;
42
+ readonly radius: number;
43
+ readonly band: number;
44
+ readonly scale: number;
45
+ readonly centre: Float32Array;
46
+ /** The world position of the first sample. */
47
+ readonly sampleOrigin: readonly [number, number, number];
48
+ /** Metres between samples, negative for a dense volume: the shader's mode. */
49
+ readonly signedSpacing: number;
50
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * How much of a DriftLight field is in the frame, and the radius inside which the frame's exact
3
+ * lights take over from it — the part every kind of field shares, kept once.
4
+ *
5
+ * **The radius shrinks at once and grows back slowly.** A pixel inside it that a left-out light
6
+ * reaches would be shaded short, so it cannot lag a shrink; a pixel it has not reached yet takes the
7
+ * summed light, which is only less exact, so a grow may ease. While nothing is summed the radius
8
+ * simply follows. The presence fades in over `fadeSec` once the field is whole, so a field
9
+ * completing in view does not pop — and a held clock never finishes a fade, so a field behind a
10
+ * loading screen asks for none.
11
+ */
12
+ /** How fast the shading radius grows back, per second, when the choice reaches further again. */
13
+ const RADIUS_GROWTH_PER_SEC = 2;
14
+ export class FieldPresence {
15
+ fadeSec;
16
+ /** Where the frame's choice was centred, which the shader measures the radius from. */
17
+ centre = new Float32Array(3);
18
+ eased = 0;
19
+ shown = 0;
20
+ constructor(fadeSec) {
21
+ this.fadeSec = fadeSec;
22
+ }
23
+ /** The radius, in metres from `centre`, inside which lights are shaded exactly. */
24
+ get radius() {
25
+ return this.eased;
26
+ }
27
+ /** How much of the summed light is in the frame, 0 until the field is whole, then up to 1. */
28
+ get presence() {
29
+ return this.shown;
30
+ }
31
+ /** Follow the frame's choice: `complete` from the selection, centred at (x, y, z). */
32
+ follow(ready, complete, x, y, z, dtSec) {
33
+ this.centre[0] = x;
34
+ this.centre[1] = y;
35
+ this.centre[2] = z;
36
+ if (this.shown <= 0 || complete < this.eased)
37
+ this.eased = complete;
38
+ else
39
+ this.eased += (complete - this.eased) * (1 - Math.exp(-RADIUS_GROWTH_PER_SEC * dtSec));
40
+ if (ready)
41
+ this.shown = this.fadeSec > 0 ? Math.min(1, this.shown + dtSec / this.fadeSec) : 1;
42
+ }
43
+ }
@@ -13,11 +13,11 @@
13
13
  * pixel the shader shades exactly inside the radius the selection promised. A mirror's camera, far
14
14
  * from the centre, gets a smaller radius and more of the volume, which is the safe direction.
15
15
  */
16
- import type { LightField } from './lightField.ts';
16
+ import type { DriftLightVolumes } from './presence.ts';
17
17
  export interface DriftLightUniforms {
18
18
  /** How much of the summed light is in, the radius, the band, the scale. */
19
19
  readonly light: Float32Array;
20
- /** The first brick's first sample, and the metres between samples. */
20
+ /** The first sample, and the metres between samples — negative for a dense volume. */
21
21
  readonly origin: Float32Array;
22
22
  }
23
23
  export declare function createDriftLightUniforms(): DriftLightUniforms;
@@ -25,4 +25,4 @@ export declare function createDriftLightUniforms(): DriftLightUniforms;
25
25
  * Fill `out` for `field`, or for no field, seen from `eye`; `probePass` while a probe's faces are
26
26
  * being drawn.
27
27
  */
28
- export declare function resolveDriftLight(field: LightField | null, probePass: boolean, eye: ArrayLike<number>, out: DriftLightUniforms): void;
28
+ export declare function resolveDriftLight(field: DriftLightVolumes | null, probePass: boolean, eye: ArrayLike<number>, out: DriftLightUniforms): void;
@@ -24,18 +24,17 @@ export function createDriftLightUniforms() {
24
24
  * being drawn.
25
25
  */
26
26
  export function resolveDriftLight(field, probePass, eye, out) {
27
- if (field === null || !field.ready || field.presence <= 0 || field.layout.count === 0) {
27
+ if (field === null || !field.ready || field.presence <= 0 || field.empty) {
28
28
  out.light.fill(0);
29
29
  return;
30
30
  }
31
- const { layout } = field;
32
31
  const offset = Math.hypot((eye[0] ?? 0) - field.centre[0], (eye[1] ?? 0) - field.centre[1], (eye[2] ?? 0) - field.centre[2]);
33
32
  out.light[0] = field.presence;
34
33
  out.light[1] = probePass ? 0 : Math.max(0, field.radius - offset);
35
34
  out.light[2] = field.band;
36
35
  out.light[3] = field.scale;
37
- out.origin[0] = layout.origin[0];
38
- out.origin[1] = layout.origin[1];
39
- out.origin[2] = layout.origin[2];
40
- out.origin[3] = layout.spacing;
36
+ out.origin[0] = field.sampleOrigin[0];
37
+ out.origin[1] = field.sampleOrigin[1];
38
+ out.origin[2] = field.sampleOrigin[2];
39
+ out.origin[3] = field.signedSpacing;
41
40
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A world's DriftLight: a dense volume baked offline (`bakeDenseField`), handed to the renderer
3
+ * whole, standing in for every fixed light past the frame's exact choice.
4
+ *
5
+ * **Whole from the start**, because nothing is baked in the page: `ready` is true on construction
6
+ * and the volume fades in over `fadeSec` from the first `follow`. The lights it summed are the
7
+ * scene's to mark `inLightField`, so the selection knows not to count them twice — a volume carries
8
+ * no list of the lights it came from.
9
+ *
10
+ * **Light and direction side by side along y**, the short axis: a city is 256 samples across and a
11
+ * few dozen up, and a 3D texture promises 256 on every axis, so stacking the two there keeps both
12
+ * inside one texture and one sampler. The shader keeps each lookup inside its own half.
13
+ */
14
+ import type { DenseLightVolume } from './denseField.ts';
15
+ import type { DriftLightVolumes } from './presence.ts';
16
+ export interface WorldLightFieldOptions {
17
+ /** Metres over which exact and summed light crossfade at the shading radius. Two by default. */
18
+ readonly band?: number;
19
+ /** Seconds the volume fades in over; zero behind a loading screen, whose clock may be held. */
20
+ readonly fadeSec?: number;
21
+ }
22
+ export declare class WorldLightField implements DriftLightVolumes {
23
+ readonly ready = true;
24
+ readonly empty = false;
25
+ readonly indexDims: readonly [number, number, number];
26
+ readonly index: Uint32Array<ArrayBuffer>;
27
+ readonly atlasSize: readonly [number, number, number];
28
+ readonly atlas: Uint16Array;
29
+ readonly band: number;
30
+ readonly sampleOrigin: readonly [number, number, number];
31
+ readonly signedSpacing: number;
32
+ /** The summed light's brightness, for a scene that dims all its lamps at once. */
33
+ scale: number;
34
+ private readonly showing;
35
+ constructor(volume: DenseLightVolume, options?: WorldLightFieldOptions);
36
+ get radius(): number;
37
+ get presence(): number;
38
+ get centre(): Float32Array;
39
+ /** Follow the frame's choice, as `LightField.follow` does. */
40
+ follow(complete: number, x: number, y: number, z: number, dtSec: number): void;
41
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * A world's DriftLight: a dense volume baked offline (`bakeDenseField`), handed to the renderer
3
+ * whole, standing in for every fixed light past the frame's exact choice.
4
+ *
5
+ * **Whole from the start**, because nothing is baked in the page: `ready` is true on construction
6
+ * and the volume fades in over `fadeSec` from the first `follow`. The lights it summed are the
7
+ * scene's to mark `inLightField`, so the selection knows not to count them twice — a volume carries
8
+ * no list of the lights it came from.
9
+ *
10
+ * **Light and direction side by side along y**, the short axis: a city is 256 samples across and a
11
+ * few dozen up, and a 3D texture promises 256 on every axis, so stacking the two there keeps both
12
+ * inside one texture and one sampler. The shader keeps each lookup inside its own half.
13
+ */
14
+ import { halfBits } from '../halfFloat.js';
15
+ import { FieldPresence } from './presence.js';
16
+ /** The index a dense volume binds in the sparse one's place: one cell, no brick. */
17
+ const NO_INDEX = new Uint32Array(1);
18
+ export class WorldLightField {
19
+ ready = true;
20
+ empty = false;
21
+ indexDims = [1, 1, 1];
22
+ index = NO_INDEX;
23
+ atlasSize;
24
+ atlas;
25
+ band;
26
+ sampleOrigin;
27
+ signedSpacing;
28
+ /** The summed light's brightness, for a scene that dims all its lamps at once. */
29
+ scale = 1;
30
+ showing;
31
+ constructor(volume, options = {}) {
32
+ const [x, y, z] = volume.dims;
33
+ this.atlasSize = [x, y * 2, z];
34
+ this.atlas = new Uint16Array(x * y * 2 * z * 4);
35
+ for (let k = 0; k < z; k++) {
36
+ for (let j = 0; j < y; j++) {
37
+ for (let i = 0; i < x; i++) {
38
+ const from = (i + x * (j + y * k)) * 4;
39
+ const light = (i + x * (j + 2 * y * k)) * 4;
40
+ const direction = (i + x * (j + y + 2 * y * k)) * 4;
41
+ for (let c = 0; c < 4; c++) {
42
+ this.atlas[light + c] = halfBits(volume.light[from + c]);
43
+ this.atlas[direction + c] = halfBits(volume.direction[from + c]);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ this.band = options.band ?? 2;
49
+ this.sampleOrigin = volume.origin;
50
+ this.signedSpacing = -volume.spacing;
51
+ this.showing = new FieldPresence(Math.max(0, options.fadeSec ?? 0.5));
52
+ }
53
+ get radius() {
54
+ return this.showing.radius;
55
+ }
56
+ get presence() {
57
+ return this.showing.presence;
58
+ }
59
+ get centre() {
60
+ return this.showing.centre;
61
+ }
62
+ /** Follow the frame's choice, as `LightField.follow` does. */
63
+ follow(complete, x, y, z, dtSec) {
64
+ this.showing.follow(true, complete, x, y, z, dtSec);
65
+ }
66
+ }
@@ -0,0 +1,20 @@
1
+ export interface FixtureAtlasLayout {
2
+ readonly width: number;
3
+ readonly height: number;
4
+ /** Rows of cookie tiles at the top: `COOKIE_TILE`, or 0 with none loaded. */
5
+ readonly cookieRows: number;
6
+ /** The first row of the profiles, below the cookies. */
7
+ readonly iesTop: number;
8
+ }
9
+ /** The atlas for `iesRows` rows of profile and `cookieTiles` cookies. */
10
+ export declare function fixtureAtlasLayout(iesRows: number, cookieTiles: number): FixtureAtlasLayout;
11
+ /**
12
+ * The profiles' rows as the atlas holds them: `data` one float a texel, `width` across and `rows`
13
+ * down, spread into the red channel of a four-channel image the atlas's size, the rest ones. The
14
+ * cookie band is left zero for the backend to fill from its images.
15
+ */
16
+ export declare function fixtureAtlasTexels(layout: FixtureAtlasLayout, ies: {
17
+ readonly data: Float32Array;
18
+ readonly width: number;
19
+ readonly height: number;
20
+ }): Float32Array;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * What a light fixture throws, in one texture: its spot cookies in the top band, its photometric
3
+ * profiles in the rows below.
4
+ *
5
+ * **One binding for two tables, because the lit stage had one sampler left.** The cookie atlas and
6
+ * the photometric atlas were two small float textures read with `textureLod`, and the widest lit
7
+ * shader bound fifteen of the sixteen fragment samplers WebGL2 guarantees — so the next sampler had
8
+ * to be found by folding two into one, which is what this is. Both describe a fixture's emission
9
+ * pattern, and both are 128 texels across, which is what lets them share a texture exactly.
10
+ *
11
+ * **Cookies on top, profiles at the bottom, and the order is load-bearing.** A profile's row blend
12
+ * only ever reaches forward — to the next plane, and from the last to the repeat of the first — so
13
+ * past its last row it meets the texture's edge and the clamp, never a cookie. A cookie's own
14
+ * half-texel inset keeps it inside its band on both axes. The shader finds the bands from
15
+ * `textureSize` and `uIesAtlasRows`, so the fold cost no uniform.
16
+ *
17
+ * **What it gives up**: every texel is four half floats where a profile needed one, a few kilobytes
18
+ * a scene; and setting either table rebuilds the whole texture, which a consumer does once per set.
19
+ */
20
+ import { IES_ATLAS_WIDTH } from './iesProfile.js';
21
+ import { COOKIE_TILE } from './lightBudget.js';
22
+ /** The atlas for `iesRows` rows of profile and `cookieTiles` cookies. */
23
+ export function fixtureAtlasLayout(iesRows, cookieTiles) {
24
+ const cookieRows = cookieTiles > 0 ? COOKIE_TILE : 0;
25
+ return {
26
+ width: Math.max(IES_ATLAS_WIDTH, COOKIE_TILE * cookieTiles),
27
+ height: cookieRows + iesRows,
28
+ cookieRows,
29
+ iesTop: cookieRows,
30
+ };
31
+ }
32
+ /**
33
+ * The profiles' rows as the atlas holds them: `data` one float a texel, `width` across and `rows`
34
+ * down, spread into the red channel of a four-channel image the atlas's size, the rest ones. The
35
+ * cookie band is left zero for the backend to fill from its images.
36
+ */
37
+ export function fixtureAtlasTexels(layout, ies) {
38
+ const out = new Float32Array(layout.width * layout.height * 4);
39
+ for (let row = 0; row < ies.height; row++) {
40
+ for (let x = 0; x < ies.width; x++) {
41
+ const at = ((layout.iesTop + row) * layout.width + x) * 4;
42
+ out[at] = ies.data[row * ies.width + x];
43
+ out[at + 1] = 1;
44
+ out[at + 2] = 1;
45
+ out[at + 3] = 1;
46
+ }
47
+ }
48
+ return out;
49
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Glass: how much light a surface lets through, how milky it is, and the colour it takes.
3
+ *
4
+ * **A thin pane, not a volume.** Glass here is a surface that shows the scene behind it — by
5
+ * `transmission`, blurred by `frost`, tinted — keeps its own highlight and reflection, and glows
6
+ * with the lights behind it in proportion to how frosted it is. The one depth it has is the path
7
+ * absorption refraction already applies (`TranslucentMeshOptions.thicknessM`). What it gives up is
8
+ * everything a solid of glass does inside itself: a sculpture or a thick block under-darkens and
9
+ * does not bend light twice. What would make it wrong is a scene whose glass is mostly that.
10
+ *
11
+ * **Zero transmission is not glass**, whatever the frost says: a surface letting nothing through is
12
+ * opaque, and every branch the shader gates on transmission is skipped for it. This module is also
13
+ * the reference the shader's Fresnel is held to (`schlickFresnel`), stated once in TypeScript.
14
+ */
15
+ import type { Vec3 } from '../math/color.ts';
16
+ export interface GlassOptions {
17
+ /** Share of light that passes, 0 (opaque) to 1 (clear). */
18
+ readonly transmission: number;
19
+ /** How milky, 0 (see-through) to 1 (fully diffusing: what is behind is blurred away). */
20
+ readonly frost: number;
21
+ /** The colour light takes through it. White, the default, is clear glass. */
22
+ readonly tint?: Vec3;
23
+ }
24
+ /** A `GlassOptions` clamped and defaulted, in a caller-owned object so a draw allocates nothing. */
25
+ export interface ResolvedGlass {
26
+ transmission: number;
27
+ frost: number;
28
+ tint: [number, number, number];
29
+ }
30
+ /**
31
+ * Resolve `options` into `out`, and say whether it is glass at all.
32
+ *
33
+ * Not glass — absent, or letting nothing through — zeroes `out` and tints it white, so a binder that
34
+ * uploads `out` either way uploads the state that switches the shader's glass branch off.
35
+ */
36
+ export declare function resolveGlass(options: GlassOptions | undefined, out: ResolvedGlass): boolean;
37
+ /**
38
+ * Schlick's Fresnel for glass, `F0 = 0.04`: the share of light a pane reflects at a view angle.
39
+ *
40
+ * `cosView` is clamped at 0.05, the floor the shader's path length already uses, so a silhouette
41
+ * seen exactly edge-on is a very reflective pane rather than a division by zero.
42
+ */
43
+ export declare function schlickFresnel(cosView: number): number;
44
+ /** How much of what is behind a pane is seen through it at a view angle. */
45
+ export declare function glassSeenShare(transmission: number, cosView: number): number;
@@ -0,0 +1,39 @@
1
+ const clamp01 = (value) => Math.min(1, Math.max(0, value));
2
+ /**
3
+ * Resolve `options` into `out`, and say whether it is glass at all.
4
+ *
5
+ * Not glass — absent, or letting nothing through — zeroes `out` and tints it white, so a binder that
6
+ * uploads `out` either way uploads the state that switches the shader's glass branch off.
7
+ */
8
+ export function resolveGlass(options, out) {
9
+ const transmission = clamp01(options?.transmission ?? 0);
10
+ if (options === undefined || transmission <= 0) {
11
+ out.transmission = 0;
12
+ out.frost = 0;
13
+ out.tint[0] = 1;
14
+ out.tint[1] = 1;
15
+ out.tint[2] = 1;
16
+ return false;
17
+ }
18
+ out.transmission = transmission;
19
+ out.frost = clamp01(options.frost);
20
+ const tint = options.tint;
21
+ out.tint[0] = tint === undefined ? 1 : clamp01(tint[0]);
22
+ out.tint[1] = tint === undefined ? 1 : clamp01(tint[1]);
23
+ out.tint[2] = tint === undefined ? 1 : clamp01(tint[2]);
24
+ return true;
25
+ }
26
+ /**
27
+ * Schlick's Fresnel for glass, `F0 = 0.04`: the share of light a pane reflects at a view angle.
28
+ *
29
+ * `cosView` is clamped at 0.05, the floor the shader's path length already uses, so a silhouette
30
+ * seen exactly edge-on is a very reflective pane rather than a division by zero.
31
+ */
32
+ export function schlickFresnel(cosView) {
33
+ const c = Math.min(1, Math.max(0.05, cosView));
34
+ return 0.04 + 0.96 * (1 - c) ** 5;
35
+ }
36
+ /** How much of what is behind a pane is seen through it at a view angle. */
37
+ export function glassSeenShare(transmission, cosView) {
38
+ return transmission * (1 - schlickFresnel(cosView));
39
+ }