@driftengine/core 4.4.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/CHANGELOG.json +97 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +28 -3
  23. package/dist/index.js +20 -1
  24. package/dist/render/backend/api.d.ts +34 -14
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  30. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  31. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  32. package/dist/render/backend/webgl2/instanced.js +72 -15
  33. package/dist/render/backend/webgl2/renderer.d.ts +201 -16
  34. package/dist/render/backend/webgl2/renderer.js +596 -224
  35. package/dist/render/backend/webgpu/buffers.d.ts +29 -1
  36. package/dist/render/backend/webgpu/buffers.js +61 -4
  37. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  38. package/dist/render/backend/webgpu/depthPass.js +7 -2
  39. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  40. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  41. package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
  42. package/dist/render/backend/webgpu/flatPass.js +57 -9
  43. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  44. package/dist/render/backend/webgpu/glassTintPass.js +206 -0
  45. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  46. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  47. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  48. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  49. package/dist/render/backend/webgpu/instanced.d.ts +23 -2
  50. package/dist/render/backend/webgpu/instanced.js +41 -4
  51. package/dist/render/backend/webgpu/latePass.d.ts +30 -0
  52. package/dist/render/backend/webgpu/latePass.js +140 -0
  53. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  54. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  55. package/dist/render/backend/webgpu/motionPass.js +491 -0
  56. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  57. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  58. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  59. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  60. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  61. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  62. package/dist/render/backend/webgpu/postPass.d.ts +3 -13
  63. package/dist/render/backend/webgpu/renderer.d.ts +271 -64
  64. package/dist/render/backend/webgpu/renderer.js +1395 -580
  65. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  66. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  67. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  68. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  69. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  70. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  71. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  72. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  73. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
  74. package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
  75. package/dist/render/driftLight/bake.d.ts +7 -0
  76. package/dist/render/driftLight/bake.js +61 -56
  77. package/dist/render/driftLight/denseField.d.ts +46 -0
  78. package/dist/render/driftLight/denseField.js +109 -0
  79. package/dist/render/driftLight/lightField.d.ts +10 -5
  80. package/dist/render/driftLight/lightField.js +25 -19
  81. package/dist/render/driftLight/presence.d.ts +50 -0
  82. package/dist/render/driftLight/presence.js +43 -0
  83. package/dist/render/driftLight/uniforms.d.ts +3 -3
  84. package/dist/render/driftLight/uniforms.js +5 -6
  85. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  86. package/dist/render/driftLight/worldLightField.js +66 -0
  87. package/dist/render/fixtureAtlas.d.ts +20 -0
  88. package/dist/render/fixtureAtlas.js +49 -0
  89. package/dist/render/glass.d.ts +45 -0
  90. package/dist/render/glass.js +39 -0
  91. package/dist/render/glassCasters.d.ts +40 -0
  92. package/dist/render/glassCasters.js +84 -0
  93. package/dist/render/glassShadow.d.ts +78 -0
  94. package/dist/render/glassShadow.js +125 -0
  95. package/dist/render/globalMediumPass.d.ts +2 -3
  96. package/dist/render/globalMediumPass.js +2 -8
  97. package/dist/render/gpudriven/lit.d.ts +12 -0
  98. package/dist/render/gpudriven/lit.js +23 -0
  99. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  100. package/dist/render/gpudriven/materialTable.js +26 -6
  101. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  102. package/dist/render/gpudriven/pipeline.js +4 -3
  103. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  104. package/dist/render/gpudriven/sunGlass.js +89 -0
  105. package/dist/render/instanceCull.d.ts +63 -0
  106. package/dist/render/instanceCull.js +160 -0
  107. package/dist/render/instances.d.ts +15 -0
  108. package/dist/render/lightBudget.d.ts +36 -35
  109. package/dist/render/lightBudget.js +38 -37
  110. package/dist/render/lightGrid.d.ts +42 -0
  111. package/dist/render/lightGrid.js +87 -0
  112. package/dist/render/mesh.d.ts +18 -24
  113. package/dist/render/mesh.js +51 -68
  114. package/dist/render/occlusion.d.ts +10 -0
  115. package/dist/render/occlusion.js +36 -7
  116. package/dist/render/pointGlassTint.d.ts +59 -0
  117. package/dist/render/pointGlassTint.js +198 -0
  118. package/dist/render/pointLightSelection.d.ts +7 -0
  119. package/dist/render/pointLightSelection.js +29 -4
  120. package/dist/render/pointShadowArray.d.ts +56 -3
  121. package/dist/render/pointShadowArray.js +127 -16
  122. package/dist/render/pointShadowMap.d.ts +23 -1
  123. package/dist/render/pointShadowMap.js +27 -1
  124. package/dist/render/rainField.d.ts +13 -0
  125. package/dist/render/rainField.js +27 -5
  126. package/dist/render/recon/changeFrames.d.ts +15 -0
  127. package/dist/render/recon/changeFrames.js +21 -0
  128. package/dist/render/recon/lateDepth.d.ts +11 -0
  129. package/dist/render/recon/lateDepth.js +70 -0
  130. package/dist/render/recon/lateRouting.d.ts +14 -0
  131. package/dist/render/recon/lateRouting.js +18 -0
  132. package/dist/render/recon/mover.d.ts +57 -0
  133. package/dist/render/recon/mover.js +86 -0
  134. package/dist/render/recon/resolve.d.ts +34 -4
  135. package/dist/render/recon/resolve.js +161 -20
  136. package/dist/render/renderQuality.d.ts +35 -0
  137. package/dist/render/renderQuality.js +7 -0
  138. package/dist/render/rendererHarness.js +16 -0
  139. package/dist/render/sceneTarget.d.ts +9 -1
  140. package/dist/render/sceneTarget.js +60 -12
  141. package/dist/render/shaders/depth.d.ts +16 -1
  142. package/dist/render/shaders/depth.js +94 -15
  143. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  144. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  145. package/dist/render/shaders/flat/driftLight.js +32 -7
  146. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  147. package/dist/render/shaders/flat/glassTint.js +25 -0
  148. package/dist/render/shaders/flat/index.d.ts +13 -0
  149. package/dist/render/shaders/flat/index.js +9 -3
  150. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  151. package/dist/render/shaders/flat/lampGlass.js +183 -0
  152. package/dist/render/shaders/flat/main.d.ts +1 -1
  153. package/dist/render/shaders/flat/main.js +190 -38
  154. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  155. package/dist/render/shaders/flat/pointShadow.js +2 -0
  156. package/dist/render/shaders/flat/preamble.js +96 -26
  157. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  158. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  159. package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
  160. package/dist/render/shaders/generated/depth.wgsl.js +230 -7
  161. package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
  162. package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
  163. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  164. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  165. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  166. package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
  167. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  168. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  169. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  170. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  171. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  172. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  173. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  174. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  175. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  176. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  177. package/dist/render/shaders/globalMedium.js +6 -6
  178. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  179. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  180. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  181. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  182. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  183. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  184. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  185. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  186. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  187. package/dist/render/shaders/lightVolume.js +6 -6
  188. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  189. package/dist/render/shaders/octahedralResolve.js +48 -17
  190. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  191. package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
  192. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  193. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  194. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  195. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  196. package/dist/render/shaders/rush.js +11 -1
  197. package/dist/render/shaders/sdfText.d.ts +1 -1
  198. package/dist/render/shaders/sdfText.js +3 -2
  199. package/dist/render/shaders/text.d.ts +1 -1
  200. package/dist/render/shaders/text.js +18 -9
  201. package/dist/render/shadowCasters.d.ts +20 -1
  202. package/dist/render/shadowCasters.js +15 -1
  203. package/dist/render/shadowMap.d.ts +54 -5
  204. package/dist/render/shadowMap.js +190 -36
  205. package/dist/render/sunGlassTint.d.ts +25 -0
  206. package/dist/render/sunGlassTint.js +79 -0
  207. package/dist/render/surfaceEffects.d.ts +83 -0
  208. package/dist/render/surfaceEffects.js +75 -0
  209. package/dist/render/surfaceScene.d.ts +8 -0
  210. package/dist/render/surfaceScene.js +8 -0
  211. package/dist/render/surfaceTexture.d.ts +18 -2
  212. package/dist/render/surfaceTexture.js +68 -15
  213. package/dist/render/temporalAa.d.ts +25 -6
  214. package/dist/render/temporalAa.js +45 -15
  215. package/dist/render/textureSource.d.ts +30 -0
  216. package/dist/render/textureSource.js +53 -0
  217. package/dist/render/translucentQueue.d.ts +6 -0
  218. package/dist/render/translucentQueue.js +15 -1
  219. package/dist/render/vertexDefaults.js +2 -0
  220. package/dist/world/hlod.d.ts +104 -0
  221. package/dist/world/hlod.js +214 -0
  222. package/package.json +6 -6
  223. package/src/geometry/boxCsg.ts +186 -0
  224. package/src/geometry/csg.ts +384 -0
  225. package/src/geometry/meshBuilder.ts +15 -0
  226. package/src/geometry/meshConcat.ts +1 -0
  227. package/src/geometry/pixelFont.ts +2 -0
  228. package/src/geometry/solid.ts +189 -0
  229. package/src/geometry/solidFlat.ts +522 -0
  230. package/src/geometry/solidHarness.ts +83 -0
  231. package/src/geometry/solidNormals.ts +103 -0
  232. package/src/geometry/solidRound.ts +443 -0
  233. package/src/geometry/solidWriter.ts +58 -0
  234. package/src/index.ts +61 -1
  235. package/src/render/backend/api.ts +36 -11
  236. package/src/render/backend/drawFog.ts +31 -0
  237. package/src/render/backend/materialChanges.ts +9 -2
  238. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  239. package/src/render/backend/webgl2/instanced.ts +80 -14
  240. package/src/render/backend/webgl2/renderer.ts +700 -250
  241. package/src/render/backend/webgpu/buffers.ts +92 -5
  242. package/src/render/backend/webgpu/depthPass.ts +7 -2
  243. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  244. package/src/render/backend/webgpu/flatPass.ts +62 -11
  245. package/src/render/backend/webgpu/glassTintPass.ts +245 -0
  246. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  247. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  248. package/src/render/backend/webgpu/instanced.ts +42 -4
  249. package/src/render/backend/webgpu/latePass.ts +161 -0
  250. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  251. package/src/render/backend/webgpu/motionPass.ts +535 -0
  252. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  253. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  254. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  255. package/src/render/backend/webgpu/renderer.ts +1671 -670
  256. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  257. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  258. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  259. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  260. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  261. package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
  262. package/src/render/driftLight/bake.ts +85 -62
  263. package/src/render/driftLight/denseField.ts +149 -0
  264. package/src/render/driftLight/lightField.ts +33 -18
  265. package/src/render/driftLight/presence.ts +69 -0
  266. package/src/render/driftLight/uniforms.ts +8 -9
  267. package/src/render/driftLight/worldLightField.ts +83 -0
  268. package/src/render/fixtureAtlas.ts +63 -0
  269. package/src/render/glass.ts +74 -0
  270. package/src/render/glassCasters.ts +164 -0
  271. package/src/render/glassShadow.ts +153 -0
  272. package/src/render/globalMediumPass.ts +4 -11
  273. package/src/render/gpudriven/lit.ts +31 -0
  274. package/src/render/gpudriven/materialTable.ts +28 -6
  275. package/src/render/gpudriven/pipeline.ts +4 -3
  276. package/src/render/gpudriven/sunGlass.ts +117 -0
  277. package/src/render/instanceCull.ts +205 -0
  278. package/src/render/instances.ts +16 -0
  279. package/src/render/lightBudget.ts +41 -38
  280. package/src/render/lightGrid.ts +130 -0
  281. package/src/render/mesh.ts +59 -72
  282. package/src/render/occlusion.ts +51 -7
  283. package/src/render/pointGlassTint.ts +250 -0
  284. package/src/render/pointLightSelection.ts +72 -4
  285. package/src/render/pointShadowArray.ts +188 -22
  286. package/src/render/pointShadowMap.ts +41 -0
  287. package/src/render/rainField.ts +39 -5
  288. package/src/render/recon/changeFrames.ts +24 -0
  289. package/src/render/recon/lateDepth.ts +83 -0
  290. package/src/render/recon/lateRouting.ts +29 -0
  291. package/src/render/recon/mover.ts +118 -0
  292. package/src/render/recon/resolve.ts +206 -20
  293. package/src/render/renderQuality.ts +47 -0
  294. package/src/render/rendererHarness.ts +16 -0
  295. package/src/render/sceneTarget.ts +62 -11
  296. package/src/render/shaders/depth.ts +118 -15
  297. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  298. package/src/render/shaders/flat/driftLight.ts +32 -7
  299. package/src/render/shaders/flat/glassTint.ts +25 -0
  300. package/src/render/shaders/flat/index.ts +22 -3
  301. package/src/render/shaders/flat/lampGlass.ts +185 -0
  302. package/src/render/shaders/flat/main.ts +190 -38
  303. package/src/render/shaders/flat/pointShadow.ts +2 -0
  304. package/src/render/shaders/flat/preamble.ts +96 -26
  305. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  306. package/src/render/shaders/generated/depth.wgsl.ts +237 -7
  307. package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
  308. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  309. package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
  310. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  311. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  312. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  313. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  314. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  315. package/src/render/shaders/globalMedium.ts +6 -6
  316. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  317. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  318. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  319. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  320. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  321. package/src/render/shaders/lightVolume.ts +6 -6
  322. package/src/render/shaders/octahedralResolve.ts +50 -17
  323. package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
  324. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  325. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  326. package/src/render/shaders/rush.ts +11 -1
  327. package/src/render/shaders/sdfText.ts +3 -2
  328. package/src/render/shaders/text.ts +18 -9
  329. package/src/render/shadowCasters.ts +28 -1
  330. package/src/render/shadowMap.ts +231 -37
  331. package/src/render/sunGlassTint.ts +86 -0
  332. package/src/render/surfaceEffects.ts +132 -0
  333. package/src/render/surfaceScene.ts +16 -0
  334. package/src/render/surfaceTexture.ts +110 -19
  335. package/src/render/temporalAa.ts +78 -14
  336. package/src/render/textureSource.ts +72 -0
  337. package/src/render/translucentQueue.ts +20 -1
  338. package/src/render/vertexDefaults.ts +2 -0
  339. package/src/world/hlod.ts +290 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Which instances of a batch can be seen: the decision both backends make, and the reference the
