pelican-engine 0.0.0-stage → 0.3.1

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 (192) hide show
  1. package/AGENTS.md +147 -0
  2. package/LICENSE +22 -0
  3. package/README.md +129 -2
  4. package/distribution.json +197 -0
  5. package/docs/AI-WORKFLOW.md +517 -0
  6. package/docs/ARCHITECTURE.md +268 -0
  7. package/docs/CAPABILITIES.md +264 -0
  8. package/docs/DECISIONS.md +107 -0
  9. package/docs/M3-USAGE.md +192 -0
  10. package/docs/PELICAN-MIGRATION.md +38 -0
  11. package/docs/RENDER-BACKENDS.md +50 -0
  12. package/docs/ROADMAP.md +239 -0
  13. package/docs/THIRD-PARTY-NOTICES.md +151 -0
  14. package/docs/api/REFERENCE.md +1435 -0
  15. package/docs/api/index.json +1139 -0
  16. package/docs/npm/DISTRIBUTION.md +130 -0
  17. package/docs/npm/GAME-AUTHOR.md +147 -0
  18. package/engine.manifest.json +15 -0
  19. package/lib/assets/audio.js +67 -0
  20. package/lib/assets/errors.js +36 -0
  21. package/lib/assets/glb.js +277 -0
  22. package/lib/assets/index.js +53 -0
  23. package/lib/assets/jpeg.js +199 -0
  24. package/lib/assets/model.js +558 -0
  25. package/lib/assets/optimize.js +95 -0
  26. package/lib/assets/texture.js +187 -0
  27. package/lib/compiler/distribution.js +17 -0
  28. package/lib/compiler/extract.js +425 -0
  29. package/lib/compiler/graph.js +366 -0
  30. package/lib/compiler/index.js +574 -0
  31. package/lib/compiler/normalize.js +1194 -0
  32. package/lib/compiler/source-map.js +25 -0
  33. package/lib/compiler/types.js +2 -0
  34. package/lib/compiler/version.js +93 -0
  35. package/lib/experiments/simulation/index.js +2 -0
  36. package/lib/experiments/simulation/math.js +41 -0
  37. package/lib/experiments/simulation/probe.js +215 -0
  38. package/lib/experiments/simulation/scenes.js +192 -0
  39. package/lib/experiments/simulation/solver.js +346 -0
  40. package/lib/experiments/simulation/types.js +1 -0
  41. package/lib/experiments/world/adapters.js +30 -0
  42. package/lib/experiments/world/geometry.js +180 -0
  43. package/lib/experiments/world/index.js +2 -0
  44. package/lib/experiments/world/probe.js +288 -0
  45. package/lib/experiments/world/types.js +1 -0
  46. package/lib/experiments/world/world.js +599 -0
  47. package/lib/navigation/index.js +157 -0
  48. package/lib/physics/index.js +909 -0
  49. package/lib/protocol/index.js +92 -0
  50. package/lib/renderer/acceleration.js +609 -0
  51. package/lib/renderer/browser.js +2015 -0
  52. package/lib/renderer/bsdf.js +260 -0
  53. package/lib/renderer/checkpoint.js +88 -0
  54. package/lib/renderer/compute-executor.js +1034 -0
  55. package/lib/renderer/compute-texture-assets.js +49 -0
  56. package/lib/renderer/compute-view-contract.js +1 -0
  57. package/lib/renderer/denoiser.js +361 -0
  58. package/lib/renderer/dielectric.js +360 -0
  59. package/lib/renderer/embedded-image-pixels.js +368 -0
  60. package/lib/renderer/emissive-lights.js +67 -0
  61. package/lib/renderer/environment-importance.js +55 -0
  62. package/lib/renderer/environment.js +346 -0
  63. package/lib/renderer/gameplay.js +464 -0
  64. package/lib/renderer/geometry.js +103 -0
  65. package/lib/renderer/graph-expressions.js +485 -0
  66. package/lib/renderer/graph-tsl.js +141 -0
  67. package/lib/renderer/graph.js +214 -0
  68. package/lib/renderer/managed-texture-sampling.js +29 -0
  69. package/lib/renderer/material-graphs.js +297 -0
  70. package/lib/renderer/medium.js +348 -0
  71. package/lib/renderer/models.js +1053 -0
  72. package/lib/renderer/motion-shader.js +40 -0
  73. package/lib/renderer/path-shader.js +196 -0
  74. package/lib/renderer/pathtrace.js +324 -0
  75. package/lib/renderer/postprocess-contract.js +1 -0
  76. package/lib/renderer/postprocess.js +494 -0
  77. package/lib/renderer/render-buffers.js +132 -0
  78. package/lib/renderer/render-textures.js +155 -0
  79. package/lib/renderer/startup.js +50 -0
  80. package/lib/renderer/surface-capture.js +199 -0
  81. package/lib/renderer/surface-gpu.js +270 -0
  82. package/lib/renderer/surface-motion.js +84 -0
  83. package/lib/renderer/surface-ray-shader.js +91 -0
  84. package/lib/renderer/surface-shader.js +88 -0
  85. package/lib/renderer/surface-tracer.js +1720 -0
  86. package/lib/renderer/surface.js +1425 -0
  87. package/lib/renderer/temporal-filter.js +512 -0
  88. package/lib/renderer/texture-pixels.js +207 -0
  89. package/lib/renderer/traversal-nodes.js +54 -0
  90. package/lib/renderer/ui.js +446 -0
  91. package/lib/renderer/vertex-bounds.js +127 -0
  92. package/lib/renderer/vertex-contract.js +1 -0
  93. package/lib/renderer/vertex-graphs.js +197 -0
  94. package/lib/renderer/volume-shader.js +50 -0
  95. package/lib/runtime/compute-contract.js +1 -0
  96. package/lib/runtime/compute-service.js +674 -0
  97. package/lib/runtime/contracts.js +1 -0
  98. package/lib/runtime/error.js +37 -0
  99. package/lib/runtime/events.js +189 -0
  100. package/lib/runtime/game-services.js +772 -0
  101. package/lib/runtime/host.js +839 -0
  102. package/lib/runtime/material-parameters.js +145 -0
  103. package/lib/runtime/plugins.js +1 -0
  104. package/lib/runtime/postprocess-contract.js +1 -0
  105. package/lib/runtime/postprocess.js +189 -0
  106. package/lib/runtime/prefabs.js +13 -0
  107. package/lib/runtime/random.js +88 -0
  108. package/lib/runtime/render-buffer-contract.js +1 -0
  109. package/lib/runtime/render-buffers.js +243 -0
  110. package/lib/runtime/render-capabilities.js +17 -0
  111. package/lib/runtime/render-settings.js +249 -0
  112. package/lib/runtime/render-texture-contract.js +1 -0
  113. package/lib/runtime/render-textures.js +271 -0
  114. package/lib/runtime/resources.js +648 -0
  115. package/lib/runtime/scheduler.js +765 -0
  116. package/lib/runtime/storage.js +169 -0
  117. package/lib/runtime/world.js +594 -0
  118. package/lib/schema/index.js +24 -0
  119. package/lib/sdk/graph-values.js +16 -0
  120. package/lib/sdk/index.d.ts +800 -0
  121. package/lib/sdk/index.js +82 -0
  122. package/lib/tools/api-index.js +113 -0
  123. package/lib/tools/browser-session.js +89 -0
  124. package/lib/tools/cli.js +513 -0
  125. package/lib/tools/compress-dist.js +59 -0
  126. package/lib/tools/control-client.js +153 -0
  127. package/lib/tools/control-server.js +633 -0
  128. package/lib/tools/diagnostic-code.js +3 -0
  129. package/lib/tools/doctor.js +496 -0
  130. package/lib/tools/npm/browser-cli.js +6 -0
  131. package/lib/tools/npm/entry.js +40 -0
  132. package/lib/tools/npm/layout.js +195 -0
  133. package/lib/tools/setup.js +153 -0
  134. package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
  135. package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
  136. package/licenses/@gltf-transform/core/LICENSE.md +21 -0
  137. package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
  138. package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
  139. package/licenses/@img/colour/LICENSE.md +82 -0
  140. package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
  141. package/licenses/@oxc-project/types/LICENSE +22 -0
  142. package/licenses/@rolldown/pluginutils/LICENSE +21 -0
  143. package/licenses/@tweenjs/tween.js/LICENSE +23 -0
  144. package/licenses/@types/ndarray/LICENSE +21 -0
  145. package/licenses/@types/node/LICENSE +21 -0
  146. package/licenses/@types/stats.js/LICENSE +21 -0
  147. package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
  148. package/licenses/@types/three/LICENSE +21 -0
  149. package/licenses/@types/webxr/LICENSE +21 -0
  150. package/licenses/@webgpu/types/LICENSE +26 -0
  151. package/licenses/THIRD-PARTY-NOTICES.md +139 -0
  152. package/licenses/cwise-compiler/LICENSE +22 -0
  153. package/licenses/detect-libc/LICENSE +201 -0
  154. package/licenses/esbuild/LICENSE.md +21 -0
  155. package/licenses/fast-png/LICENSE +21 -0
  156. package/licenses/fdir/LICENSE +7 -0
  157. package/licenses/fflate/LICENSE +21 -0
  158. package/licenses/iobuffer/LICENSE +22 -0
  159. package/licenses/iota-array/LICENSE +22 -0
  160. package/licenses/is-buffer/LICENSE +21 -0
  161. package/licenses/ktx-parse/LICENSE +21 -0
  162. package/licenses/lightningcss/LICENSE +373 -0
  163. package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
  164. package/licenses/meshoptimizer/LICENSE.md +21 -0
  165. package/licenses/nanoid/LICENSE +20 -0
  166. package/licenses/ndarray/LICENSE +22 -0
  167. package/licenses/ndarray-lanczos/LICENSE +21 -0
  168. package/licenses/ndarray-ops/LICENSE +22 -0
  169. package/licenses/ndarray-pixels/LICENSE +21 -0
  170. package/licenses/ngraph.path/LICENSE +21 -0
  171. package/licenses/picocolors/LICENSE +15 -0
  172. package/licenses/picomatch/LICENSE +21 -0
  173. package/licenses/playwright/LICENSE +202 -0
  174. package/licenses/playwright/NOTICE +5 -0
  175. package/licenses/playwright-core/LICENSE +202 -0
  176. package/licenses/playwright-core/NOTICE +5 -0
  177. package/licenses/postcss/LICENSE +20 -0
  178. package/licenses/prettier/LICENSE +7 -0
  179. package/licenses/property-graph/LICENSE +21 -0
  180. package/licenses/rolldown/LICENSE +25 -0
  181. package/licenses/semver/LICENSE +15 -0
  182. package/licenses/sharp/LICENSE +191 -0
  183. package/licenses/source-map-js/LICENSE +28 -0
  184. package/licenses/three/LICENSE +21 -0
  185. package/licenses/tinyglobby/LICENSE +21 -0
  186. package/licenses/tsx/LICENSE +21 -0
  187. package/licenses/typescript/LICENSE.txt +55 -0
  188. package/licenses/undici-types/LICENSE +21 -0
  189. package/licenses/uniq/LICENSE +22 -0
  190. package/licenses/vite/LICENSE.md +2084 -0
  191. package/package.json +49 -4
  192. package/web/index.html +34 -0
