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.
- package/AGENTS.md +147 -0
- package/LICENSE +22 -0
- package/README.md +129 -2
- package/distribution.json +197 -0
- package/docs/AI-WORKFLOW.md +517 -0
- package/docs/ARCHITECTURE.md +268 -0
- package/docs/CAPABILITIES.md +264 -0
- package/docs/DECISIONS.md +107 -0
- package/docs/M3-USAGE.md +192 -0
- package/docs/PELICAN-MIGRATION.md +38 -0
- package/docs/RENDER-BACKENDS.md +50 -0
- package/docs/ROADMAP.md +239 -0
- package/docs/THIRD-PARTY-NOTICES.md +151 -0
- package/docs/api/REFERENCE.md +1435 -0
- package/docs/api/index.json +1139 -0
- package/docs/npm/DISTRIBUTION.md +130 -0
- package/docs/npm/GAME-AUTHOR.md +147 -0
- package/engine.manifest.json +15 -0
- package/lib/assets/audio.js +67 -0
- package/lib/assets/errors.js +36 -0
- package/lib/assets/glb.js +277 -0
- package/lib/assets/index.js +53 -0
- package/lib/assets/jpeg.js +199 -0
- package/lib/assets/model.js +558 -0
- package/lib/assets/optimize.js +95 -0
- package/lib/assets/texture.js +187 -0
- package/lib/compiler/distribution.js +17 -0
- package/lib/compiler/extract.js +425 -0
- package/lib/compiler/graph.js +366 -0
- package/lib/compiler/index.js +574 -0
- package/lib/compiler/normalize.js +1194 -0
- package/lib/compiler/source-map.js +25 -0
- package/lib/compiler/types.js +2 -0
- package/lib/compiler/version.js +93 -0
- package/lib/experiments/simulation/index.js +2 -0
- package/lib/experiments/simulation/math.js +41 -0
- package/lib/experiments/simulation/probe.js +215 -0
- package/lib/experiments/simulation/scenes.js +192 -0
- package/lib/experiments/simulation/solver.js +346 -0
- package/lib/experiments/simulation/types.js +1 -0
- package/lib/experiments/world/adapters.js +30 -0
- package/lib/experiments/world/geometry.js +180 -0
- package/lib/experiments/world/index.js +2 -0
- package/lib/experiments/world/probe.js +288 -0
- package/lib/experiments/world/types.js +1 -0
- package/lib/experiments/world/world.js +599 -0
- package/lib/navigation/index.js +157 -0
- package/lib/physics/index.js +909 -0
- package/lib/protocol/index.js +92 -0
- package/lib/renderer/acceleration.js +609 -0
- package/lib/renderer/browser.js +2015 -0
- package/lib/renderer/bsdf.js +260 -0
- package/lib/renderer/checkpoint.js +88 -0
- package/lib/renderer/compute-executor.js +1034 -0
- package/lib/renderer/compute-texture-assets.js +49 -0
- package/lib/renderer/compute-view-contract.js +1 -0
- package/lib/renderer/denoiser.js +361 -0
- package/lib/renderer/dielectric.js +360 -0
- package/lib/renderer/embedded-image-pixels.js +368 -0
- package/lib/renderer/emissive-lights.js +67 -0
- package/lib/renderer/environment-importance.js +55 -0
- package/lib/renderer/environment.js +346 -0
- package/lib/renderer/gameplay.js +464 -0
- package/lib/renderer/geometry.js +103 -0
- package/lib/renderer/graph-expressions.js +485 -0
- package/lib/renderer/graph-tsl.js +141 -0
- package/lib/renderer/graph.js +214 -0
- package/lib/renderer/managed-texture-sampling.js +29 -0
- package/lib/renderer/material-graphs.js +297 -0
- package/lib/renderer/medium.js +348 -0
- package/lib/renderer/models.js +1053 -0
- package/lib/renderer/motion-shader.js +40 -0
- package/lib/renderer/path-shader.js +196 -0
- package/lib/renderer/pathtrace.js +324 -0
- package/lib/renderer/postprocess-contract.js +1 -0
- package/lib/renderer/postprocess.js +494 -0
- package/lib/renderer/render-buffers.js +132 -0
- package/lib/renderer/render-textures.js +155 -0
- package/lib/renderer/startup.js +50 -0
- package/lib/renderer/surface-capture.js +199 -0
- package/lib/renderer/surface-gpu.js +270 -0
- package/lib/renderer/surface-motion.js +84 -0
- package/lib/renderer/surface-ray-shader.js +91 -0
- package/lib/renderer/surface-shader.js +88 -0
- package/lib/renderer/surface-tracer.js +1720 -0
- package/lib/renderer/surface.js +1425 -0
- package/lib/renderer/temporal-filter.js +512 -0
- package/lib/renderer/texture-pixels.js +207 -0
- package/lib/renderer/traversal-nodes.js +54 -0
- package/lib/renderer/ui.js +446 -0
- package/lib/renderer/vertex-bounds.js +127 -0
- package/lib/renderer/vertex-contract.js +1 -0
- package/lib/renderer/vertex-graphs.js +197 -0
- package/lib/renderer/volume-shader.js +50 -0
- package/lib/runtime/compute-contract.js +1 -0
- package/lib/runtime/compute-service.js +674 -0
- package/lib/runtime/contracts.js +1 -0
- package/lib/runtime/error.js +37 -0
- package/lib/runtime/events.js +189 -0
- package/lib/runtime/game-services.js +772 -0
- package/lib/runtime/host.js +839 -0
- package/lib/runtime/material-parameters.js +145 -0
- package/lib/runtime/plugins.js +1 -0
- package/lib/runtime/postprocess-contract.js +1 -0
- package/lib/runtime/postprocess.js +189 -0
- package/lib/runtime/prefabs.js +13 -0
- package/lib/runtime/random.js +88 -0
- package/lib/runtime/render-buffer-contract.js +1 -0
- package/lib/runtime/render-buffers.js +243 -0
- package/lib/runtime/render-capabilities.js +17 -0
- package/lib/runtime/render-settings.js +249 -0
- package/lib/runtime/render-texture-contract.js +1 -0
- package/lib/runtime/render-textures.js +271 -0
- package/lib/runtime/resources.js +648 -0
- package/lib/runtime/scheduler.js +765 -0
- package/lib/runtime/storage.js +169 -0
- package/lib/runtime/world.js +594 -0
- package/lib/schema/index.js +24 -0
- package/lib/sdk/graph-values.js +16 -0
- package/lib/sdk/index.d.ts +800 -0
- package/lib/sdk/index.js +82 -0
- package/lib/tools/api-index.js +113 -0
- package/lib/tools/browser-session.js +89 -0
- package/lib/tools/cli.js +513 -0
- package/lib/tools/compress-dist.js +59 -0
- package/lib/tools/control-client.js +153 -0
- package/lib/tools/control-server.js +633 -0
- package/lib/tools/diagnostic-code.js +3 -0
- package/lib/tools/doctor.js +496 -0
- package/lib/tools/npm/browser-cli.js +6 -0
- package/lib/tools/npm/entry.js +40 -0
- package/lib/tools/npm/layout.js +195 -0
- package/lib/tools/setup.js +153 -0
- package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
- package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@gltf-transform/core/LICENSE.md +21 -0
- package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
- package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
- package/licenses/@img/colour/LICENSE.md +82 -0
- package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
- package/licenses/@oxc-project/types/LICENSE +22 -0
- package/licenses/@rolldown/pluginutils/LICENSE +21 -0
- package/licenses/@tweenjs/tween.js/LICENSE +23 -0
- package/licenses/@types/ndarray/LICENSE +21 -0
- package/licenses/@types/node/LICENSE +21 -0
- package/licenses/@types/stats.js/LICENSE +21 -0
- package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@types/three/LICENSE +21 -0
- package/licenses/@types/webxr/LICENSE +21 -0
- package/licenses/@webgpu/types/LICENSE +26 -0
- package/licenses/THIRD-PARTY-NOTICES.md +139 -0
- package/licenses/cwise-compiler/LICENSE +22 -0
- package/licenses/detect-libc/LICENSE +201 -0
- package/licenses/esbuild/LICENSE.md +21 -0
- package/licenses/fast-png/LICENSE +21 -0
- package/licenses/fdir/LICENSE +7 -0
- package/licenses/fflate/LICENSE +21 -0
- package/licenses/iobuffer/LICENSE +22 -0
- package/licenses/iota-array/LICENSE +22 -0
- package/licenses/is-buffer/LICENSE +21 -0
- package/licenses/ktx-parse/LICENSE +21 -0
- package/licenses/lightningcss/LICENSE +373 -0
- package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
- package/licenses/meshoptimizer/LICENSE.md +21 -0
- package/licenses/nanoid/LICENSE +20 -0
- package/licenses/ndarray/LICENSE +22 -0
- package/licenses/ndarray-lanczos/LICENSE +21 -0
- package/licenses/ndarray-ops/LICENSE +22 -0
- package/licenses/ndarray-pixels/LICENSE +21 -0
- package/licenses/ngraph.path/LICENSE +21 -0
- package/licenses/picocolors/LICENSE +15 -0
- package/licenses/picomatch/LICENSE +21 -0
- package/licenses/playwright/LICENSE +202 -0
- package/licenses/playwright/NOTICE +5 -0
- package/licenses/playwright-core/LICENSE +202 -0
- package/licenses/playwright-core/NOTICE +5 -0
- package/licenses/postcss/LICENSE +20 -0
- package/licenses/prettier/LICENSE +7 -0
- package/licenses/property-graph/LICENSE +21 -0
- package/licenses/rolldown/LICENSE +25 -0
- package/licenses/semver/LICENSE +15 -0
- package/licenses/sharp/LICENSE +191 -0
- package/licenses/source-map-js/LICENSE +28 -0
- package/licenses/three/LICENSE +21 -0
- package/licenses/tinyglobby/LICENSE +21 -0
- package/licenses/tsx/LICENSE +21 -0
- package/licenses/typescript/LICENSE.txt +55 -0
- package/licenses/undici-types/LICENSE +21 -0
- package/licenses/uniq/LICENSE +22 -0
- package/licenses/vite/LICENSE.md +2084 -0
- package/package.json +49 -4
- 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 {};
|