3
+ * WebGPU compute cull is held to.
4
+ *
5
+ * **An instance's bounds are the mesh's, placed by its matrix** — the mesh's bounding sphere with its
6
+ * centre through the model matrix and its radius scaled by the largest axis, exactly as
7
+ * `boundsVisible` treats one mesh — so a batch needs no bounds of its own and a caller supplies none.
8
+ * The test is `sphereInFrustum`'s: kept unless wholly beyond one plane.
9
+ *
10
+ * WebGL2 runs `cullInstances` on the CPU; WebGPU runs `CULL_INSTANCES_WGSL`, the same arithmetic, on
11
+ * the device. Both keep what this keeps: `gpu-parity.mjs` compares them on a device. What this gives
12
+ * up is shape: a long thin mesh is culled by the sphere around it, which keeps it in more views than
13
+ * its box would. What would make that wrong is a batch of long thin things seen end on, where a box
14
+ * test pays; nothing in the engine draws those instanced today.
15
+ *
16
+ * Per frame and allocation-free: everything writes into arrays the caller owns.
17
+ */
18
+ import type { MeshInstances } from './instances.ts';
19
+ import type { Bounds } from '../math/bounds.ts';
20
+ import { boxInFrustum } from '../math/frustum.ts';
21
+ import type { FrustumPlanes } from '../math/frustum.ts';
22
+
23
+ /** How many planes a frustum holds, four floats each. */
24
+ const PLANES = 6;
25
+
26
+ /**
27
+ * Indices a culling batch must draw, all its instances together, before culling it instance by
28
+ * instance pays; below it the batch is drawn whole once its box is seen (`batchBoxVisible`).
29
+ *
30
+ * **What a per-instance cull costs is fixed a batch, and what it saves grows with the batch.** On
31
+ * WebGPU it is a dispatch and an indirect draw, and Chrome validates every indirect draw in its GPU
32
+ * process — measured at 8 µs apiece in a city frame whose six hundred small prop batches took one
33
+ * each, 52 fps against 70 with those draws gone. On WebGL2 it is a loop over the instances and an
34
+ * upload of the survivors. A few thousand indices cost a device less than either.
35
+ *
36
+ * What it gives up is the instances of a small batch that stand out of view once its box is in it,
37
+ * which the device transforms and clips for nothing. What would make it wrong is a device whose
38
+ * vertex stage limits the frame while the CPU idles — where this wants to come down.
39
+ */
40
+ export const INSTANCE_CULL_MIN_INDICES = 1 << 14;
41
+
42
+ /**
43
+ * Whether a culling batch of `count` instances of a mesh of `indexCount` indices is culled instance
44
+ * by instance, or drawn whole once its box is seen. See `INSTANCE_CULL_MIN_INDICES`.
45
+ */
46
+ export function cullsInstances(count: number, indexCount: number): boolean {
47
+ return count * indexCount >= INSTANCE_CULL_MIN_INDICES;
48
+ }
49
+
50
+ /**
51
+ * Keep the instances of `data` whose spheres meet `frustum`, compacted in order into `out`, and
52
+ * return how many. `out` must hold as many as `data` does; its count is set.
53
+ */
54
+ export function cullInstances(
55
+ data: MeshInstances,
56
+ local: Bounds,
57
+ frustum: FrustumPlanes,
58
+ out: MeshInstances,
59
+ ): number {
60
+ const count = Math.min(data.count, data.capacity, out.capacity);
61
+ const models = data.models;
62
+ const tints = data.tints;
63
+ let kept = 0;
64
+ for (let i = 0; i < count; i++) {
65
+ sphereOf(models, i, local, scratch);
66
+ if (!sphereMeets(frustum, scratch)) continue;
67
+ /* Copied float by float: a subarray would be a view allocated per instance, per frame. */
68
+ const from = i * 16;
69
+ const to = kept * 16;
70
+ for (let k = 0; k < 16; k++) out.models[to + k] = models[from + k] as number;
71
+ out.tints[kept * 3] = tints[i * 3] as number;
72
+ out.tints[kept * 3 + 1] = tints[i * 3 + 1] as number;
73
+ out.tints[kept * 3 + 2] = tints[i * 3 + 2] as number;
74
+ kept++;
75
+ }
76
+ out.count = kept;
77
+ return kept;
78
+ }
79
+
80
+ /** `sphereInFrustum` over a packed (x, y, z, radius): kept unless wholly beyond one plane. */
81
+ function sphereMeets(frustum: FrustumPlanes, sphere: Float32Array): boolean {
82
+ const x = sphere[0] as number;
83
+ const y = sphere[1] as number;
84
+ const z = sphere[2] as number;
85
+ const radius = sphere[3] as number;
86
+ for (let plane = 0; plane < PLANES; plane++) {
87
+ const at = plane * 4;
88
+ const distance =
89
+ (frustum[at] as number) * x +
90
+ (frustum[at + 1] as number) * y +
91
+ (frustum[at + 2] as number) * z +
92
+ (frustum[at + 3] as number);
93
+ if (distance < -radius) return false;
94
+ }
95
+ return true;
96
+ }
97
+
98
+ /**
99
+ * The world box around every instance's sphere, for testing a whole batch at once: (minX, minY,
100
+ * minZ, maxX, maxY, maxZ) into `out`. A batch holding nothing writes a min above its max.
101
+ *
102
+ * **A box, not a sphere, and the difference is the whole point for a flat batch.** A region of props
103
+ * a hundred metres square has a sphere seventy metres deep underground, where nothing occludes it,
104
+ * so a sphere test never finds a region hidden. The box stays as flat as the props are.
105
+ */
106
+ export function instancesBox(data: MeshInstances, local: Bounds, out: Float32Array): void {
107
+ const count = Math.min(data.count, data.capacity);
108
+ if (count === 0) {
109
+ out[0] = 1;
110
+ out[1] = 1;
111
+ out[2] = 1;
112
+ out[3] = -1;
113
+ out[4] = -1;
114
+ out[5] = -1;
115
+ return;
116
+ }
117
+ let minX = Infinity;
118
+ let minY = Infinity;
119
+ let minZ = Infinity;
120
+ let maxX = -Infinity;
121
+ let maxY = -Infinity;
122
+ let maxZ = -Infinity;
123
+ for (let i = 0; i < count; i++) {
124
+ sphereOf(data.models, i, local, scratch);
125
+ const r = scratch[3] as number;
126
+ minX = Math.min(minX, (scratch[0] as number) - r);
127
+ minY = Math.min(minY, (scratch[1] as number) - r);
128
+ minZ = Math.min(minZ, (scratch[2] as number) - r);
129
+ maxX = Math.max(maxX, (scratch[0] as number) + r);
130
+ maxY = Math.max(maxY, (scratch[1] as number) + r);
131
+ maxZ = Math.max(maxZ, (scratch[2] as number) + r);
132
+ }
133
+ out[0] = minX;
134
+ out[1] = minY;
135
+ out[2] = minZ;
136
+ out[3] = maxX;
137
+ out[4] = maxY;
138
+ out[5] = maxZ;
139
+ }
140
+
141
+ const scratch = new Float32Array(4);
142
+
143
+ /** Instance `i`'s world sphere into `out` as (x, y, z, radius). */
144
+ function sphereOf(models: Float32Array, i: number, local: Bounds, out: Float32Array): void {
145
+ const m = i * 16;
146
+ const cx = local.centre[0] ?? 0;
147
+ const cy = local.centre[1] ?? 0;
148
+ const cz = local.centre[2] ?? 0;
149
+ const m0 = models[m] as number;
150
+ const m1 = models[m + 1] as number;
151
+ const m2 = models[m + 2] as number;
152
+ const m4 = models[m + 4] as number;
153
+ const m5 = models[m + 5] as number;
154
+ const m6 = models[m + 6] as number;
155
+ const m8 = models[m + 8] as number;
156
+ const m9 = models[m + 9] as number;
157
+ const m10 = models[m + 10] as number;
158
+ out[0] = m0 * cx + m4 * cy + m8 * cz + (models[m + 12] as number);
159
+ out[1] = m1 * cx + m5 * cy + m9 * cz + (models[m + 13] as number);
160
+ out[2] = m2 * cx + m6 * cy + m10 * cz + (models[m + 14] as number);
161
+ out[3] =
162
+ local.radius *
163
+ Math.sqrt(
164
+ Math.max(
165
+ m0 * m0 + m1 * m1 + m2 * m2,
166
+ m4 * m4 + m5 * m5 + m6 * m6,
167
+ m8 * m8 + m9 * m9 + m10 * m10,
168
+ ),
169
+ );
170
+ }
171
+
172
+ /** What the batch test asks of an occlusion buffer. `OcclusionBuffer` and both renderers answer it. */
173
+ export interface BoxOccluder {
174
+ occludedBox(min: ArrayLike<number>, max: ArrayLike<number>): boolean;
175
+ }
176
+
177
+ /**
178
+ * Whether a batch's box (`instancesBox`) can be seen at all: inside the frustum, and not behind the
179
+ * declared occluders. One test for a whole region's props, run before any instance is looked at.
180
+ */
181
+ export function batchBoxVisible(
182
+ box: Float32Array,
183
+ frustum: FrustumPlanes,
184
+ occluder: BoxOccluder | null,
185
+ ): boolean {
186
+ const minX = box[0] as number;
187
+ const minY = box[1] as number;
188
+ const minZ = box[2] as number;
189
+ const maxX = box[3] as number;
190
+ const maxY = box[4] as number;
191
+ const maxZ = box[5] as number;
192
+ if (minX > maxX) return false;
193
+ if (!boxInFrustum(frustum, minX, minY, minZ, maxX, maxY, maxZ)) return false;
194
+ if (occluder === null) return true;
195
+ boxMin[0] = minX;
196
+ boxMin[1] = minY;
197
+ boxMin[2] = minZ;
198
+ boxMax[0] = maxX;
199
+ boxMax[1] = maxY;
200
+ boxMax[2] = maxZ;
201
+ return !occluder.occludedBox(boxMin, boxMax);
202
+ }
203
+
204
+ const boxMin = new Float32Array(3);
205
+ const boxMax = new Float32Array(3);
@@ -90,3 +90,19 @@ export function packInstances(instances: MeshInstances, out: Float32Array): void
90
90
  out[at + 18] = tints[t + 2] as number;
