@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
@@ -2,6 +2,7 @@ import { mat3 } from 'gl-matrix';
2
2
 
3
3
  import { createEmptyTexture2D } from './emptyTexture.ts';
4
4
  import { LIVE_POINT_SHADOW_MAPS, POINT_SHADOW_POOL } from './lightBudget.ts';
5
+ import { PointGlassTint } from './pointGlassTint.ts';
5
6
  import { pointShadowFaceRotation } from './pointShadowImage.ts';
6
7
  import { compileProgram, uniformLocations } from './shader.ts';
7
8
  import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octahedralResolve.ts';
@@ -34,6 +35,37 @@ export interface PointShadowTarget {
34
35
  far: number,
35
36
  near: number,
36
37
  ): void;
38
+ /** Whether the array has grown its glass layers: `growForGlass`. */
39
+ readonly hasGlass: boolean;
40
+ /** Whether `face` of `layer` holds glass, which a face that offers none must clear. */
41
+ heldGlass(layer: number, face: number): boolean;
42
+ /** The depth scratch again, cleared, for the face's glass. */
43
+ beginGlassFace(gl: WebGL2RenderingContext): void;
44
+ resolveGlassFace(
45
+ gl: WebGL2RenderingContext,
46
+ layer: number,
47
+ face: number,
48
+ far: number,
49
+ near: number,
50
+ ): void;
51
+ /** The colour scratch, cleared to white, for what the face's glass lets through. */
52
+ beginTintFace(gl: WebGL2RenderingContext): void;
53
+ resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
54
+ }
55
+
56
+ /**
57
+ * Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
58
+ *
59
+ * **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
60
+ * index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
61
+ * 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
62
+ */
63
+ export function strideLayers(layers: Int32Array, stride: number): void {
64
+ if (stride === 1) return;
65
+ for (let i = 0; i < layers.length; i++) {
66
+ const layer = layers[i] as number;
67
+ if (layer >= 0) layers[i] = layer * stride;
68
+ }
37
69
  }
38
70
 
39
71
  /**
@@ -53,12 +85,16 @@ export interface PointShadowTarget {
53
85
  * never the same object.
54
86
  */
