@driftengine/core 3.62.0 → 4.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 (428) hide show
  1. package/CHANGELOG.json +122 -0
  2. package/NOTICE +20 -0
  3. package/README.md +1 -1
  4. package/dist/core/loop.js +18 -2
  5. package/dist/core/storage.js +5 -0
  6. package/dist/geometry/meshBuilder.d.ts +1 -1
  7. package/dist/geometry/meshBuilder.js +6 -2
  8. package/dist/index.d.ts +70 -4
  9. package/dist/index.js +123 -2
  10. package/dist/input/input.js +6 -1
  11. package/dist/math/exact.d.ts +13 -3
  12. package/dist/math/exact.js +68 -3
  13. package/dist/math/frustum.d.ts +17 -2
  14. package/dist/math/frustum.js +24 -0
  15. package/dist/render/backend/api.d.ts +34 -1
  16. package/dist/render/backend/api.js +22 -1
  17. package/dist/render/backend/budget.d.ts +7 -6
  18. package/dist/render/backend/budget.js +7 -6
  19. package/dist/render/backend/createRenderer.d.ts +57 -0
  20. package/dist/render/backend/createRenderer.js +51 -2
  21. package/dist/render/backend/materialChanges.d.ts +41 -0
  22. package/dist/render/backend/materialChanges.js +38 -0
  23. package/dist/render/backend/select.js +8 -0
  24. package/dist/render/backend/webgl2/renderer.d.ts +80 -1
  25. package/dist/render/backend/webgl2/renderer.js +208 -10
  26. package/dist/render/backend/webgpu/buffers.d.ts +20 -1
  27. package/dist/render/backend/webgpu/buffers.js +116 -58
  28. package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
  29. package/dist/render/backend/webgpu/fieldCompose.js +396 -0
  30. package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
  31. package/dist/render/backend/webgpu/giFieldPass.js +137 -0
  32. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
  33. package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
  34. package/dist/render/backend/webgpu/linePass.d.ts +4 -14
  35. package/dist/render/backend/webgpu/linePass.js +4 -33
  36. package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
  37. package/dist/render/backend/webgpu/probeBake.js +493 -0
  38. package/dist/render/backend/webgpu/renderer.d.ts +379 -24
  39. package/dist/render/backend/webgpu/renderer.js +1369 -76
  40. package/dist/render/backend/webgpu/scatterPass.js +15 -1
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +47 -14
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +128 -80
  43. package/dist/render/causticsRenderer.d.ts +3 -1
  44. package/dist/render/causticsRenderer.js +4 -1
  45. package/dist/render/depthConvention.d.ts +12 -0
  46. package/dist/render/depthConvention.js +14 -0
  47. package/dist/render/deviceFeatures.d.ts +32 -0
  48. package/dist/render/deviceFeatures.js +41 -0
  49. package/dist/render/fog.d.ts +79 -0
  50. package/dist/render/fog.js +138 -0
  51. package/dist/render/frame/alias.d.ts +30 -0
  52. package/dist/render/frame/alias.js +61 -0
  53. package/dist/render/frame/deps.d.ts +37 -0
  54. package/dist/render/frame/deps.js +122 -0
  55. package/dist/render/frame/flushGraph.d.ts +43 -0
  56. package/dist/render/frame/flushGraph.js +84 -0
  57. package/dist/render/frame/graphSchedule.d.ts +49 -0
  58. package/dist/render/frame/graphSchedule.js +120 -0
  59. package/dist/render/frame/index.d.ts +15 -0
  60. package/dist/render/frame/index.js +8 -0
  61. package/dist/render/frame/lifetime.d.ts +24 -0
  62. package/dist/render/frame/lifetime.js +36 -0
  63. package/dist/render/frame/passTimings.d.ts +42 -0
  64. package/dist/render/frame/passTimings.js +38 -0
  65. package/dist/render/frame/validate.d.ts +15 -0
  66. package/dist/render/frame/validate.js +22 -0
  67. package/dist/render/frame/virtual.d.ts +44 -0
  68. package/dist/render/frame/virtual.js +61 -0
  69. package/dist/render/gi/chain.d.ts +82 -0
  70. package/dist/render/gi/chain.js +135 -0
  71. package/dist/render/gi/denoise.d.ts +114 -0
  72. package/dist/render/gi/denoise.js +169 -0
  73. package/dist/render/gi/fieldScene.d.ts +125 -0
  74. package/dist/render/gi/fieldScene.js +219 -0
  75. package/dist/render/gi/globalField.d.ts +157 -0
  76. package/dist/render/gi/globalField.js +373 -0
  77. package/dist/render/gi/probeGridFit.d.ts +24 -0
  78. package/dist/render/gi/probeGridFit.js +71 -0
  79. package/dist/render/gi/probeTrace.d.ts +82 -0
  80. package/dist/render/gi/probeTrace.js +128 -0
  81. package/dist/render/gi/probeVolume.d.ts +136 -0
  82. package/dist/render/gi/probeVolume.js +191 -0
  83. package/dist/render/gi/reflection.d.ts +73 -0
  84. package/dist/render/gi/reflection.js +109 -0
  85. package/dist/render/gi/traceField.d.ts +91 -0
  86. package/dist/render/gi/traceField.js +129 -0
  87. package/dist/render/gi/traceScreen.d.ts +68 -0
  88. package/dist/render/gi/traceScreen.js +82 -0
  89. package/dist/render/gpuScene.d.ts +38 -0
  90. package/dist/render/gpuScene.js +41 -0
  91. package/dist/render/gpudriven/alphaTest.d.ts +28 -0
  92. package/dist/render/gpudriven/alphaTest.js +32 -0
  93. package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
  94. package/dist/render/gpudriven/clusterUpload.js +75 -0
  95. package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
  96. package/dist/render/gpudriven/clusterWorld.js +155 -0
  97. package/dist/render/gpudriven/compact.d.ts +51 -0
  98. package/dist/render/gpudriven/compact.js +68 -0
  99. package/dist/render/gpudriven/cullClusters.d.ts +129 -0
  100. package/dist/render/gpudriven/cullClusters.js +237 -0
  101. package/dist/render/gpudriven/decodeTables.d.ts +64 -0
  102. package/dist/render/gpudriven/decodeTables.js +219 -0
  103. package/dist/render/gpudriven/frustum.d.ts +25 -0
  104. package/dist/render/gpudriven/frustum.js +59 -0
  105. package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
  106. package/dist/render/gpudriven/geometryHarness.js +31 -0
  107. package/dist/render/gpudriven/hzb.d.ts +60 -0
  108. package/dist/render/gpudriven/hzb.js +97 -0
  109. package/dist/render/gpudriven/ibl.d.ts +85 -0
  110. package/dist/render/gpudriven/ibl.js +129 -0
  111. package/dist/render/gpudriven/indirect.d.ts +67 -0
  112. package/dist/render/gpudriven/indirect.js +77 -0
  113. package/dist/render/gpudriven/instances.d.ts +43 -0
  114. package/dist/render/gpudriven/instances.js +172 -0
  115. package/dist/render/gpudriven/lit.d.ts +165 -0
  116. package/dist/render/gpudriven/lit.js +165 -0
  117. package/dist/render/gpudriven/lodCut.d.ts +28 -0
  118. package/dist/render/gpudriven/lodCut.js +38 -0
  119. package/dist/render/gpudriven/materialBin.d.ts +60 -0
  120. package/dist/render/gpudriven/materialBin.js +108 -0
  121. package/dist/render/gpudriven/materialTable.d.ts +88 -0
  122. package/dist/render/gpudriven/materialTable.js +115 -0
  123. package/dist/render/gpudriven/pipeline.d.ts +135 -0
  124. package/dist/render/gpudriven/pipeline.js +237 -0
  125. package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
  126. package/dist/render/gpudriven/rangeAlloc.js +112 -0
  127. package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
  128. package/dist/render/gpudriven/sceneUpload.js +115 -0
  129. package/dist/render/gpudriven/shadeBins.d.ts +92 -0
  130. package/dist/render/gpudriven/shadeBins.js +182 -0
  131. package/dist/render/gpudriven/shadow.d.ts +136 -0
  132. package/dist/render/gpudriven/shadow.js +224 -0
  133. package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
  134. package/dist/render/gpudriven/shadowCamera.js +345 -0
  135. package/dist/render/gpudriven/streamScene.d.ts +194 -0
  136. package/dist/render/gpudriven/streamScene.js +459 -0
  137. package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
  138. package/dist/render/gpudriven/surfaceFrame.js +51 -0
  139. package/dist/render/gpudriven/twoPhase.d.ts +122 -0
  140. package/dist/render/gpudriven/twoPhase.js +160 -0
  141. package/dist/render/gpudriven/visbuffer.d.ts +25 -0
  142. package/dist/render/gpudriven/visbuffer.js +37 -0
  143. package/dist/render/halfFloat.d.ts +10 -0
  144. package/dist/render/halfFloat.js +23 -4
  145. package/dist/render/inference/attentionTiled.d.ts +27 -0
  146. package/dist/render/inference/attentionTiled.js +117 -0
  147. package/dist/render/inference/denseKernels.d.ts +29 -0
  148. package/dist/render/inference/denseKernels.js +312 -0
  149. package/dist/render/inference/device.d.ts +21 -0
  150. package/dist/render/inference/device.js +32 -0
  151. package/dist/render/inference/deviceGraph.d.ts +34 -0
  152. package/dist/render/inference/deviceGraph.js +17 -0
  153. package/dist/render/inference/fuse.d.ts +15 -0
  154. package/dist/render/inference/fuse.js +37 -0
  155. package/dist/render/inference/kernelKit.d.ts +52 -0
  156. package/dist/render/inference/kernelKit.js +70 -0
  157. package/dist/render/inference/kernels.d.ts +3 -0
  158. package/dist/render/inference/kernels.js +11 -0
  159. package/dist/render/inference/plan.d.ts +13 -0
  160. package/dist/render/inference/plan.js +18 -0
  161. package/dist/render/inference/reuse.d.ts +39 -0
  162. package/dist/render/inference/reuse.js +65 -0
  163. package/dist/render/inference/runner.d.ts +31 -0
  164. package/dist/render/inference/runner.js +175 -0
  165. package/dist/render/inference/schedule.d.ts +39 -0
  166. package/dist/render/inference/schedule.js +59 -0
  167. package/dist/render/inference/shapeKernels.d.ts +10 -0
  168. package/dist/render/inference/shapeKernels.js +80 -0
  169. package/dist/render/inference/spatialKernels.d.ts +21 -0
  170. package/dist/render/inference/spatialKernels.js +179 -0
  171. package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
  172. package/dist/render/inference/splatRaster.wgsl.js +337 -0
  173. package/dist/render/inference/splatTiles.d.ts +79 -0
  174. package/dist/render/inference/splatTiles.js +161 -0
  175. package/dist/render/lineBatch.d.ts +2 -5
  176. package/dist/render/lineBatch.js +4 -24
  177. package/dist/render/mesh.d.ts +4 -0
  178. package/dist/render/mesh.js +45 -1
  179. package/dist/render/pass.d.ts +145 -8
  180. package/dist/render/prefilterEnvMap.d.ts +22 -0
  181. package/dist/render/prefilterEnvMap.js +31 -0
  182. package/dist/render/readback.d.ts +30 -0
  183. package/dist/render/readback.js +47 -0
  184. package/dist/render/recon/clamp.d.ts +36 -0
  185. package/dist/render/recon/clamp.js +92 -0
  186. package/dist/render/recon/disocclusion.d.ts +60 -0
  187. package/dist/render/recon/disocclusion.js +109 -0
  188. package/dist/render/recon/frameGen.d.ts +81 -0
  189. package/dist/render/recon/frameGen.js +150 -0
  190. package/dist/render/recon/frameSizes.d.ts +34 -0
  191. package/dist/render/recon/frameSizes.js +58 -0
  192. package/dist/render/recon/jitter.d.ts +60 -0
  193. package/dist/render/recon/jitter.js +118 -0
  194. package/dist/render/recon/motionVectors.d.ts +55 -0
  195. package/dist/render/recon/motionVectors.js +107 -0
  196. package/dist/render/recon/reproject.d.ts +41 -0
  197. package/dist/render/recon/reproject.js +131 -0
  198. package/dist/render/recon/resolve.d.ts +90 -0
  199. package/dist/render/recon/resolve.js +313 -0
  200. package/dist/render/recon/tier.d.ts +24 -0
  201. package/dist/render/recon/tier.js +8 -0
  202. package/dist/render/reflectionProbe.d.ts +26 -0
  203. package/dist/render/reflectionProbe.js +33 -0
  204. package/dist/render/renderQuality.d.ts +69 -0
  205. package/dist/render/renderQuality.js +22 -0
  206. package/dist/render/screenSpaceReflection.d.ts +23 -0
  207. package/dist/render/screenSpaceReflection.js +22 -3
  208. package/dist/render/sdfFont.js +2 -0
  209. package/dist/render/segmentQuads.d.ts +13 -0
  210. package/dist/render/segmentQuads.js +32 -0
  211. package/dist/render/shaders/flat/lobes.d.ts +27 -0
  212. package/dist/render/shaders/flat/lobes.js +33 -3
  213. package/dist/render/shaders/flat/preamble.d.ts +7 -0
  214. package/dist/render/shaders/flat/preamble.js +24 -1
  215. package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
  216. package/dist/render/shaders/flat/probeGrid.js +71 -2
  217. package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
  218. package/dist/render/shaders/generated/flat.wgsl.js +64 -24
  219. package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
  220. package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
  221. package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
  222. package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
  223. package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
  224. package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
  225. package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
  226. package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
  227. package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
  228. package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
  229. package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
  230. package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
  231. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
  232. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
  233. package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
  234. package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
  235. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
  236. package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
  237. package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
  238. package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
  239. package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
  240. package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
  241. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
  242. package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
  243. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
  244. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
  245. package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
  246. package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
  247. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
  248. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
  249. package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
  250. package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
  251. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
  252. package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
  253. package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
  254. package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
  255. package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
  256. package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
  257. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
  258. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
  259. package/dist/render/shaders/network.wgsl.d.ts +101 -0
  260. package/dist/render/shaders/network.wgsl.js +206 -0
  261. package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
  262. package/dist/render/shaders/recon/motion.wgsl.js +96 -0
  263. package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
  264. package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
  265. package/dist/render/temporalAa.js +8 -33
  266. package/dist/render/textLayout.js +12 -6
  267. package/dist/render/textRenderer.d.ts +3 -1
  268. package/dist/render/textRenderer.js +4 -1
  269. package/dist/render/translucentQueue.d.ts +6 -0
  270. package/dist/render/translucentQueue.js +11 -0
  271. package/dist/render/windStreakRenderer.d.ts +2 -1
  272. package/dist/render/windStreakRenderer.js +3 -1
  273. package/dist/world/cell.d.ts +63 -0
  274. package/dist/world/cell.js +181 -0
  275. package/dist/world/cellStream.d.ts +103 -0
  276. package/dist/world/cellStream.js +198 -0
  277. package/dist/world/freeze.d.ts +81 -0
  278. package/dist/world/freeze.js +114 -0
  279. package/dist/world/rebase.d.ts +29 -0
  280. package/dist/world/rebase.js +41 -0
  281. package/package.json +8 -3
  282. package/scripts/frames.mjs +92 -0
  283. package/scripts/gpuCompute.mjs +323 -0
  284. package/scripts/png.d.mts +16 -0
  285. package/scripts/png.mjs +25 -0
  286. package/src/core/loop.ts +19 -2
  287. package/src/core/storage.ts +5 -0
  288. package/src/geometry/meshBuilder.ts +5 -1
  289. package/src/index.ts +261 -4
  290. package/src/input/input.ts +6 -1
  291. package/src/math/exact.ts +69 -3
  292. package/src/math/frustum.ts +52 -3
  293. package/src/render/backend/api.ts +42 -1
  294. package/src/render/backend/budget.ts +7 -6
  295. package/src/render/backend/createRenderer.ts +92 -3
  296. package/src/render/backend/materialChanges.ts +54 -0
  297. package/src/render/backend/select.ts +8 -0
  298. package/src/render/backend/webgl2/renderer.ts +211 -9
  299. package/src/render/backend/webgpu/buffers.ts +182 -69
  300. package/src/render/backend/webgpu/fieldCompose.ts +482 -0
  301. package/src/render/backend/webgpu/giFieldPass.ts +200 -0
  302. package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
  303. package/src/render/backend/webgpu/linePass.ts +4 -39
  304. package/src/render/backend/webgpu/probeBake.ts +588 -0
  305. package/src/render/backend/webgpu/renderer.ts +1481 -76
  306. package/src/render/backend/webgpu/scatterPass.ts +15 -1
  307. package/src/render/backend/webgpu/surfaceTexturePass.ts +158 -88
  308. package/src/render/causticsRenderer.ts +5 -2
  309. package/src/render/depthConvention.ts +15 -0
  310. package/src/render/deviceFeatures.ts +77 -0
  311. package/src/render/fog.ts +185 -0
  312. package/src/render/frame/alias.ts +97 -0
  313. package/src/render/frame/deps.ts +155 -0
  314. package/src/render/frame/flushGraph.ts +135 -0
  315. package/src/render/frame/graphSchedule.ts +170 -0
  316. package/src/render/frame/index.ts +52 -0
  317. package/src/render/frame/lifetime.ts +61 -0
  318. package/src/render/frame/passTimings.ts +69 -0
  319. package/src/render/frame/validate.ts +40 -0
  320. package/src/render/frame/virtual.ts +91 -0
  321. package/src/render/gi/chain.ts +225 -0
  322. package/src/render/gi/denoise.ts +276 -0
  323. package/src/render/gi/fieldScene.ts +264 -0
  324. package/src/render/gi/globalField.ts +543 -0
  325. package/src/render/gi/probeGridFit.ts +89 -0
  326. package/src/render/gi/probeTrace.ts +143 -0
  327. package/src/render/gi/probeVolume.ts +288 -0
  328. package/src/render/gi/reflection.ts +163 -0
  329. package/src/render/gi/traceField.ts +223 -0
  330. package/src/render/gi/traceScreen.ts +106 -0
  331. package/src/render/gpuScene.ts +80 -0
  332. package/src/render/gpudriven/alphaTest.ts +33 -0
  333. package/src/render/gpudriven/clusterUpload.ts +123 -0
  334. package/src/render/gpudriven/clusterWorld.ts +239 -0
  335. package/src/render/gpudriven/compact.ts +80 -0
  336. package/src/render/gpudriven/cullClusters.ts +333 -0
  337. package/src/render/gpudriven/decodeTables.ts +332 -0
  338. package/src/render/gpudriven/frustum.ts +70 -0
  339. package/src/render/gpudriven/geometryHarness.ts +51 -0
  340. package/src/render/gpudriven/hzb.ts +113 -0
  341. package/src/render/gpudriven/ibl.ts +147 -0
  342. package/src/render/gpudriven/indirect.ts +89 -0
  343. package/src/render/gpudriven/instances.ts +234 -0
  344. package/src/render/gpudriven/lit.ts +329 -0
  345. package/src/render/gpudriven/lodCut.ts +62 -0
  346. package/src/render/gpudriven/materialBin.ts +131 -0
  347. package/src/render/gpudriven/materialTable.ts +164 -0
  348. package/src/render/gpudriven/pipeline.ts +336 -0
  349. package/src/render/gpudriven/rangeAlloc.ts +122 -0
  350. package/src/render/gpudriven/sceneUpload.ts +173 -0
  351. package/src/render/gpudriven/shadeBins.ts +222 -0
  352. package/src/render/gpudriven/shadow.ts +285 -0
  353. package/src/render/gpudriven/shadowCamera.ts +406 -0
  354. package/src/render/gpudriven/streamScene.ts +575 -0
  355. package/src/render/gpudriven/surfaceFrame.ts +68 -0
  356. package/src/render/gpudriven/twoPhase.ts +214 -0
  357. package/src/render/gpudriven/visbuffer.ts +41 -0
  358. package/src/render/halfFloat.ts +22 -4
  359. package/src/render/inference/attentionTiled.ts +132 -0
  360. package/src/render/inference/denseKernels.ts +369 -0
  361. package/src/render/inference/device.ts +44 -0
  362. package/src/render/inference/deviceGraph.ts +39 -0
  363. package/src/render/inference/fuse.ts +47 -0
  364. package/src/render/inference/kernelKit.ts +120 -0
  365. package/src/render/inference/kernels.ts +18 -0
  366. package/src/render/inference/plan.ts +26 -0
  367. package/src/render/inference/reuse.ts +79 -0
  368. package/src/render/inference/runner.ts +204 -0
  369. package/src/render/inference/schedule.ts +80 -0
  370. package/src/render/inference/shapeKernels.ts +112 -0
  371. package/src/render/inference/spatialKernels.ts +220 -0
  372. package/src/render/inference/splatRaster.wgsl.ts +348 -0
  373. package/src/render/inference/splatTiles.ts +186 -0
  374. package/src/render/lineBatch.ts +4 -24
  375. package/src/render/mesh.ts +46 -1
  376. package/src/render/pass.ts +155 -10
  377. package/src/render/prefilterEnvMap.ts +33 -0
  378. package/src/render/readback.ts +73 -0
  379. package/src/render/recon/clamp.ts +113 -0
  380. package/src/render/recon/disocclusion.ts +143 -0
  381. package/src/render/recon/frameGen.ts +191 -0
  382. package/src/render/recon/frameSizes.ts +64 -0
  383. package/src/render/recon/jitter.ts +126 -0
  384. package/src/render/recon/motionVectors.ts +138 -0
  385. package/src/render/recon/reproject.ts +179 -0
  386. package/src/render/recon/resolve.ts +444 -0
  387. package/src/render/recon/tier.ts +31 -0
  388. package/src/render/reflectionProbe.ts +34 -0
  389. package/src/render/renderQuality.ts +87 -0
  390. package/src/render/screenSpaceReflection.ts +37 -3
  391. package/src/render/sdfFont.ts +2 -0
  392. package/src/render/segmentQuads.ts +39 -0
  393. package/src/render/shaders/flat/lobes.ts +35 -3
  394. package/src/render/shaders/flat/preamble.ts +25 -1
  395. package/src/render/shaders/flat/probeGrid.ts +77 -2
  396. package/src/render/shaders/generated/flat.wgsl.ts +64 -24
  397. package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
  398. package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
  399. package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
  400. package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
  401. package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
  402. package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
  403. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
  404. package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
  405. package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
  406. package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
  407. package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
  408. package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
  409. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
  410. package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
  411. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
  412. package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
  413. package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
  414. package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
  415. package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
  416. package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
  417. package/src/render/shaders/network.wgsl.ts +238 -0
  418. package/src/render/shaders/recon/motion.wgsl.ts +101 -0
  419. package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
  420. package/src/render/temporalAa.ts +9 -33
  421. package/src/render/textLayout.ts +12 -6
  422. package/src/render/textRenderer.ts +5 -2
  423. package/src/render/translucentQueue.ts +11 -0
  424. package/src/render/windStreakRenderer.ts +4 -2
  425. package/src/world/cell.ts +231 -0
  426. package/src/world/cellStream.ts +278 -0
  427. package/src/world/freeze.ts +157 -0
  428. package/src/world/rebase.ts +56 -0