91
91
  }
92
92
  }
93
+
94
+ /** How a batch made by `createInstanced` behaves. */
95
+ export interface InstancedOptions {
96
+ /**
97
+ * Cull the camera draw: the whole batch skipped when its instances' sphere is out of view or behind
98
+ * the declared occluders, and otherwise each instance kept only where its sphere meets the view —
99
+ * on the CPU on WebGL2, as compute into an indirect draw on WebGPU — once the batch is large enough
100
+ * to repay that (`cullsInstances`); a smaller one is drawn whole. Shadow, mirror-free motion and
101
+ * blended draws still see every instance. Off by default, because a batch small enough to draw whole
102
+ * pays for nothing it would save.
103
+ *
104
+ * **For a batch spread over space** — a prop across a region, a crowd, traffic. A batch in one
105
+ * place gains only the whole-batch test, which it could as well do itself.
106
+ */
107
+ readonly cull?: boolean;
108
+ }
@@ -38,13 +38,14 @@ export const MAX_POINT_LIGHTS = 16;
38
38
  export const LIVE_POINT_SHADOW_MAPS = 2;
39
39
 
40
40
  /**
41
- * Texture units the directional cascade binds: static, its depth peel, and dynamic.
41
+ * Texture units the sun's shadow binds: one, for its static, moving and peeled layers.
42
42
  *
43
- * Named because the shading pass's total unit count has to be compared against what a
44
- * device offers, and a budget assembled from three literals scattered across two files
45
- * is a budget nobody can check. See the guard in `Renderer`'s constructor.
43
+ * **It was three**, a `sampler2D` per map, until 4.5.0 folded them into one `sampler2DArray`
44
+ * (`shadowMap.ts`) the way the lamps' twelve cubemaps became one — which is where the two tints
45
+ * glass casts with found their units. Named because the shading pass's total has to be compared
46
+ * against what a device offers; see the guard in `Renderer`'s constructor.
46
47
  */
