@multiplekex/shallot 0.2.5 → 0.3.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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/core/component.ts +1 -1
  3. package/src/core/index.ts +1 -13
  4. package/src/core/math.ts +186 -0
  5. package/src/core/state.ts +1 -1
  6. package/src/core/xml.ts +56 -41
  7. package/src/extras/orbit/index.ts +1 -1
  8. package/src/extras/text/index.ts +10 -65
  9. package/src/extras/{water.ts → water/index.ts} +59 -4
  10. package/src/standard/raster/batch.ts +149 -0
  11. package/src/standard/raster/forward.ts +832 -0
  12. package/src/standard/raster/index.ts +146 -472
  13. package/src/standard/raster/shadow.ts +408 -0
  14. package/src/standard/raytracing/bvh/blas.ts +335 -87
  15. package/src/standard/raytracing/bvh/radix.ts +225 -228
  16. package/src/standard/raytracing/bvh/refit.ts +711 -0
  17. package/src/standard/raytracing/bvh/structs.ts +0 -55
  18. package/src/standard/raytracing/bvh/tlas.ts +153 -141
  19. package/src/standard/raytracing/bvh/traverse.ts +72 -64
  20. package/src/standard/raytracing/index.ts +233 -204
  21. package/src/standard/raytracing/instance.ts +30 -18
  22. package/src/standard/raytracing/ray.ts +1 -1
  23. package/src/standard/raytracing/shaders.ts +23 -40
  24. package/src/standard/render/camera.ts +10 -28
  25. package/src/standard/render/data.ts +1 -1
  26. package/src/standard/render/index.ts +68 -12
  27. package/src/standard/render/light.ts +2 -2
  28. package/src/standard/render/mesh.ts +404 -0
  29. package/src/standard/render/overlay.ts +5 -2
  30. package/src/standard/render/postprocess.ts +263 -267
  31. package/src/standard/render/surface/index.ts +81 -12
  32. package/src/standard/render/surface/shaders.ts +265 -11
  33. package/src/standard/render/surface/structs.ts +10 -0
  34. package/src/standard/tween/tween.ts +44 -115
  35. package/src/standard/render/mesh/box.ts +0 -20
  36. package/src/standard/render/mesh/index.ts +0 -315
  37. package/src/standard/render/mesh/plane.ts +0 -11
  38. package/src/standard/render/mesh/sphere.ts +0 -40
  39. package/src/standard/render/mesh/unified.ts +0 -96
  40. package/src/standard/render/surface/compile.ts +0 -65
  41. package/src/standard/render/surface/noise.ts +0 -58