@@ -0,0 +1,543 @@
1
+ /** The world near the camera as one distance field, assembled from the objects standing in it. */
2
+
3
+ import { mat4 } from 'gl-matrix';
4
+
5
+ import type { ReadonlyMat4, ReadonlyVec3 } from 'gl-matrix';
6
+
7
+ import type { Vec3 } from '../../math/color.ts';
8
+
9
+ /**
10
+ * A baked object field, as this package can describe one.
11
+ *
12
+ * **Structural rather than imported**, because `@driftengine/core` does not depend on
13
+ * `@driftengine/assets` and must not start: the baker is a build step and this is a frame. What
14
+ * `bakeObjectSdf` returns satisfies this shape, and so does what `readSdfv` hands back from a
15
+ * container. That is the arrangement `Terrain` already has with `Heightfield` across the physics
16
+ * boundary and `DTEX` has with `@driftengine/texture` across the container one.
17
+ */
18
+ export interface FieldSource {
19
+ /** Distance in metres, negative inside. `x` fastest, then `y`, then `z`. */
20
+ readonly field: Float32Array;
21
+ readonly dims: readonly [number, number, number];
22
+ /** The field's own extent in **object** space, `[minX, minY, minZ, maxX, maxY, maxZ]`. */
23
+ readonly bounds: Float32Array;
24
+ }
25
+
26
+ export interface GlobalFieldInstance {
27
+ readonly source: FieldSource;
28
+ /**
29
+ * What colour this instance's surface is, or white where it did not say.
30
+ *
31
+ * On the instance rather than the source, because the same baked field is placed many times and
32
+ * two pillars cut from one mould may be painted differently. Optional so that every caller
33
+ * written before the trace needed a colour is unchanged.
34
+ */
35
+ readonly albedo?: Vec3;
36
+ /**
37
+ * World from object.
38
+ *
39
+ * **Uniform scale only, and `composeGlobalField` refuses the rest rather than approximating.**
40
+ * A distance is not preserved by a non-uniform scale: the factor to correct by depends on the
41
+ * direction to the nearest surface, which is exactly what a distance field does not record. An
42
+ * instance squashed on one axis and sampled anyway produces something that is a distance in no
43
+ * direction and looks like one in every slice.
44
+ */
45
+ readonly transform: ReadonlyMat4;
46
+ }
47
+
48
+ /** One level of the clipmap: a grid the composer fills and `sampleGlobalField` reads. */
49
+ export interface GlobalFieldCascade {
50
+ readonly field: Float32Array;
51
+ /**
52
+ * The colour of whatever surface won the union at each sample, three floats a sample.
53
+ *
54
+ * **A union loses which instance won, and a ray that lands on a wall needs to know what colour
55
+ * it is.** `demo/dev/bounce.html` is what made this necessary: a trace whose radiance came from
56
+ * a rasterised capture could not follow a wall that changed colour, and one that read the probe
57
+ * volume's irradiance returned the same answer for a red wall and a white one — irradiance is
58
+ * light arriving, and what leaves a surface is that times its albedo.
59
+ *
60
+ * **White where no instance reached**, rather than black: the colour of a sample no ray can hit
61
+ * is never read, and black would turn a read that did happen into light quietly vanishing
62
+ * instead of a picture that is obviously wrong.
63
+ */
64
+ readonly albedo: Float32Array;
65
+ readonly dims: readonly [number, number, number];
66
+ /** Where this cascade stands, written by `composeGlobalField`. Six floats. */
67
+ readonly bounds: Float32Array;
68
+ /** Metres between samples, the same on all three axes. Written by `composeGlobalField`. */
69
+ step: number;
70
+ }
71
+
72
+ /**
73
+ * The composed world, as nested cascades centred on the camera.
74
+ *
75
+ * **A clipmap and not a sparse brick tree, which is a smaller claim than the plan made and is
76
+ * what is here.** Fine near the viewer and coarse further out is the requirement, and nesting
77
+ * grids of equal sample count at doubling extents meets it exactly: cascade 0 resolves a room and
78
+ * cascade 3 resolves a district, at one allocation each and no indirection to traverse. What a
79
+ * brick tree buys on top is *memory* — skipping the volume no surface is near — and nothing yet
80
+ * measured says this needs it. `traceField.ts` will say, because it is the thing that pays.
81
+ */
82
+ export interface GlobalField {
83
+ /** Finest first. Each is twice the reach and twice the step of the one before it. */
84
+ readonly cascades: readonly GlobalFieldCascade[];
85
+ }
86
+
87
+ /**
88
+ * How much of a cascade's extent, at each face, fades into the cascade outside it.
89
+ *
90
+ * **A hard switch between cascades is worse than either cascade's own error.** An error that is
91
+ * wrong everywhere reads as a coarse solution; one that changes abruptly along a surface reads as
92
+ * a crack, and the crack moves with the camera because the boundary does. A tenth of the extent
93
+ * is wide enough that the fade is below a voxel per step and narrow enough that the inner
94
+ * cascade's resolution is what most of the inner cascade actually delivers.
95
+ */
96
+ export const GLOBAL_FIELD_BLEND = 0.1;
97
+
98
+ /** Allocate a field. Called once; `composeGlobalField` fills it and allocates nothing. */
99
+ export function createGlobalField(resolution: number, cascades: number): GlobalField {
100
+ const samples = Math.trunc(resolution);
101
+ if (!(samples >= 2)) {
102
+ throw new Error(
103
+ `createGlobalField: resolution is ${String(resolution)} and the minimum is 2. One sample on ` +
104
+ 'an axis has nothing to interpolate between.',
105
+ );
106
+ }
107
+ const levels = Math.trunc(cascades);
108
+ if (!(levels >= 1)) {
109
+ throw new Error(
110
+ `createGlobalField: cascade count is ${String(cascades)} and the minimum is 1.`,
111
+ );
112
+ }
113
+
114
+ const out: GlobalFieldCascade[] = [];
115
+ for (let level = 0; level < levels; level++) {
116
+ out.push({
117
+ field: new Float32Array(samples ** 3),
118
+ albedo: new Float32Array(samples ** 3 * 3),
119
+ dims: [samples, samples, samples],
120
+ bounds: new Float32Array(6),
121
+ step: 0,
122
+ });
123
+ }
124
+ return { cascades: out };
125
+ }
126
+
127
+ /**
128
+ * Put one cascade where the camera is, and answer its half-extent.
129
+ *
130
+ * **Exported because the compute path needs the same placement and two spellings drift.** A pass
131
+ * filling these on a device has to agree with this about where every cascade stands, to the last
132
+ * bit: disagree by a fraction of a step and the fade between cascades lands somewhere different on
133
+ * each side, which is a seam that appears only when both paths are compared.
134
+ *
135
+ * The snapping is the decision. See `composeGlobalField` for why a grid centred on the exact camera
136
+ * position makes the whole field shimmer.
137
+ */
138
+ export function placeCascade(
139
+ cascade: GlobalFieldCascade,
140
+ level: number,
141
+ radius: number,
142
+ cameraPos: ReadonlyVec3,
143
+ ): number {
144
+ const half = radius * 2 ** level;
145
+ const step = (2 * half) / ((cascade.dims[0] as number) - 1);
146
+ cascade.step = step;
147
+ for (let axis = 0; axis < 3; axis++) {
148
+ const centre = Math.round((cameraPos[axis] as number) / step) * step;
149
+ cascade.bounds[axis] = centre - half;
150
+ cascade.bounds[axis + 3] = centre + half;
151
+ }
152
+ return half;
153
+ }
154
+
155
+ /** Scratch the composer reuses, because it runs once a frame and the rule about that is absolute. */
156
+ const INVERSE = new Float32Array(16);
157
+ const OBJECT = new Float32Array(3);
158
+ /** The nearest point of a source's box to the sample, for the bound outside it. */
159
+ const CLAMPED = new Float32Array(3);
160
+
161
+ /**
162
+ * Fill every cascade from the instances standing near the camera.
163
+ *
164
+ * `radius` is the innermost cascade's half-extent in metres; each cascade outside it reaches twice
165
+ * as far at the same sample count.
166
+ *
167
+ * **Every cascade's centre is snapped to a multiple of its own step, and that is the decision this
168
+ * function exists to make.** A grid centred on the exact camera position resamples the entire
169
+ * world at a new sub-pixel offset every frame, so every value changes slightly every frame and the
170
+ * indirect light shimmers — worst on a slow camera, which is exactly when a viewer is looking at
171
+ * it. Snapped, a camera move is either nothing at all or a whole voxel, and a whole voxel leaves
172
+ * every sample the two grids share bit-identical.
173
+ *
174
+ * **A point no instance is near takes the cascade's own reach rather than infinity.** That is an
175
+ * underestimate of the true distance, which is the safe direction: a march that steps by it takes
176
+ * a shorter step than it could and converges anyway, where an overestimate steps through the wall
177
+ * it was meant to stop at.
178
+ *
179
+ * **This is the reference, and it visits every sample of every cascade for every instance that is
180
+ * not culled outright.** `scripts/gpu-parity.mjs` is the pattern the WGSL will be checked against,
181
+ * and the WGSL is where the brick culling belongs — a CPU implementation that was already
182
+ * accelerated would be a second thing to be wrong rather than the thing the first is checked by.
183
+ */
184
+ export function composeGlobalField(
185
+ instances: readonly GlobalFieldInstance[],
186
+ cameraPos: ReadonlyVec3,
187
+ radius: number,
188
+ out: GlobalField,
189
+ ): void {
190
+ if (!(radius > 0)) {
191
+ throw new Error(`composeGlobalField: radius is ${String(radius)} and must be positive.`);
192
+ }
193
+
194
+ for (const instance of instances) {
195
+ assertUniformScale(instance.transform);
196
+ assertCubicVoxels(instance.source);
197
+ }
198
+
199
+ for (let level = 0; level < out.cascades.length; level++) {
200
+ const cascade = out.cascades[level] as GlobalFieldCascade;
201
+ const half = placeCascade(cascade, level, radius, cameraPos);
202
+ const [nx, ny, nz] = cascade.dims;
203
+ const step = cascade.step;
204
+
205
+ /* The reach, which is both the initial value and the distance past which an instance cannot
206
+ change one — so it is also what culls an instance whose box is further away than that. */
207
+ cascade.field.fill(half);
208
+ /* White, so a sample no instance reached is neutral. See `GlobalFieldCascade.albedo`. */
209
+ cascade.albedo.fill(1);
210
+
211
+ const ox = cascade.bounds[0] as number;
212
+ const oy = cascade.bounds[1] as number;
213
+ const oz = cascade.bounds[2] as number;
214
+
215
+ for (const instance of instances) {
216
+ const albedo = instance.albedo;
217
+ const ar = albedo === undefined ? 1 : (albedo[0] ?? 1);
218
+ const ag = albedo === undefined ? 1 : (albedo[1] ?? 1);
219
+ const ab = albedo === undefined ? 1 : (albedo[2] ?? 1);
220
+ const scale = uniformScaleOf(instance.transform);
221
+ if (mat4.invert(INVERSE, instance.transform) === null) continue;
222
+ /*
223
+ * **An instance further from this cascade than the cascade's own reach cannot change a
224
+ * single sample in it**, because every sample already holds the reach and the minimum keeps
225
+ * the smaller. So this is a pure speed decision and no test can see it: 64 instances over a
226
+ * kilometre with four of them near the camera, four cascades of 33, compose in **19.4 ms
227
+ * with this line and 150.2 ms without it**, to bit-identical output. Recorded as a
228
+ * measurement for that reason — `sdf.ts`'s `NEIGHBOURS` is the same situation arriving from
229
+ * the other direction, where the measurement said the extra work bought nothing.
230
+ */
231
+ if (
232
+ boxDistance(
233
+ instance,
234
+ INVERSE,
235
+ scale,
236
+ ox,
237
+ oy,
238
+ oz,
239
+ ox + 2 * half,
240
+ oy + 2 * half,
241
+ oz + 2 * half,
242
+ ) > half
243
+ ) {
244
+ continue;
245
+ }
246
+
247
+ for (let iz = 0; iz < nz; iz++) {
248
+ const wz = oz + iz * step;
249
+ for (let iy = 0; iy < ny; iy++) {
250
+ const wy = oy + iy * step;
251
+ for (let ix = 0; ix < nx; ix++) {
252
+ const index = ix + nx * (iy + ny * iz);
253
+ const current = cascade.field[index] as number;
254
+ const distance = sampleInstance(instance, INVERSE, scale, ox + ix * step, wy, wz);
255
+ /* **The minimum, because the union of two solids is the nearer surface.** A maximum
256
+ is the intersection and carves a groove along the seam of every wall that meets
257
+ another wall, with the sign right on both sides of it. */
258
+ /* **Written only where the instance won**, which is what makes the colour the
259
+ nearest surface's rather than the last one's. A composer that wrote it beside the
260
+ comparison would paint the whole field with whichever instance came last. */
261
+ if (distance < current) {
262
+ cascade.field[index] = distance;
263
+ cascade.albedo[index * 3] = ar;
264
+ cascade.albedo[index * 3 + 1] = ag;
265
+ cascade.albedo[index * 3 + 2] = ab;
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * The field at a world point, from the finest cascade that holds it.
276
+ *
277
+ * A point past every cascade reads the outermost one clamped to its edge, rather than a sentinel:
278
+ * a caller marching outward wants a number it can step by, and the outermost cascade's own reach
279
+ * is the honest one.
280
+ */
281
+ export function sampleGlobalField(field: GlobalField, x: number, y: number, z: number): number {
282
+ const last = field.cascades.length - 1;
283
+ for (let level = 0; level <= last; level++) {
284
+ const cascade = field.cascades[level] as GlobalFieldCascade;
285
+ const inside = insideBy(cascade, x, y, z);
286
+ if (inside <= 0) continue;
287
+ const value = trilinear(cascade, x, y, z);
288
+ if (level === last) return value;
289
+ const band =
290
+ GLOBAL_FIELD_BLEND * ((cascade.bounds[3] as number) - (cascade.bounds[0] as number));
291
+ if (inside >= band) return value;
292
+ const outer = trilinear(field.cascades[level + 1] as GlobalFieldCascade, x, y, z);
293
+ const weight = inside / band;
294
+ return outer + (value - outer) * weight;
295
+ }
296
+ return trilinear(field.cascades[last] as GlobalFieldCascade, x, y, z);
297
+ }
298
+
299
+ /** How far inside a cascade a point is, measured to the nearest face. Negative means outside. */
300
+ function insideBy(cascade: GlobalFieldCascade, x: number, y: number, z: number): number {
301
+ const p = [x, y, z];
302
+ let least = Infinity;
303
+ for (let axis = 0; axis < 3; axis++) {
304
+ const min = cascade.bounds[axis] as number;
305
+ const max = cascade.bounds[axis + 3] as number;
306
+ least = Math.min(least, (p[axis] as number) - min, max - (p[axis] as number));
307
+ }
308
+ return least;
309
+ }
310
+
311
+ /**
312
+ * The distance one instance reports at a world point.
313
+ *
314
+ * **The world point is carried into object space through the inverse**, which is the transform
315
+ * every implementation gets wrong first — and a sphere cannot see the mistake, because a sphere is
316
+ * invariant under every rotation. Using the placement matrix itself puts the object at the mirror
317
+ * of where it was asked to stand.
318
+ *
319
+ * **Outside the source's own box, the distance to the box is a bound and on its own it is a
320
+ * useless one.** Every surface the field describes is inside the box, so nothing can be nearer
321
+ * than the box is — true, and just outside the box it says *zero*, which is an underestimate of a
322
+ * distance that is really most of a metre. Safe for a march in the sense that it never steps
323
+ * through anything, and fatal in practice: a sphere trace stops where the field falls under its
324
+ * epsilon, so a shell of near-zero around every instance's bounding box is a **phantom surface**.
325
+ * It draws as dark fins radiating from every object, which is how it was found — by looking at
326
+ * `demo/giFieldRig.ts`, after `globalField.test.ts`'s "never overestimates" passed it happily,
327
+ * zero being an underestimate of everything.
328
+ *
329
+ * So the bound is taken against the field's own value at the nearest point of the box as well:
330
+ * `d(q) >= d(c) - |q - c|` by the triangle inequality, and `d(q) >= |q - c|` because the surface
331
+ * is inside. The larger of the two is still an underestimate and is the useful one — it reports
332
+ * the real clearance just outside the box and falls back to the box distance far away.
333
+ */
334
+ function sampleInstance(
335
+ instance: GlobalFieldInstance,
336
+ inverse: Float32Array,
337
+ scale: number,
338
+ x: number,
339
+ y: number,
340
+ z: number,
341
+ ): number {
342
+ const source = instance.source;
343
+ OBJECT[0] =
344
+ (inverse[0] as number) * x +
345
+ (inverse[4] as number) * y +
346
+ (inverse[8] as number) * z +
347
+ (inverse[12] as number);
348
+ OBJECT[1] =
349
+ (inverse[1] as number) * x +
350
+ (inverse[5] as number) * y +
351
+ (inverse[9] as number) * z +
352
+ (inverse[13] as number);
353
+ OBJECT[2] =
354
+ (inverse[2] as number) * x +
355
+ (inverse[6] as number) * y +
356
+ (inverse[10] as number) * z +
357
+ (inverse[14] as number);
358
+
359
+ let outsideSq = 0;
360
+ for (let axis = 0; axis < 3; axis++) {
361
+ const min = source.bounds[axis] as number;
362
+ const max = source.bounds[axis + 3] as number;
363
+ const value = OBJECT[axis] as number;
364
+ CLAMPED[axis] = value < min ? min : value > max ? max : value;
365
+ const over = value - (CLAMPED[axis] as number);
366
+ outsideSq += over * over;
367
+ }
368
+ if (outsideSq > 0) {
369
+ const outside = Math.sqrt(outsideSq);
370
+ const atBox = sourceAt(
371
+ source,
372
+ CLAMPED[0] as number,
373
+ CLAMPED[1] as number,
374
+ CLAMPED[2] as number,
375
+ );
376
+ return Math.max(outside, atBox - outside) * scale;
377
+ }
378
+ return sourceAt(source, OBJECT[0] as number, OBJECT[1] as number, OBJECT[2] as number) * scale;
379
+ }
380
+
381
+ /** How far an instance's box is from a cascade's, so an instance too far away is skipped whole. */
382
+ function boxDistance(
383
+ instance: GlobalFieldInstance,
384
+ inverse: Float32Array,
385
+ scale: number,
386
+ minX: number,
387
+ minY: number,
388
+ minZ: number,
389
+ maxX: number,
390
+ maxY: number,
391
+ maxZ: number,
392
+ ): number {
393
+ /*
394
+ * The nearest point of the cascade to the instance's own centre, then that point's distance to
395
+ * the instance's box. Conservative in the right direction: it never reports further than the
396
+ * true separation, so an instance that could contribute is never culled.
397
+ */
398
+ const centre = instance.source.bounds;
399
+ const world = instance.transform;
400
+ const cx = ((centre[0] as number) + (centre[3] as number)) / 2;
401
+ const cy = ((centre[1] as number) + (centre[4] as number)) / 2;
402
+ const cz = ((centre[2] as number) + (centre[5] as number)) / 2;
403
+ const wx =
404
+ (world[0] as number) * cx +
405
+ (world[4] as number) * cy +
406
+ (world[8] as number) * cz +
407
+ (world[12] as number);
408
+ const wy =
409
+ (world[1] as number) * cx +
410
+ (world[5] as number) * cy +
411
+ (world[9] as number) * cz +
412
+ (world[13] as number);
413
+ const wz =
414
+ (world[2] as number) * cx +
415
+ (world[6] as number) * cy +
416
+ (world[10] as number) * cz +
417
+ (world[14] as number);
418
+
419
+ const nearX = Math.min(Math.max(wx, minX), maxX);
420
+ const nearY = Math.min(Math.max(wy, minY), maxY);
421
+ const nearZ = Math.min(Math.max(wz, minZ), maxZ);
422
+ return sampleInstance(instance, inverse, scale, nearX, nearY, nearZ);
423
+ }
424
+
425
+ /** Trilinear read of a cascade, clamped at its own edge. */
426
+ function trilinear(cascade: GlobalFieldCascade, x: number, y: number, z: number): number {
427
+ return read(cascade.field, cascade.dims, cascade.bounds, cascade.step, x, y, z);
428
+ }
429
+
430
+ /** Trilinear read of a source field, whose step this has to derive from its own bounds. */
431
+ function sourceAt(source: FieldSource, x: number, y: number, z: number): number {
432
+ const step =
433
+ ((source.bounds[3] as number) - (source.bounds[0] as number)) /
434
+ ((source.dims[0] as number) - 1);
435
+ return read(source.field, source.dims, source.bounds, step, x, y, z);
436
+ }
437
+
438
+ /** The one trilinear read both grids use, so the two cannot disagree about the layout. */
439
+ function read(
440
+ field: Float32Array,
441
+ dims: readonly [number, number, number],
442
+ bounds: Float32Array,
443
+ step: number,
444
+ x: number,
445
+ y: number,
446
+ z: number,
447
+ ): number {
448
+ const [nx, ny, nz] = dims;
449
+ const counts = [nx, ny, nz];
450
+ const p = [x, y, z];
451
+ const low = [0, 0, 0];
452
+ const fraction = [0, 0, 0];
453
+ for (let axis = 0; axis < 3; axis++) {
454
+ const count = counts[axis] as number;
455
+ const local = ((p[axis] as number) - (bounds[axis] as number)) / step;
456
+ const clamped = local <= 0 ? 0 : local >= count - 1 ? count - 1 : local;
457
+ const index = Math.min(Math.floor(clamped), count - 2);
458
+ low[axis] = index;
459
+ fraction[axis] = clamped - index;
460
+ }
461
+
462
+ const [lx, ly, lz] = low as [number, number, number];
463
+ const [fx, fy, fz] = fraction as [number, number, number];
464
+ let total = 0;
465
+ for (let cz = 0; cz < 2; cz++) {
466
+ const wz = cz === 0 ? 1 - fz : fz;
467
+ for (let cy = 0; cy < 2; cy++) {
468
+ const wy = cy === 0 ? 1 - fy : fy;
469
+ for (let cx = 0; cx < 2; cx++) {
470
+ const wx = cx === 0 ? 1 - fx : fx;
471
+ const index = lx + cx + nx * (ly + cy + ny * (lz + cz));
472
+ total += (field[index] as number) * wx * wy * wz;
473
+ }
474
+ }
475
+ }
476
+ return total;
477
+ }
478
+
479
+ /** The scale a transform applies, refusing one that differs between axes. See `transform`. */
480
+ function uniformScaleOf(transform: ReadonlyMat4): number {
481
+ return Math.hypot(transform[0] as number, transform[1] as number, transform[2] as number);
482
+ }
483
+
484
+ /**
485
+ * Refuse a source whose voxels are not cubic, which `sourceAt` and the shader both assume.
486
+ *
487
+ * **One step for all three axes is the contract, and it was unwritten until it cost a feature.**
488
+ * `bakeObjectSdf` picks its step from the object's longest axis and gives each axis whatever count
489
+ * it needs at that step, so `dims` differ per axis and the spacing does not. Both readers rely on
490
+ * that: `sourceAt` here derives the step from x alone, and `composeField.wgsl.ts` says so in a
491
+ * comment. A source sampled with the same count on every axis of a box that is not a cube has
492
+ * oblong voxels, and every sample of it is taken at the wrong place along y and z — far enough
493
+ * wrong that a point at the centre of a slab reads the slab's corner.
494
+ *
495
+ * **Measured on `demo/dev/bounce.html`**, whose room is five such slabs: the composed field held
496
+ * almost no room, 98.9% of every probe's rays left a closed room, and the traced bounce delivered a
497
+ * thirty-fifth of the light. Nothing failed; the field was simply empty. Refusing is the only
498
+ * honest answer, because a distance field carries no record of the axis it was sampled along and
499
+ * there is nothing here to correct it with — the same reason a non-uniform scale is refused above.
500
+ *
501
+ * **An axis of one sample is refused here too, and by its own sentence**, because `read` addresses
502
+ * `index` and `index + 1` on every axis and cannot be given an axis with no pair. `bakeObjectSdf`
503
+ * never produces one — its counts are a ceiling plus one over a span that includes two voxels of
504
+ * padding — but `FieldSource` is a shape a consumer may build by hand, which is the whole reason
505
+ * this function exists.
506
+ */
507
+ export function assertCubicVoxels(source: FieldSource): void {
508
+ const steps = [0, 1, 2].map((axis) => {
509
+ const count = source.dims[axis] as number;
510
+ if (count < 2) {
511
+ throw new Error(
512
+ `driftengine: a distance field source is ${source.dims.join('x')} samples and every axis ` +
513
+ 'needs at least two. One sample on an axis has no neighbour to interpolate towards.',
514
+ );
515
+ }
516
+ return ((source.bounds[axis + 3] as number) - (source.bounds[axis] as number)) / (count - 1);
517
+ });
518
+ const low = Math.min(...steps);
519
+ const high = Math.max(...steps);
520
+ if (high - low <= 1e-3 * high) return;
521
+ throw new Error(
522
+ `driftengine: a distance field source's voxels are ${steps
523
+ .map((step) => step.toFixed(4))
524
+ .join(' by ')} metres and have to be cubic. Its dims are ${source.dims.join('x')} over a ` +
525
+ 'box that is not that shape; give each axis the count it needs at one step, as ' +
526
+ 'bakeObjectSdf does.',
527
+ );
528
+ }
529
+
530
+ function assertUniformScale(transform: ReadonlyMat4): void {
531
+ const x = Math.hypot(transform[0] as number, transform[1] as number, transform[2] as number);
532
+ const y = Math.hypot(transform[4] as number, transform[5] as number, transform[6] as number);
533
+ const z = Math.hypot(transform[8] as number, transform[9] as number, transform[10] as number);
534
+ const spread = Math.max(x, y, z) - Math.min(x, y, z);
535
+ if (spread > 1e-4 * Math.max(x, y, z)) {
536
+ throw new Error(
537
+ `composeGlobalField: an instance is scaled ${x.toFixed(3)}, ${y.toFixed(3)}, ` +
538
+ `${z.toFixed(3)}, and a distance field takes a uniform scale only. There is no factor to ` +
539
+ 'correct a non-uniform one by: it depends on the direction to the nearest surface, which ' +
540
+ 'is what the field does not record.',
541
+ );
542
+ }
543
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A probe grid for a scene that asked for indirect light and declared no grid.
3
+ *
4
+ * **A declared grid always wins.** This exists so that turning the feature on shows light rather
5
+ * than a warning, which is the difference between a feature a consumer can try and one they have
6
+ * to study first. A scene that cares about where its probes stand says so and this is never asked.
7
+ *
8
+ * **Every axis carries the same spacing.** Fitting each axis to its own extent would make a probe's
9
+ * cell a slab, and a slab interpolates over different distances along different axes — so a
10
+ * surface's indirect light would change as it turned, which is the one thing an irradiance probe
11
+ * must not do. When the budget bites, the spacing widens uniformly and the cells stay cubic.
12
+ */
13
+
14
+ import { type Vec3 } from '../../math/color.ts';
15
+ import type { ProbeGridOptions } from '../probeGrid.ts';
16
+
17
+ /** How far outside the scene the outermost probes stand, in cells. */
18
+ const MARGIN_CELLS = 1;
19
+
20
+ /**
21
+ * A grid covering `bounds` at about `spacing` metres, within `maxProbes`.
22
+ *
23
+ * The probes surround the scene rather than stopping at it: a surface on the boundary has to be
24
+ * between probes on both sides of it, and a grid that stopped at the bounds would leave every
25
+ * outward-facing wall interpolating between probes that are all behind it.
26
+ */
27
+ export function fitProbeGrid(
28
+ bounds: { readonly min: ArrayLike<number>; readonly max: ArrayLike<number> },
29
+ spacing: number,
30
+ maxProbes: number,
31
+ ): ProbeGridOptions {
32
+ const budget = Math.max(1, Math.floor(maxProbes));
33
+ const extent = [0, 1, 2].map((axis) =>
34
+ Math.max(0, (bounds.max[axis] as number) - (bounds.min[axis] as number)),
35
+ );
36
+ const centre = [0, 1, 2].map(
37
+ (axis) => ((bounds.min[axis] as number) + (bounds.max[axis] as number)) / 2,
38
+ );
39
+
40
+ /*
41
+ * Widened until the whole grid fits the budget, by whole steps rather than by solving for it:
42
+ * the count is a ceiling of a ratio, so the closed form would still have to be checked and a
43
+ * loop over a handful of doublings is clearer than a formula nobody can verify by reading.
44
+ */
45
+ let step = Math.max(1e-3, spacing);
46
+ let counts = countsFor(extent, step);
47
+ while (counts[0] * counts[1] * counts[2] > budget && step < 1e9) {
48
+ step *= 1.5;
49
+ counts = countsFor(extent, step);
50
+ }
51
+
52
+ /*
53
+ * **A budget too small for a margin collapses to one probe**, which is not a special case
54
+ * anywhere: `probeGrid.ts` says a world that bakes one environment is a grid of one, through the
55
+ * same array, convolution and shader. An axis with any extent needs three probes to surround it,
56
+ * so the smallest grid that surrounds anything is twenty-seven — and a budget below that cannot
57
+ * be met by widening, however far the step goes.
58
+ */
59
+ if (counts[0] * counts[1] * counts[2] > budget) {
60
+ return {
61
+ origin: [centre[0] as number, centre[1] as number, centre[2] as number],
62
+ spacing: [step, step, step],
63
+ counts: [1, 1, 1],
64
+ };
65
+ }
66
+
67
+ const origin: Vec3 = [0, 0, 0];
68
+ for (let axis = 0; axis < 3; axis += 1) {
69
+ /* Centred on the scene, so the margin outside each face is the same. */
70
+ origin[axis] = (centre[axis] as number) - (step * ((counts[axis] as number) - 1)) / 2;
71
+ }
72
+ return {
73
+ origin,
74
+ spacing: [step, step, step],
75
+ counts: [counts[0] as number, counts[1] as number, counts[2] as number],
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Probes an axis needs to span `extent` at `step`, with a cell of margin at each end.
81
+ *
82
+ * At least one, always: an axis with no extent — a scene that is a floor, or a point — is a single
83
+ * probe rather than a count of zero, which would make the grid empty and the shading read nothing.
84
+ */
85
+ function countsFor(extent: readonly number[], step: number): number[] {
86
+ return extent.map((length) =>
87
+ length <= 0 ? 1 : Math.ceil(length / step) + 1 + 2 * MARGIN_CELLS - 1,
88
+ );
89
+ }