55
87
  export class PointShadowArray implements PointShadowTarget {
56
- readonly texture: WebGLTexture;
57
88
  /**
58
- * How many layers this array holds: the point pool, the two live transition maps, and two more
59
- * for each rectangle a world said would cast. See the constructor's `extraLayers`.
89
+ * How many lights this array holds: the point pool, the two live transition maps, and two more
90
+ * for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
91
+ * lights, so it does not move when glass doubles the storage under it.
60
92
  */
61
93
  readonly layers: number;
94
+ /** Replaced once, when glass first casts: see `growForGlass`. */
95
+ private current: WebGLTexture;
96
+ /** What the lights' glass lets through, made with the glass layers. */
97
+ private tint: PointGlassTint | null = null;
62
98
 
63
99
  private readonly framebuffer: WebGLFramebuffer;
64
100
  private readonly scratchTexture: WebGLTexture;
@@ -96,23 +132,8 @@ export class PointShadowArray implements PointShadowTarget {
96
132
  this.layers =
97
133
  Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
98
134
 
99
- const texture = gl.createTexture();
100
- if (texture === null) throw new Error('PointShadowArray: createTexture failed');
101
- this.texture = texture;
102
- gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
103
- gl.texStorage3D(
104
- gl.TEXTURE_2D_ARRAY,
105
- 1,
106
- gl.DEPTH_COMPONENT24,
107
- OCTAHEDRAL_EDGE,
108
- OCTAHEDRAL_EDGE,
109
- this.layers,
110
- );
111
- /* One storage level and no filtering, so textureLod(..., 0.0) is the only fetch there is. */
112
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
113
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
114
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
115
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
135
+ const texture = depthArray(gl, this.layers);
136
+ this.current = texture;
116
137
 
117
138
  const scratchTexture = gl.createTexture();
118
139
  if (scratchTexture === null) throw new Error('PointShadowArray: createTexture failed');
@@ -183,6 +204,117 @@ export class PointShadowArray implements PointShadowTarget {
183
204
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
184
205
  }
185
206
 
207
+ /** The array texture the lit pass samples. Replaced once, when glass first casts. */
208
+ get texture(): WebGLTexture {
209
+ return this.current;
210
+ }
211
+
212
+ get hasGlass(): boolean {
213
+ return this.tint !== null;
214
+ }
215
+
216
+ /** Physical layers a light: 1, or 2 once glass has interleaved the array. */
217
+ get stride(): number {
218
+ return this.tint === null ? 1 : 2;
219
+ }
220
+
221
+ /** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
222
+ get tintTexture(): WebGLTexture | null {
223
+ return this.tint?.texture ?? null;
224
+ }
225
+
226
+ /**
227
+ * Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
228
+ * the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
229
+ *
230
+ * **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
231
+ * its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
232
+ * baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
233
+ * again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
234
+ * glass layer born at the far plane with a white tint is exactly what its bake would draw. What
235
+ * it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
236
+ * chain, 1.4 MB at `'half'`), from then on.
237
+ */
238
+ growForGlass(gl: WebGL2RenderingContext, tintEdge: number): boolean {
239
+ if (this.tint !== null) return false;
240
+ const old = this.current;
241
+ const texture = depthArray(gl, this.layers * 2);
242
+ /* Off the unit it was made on, as the constructor leaves its own: see `beginFace`. */
243
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
244
+ const read = gl.createFramebuffer();
245
+ if (read === null) throw new Error('PointShadowArray: createFramebuffer failed');
246
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
247
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
248
+ for (let layer = 0; layer < this.layers; layer++) {
249
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
250
+ gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer);
251
+ gl.blitFramebuffer(
252
+ 0,
253
+ 0,
254
+ OCTAHEDRAL_EDGE,
255
+ OCTAHEDRAL_EDGE,
256
+ 0,
257
+ 0,
258
+ OCTAHEDRAL_EDGE,
259
+ OCTAHEDRAL_EDGE,
260
+ gl.DEPTH_BUFFER_BIT,
261
+ gl.NEAREST,
262
+ );
263
+ }
264
+ /* Nothing is nearer a lamp than a pane that has not been drawn. */
265
+ for (let layer = 0; layer < this.layers; layer++) {
266
+ gl.framebufferTextureLayer(
267
+ gl.DRAW_FRAMEBUFFER,
268
+ gl.DEPTH_ATTACHMENT,
269
+ texture,
270
+ 0,
271
+ 2 * layer + 1,
272
+ );
273
+ gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
274
+ }
275
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
276
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
277
+ gl.deleteFramebuffer(read);
278
+ gl.deleteTexture(old);
279
+ this.current = texture;
280
+ this.tint = new PointGlassTint(gl, this.faceSize, tintEdge, this.layers);
281
+ return true;
282
+ }
283
+
284
+ heldGlass(layer: number, face: number): boolean {
285
+ return this.tint?.held(layer, face) ?? false;
286
+ }
287
+
288
+ /** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
289
+ beginGlassFace(gl: WebGL2RenderingContext): void {
290
+ this.beginFace(gl);
291
+ }
292
+
293
+ /** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
294
+ resolveGlassFace(
295
+ gl: WebGL2RenderingContext,
296
+ layer: number,
297
+ face: number,
298
+ far: number,
299
+ near: number,
300
+ ): void {
301
+ if (this.tint === null) return;
302
+ this.resolveInto(gl, 2 * layer + 1, face, far, near);
303
+ }
304
+
305
+ beginTintFace(gl: WebGL2RenderingContext): void {
306
+ this.tint?.beginFace(gl);
307
+ }
308
+
309
+ resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void {
310
+ this.tint?.resolveFace(gl, layer, face, drawn);
311
+ }
312
+
313
+ /** The chain frost reads, for every light whose tint a round touched: once a round. */
314
+ finishTints(gl: WebGL2RenderingContext): void {
315
+ this.tint?.fillMips(gl);
316
+ }
317
+
186
318
  /**
187
319
  * Bind the scratch as the render target for one face of one bake, and clear it.
188
320
  *
@@ -224,9 +356,20 @@ export class PointShadowArray implements PointShadowTarget {
224
356
  face: number,
225
357
  far: number,
226
358
  near: number,
359
+ ): void {
360
+ this.resolveInto(gl, layer * this.stride, face, far, near);
361
+ }
362
+
363
+ /** The scratch into physical layer `physical`'s region for `face`. */
364
+ private resolveInto(
365
+ gl: WebGL2RenderingContext,
366
+ physical: number,
367
+ face: number,
368
+ far: number,
369
+ near: number,
227
370
  ): void {
228
371
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
229
- gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.texture, 0, layer);
372
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, physical);
230
373
  gl.viewport(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE);