47
- export const DIRECTIONAL_SHADOW_UNITS = 3;
48
+ export const DIRECTIONAL_SHADOW_UNITS = 1;
48
49
 
49
50
  /**
50
51
  * Texture units every point light's shadow binds: one, for all of them.
@@ -142,22 +143,20 @@ export const ENVIRONMENT_TEXTURE_UNIT = EMISSIVE_TEXTURE_UNIT + 1;
142
143
  export const CLUSTER_TABLE_TEXTURE_UNIT = ENVIRONMENT_TEXTURE_UNIT + 1;
143
144
 
144
145
  /**
145
- * Where the photometric atlas binds: one row per IES profile, any number of them.
146
- *
147
- * **One unit for every profile a world holds, which is the whole reason it is an atlas.** A
148
- * fixture's distribution is a curve rather than an image, so a row of 128 floats holds one and a
149
- * texture holds as many as a consumer loads. Per-profile textures would have spent the remaining
150
- * units on the third fixture.
151
- *
152
- * **Spent unconditionally, for the same reason the froxel table is**, and the argument is at that
153
- * declaration: the lookup is a branch on a value rather than a permutation, so the sampler is
154
- * declared in every profile and a declared sampler needs a complete texture bound whether or not
155
- * the branch reads it. With no profile loaded that texture is a single row of ones, which is the
156
- * multiplicative identity — a row of zeros would switch off every light that reached it.
157
- *
158
- * The tenth of sixteen. `CAPABILITIES.md` §3 keeps the running count.
146
+ * Where the fixture atlas binds: a fixture's spot cookies and its photometric profiles, one texture.
147
+ *
148
+ * **Two tables in one binding since 2026-09-30**, which is the fold `textureUnitBudget.test.ts` asks
149
+ * the next sampler for: the widest lit shader bound fifteen of the sixteen fragment samplers WebGL2
150
+ * guarantees, and the cookie atlas and the photometric atlas were two small float tables read with
151
+ * `textureLod`, both 128 texels across. See `fixtureAtlas.ts` for the layout and why cookies sit on
152
+ * top.
153
+ *
154
+ * **Spent unconditionally, for the reason the froxel table is**: both lookups are branches on values
155
+ * rather than permutations, so the sampler is declared in every profile and a declared sampler needs
156
+ * a complete texture. With nothing loaded that is a single row of ones — the multiplicative
157
+ * identity, since a row of zeros would switch off every light that reached it.
159
158
  */
