@multiplekex/shallot 0.1.12 → 0.2.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 (62) hide show
  1. package/package.json +3 -4
  2. package/src/core/builder.ts +71 -32
  3. package/src/core/component.ts +25 -11
  4. package/src/core/index.ts +14 -13
  5. package/src/core/math.ts +135 -0
  6. package/src/core/runtime.ts +0 -1
  7. package/src/core/state.ts +9 -68
  8. package/src/core/xml.ts +381 -265
  9. package/src/editor/format.ts +5 -0
  10. package/src/editor/index.ts +101 -0
  11. package/src/extras/arrows/index.ts +28 -69
  12. package/src/extras/gradient/index.ts +36 -52
  13. package/src/extras/lines/index.ts +51 -122
  14. package/src/extras/orbit/index.ts +40 -15
  15. package/src/extras/text/font.ts +546 -0
  16. package/src/extras/text/index.ts +158 -204
  17. package/src/extras/text/sdf.ts +429 -0
  18. package/src/standard/activity/index.ts +172 -0
  19. package/src/standard/compute/graph.ts +23 -23
  20. package/src/standard/compute/index.ts +76 -61
  21. package/src/standard/defaults.ts +8 -5
  22. package/src/standard/index.ts +1 -0
  23. package/src/standard/input/index.ts +30 -19
  24. package/src/standard/loading/index.ts +18 -13
  25. package/src/standard/render/bvh/blas.ts +752 -0
  26. package/src/standard/render/bvh/radix.ts +476 -0
  27. package/src/standard/render/bvh/structs.ts +167 -0
  28. package/src/standard/render/bvh/tlas.ts +886 -0
  29. package/src/standard/render/bvh/traverse.ts +467 -0
  30. package/src/standard/render/camera.ts +302 -27
  31. package/src/standard/render/data.ts +93 -0
  32. package/src/standard/render/depth.ts +117 -0
  33. package/src/standard/render/forward/index.ts +259 -0
  34. package/src/standard/render/forward/raster.ts +228 -0
  35. package/src/standard/render/index.ts +443 -70
  36. package/src/standard/render/indirect.ts +40 -0
  37. package/src/standard/render/instance.ts +214 -0
  38. package/src/standard/render/intersection.ts +72 -0
  39. package/src/standard/render/light.ts +16 -16
  40. package/src/standard/render/mesh/index.ts +67 -75
  41. package/src/standard/render/mesh/unified.ts +96 -0
  42. package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
  43. package/src/standard/render/pass.ts +10 -4
  44. package/src/standard/render/postprocess.ts +142 -64
  45. package/src/standard/render/ray.ts +61 -0
  46. package/src/standard/render/scene.ts +38 -164
  47. package/src/standard/render/shaders.ts +484 -0
  48. package/src/standard/render/surface/compile.ts +3 -10
  49. package/src/standard/render/surface/index.ts +60 -30
  50. package/src/standard/render/surface/noise.ts +45 -0
  51. package/src/standard/render/surface/structs.ts +60 -19
  52. package/src/standard/render/surface/wgsl.ts +573 -0
  53. package/src/standard/render/triangle.ts +84 -0
  54. package/src/standard/transforms/index.ts +4 -6
  55. package/src/standard/tween/index.ts +10 -1
  56. package/src/standard/tween/sequence.ts +24 -16
  57. package/src/standard/tween/tween.ts +67 -16
  58. package/src/core/types.ts +0 -37
  59. package/src/standard/compute/inspect.ts +0 -201
  60. package/src/standard/compute/pass.ts +0 -23
  61. package/src/standard/compute/timing.ts +0 -139
  62. package/src/standard/render/forward.ts +0 -273
