@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.
- package/CHANGELOG.json +97 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.js +20 -1
- package/dist/render/backend/api.d.ts +34 -14
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +201 -16
- package/dist/render/backend/webgl2/renderer.js +596 -224
- package/dist/render/backend/webgpu/buffers.d.ts +29 -1
- package/dist/render/backend/webgpu/buffers.js +61 -4
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +7 -2
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
- package/dist/render/backend/webgpu/flatPass.js +57 -9
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +206 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +23 -2
- package/dist/render/backend/webgpu/instanced.js +41 -4
- package/dist/render/backend/webgpu/latePass.d.ts +30 -0
- package/dist/render/backend/webgpu/latePass.js +140 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +3 -13
- package/dist/render/backend/webgpu/renderer.d.ts +271 -64
- package/dist/render/backend/webgpu/renderer.js +1395 -580
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +36 -35
- package/dist/render/lightBudget.js +38 -37
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +16 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +60 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +94 -15
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +9 -3
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +190 -38
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +96 -26
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
- package/dist/render/shaders/generated/depth.wgsl.js +230 -7
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
- package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +61 -1
- package/src/render/backend/api.ts +36 -11
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +700 -250
- package/src/render/backend/webgpu/buffers.ts +92 -5
- package/src/render/backend/webgpu/depthPass.ts +7 -2
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +62 -11
- package/src/render/backend/webgpu/glassTintPass.ts +245 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +42 -4
- package/src/render/backend/webgpu/latePass.ts +161 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1671 -670
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +41 -38
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +16 -0
- package/src/render/sceneTarget.ts +62 -11
- package/src/render/shaders/depth.ts +118 -15
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +22 -3
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +190 -38
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +96 -26
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +237 -7
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/translucentQueue.ts +20 -1
- package/src/render/vertexDefaults.ts +2 -0
- 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);
|
package/src/render/instances.ts
CHANGED
|
@@ -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
|
|
41
|
+
* Texture units the sun's shadow binds: one, for its static, moving and peeled layers.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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 =
|
|
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
|
|
146
|
-
*
|
|
147
|
-
* **
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* declared in every profile and a declared sampler needs
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
+
}
|