231
374
 
232
375
  gl.useProgram(this.program);
@@ -267,10 +410,33 @@ export class PointShadowArray implements PointShadowTarget {
267
410
  dispose(gl: WebGL2RenderingContext): void {
268
411
  gl.deleteFramebuffer(this.framebuffer);
269
412
  gl.deleteFramebuffer(this.scratchFramebuffer);
270
- gl.deleteTexture(this.texture);
413
+ gl.deleteTexture(this.current);
271
414
  gl.deleteTexture(this.scratchTexture);
415
+ this.tint?.dispose(gl);
272
416
  gl.deleteTexture(this.parked);
273
417
  gl.deleteProgram(this.program);
274
418
  gl.deleteVertexArray(this.vao);
275
419
  }
276
420
  }
421
+
422
+ const FAR_PLANE = new Float32Array([1]);
423
+
424
+ /** The depth array: one storage level and no filtering, so textureLod(..., 0.0) is the only fetch. */
425
+ function depthArray(gl: WebGL2RenderingContext, layers: number): WebGLTexture {
426
+ const texture = gl.createTexture();
427
+ if (texture === null) throw new Error('PointShadowArray: createTexture failed');
428
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
429
+ gl.texStorage3D(
430
+ gl.TEXTURE_2D_ARRAY,
431
+ 1,
432
+ gl.DEPTH_COMPONENT24,
433
+ OCTAHEDRAL_EDGE,
434
+ OCTAHEDRAL_EDGE,
435
+ layers,
436
+ );
437
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
438
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
439
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
440
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
441
+ return texture;
442
+ }
@@ -36,6 +36,19 @@ export { FACE_COUNT, POINT_SHADOW_NEAR };
36
36
 
37
37
  const scratchViewProj = mat4.create();
38
38
 
39
+ /**
40
+ * What a bake asks of the renderer about glass, after each face's opaque depth.
41
+ *
42
+ * **Asked before it is drawn** because the answer decides whether the face binds a glass target at
43
+ * all: a face that offers no glass and holds none from a previous owner does no glass work.
44
+ */
45
+ export interface PointGlassDraw {
46
+ /** Whether the face just drawn offered any glass. */
47
+ offered(): boolean;
48
+ /** Draw that glass: its depth, or what it lets through lit from the bake origin. */
49
+ draw(pass: 'depth' | 'tint', x: number, y: number, z: number): void;
50
+ }
51
+
39
52
  export class PointShadowMap {
40
53
  readonly size: number;
41
54
  /**
@@ -163,6 +176,8 @@ export class PointShadowMap {
163
176
  near = POINT_SHADOW_NEAR,
164
177
  sourceRadius = DEFAULT_SOURCE_RADIUS,
165
178
  maxFaces = FACE_COUNT,
179
+ /** The face's glass, when glass shadows are on: `PointGlassDraw`. */
180
+ glass: PointGlassDraw | null = null,
166
181
  ): number {
167
182
  /*
168
183
  * What to render now, **and what to render it for**. A bake in flight owns the parameters
@@ -220,6 +235,7 @@ export class PointShadowMap {
220
235
  * next face's pass does care about.
221
236
  */
222
237
  array.resolveFace(gl, this.layer, face, plan.range, plan.near);
238
+ if (glass !== null) this.bakeGlassFace(gl, array, glass, face, plan);
223
239
  }
224
240
 
225
241
  gl.enable(gl.CULL_FACE);
@@ -237,5 +253,30 @@ export class PointShadowMap {
237
253
  * needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
238
254
  * renderer that built it.
239
255
  */
256
+ /**
257
+ * The face's glass, twice: where the nearest pane is, then what the panes let through.
258
+ *
259
+ * A face that offers no glass still clears a region a previous owner's glass is in — the depth to
260
+ * the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
261
+ * slot changes hands with its layer.
262
+ */
263
+ private bakeGlassFace(
264
+ gl: WebGL2RenderingContext,
265
+ array: PointShadowTarget,
266
+ glass: PointGlassDraw,
267
+ face: number,
268
+ plan: FaceRange,
269
+ ): void {
270
+ if (!array.hasGlass) return;
271
+ const offered = glass.offered();
272
+ if (!offered && !array.heldGlass(this.layer, face)) return;
273
+ array.beginGlassFace(gl);
274
+ if (offered) glass.draw('depth', plan.x, plan.y, plan.z);
275
+ array.resolveGlassFace(gl, this.layer, face, plan.range, plan.near);
276
+ array.beginTintFace(gl);
277
+ if (offered) glass.draw('tint', plan.x, plan.y, plan.z);
278
+ array.resolveTintFace(gl, this.layer, face, offered);
279
+ }
280
+
240
281
  dispose(_gl: WebGL2RenderingContext): void {}
241
282
  }