160
- export const IES_ATLAS_TEXTURE_UNIT = CLUSTER_TABLE_TEXTURE_UNIT + 1;
159
+ export const FIXTURE_ATLAS_TEXTURE_UNIT = CLUSTER_TABLE_TEXTURE_UNIT + 1;
161
160
 
162
161
  /**
163
162
  * The joint palette, read by the **vertex** stage.
@@ -176,7 +175,7 @@ export const IES_ATLAS_TEXTURE_UNIT = CLUSTER_TABLE_TEXTURE_UNIT + 1;
176
175
  * What it costs is one of the five units left after the IES atlas. What would make it wrong is
177
176
  * the lit pass growing past sixteen, which `textureUnitBudget.test.ts` asserts against.
178
177
  */
179
- export const SKIN_PALETTE_TEXTURE_UNIT = IES_ATLAS_TEXTURE_UNIT + 1;
178
+ export const SKIN_PALETTE_TEXTURE_UNIT = FIXTURE_ATLAS_TEXTURE_UNIT + 1;
180
179
 
181
180
  /**
182
181
  * A mesh's morph deltas, also read by the vertex stage, and also out of this same sixteen.
@@ -187,22 +186,6 @@ export const SKIN_PALETTE_TEXTURE_UNIT = IES_ATLAS_TEXTURE_UNIT + 1;
187
186
  */