@@ -0,0 +1,214 @@
1
+ import type { ComputeNode, ExecutionContext } from "../compute";
2
+
3
+ const WORKGROUP_SIZE = 64;
4
+
5
+ const shader = /* wgsl */ `
6
+ struct ShapeAABB {
7
+ minX: f32,
8
+ minY: f32,
9
+ minZ: f32,
10
+ _pad0: u32,
11
+ maxX: f32,
12
+ maxY: f32,
13
+ maxZ: f32,
14
+ _pad1: u32,
15
+ }
16
+
17
+ struct InstanceAABB {
18
+ minX: f32,
19
+ minY: f32,
20
+ minZ: f32,
21
+ _pad0: u32,
22
+ maxX: f32,
23
+ maxY: f32,
24
+ maxZ: f32,
25
+ _pad1: u32,
26
+ }
27
+
28
+ @group(0) @binding(0) var<storage, read> matrices: array<mat4x4<f32>>;
29
+ @group(0) @binding(1) var<storage, read> sizes: array<vec4<f32>>;
30
+ @group(0) @binding(2) var<storage, read> shapes: array<u32>;
31
+ @group(0) @binding(3) var<storage, read> shapeAABBs: array<ShapeAABB>;
32
+ @group(0) @binding(4) var<storage, read> entityCount: array<u32>;
33
+ @group(0) @binding(5) var<storage, read_write> instanceAABBs: array<InstanceAABB>;
34
+ @group(0) @binding(6) var<storage, read_write> instanceInverses: array<mat4x4<f32>>;
35
+
36
+ fn scaleColumns(m: mat4x4<f32>, s: vec3<f32>) -> mat4x4<f32> {
37
+ return mat4x4<f32>(
38
+ m[0] * s.x,
39
+ m[1] * s.y,
40
+ m[2] * s.z,
41
+ m[3]
42
+ );
43
+ }
44
+
45
+ fn transformPoint(p: vec3<f32>, m: mat4x4<f32>) -> vec3<f32> {
46
+ return (m * vec4<f32>(p, 1.0)).xyz;
47
+ }
48
+
49
+ fn transformAABB(aabbMin: vec3<f32>, aabbMax: vec3<f32>, m: mat4x4<f32>) -> array<vec3<f32>, 2> {
50
+ let corners = array<vec3<f32>, 8>(
51
+ vec3<f32>(aabbMin.x, aabbMin.y, aabbMin.z),
52
+ vec3<f32>(aabbMin.x, aabbMin.y, aabbMax.z),
53
+ vec3<f32>(aabbMin.x, aabbMax.y, aabbMin.z),
54
+ vec3<f32>(aabbMin.x, aabbMax.y, aabbMax.z),
55
+ vec3<f32>(aabbMax.x, aabbMin.y, aabbMin.z),
56
+ vec3<f32>(aabbMax.x, aabbMin.y, aabbMax.z),
57
+ vec3<f32>(aabbMax.x, aabbMax.y, aabbMin.z),
58
+ vec3<f32>(aabbMax.x, aabbMax.y, aabbMax.z)
59
+ );
60
+
61
+ var newMin = vec3<f32>(1e30, 1e30, 1e30);
62
+ var newMax = vec3<f32>(-1e30, -1e30, -1e30);
63
+
64
+ for (var i = 0u; i < 8u; i++) {
65
+ let t = transformPoint(corners[i], m);
66
+ newMin = min(newMin, t);
67
+ newMax = max(newMax, t);
68
+ }
69
+
70
+ return array<vec3<f32>, 2>(newMin, newMax);
71
+ }
72
+
73
+ fn inverse4x4(m: mat4x4<f32>) -> mat4x4<f32> {
74
+ let m00 = m[0][0]; let m10 = m[0][1]; let m20 = m[0][2]; let m30 = m[0][3];
75
+ let m01 = m[1][0]; let m11 = m[1][1]; let m21 = m[1][2]; let m31 = m[1][3];
76
+ let m02 = m[2][0]; let m12 = m[2][1]; let m22 = m[2][2]; let m32 = m[2][3];
77
+ let m03 = m[3][0]; let m13 = m[3][1]; let m23 = m[3][2]; let m33 = m[3][3];
78
+
79
+ let c00 = m11 * (m22 * m33 - m32 * m23) - m21 * (m12 * m33 - m32 * m13) + m31 * (m12 * m23 - m22 * m13);
80
+ let c01 = -(m01 * (m22 * m33 - m32 * m23) - m21 * (m02 * m33 - m32 * m03) + m31 * (m02 * m23 - m22 * m03));
81
+ let c02 = m01 * (m12 * m33 - m32 * m13) - m11 * (m02 * m33 - m32 * m03) + m31 * (m02 * m13 - m12 * m03);
82
+ let c03 = -(m01 * (m12 * m23 - m22 * m13) - m11 * (m02 * m23 - m22 * m03) + m21 * (m02 * m13 - m12 * m03));
83
+
84
+ let c10 = -(m10 * (m22 * m33 - m32 * m23) - m20 * (m12 * m33 - m32 * m13) + m30 * (m12 * m23 - m22 * m13));
85
+ let c11 = m00 * (m22 * m33 - m32 * m23) - m20 * (m02 * m33 - m32 * m03) + m30 * (m02 * m23 - m22 * m03);
86
+ let c12 = -(m00 * (m12 * m33 - m32 * m13) - m10 * (m02 * m33 - m32 * m03) + m30 * (m02 * m13 - m12 * m03));
87
+ let c13 = m00 * (m12 * m23 - m22 * m13) - m10 * (m02 * m23 - m22 * m03) + m20 * (m02 * m13 - m12 * m03);
88
+
89
+ let c20 = m10 * (m21 * m33 - m31 * m23) - m20 * (m11 * m33 - m31 * m13) + m30 * (m11 * m23 - m21 * m13);
90
+ let c21 = -(m00 * (m21 * m33 - m31 * m23) - m20 * (m01 * m33 - m31 * m03) + m30 * (m01 * m23 - m21 * m03));
91
+ let c22 = m00 * (m11 * m33 - m31 * m13) - m10 * (m01 * m33 - m31 * m03) + m30 * (m01 * m13 - m11 * m03);
92
+ let c23 = -(m00 * (m11 * m23 - m21 * m13) - m10 * (m01 * m23 - m21 * m03) + m20 * (m01 * m13 - m11 * m03));
93
+
94
+ let c30 = -(m10 * (m21 * m32 - m31 * m22) - m20 * (m11 * m32 - m31 * m12) + m30 * (m11 * m22 - m21 * m12));
95
+ let c31 = m00 * (m21 * m32 - m31 * m22) - m20 * (m01 * m32 - m31 * m02) + m30 * (m01 * m22 - m21 * m02);
96
+ let c32 = -(m00 * (m11 * m32 - m31 * m12) - m10 * (m01 * m32 - m31 * m02) + m30 * (m01 * m12 - m11 * m02));
97
+ let c33 = m00 * (m11 * m22 - m21 * m12) - m10 * (m01 * m22 - m21 * m02) + m20 * (m01 * m12 - m11 * m02);
98
+
99
+ let det = m00 * c00 + m01 * c10 + m02 * c20 + m03 * c30;
100
+ let invDet = select(0.0, 1.0 / det, abs(det) > 1e-10);
101
+
102
+ return mat4x4<f32>(
103
+ vec4<f32>(c00, c10, c20, c30) * invDet,
104
+ vec4<f32>(c01, c11, c21, c31) * invDet,
105
+ vec4<f32>(c02, c12, c22, c32) * invDet,
106
+ vec4<f32>(c03, c13, c23, c33) * invDet
107
+ );
108
+ }
109
+
110
+ @compute @workgroup_size(${WORKGROUP_SIZE})
111
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
112
+ let eid = gid.x;
113
+ let count = entityCount[0];
114
+ if (eid >= count) { return; }
115
+
116
+ let shapeId = shapes[eid];
117
+ let shapeAABB = shapeAABBs[shapeId];
118
+ let matrix = matrices[eid];
119
+ let size = sizes[eid].xyz;
120
+
121
+ let hasZeroScale = size.x == 0.0 || size.y == 0.0 || size.z == 0.0;
122
+
123
+ if (hasZeroScale) {
124
+ var zeroAABB: InstanceAABB;
125
+ zeroAABB.minX = 0.0;
126
+ zeroAABB.minY = 0.0;
127
+ zeroAABB.minZ = 0.0;
128
+ zeroAABB._pad0 = 0u;
129
+ zeroAABB.maxX = 0.0;
130
+ zeroAABB.maxY = 0.0;
131
+ zeroAABB.maxZ = 0.0;
132
+ zeroAABB._pad1 = 0u;
133
+ instanceAABBs[eid] = zeroAABB;
134
+ instanceInverses[eid] = mat4x4<f32>();
135
+ return;
136
+ }
137
+
138
+ let scaledMatrix = scaleColumns(matrix, size);
139
+
140
+ let aabbMin = vec3<f32>(shapeAABB.minX, shapeAABB.minY, shapeAABB.minZ);
141
+ let aabbMax = vec3<f32>(shapeAABB.maxX, shapeAABB.maxY, shapeAABB.maxZ);
142
+ let worldAABB = transformAABB(aabbMin, aabbMax, scaledMatrix);
143
+
144
+ var outAABB: InstanceAABB;
145
+ outAABB.minX = worldAABB[0].x;
146
+ outAABB.minY = worldAABB[0].y;
147
+ outAABB.minZ = worldAABB[0].z;
148
+ outAABB._pad0 = 0u;
149
+ outAABB.maxX = worldAABB[1].x;
150
+ outAABB.maxY = worldAABB[1].y;
151
+ outAABB.maxZ = worldAABB[1].z;
152
+ outAABB._pad1 = 0u;
153
+ instanceAABBs[eid] = outAABB;
154
+
155
+ instanceInverses[eid] = inverse4x4(scaledMatrix);
156
+ }
157
+ `;
158
+
159
+ export interface InstanceConfig {
160
+ matrices: GPUBuffer;
161
+ sizes: GPUBuffer;
162
+ shapes: GPUBuffer;
163
+ shapeAABBs: GPUBuffer;
164
+ entityCount: GPUBuffer;
165
+ instanceAABBs: GPUBuffer;
166
+ instanceInverses: GPUBuffer;
167
+ getEntityCount: () => number;
168
+ }
169
+
170
+ export function createInstanceNode(config: InstanceConfig): ComputeNode {
171
+ let pipeline: GPUComputePipeline | null = null;
172
+ let bindGroup: GPUBindGroup | null = null;
173
+
174
+ return {
175
+ id: "instance",
176
+ inputs: [],
177
+ outputs: [
178
+ { id: "instance-aabbs", access: "write" },
179
+ { id: "instance-inverses", access: "write" },
180
+ ],
181
+
182
+ async prepare(device: GPUDevice) {
183
+ const module = await device.createShaderModule({ code: shader });
184
+
185
+ pipeline = await device.createComputePipelineAsync({
186
+ layout: "auto",
187
+ compute: { module, entryPoint: "main" },
188
+ });
189
+
190
+ bindGroup = device.createBindGroup({
191
+ layout: pipeline.getBindGroupLayout(0),
192
+ entries: [
193
+ { binding: 0, resource: { buffer: config.matrices } },
194
+ { binding: 1, resource: { buffer: config.sizes } },
195
+ { binding: 2, resource: { buffer: config.shapes } },
196
+ { binding: 3, resource: { buffer: config.shapeAABBs } },
197
+ { binding: 4, resource: { buffer: config.entityCount } },
198
+ { binding: 5, resource: { buffer: config.instanceAABBs } },
199
+ { binding: 6, resource: { buffer: config.instanceInverses } },
200
+ ],
201
+ });
202
+ },
203
+
204
+ execute(ctx: ExecutionContext) {
205
+ const workgroups = Math.ceil(config.getEntityCount() / WORKGROUP_SIZE);
206
+
207
+ const pass = ctx.encoder.beginComputePass();
208
+ pass.setPipeline(pipeline!);
209
+ pass.setBindGroup(0, bindGroup!);
210
+ pass.dispatchWorkgroups(workgroups);
211
+ pass.end();
212
+ },
213
+ };
214
+ }
@@ -0,0 +1,72 @@
1
+ const EPSILON = 1e-7;
2
+
3
+ export interface RayTriangleResult {
4
+ hit: boolean;
5
+ t: number;
6
+ u: number;
7
+ v: number;
8
+ }
9
+
10
+ export function intersectRayTriangle(
11
+ originX: number,
12
+ originY: number,
13
+ originZ: number,
14
+ directionX: number,
15
+ directionY: number,
16
+ directionZ: number,
17
+ v0x: number,
18
+ v0y: number,
19
+ v0z: number,
20
+ v1x: number,
21
+ v1y: number,
22
+ v1z: number,
23
+ v2x: number,
24
+ v2y: number,
25
+ v2z: number
26
+ ): RayTriangleResult {
27
+ const e1x = v1x - v0x;
28
+ const e1y = v1y - v0y;
29
+ const e1z = v1z - v0z;
30
+
31
+ const e2x = v2x - v0x;
32
+ const e2y = v2y - v0y;
33
+ const e2z = v2z - v0z;
34
+
35
+ const hx = directionY * e2z - directionZ * e2y;
36
+ const hy = directionZ * e2x - directionX * e2z;
37
+ const hz = directionX * e2y - directionY * e2x;
38
+
39
+ const a = e1x * hx + e1y * hy + e1z * hz;
40
+
41
+ if (a > -EPSILON && a < EPSILON) {
42
+ return { hit: false, t: 0, u: 0, v: 0 };
43
+ }
44
+
45
+ const f = 1 / a;
46
+
47
+ const sx = originX - v0x;
48
+ const sy = originY - v0y;
49
+ const sz = originZ - v0z;
50
+
51
+ const u = f * (sx * hx + sy * hy + sz * hz);
52
+ if (u < 0 || u > 1) {
53
+ return { hit: false, t: 0, u: 0, v: 0 };
54
+ }
55
+
56
+ const qx = sy * e1z - sz * e1y;
57
+ const qy = sz * e1x - sx * e1z;
58
+ const qz = sx * e1y - sy * e1x;
59
+
60
+ const v = f * (directionX * qx + directionY * qy + directionZ * qz);
61
+ if (v < 0 || u + v > 1) {
62
+ return { hit: false, t: 0, u: 0, v: 0 };
63
+ }
64
+
65
+ const t = f * (e2x * qx + e2y * qy + e2z * qz);
66
+
67
+ if (t > EPSILON) {
68
+ return { hit: true, t, u, v };
69
+ }
70
+
71
+ return { hit: false, t: 0, u: 0, v: 0 };
72
+ }
@@ -21,7 +21,7 @@ export const DirectionalLight = {
21
21
  setTraits(DirectionalLight, {
22
22
  defaults: () => ({
23
23
  color: 0xffffff,
24
- intensity: 1.0,
24
+ intensity: 0.8,
25
25
  directionX: -0.6,
26
26
  directionY: -1.0,
27
27
  directionZ: -0.8,
@@ -49,33 +49,33 @@ export interface DirectionalLightData {
49
49
  directionZ: number;
50
50
  }
51
51
 
52
+ const lightData = new Float32Array(12);
53
+
52
54
  export function packLightUniforms(
53
55
  ambient: AmbientLightData,
54
56
  directional: DirectionalLightData
55
57
  ): Float32Array {
56
- const data = new Float32Array(12);
57
-
58
58
  const ambientRgb = unpackColor(ambient.color);
59
- data[0] = ambientRgb.r;
60
- data[1] = ambientRgb.g;
61
- data[2] = ambientRgb.b;
62
- data[3] = ambient.intensity;
59
+ lightData[0] = ambientRgb.r;
60
+ lightData[1] = ambientRgb.g;
61
+ lightData[2] = ambientRgb.b;
62
+ lightData[3] = ambient.intensity;
63
63
 
64
64
  const [dx, dy, dz] = normalizeDirection(
65
65
  directional.directionX,
66
66
  directional.directionY,
67
67
  directional.directionZ
68
68
  );
69
- data[4] = dx;
70
- data[5] = dy;
71
- data[6] = dz;
72
- data[7] = 0;
69
+ lightData[4] = dx;
70
+ lightData[5] = dy;
71
+ lightData[6] = dz;
72
+ lightData[7] = 0;
73
73
 
74
74
  const sunRgb = unpackColor(directional.color);
75
- data[8] = sunRgb.r * directional.intensity;
76
- data[9] = sunRgb.g * directional.intensity;
77
- data[10] = sunRgb.b * directional.intensity;
78
- data[11] = 0;
75
+ lightData[8] = sunRgb.r * directional.intensity;
76
+ lightData[9] = sunRgb.g * directional.intensity;
77
+ lightData[10] = sunRgb.b * directional.intensity;
78
+ lightData[11] = 0;
79
79
 
80
- return data;
80
+ return lightData;
81
81
  }
@@ -1,14 +1,16 @@
1
1
  import { MAX_ENTITIES } from "../../../core";
2
2
  import { setTraits, type FieldAccessor } from "../../../core/component";
3
3
  import { createEntityIdBuffer } from "../../compute";
4
- import { writeIndirect } from "../forward";
4
+ import { writeIndirect } from "../indirect";
5
5
  import { createBox } from "./box";
6
6
  import { createSphere } from "./sphere";
7
7
  import { createPlane } from "./plane";
8
8
 
9
9
  export const MAX_SURFACES = 16;
10
10
  export const MAX_BATCH_SLOTS = 64;
11
- export const INVALID_SHAPE = 0xffffffff;
11
+ const INVALID_SHAPE = 0xffffffff;
12
+
13
+ const batchEntityIds = new Uint32Array(MAX_ENTITIES);
12
14
 
13
15
  export interface MeshData {
14
16
  vertices: Float32Array<ArrayBuffer>;
@@ -70,12 +72,14 @@ export const MeshEmission = {
70
72
  data: new Float32Array(MAX_ENTITIES * 4),
71
73
  };
72
74
 
73
- function initPBRDefaults(): void {
74
- for (let i = 0; i < MAX_ENTITIES; i++) {
75
- MeshPBR.data[i * 4] = 0.9;
76
- MeshPBR.data[i * 4 + 1] = 0.0;
77
- }
78
- }
75
+ export const MeshVolumes = {
76
+ data: new Uint8Array(MAX_ENTITIES),
77
+ };
78
+
79
+ export const Volume = {
80
+ Solid: 0,
81
+ HalfSpace: 1,
82
+ } as const;
79
83
 
80
84
  interface ColorProxy extends Array<number>, FieldAccessor {}
81
85
 
@@ -95,7 +99,6 @@ function colorProxy(): ColorProxy {
95
99
  data[offset] = ((value >> 16) & 0xff) / 255;
96
100
  data[offset + 1] = ((value >> 8) & 0xff) / 255;
97
101
  data[offset + 2] = (value & 0xff) / 255;
98
- data[offset + 3] = 1;
99
102
  }
100
103
 
101
104
  return new Proxy([] as unknown as ColorProxy, {
@@ -277,53 +280,49 @@ export const Mesh: {
277
280
  colorR: ColorChannelProxy;
278
281
  colorG: ColorChannelProxy;
279
282
  colorB: ColorChannelProxy;
283
+ opacity: ColorChannelProxy;
280
284
  sizeX: SizeProxy;
281
285
  sizeY: SizeProxy;
282
286
  sizeZ: SizeProxy;
283
287
  roughness: PBRProxy;
284
288
  metallic: PBRProxy;
289
+ ior: PBRProxy;
285
290
  emission: EmissionProxy;
286
291
  emissionIntensity: PBRProxy;
292
+ volume: Uint8Array;
287
293
  } = {
288
294
  shape: MeshShapes.data,
289
295
  color: colorProxy(),
290
296
  colorR: colorChannelProxy(0),
291
297
  colorG: colorChannelProxy(1),
292
298
  colorB: colorChannelProxy(2),
299
+ opacity: colorChannelProxy(3),
293
300
  sizeX: sizeProxy(0),
294
301
  sizeY: sizeProxy(1),
295
302
  sizeZ: sizeProxy(2),
296
303
  roughness: pbrProxy(0, 0.9),
297
304
  metallic: pbrProxy(1, 0.0),
305
+ ior: pbrProxy(2, 1.0),
298
306
  emission: emissionProxy(),
299
307
  emissionIntensity: emissionIntensityProxy(),
308
+ volume: MeshVolumes.data,
300
309
  };
301
310
 
302
311
  setTraits(Mesh, {
303
312
  defaults: () => ({
304
313
  shape: MeshShape.Box,
305
314
  color: 0xffffff,
315
+ opacity: 1.0,
306
316
  sizeX: 1,
307
317
  sizeY: 1,
308
318
  sizeZ: 1,
309
- roughness: 0.9,
319
+ roughness: 1.0,
310
320
  metallic: 0.0,
321
+ ior: 1.0,
311
322
  emission: 0x000000,
312
323
  emissionIntensity: 0.0,
324
+ volume: Volume.Solid,
313
325
  }),
314
- accessors: {
315
- color: Mesh.color,
316
- colorR: Mesh.colorR,
317
- colorG: Mesh.colorG,
318
- colorB: Mesh.colorB,
319
- sizeX: Mesh.sizeX,
320
- sizeY: Mesh.sizeY,
321
- sizeZ: Mesh.sizeZ,
322
- roughness: Mesh.roughness,
323
- metallic: Mesh.metallic,
324
- emission: Mesh.emission,
325
- emissionIntensity: Mesh.emissionIntensity,
326
- },
327
326
  });
328
327
 
329
328
  export interface MeshBuffers {
@@ -350,90 +349,88 @@ export function createMeshBuffers(device: GPUDevice, mesh: MeshData): MeshBuffer
350
349
  return { vertex, index, indexCount: mesh.indexCount };
351
350
  }
352
351
 
353
- export interface BatchKey {
354
- shape: number;
355
- surface: number;
356
- }
352
+ export type BatchEntities = (number[] | null)[];
357
353
 
358
- export function batchKeyString(key: BatchKey): string {
359
- return `${key.shape}:${key.surface}`;
360
- }
361
-
362
- export function collectByShapeAndSurface(
354
+ export function collectBatches(
363
355
  entities: Iterable<number>,
364
- getSurface: (eid: number) => number
365
- ): Map<string, { key: BatchKey; entities: number[] }> {
366
- const batches = new Map<string, { key: BatchKey; entities: number[] }>();
356
+ getSurface: (eid: number) => number,
357
+ out: BatchEntities
358
+ ): void {
359
+ for (let i = 0; i < MAX_BATCH_SLOTS; i++) {
360
+ if (out[i]) out[i]!.length = 0;
361
+ }
367
362
  for (const eid of entities) {
368
363
  const shape = Mesh.shape[eid];
369
364
  const surface = getSurface(eid);
370
- const key: BatchKey = { shape, surface };
371
- const keyStr = batchKeyString(key);
372
- let entry = batches.get(keyStr);
365
+ const batchIndex = shape * MAX_SURFACES + surface;
366
+ if (batchIndex >= MAX_BATCH_SLOTS) continue;
367
+ let entry = out[batchIndex];
373
368
  if (!entry) {
374
- entry = { key, entities: [] };
375
- batches.set(keyStr, entry);
369
+ entry = [];
370
+ out[batchIndex] = entry;
376
371
  }
377
- entry.entities.push(eid);
372
+ entry.push(eid);
378
373
  }
379
- return batches;
380
374
  }
381
375
 
382
- export interface ShapeBatch {
383
- index: number;
384
- shape: number;
385
- surface: number;
376
+ export interface Batch {
386
377
  buffers: MeshBuffers;
387
378
  entityIds: GPUBuffer;
388
379
  count: number;
389
380
  }
390
381
 
391
382
  export interface BatchState {
392
- batches: Map<string, ShapeBatch>;
383
+ batches: (Batch | null)[];
393
384
  buffers: Map<number, MeshBuffers>;
394
385
  }
395
386
 
396
387
  export function updateBatches(
397
388
  device: GPUDevice,
398
- byShapeAndSurface: Map<string, { key: BatchKey; entities: number[] }>,
389
+ batchEntities: BatchEntities,
399
390
  state: BatchState,
400
391
  indirect: GPUBuffer
401
392
  ): void {
402
- const activeBatches = new Set<string>();
403
-
404
- for (const [keyStr, { key, entities }] of byShapeAndSurface) {
405
- activeBatches.add(keyStr);
393
+ for (let batchIndex = 0; batchIndex < MAX_BATCH_SLOTS; batchIndex++) {
394
+ const entities = batchEntities[batchIndex];
395
+ if (!entities || entities.length === 0) {
396
+ const batch = state.batches[batchIndex];
397
+ if (batch) {
398
+ batch.count = 0;
399
+ writeIndirect(device, indirect, batchIndex, {
400
+ indexCount: 0,
401
+ instanceCount: 0,
402
+ firstIndex: 0,
403
+ baseVertex: 0,
404
+ firstInstance: 0,
405
+ });
406
+ }
407
+ continue;
408
+ }
406
409
 
407
- let batch = state.batches.get(keyStr);
410
+ let batch = state.batches[batchIndex];
408
411
  if (!batch) {
409
- const batchIndex = key.shape * MAX_SURFACES + key.surface;
410
- if (batchIndex >= MAX_BATCH_SLOTS) {
411
- console.warn(
412
- `Batch index ${batchIndex} exceeds limit (${MAX_BATCH_SLOTS}), skipping batch`
413
- );
414
- continue;
415
- }
416
- let buffers = state.buffers.get(key.shape);
412
+ const shape = Math.floor(batchIndex / MAX_SURFACES);
413
+ let buffers = state.buffers.get(shape);
417
414
  if (!buffers) {
418
- const data = getMesh(key.shape) ?? getMesh(MeshShape.Box)!;
415
+ const data = getMesh(shape) ?? getMesh(MeshShape.Box)!;
419
416
  buffers = createMeshBuffers(device, data);
420
- state.buffers.set(key.shape, buffers);
417
+ state.buffers.set(shape, buffers);
421
418
  }
422
419
  batch = {
423
- index: batchIndex,
424
- shape: key.shape,
425
- surface: key.surface,
426
420
  buffers,
427
421
  entityIds: createEntityIdBuffer(device, MAX_ENTITIES),
428
422
  count: 0,
429
423
  };
430
- state.batches.set(keyStr, batch);
424
+ state.batches[batchIndex] = batch;
431
425
  }
432
426
 
433
- device.queue.writeBuffer(batch.entityIds, 0, new Uint32Array(entities));
427
+ for (let i = 0; i < entities.length; i++) {
428
+ batchEntityIds[i] = entities[i];
429
+ }
430
+ device.queue.writeBuffer(batch.entityIds, 0, batchEntityIds, 0, entities.length);
434
431
  batch.count = entities.length;
435
432
 
436
- writeIndirect(device, indirect, batch.index, {
433
+ writeIndirect(device, indirect, batchIndex, {
437
434
  indexCount: batch.buffers.indexCount,
438
435
  instanceCount: entities.length,
439
436
  firstIndex: 0,
@@ -441,14 +438,9 @@ export function updateBatches(
441
438
  firstInstance: 0,
442
439
  });
443
440
  }
444
-
445
- for (const keyStr of state.batches.keys()) {
446
- if (!activeBatches.has(keyStr)) {
447
- state.batches.delete(keyStr);
448
- }
449
- }
450
441
  }
451
442
 
452
443
  export { createBox } from "./box";
453
444
  export { createSphere } from "./sphere";
454
445
  export { createPlane } from "./plane";
446
+ export { createShapeAtlas } from "./unified";