@@ -53,6 +53,17 @@ export interface RainFieldOptions {
53
53
  streakSec?: number;
54
54
  /** The ground drops land on, and the roofs they do not fall through. */
55
55
  ground?: GroundSurface;
56
+ /**
57
+ * How near the viewer a streak may pass and still be drawn, metres. Default 1.
58
+ *
59
+ * **A drop that close is out of any focus a camera holds**, and drawn sharp it was a bar across
60
+ * the frame: a streak is a world-space width, so a 6 mm drop 30 cm from the eye drew fourteen
61
+ * pixels thick and as long as the frame, from a city seen from the air in the rain. A streak
62
+ * whose nearest point is inside this distance is not drawn; the drop keeps falling, as a
63
+ * sheltered one does. What it gives up is rain on a lens or a visor, which this field never
64
+ * drew as such anyway. What would make it wrong is a view that wants drops in macro: pass 0.
65
+ */
66
+ clearM?: number;
56
67
  }
57
68
 
58
69
  export class RainField {
@@ -97,6 +108,8 @@ export class RainField {
97
108
  */
98
109
  streakSec: number;
99
110
  private readonly ground: GroundSurface | null;
111
+ /** How near the viewer a streak may pass and still be drawn, squared. */
112
+ private readonly clear2: number;
100
113
  /** Drops not drawn this update because something was over them. */
101
114
  private sheltered = 0;
102
115
  /**
@@ -125,6 +138,8 @@ export class RainField {
125
138
  this.speedMps = options.speedMps ?? 9;
126
139
  this.streakSec = options.streakSec ?? 0.04;
127
140
  this.ground = options.ground ?? null;
141
+ const clear = options.clearM ?? 1;
142
+ this.clear2 = clear * clear;
128
143
  this.segments = createLineSegments(options.count);
129
144
  this.x = new Float32Array(options.count);
130
145
  this.y = new Float32Array(options.count);
@@ -212,15 +227,34 @@ export class RainField {
212
227
  continue;
213
228
  }
214
229
 
230
+ /* Where it was a shutter-time ago, which is the streak: down by its fall and back along the
231
+ wind, so a gust leans every drop at the same angle without anything trigonometric. */
232
+ const backX = -windX * this.streakSec;
233
+ const backY = this.speedMps * this.streakSec;
234
+ const backZ = -windZ * this.streakSec;
235
+ /* Too near the eye to be drawn (`clearM`): the streak's nearest point to the viewer. */
236
+ if (this.clear2 > 0) {
237
+ const ox = dropX - viewerX;
238
+ const oy = dropY - viewerY;
239
+ const oz = dropZ - viewerZ;
240
+ const length2 = backX * backX + backY * backY + backZ * backZ;
241
+ const along =
242
+ length2 > 0
243
+ ? Math.min(Math.max(-(ox * backX + oy * backY + oz * backZ) / length2, 0), 1)
244
+ : 0;
245
+ const nx = ox + backX * along;
246
+ const ny = oy + backY * along;
247
+ const nz = oz + backZ * along;
248
+ if (nx * nx + ny * ny + nz * nz < this.clear2) continue;
249
+ }
250
+
215
251
  const at = this.segments.count;
216
252
  this.segments.from[at * 3] = dropX;
217
253
  this.segments.from[at * 3 + 1] = dropY;
218
254
  this.segments.from[at * 3 + 2] = dropZ;
219
- /* Where it was a shutter-time ago, which is the streak: down by its fall and back along the
220
- wind, so a gust leans every drop at the same angle without anything trigonometric. */
221
- this.segments.to[at * 3] = dropX - windX * this.streakSec;
222
- this.segments.to[at * 3 + 1] = dropY + this.speedMps * this.streakSec;
223
- this.segments.to[at * 3 + 2] = dropZ - windZ * this.streakSec;
255
+ this.segments.to[at * 3] = dropX + backX;
256
+ this.segments.to[at * 3 + 1] = dropY + backY;
257
+ this.segments.to[at * 3 + 2] = dropZ + backZ;
224
258
  this.segments.count = at + 1;
225
259
  }
226
260
  this.primed = true;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Which frame a change to a dynamic mesh or an instanced batch belongs to.
3
+ *
4
+ * **A consumer may change geometry in its simulation step, before `beginFrame`.** That change is
5
+ * what the *next* frame draws, so it is that frame's — counting it against the frame last begun
6
+ * would say the geometry changed a frame early and then did not change at all, which is a motion
7
+ * vector on the wrong frame and none on the right one.
8
+ *
9
+ * Only the **first** change in a frame keeps the state before it: a second rewrite in the same
10
+ * frame must not replace "where it was last frame" with "where it was a moment ago".
11
+ */
12
+
13
+ export function frameOfChange(serial: number, inFrame: boolean): number {
14
+ return inFrame ? serial : serial + 1;
15
+ }
16
+
17
+ export function keepsPrevious(lastChange: number, frame: number): boolean {
18
+ return lastChange !== frame;
19
+ }
20
+
21
+ /** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
22
+ export function pairedInstances(previousCount: number, count: number): number {
23
+ return Math.min(previousCount, count);
24
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The depth a reconstructing frame's late draws are tested against at an output pixel, taken from
3
+ * the render's jittered depth. `shaders/recon/lateDepth.wgsl.ts` is the same arithmetic on the device.
4
+ *
5
+ * **Each surface taken to the pixel, and the nearest of them.** A late draw — a glow, a pane, a
6
+ * caption — is drawn unjittered at the output size, and it has to meet the depth of what it lies on
7
+ * where *it* is. Each of the four render texels about the pixel, placed where the jitter put it,
8
+ * carries its surface on to the pixel's unjittered centre along that surface's own slope; the
9
+ * nearest of the four so carried stands, and a quarter of its slope is given back toward the far
10
+ * side so that a draw lying exactly on the surface is not decided by rounding. On a plane all four
11
+ * arrive at the plane's own depth at the pixel: exact, and the same whatever the jitter was.
12
+ *
13
+ * **A texel's slope is the smaller of its two differences along each axis, and none where they
14
+ * disagree in sign** — the minmod limiter. Beside an edge one difference is the jump, so it is the
15
+ * other, the surface's own, that is taken; a sliver one texel wide disagrees both ways and keeps its
16
+ * own depth, the nearest of the four as before. So a translucent draw still never comes through an
17
+ * opaque edge the render covered by more than the render's own texel.
18
+ *
19
+ * **Why this replaced the nearest of the four.** On a wall seen aslant the nearest of four neighbours
20
+ * stands up to a texel's step in front of the wall at the pixel, so a glow lying a few centimetres
21
+ * proud of it was judged behind the wall; and which neighbour was nearest moved with the jitter, so
22
+ * the glow was drawn one frame and not the next. Measured on a street of lit facades: every strip of
23
+ * glow on every tower flickered frame by frame, 12,415 pixels of one tower between two frames.
24
+ *
25
+ * What it gives up: a surface curving within a texel is carried on as a plane, which a quarter-step
26
+ * tolerance covers. What would make it wrong is depth that is not affine on a plane — a depth written
27
+ * from the fragment stage — which nothing drawn before the late pass writes.
28
+ */
29
+ import { REVERSED_DEPTH } from '../depthConvention.ts';
30
+
31
+ /** The share of a surface's slope given back toward the far side, for a draw lying on it. */
32
+ const TOLERANCE_SHARE = 0.25;
33
+
34
+ /** The smaller of two differences, or none where they disagree in sign. */
35
+ function minmod(a: number, b: number): number {
36
+ if (a * b <= 0) return 0;
37
+ return Math.abs(a) < Math.abs(b) ? a : b;
38
+ }
39
+
40
+ /**
41
+ * Where output pixel (`outX`, `outY`) — its centre, in pixels — falls among the render texels
42
+ * jittered by (`jitterX`, `jitterY`) render texels: into `out`, the cell's first texel and the
43
+ * fraction across it, `[x, y, fx, fy]`.
44
+ */
45
+ export function lateDepthCell(
46
+ outX: number,
47
+ outY: number,
48
+ renderWidth: number,
49
+ renderHeight: number,
50
+ outputWidth: number,
51
+ outputHeight: number,
52
+ jitterX: number,
53
+ jitterY: number,
54
+ out: Float64Array | Float32Array,
55
+ ): void {
56
+ /* A texel stands at its index plus a half less the jitter, as the resolve places it. */
57
+ const x = (outX * renderWidth) / outputWidth - 0.5 + jitterX;
58
+ const y = (outY * renderHeight) / outputHeight - 0.5 + jitterY;
59
+ out[0] = Math.floor(x);
60
+ out[1] = Math.floor(y);
61
+ out[2] = x - Math.floor(x);
62
+ out[3] = y - Math.floor(y);
63
+ }
64
+
65
+ /**
66
+ * The depth to test against, from `taps` — a 4x4 block of render texels, row-major, starting one
67
+ * texel up and left of the cell's first — at (`fx`, `fy`) across the cell.
68
+ */
69
+ export function lateDepthAt(taps: ArrayLike<number>, fx: number, fy: number): number {
70
+ const t = (x: number, y: number): number => taps[(y + 1) * 4 + x + 1] as number;
71
+ let depth = REVERSED_DEPTH ? -Infinity : Infinity;
72
+ for (let oy = 0; oy < 2; oy += 1) {
73
+ for (let ox = 0; ox < 2; ox += 1) {
74
+ const d = t(ox, oy);
75
+ const gx = minmod(t(ox + 1, oy) - d, d - t(ox - 1, oy));
76
+ const gy = minmod(t(ox, oy + 1) - d, d - t(ox, oy - 1));
77
+ const carried = d + gx * (fx - ox) + gy * (fy - oy);
78
+ const give = TOLERANCE_SHARE * Math.max(Math.abs(gx), Math.abs(gy));
79
+ depth = REVERSED_DEPTH ? Math.max(depth, carried - give) : Math.min(depth, carried + give);
80
+ }
81
+ }
82
+ return depth;
83
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Two decisions about one draw in a reconstructing frame, and why they are the same set.
3
+ *
4
+ * A blended draw goes **late** — after the reconstruction, at output resolution, unjittered — and a
5
+ * mover's motion is **tracked**, only for draws that land in the picture being reconstructed. A
6
+ * planar mirror and a probe bake are other pictures drawn through the same verbs, and the overlay
7
+ * after `endFrame` is past the picture altogether; routing any of them late, or counting a mirrored
8
+ * draw as a mover's second draw of the frame, would be wrong in the way that reads as a ghost.
9
+ *
10
+ * Plain booleans rather than a state object, so the per-draw call allocates nothing.
11
+ */
12
+ export function routesLate(
13
+ reconstructing: boolean,
14
+ mirror: boolean,
15
+ probe: boolean,
16
+ presented: boolean,
17
+ ): boolean {
18
+ return reconstructing && !mirror && !probe && !presented;
19
+ }
20
+
21
+ /** The same set as `routesLate`, named for the other reader. */
22
+ export function tracksMotion(
23
+ reconstructing: boolean,
24
+ mirror: boolean,
25
+ probe: boolean,
26
+ presented: boolean,
27
+ ): boolean {
28
+ return routesLate(reconstructing, mirror, probe, presented);
29
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Which object a draw is, so a reconstruction can find where that object was last frame.
3
+ *
4
+ * **The engine cannot infer this and does not try.** `drawMesh` is immediate: a mesh drawn three
5
+ * times is three objects, and nothing in the call says which of last frame's three each one is. A
6
+ * consumer that makes one `Mover` per moving object and passes it with every draw of that object
7
+ * says exactly that, and the renderer keeps the rest — last frame's model and, for a skinned
8
+ * draw, last frame's palette.
9
+ *
10
+ * **Plain data, advanced by the renderer.** No renderer is needed to make one, so a consumer holds
11
+ * it beside the object it belongs to. Only a reconstructing WebGPU renderer reads or writes it; it
12
+ * costs nothing anywhere else. The fields past `kind` are the renderer's and a caller leaves them
13
+ * alone.
14
+ *
15
+ * **What would make this wrong** is a consumer whose objects are rebuilt every frame with no state
16
+ * of their own to hold a mover in; the identity then has to live in whatever *does* persist.
17
+ */
18
+
19
+ export const MOVER_KIND = 'drift.mover' as const;
20
+
21
+ export interface Mover {
22
+ readonly kind: typeof MOVER_KIND;
23
+ /** The renderer that last drew it, from `claimRendererId`; -1 before its first draw. */
24
+ renderer: number;
25
+ /** That renderer's frame serial at the last draw. */
26
+ frame: number;
27
+ readonly model: Float32Array;
28
+ readonly previousModel: Float32Array;
29
+ /**
30
+ * This frame's palette, exactly `paletteLength` floats long once a skinned draw has set it.
31
+ * Swapped with the previous one each frame and reallocated only when the rig changes.
32
+ */
33
+ palette: Float32Array;
34
+ previousPalette: Float32Array;
35
+ paletteLength: number;
36
+ previousPaletteLength: number;
37
+ }
38
+
39
+ /** Nothing to reproject from: first draw, a missed frame, another renderer, or a changed rig. */
40
+ export const MOVER_NONE = 0;
41
+ /** Drawn on the frame before by this renderer: `previousModel` and `previousPalette` are real. */
42
+ export const MOVER_MOTION = 1;
43
+ /** Drawn already this frame. The second draw is a different object and the caller's error. */
44
+ export const MOVER_TWICE = 2;
45
+ export type MoverVerdict = typeof MOVER_NONE | typeof MOVER_MOTION | typeof MOVER_TWICE;
46
+
47
+ let nextRendererId = 1;
48
+
49
+ /** A number no other renderer in this page holds, so a mover can tell renderers apart. */
50
+ export function claimRendererId(): number {
51
+ const id = nextRendererId;
52
+ nextRendererId += 1;
53
+ return id;
54
+ }
55
+
56
+ export function createMover(): Mover {
57
+ return {
58
+ kind: MOVER_KIND,
59
+ renderer: -1,
60
+ frame: -2,
61
+ model: new Float32Array(16),
62
+ previousModel: new Float32Array(16),
63
+ palette: new Float32Array(0),
64
+ previousPalette: new Float32Array(0),
65
+ paletteLength: 0,
66
+ previousPaletteLength: 0,
67
+ };
68
+ }
69
+
70
+ export function isMover(value: unknown): value is Mover {
71
+ return (
72
+ typeof value === 'object' && value !== null && (value as { kind?: unknown }).kind === MOVER_KIND
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Advance a mover to this draw, and say whether last frame's state is usable.
78
+ *
79
+ * **A second draw in one frame changes nothing**, so the first draw's motion stands; the caller
80
+ * warns. Otherwise what the mover held becomes its previous and this draw becomes its current —
81
+ * the palettes by swapping two arrays, so a steady rig allocates nothing after its first frame.
82
+ *
83
+ * **A changed joint count is no motion**: skinning a vertex with a palette of a different rig is
84
+ * not an old pose of this one.
85
+ */
86
+ export function advanceMover(
87
+ mover: Mover,
88
+ renderer: number,
89
+ frame: number,
90
+ model: ArrayLike<number>,
91
+ palette: Float32Array | null,
92
+ ): MoverVerdict {
93
+ if (mover.renderer === renderer && mover.frame === frame) return MOVER_TWICE;
94
+ const drawnLast = mover.renderer === renderer && mover.frame === frame - 1;
95
+
96
+ mover.previousModel.set(mover.model);
97
+ const spare = mover.previousPalette;
98
+ mover.previousPalette = mover.palette;
99
+ mover.palette = spare;
100
+ mover.previousPaletteLength = mover.paletteLength;
101
+
102
+ mover.model.set(model);
103
+ if (palette === null) {
104
+ mover.paletteLength = 0;
105
+ } else {
106
+ /* Exactly the rig's length, because the array is uploaded as it stands; reallocated only when
107
+ the rig changes, which a steady character never does. */
108
+ if (mover.palette.length !== palette.length) mover.palette = new Float32Array(palette.length);
109
+ mover.palette.set(palette);
110
+ mover.paletteLength = palette.length;
111
+ }
112
+ mover.renderer = renderer;
113
+ mover.frame = frame;
114
+
115
+ if (!drawnLast) return MOVER_NONE;
116
+ if (mover.paletteLength !== mover.previousPaletteLength) return MOVER_NONE;
117
+ return MOVER_MOTION;
118
+ }