188
187
  export const MORPH_DELTA_TEXTURE_UNIT = SKIN_PALETTE_TEXTURE_UNIT + 1;
189
188
 
190
- /**
191
- * Where the spot-light cookie atlas binds: one row of square tiles, one tile a cookie.
192
- *
193
- * **The fourteenth of sixteen, and two are left.** `CAPABILITIES.md` §3 keeps the running count —
194
- * and had been three units stale for as long as the IES atlas, the skin palette and the morph
195
- * deltas have existed, which is why the number is derived in `textureUnitBudget.test.ts` and only
196
- * quoted in prose.
197
- *
198
- * **Spent unconditionally, for the reason the froxel table and the IES atlas are**: a declared
199
- * sampler needs a complete texture bound whether or not the branch reads it, so a one-texel white
200
- * placeholder stands in it when no consumer has loaded a cookie. White rather than black, because
201
- * it multiplies a light's colour and a black stand-in would switch off every light that reached
202
- * it — the identity `packIesAtlas` chooses for the same reason.
203
- */
204
- export const COOKIE_ATLAS_TEXTURE_UNIT = MORPH_DELTA_TEXTURE_UNIT + 1;
205
-
206
189
  /**
207
190
  * The colour the frame had already drawn, for a refracting surface to sample.
208
191
  *
@@ -216,7 +199,7 @@ export const COOKIE_ATLAS_TEXTURE_UNIT = MORPH_DELTA_TEXTURE_UNIT + 1;
216
199
  * cubemaps became one array, which is what freed the units this one is spending.
217
200
  * `textureUnitBudget.test.ts` carries the phone that budget was written for.
218
201
  */
