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,446 @@
1
+ export function createUIRenderer(definitions, callbacks) {
2
+ const root = document.createElement("div");
3
+ root.dataset.pelicanUi = "";
4
+ Object.assign(root.style, {
5
+ position: "absolute",
6
+ inset: "0",
7
+ pointerEvents: "none",
8
+ zIndex: "3",
9
+ fontFamily: "system-ui",
10
+ color: "white",
11
+ });
12
+ const rows = new Map();
13
+ let order = [];
14
+ let mounted = false;
15
+ let disposed = false;
16
+ let sequence = 0;
17
+ let reportedFocus = null;
18
+ const traces = [];
19
+ const listeners = [];
20
+ let previousPads = new Map();
21
+ let gamepad = {
22
+ source: "none",
23
+ hardwareVerified: false,
24
+ index: null,
25
+ connected: 0,
26
+ };
27
+ function selection(row) {
28
+ const node = row.input;
29
+ return node
30
+ ? {
31
+ start: node.selectionStart ?? 0,
32
+ end: node.selectionEnd ?? 0,
33
+ direction: node.selectionDirection ?? "none",
34
+ }
35
+ : null;
36
+ }
37
+ function trace(type, row, event) {
38
+ const source = event;
39
+ traces.push({
40
+ sequence: ++sequence,
41
+ type,
42
+ id: row?.definition.id ?? null,
43
+ value: row?.input?.value ?? null,
44
+ composing: row?.composing ?? false,
45
+ selection: row ? selection(row) : null,
46
+ data: typeof source?.data === "string" ? source.data : null,
47
+ inputType: source && "inputType" in source ? source.inputType : null,
48
+ trusted: event?.isTrusted ?? false,
49
+ });
50
+ if (traces.length > 128)
51
+ traces.splice(0, traces.length - 128);
52
+ }
53
+ function focused() {
54
+ return [...rows.values()].find((row) => row.node === document.activeElement);
55
+ }
56
+ function interactive(row) {
57
+ return (mounted &&
58
+ !disposed &&
59
+ row.shown &&
60
+ root.isConnected &&
61
+ (row.definition.kind === "button" || row.definition.kind === "input"));
62
+ }
63
+ function targetRow(event) {
64
+ return [...rows.values()].find((row) => row.node === event.target);
65
+ }
66
+ function emitText(row) {
67
+ if (!row.input || row.composing || !interactive(row))
68
+ return;
69
+ const text = row.input.value;
70
+ if (row.emittedText === text)
71
+ return;
72
+ row.emittedText = text;
73
+ row.pendingCommit = text;
74
+ callbacks.emit("ui.text", {
75
+ id: row.definition.id,
76
+ text,
77
+ composing: false,
78
+ });
79
+ }
80
+ function listen(type, action) {
81
+ const listener = (event) => {
82
+ const row = targetRow(event);
83
+ if (row && !disposed)
84
+ action(event, row);
85
+ };
86
+ listeners.push({ type, listener });
87
+ root.addEventListener(type, listener);
88
+ }
89
+ function reportFocus(id) {
90
+ if (reportedFocus === id)
91
+ return;
92
+ reportedFocus = id;
93
+ callbacks.emit("ui.focus", { id });
94
+ }
95
+ listen("focusin", (event, row) => {
96
+ trace("focusin", row, event);
97
+ reportFocus(row.definition.id);
98
+ });
99
+ listen("focusout", (event, row) => {
100
+ trace("focusout", row, event);
101
+ reportFocus(null);
102
+ });
103
+ listen("compositionstart", (event, row) => {
104
+ if (!row.input || !interactive(row))
105
+ return;
106
+ row.composing = true;
107
+ trace(event.type, row, event);
108
+ });
109
+ listen("compositionupdate", (event, row) => trace(event.type, row, event));
110
+ listen("compositionend", (event, row) => {
111
+ row.composing = false;
112
+ trace(event.type, row, event);
113
+ emitText(row);
114
+ });
115
+ for (const type of ["beforeinput", "paste", "select"])
116
+ listen(type, (event, row) => trace(type, row, event));
117
+ listen("input", (event, row) => {
118
+ trace("input", row, event);
119
+ if (!event.isComposing)
120
+ emitText(row);
121
+ });
122
+ listen("keydown", (event, row) => {
123
+ const key = event;
124
+ if (!row.input || !interactive(row))
125
+ return;
126
+ trace("keydown", row, event);
127
+ if (key.key === "Enter" &&
128
+ !key.isComposing &&
129
+ !row.composing &&
130
+ key.keyCode !== 229) {
131
+ key.preventDefault();
132
+ if (!key.repeat)
133
+ callbacks.emit("ui.submit", {
134
+ id: row.definition.id,
135
+ text: row.input.value,
136
+ });
137
+ }
138
+ });
139
+ listen("click", (event, row) => {
140
+ if (row.definition.kind !== "button" || !interactive(row))
141
+ return;
142
+ trace("activate", row, event);
143
+ callbacks.input("ui:" + row.definition.id, true);
144
+ callbacks.input("ui:" + row.definition.id, false);
145
+ callbacks.emit("ui.activate", { id: row.definition.id });
146
+ });
147
+ function retire(row) {
148
+ row.shown = false;
149
+ row.composing = false;
150
+ if (row.node === document.activeElement)
151
+ row.node.blur();
152
+ row.node.remove();
153
+ }
154
+ function render(elements, positions) {
155
+ const ids = new Set();
156
+ for (const definition of elements) {
157
+ if (ids.has(definition.id))
158
+ throw new Error("TC_UI_DUPLICATE_ID: " + definition.id);
159
+ ids.add(definition.id);
160
+ }
161
+ for (const [id, row] of rows) {
162
+ if (!ids.has(id)) {
163
+ retire(row);
164
+ rows.delete(id);
165
+ }
166
+ }
167
+ order = elements.map((element) => element.id);
168
+ for (const definition of elements) {
169
+ let row = rows.get(definition.id);
170
+ if (row && row.definition.kind !== definition.kind) {
171
+ retire(row);
172
+ rows.delete(definition.id);
173
+ row = undefined;
174
+ }
175
+ if (!row) {
176
+ const node = document.createElement(definition.kind === "button"
177
+ ? "button"
178
+ : definition.kind === "bar"
179
+ ? "progress"
180
+ : definition.kind === "input"
181
+ ? "input"
182
+ : "div");
183
+ node.dataset.pelicanElement = definition.id;
184
+ Object.assign(node.style, {
185
+ position: "absolute",
186
+ padding: "8px 12px",
187
+ borderRadius: "8px",
188
+ background: "rgba(12,20,31,0.82)",
189
+ fontSize: "16px",
190
+ boxSizing: "border-box",
191
+ maxWidth: "90%",
192
+ });
193
+ const input = definition.kind === "input" ? node : null;
194
+ if (input) {
195
+ input.type = "text";
196
+ input.autocomplete = "off";
197
+ input.value = definition.text;
198
+ }
199
+ if (definition.kind === "button")
200
+ node.type = "button";
201
+ if (definition.kind === "bar")
202
+ node.max = 1;
203
+ row = {
204
+ definition,
205
+ node,
206
+ input,
207
+ shown: false,
208
+ composing: false,
209
+ authoredText: definition.text,
210
+ emittedText: null,
211
+ pendingCommit: null,
212
+ };
213
+ rows.set(definition.id, row);
214
+ root.appendChild(node);
215
+ }
216
+ row.definition = {
217
+ ...definition,
218
+ world: definition.world
219
+ ? {
220
+ ...definition.world,
221
+ offset: [...definition.world.offset],
222
+ }
223
+ : undefined,
224
+ };
225
+ const { node, input } = row;
226
+ // Per-element keys allow distinct offsets on the same world entity.
227
+ const position = definition.world
228
+ ? positions?.get(definition.id)
229
+ : undefined;
230
+ row.shown =
231
+ definition.visible &&
232
+ (!definition.world ||
233
+ (!!position &&
234
+ position.visible &&
235
+ Number.isFinite(position.x) &&
236
+ Number.isFinite(position.y) &&
237
+ (!definition.world.occlude || !position.occluded)));
238
+ if (!row.shown) {
239
+ // No hidden control can emit a blur commit or retain keyboard/gamepad focus.
240
+ row.composing = false;
241
+ if (node === document.activeElement)
242
+ node.blur();
243
+ }
244
+ node.hidden = !row.shown;
245
+ node.style.display = row.shown ? "" : "none";
246
+ const control = definition.kind === "input" || definition.kind === "button";
247
+ if (control)
248
+ node.disabled = !row.shown;
249
+ node.tabIndex = control && row.shown ? 0 : -1;
250
+ node.style.pointerEvents = control && row.shown ? "auto" : "none";
251
+ node.style.width = definition.width + "px";
252
+ node.style.color = definition.color;
253
+ const style = node.style;
254
+ style.left =
255
+ style.right =
256
+ style.top =
257
+ style.bottom =
258
+ style.transform =
259
+ "";
260
+ if (definition.world) {
261
+ style.left = (position?.x ?? 0) + "px";
262
+ style.top = (position?.y ?? 0) + "px";
263
+ }
264
+ else if (definition.anchor === "center") {
265
+ Object.assign(style, {
266
+ left: "50%",
267
+ top: "50%",
268
+ transform: "translate(-50%,-50%)",
269
+ });
270
+ }
271
+ else {
272
+ style[definition.anchor.endsWith("right") ? "right" : "left"] =
273
+ definition.x + "px";
274
+ style[definition.anchor.startsWith("bottom") ? "bottom" : "top"] =
275
+ definition.y + "px";
276
+ }
277
+ if (input) {
278
+ const active = document.activeElement === input;
279
+ const pending = row.pendingCommit !== null;
280
+ const echo = pending && definition.text === row.pendingCommit;
281
+ const allowWrite = !row.composing && !(active && pending);
282
+ // A game tick may still contain the pre-commit value. Wait for its exact echo while focused.
283
+ if (allowWrite &&
284
+ (row.authoredText !== definition.text || pending) &&
285
+ input.value !== definition.text) {
286
+ const saved = selection(row);
287
+ input.value = definition.text;
288
+ input.setSelectionRange(Math.min(saved.start, input.value.length), Math.min(saved.end, input.value.length), saved.direction);
289
+ row.emittedText = definition.text;
290
+ }
291
+ if (echo || (!active && !row.composing))
292
+ row.pendingCommit = null;
293
+ row.authoredText = definition.text;
294
+ }
295
+ else if (definition.kind === "bar") {
296
+ node.value = definition.value;
297
+ }
298
+ else if (node.textContent !== definition.text)
299
+ node.textContent = definition.text;
300
+ }
301
+ }
302
+ function moveFocus(direction) {
303
+ const candidates = order.map((id) => rows.get(id)).filter(interactive);
304
+ if (!candidates.length)
305
+ return;
306
+ const index = candidates.indexOf(focused());
307
+ const next = index < 0
308
+ ? direction > 0
309
+ ? 0
310
+ : candidates.length - 1
311
+ : (index + direction + candidates.length) % candidates.length;
312
+ candidates[next].node.focus({ preventScroll: true });
313
+ trace("gamepad.focus", candidates[next]);
314
+ }
315
+ function pollGamepads() {
316
+ const samples = typeof navigator.getGamepads === "function"
317
+ ? navigator.getGamepads()
318
+ : [];
319
+ const nextPads = new Map();
320
+ gamepad = {
321
+ source: "none",
322
+ hardwareVerified: false,
323
+ index: null,
324
+ connected: 0,
325
+ };
326
+ for (const pad of samples) {
327
+ if (!pad?.connected || pad.mapping !== "standard")
328
+ continue;
329
+ gamepad.connected++;
330
+ if (gamepad.index === null) {
331
+ gamepad.index = pad.index;
332
+ gamepad.source =
333
+ typeof Gamepad !== "undefined" && pad instanceof Gamepad
334
+ ? "physical"
335
+ : "virtual";
336
+ }
337
+ const pressed = (index) => pad.buttons[index]?.pressed === true ||
338
+ (pad.buttons[index]?.value ?? 0) > 0.5;
339
+ const state = [
340
+ pressed(12) || (pad.axes[1] ?? 0) < -0.5,
341
+ pressed(14) || (pad.axes[0] ?? 0) < -0.5,
342
+ pressed(13) || (pad.axes[1] ?? 0) > 0.5,
343
+ pressed(15) || (pad.axes[0] ?? 0) > 0.5,
344
+ pressed(0),
345
+ ];
346
+ const previous = previousPads.get(pad.index) ?? [
347
+ false,
348
+ false,
349
+ false,
350
+ false,
351
+ false,
352
+ ];
353
+ nextPads.set(pad.index, state);
354
+ // Consume edges during IME without moving focus or activating another control.
355
+ if ([...rows.values()].some((row) => row.composing))
356
+ continue;
357
+ const direction = state.findIndex((value, index) => index < 4 && value && !previous[index]);
358
+ if (direction >= 0)
359
+ moveFocus(direction < 2 ? -1 : 1);
360
+ if (state[4] && !previous[4]) {
361
+ const row = focused();
362
+ if (!row || !interactive(row))
363
+ moveFocus(1);
364
+ else if (row.definition.kind === "button")
365
+ row.node.click();
366
+ }
367
+ }
368
+ previousPads = nextPads;
369
+ }
370
+ render(definitions);
371
+ return {
372
+ mount() {
373
+ if (disposed || mounted)
374
+ return;
375
+ document.body.appendChild(root);
376
+ mounted = true;
377
+ trace("mount");
378
+ },
379
+ present(elements, positions) {
380
+ if (disposed)
381
+ return;
382
+ render(elements, positions);
383
+ if (mounted)
384
+ pollGamepads();
385
+ },
386
+ consumesKeyboard(event) {
387
+ if (disposed || !mounted)
388
+ return false;
389
+ return (event.isComposing ||
390
+ [...rows.values()].some((row) => row.composing) ||
391
+ !!focused()?.input ||
392
+ !!targetRow(event)?.input);
393
+ },
394
+ inspect() {
395
+ const focus = focused();
396
+ return {
397
+ mounted,
398
+ disposed,
399
+ focus: focus?.definition.id ?? null,
400
+ composing: [...rows.values()].some((row) => row.composing),
401
+ inputValue: focus?.input?.value ?? null,
402
+ selection: focus ? selection(focus) : null,
403
+ elements: order.map((id) => {
404
+ const row = rows.get(id);
405
+ return {
406
+ id,
407
+ kind: row.definition.kind,
408
+ visible: row.shown,
409
+ composing: row.composing,
410
+ inputValue: row.input?.value ?? null,
411
+ selection: selection(row),
412
+ pendingCommit: row.pendingCommit,
413
+ };
414
+ }),
415
+ trace: traces.map((entry) => ({
416
+ ...entry,
417
+ selection: entry.selection ? { ...entry.selection } : null,
418
+ })),
419
+ gamepad: { ...gamepad },
420
+ };
421
+ },
422
+ dispose() {
423
+ if (disposed)
424
+ return;
425
+ disposed = true;
426
+ mounted = false;
427
+ reportFocus(null);
428
+ for (const { type, listener } of listeners)
429
+ root.removeEventListener(type, listener);
430
+ listeners.length = 0;
431
+ for (const row of rows.values())
432
+ retire(row);
433
+ rows.clear();
434
+ order = [];
435
+ previousPads.clear();
436
+ gamepad = {
437
+ source: "none",
438
+ hardwareVerified: false,
439
+ index: null,
440
+ connected: 0,
441
+ };
442
+ root.remove();
443
+ trace("dispose");
444
+ },
445
+ };
446
+ }
@@ -0,0 +1,127 @@
1
+ import * as THREE from "three/webgpu";
2
+ import { createSurfaceCapture } from "./surface-capture.js";
3
+ import { createSurfaceAcceleration } from "./acceleration.js";
4
+ const fail = (message) => { throw new Error("TC_VERTEX_BOUNDS: " + message + " Repair: Supply finite nonsingular transforms and valid vertex graphs."); };
5
+ const entityName = (mesh) => {
6
+ for (let object = mesh; object; object = object.parent)
7
+ if (Object.hasOwn(object.userData, "pelicanPrefab"))
8
+ return object.name;
9
+ return mesh.parent?.name ?? "";
10
+ };
11
+ /** Content-owned derived bounds and render raycasts; explicit physics colliders are separate. */
12
+ export function createVertexBoundsOwner() {
13
+ const capture = createSurfaceCapture();
14
+ const states = new Map();
15
+ let disposed = false, updates = 0;
16
+ const restore = (state) => {
17
+ for (const [name, descriptor] of [["boundingSphere", state.sphere], ["boundingBox", state.box], ["raycast", state.raycast]]) {
18
+ if (descriptor)
19
+ Object.defineProperty(state.mesh, name, descriptor);
20
+ else
21
+ delete state.mesh[name];
22
+ }
23
+ state.acceleration.dispose();
24
+ };
25
+ const install = (mesh, surface, triangles) => {
26
+ const state = { mesh, sphere: Object.getOwnPropertyDescriptor(mesh, "boundingSphere"), box: Object.getOwnPropertyDescriptor(mesh, "boundingBox"), raycast: Object.getOwnPropertyDescriptor(mesh, "raycast"), acceleration: createSurfaceAcceleration(), surface: { ...surface, triangles } };
27
+ mesh.raycast = (raycaster, intersections) => {
28
+ const direction = raycaster.ray.direction.clone().normalize();
29
+ const near = Math.max(0, raycaster.near), far = raycaster.far;
30
+ if (far < near)
31
+ return;
32
+ const origin = raycaster.ray.origin.clone().addScaledVector(direction, near);
33
+ const hit = state.acceleration.intersect(origin.toArray(), direction.toArray(), far - near);
34
+ if (!hit)
35
+ return;
36
+ const triangle = state.surface.triangles[hit.triangleIndex];
37
+ const point = origin.addScaledVector(direction, hit.distance);
38
+ const [a, b, c] = triangle.vertexIndices;
39
+ const inverse = mesh.matrixWorld.clone().invert();
40
+ const local = triangle.vertices.map(vertex => new THREE.Vector3(...vertex.position).applyMatrix4(inverse));
41
+ const faceNormal = new THREE.Triangle(local[0], local[1], local[2]).getNormal(new THREE.Vector3());
42
+ intersections.push({ distance: near + hit.distance, point, object: mesh, faceIndex: triangle.elementOffset / 3, face: { a, b, c, normal: faceNormal, materialIndex: triangle.materialIndex }, ...(triangle.instanceId !== undefined ? { instanceId: triangle.instanceId } : {}) });
43
+ };
44
+ return state;
45
+ };
46
+ return {
47
+ update(scene, graphs) {
48
+ if (disposed)
49
+ fail("Derived vertex owner is disposed.");
50
+ if (![...graphs.values()].some(binding => binding.vertex)) {
51
+ for (const state of states.values())
52
+ restore(state);
53
+ states.clear();
54
+ return;
55
+ }
56
+ const captured = capture.capture(scene, new Set(graphs.keys()), graphs, { geometryOnly: true });
57
+ const byObject = new Map();
58
+ for (const triangle of captured.surface.triangles) {
59
+ if (!byObject.has(triangle.objectUuid))
60
+ byObject.set(triangle.objectUuid, []);
61
+ byObject.get(triangle.objectUuid).push(triangle);
62
+ }
63
+ const staged = [];
64
+ scene.traverseVisible(object => {
65
+ if (!(object instanceof THREE.Mesh))
66
+ return;
67
+ const triangles = byObject.get(object.uuid);
68
+ if (!triangles)
69
+ return;
70
+ const inverse = object.matrixWorld.clone().invert();
71
+ const bounds = new THREE.Box3();
72
+ for (const triangle of triangles)
73
+ for (const vertex of triangle.vertices)
74
+ bounds.expandByPoint(new THREE.Vector3(...vertex.position).applyMatrix4(inverse));
75
+ const sphere = bounds.getBoundingSphere(new THREE.Sphere());
76
+ if (![...bounds.min.toArray(), ...bounds.max.toArray(), ...sphere.center.toArray(), sphere.radius].every(Number.isFinite))
77
+ fail("Derived bounds are nonfinite for " + object.uuid);
78
+ staged.push({ mesh: object, triangles, bounds, sphere });
79
+ });
80
+ const live = new Set(staged.map(item => item.mesh));
81
+ for (const [mesh, state] of states)
82
+ if (!live.has(mesh)) {
83
+ restore(state);
84
+ states.delete(mesh);
85
+ }
86
+ for (const item of staged) {
87
+ let state = states.get(item.mesh);
88
+ if (!state) {
89
+ state = install(item.mesh, captured.surface, item.triangles);
90
+ states.set(item.mesh, state);
91
+ }
92
+ const next = { ...captured.surface, triangles: item.triangles };
93
+ state.acceleration.update(next);
94
+ state.surface = next;
95
+ Object.defineProperty(item.mesh, "boundingBox", { configurable: true, writable: true, enumerable: true, value: item.bounds });
96
+ Object.defineProperty(item.mesh, "boundingSphere", { configurable: true, writable: true, enumerable: true, value: item.sphere });
97
+ }
98
+ updates++;
99
+ },
100
+ inspect() {
101
+ return { disposed, updates, meshes: states.size, capture: capture.inspect(), accelerations: [...states.values()].map(state => ({ object: state.mesh.uuid, ...state.acceleration.inspect() })), bounds: [...states.values()].map(state => {
102
+ const mesh = state.mesh;
103
+ return { object: mesh.uuid, entity: entityName(mesh), frustumCulled: mesh.frustumCulled, center: mesh.boundingSphere.center.toArray(), worldCenter: mesh.boundingSphere.center.clone().applyMatrix4(mesh.matrixWorld).toArray(), radius: mesh.boundingSphere.radius, min: mesh.boundingBox.min.toArray(), max: mesh.boundingBox.max.toArray() };
104
+ }) };
105
+ },
106
+ intersect(origin, direction, maxDistance = Infinity) {
107
+ if (disposed)
108
+ fail("Derived vertex owner is disposed.");
109
+ let nearest = null;
110
+ for (const state of states.values()) {
111
+ const hit = state.acceleration.intersect(origin, direction, maxDistance);
112
+ if (hit && (!nearest || hit.distance < nearest.distance))
113
+ nearest = { distance: hit.distance, normal: hit.normal, object: state.mesh.uuid, entity: entityName(state.mesh) };
114
+ }
115
+ return nearest;
116
+ },
117
+ dispose() {
118
+ if (disposed)
119
+ return;
120
+ disposed = true;
121
+ for (const state of states.values())
122
+ restore(state);
123
+ states.clear();
124
+ capture.dispose();
125
+ },
126
+ };
127
+ }
@@ -0,0 +1 @@
1
+ export {};