@@ -0,0 +1,609 @@
1
+ import { isFrozenCapturedPixels } from "./surface.js";
2
+ export class SurfaceAccelerationError extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(code + ": " + message);
6
+ this.code = code;
7
+ this.name = "SurfaceAccelerationError";
8
+ }
9
+ }
10
+ function fail(code, message) {
11
+ throw new SurfaceAccelerationError("TC_ACCELERATION_" + code, message);
12
+ }
13
+ const LEAF_SIZE = 4;
14
+ const SAH_BINS = 16;
15
+ // Reject splits with fewer than ceil(N / 32) primitives on either side.
16
+ const SAH_BALANCE = 32;
17
+ const MAX_FLOAT = 3.4028234663852886e38;
18
+ // Numeric pointers in the fixed float layout must be exact integers.
19
+ const MAX_EXACT_POINTER = 2 ** 24;
20
+ const roundBuffer = new ArrayBuffer(4);
21
+ const roundFloat = new Float32Array(roundBuffer);
22
+ const roundBits = new Uint32Array(roundBuffer);
23
+ function outward(value, lower) {
24
+ const rounded = Math.fround(value);
25
+ if (lower ? rounded <= value : rounded >= value)
26
+ return rounded;
27
+ if (rounded === 0)
28
+ return lower ? -(2 ** -149) : 2 ** -149;
29
+ roundFloat[0] = rounded;
30
+ roundBits[0] += rounded > 0 === lower ? -1 : 1;
31
+ return roundFloat[0];
32
+ }
33
+ function vector(value, length, label) {
34
+ if (!Array.isArray(value) ||
35
+ value.length !== length ||
36
+ value.some((n) => typeof n !== "number" || !Number.isFinite(n))) {
37
+ fail("INVALID_SNAPSHOT", label +
38
+ " must contain " +
39
+ length +
40
+ " finite numbers; re-extract the surface");
41
+ }
42
+ }
43
+ function finiteData(value, label, validatedFrozenData, visiting = new WeakSet()) {
44
+ if (typeof value === "number" && !Number.isFinite(value))
45
+ fail("INVALID_SNAPSHOT", label + " contains a non-finite value; re-extract the surface");
46
+ if (value && typeof value === "object") {
47
+ if (isFrozenCapturedPixels(value))
48
+ return true;
49
+ if (validatedFrozenData.has(value))
50
+ return true;
51
+ if (visiting.has(value))
52
+ fail("INVALID_SNAPSHOT", label + " contains cyclic data.");
53
+ visiting.add(value);
54
+ // Frozen accessors can still return mutable/changing values; never memoize them.
55
+ const prototype = Object.getPrototypeOf(value);
56
+ let immutable = Object.isFrozen(value) &&
57
+ (prototype === Object.prototype ||
58
+ prototype === Array.prototype ||
59
+ prototype === null);
60
+ for (const key of Reflect.ownKeys(value)) {
61
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
62
+ if (!("value" in descriptor))
63
+ immutable = false;
64
+ if (!finiteData("value" in descriptor ? descriptor.value : Reflect.get(value, key), label + "." + String(key), validatedFrozenData, visiting))
65
+ immutable = false;
66
+ }
67
+ visiting.delete(value);
68
+ if (immutable)
69
+ validatedFrozenData.add(value);
70
+ return immutable;
71
+ }
72
+ return true;
73
+ }
74
+ function equal(a, b) {
75
+ if (a === b)
76
+ return true;
77
+ if (!a || !b || typeof a !== "object" || typeof b !== "object")
78
+ return false;
79
+ const aa = a, bb = b;
80
+ const keys = Object.keys(aa);
81
+ return (keys.length === Object.keys(bb).length &&
82
+ keys.every((key) => Object.hasOwn(bb, key) && equal(aa[key], bb[key])));
83
+ }
84
+ function identity(t) {
85
+ return JSON.stringify([
86
+ t.objectUuid,
87
+ t.geometryUuid,
88
+ t.instanceId,
89
+ t.groupIndex,
90
+ t.elementOffset,
91
+ t.vertexIndices,
92
+ ]);
93
+ }
94
+ function orientation(t) {
95
+ const m = t.normalMatrix;
96
+ // Column-major inverse transpose: determinant has the world transform's sign.
97
+ // Scale columns independently so finite anisotropic matrices do not acquire
98
+ // an artificial zero determinant from multiplying tiny common-scale ratios.
99
+ const scales = [0, 3, 6].map((i) => Math.max(Math.abs(m[i]), Math.abs(m[i + 1]), Math.abs(m[i + 2])));
100
+ if (scales.some((scale) => scale === 0))
101
+ return 0;
102
+ const a = m.map((x, i) => x / scales[Math.floor(i / 3)]);
103
+ return Math.sign(a[0] * (a[4] * a[8] - a[7] * a[5]) -
104
+ a[3] * (a[1] * a[8] - a[7] * a[2]) +
105
+ a[6] * (a[1] * a[5] - a[4] * a[2]));
106
+ }
107
+ function cross(a, b) {
108
+ return [
109
+ a[1] * b[2] - a[2] * b[1],
110
+ a[2] * b[0] - a[0] * b[2],
111
+ a[0] * b[1] - a[1] * b[0],
112
+ ];
113
+ }
114
+ function sub(a, b) {
115
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
116
+ }
117
+ function dot(a, b) {
118
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
119
+ }
120
+ function unit(a) {
121
+ const scale = Math.max(...a.map(Math.abs));
122
+ if (!scale || !Number.isFinite(scale))
123
+ return null;
124
+ const v = a.map((x) => x / scale), length = Math.hypot(...v);
125
+ return [v[0] / length, v[1] / length, v[2] / length];
126
+ }
127
+ function validate(snapshot, frozenData, triangles) {
128
+ if (!snapshot ||
129
+ snapshot.version !== 1 ||
130
+ !Array.isArray(snapshot.triangles) ||
131
+ !Array.isArray(snapshot.materials) ||
132
+ !Array.isArray(snapshot.textures) ||
133
+ !Array.isArray(snapshot.diagnostics))
134
+ fail("INVALID_SNAPSHOT", "Expected a version 1 SurfaceScene; re-extract the surface");
135
+ finiteData(snapshot, "snapshot", frozenData);
136
+ const materials = new Map();
137
+ for (const m of snapshot.materials) {
138
+ if (!m ||
139
+ typeof m.uuid !== "string" ||
140
+ materials.has(m.uuid) ||
141
+ !["front", "back", "double"].includes(m.side))
142
+ fail("INVALID_MATERIAL", "Material UUIDs must be unique and side must be front/back/double");
143
+ materials.set(m.uuid, m);
144
+ }
145
+ const indices = new Map();
146
+ snapshot.triangles.forEach((t, index) => {
147
+ const label = "triangle " + index;
148
+ if (!t ||
149
+ typeof t.objectUuid !== "string" ||
150
+ typeof t.geometryUuid !== "string" ||
151
+ ![t.instanceId, t.groupIndex, t.elementOffset, t.materialIndex].every(Number.isSafeInteger))
152
+ fail("INVALID_SNAPSHOT", label + " has invalid identity; re-extract the surface");
153
+ if (!triangles.has(t)) {
154
+ vector(t.vertexIndices, 3, label + " indices");
155
+ if (!t.vertexIndices.every((n) => Number.isSafeInteger(n) && n >= 0))
156
+ fail("INVALID_SNAPSHOT", label + " has invalid vertex indices");
157
+ vector(t.normalMatrix, 9, label + " normalMatrix");
158
+ if (!orientation(t))
159
+ fail("INVALID_SNAPSHOT", label + " has a singular normalMatrix");
160
+ if (!Array.isArray(t.vertices) || t.vertices.length !== 3)
161
+ fail("INVALID_SNAPSHOT", label + " must have three vertices");
162
+ for (const v of t.vertices) {
163
+ vector(v?.position, 3, label + " position");
164
+ vector(v?.normal, 3, label + " normal");
165
+ if (v.position.some((n) => Math.abs(n) > MAX_FLOAT))
166
+ fail("INVALID_SNAPSHOT", label + " position exceeds finite Float32 bounds");
167
+ if (v.uv0)
168
+ vector(v.uv0, 2, label + " uv0");
169
+ if (v.uv1)
170
+ vector(v.uv1, 2, label + " uv1");
171
+ if (v.tangent)
172
+ vector(v.tangent, 4, label + " tangent");
173
+ if (v.color)
174
+ vector(v.color, 4, label + " color");
175
+ }
176
+ const face = cross(sub(t.vertices[1].position, t.vertices[0].position), sub(t.vertices[2].position, t.vertices[0].position));
177
+ if (!unit(face))
178
+ fail("DEGENERATE_TRIANGLE", label +
179
+ " has zero or unrepresentable area; remove degenerate geometry");
180
+ if (frozenData.has(t))
181
+ triangles.add(t);
182
+ }
183
+ if (!materials.has(t.materialUuid))
184
+ fail("INVALID_MATERIAL", label + " references missing material " + t.materialUuid);
185
+ const key = identity(t);
186
+ if (indices.has(key))
187
+ fail("DUPLICATE_IDENTITY", label +
188
+ " duplicates a triangle identity; re-extract unique groups/elements");
189
+ indices.set(key, index);
190
+ });
191
+ return { indices, materials };
192
+ }
193
+ function leafBounds(nodes, offset, triangles) {
194
+ const start = nodes[offset + 3], count = nodes[offset + 7];
195
+ for (let axis = 0; axis < 3; axis++) {
196
+ let lo = Infinity, hi = -Infinity;
197
+ for (let i = start; i < start + count; i++)
198
+ for (const v of triangles[i].vertices) {
199
+ lo = Math.min(lo, v.position[axis]);
200
+ hi = Math.max(hi, v.position[axis]);
201
+ }
202
+ nodes[offset + axis] = outward(lo, true);
203
+ nodes[offset + 4 + axis] = outward(hi, false);
204
+ }
205
+ }
206
+ function refit(nodes, triangles) {
207
+ // Build order is preorder: every child has a greater node index than its parent.
208
+ for (let offset = nodes.length - 8; offset >= 0; offset -= 8) {
209
+ if (nodes[offset + 7] > 0)
210
+ leafBounds(nodes, offset, triangles);
211
+ else {
212
+ const left = nodes[offset + 3] * 8, right = -nodes[offset + 7] * 8;
213
+ for (let axis = 0; axis < 3; axis++) {
214
+ nodes[offset + axis] = Math.min(nodes[left + axis], nodes[right + axis]);
215
+ nodes[offset + 4 + axis] = Math.max(nodes[left + 4 + axis], nodes[right + 4 + axis]);
216
+ }
217
+ }
218
+ }
219
+ }
220
+ function build(snapshot) {
221
+ const order = snapshot.triangles.map((_, i) => i), data = [];
222
+ const centers = new Float64Array(order.length * 3), bounds = new Float64Array(order.length * 6), scratch = new Uint32Array(order.length);
223
+ snapshot.triangles.forEach((t, i) => {
224
+ for (let axis = 0; axis < 3; axis++) {
225
+ centers[i * 3 + axis] = t.vertices.reduce((sum, v) => sum + v.position[axis] / 3, 0);
226
+ bounds[i * 6 + axis] = Math.min(...t.vertices.map((v) => v.position[axis]));
227
+ bounds[i * 6 + 3 + axis] = Math.max(...t.vertices.map((v) => v.position[axis]));
228
+ }
229
+ });
230
+ // Shared workspace is used only while choosing a split, before recursing.
231
+ const binCounts = new Uint32Array(SAH_BINS), binBounds = new Float64Array(SAH_BINS * 6), rightCounts = new Uint32Array(SAH_BINS), rightAreas = new Float64Array(SAH_BINS), union = new Float64Array(6);
232
+ const depthLimit = 2 * Math.ceil(Math.log2(Math.max(1, Math.ceil(order.length / LEAF_SIZE)))) +
233
+ 1;
234
+ function resetBox(box, offset = 0) {
235
+ for (let axis = 0; axis < 3; axis++) {
236
+ box[offset + axis] = Infinity;
237
+ box[offset + 3 + axis] = -Infinity;
238
+ }
239
+ }
240
+ function mergeBox(target, offset, source, from) {
241
+ for (let axis = 0; axis < 3; axis++) {
242
+ target[offset + axis] = Math.min(target[offset + axis], source[from + axis]);
243
+ target[offset + 3 + axis] = Math.max(target[offset + 3 + axis], source[from + 3 + axis]);
244
+ }
245
+ }
246
+ function area(box) {
247
+ const x = box[3] - box[0], y = box[4] - box[1], z = box[5] - box[2];
248
+ return x < 0 || y < 0 || z < 0 ? 0 : 2 * (x * y + y * z + z * x);
249
+ }
250
+ function bin(source, axis, lo, extent) {
251
+ return Math.min(SAH_BINS - 1, Math.max(0, Math.floor(((centers[source * 3 + axis] - lo) / extent) * SAH_BINS)));
252
+ }
253
+ let depth = 0;
254
+ function partition(start, end, level) {
255
+ const index = data.length / 8;
256
+ if (index > MAX_EXACT_POINTER || start > MAX_EXACT_POINTER)
257
+ fail("BUFFER_CAPACITY", "Tree pointers exceed exact Float32 integers; split the surface into separate accelerations");
258
+ data.push(0, 0, 0, 0, 0, 0, 0, 0);
259
+ depth = Math.max(depth, level);
260
+ if (end - start <= LEAF_SIZE) {
261
+ data[index * 8 + 3] = start;
262
+ data[index * 8 + 7] = end - start;
263
+ }
264
+ else {
265
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
266
+ for (let i = start; i < end; i++)
267
+ for (let axis = 0; axis < 3; axis++) {
268
+ lo[axis] = Math.min(lo[axis], centers[order[i] * 3 + axis]);
269
+ hi[axis] = Math.max(hi[axis], centers[order[i] * 3 + axis]);
270
+ }
271
+ const count = end - start, minimumSide = Math.ceil(count / SAH_BALANCE);
272
+ let bestCost = Infinity, bestAxis = -1, bestBin = -1, bestLeftCount = 0;
273
+ for (let axis = 0; axis < 3; axis++) {
274
+ const extent = hi[axis] - lo[axis];
275
+ if (!(extent > 0))
276
+ continue;
277
+ binCounts.fill(0);
278
+ for (let b = 0; b < SAH_BINS; b++)
279
+ resetBox(binBounds, b * 6);
280
+ for (let i = start; i < end; i++) {
281
+ const source = order[i], b = bin(source, axis, lo[axis], extent);
282
+ binCounts[b]++;
283
+ mergeBox(binBounds, b * 6, bounds, source * 6);
284
+ }
285
+ resetBox(union);
286
+ let rightCount = 0;
287
+ for (let b = SAH_BINS - 1; b >= 0; b--) {
288
+ rightCount += binCounts[b];
289
+ if (binCounts[b])
290
+ mergeBox(union, 0, binBounds, b * 6);
291
+ rightCounts[b] = rightCount;
292
+ rightAreas[b] = area(union);
293
+ }
294
+ // An underflowed/zero-area box has no useful geometric SAH signal.
295
+ if (!(rightAreas[0] > 0))
296
+ continue;
297
+ resetBox(union);
298
+ let leftCount = 0;
299
+ for (let b = 0; b < SAH_BINS - 1; b++) {
300
+ leftCount += binCounts[b];
301
+ if (binCounts[b])
302
+ mergeBox(union, 0, binBounds, b * 6);
303
+ const right = rightCounts[b + 1];
304
+ if (leftCount < minimumSide || right < minimumSide)
305
+ continue;
306
+ // Always reserve enough remaining levels for a balanced median tail.
307
+ const tail = Math.ceil(Math.log2(Math.ceil(Math.max(leftCount, right) / LEAF_SIZE)));
308
+ if (level + 1 + tail > depthLimit)
309
+ continue;
310
+ const cost = area(union) * leftCount + rightAreas[b + 1] * right;
311
+ // Equal costs keep the first axis/bin, making ties deterministic.
312
+ if (cost < bestCost) {
313
+ bestCost = cost;
314
+ bestAxis = axis;
315
+ bestBin = b;
316
+ bestLeftCount = leftCount;
317
+ }
318
+ }
319
+ }
320
+ let middle;
321
+ if (bestAxis >= 0) {
322
+ middle = start + bestLeftCount;
323
+ let left = start, right = middle;
324
+ for (let i = start; i < end; i++) {
325
+ const source = order[i];
326
+ if (bin(source, bestAxis, lo[bestAxis], hi[bestAxis] - lo[bestAxis]) <=
327
+ bestBin)
328
+ scratch[left++] = source;
329
+ else
330
+ scratch[right++] = source;
331
+ }
332
+ for (let i = start; i < end; i++)
333
+ order[i] = scratch[i];
334
+ }
335
+ else {
336
+ let axis = 0;
337
+ for (let k = 1; k < 3; k++)
338
+ if (hi[k] - lo[k] > hi[axis] - lo[axis])
339
+ axis = k;
340
+ const sorted = order
341
+ .slice(start, end)
342
+ .sort((a, b) => centers[a * 3 + axis] - centers[b * 3 + axis] || a - b);
343
+ for (let i = start; i < end; i++)
344
+ order[i] = sorted[i - start];
345
+ middle = start + Math.floor(count / 2);
346
+ }
347
+ const left = partition(start, middle, level + 1), right = partition(middle, end, level + 1);
348
+ data[index * 8 + 3] = left;
349
+ data[index * 8 + 7] = -right;
350
+ }
351
+ return index;
352
+ }
353
+ if (order.length)
354
+ partition(0, order.length, 1);
355
+ const nodes = new Float32Array(data), sources = Uint32Array.from(order);
356
+ refit(nodes, order.map((i) => snapshot.triangles[i]));
357
+ return { nodes, sources, depth };
358
+ }
359
+ function ranges(before, after) {
360
+ const result = [];
361
+ for (let offset = 0; offset < after.length; offset += 8) {
362
+ let changed = false;
363
+ for (let k = 0; k < 8; k++)
364
+ if (before[offset + k] !== after[offset + k])
365
+ changed = true;
366
+ if (!changed)
367
+ continue;
368
+ const previous = result.at(-1);
369
+ if (previous && previous.offset + previous.count === offset)
370
+ previous.count += 8;
371
+ else
372
+ result.push({ offset, count: 8 });
373
+ }
374
+ return result;
375
+ }
376
+ function boundsHit(nodes, index, origin, direction, limit) {
377
+ let near = 0, far = limit;
378
+ for (let axis = 0; axis < 3; axis++) {
379
+ const lo = nodes[index * 8 + axis], hi = nodes[index * 8 + axis + 4];
380
+ if (direction[axis] === 0) {
381
+ if (origin[axis] < lo || origin[axis] > hi)
382
+ return false;
383
+ }
384
+ else {
385
+ const a = (lo - origin[axis]) / direction[axis], b = (hi - origin[axis]) / direction[axis];
386
+ near = Math.max(near, Math.min(a, b));
387
+ far = Math.min(far, Math.max(a, b));
388
+ if (near > far)
389
+ return false;
390
+ }
391
+ }
392
+ return true;
393
+ }
394
+ function triangleHit(t, material, origin, direction, limit, triangleIndex) {
395
+ const a = t.vertices[0].position, e1 = sub(t.vertices[1].position, a), e2 = sub(t.vertices[2].position, a);
396
+ const p = cross(direction, e2), determinant = dot(e1, p);
397
+ if (determinant === 0)
398
+ return null;
399
+ const front = determinant * orientation(t) > 0;
400
+ const physical = (material.transmission ?? 0) > 0;
401
+ if (!physical &&
402
+ ((material.side === "front" && !front) ||
403
+ (material.side === "back" && front)))
404
+ return null;
405
+ const fromA = sub(origin, a), u = dot(fromA, p) / determinant;
406
+ if (!(u >= 0 && u <= 1))
407
+ return null;
408
+ const q = cross(fromA, e1), v = dot(direction, q) / determinant;
409
+ if (!(v >= 0 && u + v <= 1))
410
+ return null;
411
+ const distance = dot(e2, q) / determinant;
412
+ if (!Number.isFinite(distance) || distance < 0 || distance > limit)
413
+ return null;
414
+ const barycentric = [1 - u - v, u, v];
415
+ const blended = [0, 1, 2].map((axis) => t.vertices.reduce((sum, vertex, i) => sum + vertex.normal[axis] * barycentric[i], 0));
416
+ let normal = unit(blended) ?? unit(cross(e1, e2));
417
+ if (physical
418
+ ? !front
419
+ : material.side === "back" || (material.side === "double" && !front))
420
+ normal = [-normal[0], -normal[1], -normal[2]];
421
+ return {
422
+ distance: Math.max(0, distance),
423
+ triangleIndex,
424
+ barycentric,
425
+ normal,
426
+ };
427
+ }
428
+ export function createSurfaceAcceleration() {
429
+ let frozenData = new WeakSet(), validatedTriangles = new WeakSet();
430
+ let disposed = false, snapshot;
431
+ let nodes = new Float32Array(), sources = new Uint32Array();
432
+ let ordered = [], keys = [];
433
+ let materials = new Map();
434
+ let revision = 0, topologyRevision = 0, depth = 0, updates = 0, rebuilds = 0, refits = 0;
435
+ let metrics = {
436
+ buildMs: 0,
437
+ refitMs: 0,
438
+ triangles: 0,
439
+ nodes: 0,
440
+ depth: 0,
441
+ };
442
+ let changes = { kind: "none", nodeRanges: [] };
443
+ const active = () => {
444
+ if (disposed)
445
+ fail("DISPOSED", "Acceleration was disposed; create a new acceleration");
446
+ };
447
+ return {
448
+ update(next) {
449
+ active();
450
+ const started = performance.now();
451
+ // All validation, allocation and calculations precede publication (atomic failure).
452
+ const validated = validate(next, frozenData, validatedTriangles);
453
+ const topologyChanged = !snapshot ||
454
+ keys.length !== next.triangles.length ||
455
+ keys.some((key) => !validated.indices.has(key));
456
+ let kind, nextNodes, nextSources, nextDepth = depth;
457
+ let nextOrdered;
458
+ if (topologyChanged) {
459
+ const built = build(next);
460
+ nextNodes = built.nodes;
461
+ nextSources = built.sources;
462
+ nextDepth = built.depth;
463
+ nextOrdered = Array.from(nextSources, (index) => next.triangles[index]);
464
+ kind = "rebuild";
465
+ }
466
+ else {
467
+ nextSources = Uint32Array.from(keys, (key) => validated.indices.get(key));
468
+ nextOrdered = Array.from(nextSources, (index) => next.triangles[index]);
469
+ const moved = nextOrdered.some((t, i) => t.vertices.some((v, j) => v.position.some((n, axis) => n !== ordered[i].vertices[j].position[axis])));
470
+ nextNodes = moved ? nodes.slice() : nodes;
471
+ if (moved) {
472
+ refit(nextNodes, nextOrdered);
473
+ kind = "refit";
474
+ }
475
+ else
476
+ kind = equal(snapshot, next) ? "none" : "materials";
477
+ }
478
+ const nodeRanges = kind === "rebuild"
479
+ ? nextNodes.length
480
+ ? [{ offset: 0, count: nextNodes.length }]
481
+ : []
482
+ : kind === "refit"
483
+ ? ranges(nodes, nextNodes)
484
+ : [];
485
+ const nextRevision = revision + (kind === "none" ? 0 : 1);
486
+ const nextTopology = topologyRevision + (topologyChanged ? 1 : 0);
487
+ const nextChanges = Object.freeze({
488
+ kind,
489
+ nodeRanges,
490
+ });
491
+ const nextMetrics = {
492
+ buildMs: 0,
493
+ refitMs: 0,
494
+ triangles: nextOrdered.length,
495
+ nodes: nextNodes.length / 8,
496
+ depth: nextDepth,
497
+ };
498
+ // Returned mutable typed arrays are private copies, including on none/material updates.
499
+ const frameNodes = nextNodes.slice(), frameSources = nextSources.slice();
500
+ const frameOrdered = Object.freeze([...nextOrdered]);
501
+ const nextKeys = topologyChanged ? nextOrdered.map(identity) : keys;
502
+ const elapsed = performance.now() - started;
503
+ if (kind === "rebuild")
504
+ nextMetrics.buildMs = elapsed;
505
+ if (kind === "refit")
506
+ nextMetrics.refitMs = elapsed;
507
+ const frame = Object.freeze({
508
+ nodes: frameNodes,
509
+ sourceIndices: frameSources,
510
+ orderedTriangles: frameOrdered,
511
+ revision: nextRevision,
512
+ topologyRevision: nextTopology,
513
+ changes: Object.freeze({
514
+ kind,
515
+ nodeRanges: nodeRanges.map((range) => ({ ...range })),
516
+ }),
517
+ metrics: Object.freeze(nextMetrics),
518
+ });
519
+ snapshot = next;
520
+ nodes = nextNodes;
521
+ sources = nextSources;
522
+ ordered = frameOrdered;
523
+ keys = nextKeys;
524
+ materials = validated.materials;
525
+ depth = nextDepth;
526
+ revision = nextRevision;
527
+ topologyRevision = nextTopology;
528
+ metrics = frame.metrics;
529
+ changes = nextChanges;
530
+ updates++;
531
+ if (kind === "rebuild")
532
+ rebuilds++;
533
+ if (kind === "refit")
534
+ refits++;
535
+ return frame;
536
+ },
537
+ intersect(origin, direction, maxDistance = Infinity) {
538
+ active();
539
+ if (!Array.isArray(origin) ||
540
+ origin.length !== 3 ||
541
+ !origin.every(Number.isFinite) ||
542
+ !Array.isArray(direction) ||
543
+ direction.length !== 3 ||
544
+ !direction.every(Number.isFinite))
545
+ fail("INVALID_RAY", "Origin and direction must be finite three-component vectors");
546
+ const ray = unit(direction);
547
+ if (!ray)
548
+ fail("INVALID_RAY", "Direction must have nonzero length");
549
+ if (!(maxDistance >= 0) ||
550
+ (maxDistance !== Infinity && !Number.isFinite(maxDistance)))
551
+ fail("INVALID_DISTANCE", "maxDistance must be nonnegative finite or positive Infinity");
552
+ if (!nodes.length)
553
+ return null;
554
+ let hit = null, limit = maxDistance;
555
+ const stack = [0];
556
+ while (stack.length) {
557
+ const index = stack.pop(), offset = index * 8;
558
+ if (!boundsHit(nodes, index, origin, ray, limit))
559
+ continue;
560
+ if (nodes[offset + 7] > 0) {
561
+ const start = nodes[offset + 3], end = start + nodes[offset + 7];
562
+ for (let i = start; i < end; i++) {
563
+ const candidate = triangleHit(ordered[i], materials.get(ordered[i].materialUuid), origin, ray, limit, sources[i]);
564
+ if (candidate &&
565
+ (!hit ||
566
+ candidate.distance < hit.distance ||
567
+ (candidate.distance === hit.distance &&
568
+ candidate.triangleIndex < hit.triangleIndex))) {
569
+ hit = candidate;
570
+ limit = candidate.distance;
571
+ }
572
+ }
573
+ }
574
+ else {
575
+ stack.push(-nodes[offset + 7], nodes[offset + 3]);
576
+ }
577
+ }
578
+ return hit;
579
+ },
580
+ inspect() {
581
+ active();
582
+ return {
583
+ revision,
584
+ topologyRevision,
585
+ changes: {
586
+ kind: changes.kind,
587
+ nodeRanges: changes.nodeRanges.map((range) => ({ ...range })),
588
+ },
589
+ metrics: { ...metrics },
590
+ updates,
591
+ rebuilds,
592
+ refits,
593
+ };
594
+ },
595
+ dispose() {
596
+ if (disposed)
597
+ return;
598
+ disposed = true;
599
+ snapshot = undefined;
600
+ nodes = new Float32Array();
601
+ sources = new Uint32Array();
602
+ ordered = [];
603
+ keys = [];
604
+ materials.clear();
605
+ frozenData = new WeakSet();
606
+ validatedTriangles = new WeakSet();
607
+ },
608
+ };
609
+ }