219
- export const REFRACT_SCENE_TEXTURE_UNIT = COOKIE_ATLAS_TEXTURE_UNIT + 1;
202
+ export const REFRACT_SCENE_TEXTURE_UNIT = MORPH_DELTA_TEXTURE_UNIT + 1;
220
203
 
221
204
  /**
222
205
  * DriftLight's brick index and atlas, the lit program's fourteenth and fifteenth fragment samplers.
@@ -230,6 +213,26 @@ export const REFRACT_SCENE_TEXTURE_UNIT = COOKIE_ATLAS_TEXTURE_UNIT + 1;
230
213
  export const DRIFT_LIGHT_INDEX_TEXTURE_UNIT = REFRACT_SCENE_TEXTURE_UNIT + 1;
231
214
  export const DRIFT_LIGHT_ATLAS_TEXTURE_UNIT = DRIFT_LIGHT_INDEX_TEXTURE_UNIT + 1;
232
215
 
216
+ /**
217
+ * What the sun keeps through glass (`sunGlassTint.ts`), the lit program's fourteenth fragment
218
+ * sampler: the unit the sun's three maps gave back when they became one array.
219
+ */
220
+ export const SUN_GLASS_TINT_TEXTURE_UNIT = DRIFT_LIGHT_ATLAS_TEXTURE_UNIT + 1;
221
+
222
+ /**
223
+ * What each lamp keeps through glass (`pointGlassTint.ts`), a fragment sampler of the lit program.
224
+ * A unit of the combined pool, which WebGL2 guarantees thirty-two of; what a stage may declare is
225
+ * sixteen, which `textureUnitBudget.test.ts` counts from the shader source.
226
+ */
227
+ export const POINT_GLASS_TINT_TEXTURE_UNIT = SUN_GLASS_TINT_TEXTURE_UNIT + 1;
228
+
229
+ /**
230
+ * Each surface layer's effects table (`surfaceEffects.ts`), bound beside the albedo array it
231
+ * describes. **Paid for by folding**: the cookie and photometric atlases became one fixture atlas
232
+ * to free the sampler this takes, so the lit stage still keeps one of its sixteen spare.
233
+ */
234
+ export const SURFACE_EFFECTS_TEXTURE_UNIT = POINT_GLASS_TINT_TEXTURE_UNIT + 1;
235
+
233
236
  /**
234
237
  * A cookie's tile, in texels a side.
235
238
  *
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A world's fixed lights bucketed once, so the frame's exact choice looks only at the lights near
3
+ * the eye rather than at every one of them.
4
+ *
5
+ * **The choice must be the full scan's, exactly, and needs no sort to be.** Both of the choice's
6
+ * lists break a tie in distance by source index, so the order the lights are met in cannot reach the
7
+ * result: what reaches it is the full scan's candidates minus only lights no query could keep —
8
+ * every light whose cell lies within `viewRange + maxRadius` of the eye is gathered, and the
9
+ * selection's own `viewRange + radius` test does the rest.
10
+ *
11
+ * **Cost stated**: built once, from where the lights stood. A light that moves or changes its reach
12
+ * must be re-bucketed by the caller — this is for the city's fixed thousands, and a scene's handful
13
+ * of movers stays with the plain scan.
14
+ *
15
+ * Nothing here allocates after construction; the gather writes into the grid's own scratch.
16
+ */
17
+ import type { PointLightSource } from './pointLightSelection.ts';
18
+
19
+ /** The most cells a grid may span, which keeps a stray light far away from costing a gigabyte. */
20
+ export const LIGHT_GRID_MAX_CELLS = 1 << 22;
21
+
22
+ export interface LightGrid {
23
+ readonly sources: readonly PointLightSource[];
24
+ /** Metres a cell spans on each axis. */
25
+ readonly cell: number;
26
+ /** The world position of cell (0, 0, 0)'s corner. */
27
+ readonly origin: readonly [number, number, number];
28
+ readonly dims: readonly [number, number, number];
29
+ /** Where cell `c`'s lights start in `lights`; `start[c + 1]` is where they end. */
30
+ readonly start: Uint32Array;
31
+ /** Source indices by cell, ascending within each. */
32
+ readonly lights: Uint32Array;
33
+ /** The largest reach of any source, which widens every query. */
34
+ readonly maxRadius: number;
35
+ /** The last gather, cell by cell: `gathered[0..count)`. */
36
+ readonly gathered: Int32Array;
37
+ }
38
+
39
+ /** Bucket `sources` into cells of `cell` metres. Built once; not a frame path. */
40
+ export function createLightGrid(sources: readonly PointLightSource[], cell: number): LightGrid {
41
+ if (!(cell > 0)) throw new Error(`createLightGrid: a cell of ${String(cell)} metres`);
42
+ let minX = Infinity;
43
+ let minY = Infinity;
44
+ let minZ = Infinity;
45
+ let maxX = -Infinity;
46
+ let maxY = -Infinity;
47
+ let maxZ = -Infinity;
48
+ let maxRadius = 0;
49
+ for (const light of sources) {
50
+ minX = Math.min(minX, light.x);
51
+ minY = Math.min(minY, light.y);
52
+ minZ = Math.min(minZ, light.z);
53
+ maxX = Math.max(maxX, light.x);
54
+ maxY = Math.max(maxY, light.y);
55
+ maxZ = Math.max(maxZ, light.z);
56
+ maxRadius = Math.max(maxRadius, light.radius);
57
+ }
58
+ if (sources.length === 0) minX = minY = minZ = maxX = maxY = maxZ = 0;
59
+ const dims: [number, number, number] = [
60
+ Math.floor((maxX - minX) / cell) + 1,
61
+ Math.floor((maxY - minY) / cell) + 1,
62
+ Math.floor((maxZ - minZ) / cell) + 1,
63
+ ];
64
+ const cells = dims[0] * dims[1] * dims[2];
65
+ if (cells > LIGHT_GRID_MAX_CELLS) {
66
+ throw new Error(
67
+ `createLightGrid: these lights span ${dims.join(' × ')} cells of ${cell} m, past ` +
68
+ `${LIGHT_GRID_MAX_CELLS}; a larger cell fits them.`,
69
+ );
70
+ }
71
+ const cellOf = (light: PointLightSource): number =>
72
+ Math.floor((light.x - minX) / cell) +
73
+ dims[0] * (Math.floor((light.y - minY) / cell) + dims[1] * Math.floor((light.z - minZ) / cell));
74
+ const start = new Uint32Array(cells + 1);
75
+ for (const light of sources) {
76
+ const c = cellOf(light);
77
+ start[c + 1] = (start[c + 1] as number) + 1;
78
+ }
79
+ for (let c = 0; c < cells; c++) start[c + 1] = (start[c + 1] as number) + (start[c] as number);
80
+ const lights = new Uint32Array(sources.length);
81
+ const cursor = start.slice(0, cells);
82
+ for (let index = 0; index < sources.length; index++) {
83
+ const c = cellOf(sources[index] as PointLightSource);
84
+ lights[cursor[c] as number] = index;
85
+ cursor[c] = (cursor[c] as number) + 1;
86
+ }
87
+ return {
88
+ sources,
89
+ cell,
90
+ origin: [minX, minY, minZ],
91
+ dims,
92
+ start,
93
+ lights,
94
+ maxRadius,
95
+ gathered: new Int32Array(sources.length),
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Gather into `grid.gathered`, cell by cell, every light whose cell lies within `reach` metres of
101
+ * (x, y, z) on each axis, and answer how many.
102
+ */
103
+ export function gatherLights(
104
+ grid: LightGrid,
105
+ x: number,
106
+ y: number,
107
+ z: number,
108
+ reach: number,
109
+ ): number {
110
+ const { cell, origin, dims, start, lights, gathered } = grid;
111
+ const x0 = Math.max(0, Math.floor((x - reach - origin[0]) / cell));
112
+ const x1 = Math.min(dims[0] - 1, Math.floor((x + reach - origin[0]) / cell));
113
+ const y0 = Math.max(0, Math.floor((y - reach - origin[1]) / cell));
114
+ const y1 = Math.min(dims[1] - 1, Math.floor((y + reach - origin[1]) / cell));
115
+ const z0 = Math.max(0, Math.floor((z - reach - origin[2]) / cell));
116
+ const z1 = Math.min(dims[2] - 1, Math.floor((z + reach - origin[2]) / cell));
117
+ let count = 0;
118
+ for (let cz = z0; cz <= z1; cz++) {
119
+ for (let cy = y0; cy <= y1; cy++) {
120
+ const row = dims[0] * (cy + dims[1] * cz);
121
+ for (let cx = x0; cx <= x1; cx++) {
122
+ const c = row + cx;
123
+ for (let at = start[c] as number; at < (start[c + 1] as number); at++) {
124
+ gathered[count++] = lights[at] as number;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return count;
130
+ }