@@ -58,53 +58,6 @@ export function leafIndex(child: number): number {
58
58
 
59
59
  export const LEAF_FLAG_WGSL = /* wgsl */ `const LEAF_FLAG: u32 = 0x80000000u;`;
60
60
 
61
- export const TRIANGLE_STRUCT_WGSL = /* wgsl */ `
62
- struct Triangle {
63
- v0: vec3<f32>,
64
- entityId: u32,
65
- e1: vec3<f32>,
66
- _pad0: u32,
67
- e2: vec3<f32>,
68
- _pad1: u32,
69
- n0_enc: u32,
70
- n1_enc: u32,
71
- n2_enc: u32,
72
- _pad2: u32,
73
- }
74
-
75
- fn octEncode(n: vec3<f32>) -> u32 {
76
- let absSum = abs(n.x) + abs(n.y) + abs(n.z);
77
- var vx = n.x / absSum;
78
- var vy = n.y / absSum;
79
- let vz = n.z / absSum;
80
- if (vz < 0.0) {
81
- let signX = select(-1.0, 1.0, vx >= 0.0);
82
- let signY = select(-1.0, 1.0, vy >= 0.0);
83
- let newVx = (1.0 - abs(vy)) * signX;
84
- let newVy = (1.0 - abs(vx)) * signY;
85
- vx = newVx;
86
- vy = newVy;
87
- }
88
- let x = u32(clamp((vx * 0.5 + 0.5) * 65535.0, 0.0, 65535.0));
89
- let y = u32(clamp((vy * 0.5 + 0.5) * 65535.0, 0.0, 65535.0));
90
- return (y << 16u) | x;
91
- }
92
-
93
- fn octDecode(enc: u32) -> vec3<f32> {
94
- let x = f32(enc & 0xFFFFu) / 65535.0 * 2.0 - 1.0;
95
- let y = f32(enc >> 16u) / 65535.0 * 2.0 - 1.0;
96
- let z = 1.0 - abs(x) - abs(y);
97
- var n: vec3<f32>;
98
- if (z < 0.0) {
99
- let signX = select(-1.0, 1.0, x >= 0.0);
100
- let signY = select(-1.0, 1.0, y >= 0.0);
101
- n = vec3<f32>((1.0 - abs(y)) * signX, (1.0 - abs(x)) * signY, z);
102
- } else {
103
- n = vec3<f32>(x, y, z);
104
- }
105
- return normalize(n);
106
- }`;
107
-
108
61
  export const TREE_NODE_STRUCT_WGSL = /* wgsl */ `
109
62
  struct TreeNode {
110
63
  minX: f32,
@@ -131,16 +84,8 @@ struct BVHNode {
131
84
 
132
85
  export const TREE_NODE_SIZE = 32;
133
86
  export const BVH_NODE_SIZE = 128;
134
- export const BLAS_NODE_SIZE = 32;
135
- export const BLAS_TRIANGLE_SIZE = 64;
136
87
  export const INVALID_NODE = 0xffffffff;
137
88
 
138
- export const BLAS_NODE_STRUCT_WGSL = /* wgsl */ `
139
- struct BLASNode {
140
- minX: f32, minY: f32, minZ: f32, leftChild: u32,
141
- maxX: f32, maxY: f32, maxZ: f32, rightChild: u32,
142
- }`;
143
-
144
89
  export const BLAS_TRIANGLE_STRUCT_WGSL = /* wgsl */ `
145
90
  struct BLASTriangle {
146
91
  v0: vec3<f32>, _pad0: u32,
@@ -1,6 +1,6 @@
1
1
  import { MAX_ENTITIES } from "../../../core";
2
2
  import type { ComputeNode, ExecutionContext } from "../../compute";
3
- import { createRadixSort } from "./radix";
3
+ import { createRadixSort, dispatchRadixSort } from "./radix";
4
4
  import {
5
5
  TREE_NODE_STRUCT_WGSL,
6
6
  BVH_NODE_STRUCT_WGSL,
@@ -724,10 +724,156 @@ export interface TLASConfig {
724
724
  getRaytracing?: () => boolean;
725
725
  }
726
726
 
727
+ interface TLASGPU {
728
+ pipelines: TLASPipelines;
729
+ bindGroups: TLASBindGroups;
730
+ radixSort: Awaited<ReturnType<typeof createRadixSort>>;
731
+ }
732
+
733
+ async function prepareTLAS(device: GPUDevice, config: TLASConfig): Promise<TLASGPU> {
734
+ const [boundsModule, mortonModule, treeModule, propagateModule, collapseModule] =
735
+ await Promise.all([
736
+ device.createShaderModule({ code: boundsShader }),
737
+ device.createShaderModule({ code: mortonShader }),
738
+ device.createShaderModule({ code: treeShader }),
739
+ device.createShaderModule({ code: propagateShader }),
740
+ device.createShaderModule({ code: collapseShader }),
741
+ ]);
742
+
743
+ const [bounds, morton, tree, propagate, collapse, radixSort] = await Promise.all([
744
+ device.createComputePipelineAsync({
745
+ layout: "auto",
746
+ compute: { module: boundsModule, entryPoint: "main" },
747
+ }),
748
+ device.createComputePipelineAsync({
749
+ layout: "auto",
750
+ compute: { module: mortonModule, entryPoint: "main" },
751
+ }),
752
+ device.createComputePipelineAsync({
753
+ layout: "auto",
754
+ compute: { module: treeModule, entryPoint: "main" },
755
+ }),
756
+ device.createComputePipelineAsync({
757
+ layout: "auto",
758
+ compute: { module: propagateModule, entryPoint: "main" },
759
+ }),
760
+ device.createComputePipelineAsync({
761
+ layout: "auto",
762
+ compute: { module: collapseModule, entryPoint: "main" },
763
+ }),
764
+ createRadixSort(device, {
765
+ keys: config.tlas.mortonCodes,
766
+ values: config.tlas.instanceIds,
767
+ count: MAX_ENTITIES,
768
+ }),
769
+ ]);
770
+
771
+ const pipelines = { bounds, morton, tree, propagate, collapse };
772
+
773
+ const bindGroups: TLASBindGroups = {
774
+ bounds: device.createBindGroup({
775
+ layout: pipelines.bounds.getBindGroupLayout(0),
776
+ entries: [
777
+ { binding: 0, resource: { buffer: config.instanceAABBs } },
778
+ { binding: 1, resource: { buffer: config.instanceCount } },
779
+ { binding: 2, resource: { buffer: config.tlas.sceneBounds } },
780
+ { binding: 3, resource: { buffer: config.tlas.entityIds } },
781
+ ],
782
+ }),
783
+ morton: device.createBindGroup({
784
+ layout: pipelines.morton.getBindGroupLayout(0),
785
+ entries: [
786
+ { binding: 0, resource: { buffer: config.instanceAABBs } },
787
+ { binding: 1, resource: { buffer: config.instanceCount } },
788
+ { binding: 2, resource: { buffer: config.tlas.sceneBounds } },
789
+ { binding: 3, resource: { buffer: config.tlas.mortonCodes } },
790
+ { binding: 4, resource: { buffer: config.tlas.instanceIds } },
791
+ { binding: 5, resource: { buffer: config.tlas.entityIds } },
792
+ ],
793
+ }),
794
+ tree: device.createBindGroup({
795
+ layout: pipelines.tree.getBindGroupLayout(0),
796
+ entries: [
797
+ { binding: 0, resource: { buffer: config.tlas.mortonCodes } },
798
+ { binding: 1, resource: { buffer: config.instanceCount } },
799
+ { binding: 2, resource: { buffer: config.tlas.treeNodes } },
800
+ { binding: 3, resource: { buffer: config.tlas.parentIndices } },
801
+ ],
802
+ }),
803
+ propagate: device.createBindGroup({
804
+ layout: pipelines.propagate.getBindGroupLayout(0),
805
+ entries: [
806
+ { binding: 0, resource: { buffer: config.instanceAABBs } },
807
+ { binding: 1, resource: { buffer: config.tlas.instanceIds } },
808
+ { binding: 2, resource: { buffer: config.instanceCount } },
809
+ { binding: 3, resource: { buffer: config.tlas.treeNodes } },
810
+ { binding: 4, resource: { buffer: config.tlas.boundsFlags } },
811
+ { binding: 5, resource: { buffer: config.tlas.parentIndices } },
812
+ ],
813
+ }),
814
+ collapse: device.createBindGroup({
815
+ layout: pipelines.collapse.getBindGroupLayout(0),
816
+ entries: [
817
+ { binding: 0, resource: { buffer: config.tlas.treeNodes } },
818
+ { binding: 1, resource: { buffer: config.instanceCount } },
819
+ { binding: 2, resource: { buffer: config.tlas.parentIndices } },
820
+ { binding: 3, resource: { buffer: config.tlas.bvhNodes } },
821
+ ],
822
+ }),
823
+ };
824
+
825
+ return { pipelines, bindGroups, radixSort };
826
+ }
827
+
828
+ const initBounds = new Int32Array([
829
+ 0x7f7fffff, 0x7f7fffff, 0x7f7fffff, 0, 0x80800000, 0x80800000, 0x80800000, 0,
830
+ ]);
831
+
832
+ function executeTLAS(gpu: TLASGPU, ctx: ExecutionContext, config: TLASConfig): void {
833
+ const { device, encoder } = ctx;
834
+ const workgroups = Math.ceil(MAX_ENTITIES / WORKGROUP_SIZE);
835
+
836
+ device.queue.writeBuffer(config.tlas.sceneBounds, 0, initBounds);
837
+ encoder.clearBuffer(config.tlas.boundsFlags);
838
+ encoder.clearBuffer(config.tlas.parentIndices);
839
+
840
+ const boundsPass = encoder.beginComputePass();
841
+ boundsPass.setPipeline(gpu.pipelines.bounds);
842
+ boundsPass.setBindGroup(0, gpu.bindGroups.bounds);
843
+ boundsPass.dispatchWorkgroups(workgroups);
844
+ boundsPass.end();
845
+
846
+ const mortonPass = encoder.beginComputePass();
847
+ mortonPass.setPipeline(gpu.pipelines.morton);
848
+ mortonPass.setBindGroup(0, gpu.bindGroups.morton);
849
+ mortonPass.dispatchWorkgroups(workgroups);
850
+ mortonPass.end();
851
+
852
+ const sortPass = encoder.beginComputePass();
853
+ dispatchRadixSort(gpu.radixSort, sortPass);
854
+ sortPass.end();
855
+
856
+ const treePass = encoder.beginComputePass();
857
+ treePass.setPipeline(gpu.pipelines.tree);
858
+ treePass.setBindGroup(0, gpu.bindGroups.tree);
859
+ treePass.dispatchWorkgroups(Math.ceil((MAX_ENTITIES - 1) / WORKGROUP_SIZE));
860
+ treePass.end();
861
+
862
+ const propagatePass = encoder.beginComputePass();
863
+ propagatePass.setPipeline(gpu.pipelines.propagate);
864
+ propagatePass.setBindGroup(0, gpu.bindGroups.propagate);
865
+ propagatePass.dispatchWorkgroups(workgroups);
866
+ propagatePass.end();
867
+
868
+ const collapsePass = encoder.beginComputePass();
869
+ collapsePass.setPipeline(gpu.pipelines.collapse);
870
+ collapsePass.setBindGroup(0, gpu.bindGroups.collapse);
871
+ collapsePass.dispatchWorkgroups(Math.ceil((MAX_ENTITIES - 1) / WORKGROUP_SIZE));
872
+ collapsePass.end();
873
+ }
874
+
727
875
  export function createTLASNode(config: TLASConfig): ComputeNode {
728
- let pipelines: TLASPipelines | null = null;
729
- let bindGroups: TLASBindGroups | null = null;
730
- let radixSort: Awaited<ReturnType<typeof createRadixSort>> | null = null;
876
+ let gpu: TLASGPU | null = null;
731
877
 
732
878
  return {
733
879
  id: "tlas",
@@ -743,147 +889,13 @@ export function createTLASNode(config: TLASConfig): ComputeNode {
743
889
  ],
744
890
 
745
891
  async prepare(device: GPUDevice) {
746
- const [boundsModule, mortonModule, treeModule, propagateModule, collapseModule] =
747
- await Promise.all([
748
- device.createShaderModule({ code: boundsShader }),
749
- device.createShaderModule({ code: mortonShader }),
750
- device.createShaderModule({ code: treeShader }),
751
- device.createShaderModule({ code: propagateShader }),
752
- device.createShaderModule({ code: collapseShader }),
753
- ]);
754
-
755
- const [bounds, morton, tree, propagate, collapse, sort] = await Promise.all([
756
- device.createComputePipelineAsync({
757
- layout: "auto",
758
- compute: { module: boundsModule, entryPoint: "main" },
759
- }),
760
- device.createComputePipelineAsync({
761
- layout: "auto",
762
- compute: { module: mortonModule, entryPoint: "main" },
763
- }),
764
- device.createComputePipelineAsync({
765
- layout: "auto",
766
- compute: { module: treeModule, entryPoint: "main" },
767
- }),
768
- device.createComputePipelineAsync({
769
- layout: "auto",
770
- compute: { module: propagateModule, entryPoint: "main" },
771
- }),
772
- device.createComputePipelineAsync({
773
- layout: "auto",
774
- compute: { module: collapseModule, entryPoint: "main" },
775
- }),
776
- createRadixSort(device, {
777
- keys: config.tlas.mortonCodes,
778
- values: config.tlas.instanceIds,
779
- count: MAX_ENTITIES,
780
- }),
781
- ]);
782
-
783
- pipelines = { bounds, morton, tree, propagate, collapse };
784
- radixSort = sort;
785
-
786
- bindGroups = {
787
- bounds: device.createBindGroup({
788
- layout: pipelines.bounds.getBindGroupLayout(0),
789
- entries: [
790
- { binding: 0, resource: { buffer: config.instanceAABBs } },
791
- { binding: 1, resource: { buffer: config.instanceCount } },
792
- { binding: 2, resource: { buffer: config.tlas.sceneBounds } },
793
- { binding: 3, resource: { buffer: config.tlas.entityIds } },
794
- ],
795
- }),
796
- morton: device.createBindGroup({
797
- layout: pipelines.morton.getBindGroupLayout(0),
798
- entries: [
799
- { binding: 0, resource: { buffer: config.instanceAABBs } },
800
- { binding: 1, resource: { buffer: config.instanceCount } },
801
- { binding: 2, resource: { buffer: config.tlas.sceneBounds } },
802
- { binding: 3, resource: { buffer: config.tlas.mortonCodes } },
803
- { binding: 4, resource: { buffer: config.tlas.instanceIds } },
804
- { binding: 5, resource: { buffer: config.tlas.entityIds } },
805
- ],
806
- }),
807
- tree: device.createBindGroup({
808
- layout: pipelines.tree.getBindGroupLayout(0),
809
- entries: [
810
- { binding: 0, resource: { buffer: config.tlas.mortonCodes } },
811
- { binding: 1, resource: { buffer: config.instanceCount } },
812
- { binding: 2, resource: { buffer: config.tlas.treeNodes } },
813
- { binding: 3, resource: { buffer: config.tlas.parentIndices } },
814
- ],
815
- }),
816
- propagate: device.createBindGroup({
817
- layout: pipelines.propagate.getBindGroupLayout(0),
818
- entries: [
819
- { binding: 0, resource: { buffer: config.instanceAABBs } },
820
- { binding: 1, resource: { buffer: config.tlas.instanceIds } },
821
- { binding: 2, resource: { buffer: config.instanceCount } },
822
- { binding: 3, resource: { buffer: config.tlas.treeNodes } },
823
- { binding: 4, resource: { buffer: config.tlas.boundsFlags } },
824
- { binding: 5, resource: { buffer: config.tlas.parentIndices } },
825
- ],
826
- }),
827
- collapse: device.createBindGroup({
828
- layout: pipelines.collapse.getBindGroupLayout(0),
829
- entries: [
830
- { binding: 0, resource: { buffer: config.tlas.treeNodes } },
831
- { binding: 1, resource: { buffer: config.instanceCount } },
832
- { binding: 2, resource: { buffer: config.tlas.parentIndices } },
833
- { binding: 3, resource: { buffer: config.tlas.bvhNodes } },
834
- ],
835
- }),
836
- };
892
+ gpu = await prepareTLAS(device, config);
837
893
  },
838
894
 
839
895
  execute(ctx: ExecutionContext) {
840
896
  if (config.getRaytracing && !config.getRaytracing()) return;
841
-
842
- const { device, encoder } = ctx;
843
-
844
- const workgroups = Math.ceil(MAX_ENTITIES / WORKGROUP_SIZE);
845
-
846
- const initBounds = new Int32Array([
847
- 0x7f7fffff, 0x7f7fffff, 0x7f7fffff, 0, 0x80800000, 0x80800000, 0x80800000, 0,
848
- ]);
849
- device.queue.writeBuffer(config.tlas.sceneBounds, 0, initBounds);
850
-
851
- encoder.clearBuffer(config.tlas.boundsFlags);
852
- encoder.clearBuffer(config.tlas.parentIndices);
853
-
854
- const boundsPass = encoder.beginComputePass();
855
- boundsPass.setPipeline(pipelines!.bounds);
856
- boundsPass.setBindGroup(0, bindGroups!.bounds);
857
- boundsPass.dispatchWorkgroups(workgroups);
858
- boundsPass.end();
859
-
860
- const mortonPass = encoder.beginComputePass();
861
- mortonPass.setPipeline(pipelines!.morton);
862
- mortonPass.setBindGroup(0, bindGroups!.morton);
863
- mortonPass.dispatchWorkgroups(workgroups);
864
- mortonPass.end();
865
-
866
- const sortPass = encoder.beginComputePass();
867
- radixSort!.dispatch(sortPass);
868
- sortPass.end();
869
-
870
- const treePass = encoder.beginComputePass();
871
- treePass.setPipeline(pipelines!.tree);
872
- treePass.setBindGroup(0, bindGroups!.tree);
873
- treePass.dispatchWorkgroups(Math.ceil((MAX_ENTITIES - 1) / WORKGROUP_SIZE));
874
- treePass.end();
875
-
876
- const propagatePass = encoder.beginComputePass();
877
- propagatePass.setPipeline(pipelines!.propagate);
878
- propagatePass.setBindGroup(0, bindGroups!.propagate);
879
- propagatePass.dispatchWorkgroups(workgroups);
880
- propagatePass.end();
881
-
882
- const collapsePass = encoder.beginComputePass();
883
- collapsePass.setPipeline(pipelines!.collapse);
884
- collapsePass.setBindGroup(0, bindGroups!.collapse);
885
- collapsePass.dispatchWorkgroups(Math.ceil((MAX_ENTITIES - 1) / WORKGROUP_SIZE));
886
- collapsePass.end();
897
+ if (!gpu) return;
898
+ executeTLAS(gpu, ctx, config);
887
899
  },
888
900
  };
889
901
  }
@@ -1,11 +1,6 @@
1
- import {
2
- BVH_NODE_STRUCT_WGSL,
3
- LEAF_FLAG_WGSL,
4
- BLAS_NODE_STRUCT_WGSL,
5
- BLAS_TRIANGLE_STRUCT_WGSL,
6
- } from "./structs";
7
-
8
- export const OCT_DECODE_WGSL = /* wgsl */ `
1
+ import { BVH_NODE_STRUCT_WGSL, LEAF_FLAG_WGSL, BLAS_TRIANGLE_STRUCT_WGSL } from "./structs";
2
+
3
+ const OCT_DECODE_WGSL = /* wgsl */ `
9
4
  fn octDecode(enc: u32) -> vec3<f32> {
10
5
  let x = f32(enc & 0xFFFFu) / 65535.0 * 2.0 - 1.0;
11
6
  let y = f32(enc >> 16u) / 65535.0 * 2.0 - 1.0;
@@ -32,15 +27,13 @@ ${OCT_DECODE_WGSL}
32
27
  `;
33
28
 
34
29
  export const TLAS_BLAS_STRUCTS = /* wgsl */ `
35
- ${BLAS_NODE_STRUCT_WGSL}
36
-
37
30
  ${BLAS_TRIANGLE_STRUCT_WGSL}
38
31
  `;
39
32
 
40
33
  export const TLAS_BLAS_BINDINGS = /* wgsl */ `
41
34
  @group(1) @binding(0) var<storage, read> tlasNodes: array<BVHNode>;
42
35
  @group(1) @binding(1) var<storage, read> tlasInstanceIds: array<u32>;
43
- @group(1) @binding(2) var<storage, read> blasNodes: array<BLASNode>;
36
+ @group(1) @binding(2) var<storage, read> blasNodes: array<BVHNode>;
44
37
  @group(1) @binding(3) var<storage, read> blasTriIds: array<u32>;
45
38
  @group(1) @binding(4) var<storage, read> blasTriangles: array<BLASTriangle>;
46
39
  @group(1) @binding(5) var<storage, read> blasMeta: array<u32>;
@@ -142,7 +135,7 @@ fn traceBLAS(
142
135
  nodeOffset: u32,
143
136
  triIdOffset: u32,
144
137
  triOffset: u32,
145
- triCount_: u32,
138
+ totalTriCount: u32,
146
139
  maxT: f32
147
140
  ) -> HitResult {
148
141
  var closest: HitResult;
@@ -154,17 +147,7 @@ fn traceBLAS(
154
147
  closest.normal = vec3(0.0, 1.0, 0.0);
155
148
  closest.worldPos = vec3(0.0);
156
149
 
157
- if (triCount_ == 0u) {
158
- return closest;
159
- }
160
-
161
- if (triCount_ == 1u) {
162
- let triIdx = blasTriIds[triIdOffset];
163
- let tri = blasTriangles[triOffset + triIdx];
164
- let hit = intersectBLASTriangle(ray, tri);
165
- if (hit.hit && hit.t < maxT) {
166
- return hit;
167
- }
150
+ if (totalTriCount == 0u) {
168
151
  return closest;
169
152
  }
170
153
 
@@ -177,7 +160,7 @@ fn traceBLAS(
177
160
  stackPtr++;
178
161
 
179
162
  var iterations = 0u;
180
- let maxIterations = min(triCount_ * 3u, 10000u);
163
+ let maxIterations = min(totalTriCount * 3u, 10000u);
181
164
 
182
165
  while (stackPtr > 0u && iterations < maxIterations) {
183
166
  iterations++;
@@ -185,50 +168,75 @@ fn traceBLAS(
185
168
  let localIdx = stack[stackPtr];
186
169
  let node = blasNodes[nodeOffset + localIdx];
187
170
 
188
- let leftChild = node.leftChild;
189
- let rightChild = node.rightChild;
171
+ var children: array<u32, 4>;
172
+ var dists: array<f32, 4>;
190
173
 
191
- if (leftChild != INVALID_NODE) {
192
- if (isLeaf(leftChild)) {
193
- let leafIdx = leafIndex(leftChild);
194
- let triIdx = blasTriIds[triIdOffset + leafIdx];
195
- let tri = blasTriangles[triOffset + triIdx];
196
- let hit = intersectBLASTriangle(ray, tri);
197
- if (hit.hit && hit.t < closest.t) {
198
- closest = hit;
199
- }
200
- } else {
201
- let leftNode = blasNodes[nodeOffset + leftChild];
202
- let leftMin = vec3(leftNode.minX, leftNode.minY, leftNode.minZ);
203
- let leftMax = vec3(leftNode.maxX, leftNode.maxY, leftNode.maxZ);
204
- let leftDist = intersectAABBDist(ray.origin, invDir, leftMin, leftMax);
205
-
206
- if (leftDist < closest.t && stackPtr < MAX_STACK_DEPTH) {
207
- stack[stackPtr] = leftChild;
208
- stackPtr++;
209
- }
174
+ children[0] = node.child0;
175
+ children[1] = node.child1;
176
+ children[2] = node.child2;
177
+ children[3] = node.child3;
178
+
179
+ dists[0] = select(
180
+ intersectAABBDist(ray.origin, invDir,
181
+ vec3(node.c0_minX, node.c0_minY, node.c0_minZ),
182
+ vec3(node.c0_maxX, node.c0_maxY, node.c0_maxZ)),
183
+ 1e30,
184
+ children[0] == INVALID_NODE
185
+ );
186
+ dists[1] = select(
187
+ intersectAABBDist(ray.origin, invDir,
188
+ vec3(node.c1_minX, node.c1_minY, node.c1_minZ),
189
+ vec3(node.c1_maxX, node.c1_maxY, node.c1_maxZ)),
190
+ 1e30,
191
+ children[1] == INVALID_NODE
192
+ );
193
+ dists[2] = select(
194
+ intersectAABBDist(ray.origin, invDir,
195
+ vec3(node.c2_minX, node.c2_minY, node.c2_minZ),
196
+ vec3(node.c2_maxX, node.c2_maxY, node.c2_maxZ)),
197
+ 1e30,
198
+ children[2] == INVALID_NODE
199
+ );
200
+ dists[3] = select(
201
+ intersectAABBDist(ray.origin, invDir,
202
+ vec3(node.c3_minX, node.c3_minY, node.c3_minZ),
203
+ vec3(node.c3_maxX, node.c3_maxY, node.c3_maxZ)),
204
+ 1e30,
205
+ children[3] == INVALID_NODE
206
+ );
207
+
208
+ for (var i = 1u; i < 4u; i++) {
209
+ let keyDist = dists[i];
210
+ let keyChild = children[i];
211
+ var j = i;
212
+ while (j > 0u && dists[j - 1u] > keyDist) {
213
+ dists[j] = dists[j - 1u];
214
+ children[j] = children[j - 1u];
215
+ j--;
210
216
  }
217
+ dists[j] = keyDist;
218
+ children[j] = keyChild;
211
219
  }
212
220
 
213
- if (rightChild != INVALID_NODE) {
214
- if (isLeaf(rightChild)) {
215
- let leafIdx = leafIndex(rightChild);
221
+ for (var i = 3i; i >= 0i; i--) {
222
+ let child = children[i];
223
+ let dist = dists[i];
224
+
225
+ if (child == INVALID_NODE || dist >= closest.t) {
226
+ continue;
227
+ }
228
+
229
+ if (isLeaf(child)) {
230
+ let leafIdx = leafIndex(child);
216
231
  let triIdx = blasTriIds[triIdOffset + leafIdx];
217
232
  let tri = blasTriangles[triOffset + triIdx];
218
233
  let hit = intersectBLASTriangle(ray, tri);
219
234
  if (hit.hit && hit.t < closest.t) {
220
235
  closest = hit;
221
236
  }
222
- } else {
223
- let rightNode = blasNodes[nodeOffset + rightChild];
224
- let rightMin = vec3(rightNode.minX, rightNode.minY, rightNode.minZ);
225
- let rightMax = vec3(rightNode.maxX, rightNode.maxY, rightNode.maxZ);
226
- let rightDist = intersectAABBDist(ray.origin, invDir, rightMin, rightMax);
227
-
228
- if (rightDist < closest.t && stackPtr < MAX_STACK_DEPTH) {
229
- stack[stackPtr] = rightChild;
230
- stackPtr++;
231
- }
237
+ } else if (stackPtr < MAX_STACK_DEPTH) {
238
+ stack[stackPtr] = child;
239
+ stackPtr++;
232
240
  }
233
241
  }
234
242
  }
@@ -334,9 +342,9 @@ fn trace(ray: Ray) -> HitResult {
334
342
  let nodeOffset = blasMeta[shapeId * 4u];
335
343
  let triIdOffset = blasMeta[shapeId * 4u + 1u];
336
344
  let triOffset = blasMeta[shapeId * 4u + 2u];
337
- let triCount_ = blasMeta[shapeId * 4u + 3u];
345
+ let totalTriCount = blasMeta[shapeId * 4u + 3u];
338
346
 
339
- if (triCount_ == 0u) {
347
+ if (totalTriCount == 0u) {
340
348
  continue;
341
349
  }
342
350
 
@@ -345,7 +353,7 @@ fn trace(ray: Ray) -> HitResult {
345
353
  objRay.origin = (invMatrix * vec4(ray.origin, 1.0)).xyz;
346
354
  objRay.direction = (invMatrix * vec4(ray.direction, 0.0)).xyz;
347
355
 
348
- let blasHit = traceBLAS(objRay, nodeOffset, triIdOffset, triOffset, triCount_, closest.t);
356
+ let blasHit = traceBLAS(objRay, nodeOffset, triIdOffset, triOffset, totalTriCount, closest.t);
349
357
 
350
358
  if (blasHit.hit && blasHit.t < closest.t) {
351
359
  closest = blasHit;
@@ -440,9 +448,9 @@ fn traceAnyHit(ray: Ray, tMax: f32) -> bool {
440
448
  let nodeOffset = blasMeta[shapeId * 4u];
441
449
  let triIdOffset = blasMeta[shapeId * 4u + 1u];
442
450
  let triOffset = blasMeta[shapeId * 4u + 2u];
443
- let triCount_ = blasMeta[shapeId * 4u + 3u];
451
+ let totalTriCount = blasMeta[shapeId * 4u + 3u];
444
452
 
445
- if (triCount_ == 0u) {
453
+ if (totalTriCount == 0u) {
446
454
  continue;
447
455
  }
448
456
 
@@ -451,7 +459,7 @@ fn traceAnyHit(ray: Ray, tMax: f32) -> bool {
451
459
  objRay.origin = (invMatrix * vec4(ray.origin, 1.0)).xyz;
452
460
  objRay.direction = (invMatrix * vec4(ray.direction, 0.0)).xyz;
453
461
 
454
- let blasHit = traceBLAS(objRay, nodeOffset, triIdOffset, triOffset, triCount_, tMax);
462
+ let blasHit = traceBLAS(objRay, nodeOffset, triIdOffset, triOffset, totalTriCount, tMax);
455
463
  if (blasHit.hit && blasHit.t < tMax) {
456
464
  return true;
457
465
  }