@vizij/runtime-react 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +422 -117
- package/dist/index.cjs +3642 -352
- package/dist/index.d.cts +204 -14
- package/dist/index.d.ts +204 -14
- package/dist/index.js +3628 -365
- package/package.json +26 -22
- package/LICENSE +0 -201
package/dist/index.js
CHANGED
|
@@ -1,31 +1,494 @@
|
|
|
1
1
|
// src/VizijRuntimeProvider.tsx
|
|
2
|
-
import {
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
useState
|
|
9
|
-
} from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
3
|
import {
|
|
11
4
|
VizijContext,
|
|
12
5
|
createVizijStore,
|
|
13
6
|
loadGLTFWithBundle,
|
|
14
7
|
loadGLTFFromBlobWithBundle
|
|
15
8
|
} from "@vizij/render";
|
|
16
|
-
import {
|
|
17
|
-
OrchestratorProvider,
|
|
18
|
-
OrchestratorContext,
|
|
19
|
-
useOrchestrator,
|
|
20
|
-
useOrchFrame
|
|
21
|
-
} from "@vizij/orchestrator-react";
|
|
22
9
|
import { compileIrGraph } from "@vizij/node-graph-authoring";
|
|
23
10
|
import { valueAsNumber as valueAsNumber2 } from "@vizij/value-json";
|
|
11
|
+
import { getLookup } from "@vizij/utils";
|
|
12
|
+
import { loadAnimationModule } from "@vizij/animation-module";
|
|
13
|
+
|
|
14
|
+
// src/engine/aroraEngine.ts
|
|
15
|
+
import {
|
|
16
|
+
init,
|
|
17
|
+
startRuntime
|
|
18
|
+
} from "@vizij/runtime";
|
|
19
|
+
|
|
20
|
+
// src/engine/graphSpecDiff.ts
|
|
21
|
+
function asRecords(value) {
|
|
22
|
+
return Array.isArray(value) ? value : [];
|
|
23
|
+
}
|
|
24
|
+
function nodeId(node) {
|
|
25
|
+
return typeof node.id === "string" ? node.id : void 0;
|
|
26
|
+
}
|
|
27
|
+
function endpointId(endpoint) {
|
|
28
|
+
const id = endpoint?.node_id;
|
|
29
|
+
return typeof id === "string" ? id : void 0;
|
|
30
|
+
}
|
|
31
|
+
function endpointInput(endpoint) {
|
|
32
|
+
const input = endpoint?.input;
|
|
33
|
+
return typeof input === "string" ? input : void 0;
|
|
34
|
+
}
|
|
35
|
+
function stable(value) {
|
|
36
|
+
return JSON.stringify(sortKeys(value));
|
|
37
|
+
}
|
|
38
|
+
function sortKeys(value) {
|
|
39
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const key of Object.keys(value).sort()) {
|
|
43
|
+
out[key] = sortKeys(value[key]);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function edgeKey(edge) {
|
|
50
|
+
return stable([
|
|
51
|
+
endpointId(edge.from),
|
|
52
|
+
edge.from?.output ?? "out",
|
|
53
|
+
endpointId(edge.to),
|
|
54
|
+
endpointInput(edge.to),
|
|
55
|
+
edge.selector ?? null
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
function graphSpecDiff(prev, next) {
|
|
59
|
+
const prevNodes = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const node of asRecords(prev.nodes)) {
|
|
61
|
+
const id = nodeId(node);
|
|
62
|
+
if (id) prevNodes.set(id, node);
|
|
63
|
+
}
|
|
64
|
+
const nextNodes = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const node of asRecords(next.nodes)) {
|
|
66
|
+
const id = nodeId(node);
|
|
67
|
+
if (id) nextNodes.set(id, node);
|
|
68
|
+
}
|
|
69
|
+
const prevEdges = asRecords(prev.edges);
|
|
70
|
+
const nextEdges = asRecords(next.edges);
|
|
71
|
+
const remove_nodes = [...prevNodes.keys()].filter((id) => !nextNodes.has(id));
|
|
72
|
+
const upsertIds = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const [id, node] of nextNodes) {
|
|
74
|
+
const before = prevNodes.get(id);
|
|
75
|
+
if (!before || stable(before) !== stable(node)) upsertIds.add(id);
|
|
76
|
+
}
|
|
77
|
+
const prevEdgeKeys = new Set(prevEdges.map(edgeKey));
|
|
78
|
+
const nextEdgeKeys = new Set(nextEdges.map(edgeKey));
|
|
79
|
+
const removedEdges = prevEdges.filter((e) => !nextEdgeKeys.has(edgeKey(e)));
|
|
80
|
+
const changedEdges = [
|
|
81
|
+
...nextEdges.filter((e) => !prevEdgeKeys.has(edgeKey(e))),
|
|
82
|
+
...removedEdges
|
|
83
|
+
];
|
|
84
|
+
for (const edge of changedEdges) {
|
|
85
|
+
for (const id of [endpointId(edge.from), endpointId(edge.to)]) {
|
|
86
|
+
if (id && nextNodes.has(id)) upsertIds.add(id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const incidentToUpsert = (edge) => {
|
|
90
|
+
const from = endpointId(edge.from);
|
|
91
|
+
const to = endpointId(edge.to);
|
|
92
|
+
return from !== void 0 && upsertIds.has(from) || to !== void 0 && upsertIds.has(to);
|
|
93
|
+
};
|
|
94
|
+
const upsert_nodes = [...upsertIds].map((id) => nextNodes.get(id));
|
|
95
|
+
const upsert_edges = nextEdges.filter(incidentToUpsert);
|
|
96
|
+
const removeNodeSet = new Set(remove_nodes);
|
|
97
|
+
const remove_edges = removedEdges.flatMap((edge) => {
|
|
98
|
+
const to = endpointId(edge.to);
|
|
99
|
+
const input = endpointInput(edge.to);
|
|
100
|
+
if (!to || !input || removeNodeSet.has(to) || upsertIds.has(to)) return [];
|
|
101
|
+
return [{ node_id: to, input }];
|
|
102
|
+
});
|
|
103
|
+
if (upsert_nodes.length === 0 && remove_nodes.length === 0 && upsert_edges.length === 0 && remove_edges.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return { upsert_nodes, remove_nodes, upsert_edges, remove_edges };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/engine/aroraEngine.ts
|
|
110
|
+
var BUILT_IN_PREFIX = "arora/";
|
|
111
|
+
function isBuiltInPath(path) {
|
|
112
|
+
return path.startsWith(BUILT_IN_PREFIX);
|
|
113
|
+
}
|
|
114
|
+
var RUN_PERIOD_MS = 1e3 / 60;
|
|
115
|
+
var initPromise = null;
|
|
116
|
+
function ensureWasmInit(input) {
|
|
117
|
+
if (!initPromise) {
|
|
118
|
+
initPromise = init(input);
|
|
119
|
+
}
|
|
120
|
+
return initPromise;
|
|
121
|
+
}
|
|
122
|
+
var DeviceSlot = class {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.handle = null;
|
|
125
|
+
/** Module loading a boot must wait out before it reads `modules`. */
|
|
126
|
+
this.modulesLoading = null;
|
|
127
|
+
/**
|
|
128
|
+
* When set, a booted device is handed to its own loop (`device.run`) at
|
|
129
|
+
* this period. `null`: the host drives `step()` itself.
|
|
130
|
+
*/
|
|
131
|
+
this.runPeriodMs = null;
|
|
132
|
+
/**
|
|
133
|
+
* Serializes device operations. Every `recompose` chains behind the
|
|
134
|
+
* previous op so two never run concurrently — a bundle whose import fans
|
|
135
|
+
* out into several graph registrations recomposes in a burst. Failures are
|
|
136
|
+
* swallowed on the chain so one failed op doesn't wedge later ones; each
|
|
137
|
+
* caller still sees its own rejection through the promise `enqueue`
|
|
138
|
+
* returns.
|
|
139
|
+
*/
|
|
140
|
+
this.opChain = Promise.resolve();
|
|
141
|
+
}
|
|
142
|
+
get current() {
|
|
143
|
+
return this.handle;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The modules every subsequent device boot loads. The live device (if any)
|
|
147
|
+
* is not touched: a later `recompose` that sees the list changed rebuilds.
|
|
148
|
+
*/
|
|
149
|
+
setModules(modules) {
|
|
150
|
+
this.modules = modules && modules.length > 0 ? modules : void 0;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Announce module loading in flight: boots wait for `loading` to settle
|
|
154
|
+
* before reading the module list, so a device never boots without modules
|
|
155
|
+
* that were already on their way. Pass a promise that cannot reject (the
|
|
156
|
+
* owner handles its own failures) — boots resume either way.
|
|
157
|
+
*/
|
|
158
|
+
waitForModules(loading) {
|
|
159
|
+
this.modulesLoading = loading;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Hand the live device to its own loop, if there is one and `runPeriodMs`
|
|
163
|
+
* says so — for a host whose drive mode turns self-paced after boot.
|
|
164
|
+
*/
|
|
165
|
+
startRun() {
|
|
166
|
+
if (this.handle) {
|
|
167
|
+
this.maybeRun(this.handle.device);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
maybeRun(device) {
|
|
171
|
+
if (this.runPeriodMs === null || device.running) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
device.run(this.runPeriodMs).catch((error) => {
|
|
175
|
+
this.onRunEnded?.(error);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
enqueue(op) {
|
|
179
|
+
const run = this.opChain.then(op, op);
|
|
180
|
+
this.opChain = run.then(
|
|
181
|
+
() => void 0,
|
|
182
|
+
() => void 0
|
|
183
|
+
);
|
|
184
|
+
return run;
|
|
185
|
+
}
|
|
186
|
+
async boot(spec) {
|
|
187
|
+
await ensureWasmInit();
|
|
188
|
+
if (this.modulesLoading) {
|
|
189
|
+
await this.modulesLoading;
|
|
190
|
+
}
|
|
191
|
+
const device = await startRuntime(spec, void 0, this.modules);
|
|
192
|
+
this.handle = { device, spec, modules: this.modules };
|
|
193
|
+
this.onDeviceStarted?.(this.handle);
|
|
194
|
+
this.maybeRun(device);
|
|
195
|
+
return this.handle;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Rebuild the device because its module set changed since boot — the one
|
|
199
|
+
* change a live device cannot take. Carries the store across (snapshot
|
|
200
|
+
* minus built-in keys) and replays module setup via `onDeviceStarted`. The
|
|
201
|
+
* old device's wrapper is freed; if it was self-paced its loop keeps
|
|
202
|
+
* ticking unreachable — which is why boots wait for announced modules
|
|
203
|
+
* instead of leaning on this path.
|
|
204
|
+
*/
|
|
205
|
+
async rebuild(old, spec) {
|
|
206
|
+
const carried = {};
|
|
207
|
+
for (const [path, value] of Object.entries(old.device.snapshot())) {
|
|
208
|
+
if (!isBuiltInPath(path)) {
|
|
209
|
+
carried[path] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const device = await startRuntime(spec, void 0, this.modules);
|
|
213
|
+
if (Object.keys(carried).length > 0) {
|
|
214
|
+
device.writeValues(carried);
|
|
215
|
+
}
|
|
216
|
+
this.handle = { device, spec, modules: this.modules };
|
|
217
|
+
old.device.dispose();
|
|
218
|
+
this.onDeviceStarted?.(this.handle);
|
|
219
|
+
this.maybeRun(device);
|
|
220
|
+
return this.handle;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Make the device run `spec`: boot it if none is live, otherwise swap the
|
|
224
|
+
* graph in place (the store is the same store, so nothing is carried).
|
|
225
|
+
* No-op if the spec is identical by reference. Serialized behind any
|
|
226
|
+
* in-flight op (see `opChain`).
|
|
227
|
+
*/
|
|
228
|
+
recompose(spec) {
|
|
229
|
+
return this.enqueue(async () => {
|
|
230
|
+
const old = this.handle;
|
|
231
|
+
if (!old) {
|
|
232
|
+
return this.boot(spec);
|
|
233
|
+
}
|
|
234
|
+
if (old.modules !== this.modules) {
|
|
235
|
+
return this.rebuild(old, spec);
|
|
236
|
+
}
|
|
237
|
+
if (old.spec === spec) {
|
|
238
|
+
return old;
|
|
239
|
+
}
|
|
240
|
+
const edits = graphSpecDiff(old.spec, spec);
|
|
241
|
+
if (edits) {
|
|
242
|
+
try {
|
|
243
|
+
await old.device.applyGraphEdits(edits);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.warn(
|
|
246
|
+
"[vizij-runtime] applyGraphEdits failed; reloading the whole graph",
|
|
247
|
+
err
|
|
248
|
+
);
|
|
249
|
+
await old.device.loadGraph(spec);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
old.spec = spec;
|
|
253
|
+
return old;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/utils/composeGraph.ts
|
|
259
|
+
function asArray(value) {
|
|
260
|
+
return Array.isArray(value) ? value.filter((n) => n && typeof n === "object") : [];
|
|
261
|
+
}
|
|
262
|
+
function prefixId(sourceId, id) {
|
|
263
|
+
return typeof id === "string" && id ? `${sourceId}::${id}` : id;
|
|
264
|
+
}
|
|
265
|
+
function prefixEndpoint(sourceId, endpoint) {
|
|
266
|
+
if (!endpoint || typeof endpoint !== "object") {
|
|
267
|
+
return endpoint;
|
|
268
|
+
}
|
|
269
|
+
const record = endpoint;
|
|
270
|
+
if (typeof record.node_id !== "string") {
|
|
271
|
+
return endpoint;
|
|
272
|
+
}
|
|
273
|
+
return { ...record, node_id: prefixId(sourceId, record.node_id) };
|
|
274
|
+
}
|
|
275
|
+
function outputPathsOf(spec) {
|
|
276
|
+
const paths = [];
|
|
277
|
+
for (const node of asArray(spec.nodes)) {
|
|
278
|
+
if (String(node.type ?? "").toLowerCase() !== "output") {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const path = node.params?.path;
|
|
282
|
+
if (typeof path === "string" && path.trim()) {
|
|
283
|
+
paths.push(path.trim());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return paths;
|
|
287
|
+
}
|
|
288
|
+
function composeGraphSpecs(sources) {
|
|
289
|
+
const nodes = [];
|
|
290
|
+
const edges = [];
|
|
291
|
+
const outputOwners = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const { sourceId, spec } of sources) {
|
|
293
|
+
for (const path of outputPathsOf(spec)) {
|
|
294
|
+
const owner = outputOwners.get(path);
|
|
295
|
+
if (owner && owner !== sourceId) {
|
|
296
|
+
console.warn(
|
|
297
|
+
`[vizij-runtime] output path "${path}" is written by both "${owner}" and "${sourceId}"; last writer wins ("${sourceId}"). There is no combiner node for additive merges yet.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
outputOwners.set(path, sourceId);
|
|
301
|
+
}
|
|
302
|
+
for (const node of asArray(spec.nodes)) {
|
|
303
|
+
nodes.push({ ...node, id: prefixId(sourceId, node.id) });
|
|
304
|
+
}
|
|
305
|
+
for (const edge of asArray(spec.edges)) {
|
|
306
|
+
edges.push({
|
|
307
|
+
...edge,
|
|
308
|
+
from: prefixEndpoint(sourceId, edge.from),
|
|
309
|
+
to: prefixEndpoint(sourceId, edge.to)
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return { nodes, edges };
|
|
314
|
+
}
|
|
24
315
|
|
|
25
316
|
// src/context.ts
|
|
26
317
|
import { createContext } from "react";
|
|
27
318
|
var VizijRuntimeContext = createContext(null);
|
|
28
319
|
|
|
320
|
+
// src/memoryInvestigation.ts
|
|
321
|
+
function getGlobalMemoryInvestigation() {
|
|
322
|
+
return globalThis;
|
|
323
|
+
}
|
|
324
|
+
function updateRuntimeMap(updater) {
|
|
325
|
+
const globalObj = getGlobalMemoryInvestigation();
|
|
326
|
+
if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!globalObj.__vizijMemoryDebugState) {
|
|
330
|
+
globalObj.__vizijMemoryDebugState = {};
|
|
331
|
+
}
|
|
332
|
+
if (!globalObj.__vizijMemoryDebugState.runtimes) {
|
|
333
|
+
globalObj.__vizijMemoryDebugState.runtimes = {};
|
|
334
|
+
}
|
|
335
|
+
updater(globalObj.__vizijMemoryDebugState.runtimes);
|
|
336
|
+
}
|
|
337
|
+
function setRuntimeDebugState(runtimeId, state) {
|
|
338
|
+
updateRuntimeMap((runtimeMap) => {
|
|
339
|
+
runtimeMap[runtimeId] = state;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function clearRuntimeDebugState(runtimeId) {
|
|
343
|
+
updateRuntimeMap((runtimeMap) => {
|
|
344
|
+
delete runtimeMap[runtimeId];
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/updatePolicy.ts
|
|
349
|
+
var blobIdentityMap = /* @__PURE__ */ new WeakMap();
|
|
350
|
+
var nextBlobIdentity = 0;
|
|
351
|
+
function getBlobIdentity(blob) {
|
|
352
|
+
if (!blob) {
|
|
353
|
+
return "missing";
|
|
354
|
+
}
|
|
355
|
+
let identity = blobIdentityMap.get(blob);
|
|
356
|
+
if (identity === void 0) {
|
|
357
|
+
identity = nextBlobIdentity++;
|
|
358
|
+
blobIdentityMap.set(blob, identity);
|
|
359
|
+
}
|
|
360
|
+
return `${identity}:${blob.size}:${blob.type}`;
|
|
361
|
+
}
|
|
362
|
+
function applyRuntimeGraphBundle(base, bundle) {
|
|
363
|
+
const next = {
|
|
364
|
+
...base
|
|
365
|
+
};
|
|
366
|
+
const hasRigOverride = Object.prototype.hasOwnProperty.call(bundle, "rig");
|
|
367
|
+
const hasPoseOverride = Object.prototype.hasOwnProperty.call(bundle, "pose");
|
|
368
|
+
const hasAnimationsOverride = Object.prototype.hasOwnProperty.call(
|
|
369
|
+
bundle,
|
|
370
|
+
"animations"
|
|
371
|
+
);
|
|
372
|
+
const hasProgramsOverride = Object.prototype.hasOwnProperty.call(
|
|
373
|
+
bundle,
|
|
374
|
+
"programs"
|
|
375
|
+
);
|
|
376
|
+
if (hasRigOverride) {
|
|
377
|
+
next.rig = bundle.rig;
|
|
378
|
+
}
|
|
379
|
+
if (hasPoseOverride) {
|
|
380
|
+
next.pose = bundle.pose;
|
|
381
|
+
}
|
|
382
|
+
if (hasAnimationsOverride) {
|
|
383
|
+
next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
|
|
384
|
+
}
|
|
385
|
+
if (hasProgramsOverride) {
|
|
386
|
+
next.programs = Array.isArray(bundle.programs) ? bundle.programs : void 0;
|
|
387
|
+
}
|
|
388
|
+
return next;
|
|
389
|
+
}
|
|
390
|
+
function normalizeSpecPayload(value) {
|
|
391
|
+
if (!value) {
|
|
392
|
+
return "";
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
return JSON.stringify(value);
|
|
396
|
+
} catch {
|
|
397
|
+
return String(value);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function glbSignature(glb) {
|
|
401
|
+
if (glb.kind === "url") {
|
|
402
|
+
const importOptions = normalizeSpecPayload({
|
|
403
|
+
aggressiveImport: glb.aggressiveImport ?? false,
|
|
404
|
+
rootBounds: glb.rootBounds ?? null
|
|
405
|
+
});
|
|
406
|
+
return `url:${glb.src}:${importOptions}`;
|
|
407
|
+
}
|
|
408
|
+
if (glb.kind === "blob") {
|
|
409
|
+
const importOptions = normalizeSpecPayload({
|
|
410
|
+
aggressiveImport: glb.aggressiveImport ?? false,
|
|
411
|
+
rootBounds: glb.rootBounds ?? null
|
|
412
|
+
});
|
|
413
|
+
return `blob:${getBlobIdentity(glb.blob)}:${importOptions}`;
|
|
414
|
+
}
|
|
415
|
+
return `world:${normalizeSpecPayload(glb.world)}`;
|
|
416
|
+
}
|
|
417
|
+
function graphSignature(graph) {
|
|
418
|
+
if (!graph) {
|
|
419
|
+
return "";
|
|
420
|
+
}
|
|
421
|
+
const id = graph.id ?? "";
|
|
422
|
+
return `${id}:${normalizeSpecPayload(graph.spec ?? graph.ir ?? null)}`;
|
|
423
|
+
}
|
|
424
|
+
function poseSignature(pose) {
|
|
425
|
+
if (!pose) {
|
|
426
|
+
return "";
|
|
427
|
+
}
|
|
428
|
+
const graph = pose.graph;
|
|
429
|
+
const config = pose.config;
|
|
430
|
+
const graphPart = graph ? graphSignature({ id: graph.id, spec: graph.spec }) : "";
|
|
431
|
+
const configPart = config ? normalizeSpecPayload(config) : "";
|
|
432
|
+
return `${graphPart}:${configPart}`;
|
|
433
|
+
}
|
|
434
|
+
function animationsSignature(animations) {
|
|
435
|
+
if (!Array.isArray(animations) || animations.length === 0) {
|
|
436
|
+
return "";
|
|
437
|
+
}
|
|
438
|
+
return animations.map((animation) => {
|
|
439
|
+
const id = animation.id ?? "";
|
|
440
|
+
const clipSignature = normalizeSpecPayload(animation.clip ?? null);
|
|
441
|
+
const setupSignature = normalizeSpecPayload(animation.setup ?? null);
|
|
442
|
+
const weightSignature = animation.weight == null ? "" : String(animation.weight);
|
|
443
|
+
return `${id}:${clipSignature}:${setupSignature}:${weightSignature}`;
|
|
444
|
+
}).sort().join("|");
|
|
445
|
+
}
|
|
446
|
+
function programsSignature(programs) {
|
|
447
|
+
if (!Array.isArray(programs) || programs.length === 0) {
|
|
448
|
+
return "";
|
|
449
|
+
}
|
|
450
|
+
return programs.map((program) => {
|
|
451
|
+
const id = program.id ?? "";
|
|
452
|
+
const label = program.label ?? "";
|
|
453
|
+
const graphId = program.graph?.id ?? "";
|
|
454
|
+
const graphPayload = normalizeSpecPayload(
|
|
455
|
+
program.graph?.spec ?? program.graph?.ir ?? null
|
|
456
|
+
);
|
|
457
|
+
const resetValues = normalizeSpecPayload(program.resetValues ?? null);
|
|
458
|
+
return `${id}:${label}:${graphId}:${graphPayload}:${resetValues}`;
|
|
459
|
+
}).sort().join("|");
|
|
460
|
+
}
|
|
461
|
+
function resolveRuntimeUpdatePlan(previous, next, tier) {
|
|
462
|
+
if (!previous) {
|
|
463
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
464
|
+
}
|
|
465
|
+
const glbChanged = glbSignature(previous.glb) !== glbSignature(next.glb);
|
|
466
|
+
const rigChanged = graphSignature(previous.rig) !== graphSignature(next.rig);
|
|
467
|
+
const poseChanged = poseSignature(previous.pose) !== poseSignature(next.pose);
|
|
468
|
+
const rigReferenceChanged = previous.rig?.id !== next.rig?.id || previous.rig?.spec !== next.rig?.spec || previous.rig?.ir !== next.rig?.ir;
|
|
469
|
+
const poseReferenceChanged = previous.pose?.graph?.id !== next.pose?.graph?.id || previous.pose?.graph?.spec !== next.pose?.graph?.spec || previous.pose?.config !== next.pose?.config;
|
|
470
|
+
const graphsChanged = rigChanged || poseChanged || rigReferenceChanged || poseReferenceChanged;
|
|
471
|
+
const animationsChanged = animationsSignature(previous.animations) !== animationsSignature(next.animations);
|
|
472
|
+
const programsChanged = programsSignature(previous.programs) !== programsSignature(next.programs);
|
|
473
|
+
const controllersChanged = graphsChanged || animationsChanged || programsChanged;
|
|
474
|
+
if (tier === "assets") {
|
|
475
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
476
|
+
}
|
|
477
|
+
if (tier === "graphs") {
|
|
478
|
+
if (glbChanged) {
|
|
479
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
480
|
+
}
|
|
481
|
+
return { reloadAssets: false, reregisterGraphs: controllersChanged };
|
|
482
|
+
}
|
|
483
|
+
if (glbChanged) {
|
|
484
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
485
|
+
}
|
|
486
|
+
if (controllersChanged) {
|
|
487
|
+
return { reloadAssets: false, reregisterGraphs: true };
|
|
488
|
+
}
|
|
489
|
+
return { reloadAssets: false, reregisterGraphs: false };
|
|
490
|
+
}
|
|
491
|
+
|
|
29
492
|
// src/utils/graph.ts
|
|
30
493
|
function getNodes(spec) {
|
|
31
494
|
if (!spec || typeof spec !== "object") {
|
|
@@ -70,6 +533,12 @@ function collectInputPaths(spec) {
|
|
|
70
533
|
}
|
|
71
534
|
function collectInputPathMap(spec) {
|
|
72
535
|
const map = {};
|
|
536
|
+
const addVariant = (key, path, force = false) => {
|
|
537
|
+
if (!key || !force && map[key]) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
map[key] = path;
|
|
541
|
+
};
|
|
73
542
|
const nodes = getNodes(spec);
|
|
74
543
|
nodes.forEach((node) => {
|
|
75
544
|
if (String(node.type ?? "").toLowerCase() !== "input") {
|
|
@@ -81,13 +550,468 @@ function collectInputPathMap(spec) {
|
|
|
81
550
|
}
|
|
82
551
|
const id = String(node.id ?? "");
|
|
83
552
|
const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
|
|
84
|
-
|
|
553
|
+
const trimmedPath = path.trim();
|
|
554
|
+
addVariant(key, trimmedPath);
|
|
555
|
+
if (key.startsWith("direct_")) {
|
|
556
|
+
addVariant(key.slice("direct_".length), trimmedPath, true);
|
|
557
|
+
}
|
|
558
|
+
if (key.startsWith("pose_control_")) {
|
|
559
|
+
addVariant(key.slice("pose_control_".length), trimmedPath);
|
|
560
|
+
}
|
|
85
561
|
});
|
|
86
562
|
return map;
|
|
87
563
|
}
|
|
88
564
|
|
|
565
|
+
// src/utils/posePaths.ts
|
|
566
|
+
var POSE_WEIGHT_INPUT_PATH_PREFIX = "/poses/";
|
|
567
|
+
var VISEME_POSE_KEYS = [
|
|
568
|
+
"a",
|
|
569
|
+
"at",
|
|
570
|
+
"b",
|
|
571
|
+
"e",
|
|
572
|
+
"e_2",
|
|
573
|
+
"f",
|
|
574
|
+
"i",
|
|
575
|
+
"k",
|
|
576
|
+
"m",
|
|
577
|
+
"o",
|
|
578
|
+
"o_2",
|
|
579
|
+
"p",
|
|
580
|
+
"r",
|
|
581
|
+
"s",
|
|
582
|
+
"t",
|
|
583
|
+
"t_2",
|
|
584
|
+
"u"
|
|
585
|
+
];
|
|
586
|
+
var EXPRESSIVE_EMOTION_POSE_KEYS = [
|
|
587
|
+
"concerned",
|
|
588
|
+
"happy",
|
|
589
|
+
"sad",
|
|
590
|
+
"sleepy",
|
|
591
|
+
"surprise"
|
|
592
|
+
];
|
|
593
|
+
var EMOTION_POSE_KEYS = [
|
|
594
|
+
"concerned",
|
|
595
|
+
"happy",
|
|
596
|
+
"neutral",
|
|
597
|
+
"sad",
|
|
598
|
+
"sleepy",
|
|
599
|
+
"surprise",
|
|
600
|
+
"angry"
|
|
601
|
+
];
|
|
602
|
+
var VISEME_GROUP_NEEDLES = ["viseme", "phoneme", "lip", "mouth"];
|
|
603
|
+
var EMOTION_GROUP_NEEDLES = ["emotion", "expression", "mood", "affect"];
|
|
604
|
+
var VISEME_POSE_KEY_SET = new Set(VISEME_POSE_KEYS);
|
|
605
|
+
var EMOTION_POSE_KEY_SET = new Set(EMOTION_POSE_KEYS);
|
|
606
|
+
var POSE_KEY_ALIASES = {
|
|
607
|
+
concern: "concerned",
|
|
608
|
+
surprised: "surprise"
|
|
609
|
+
};
|
|
610
|
+
function buildRigInputPath(faceId, path) {
|
|
611
|
+
let trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
612
|
+
if (!trimmed) {
|
|
613
|
+
return `rig/${faceId}`;
|
|
614
|
+
}
|
|
615
|
+
while (trimmed.startsWith("rig/")) {
|
|
616
|
+
const segments = trimmed.split("/");
|
|
617
|
+
if (segments.length >= 3) {
|
|
618
|
+
const existingFaceId = segments[1];
|
|
619
|
+
const remainder = segments.slice(2).join("/");
|
|
620
|
+
if (existingFaceId === faceId) {
|
|
621
|
+
return trimmed;
|
|
622
|
+
}
|
|
623
|
+
trimmed = remainder || "";
|
|
624
|
+
} else {
|
|
625
|
+
trimmed = segments.slice(1).join("/");
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const suffix = trimmed ? `/${trimmed}` : "";
|
|
629
|
+
return `rig/${faceId}${suffix}`;
|
|
630
|
+
}
|
|
631
|
+
function normalizePoseWeightPathSegment(value) {
|
|
632
|
+
const trimmed = value?.trim() ?? "";
|
|
633
|
+
if (!trimmed) {
|
|
634
|
+
return "pose";
|
|
635
|
+
}
|
|
636
|
+
const normalized = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
637
|
+
return normalized || "pose";
|
|
638
|
+
}
|
|
639
|
+
function buildPoseWeightInputPathSegment(poseId) {
|
|
640
|
+
return normalizePoseWeightPathSegment(poseId);
|
|
641
|
+
}
|
|
642
|
+
function buildPoseWeightRelativePath(poseId) {
|
|
643
|
+
return `${POSE_WEIGHT_INPUT_PATH_PREFIX}${buildPoseWeightInputPathSegment(
|
|
644
|
+
poseId
|
|
645
|
+
)}.weight`;
|
|
646
|
+
}
|
|
647
|
+
function buildPoseWeightPathMap(poses, faceId) {
|
|
648
|
+
const faceSegment = faceId?.trim() || "face";
|
|
649
|
+
const map = /* @__PURE__ */ new Map();
|
|
650
|
+
poses.forEach((pose) => {
|
|
651
|
+
map.set(
|
|
652
|
+
pose.id,
|
|
653
|
+
buildRigInputPath(faceSegment, buildPoseWeightRelativePath(pose.id))
|
|
654
|
+
);
|
|
655
|
+
});
|
|
656
|
+
return map;
|
|
657
|
+
}
|
|
658
|
+
function normalizePoseSemanticKey(value) {
|
|
659
|
+
const trimmed = value?.trim() ?? "";
|
|
660
|
+
if (!trimmed) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
664
|
+
if (!normalized) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
return POSE_KEY_ALIASES[normalized] ?? normalized;
|
|
668
|
+
}
|
|
669
|
+
function derivePoseSemanticKeyFromId(poseId) {
|
|
670
|
+
const trimmed = poseId?.trim() ?? "";
|
|
671
|
+
if (!trimmed) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
const stripped = trimmed.replace(/^pose_d_/i, "").replace(/^pose_/i, "").replace(/^d_/i, "").replace(/_d$/i, "");
|
|
675
|
+
return normalizePoseSemanticKey(stripped);
|
|
676
|
+
}
|
|
677
|
+
function getPoseSemanticKey(pose) {
|
|
678
|
+
return normalizePoseSemanticKey(pose.name) ?? derivePoseSemanticKeyFromId(pose.id) ?? null;
|
|
679
|
+
}
|
|
680
|
+
function normalizePoseGroupPath(value) {
|
|
681
|
+
const trimmed = value?.trim() ?? "";
|
|
682
|
+
if (!trimmed) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return trimmed.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
|
|
686
|
+
}
|
|
687
|
+
function sanitizePoseGroupId(value, fallback) {
|
|
688
|
+
const normalized = (value ?? "").trim().replace(/[^a-zA-Z0-9_/-]+/g, "_").replace(/^\/+|\/+$/g, "").replace(/\/+/g, "_");
|
|
689
|
+
if (!normalized) {
|
|
690
|
+
return fallback.replace(/\//g, "_");
|
|
691
|
+
}
|
|
692
|
+
return normalized;
|
|
693
|
+
}
|
|
694
|
+
function buildPoseGroupLookup(groups) {
|
|
695
|
+
const byId = /* @__PURE__ */ new Map();
|
|
696
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
697
|
+
const orderById = /* @__PURE__ */ new Map();
|
|
698
|
+
(groups ?? []).forEach((group, index) => {
|
|
699
|
+
const path = normalizePoseGroupPath(group.path ?? group.name ?? group.id);
|
|
700
|
+
if (!path) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const id = sanitizePoseGroupId(group.id, path);
|
|
704
|
+
const humanizedName = typeof group.name === "string" && group.name.trim().length > 0 ? group.name.trim() : path.split("/").filter(Boolean).pop()?.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") ?? path;
|
|
705
|
+
const normalized = {
|
|
706
|
+
...group,
|
|
707
|
+
id,
|
|
708
|
+
path,
|
|
709
|
+
name: humanizedName
|
|
710
|
+
};
|
|
711
|
+
byId.set(id, normalized);
|
|
712
|
+
if (!orderById.has(id)) {
|
|
713
|
+
orderById.set(id, index);
|
|
714
|
+
}
|
|
715
|
+
if (!byPath.has(path)) {
|
|
716
|
+
byPath.set(path, normalized);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
return { byId, byPath, orderById };
|
|
720
|
+
}
|
|
721
|
+
function orderPoseMembershipIds(groupIds, groups) {
|
|
722
|
+
const { orderById } = buildPoseGroupLookup(groups);
|
|
723
|
+
const unique = Array.from(
|
|
724
|
+
new Set(
|
|
725
|
+
Array.from(groupIds).map((groupId) => groupId.trim()).filter((groupId) => groupId.length > 0)
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
unique.sort((left, right) => {
|
|
729
|
+
const leftIndex = orderById.get(left);
|
|
730
|
+
const rightIndex = orderById.get(right);
|
|
731
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) {
|
|
732
|
+
return leftIndex - rightIndex;
|
|
733
|
+
}
|
|
734
|
+
if (leftIndex !== void 0) {
|
|
735
|
+
return -1;
|
|
736
|
+
}
|
|
737
|
+
if (rightIndex !== void 0) {
|
|
738
|
+
return 1;
|
|
739
|
+
}
|
|
740
|
+
const leftPath = normalizePoseGroupPath(left) ?? left;
|
|
741
|
+
const rightPath = normalizePoseGroupPath(right) ?? right;
|
|
742
|
+
const byPath = leftPath.localeCompare(rightPath);
|
|
743
|
+
if (byPath !== 0) {
|
|
744
|
+
return byPath;
|
|
745
|
+
}
|
|
746
|
+
return left.localeCompare(right);
|
|
747
|
+
});
|
|
748
|
+
return unique;
|
|
749
|
+
}
|
|
750
|
+
function valueHasNeedle(value, needles) {
|
|
751
|
+
const normalized = normalizePoseSemanticKey(value);
|
|
752
|
+
if (!normalized) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
return needles.some((needle) => normalized.includes(needle));
|
|
756
|
+
}
|
|
757
|
+
function resolvePoseMembership(pose, groups) {
|
|
758
|
+
const { byId, byPath } = buildPoseGroupLookup(groups);
|
|
759
|
+
const resolvedGroupIds = [];
|
|
760
|
+
const pathById = /* @__PURE__ */ new Map();
|
|
761
|
+
const addMembership = (groupId, path) => {
|
|
762
|
+
if (!groupId) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (!resolvedGroupIds.includes(groupId)) {
|
|
766
|
+
resolvedGroupIds.push(groupId);
|
|
767
|
+
}
|
|
768
|
+
if (!path) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const existingPath = pathById.get(groupId);
|
|
772
|
+
const normalizedGroupIdPath = normalizePoseGroupPath(groupId);
|
|
773
|
+
const normalizedExistingPath = normalizePoseGroupPath(existingPath) ?? existingPath ?? null;
|
|
774
|
+
const normalizedIncomingPath = normalizePoseGroupPath(path) ?? path ?? null;
|
|
775
|
+
const shouldPromotePath = normalizedExistingPath === null || normalizedGroupIdPath !== null && normalizedExistingPath === normalizedGroupIdPath && normalizedIncomingPath !== normalizedGroupIdPath;
|
|
776
|
+
if (shouldPromotePath) {
|
|
777
|
+
pathById.set(groupId, path);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const addByPath = (rawPath) => {
|
|
781
|
+
const normalizedPath = normalizePoseGroupPath(rawPath);
|
|
782
|
+
if (!normalizedPath) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const existing = byPath.get(normalizedPath);
|
|
786
|
+
if (existing) {
|
|
787
|
+
addMembership(existing.id, existing.path);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
addMembership(sanitizePoseGroupId(null, normalizedPath), normalizedPath);
|
|
791
|
+
};
|
|
792
|
+
const addById = (rawId) => {
|
|
793
|
+
const trimmed = rawId?.trim() ?? "";
|
|
794
|
+
if (!trimmed) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const normalizedPath = normalizePoseGroupPath(trimmed);
|
|
798
|
+
const normalizedId = sanitizePoseGroupId(trimmed, trimmed);
|
|
799
|
+
const matchedById = byId.get(trimmed) ?? byId.get(normalizedId);
|
|
800
|
+
if (matchedById) {
|
|
801
|
+
addMembership(matchedById.id, matchedById.path);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (normalizedPath) {
|
|
805
|
+
const matchedByPath = byPath.get(normalizedPath);
|
|
806
|
+
if (matchedByPath) {
|
|
807
|
+
addMembership(matchedByPath.id, matchedByPath.path);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
addMembership(
|
|
812
|
+
normalizedId,
|
|
813
|
+
normalizedPath && normalizedPath.length > 0 ? normalizedPath : null
|
|
814
|
+
);
|
|
815
|
+
};
|
|
816
|
+
pose.groupIds?.forEach((groupId) => addById(groupId));
|
|
817
|
+
addById(pose.groupId);
|
|
818
|
+
addByPath(pose.group);
|
|
819
|
+
const orderedGroupIds = orderPoseMembershipIds(resolvedGroupIds, groups);
|
|
820
|
+
const primaryGroupId = orderedGroupIds[0] ?? null;
|
|
821
|
+
const primaryGroupPath = primaryGroupId ? byId.get(primaryGroupId)?.path ?? pathById.get(primaryGroupId) ?? null : null;
|
|
822
|
+
const groupPathsById = {};
|
|
823
|
+
orderedGroupIds.forEach((groupId) => {
|
|
824
|
+
const path = byId.get(groupId)?.path ?? pathById.get(groupId) ?? null;
|
|
825
|
+
if (path) {
|
|
826
|
+
groupPathsById[groupId] = path;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
return {
|
|
830
|
+
groupIds: orderedGroupIds,
|
|
831
|
+
primaryGroupId,
|
|
832
|
+
primaryGroupPath,
|
|
833
|
+
groupPathsById
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function poseMatchesGroupKind(pose, groups, needles) {
|
|
837
|
+
const membership = resolvePoseMembership(pose, groups);
|
|
838
|
+
if (valueHasNeedle(pose.group, needles) || valueHasNeedle(pose.groupId, needles)) {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
if (pose.groupIds?.some((groupId) => valueHasNeedle(groupId, needles))) {
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
return membership.groupIds.some((groupId) => {
|
|
845
|
+
const path = membership.groupPathsById[groupId] ?? null;
|
|
846
|
+
const group = (groups ?? []).find((entry) => entry.id === groupId) ?? null;
|
|
847
|
+
return valueHasNeedle(groupId, needles) || valueHasNeedle(path, needles) || valueHasNeedle(group?.name, needles);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
function resolvePoseSemantics(pose, groups) {
|
|
851
|
+
const membership = resolvePoseMembership(pose, groups);
|
|
852
|
+
const key = getPoseSemanticKey(pose);
|
|
853
|
+
const looksLikeVisemeGroup = poseMatchesGroupKind(
|
|
854
|
+
pose,
|
|
855
|
+
groups,
|
|
856
|
+
VISEME_GROUP_NEEDLES
|
|
857
|
+
);
|
|
858
|
+
const looksLikeEmotionGroup = poseMatchesGroupKind(
|
|
859
|
+
pose,
|
|
860
|
+
groups,
|
|
861
|
+
EMOTION_GROUP_NEEDLES
|
|
862
|
+
);
|
|
863
|
+
let kind = "other";
|
|
864
|
+
if (looksLikeVisemeGroup || key && VISEME_POSE_KEY_SET.has(key)) {
|
|
865
|
+
kind = "viseme";
|
|
866
|
+
} else if (looksLikeEmotionGroup || key && EMOTION_POSE_KEY_SET.has(key)) {
|
|
867
|
+
kind = "emotion";
|
|
868
|
+
}
|
|
869
|
+
return { key, kind, membership };
|
|
870
|
+
}
|
|
871
|
+
function filterPosesBySemanticKind(poses, groups, kind) {
|
|
872
|
+
return poses.filter(
|
|
873
|
+
(pose) => resolvePoseSemantics(pose, groups).kind === kind
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
function buildSemanticPoseWeightPathMap(poses, groups, faceId, kind) {
|
|
877
|
+
const map = /* @__PURE__ */ new Map();
|
|
878
|
+
const pathMap = buildPoseWeightPathMap(poses, faceId);
|
|
879
|
+
poses.forEach((pose) => {
|
|
880
|
+
const semantics = resolvePoseSemantics(pose, groups);
|
|
881
|
+
const path = pathMap.get(pose.id);
|
|
882
|
+
if (semantics.kind !== kind || !semantics.key || !path || map.has(semantics.key)) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
map.set(semantics.key, path);
|
|
886
|
+
});
|
|
887
|
+
return map;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/utils/poseRuntime.ts
|
|
891
|
+
function shouldUseLegacyPoseWeightFallback(hasPoseGraph) {
|
|
892
|
+
return !hasPoseGraph;
|
|
893
|
+
}
|
|
894
|
+
function resolvePoseControlInputPath({
|
|
895
|
+
inputId,
|
|
896
|
+
basePath,
|
|
897
|
+
rigInputPathMap,
|
|
898
|
+
hasNativePoseControlInput
|
|
899
|
+
}) {
|
|
900
|
+
if (!inputId.trim()) {
|
|
901
|
+
return void 0;
|
|
902
|
+
}
|
|
903
|
+
return rigInputPathMap[inputId] ?? rigInputPathMap[`pose_control_${inputId}`] ?? rigInputPathMap[`direct_${inputId}`] ?? (hasNativePoseControlInput ? basePath : void 0);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// src/utils/clipPlayback.ts
|
|
907
|
+
function toFiniteNumber(value, fallback) {
|
|
908
|
+
const parsed = Number(value);
|
|
909
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
910
|
+
}
|
|
911
|
+
function lastKeyframeTime(track) {
|
|
912
|
+
const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
|
|
913
|
+
let maxTime = 0;
|
|
914
|
+
keyframes.forEach((keyframe) => {
|
|
915
|
+
const time = Number(keyframe?.time);
|
|
916
|
+
if (Number.isFinite(time) && time > maxTime) {
|
|
917
|
+
maxTime = time;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
return maxTime;
|
|
921
|
+
}
|
|
922
|
+
function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
923
|
+
const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
|
|
924
|
+
if (!clip || typeof clip !== "object") {
|
|
925
|
+
return fallback;
|
|
926
|
+
}
|
|
927
|
+
const clipDuration = Number(clip.duration);
|
|
928
|
+
if (Number.isFinite(clipDuration) && clipDuration > 0) {
|
|
929
|
+
return clipDuration;
|
|
930
|
+
}
|
|
931
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
932
|
+
let maxTime = 0;
|
|
933
|
+
tracks.forEach((track) => {
|
|
934
|
+
const time = lastKeyframeTime(track);
|
|
935
|
+
if (time > maxTime) {
|
|
936
|
+
maxTime = time;
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
return maxTime > 0 ? maxTime : fallback;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/utils/animationBridge.ts
|
|
943
|
+
function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
|
|
944
|
+
const normalizedChannel = channel.trim().replace(/^\/+/, "");
|
|
945
|
+
if (!normalizedChannel) {
|
|
946
|
+
return [];
|
|
947
|
+
}
|
|
948
|
+
const outputPaths = /* @__PURE__ */ new Set([normalizedChannel]);
|
|
949
|
+
if (normalizedChannel.startsWith("animation/")) {
|
|
950
|
+
return Array.from(outputPaths).sort(
|
|
951
|
+
(left, right) => left.localeCompare(right)
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
if (rigInputMap && Object.keys(rigInputMap).length > 0) {
|
|
955
|
+
const candidateKeys = /* @__PURE__ */ new Set([normalizedChannel]);
|
|
956
|
+
const rigChannelMatch2 = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
|
|
957
|
+
if (rigChannelMatch2?.[1]) {
|
|
958
|
+
candidateKeys.add(rigChannelMatch2[1]);
|
|
959
|
+
}
|
|
960
|
+
candidateKeys.forEach((key) => {
|
|
961
|
+
const mapped = rigInputMap[key];
|
|
962
|
+
const normalized = mapped?.trim().replace(/^\/+/, "");
|
|
963
|
+
if (normalized) {
|
|
964
|
+
outputPaths.add(normalized);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
const suffix = normalizedChannel.includes("/") ? normalizedChannel : `/${normalizedChannel}`;
|
|
968
|
+
Object.values(rigInputMap).forEach((mappedPath) => {
|
|
969
|
+
const normalized = mappedPath?.trim().replace(/^\/+/, "");
|
|
970
|
+
if (!normalized) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (normalized === normalizedChannel || normalized.endsWith(suffix)) {
|
|
974
|
+
outputPaths.add(normalized);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
if (!faceId) {
|
|
979
|
+
return Array.from(outputPaths).sort(
|
|
980
|
+
(left, right) => left.localeCompare(right)
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
const rigChannelMatch = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
|
|
984
|
+
if (rigChannelMatch?.[1]) {
|
|
985
|
+
outputPaths.add(`rig/${faceId}/${rigChannelMatch[1]}`);
|
|
986
|
+
} else if (!normalizedChannel.startsWith("rig/")) {
|
|
987
|
+
outputPaths.add(`rig/${faceId}/${normalizedChannel}`);
|
|
988
|
+
}
|
|
989
|
+
return Array.from(outputPaths).sort(
|
|
990
|
+
(left, right) => left.localeCompare(right)
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
|
|
994
|
+
const outputPaths = /* @__PURE__ */ new Set();
|
|
995
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
996
|
+
tracks.forEach((track) => {
|
|
997
|
+
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
998
|
+
if (!channel) {
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
1002
|
+
(path) => {
|
|
1003
|
+
outputPaths.add(path);
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
});
|
|
1007
|
+
return Array.from(outputPaths).sort(
|
|
1008
|
+
(left, right) => left.localeCompare(right)
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
89
1012
|
// src/utils/valueConversion.ts
|
|
90
1013
|
import {
|
|
1014
|
+
fromAroraValueJSON,
|
|
91
1015
|
isNormalizedValue,
|
|
92
1016
|
valueAsBool,
|
|
93
1017
|
valueAsColorRgba,
|
|
@@ -140,27 +1064,28 @@ function valueJSONToRaw(value) {
|
|
|
140
1064
|
if (Array.isArray(value)) {
|
|
141
1065
|
return value.map((entry) => valueJSONToRaw(entry));
|
|
142
1066
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1067
|
+
const normalized = isNormalizedValue(value) ? value : fromAroraValueJSON(value);
|
|
1068
|
+
if (normalized === void 0) {
|
|
1069
|
+
if (typeof value === "object" && !("type" in value)) {
|
|
1070
|
+
const entries = Object.entries(value).map(([key, entry]) => [
|
|
1071
|
+
key,
|
|
1072
|
+
valueJSONToRaw(entry)
|
|
1073
|
+
]);
|
|
1074
|
+
return Object.fromEntries(entries);
|
|
1075
|
+
}
|
|
151
1076
|
return void 0;
|
|
152
1077
|
}
|
|
153
|
-
switch (
|
|
1078
|
+
switch (normalized.type) {
|
|
154
1079
|
case "float": {
|
|
155
|
-
const num = valueAsNumber(
|
|
1080
|
+
const num = valueAsNumber(normalized);
|
|
156
1081
|
return typeof num === "number" ? num : void 0;
|
|
157
1082
|
}
|
|
158
1083
|
case "bool": {
|
|
159
|
-
const boolVal = valueAsBool(
|
|
1084
|
+
const boolVal = valueAsBool(normalized);
|
|
160
1085
|
return typeof boolVal === "boolean" ? boolVal : void 0;
|
|
161
1086
|
}
|
|
162
1087
|
case "text": {
|
|
163
|
-
const text = valueAsText(
|
|
1088
|
+
const text = valueAsText(normalized);
|
|
164
1089
|
return typeof text === "string" ? text : void 0;
|
|
165
1090
|
}
|
|
166
1091
|
case "vec2":
|
|
@@ -168,11 +1093,11 @@ function valueJSONToRaw(value) {
|
|
|
168
1093
|
case "vec4":
|
|
169
1094
|
case "quat":
|
|
170
1095
|
case "vector": {
|
|
171
|
-
const vec = valueAsVector(
|
|
1096
|
+
const vec = valueAsVector(normalized);
|
|
172
1097
|
return vec ? numericArrayToRaw(vec) : void 0;
|
|
173
1098
|
}
|
|
174
1099
|
case "colorrgba": {
|
|
175
|
-
const color = valueAsColorRgba(
|
|
1100
|
+
const color = valueAsColorRgba(normalized);
|
|
176
1101
|
if (!color) {
|
|
177
1102
|
return void 0;
|
|
178
1103
|
}
|
|
@@ -180,7 +1105,7 @@ function valueJSONToRaw(value) {
|
|
|
180
1105
|
return { r, g, b, a };
|
|
181
1106
|
}
|
|
182
1107
|
case "transform": {
|
|
183
|
-
const transform = valueAsTransform(
|
|
1108
|
+
const transform = valueAsTransform(normalized);
|
|
184
1109
|
if (!transform) {
|
|
185
1110
|
return void 0;
|
|
186
1111
|
}
|
|
@@ -190,15 +1115,20 @@ function valueJSONToRaw(value) {
|
|
|
190
1115
|
scale: numericArrayToRaw(transform.scale)
|
|
191
1116
|
};
|
|
192
1117
|
}
|
|
1118
|
+
case "array":
|
|
1119
|
+
case "list":
|
|
1120
|
+
case "tuple":
|
|
1121
|
+
return (normalized.data ?? []).map(
|
|
1122
|
+
(entry) => valueJSONToRaw(entry)
|
|
1123
|
+
);
|
|
193
1124
|
case "record": {
|
|
194
|
-
const entries = Object.entries(
|
|
195
|
-
key,
|
|
196
|
-
|
|
197
|
-
]);
|
|
1125
|
+
const entries = Object.entries(normalized.data ?? {}).map(
|
|
1126
|
+
([key, entry]) => [key, valueJSONToRaw(entry)]
|
|
1127
|
+
);
|
|
198
1128
|
return Object.fromEntries(entries);
|
|
199
1129
|
}
|
|
200
1130
|
case "enum": {
|
|
201
|
-
const [tag, inner] =
|
|
1131
|
+
const [tag, inner] = normalized.data;
|
|
202
1132
|
return {
|
|
203
1133
|
tag,
|
|
204
1134
|
value: valueJSONToRaw(inner)
|
|
@@ -209,16 +1139,620 @@ function valueJSONToRaw(value) {
|
|
|
209
1139
|
}
|
|
210
1140
|
}
|
|
211
1141
|
|
|
1142
|
+
// src/engine/animationModule.ts
|
|
1143
|
+
var ANIMATION_MODULE_FN = {
|
|
1144
|
+
loadAnimation: "76697a69-6a00-0000-0f00-000000000001",
|
|
1145
|
+
createPlayer: "76697a69-6a00-0000-0f00-000000000002",
|
|
1146
|
+
addInstance: "76697a69-6a00-0000-0f00-000000000003",
|
|
1147
|
+
step: "76697a69-6a00-0000-0f00-000000000004",
|
|
1148
|
+
play: "76697a69-6a00-0000-0f00-000000000005",
|
|
1149
|
+
pause: "76697a69-6a00-0000-0f00-000000000006",
|
|
1150
|
+
stop: "76697a69-6a00-0000-0f00-000000000007",
|
|
1151
|
+
seek: "76697a69-6a00-0000-0f00-000000000008",
|
|
1152
|
+
setSpeed: "76697a69-6a00-0000-0f00-000000000009",
|
|
1153
|
+
setLoop: "76697a69-6a00-0000-0f00-00000000000a",
|
|
1154
|
+
setWeight: "76697a69-6a00-0000-0f00-00000000000b",
|
|
1155
|
+
removeInstance: "76697a69-6a00-0000-0f00-00000000000c",
|
|
1156
|
+
playerStates: "76697a69-6a00-0000-0f00-00000000000d"
|
|
1157
|
+
};
|
|
1158
|
+
var ANIMATION_MODULE_PARAM = {
|
|
1159
|
+
clip: "76697a69-6a00-0000-0f01-000000000001",
|
|
1160
|
+
playerName: "76697a69-6a00-0000-0f02-000000000001",
|
|
1161
|
+
player: "76697a69-6a00-0000-0f03-000000000001",
|
|
1162
|
+
anim: "76697a69-6a00-0000-0f03-000000000002",
|
|
1163
|
+
dtNs: "76697a69-6a00-0000-0f04-000000000001",
|
|
1164
|
+
playPlayer: "76697a69-6a00-0000-0f05-000000000001",
|
|
1165
|
+
pausePlayer: "76697a69-6a00-0000-0f06-000000000001",
|
|
1166
|
+
stopPlayer: "76697a69-6a00-0000-0f07-000000000001",
|
|
1167
|
+
seekPlayer: "76697a69-6a00-0000-0f08-000000000001",
|
|
1168
|
+
seekTimeNs: "76697a69-6a00-0000-0f08-000000000002",
|
|
1169
|
+
speedPlayer: "76697a69-6a00-0000-0f09-000000000001",
|
|
1170
|
+
speedValue: "76697a69-6a00-0000-0f09-000000000002",
|
|
1171
|
+
loopPlayer: "76697a69-6a00-0000-0f0a-000000000001",
|
|
1172
|
+
loopMode: "76697a69-6a00-0000-0f0a-000000000002",
|
|
1173
|
+
weightPlayer: "76697a69-6a00-0000-0f0b-000000000001",
|
|
1174
|
+
weightInstance: "76697a69-6a00-0000-0f0b-000000000002",
|
|
1175
|
+
weightValue: "76697a69-6a00-0000-0f0b-000000000003",
|
|
1176
|
+
removePlayer: "76697a69-6a00-0000-0f0c-000000000001",
|
|
1177
|
+
removeInstance: "76697a69-6a00-0000-0f0c-000000000002"
|
|
1178
|
+
};
|
|
1179
|
+
var ANIMATION_MODULE_TYPE = {
|
|
1180
|
+
clip: "76697a69-6a00-0000-0000-000000000100",
|
|
1181
|
+
track: "76697a69-6a00-0000-0000-000000000101",
|
|
1182
|
+
keypoint: "76697a69-6a00-0000-0000-000000000102",
|
|
1183
|
+
transitionHandle: "76697a69-6a00-0000-0000-000000000103",
|
|
1184
|
+
trackOutput: "76697a69-6a00-0000-0000-000000000110",
|
|
1185
|
+
playerState: "76697a69-6a00-0000-0000-000000000111"
|
|
1186
|
+
};
|
|
1187
|
+
var ANIMATION_MODULE_FIELD = {
|
|
1188
|
+
clipName: "76697a69-6a00-0000-0100-000000000001",
|
|
1189
|
+
clipDuration: "76697a69-6a00-0000-0100-000000000002",
|
|
1190
|
+
clipTracks: "76697a69-6a00-0000-0100-000000000003",
|
|
1191
|
+
trackId: "76697a69-6a00-0000-0101-000000000001",
|
|
1192
|
+
trackName: "76697a69-6a00-0000-0101-000000000002",
|
|
1193
|
+
trackAnimatable: "76697a69-6a00-0000-0101-000000000003",
|
|
1194
|
+
trackPoints: "76697a69-6a00-0000-0101-000000000004",
|
|
1195
|
+
keypointId: "76697a69-6a00-0000-0102-000000000001",
|
|
1196
|
+
keypointStamp: "76697a69-6a00-0000-0102-000000000002",
|
|
1197
|
+
keypointValue: "76697a69-6a00-0000-0102-000000000003",
|
|
1198
|
+
keypointTransitionsIn: "76697a69-6a00-0000-0102-000000000004",
|
|
1199
|
+
keypointTransitionsOut: "76697a69-6a00-0000-0102-000000000005",
|
|
1200
|
+
handleX: "76697a69-6a00-0000-0103-000000000001",
|
|
1201
|
+
handleY: "76697a69-6a00-0000-0103-000000000002",
|
|
1202
|
+
outputTrackId: "76697a69-6a00-0000-0110-000000000001",
|
|
1203
|
+
outputDefaultKey: "76697a69-6a00-0000-0110-000000000002",
|
|
1204
|
+
outputValue: "76697a69-6a00-0000-0110-000000000003",
|
|
1205
|
+
statePlayer: "76697a69-6a00-0000-0111-000000000001",
|
|
1206
|
+
stateState: "76697a69-6a00-0000-0111-000000000002",
|
|
1207
|
+
stateTimeNs: "76697a69-6a00-0000-0111-000000000003",
|
|
1208
|
+
stateDurationNs: "76697a69-6a00-0000-0111-000000000004",
|
|
1209
|
+
stateSpeed: "76697a69-6a00-0000-0111-000000000005"
|
|
1210
|
+
};
|
|
1211
|
+
var field = (id, value) => ({
|
|
1212
|
+
id,
|
|
1213
|
+
value
|
|
1214
|
+
});
|
|
1215
|
+
function storedClipToModuleValue(clip, resolveKeys) {
|
|
1216
|
+
const name = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : typeof clip.id === "string" ? clip.id : "";
|
|
1217
|
+
const durationMs = Number(clip.duration);
|
|
1218
|
+
const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs) : 1;
|
|
1219
|
+
const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, index) => {
|
|
1220
|
+
if (!rawTrack || typeof rawTrack !== "object") {
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
const animatableId = typeof rawTrack.animatableId === "string" ? rawTrack.animatableId.trim() : "";
|
|
1224
|
+
if (!animatableId) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
const trackId = typeof rawTrack.id === "string" && rawTrack.id.trim().length > 0 ? rawTrack.id.trim() : `track-${index}`;
|
|
1228
|
+
const trackName = typeof rawTrack.name === "string" && rawTrack.name.trim().length > 0 ? rawTrack.name.trim() : trackId;
|
|
1229
|
+
const points = (Array.isArray(rawTrack.points) ? rawTrack.points : []).map((rawPoint, pointIndex) => {
|
|
1230
|
+
if (!rawPoint || typeof rawPoint !== "object") {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
const stamp = Number(rawPoint.stamp);
|
|
1234
|
+
const value = Number(rawPoint.value);
|
|
1235
|
+
if (!Number.isFinite(stamp) || !Number.isFinite(value)) {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
const pointId = typeof rawPoint.id === "string" && rawPoint.id.trim().length > 0 ? rawPoint.id.trim() : `${trackId}:point-${pointIndex}`;
|
|
1239
|
+
const handles = (handle) => ({
|
|
1240
|
+
structs: {
|
|
1241
|
+
id: ANIMATION_MODULE_TYPE.transitionHandle,
|
|
1242
|
+
elements: handle ? [
|
|
1243
|
+
{
|
|
1244
|
+
fields: [
|
|
1245
|
+
field(ANIMATION_MODULE_FIELD.handleX, {
|
|
1246
|
+
f32: handle.x
|
|
1247
|
+
}),
|
|
1248
|
+
field(ANIMATION_MODULE_FIELD.handleY, {
|
|
1249
|
+
f32: handle.y
|
|
1250
|
+
})
|
|
1251
|
+
]
|
|
1252
|
+
}
|
|
1253
|
+
] : []
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
return {
|
|
1257
|
+
fields: [
|
|
1258
|
+
field(ANIMATION_MODULE_FIELD.keypointId, { str: pointId }),
|
|
1259
|
+
field(ANIMATION_MODULE_FIELD.keypointStamp, {
|
|
1260
|
+
f32: Math.max(0, Math.min(1, stamp))
|
|
1261
|
+
}),
|
|
1262
|
+
field(ANIMATION_MODULE_FIELD.keypointValue, { f32: value }),
|
|
1263
|
+
field(
|
|
1264
|
+
ANIMATION_MODULE_FIELD.keypointTransitionsIn,
|
|
1265
|
+
handles(rawPoint.transitions?.in)
|
|
1266
|
+
),
|
|
1267
|
+
field(
|
|
1268
|
+
ANIMATION_MODULE_FIELD.keypointTransitionsOut,
|
|
1269
|
+
handles(rawPoint.transitions?.out)
|
|
1270
|
+
)
|
|
1271
|
+
]
|
|
1272
|
+
};
|
|
1273
|
+
}).filter(Boolean);
|
|
1274
|
+
if (points.length === 0) {
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
const targets = resolveKeys?.(animatableId) ?? [animatableId];
|
|
1278
|
+
return targets.map((target, targetIndex) => ({
|
|
1279
|
+
fields: [
|
|
1280
|
+
field(ANIMATION_MODULE_FIELD.trackId, {
|
|
1281
|
+
str: targetIndex === 0 ? trackId : `${trackId}~${targetIndex}`
|
|
1282
|
+
}),
|
|
1283
|
+
field(ANIMATION_MODULE_FIELD.trackName, { str: trackName }),
|
|
1284
|
+
field(ANIMATION_MODULE_FIELD.trackAnimatable, { str: target }),
|
|
1285
|
+
field(ANIMATION_MODULE_FIELD.trackPoints, {
|
|
1286
|
+
structs: {
|
|
1287
|
+
id: ANIMATION_MODULE_TYPE.keypoint,
|
|
1288
|
+
elements: points
|
|
1289
|
+
}
|
|
1290
|
+
})
|
|
1291
|
+
]
|
|
1292
|
+
}));
|
|
1293
|
+
}).filter(Boolean).flat();
|
|
1294
|
+
return {
|
|
1295
|
+
struct: {
|
|
1296
|
+
id: ANIMATION_MODULE_TYPE.clip,
|
|
1297
|
+
fields: [
|
|
1298
|
+
field(ANIMATION_MODULE_FIELD.clipName, { str: name }),
|
|
1299
|
+
field(ANIMATION_MODULE_FIELD.clipDuration, { u32: duration }),
|
|
1300
|
+
field(ANIMATION_MODULE_FIELD.clipTracks, {
|
|
1301
|
+
structs: {
|
|
1302
|
+
id: ANIMATION_MODULE_TYPE.track,
|
|
1303
|
+
elements: tracks
|
|
1304
|
+
}
|
|
1305
|
+
})
|
|
1306
|
+
]
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function loadAnimationCall(clipValue) {
|
|
1311
|
+
return {
|
|
1312
|
+
id: ANIMATION_MODULE_FN.loadAnimation,
|
|
1313
|
+
args: [field(ANIMATION_MODULE_PARAM.clip, clipValue)]
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function createPlayerCall(name) {
|
|
1317
|
+
return {
|
|
1318
|
+
id: ANIMATION_MODULE_FN.createPlayer,
|
|
1319
|
+
args: [field(ANIMATION_MODULE_PARAM.playerName, { str: name })]
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function addInstanceCall(player, anim) {
|
|
1323
|
+
return {
|
|
1324
|
+
id: ANIMATION_MODULE_FN.addInstance,
|
|
1325
|
+
args: [
|
|
1326
|
+
field(ANIMATION_MODULE_PARAM.player, { u32: player }),
|
|
1327
|
+
field(ANIMATION_MODULE_PARAM.anim, { u32: anim })
|
|
1328
|
+
]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function playCall(player) {
|
|
1332
|
+
return {
|
|
1333
|
+
id: ANIMATION_MODULE_FN.play,
|
|
1334
|
+
args: [field(ANIMATION_MODULE_PARAM.playPlayer, { u32: player })]
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function pauseCall(player) {
|
|
1338
|
+
return {
|
|
1339
|
+
id: ANIMATION_MODULE_FN.pause,
|
|
1340
|
+
args: [field(ANIMATION_MODULE_PARAM.pausePlayer, { u32: player })]
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function stopCall(player) {
|
|
1344
|
+
return {
|
|
1345
|
+
id: ANIMATION_MODULE_FN.stop,
|
|
1346
|
+
args: [field(ANIMATION_MODULE_PARAM.stopPlayer, { u32: player })]
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function seekCall(player, timeNs) {
|
|
1350
|
+
return {
|
|
1351
|
+
id: ANIMATION_MODULE_FN.seek,
|
|
1352
|
+
args: [
|
|
1353
|
+
field(ANIMATION_MODULE_PARAM.seekPlayer, { u32: player }),
|
|
1354
|
+
field(ANIMATION_MODULE_PARAM.seekTimeNs, {
|
|
1355
|
+
u64: Math.max(0, Math.round(timeNs))
|
|
1356
|
+
})
|
|
1357
|
+
]
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function setSpeedCall(player, speed) {
|
|
1361
|
+
return {
|
|
1362
|
+
id: ANIMATION_MODULE_FN.setSpeed,
|
|
1363
|
+
args: [
|
|
1364
|
+
field(ANIMATION_MODULE_PARAM.speedPlayer, { u32: player }),
|
|
1365
|
+
field(ANIMATION_MODULE_PARAM.speedValue, { f32: speed })
|
|
1366
|
+
]
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function setLoopCall(player, mode) {
|
|
1370
|
+
return {
|
|
1371
|
+
id: ANIMATION_MODULE_FN.setLoop,
|
|
1372
|
+
args: [
|
|
1373
|
+
field(ANIMATION_MODULE_PARAM.loopPlayer, { u32: player }),
|
|
1374
|
+
field(ANIMATION_MODULE_PARAM.loopMode, { str: mode })
|
|
1375
|
+
]
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function setWeightCall(player, instance, weight) {
|
|
1379
|
+
return {
|
|
1380
|
+
id: ANIMATION_MODULE_FN.setWeight,
|
|
1381
|
+
args: [
|
|
1382
|
+
field(ANIMATION_MODULE_PARAM.weightPlayer, { u32: player }),
|
|
1383
|
+
field(ANIMATION_MODULE_PARAM.weightInstance, { u32: instance }),
|
|
1384
|
+
field(ANIMATION_MODULE_PARAM.weightValue, { f32: weight })
|
|
1385
|
+
]
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function callResultU32(result) {
|
|
1389
|
+
const ret = result.ret;
|
|
1390
|
+
if (ret && typeof ret === "object" && "u32" in ret) {
|
|
1391
|
+
const value = Number(ret.u32);
|
|
1392
|
+
return Number.isFinite(value) ? value : null;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
var ANIMATION_PLAYERS_PATH = "vizij/animations/players";
|
|
1397
|
+
var ANIMATIONS_SOURCE_ID = "animations";
|
|
1398
|
+
function animationsGraphSource() {
|
|
1399
|
+
return {
|
|
1400
|
+
sourceId: ANIMATIONS_SOURCE_ID,
|
|
1401
|
+
spec: {
|
|
1402
|
+
nodes: [
|
|
1403
|
+
{ id: "dt", type: "input", params: { path: "arora/dt" } },
|
|
1404
|
+
{
|
|
1405
|
+
id: "step",
|
|
1406
|
+
type: "externalfunction",
|
|
1407
|
+
params: {
|
|
1408
|
+
function: ANIMATION_MODULE_FN.step,
|
|
1409
|
+
param_ids: [ANIMATION_MODULE_PARAM.dtNs]
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
id: "apply",
|
|
1414
|
+
type: "output",
|
|
1415
|
+
params: {
|
|
1416
|
+
key_field: ANIMATION_MODULE_FIELD.outputDefaultKey,
|
|
1417
|
+
value_field: ANIMATION_MODULE_FIELD.outputValue
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
id: "states",
|
|
1422
|
+
type: "externalfunction",
|
|
1423
|
+
params: { function: ANIMATION_MODULE_FN.playerStates, param_ids: [] }
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
id: "states-out",
|
|
1427
|
+
type: "output",
|
|
1428
|
+
params: { path: ANIMATION_PLAYERS_PATH }
|
|
1429
|
+
}
|
|
1430
|
+
],
|
|
1431
|
+
edges: [
|
|
1432
|
+
{ from: { node_id: "dt" }, to: { node_id: "step", input: "args_0" } },
|
|
1433
|
+
{ from: { node_id: "step" }, to: { node_id: "apply", input: "in" } },
|
|
1434
|
+
{
|
|
1435
|
+
from: { node_id: "states" },
|
|
1436
|
+
to: { node_id: "states-out", input: "in" }
|
|
1437
|
+
}
|
|
1438
|
+
]
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function fieldNumber(value) {
|
|
1443
|
+
if (!value || typeof value !== "object") {
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
for (const key of ["u64", "u32", "f32", "f64"]) {
|
|
1447
|
+
if (key in value) {
|
|
1448
|
+
const parsed = Number(value[key]);
|
|
1449
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
function decodePlayerStates(raw) {
|
|
1455
|
+
if (!raw || typeof raw !== "object" || !("structs" in raw)) {
|
|
1456
|
+
return [];
|
|
1457
|
+
}
|
|
1458
|
+
const structs = raw.structs;
|
|
1459
|
+
const elements = Array.isArray(structs?.elements) ? structs.elements : [];
|
|
1460
|
+
const states = [];
|
|
1461
|
+
for (const element of elements) {
|
|
1462
|
+
const fields = element?.fields;
|
|
1463
|
+
if (!Array.isArray(fields)) {
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1467
|
+
for (const entry of fields) {
|
|
1468
|
+
const id = entry?.id;
|
|
1469
|
+
const value = entry?.value;
|
|
1470
|
+
if (typeof id === "string" && value && typeof value === "object") {
|
|
1471
|
+
byId.set(id, value);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
|
|
1475
|
+
if (player === null) {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
|
|
1479
|
+
states.push({
|
|
1480
|
+
player,
|
|
1481
|
+
state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
|
|
1482
|
+
time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
|
|
1483
|
+
duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
|
|
1484
|
+
speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
return states;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/engine/animationModuleHost.ts
|
|
1491
|
+
var AnimationModuleHost = class {
|
|
1492
|
+
constructor(getDevice, resolveKeys = (key) => [key]) {
|
|
1493
|
+
this.getDevice = getDevice;
|
|
1494
|
+
this.resolveKeys = resolveKeys;
|
|
1495
|
+
this.clips = /* @__PURE__ */ new Map();
|
|
1496
|
+
}
|
|
1497
|
+
/** Whether any clip is currently marked playing (drives source lifecycle). */
|
|
1498
|
+
hasPlaying() {
|
|
1499
|
+
for (const entry of this.clips.values()) {
|
|
1500
|
+
if (entry.playing) {
|
|
1501
|
+
return true;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
/** Whether a clip id is known to the host. */
|
|
1507
|
+
has(clipId) {
|
|
1508
|
+
return this.clips.has(clipId);
|
|
1509
|
+
}
|
|
1510
|
+
/** The clip's module `PlayerId` on the live device (feedback correlation). */
|
|
1511
|
+
playerIdOf(clipId) {
|
|
1512
|
+
return this.clips.get(clipId)?.playerId ?? null;
|
|
1513
|
+
}
|
|
1514
|
+
/** The clip's transport state as last commanded. */
|
|
1515
|
+
transportOf(clipId) {
|
|
1516
|
+
const entry = this.clips.get(clipId);
|
|
1517
|
+
if (!entry) {
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
return {
|
|
1521
|
+
playing: entry.playing,
|
|
1522
|
+
speed: entry.speed,
|
|
1523
|
+
loop: entry.loop,
|
|
1524
|
+
weight: entry.weight
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Declare the clips the bundle carries, as data. Keeps playing/transport
|
|
1529
|
+
* state for clips that survive; clips no longer present are dropped. Does
|
|
1530
|
+
* not touch the device — loading is lazy (on first play), so muted/unused
|
|
1531
|
+
* clips never enter the module, and conversion happens at load with the
|
|
1532
|
+
* key resolver's state at that moment.
|
|
1533
|
+
*/
|
|
1534
|
+
setClips(clips) {
|
|
1535
|
+
const nextIds = new Set(clips.map((clip) => clip.id));
|
|
1536
|
+
for (const id of Array.from(this.clips.keys())) {
|
|
1537
|
+
if (!nextIds.has(id)) {
|
|
1538
|
+
this.clips.delete(id);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
for (const clip of clips) {
|
|
1542
|
+
const existing = this.clips.get(clip.id);
|
|
1543
|
+
if (existing) {
|
|
1544
|
+
existing.stored = clip.stored;
|
|
1545
|
+
existing.animId = null;
|
|
1546
|
+
existing.playerId = null;
|
|
1547
|
+
existing.instId = null;
|
|
1548
|
+
} else {
|
|
1549
|
+
this.clips.set(clip.id, {
|
|
1550
|
+
stored: clip.stored,
|
|
1551
|
+
playing: false,
|
|
1552
|
+
speed: 1,
|
|
1553
|
+
loop: "loop",
|
|
1554
|
+
weight: 1,
|
|
1555
|
+
animId: null,
|
|
1556
|
+
playerId: null,
|
|
1557
|
+
instId: null
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Mark a clip playing, ensure it is loaded into the live device with a
|
|
1564
|
+
* player + instance, and issue `play` (a resume after `pause` needs it; on
|
|
1565
|
+
* a fresh player it is a no-op). Idempotent. Resolves once the calls have
|
|
1566
|
+
* dispatched (or immediately when there is no live device yet — the
|
|
1567
|
+
* provider registers the source, which boots the device and replays via
|
|
1568
|
+
* `replayInto`).
|
|
1569
|
+
*/
|
|
1570
|
+
async play(clipId) {
|
|
1571
|
+
const entry = this.clips.get(clipId);
|
|
1572
|
+
if (!entry) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
entry.playing = true;
|
|
1576
|
+
const device = this.getDevice();
|
|
1577
|
+
if (!device) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
await this.ensureLoaded(device, entry);
|
|
1581
|
+
if (entry.playerId !== null) {
|
|
1582
|
+
await device.call(playCall(entry.playerId));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/** Hold the clip's playhead (`pause` on its player). */
|
|
1586
|
+
pause(clipId) {
|
|
1587
|
+
const entry = this.clips.get(clipId);
|
|
1588
|
+
if (!entry) {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
entry.playing = false;
|
|
1592
|
+
this.dispatch(
|
|
1593
|
+
(device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Stop a clip: not playing, playhead reset to the window start (`stop` on
|
|
1598
|
+
* its player — the next tick emits the clip's t=0 pose). The clip stays
|
|
1599
|
+
* loaded for a cheap replay.
|
|
1600
|
+
*/
|
|
1601
|
+
stop(clipId) {
|
|
1602
|
+
const entry = this.clips.get(clipId);
|
|
1603
|
+
if (!entry) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
entry.playing = false;
|
|
1607
|
+
this.dispatch(
|
|
1608
|
+
(device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
/** Move the clip's playhead to `seconds`. */
|
|
1612
|
+
seek(clipId, seconds) {
|
|
1613
|
+
const entry = this.clips.get(clipId);
|
|
1614
|
+
if (!entry) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
this.dispatch(
|
|
1618
|
+
(device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
/** Set the clip's playback speed multiplier. */
|
|
1622
|
+
setSpeed(clipId, speed) {
|
|
1623
|
+
const entry = this.clips.get(clipId);
|
|
1624
|
+
if (!entry) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
entry.speed = speed;
|
|
1628
|
+
this.dispatch(
|
|
1629
|
+
(device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
/** Set how the clip's player time maps into clip time. */
|
|
1633
|
+
setLoop(clipId, mode) {
|
|
1634
|
+
const entry = this.clips.get(clipId);
|
|
1635
|
+
if (!entry) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
entry.loop = mode;
|
|
1639
|
+
this.dispatch(
|
|
1640
|
+
(device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
/** Set the clip instance's blend weight. */
|
|
1644
|
+
setWeight(clipId, weight) {
|
|
1645
|
+
const entry = this.clips.get(clipId);
|
|
1646
|
+
if (!entry) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
entry.weight = weight;
|
|
1650
|
+
this.dispatch(
|
|
1651
|
+
(device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Re-issue setup and transport for every playing clip against a freshly
|
|
1656
|
+
* started device (module guest state did not survive the rebuild). Voids
|
|
1657
|
+
* stale ids first. Called from `DeviceSlot.onDeviceStarted`.
|
|
1658
|
+
*/
|
|
1659
|
+
replayInto(device) {
|
|
1660
|
+
for (const entry of this.clips.values()) {
|
|
1661
|
+
entry.animId = null;
|
|
1662
|
+
entry.playerId = null;
|
|
1663
|
+
entry.instId = null;
|
|
1664
|
+
if (entry.playing) {
|
|
1665
|
+
void this.ensureLoaded(device, entry);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
/** Fire-and-forget a transport call against the live device, if any. */
|
|
1670
|
+
dispatch(issue) {
|
|
1671
|
+
const device = this.getDevice();
|
|
1672
|
+
if (!device) {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
void issue(device)?.catch(() => {
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
async ensureLoaded(device, entry) {
|
|
1679
|
+
if (entry.instId !== null) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (entry.animId === null) {
|
|
1683
|
+
const anim = await device.call(
|
|
1684
|
+
loadAnimationCall(
|
|
1685
|
+
storedClipToModuleValue(entry.stored, this.resolveKeys)
|
|
1686
|
+
)
|
|
1687
|
+
);
|
|
1688
|
+
entry.animId = callResultU32(anim);
|
|
1689
|
+
}
|
|
1690
|
+
if (entry.playerId === null) {
|
|
1691
|
+
const player = await device.call(createPlayerCall(""));
|
|
1692
|
+
entry.playerId = callResultU32(player);
|
|
1693
|
+
}
|
|
1694
|
+
if (entry.instId === null && entry.animId !== null && entry.playerId !== null) {
|
|
1695
|
+
const inst = await device.call(
|
|
1696
|
+
addInstanceCall(entry.playerId, entry.animId)
|
|
1697
|
+
);
|
|
1698
|
+
entry.instId = callResultU32(inst);
|
|
1699
|
+
}
|
|
1700
|
+
const followups = [];
|
|
1701
|
+
if (entry.playerId !== null) {
|
|
1702
|
+
if (entry.speed !== 1) {
|
|
1703
|
+
followups.push(setSpeedCall(entry.playerId, entry.speed));
|
|
1704
|
+
}
|
|
1705
|
+
if (entry.loop !== "loop") {
|
|
1706
|
+
followups.push(setLoopCall(entry.playerId, entry.loop));
|
|
1707
|
+
}
|
|
1708
|
+
if (entry.instId !== null && entry.weight !== 1) {
|
|
1709
|
+
followups.push(
|
|
1710
|
+
setWeightCall(entry.playerId, entry.instId, entry.weight)
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
for (const call of followups) {
|
|
1715
|
+
await device.call(call);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
212
1720
|
// src/VizijRuntimeProvider.tsx
|
|
213
1721
|
import { jsx } from "react/jsx-runtime";
|
|
214
1722
|
var ACTIVE_GRACE_MS = 250;
|
|
215
1723
|
var VISIBLE_IDLE_FPS = 30;
|
|
216
1724
|
var HIDDEN_IDLE_FPS = 1;
|
|
1725
|
+
var IMPLICIT_PAUSE_GAP_S = 5;
|
|
1726
|
+
var inferImplicitPause = (dt) => {
|
|
1727
|
+
if (dt <= IMPLICIT_PAUSE_GAP_S) {
|
|
1728
|
+
return dt;
|
|
1729
|
+
}
|
|
1730
|
+
console.warn(
|
|
1731
|
+
`[vizij-runtime] step loop woke after a ${dt.toFixed(
|
|
1732
|
+
1
|
|
1733
|
+
)}s wall-clock gap; treating it as a suspension (dt = 0)`
|
|
1734
|
+
);
|
|
1735
|
+
return 0;
|
|
1736
|
+
};
|
|
217
1737
|
var DEFAULT_MERGE = {
|
|
218
1738
|
outputs: "add",
|
|
219
1739
|
intermediate: "add"
|
|
220
1740
|
};
|
|
221
1741
|
var DEFAULT_DURATION = 0.35;
|
|
1742
|
+
var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
|
|
1743
|
+
var runtimeDebugInstanceSequence = 0;
|
|
1744
|
+
var DEV_MODE = (() => {
|
|
1745
|
+
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
1746
|
+
return typeof nodeEnv === "string" && nodeEnv === "development";
|
|
1747
|
+
})();
|
|
1748
|
+
function isRuntimeDebugEnabled() {
|
|
1749
|
+
if (DEV_MODE) {
|
|
1750
|
+
return true;
|
|
1751
|
+
}
|
|
1752
|
+
return Boolean(
|
|
1753
|
+
globalThis.__VIZIJ_RUNTIME_DEBUG__
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
222
1756
|
var EASINGS = {
|
|
223
1757
|
linear: (t) => t,
|
|
224
1758
|
easeIn: (t) => t * t,
|
|
@@ -235,12 +1769,19 @@ function resolveEasing(easing) {
|
|
|
235
1769
|
return EASINGS.linear;
|
|
236
1770
|
}
|
|
237
1771
|
function findRootId(world) {
|
|
1772
|
+
let fallback = null;
|
|
238
1773
|
for (const entry of Object.values(world)) {
|
|
239
|
-
if (entry
|
|
1774
|
+
if (!entry || typeof entry !== "object" || entry.type !== "group") {
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
if (entry.rootBounds && entry.id) {
|
|
240
1778
|
return entry.id;
|
|
241
1779
|
}
|
|
1780
|
+
if (!fallback && entry.id) {
|
|
1781
|
+
fallback = entry.id;
|
|
1782
|
+
}
|
|
242
1783
|
}
|
|
243
|
-
return
|
|
1784
|
+
return fallback;
|
|
244
1785
|
}
|
|
245
1786
|
function normalisePath(path) {
|
|
246
1787
|
if (!path) {
|
|
@@ -410,6 +1951,30 @@ function namespaceGraphSpec(spec, namespace) {
|
|
|
410
1951
|
nodes: nextNodes
|
|
411
1952
|
};
|
|
412
1953
|
}
|
|
1954
|
+
function stripNulls(value) {
|
|
1955
|
+
if (value === null) {
|
|
1956
|
+
return void 0;
|
|
1957
|
+
}
|
|
1958
|
+
if (Array.isArray(value)) {
|
|
1959
|
+
const next2 = value.map((entry) => stripNulls(entry)).filter((entry) => entry !== void 0 && entry !== null);
|
|
1960
|
+
return next2;
|
|
1961
|
+
}
|
|
1962
|
+
if (typeof value !== "object" || value === void 0) {
|
|
1963
|
+
return value;
|
|
1964
|
+
}
|
|
1965
|
+
const next = {};
|
|
1966
|
+
Object.entries(value).forEach(([key, entry]) => {
|
|
1967
|
+
if (entry === null) {
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const cleaned = stripNulls(entry);
|
|
1971
|
+
if (cleaned === void 0) {
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
next[key] = cleaned;
|
|
1975
|
+
});
|
|
1976
|
+
return next;
|
|
1977
|
+
}
|
|
413
1978
|
var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
414
1979
|
function pickBundleGraph(bundle, preferredKinds) {
|
|
415
1980
|
if (!bundle?.graphs || bundle.graphs.length === 0) {
|
|
@@ -580,26 +2145,49 @@ function convertExtractedAnimations(clips) {
|
|
|
580
2145
|
}
|
|
581
2146
|
const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
|
|
582
2147
|
const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
|
|
583
|
-
|
|
2148
|
+
const interpolationRaw = typeof track.interpolation === "string" ? track.interpolation.trim().toLowerCase() : "linear";
|
|
2149
|
+
const interpolation = interpolationRaw === "step" ? "step" : interpolationRaw === "cubic" || interpolationRaw === "cubicspline" ? "cubic" : "linear";
|
|
2150
|
+
const isCubic = interpolation === "cubic";
|
|
2151
|
+
const hasTripletTangents = isCubic && values.length === times.length * valueSize * 3;
|
|
2152
|
+
const hasFlatValues = values.length === times.length * valueSize;
|
|
2153
|
+
if (!hasTripletTangents && !hasFlatValues) {
|
|
584
2154
|
return;
|
|
585
2155
|
}
|
|
586
2156
|
const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
|
|
587
2157
|
const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
|
|
588
2158
|
const keyframes = [];
|
|
589
2159
|
times.forEach((time, index) => {
|
|
590
|
-
const
|
|
591
|
-
const
|
|
2160
|
+
const flatBase = index * valueSize + componentIndex;
|
|
2161
|
+
const valueBase = hasTripletTangents ? index * valueSize * 3 + valueSize + componentIndex : flatBase;
|
|
2162
|
+
const value = values[valueBase];
|
|
592
2163
|
if (!Number.isFinite(value)) {
|
|
593
2164
|
return;
|
|
594
2165
|
}
|
|
595
|
-
|
|
2166
|
+
const keyframe = {
|
|
2167
|
+
time,
|
|
2168
|
+
value
|
|
2169
|
+
};
|
|
2170
|
+
if (hasTripletTangents) {
|
|
2171
|
+
const inBase = index * valueSize * 3 + componentIndex;
|
|
2172
|
+
const outBase = index * valueSize * 3 + valueSize * 2 + componentIndex;
|
|
2173
|
+
const inTangent = values[inBase];
|
|
2174
|
+
const outTangent = values[outBase];
|
|
2175
|
+
if (Number.isFinite(inTangent)) {
|
|
2176
|
+
keyframe.inTangent = inTangent;
|
|
2177
|
+
}
|
|
2178
|
+
if (Number.isFinite(outTangent)) {
|
|
2179
|
+
keyframe.outTangent = outTangent;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
keyframes.push(keyframe);
|
|
596
2183
|
});
|
|
597
2184
|
if (keyframes.length === 0) {
|
|
598
2185
|
return;
|
|
599
2186
|
}
|
|
600
2187
|
convertedTracks.push({
|
|
601
2188
|
channel: channelId,
|
|
602
|
-
keyframes
|
|
2189
|
+
keyframes,
|
|
2190
|
+
interpolation
|
|
603
2191
|
});
|
|
604
2192
|
});
|
|
605
2193
|
if (convertedTracks.length === 0) {
|
|
@@ -643,12 +2231,15 @@ function pickExtractedAnimations(asset) {
|
|
|
643
2231
|
}
|
|
644
2232
|
return animations;
|
|
645
2233
|
}
|
|
646
|
-
function mergeAnimationLists(explicit, fromBundle) {
|
|
647
|
-
if (!
|
|
2234
|
+
function mergeAnimationLists(explicit, hasExplicitOverride, fromBundle) {
|
|
2235
|
+
if (!hasExplicitOverride) {
|
|
2236
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
2237
|
+
}
|
|
2238
|
+
if (!Array.isArray(explicit)) {
|
|
648
2239
|
return void 0;
|
|
649
2240
|
}
|
|
650
|
-
if (
|
|
651
|
-
return
|
|
2241
|
+
if (explicit.length === 0) {
|
|
2242
|
+
return [];
|
|
652
2243
|
}
|
|
653
2244
|
if (fromBundle.length === 0) {
|
|
654
2245
|
return explicit;
|
|
@@ -666,21 +2257,153 @@ function mergeAnimationLists(explicit, fromBundle) {
|
|
|
666
2257
|
}
|
|
667
2258
|
return changed ? merged : explicit;
|
|
668
2259
|
}
|
|
2260
|
+
function extractProgramResetValues(value) {
|
|
2261
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2262
|
+
return void 0;
|
|
2263
|
+
}
|
|
2264
|
+
const entries = Object.entries(value).filter(
|
|
2265
|
+
([, rawValue]) => Number.isFinite(Number(rawValue))
|
|
2266
|
+
);
|
|
2267
|
+
if (entries.length === 0) {
|
|
2268
|
+
return void 0;
|
|
2269
|
+
}
|
|
2270
|
+
return Object.fromEntries(
|
|
2271
|
+
entries.map(([path, rawValue]) => [path, Number(rawValue)])
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
function convertBundlePrograms(entries) {
|
|
2275
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
2276
|
+
return [];
|
|
2277
|
+
}
|
|
2278
|
+
return entries.filter((entry) => normaliseBundleKind(entry?.kind) === "motiongraph").map((entry) => {
|
|
2279
|
+
const graph = convertBundleGraph(entry);
|
|
2280
|
+
if (!graph) {
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
const metadata = entry.metadata && typeof entry.metadata === "object" && !Array.isArray(entry.metadata) ? entry.metadata : void 0;
|
|
2284
|
+
return {
|
|
2285
|
+
id: entry.id,
|
|
2286
|
+
label: typeof entry.label === "string" ? entry.label : void 0,
|
|
2287
|
+
graph,
|
|
2288
|
+
metadata,
|
|
2289
|
+
resetValues: extractProgramResetValues(metadata?.resetValues)
|
|
2290
|
+
};
|
|
2291
|
+
}).filter(Boolean);
|
|
2292
|
+
}
|
|
2293
|
+
function resolveConstraintDefault(path, namespace, inputConstraints) {
|
|
2294
|
+
const trimmed = path.trim();
|
|
2295
|
+
if (!trimmed) {
|
|
2296
|
+
return void 0;
|
|
2297
|
+
}
|
|
2298
|
+
const stripped = stripRigFacePrefix(trimmed);
|
|
2299
|
+
const relativePath = stripped ? `/${stripped}` : "";
|
|
2300
|
+
const candidates = [
|
|
2301
|
+
namespaceTypedPath(trimmed, namespace),
|
|
2302
|
+
trimmed,
|
|
2303
|
+
stripped ? namespaceTypedPath(stripped, namespace) : void 0,
|
|
2304
|
+
stripped || void 0,
|
|
2305
|
+
relativePath || void 0
|
|
2306
|
+
];
|
|
2307
|
+
for (const candidate of candidates) {
|
|
2308
|
+
if (!candidate) {
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
const defaultValue = inputConstraints[candidate]?.defaultValue;
|
|
2312
|
+
if (Number.isFinite(defaultValue)) {
|
|
2313
|
+
return Number(defaultValue);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
return void 0;
|
|
2317
|
+
}
|
|
2318
|
+
function deriveProgramInputSeedValues(args) {
|
|
2319
|
+
const graphSpec = resolveGraphSpec(
|
|
2320
|
+
args.program.graph,
|
|
2321
|
+
`${args.program.id ?? "program"} graph (seed defaults)`
|
|
2322
|
+
);
|
|
2323
|
+
if (!graphSpec) {
|
|
2324
|
+
return [];
|
|
2325
|
+
}
|
|
2326
|
+
return collectInputPaths(graphSpec).map((path) => path.trim()).filter((path) => path.length > 0).flatMap((path) => {
|
|
2327
|
+
const namespacedPath = namespaceTypedPath(path, args.namespace);
|
|
2328
|
+
if (args.stagedInputs.has(namespacedPath)) {
|
|
2329
|
+
return [];
|
|
2330
|
+
}
|
|
2331
|
+
if (args.getPathSnapshot(namespacedPath) !== void 0) {
|
|
2332
|
+
return [];
|
|
2333
|
+
}
|
|
2334
|
+
const defaultValue = resolveConstraintDefault(
|
|
2335
|
+
path,
|
|
2336
|
+
args.namespace,
|
|
2337
|
+
args.inputConstraints
|
|
2338
|
+
);
|
|
2339
|
+
if (!Number.isFinite(defaultValue)) {
|
|
2340
|
+
return [];
|
|
2341
|
+
}
|
|
2342
|
+
return [{ path, value: { float: Number(defaultValue) } }];
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
function mergeProgramLists(explicit, hasExplicitOverride, fromBundle) {
|
|
2346
|
+
if (!hasExplicitOverride) {
|
|
2347
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
2348
|
+
}
|
|
2349
|
+
if (!Array.isArray(explicit)) {
|
|
2350
|
+
return void 0;
|
|
2351
|
+
}
|
|
2352
|
+
if (explicit.length === 0) {
|
|
2353
|
+
return [];
|
|
2354
|
+
}
|
|
2355
|
+
if (fromBundle.length === 0) {
|
|
2356
|
+
return explicit;
|
|
2357
|
+
}
|
|
2358
|
+
const seen = new Set(explicit.map((program) => program.id));
|
|
2359
|
+
let changed = false;
|
|
2360
|
+
const merged = [...explicit];
|
|
2361
|
+
for (const program of fromBundle) {
|
|
2362
|
+
if (!program.id || seen.has(program.id)) {
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
merged.push(program);
|
|
2366
|
+
seen.add(program.id);
|
|
2367
|
+
changed = true;
|
|
2368
|
+
}
|
|
2369
|
+
return changed ? merged : explicit;
|
|
2370
|
+
}
|
|
669
2371
|
function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
670
2372
|
const resolvedBundle = base.bundle ?? extracted ?? null;
|
|
2373
|
+
const hasBaseRigOverride = Object.prototype.hasOwnProperty.call(base, "rig");
|
|
2374
|
+
const hasBasePoseOverride = Object.prototype.hasOwnProperty.call(
|
|
2375
|
+
base,
|
|
2376
|
+
"pose"
|
|
2377
|
+
);
|
|
2378
|
+
const hasBaseAnimationsOverride = Object.prototype.hasOwnProperty.call(
|
|
2379
|
+
base,
|
|
2380
|
+
"animations"
|
|
2381
|
+
);
|
|
2382
|
+
const hasBaseProgramsOverride = Object.prototype.hasOwnProperty.call(
|
|
2383
|
+
base,
|
|
2384
|
+
"programs"
|
|
2385
|
+
);
|
|
671
2386
|
const rigFromBundle = convertBundleGraph(
|
|
672
2387
|
pickBundleGraph(resolvedBundle, ["rig"])
|
|
673
2388
|
);
|
|
674
|
-
const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
|
|
2389
|
+
const resolvedRig = hasBaseRigOverride ? base.rig : base.rig ?? rigFromBundle ?? void 0;
|
|
675
2390
|
const basePose = base.pose;
|
|
2391
|
+
const hasBasePoseGraphOverride = Boolean(
|
|
2392
|
+
basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
|
|
2393
|
+
);
|
|
2394
|
+
const hasBasePoseConfigOverride = Boolean(
|
|
2395
|
+
basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
|
|
2396
|
+
);
|
|
676
2397
|
const poseStageFilter = basePose?.stageNeutralFilter;
|
|
677
|
-
const poseGraphFromBundle = basePose
|
|
2398
|
+
const poseGraphFromBundle = hasBasePoseOverride && !basePose ? null : hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
678
2399
|
pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
|
|
679
2400
|
);
|
|
680
|
-
const resolvedPoseGraph = basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
681
|
-
const resolvedPoseConfig = basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
2401
|
+
const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
2402
|
+
const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
682
2403
|
let resolvedPose = basePose;
|
|
683
|
-
if (basePose) {
|
|
2404
|
+
if (hasBasePoseOverride && !basePose) {
|
|
2405
|
+
resolvedPose = void 0;
|
|
2406
|
+
} else if (basePose) {
|
|
684
2407
|
const nextPose = { ...basePose };
|
|
685
2408
|
let changed = false;
|
|
686
2409
|
if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
|
|
@@ -708,15 +2431,22 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
708
2431
|
);
|
|
709
2432
|
let resolvedAnimations = mergeAnimationLists(
|
|
710
2433
|
base.animations,
|
|
2434
|
+
hasBaseAnimationsOverride,
|
|
711
2435
|
animationsFromBundle
|
|
712
2436
|
);
|
|
713
2437
|
const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
|
|
714
2438
|
if (animationsFromAsset.length > 0) {
|
|
715
2439
|
resolvedAnimations = mergeAnimationLists(
|
|
716
2440
|
resolvedAnimations,
|
|
2441
|
+
true,
|
|
717
2442
|
animationsFromAsset
|
|
718
2443
|
);
|
|
719
2444
|
}
|
|
2445
|
+
const programsFromBundle = mergeProgramLists(
|
|
2446
|
+
base.programs,
|
|
2447
|
+
hasBaseProgramsOverride,
|
|
2448
|
+
convertBundlePrograms(resolvedBundle?.graphs)
|
|
2449
|
+
);
|
|
720
2450
|
const merged = {
|
|
721
2451
|
...base
|
|
722
2452
|
};
|
|
@@ -727,124 +2457,223 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
727
2457
|
}
|
|
728
2458
|
merged.pose = resolvedPose;
|
|
729
2459
|
merged.animations = resolvedAnimations;
|
|
2460
|
+
merged.programs = programsFromBundle;
|
|
730
2461
|
merged.bundle = resolvedBundle;
|
|
731
2462
|
return merged;
|
|
732
2463
|
}
|
|
2464
|
+
function normalizeStoredAnimationInterpolation(interpolation) {
|
|
2465
|
+
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
2466
|
+
if (mode === "step") {
|
|
2467
|
+
return "step";
|
|
2468
|
+
}
|
|
2469
|
+
if (mode === "cubic" || mode === "cubicspline") {
|
|
2470
|
+
return "cubic";
|
|
2471
|
+
}
|
|
2472
|
+
return "linear";
|
|
2473
|
+
}
|
|
2474
|
+
function buildStoredAnimationTransitions(mode) {
|
|
2475
|
+
if (mode === "cubic") {
|
|
2476
|
+
return void 0;
|
|
2477
|
+
}
|
|
2478
|
+
if (mode === "step") {
|
|
2479
|
+
return {
|
|
2480
|
+
in: { x: 1, y: 1 },
|
|
2481
|
+
out: { x: 1, y: 0 }
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
return {
|
|
2485
|
+
in: { x: 1, y: 1 },
|
|
2486
|
+
out: { x: 0, y: 0 }
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
function toStoredAnimationClip(fallbackId, clip) {
|
|
2490
|
+
const clipId = typeof clip.id === "string" && clip.id.trim().length > 0 ? clip.id.trim() : fallbackId;
|
|
2491
|
+
const clipName = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : clipId;
|
|
2492
|
+
const durationSeconds = resolveClipDurationSeconds(clip, 0);
|
|
2493
|
+
const durationMs = Math.max(1, Math.round(durationSeconds * 1e3));
|
|
2494
|
+
const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, trackIndex) => {
|
|
2495
|
+
const channel = typeof rawTrack.channel === "string" ? rawTrack.channel.trim() : "";
|
|
2496
|
+
if (!channel) {
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2499
|
+
const keyframes = (Array.isArray(rawTrack.keyframes) ? rawTrack.keyframes : []).map((keyframe) => {
|
|
2500
|
+
const time = Number(keyframe.time);
|
|
2501
|
+
const value = Number(keyframe.value);
|
|
2502
|
+
const keyframeId = keyframe["id"];
|
|
2503
|
+
const keyframeInterpolation = keyframe["interpolation"];
|
|
2504
|
+
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
return {
|
|
2508
|
+
id: typeof keyframeId === "string" && keyframeId.trim().length > 0 ? keyframeId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}:point-${time.toFixed(6)}`,
|
|
2509
|
+
time,
|
|
2510
|
+
value,
|
|
2511
|
+
mode: normalizeStoredAnimationInterpolation(
|
|
2512
|
+
keyframeInterpolation ?? rawTrack.interpolation
|
|
2513
|
+
)
|
|
2514
|
+
};
|
|
2515
|
+
}).filter(Boolean);
|
|
2516
|
+
if (keyframes.length === 0) {
|
|
2517
|
+
return null;
|
|
2518
|
+
}
|
|
2519
|
+
keyframes.sort((left, right) => {
|
|
2520
|
+
if (left.time !== right.time) {
|
|
2521
|
+
return left.time - right.time;
|
|
2522
|
+
}
|
|
2523
|
+
return left.id.localeCompare(right.id);
|
|
2524
|
+
});
|
|
2525
|
+
const rawTrackId = rawTrack["id"];
|
|
2526
|
+
const rawTrackName = rawTrack["name"];
|
|
2527
|
+
const trackId = typeof rawTrackId === "string" && rawTrackId.trim().length > 0 ? rawTrackId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}`;
|
|
2528
|
+
const trackName = typeof rawTrackName === "string" && rawTrackName.trim().length > 0 ? rawTrackName.trim() : channel.replace(/^\/+/, "") || trackId;
|
|
2529
|
+
const denominator = durationSeconds > 0 ? durationSeconds : 1;
|
|
2530
|
+
return {
|
|
2531
|
+
id: trackId,
|
|
2532
|
+
name: trackName,
|
|
2533
|
+
animatableId: channel,
|
|
2534
|
+
points: keyframes.map((keyframe) => {
|
|
2535
|
+
const stamp = Math.max(0, Math.min(1, keyframe.time / denominator));
|
|
2536
|
+
const transitions = buildStoredAnimationTransitions(keyframe.mode);
|
|
2537
|
+
return {
|
|
2538
|
+
id: keyframe.id,
|
|
2539
|
+
stamp,
|
|
2540
|
+
value: keyframe.value,
|
|
2541
|
+
...transitions ? { transitions } : {}
|
|
2542
|
+
};
|
|
2543
|
+
})
|
|
2544
|
+
};
|
|
2545
|
+
}).filter(Boolean);
|
|
2546
|
+
return {
|
|
2547
|
+
id: clipId,
|
|
2548
|
+
name: clipName,
|
|
2549
|
+
duration: durationMs,
|
|
2550
|
+
groups: {},
|
|
2551
|
+
tracks
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
733
2554
|
function VizijRuntimeProvider({
|
|
734
2555
|
assetBundle,
|
|
735
2556
|
children,
|
|
736
2557
|
namespace: namespaceProp,
|
|
737
2558
|
faceId: faceIdProp,
|
|
2559
|
+
updateTier = "auto",
|
|
738
2560
|
autoCreate = true,
|
|
739
|
-
createOptions,
|
|
740
2561
|
autostart = false,
|
|
741
|
-
|
|
2562
|
+
driveRuntime = true,
|
|
742
2563
|
mergeStrategy,
|
|
743
2564
|
onRegisterControllers,
|
|
744
2565
|
onStatusChange,
|
|
745
|
-
|
|
2566
|
+
transformOutputWrite
|
|
746
2567
|
}) {
|
|
747
2568
|
const storeRef = useRef(null);
|
|
748
2569
|
if (!storeRef.current) {
|
|
749
2570
|
storeRef.current = createVizijStore();
|
|
750
2571
|
}
|
|
751
|
-
|
|
752
|
-
const hasParentOrchestrator = Boolean(parentOrchestrator);
|
|
753
|
-
const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
|
|
754
|
-
if (orchestratorScope === "shared" && !hasParentOrchestrator) {
|
|
755
|
-
console.warn(
|
|
756
|
-
'[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
const runtimeTree = /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
|
|
2572
|
+
return /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
|
|
760
2573
|
VizijRuntimeProviderInner,
|
|
761
2574
|
{
|
|
762
2575
|
assetBundle,
|
|
763
2576
|
namespace: namespaceProp,
|
|
764
2577
|
faceId: faceIdProp,
|
|
2578
|
+
updateTier,
|
|
765
2579
|
autoCreate,
|
|
766
2580
|
autostart,
|
|
767
|
-
|
|
768
|
-
createOptions,
|
|
2581
|
+
driveRuntime,
|
|
769
2582
|
mergeStrategy,
|
|
770
2583
|
onRegisterControllers,
|
|
771
2584
|
onStatusChange,
|
|
2585
|
+
transformOutputWrite,
|
|
772
2586
|
store: storeRef.current,
|
|
773
2587
|
children
|
|
774
2588
|
}
|
|
775
2589
|
) });
|
|
776
|
-
if (!shouldProvideOrchestrator) {
|
|
777
|
-
return runtimeTree;
|
|
778
|
-
}
|
|
779
|
-
return /* @__PURE__ */ jsx(
|
|
780
|
-
OrchestratorProvider,
|
|
781
|
-
{
|
|
782
|
-
autoCreate,
|
|
783
|
-
createOptions,
|
|
784
|
-
autostart: false,
|
|
785
|
-
children: runtimeTree
|
|
786
|
-
}
|
|
787
|
-
);
|
|
788
2590
|
}
|
|
789
2591
|
function VizijRuntimeProviderInner({
|
|
790
2592
|
assetBundle: initialAssetBundle,
|
|
791
2593
|
namespace: namespaceProp,
|
|
792
2594
|
faceId: faceIdProp,
|
|
2595
|
+
updateTier,
|
|
793
2596
|
mergeStrategy,
|
|
794
2597
|
onRegisterControllers,
|
|
795
2598
|
onStatusChange,
|
|
2599
|
+
transformOutputWrite,
|
|
796
2600
|
store,
|
|
797
2601
|
children,
|
|
798
2602
|
autoCreate,
|
|
799
2603
|
autostart,
|
|
800
|
-
|
|
801
|
-
driveOrchestrator
|
|
2604
|
+
driveRuntime
|
|
802
2605
|
}) {
|
|
2606
|
+
const [assetBundleOverride, setAssetBundleOverride] = useState(null);
|
|
2607
|
+
const [graphUpdateToken, setGraphUpdateToken] = useState(0);
|
|
2608
|
+
const effectiveAssetBundle = assetBundleOverride ?? initialAssetBundle;
|
|
2609
|
+
const latestEffectiveAssetBundleRef = useRef(effectiveAssetBundle);
|
|
803
2610
|
const [extractedBundle, setExtractedBundle] = useState(() => {
|
|
804
|
-
if (
|
|
805
|
-
return
|
|
2611
|
+
if (effectiveAssetBundle.bundle) {
|
|
2612
|
+
return effectiveAssetBundle.bundle;
|
|
806
2613
|
}
|
|
807
|
-
if (
|
|
808
|
-
return
|
|
2614
|
+
if (effectiveAssetBundle.glb.kind === "world" && effectiveAssetBundle.glb.bundle) {
|
|
2615
|
+
return effectiveAssetBundle.glb.bundle;
|
|
809
2616
|
}
|
|
810
2617
|
return null;
|
|
811
2618
|
});
|
|
812
2619
|
const [extractedAnimations, setExtractedAnimations] = useState([]);
|
|
2620
|
+
const previousBundleRef = useRef(null);
|
|
2621
|
+
const suppressNextBundlePlanRef = useRef(false);
|
|
2622
|
+
const pendingPlanRef = useRef(null);
|
|
2623
|
+
const updateTierRef = useRef(updateTier);
|
|
813
2624
|
useEffect(() => {
|
|
814
|
-
if (
|
|
815
|
-
setExtractedBundle(
|
|
2625
|
+
if (effectiveAssetBundle.bundle) {
|
|
2626
|
+
setExtractedBundle(effectiveAssetBundle.bundle);
|
|
816
2627
|
return;
|
|
817
2628
|
}
|
|
818
|
-
if (
|
|
819
|
-
setExtractedBundle(
|
|
2629
|
+
if (effectiveAssetBundle.glb.kind === "world") {
|
|
2630
|
+
setExtractedBundle(effectiveAssetBundle.glb.bundle ?? null);
|
|
820
2631
|
} else {
|
|
821
2632
|
setExtractedBundle(null);
|
|
822
2633
|
}
|
|
823
|
-
}, [
|
|
2634
|
+
}, [effectiveAssetBundle]);
|
|
2635
|
+
useEffect(() => {
|
|
2636
|
+
updateTierRef.current = updateTier;
|
|
2637
|
+
}, [updateTier]);
|
|
824
2638
|
const assetBundle = useMemo(
|
|
825
2639
|
() => mergeAssetBundle(
|
|
826
|
-
|
|
2640
|
+
effectiveAssetBundle,
|
|
827
2641
|
extractedBundle,
|
|
828
2642
|
extractedAnimations
|
|
829
2643
|
),
|
|
830
|
-
[
|
|
2644
|
+
[effectiveAssetBundle, extractedBundle, extractedAnimations]
|
|
831
2645
|
);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
2646
|
+
useEffect(() => {
|
|
2647
|
+
latestEffectiveAssetBundleRef.current = effectiveAssetBundle;
|
|
2648
|
+
}, [effectiveAssetBundle]);
|
|
2649
|
+
useEffect(() => {
|
|
2650
|
+
if (suppressNextBundlePlanRef.current) {
|
|
2651
|
+
suppressNextBundlePlanRef.current = false;
|
|
2652
|
+
previousBundleRef.current = effectiveAssetBundle;
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
const plan = resolveRuntimeUpdatePlan(
|
|
2656
|
+
previousBundleRef.current,
|
|
2657
|
+
effectiveAssetBundle,
|
|
2658
|
+
updateTierRef.current
|
|
2659
|
+
);
|
|
2660
|
+
pendingPlanRef.current = plan;
|
|
2661
|
+
previousBundleRef.current = effectiveAssetBundle;
|
|
2662
|
+
if (plan.reregisterGraphs) {
|
|
2663
|
+
setGraphUpdateToken((prev) => prev + 1);
|
|
2664
|
+
}
|
|
2665
|
+
}, [effectiveAssetBundle]);
|
|
2666
|
+
const deviceSlotRef = useRef(null);
|
|
2667
|
+
if (!deviceSlotRef.current) {
|
|
2668
|
+
deviceSlotRef.current = new DeviceSlot();
|
|
2669
|
+
}
|
|
2670
|
+
const deviceSlot = deviceSlotRef.current;
|
|
2671
|
+
const [ready, setReady] = useState(false);
|
|
2672
|
+
const graphSourcesRef = useRef([]);
|
|
846
2673
|
const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
|
|
847
2674
|
const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
|
|
2675
|
+
const faceIdRef = useRef(faceId);
|
|
2676
|
+
faceIdRef.current = faceId;
|
|
848
2677
|
const [status, setStatus] = useState({
|
|
849
2678
|
loading: true,
|
|
850
2679
|
ready: false,
|
|
@@ -857,20 +2686,68 @@ function VizijRuntimeProviderInner({
|
|
|
857
2686
|
stepHz: void 0,
|
|
858
2687
|
controllers: { graphs: [], anims: [] }
|
|
859
2688
|
});
|
|
2689
|
+
const runtimeDebugInstanceIdRef = useRef(
|
|
2690
|
+
`vizij-runtime:${runtimeDebugInstanceSequence++}`
|
|
2691
|
+
);
|
|
860
2692
|
const errorsRef = useRef([]);
|
|
861
2693
|
const outputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
862
2694
|
const baseOutputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
863
2695
|
const namespacedOutputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
864
2696
|
const namespaceRef = useRef(namespace);
|
|
865
|
-
const
|
|
2697
|
+
const driveRuntimeRef = useRef(driveRuntime);
|
|
866
2698
|
const rigInputMapRef = useRef({});
|
|
2699
|
+
const rigPoseControlInputIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
867
2700
|
const registeredGraphsRef = useRef([]);
|
|
868
2701
|
const registeredAnimationsRef = useRef([]);
|
|
869
2702
|
const mergedGraphRef = useRef(null);
|
|
2703
|
+
const poseControlBridgeValuesRef = useRef(/* @__PURE__ */ new Map());
|
|
2704
|
+
const poseWeightFallbackMap = useMemo(() => {
|
|
2705
|
+
const map = /* @__PURE__ */ new Map();
|
|
2706
|
+
const poseConfig = assetBundle.pose?.config;
|
|
2707
|
+
if (!poseConfig) {
|
|
2708
|
+
return map;
|
|
2709
|
+
}
|
|
2710
|
+
const posePaths = buildPoseWeightPathMap(
|
|
2711
|
+
poseConfig.poses ?? [],
|
|
2712
|
+
poseConfig.faceId ?? faceId ?? "face"
|
|
2713
|
+
);
|
|
2714
|
+
(poseConfig.poses ?? []).forEach((pose) => {
|
|
2715
|
+
const posePath = posePaths.get(pose.id);
|
|
2716
|
+
if (!posePath) {
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
const values = Object.fromEntries(
|
|
2720
|
+
Object.entries(pose.values ?? {}).filter(
|
|
2721
|
+
([, value]) => Number.isFinite(value)
|
|
2722
|
+
)
|
|
2723
|
+
);
|
|
2724
|
+
map.set(posePath, values);
|
|
2725
|
+
});
|
|
2726
|
+
return map;
|
|
2727
|
+
}, [assetBundle.pose?.config, faceId]);
|
|
2728
|
+
const useLegacyPoseWeightFallback = useMemo(
|
|
2729
|
+
() => shouldUseLegacyPoseWeightFallback(Boolean(assetBundle.pose?.graph)),
|
|
2730
|
+
[assetBundle.pose?.graph]
|
|
2731
|
+
);
|
|
2732
|
+
const resolvedProgramAssets = useMemo(
|
|
2733
|
+
() => assetBundle.programs && assetBundle.programs.length > 0 ? assetBundle.programs : convertBundlePrograms(assetBundle.bundle?.graphs),
|
|
2734
|
+
[assetBundle.bundle?.graphs, assetBundle.programs]
|
|
2735
|
+
);
|
|
870
2736
|
const [inputConstraints, setInputConstraints] = useState({});
|
|
2737
|
+
const inputConstraintsRef = useRef({});
|
|
871
2738
|
const avgStepDtRef = useRef(null);
|
|
2739
|
+
const inputDriverIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
872
2740
|
const animationTweensRef = useRef(/* @__PURE__ */ new Map());
|
|
873
2741
|
const clipPlaybackRef = useRef(/* @__PURE__ */ new Map());
|
|
2742
|
+
const programPlaybackRef = useRef(
|
|
2743
|
+
/* @__PURE__ */ new Map()
|
|
2744
|
+
);
|
|
2745
|
+
const programControllerIdsRef = useRef(/* @__PURE__ */ new Map());
|
|
2746
|
+
const animationModuleRef = useRef(null);
|
|
2747
|
+
const animationHostRef = useRef(null);
|
|
2748
|
+
const animationsSourceRegisteredRef = useRef(false);
|
|
2749
|
+
const animationGapWarnedRef = useRef(/* @__PURE__ */ new Set());
|
|
2750
|
+
const animationSystemActiveRef = useRef(true);
|
|
874
2751
|
const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
|
|
875
2752
|
const autostartRef = useRef(autostart);
|
|
876
2753
|
const lastActivityTimeRef = useRef(now());
|
|
@@ -885,9 +2762,62 @@ function VizijRuntimeProviderInner({
|
|
|
885
2762
|
runtimeMountedRef.current = false;
|
|
886
2763
|
};
|
|
887
2764
|
}, []);
|
|
2765
|
+
const publishRuntimeDebugState = useCallback(() => {
|
|
2766
|
+
const storeState = store.getState();
|
|
2767
|
+
setRuntimeDebugState(runtimeDebugInstanceIdRef.current, {
|
|
2768
|
+
namespace,
|
|
2769
|
+
faceId: faceId ?? null,
|
|
2770
|
+
rootId: status.rootId,
|
|
2771
|
+
ready: status.ready,
|
|
2772
|
+
loading: status.loading,
|
|
2773
|
+
autostart,
|
|
2774
|
+
driveRuntime,
|
|
2775
|
+
loopMode,
|
|
2776
|
+
outputCount: status.outputPaths.length,
|
|
2777
|
+
graphControllerCount: status.controllers.graphs.length,
|
|
2778
|
+
animationControllerCount: status.controllers.anims.length,
|
|
2779
|
+
registeredGraphCount: registeredGraphsRef.current.length,
|
|
2780
|
+
registeredAnimationCount: registeredAnimationsRef.current.length,
|
|
2781
|
+
programControllerCount: programControllerIdsRef.current.size,
|
|
2782
|
+
animationTweenCount: animationTweensRef.current.size,
|
|
2783
|
+
clipPlaybackCount: clipPlaybackRef.current.size,
|
|
2784
|
+
programPlaybackCount: programPlaybackRef.current.size,
|
|
2785
|
+
stagedInputCount: stagedInputsRef.current.size,
|
|
2786
|
+
activeDriverCount: inputDriverIdsRef.current.size,
|
|
2787
|
+
worldEntryCount: Object.keys(storeState.world ?? {}).length,
|
|
2788
|
+
animatableCount: Object.keys(storeState.animatables ?? {}).length,
|
|
2789
|
+
valuesSize: storeState.values instanceof Map ? storeState.values.size : null,
|
|
2790
|
+
stepHz: status.stepHz ?? null
|
|
2791
|
+
});
|
|
2792
|
+
}, [
|
|
2793
|
+
autostart,
|
|
2794
|
+
driveRuntime,
|
|
2795
|
+
faceId,
|
|
2796
|
+
loopMode,
|
|
2797
|
+
namespace,
|
|
2798
|
+
status.controllers.anims.length,
|
|
2799
|
+
status.controllers.graphs.length,
|
|
2800
|
+
status.loading,
|
|
2801
|
+
status.outputPaths.length,
|
|
2802
|
+
status.ready,
|
|
2803
|
+
status.rootId,
|
|
2804
|
+
status.stepHz,
|
|
2805
|
+
store
|
|
2806
|
+
]);
|
|
2807
|
+
useEffect(() => {
|
|
2808
|
+
publishRuntimeDebugState();
|
|
2809
|
+
const unsubscribe = store.subscribe(() => {
|
|
2810
|
+
publishRuntimeDebugState();
|
|
2811
|
+
});
|
|
2812
|
+
return () => {
|
|
2813
|
+
unsubscribe();
|
|
2814
|
+
clearRuntimeDebugState(runtimeDebugInstanceIdRef.current);
|
|
2815
|
+
};
|
|
2816
|
+
}, [publishRuntimeDebugState, store]);
|
|
888
2817
|
useEffect(() => {
|
|
889
2818
|
const rigAsset = assetBundle.rig;
|
|
890
2819
|
if (!rigAsset) {
|
|
2820
|
+
inputConstraintsRef.current = {};
|
|
891
2821
|
setInputConstraints({});
|
|
892
2822
|
return;
|
|
893
2823
|
}
|
|
@@ -900,15 +2830,8 @@ function VizijRuntimeProviderInner({
|
|
|
900
2830
|
rigAsset.inputMetadata,
|
|
901
2831
|
namespace
|
|
902
2832
|
);
|
|
2833
|
+
inputConstraintsRef.current = constraints;
|
|
903
2834
|
setInputConstraints(constraints);
|
|
904
|
-
const isDevEnv = typeof globalThis !== "undefined" && Boolean(globalThis?.process?.env?.NODE_ENV !== "production");
|
|
905
|
-
if (isDevEnv) {
|
|
906
|
-
const size = Object.keys(constraints).length;
|
|
907
|
-
console.log("[vizij-runtime] input constraints computed", size, {
|
|
908
|
-
namespace,
|
|
909
|
-
rigId: rigAsset.id
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
2835
|
}, [assetBundle.rig, namespace]);
|
|
913
2836
|
const requestLoopMode = useCallback((mode) => {
|
|
914
2837
|
if (!runtimeMountedRef.current) {
|
|
@@ -917,7 +2840,28 @@ function VizijRuntimeProviderInner({
|
|
|
917
2840
|
setLoopMode((prev) => prev === mode ? prev : mode);
|
|
918
2841
|
}, []);
|
|
919
2842
|
const hasActiveAnimations = useCallback(() => {
|
|
920
|
-
|
|
2843
|
+
if (animationTweensRef.current.size > 0) {
|
|
2844
|
+
return true;
|
|
2845
|
+
}
|
|
2846
|
+
if (!animationSystemActiveRef.current) {
|
|
2847
|
+
for (const state of programPlaybackRef.current.values()) {
|
|
2848
|
+
if (state.state === "playing") {
|
|
2849
|
+
return true;
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
return false;
|
|
2853
|
+
}
|
|
2854
|
+
for (const state of clipPlaybackRef.current.values()) {
|
|
2855
|
+
if (state.playing) {
|
|
2856
|
+
return true;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
for (const state of programPlaybackRef.current.values()) {
|
|
2860
|
+
if (state.state === "playing") {
|
|
2861
|
+
return true;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return false;
|
|
921
2865
|
}, []);
|
|
922
2866
|
const computeDesiredLoopMode = useCallback(() => {
|
|
923
2867
|
const hasAnimations = hasActiveAnimations();
|
|
@@ -939,11 +2883,50 @@ function VizijRuntimeProviderInner({
|
|
|
939
2883
|
}, [updateLoopMode]);
|
|
940
2884
|
const setInput = useCallback(
|
|
941
2885
|
(path, value, shape) => {
|
|
2886
|
+
const numericValue = valueAsNumber2(value);
|
|
2887
|
+
const basePath = stripNamespace(
|
|
2888
|
+
normalisePath(path),
|
|
2889
|
+
namespaceRef.current
|
|
2890
|
+
);
|
|
2891
|
+
const poseValues = useLegacyPoseWeightFallback && numericValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
|
|
2892
|
+
if (poseValues && numericValue != null) {
|
|
2893
|
+
const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
|
|
2894
|
+
const rigMap = rigInputMapRef.current;
|
|
2895
|
+
Object.entries(poseValues).forEach(([inputId, poseValue]) => {
|
|
2896
|
+
if (!Number.isFinite(poseValue)) {
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
const controlPath = resolvePoseControlInputPath({
|
|
2900
|
+
inputId,
|
|
2901
|
+
basePath: buildRigInputPath(
|
|
2902
|
+
poseFaceId,
|
|
2903
|
+
`/pose/control/${inputId}`
|
|
2904
|
+
),
|
|
2905
|
+
rigInputPathMap: rigMap,
|
|
2906
|
+
hasNativePoseControlInput: true
|
|
2907
|
+
}) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
|
|
2908
|
+
setInput(controlPath, { float: Number(poseValue) * numericValue });
|
|
2909
|
+
});
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
942
2912
|
markActivity();
|
|
943
2913
|
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2914
|
+
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2915
|
+
console.log("[vizij-runtime] stage input", {
|
|
2916
|
+
path,
|
|
2917
|
+
namespacedPath,
|
|
2918
|
+
value
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
944
2921
|
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
945
2922
|
},
|
|
946
|
-
[
|
|
2923
|
+
[
|
|
2924
|
+
assetBundle.pose?.config?.faceId,
|
|
2925
|
+
faceId,
|
|
2926
|
+
markActivity,
|
|
2927
|
+
poseWeightFallbackMap,
|
|
2928
|
+
useLegacyPoseWeightFallback
|
|
2929
|
+
]
|
|
947
2930
|
);
|
|
948
2931
|
const reportStatus = useCallback(
|
|
949
2932
|
(updater) => {
|
|
@@ -953,32 +2936,262 @@ function VizijRuntimeProviderInner({
|
|
|
953
2936
|
return next;
|
|
954
2937
|
});
|
|
955
2938
|
},
|
|
956
|
-
[onStatusChange]
|
|
2939
|
+
[onStatusChange]
|
|
2940
|
+
);
|
|
2941
|
+
const pushError = useCallback(
|
|
2942
|
+
(error) => {
|
|
2943
|
+
errorsRef.current = [...errorsRef.current, error];
|
|
2944
|
+
reportStatus((prev) => ({
|
|
2945
|
+
...prev,
|
|
2946
|
+
error,
|
|
2947
|
+
errors: errorsRef.current
|
|
2948
|
+
}));
|
|
2949
|
+
console.warn("[vizij-runtime]", error.message, error.cause);
|
|
2950
|
+
},
|
|
2951
|
+
[reportStatus]
|
|
2952
|
+
);
|
|
2953
|
+
const resetErrors = useCallback(() => {
|
|
2954
|
+
errorsRef.current = [];
|
|
2955
|
+
reportStatus((prev) => ({
|
|
2956
|
+
...prev,
|
|
2957
|
+
error: null,
|
|
2958
|
+
errors: []
|
|
2959
|
+
}));
|
|
2960
|
+
}, [reportStatus]);
|
|
2961
|
+
useEffect(() => {
|
|
2962
|
+
autostartRef.current = autostart;
|
|
2963
|
+
updateLoopMode();
|
|
2964
|
+
}, [autostart, updateLoopMode]);
|
|
2965
|
+
const pendingWritesRef = useRef(/* @__PURE__ */ new Map());
|
|
2966
|
+
const getDevice = useCallback(
|
|
2967
|
+
() => deviceSlot.current?.device ?? null,
|
|
2968
|
+
[deviceSlot]
|
|
2969
|
+
);
|
|
2970
|
+
const ensureAnimationHost = useCallback(() => {
|
|
2971
|
+
if (!animationModuleRef.current) {
|
|
2972
|
+
return null;
|
|
2973
|
+
}
|
|
2974
|
+
if (!animationHostRef.current) {
|
|
2975
|
+
animationHostRef.current = new AnimationModuleHost(
|
|
2976
|
+
getDevice,
|
|
2977
|
+
(key) => resolveAnimationBridgeOutputPaths(
|
|
2978
|
+
key,
|
|
2979
|
+
faceIdRef.current ?? void 0,
|
|
2980
|
+
rigInputMapRef.current
|
|
2981
|
+
)
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
return animationHostRef.current;
|
|
2985
|
+
}, [getDevice]);
|
|
2986
|
+
const applyEngineChangesRef = useRef(() => {
|
|
2987
|
+
});
|
|
2988
|
+
const recomposeDevice = useCallback(() => {
|
|
2989
|
+
const spec = composeGraphSpecs(graphSourcesRef.current);
|
|
2990
|
+
deviceSlot.recompose(spec).then((handle) => {
|
|
2991
|
+
if (pendingWritesRef.current.size > 0) {
|
|
2992
|
+
handle.device.writeValues(
|
|
2993
|
+
Object.fromEntries(pendingWritesRef.current)
|
|
2994
|
+
);
|
|
2995
|
+
pendingWritesRef.current.clear();
|
|
2996
|
+
}
|
|
2997
|
+
}).catch((err) => {
|
|
2998
|
+
pushError({
|
|
2999
|
+
message: "Failed to (re)compose the arora device",
|
|
3000
|
+
cause: err,
|
|
3001
|
+
phase: "engine",
|
|
3002
|
+
timestamp: performance.now()
|
|
3003
|
+
});
|
|
3004
|
+
});
|
|
3005
|
+
}, [deviceSlot, pushError]);
|
|
3006
|
+
const initEngine = useCallback(async () => {
|
|
3007
|
+
await ensureWasmInit();
|
|
3008
|
+
setReady(true);
|
|
3009
|
+
}, []);
|
|
3010
|
+
const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
|
|
3011
|
+
useEffect(() => {
|
|
3012
|
+
if (!hasBundleAnimations || animationModuleRef.current) {
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
let cancelled = false;
|
|
3016
|
+
const loading = loadAnimationModule().then((module) => {
|
|
3017
|
+
if (cancelled) {
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
animationModuleRef.current = module;
|
|
3021
|
+
deviceSlot.setModules([module]);
|
|
3022
|
+
deviceSlot.onDeviceStarted = (handle) => {
|
|
3023
|
+
animationHostRef.current?.replayInto(handle.device);
|
|
3024
|
+
};
|
|
3025
|
+
ensureAnimationHost();
|
|
3026
|
+
}).catch((err) => {
|
|
3027
|
+
pushError({
|
|
3028
|
+
message: "Failed to load the animation module",
|
|
3029
|
+
cause: err,
|
|
3030
|
+
phase: "engine",
|
|
3031
|
+
timestamp: performance.now()
|
|
3032
|
+
});
|
|
3033
|
+
});
|
|
3034
|
+
deviceSlot.waitForModules(loading);
|
|
3035
|
+
return () => {
|
|
3036
|
+
cancelled = true;
|
|
3037
|
+
};
|
|
3038
|
+
}, [deviceSlot, ensureAnimationHost, hasBundleAnimations, pushError]);
|
|
3039
|
+
const removeGraph = useCallback(
|
|
3040
|
+
(id) => {
|
|
3041
|
+
graphSourcesRef.current = graphSourcesRef.current.filter(
|
|
3042
|
+
(s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
|
|
3043
|
+
);
|
|
3044
|
+
recomposeDevice();
|
|
3045
|
+
},
|
|
3046
|
+
[recomposeDevice]
|
|
3047
|
+
);
|
|
3048
|
+
const registerGraph = useCallback(
|
|
3049
|
+
(cfg) => {
|
|
3050
|
+
const id = cfg.id ?? `graph-${graphSourcesRef.current.length}`;
|
|
3051
|
+
graphSourcesRef.current = [
|
|
3052
|
+
...graphSourcesRef.current.filter((s) => s.sourceId !== id),
|
|
3053
|
+
{ sourceId: id, spec: cfg.spec ?? {} }
|
|
3054
|
+
];
|
|
3055
|
+
recomposeDevice();
|
|
3056
|
+
return id;
|
|
3057
|
+
},
|
|
3058
|
+
[recomposeDevice]
|
|
3059
|
+
);
|
|
3060
|
+
const registerMergedGraph = useCallback(
|
|
3061
|
+
(cfg) => {
|
|
3062
|
+
const id = cfg.id ?? `merged-${graphSourcesRef.current.length}`;
|
|
3063
|
+
const members = cfg.graphs.map((graph, index) => ({
|
|
3064
|
+
sourceId: `${id}#${graph.id ?? index}`,
|
|
3065
|
+
spec: graph.spec ?? {}
|
|
3066
|
+
}));
|
|
3067
|
+
graphSourcesRef.current = [
|
|
3068
|
+
...graphSourcesRef.current.filter(
|
|
3069
|
+
(s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
|
|
3070
|
+
),
|
|
3071
|
+
...members
|
|
3072
|
+
];
|
|
3073
|
+
recomposeDevice();
|
|
3074
|
+
return id;
|
|
3075
|
+
},
|
|
3076
|
+
[recomposeDevice]
|
|
3077
|
+
);
|
|
3078
|
+
const registerAnimation = useCallback(
|
|
3079
|
+
(cfg) => cfg.id ?? `animation-${registeredAnimationsRef.current.length}`,
|
|
3080
|
+
[]
|
|
3081
|
+
);
|
|
3082
|
+
const removeAnimation = useCallback((_id) => {
|
|
3083
|
+
}, []);
|
|
3084
|
+
const setAnimationsSourceRegistered = useCallback(
|
|
3085
|
+
(registered) => {
|
|
3086
|
+
if (registered === animationsSourceRegisteredRef.current) {
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
animationsSourceRegisteredRef.current = registered;
|
|
3090
|
+
if (registered) {
|
|
3091
|
+
graphSourcesRef.current = [
|
|
3092
|
+
...graphSourcesRef.current.filter(
|
|
3093
|
+
(s) => s.sourceId !== animationsGraphSource().sourceId
|
|
3094
|
+
),
|
|
3095
|
+
animationsGraphSource()
|
|
3096
|
+
];
|
|
3097
|
+
} else {
|
|
3098
|
+
graphSourcesRef.current = graphSourcesRef.current.filter(
|
|
3099
|
+
(s) => s.sourceId !== animationsGraphSource().sourceId
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
recomposeDevice();
|
|
3103
|
+
},
|
|
3104
|
+
[recomposeDevice]
|
|
3105
|
+
);
|
|
3106
|
+
const listControllers = useCallback(
|
|
3107
|
+
() => ({
|
|
3108
|
+
graphs: [...registeredGraphsRef.current],
|
|
3109
|
+
anims: [...registeredAnimationsRef.current]
|
|
3110
|
+
}),
|
|
3111
|
+
[]
|
|
3112
|
+
);
|
|
3113
|
+
const deviceSetInput = useCallback(
|
|
3114
|
+
(path, value, _shape) => {
|
|
3115
|
+
const handle = deviceSlot.current;
|
|
3116
|
+
if (handle) {
|
|
3117
|
+
handle.device.setValue(path, value);
|
|
3118
|
+
} else {
|
|
3119
|
+
pendingWritesRef.current.set(path, value);
|
|
3120
|
+
}
|
|
3121
|
+
},
|
|
3122
|
+
[deviceSlot]
|
|
3123
|
+
);
|
|
3124
|
+
const getPathSnapshot = useCallback(
|
|
3125
|
+
(path) => {
|
|
3126
|
+
const handle = deviceSlot.current;
|
|
3127
|
+
if (!handle) {
|
|
3128
|
+
return pendingWritesRef.current.get(path);
|
|
3129
|
+
}
|
|
3130
|
+
return handle.device.readValues([path])[path] ?? void 0;
|
|
3131
|
+
},
|
|
3132
|
+
[deviceSlot]
|
|
3133
|
+
);
|
|
3134
|
+
const getStoreSnapshot = useCallback(() => {
|
|
3135
|
+
const handle = deviceSlot.current;
|
|
3136
|
+
if (!handle) {
|
|
3137
|
+
return void 0;
|
|
3138
|
+
}
|
|
3139
|
+
return handle.device.snapshot();
|
|
3140
|
+
}, [deviceSlot]);
|
|
3141
|
+
const stepListenersRef = useRef(/* @__PURE__ */ new Set());
|
|
3142
|
+
const subscribeToStep = useCallback((listener) => {
|
|
3143
|
+
const listeners = stepListenersRef.current;
|
|
3144
|
+
listeners.add(listener);
|
|
3145
|
+
return () => {
|
|
3146
|
+
listeners.delete(listener);
|
|
3147
|
+
};
|
|
3148
|
+
}, []);
|
|
3149
|
+
const stepRuntime = useCallback(
|
|
3150
|
+
(dt) => {
|
|
3151
|
+
const handle = deviceSlot.current;
|
|
3152
|
+
if (!handle) {
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
if (!handle.device.running) {
|
|
3156
|
+
handle.device.step(dt * 1e3);
|
|
3157
|
+
}
|
|
3158
|
+
const changes = handle.device.drainChanges();
|
|
3159
|
+
applyEngineChangesRef.current(changes);
|
|
3160
|
+
if (storeChangeListenersRef.current.size > 0) {
|
|
3161
|
+
const paths = Object.keys(changes);
|
|
3162
|
+
if (paths.length > 0) {
|
|
3163
|
+
storeChangeListenersRef.current.forEach((listener) => {
|
|
3164
|
+
try {
|
|
3165
|
+
listener(changes);
|
|
3166
|
+
} catch (err) {
|
|
3167
|
+
console.error("[vizij-runtime] store-change listener error", err);
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
stepListenersRef.current.forEach((listener) => {
|
|
3173
|
+
try {
|
|
3174
|
+
listener();
|
|
3175
|
+
} catch (err) {
|
|
3176
|
+
console.error("[vizij-runtime] step listener error", err);
|
|
3177
|
+
}
|
|
3178
|
+
});
|
|
3179
|
+
},
|
|
3180
|
+
[deviceSlot]
|
|
957
3181
|
);
|
|
958
|
-
const
|
|
959
|
-
(
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
3182
|
+
const storeChangeListenersRef = useRef(
|
|
3183
|
+
/* @__PURE__ */ new Set()
|
|
3184
|
+
);
|
|
3185
|
+
const subscribeToStoreChanges = useCallback(
|
|
3186
|
+
(listener) => {
|
|
3187
|
+
const listeners = storeChangeListenersRef.current;
|
|
3188
|
+
listeners.add(listener);
|
|
3189
|
+
return () => {
|
|
3190
|
+
listeners.delete(listener);
|
|
3191
|
+
};
|
|
967
3192
|
},
|
|
968
|
-
[
|
|
3193
|
+
[]
|
|
969
3194
|
);
|
|
970
|
-
const resetErrors = useCallback(() => {
|
|
971
|
-
errorsRef.current = [];
|
|
972
|
-
reportStatus((prev) => ({
|
|
973
|
-
...prev,
|
|
974
|
-
error: null,
|
|
975
|
-
errors: []
|
|
976
|
-
}));
|
|
977
|
-
}, [reportStatus]);
|
|
978
|
-
useEffect(() => {
|
|
979
|
-
autostartRef.current = autostart;
|
|
980
|
-
updateLoopMode();
|
|
981
|
-
}, [autostart, updateLoopMode]);
|
|
982
3195
|
const clearControllers = useCallback(() => {
|
|
983
3196
|
const existing = listControllers();
|
|
984
3197
|
existing.graphs.forEach((id) => {
|
|
@@ -1007,10 +3220,12 @@ function VizijRuntimeProviderInner({
|
|
|
1007
3220
|
});
|
|
1008
3221
|
registeredGraphsRef.current = [];
|
|
1009
3222
|
registeredAnimationsRef.current = [];
|
|
3223
|
+
programControllerIdsRef.current.clear();
|
|
1010
3224
|
mergedGraphRef.current = null;
|
|
1011
3225
|
outputPathsRef.current = /* @__PURE__ */ new Set();
|
|
1012
3226
|
baseOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
1013
3227
|
namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
3228
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
1014
3229
|
}, [listControllers, removeAnimation, removeGraph, pushError]);
|
|
1015
3230
|
useEffect(() => {
|
|
1016
3231
|
namespaceRef.current = namespace;
|
|
@@ -1021,12 +3236,37 @@ function VizijRuntimeProviderInner({
|
|
|
1021
3236
|
}));
|
|
1022
3237
|
}, [namespace, faceId, reportStatus]);
|
|
1023
3238
|
useEffect(() => {
|
|
1024
|
-
|
|
1025
|
-
}, [
|
|
1026
|
-
|
|
1027
|
-
|
|
3239
|
+
driveRuntimeRef.current = driveRuntime;
|
|
3240
|
+
}, [driveRuntime]);
|
|
3241
|
+
useEffect(() => {
|
|
3242
|
+
deviceSlot.onRunEnded = (error) => {
|
|
3243
|
+
pushError({
|
|
3244
|
+
message: "The device's run loop ended: stepping failed",
|
|
3245
|
+
cause: error,
|
|
3246
|
+
phase: "engine",
|
|
3247
|
+
timestamp: performance.now()
|
|
3248
|
+
});
|
|
3249
|
+
};
|
|
3250
|
+
if (driveRuntime) {
|
|
3251
|
+
deviceSlot.runPeriodMs = RUN_PERIOD_MS;
|
|
3252
|
+
deviceSlot.startRun();
|
|
3253
|
+
} else {
|
|
3254
|
+
deviceSlot.runPeriodMs = null;
|
|
3255
|
+
}
|
|
3256
|
+
}, [deviceSlot, driveRuntime, pushError]);
|
|
3257
|
+
const glbAsset = effectiveAssetBundle.glb;
|
|
3258
|
+
const baseBundle = effectiveAssetBundle.bundle ?? null;
|
|
1028
3259
|
useEffect(() => {
|
|
1029
3260
|
let cancelled = false;
|
|
3261
|
+
const plan = pendingPlanRef.current;
|
|
3262
|
+
if (plan && !plan.reloadAssets && status.rootId !== null) {
|
|
3263
|
+
reportStatus(
|
|
3264
|
+
(prev) => prev.loading ? { ...prev, loading: false } : prev
|
|
3265
|
+
);
|
|
3266
|
+
return () => {
|
|
3267
|
+
cancelled = true;
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
1030
3270
|
resetErrors();
|
|
1031
3271
|
reportStatus((prev) => ({
|
|
1032
3272
|
...prev,
|
|
@@ -1078,6 +3318,12 @@ function VizijRuntimeProviderInner({
|
|
|
1078
3318
|
setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
|
|
1079
3319
|
const rootId = findRootId(world);
|
|
1080
3320
|
store.getState().addWorldElements(world, animatables, true);
|
|
3321
|
+
if (pendingPlanRef.current?.reloadAssets) {
|
|
3322
|
+
pendingPlanRef.current = {
|
|
3323
|
+
...pendingPlanRef.current,
|
|
3324
|
+
reloadAssets: false
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
1081
3327
|
reportStatus((prev) => ({
|
|
1082
3328
|
...prev,
|
|
1083
3329
|
loading: false,
|
|
@@ -1115,22 +3361,32 @@ function VizijRuntimeProviderInner({
|
|
|
1115
3361
|
reportStatus,
|
|
1116
3362
|
resetErrors,
|
|
1117
3363
|
setExtractedBundle,
|
|
1118
|
-
setExtractedAnimations
|
|
3364
|
+
setExtractedAnimations,
|
|
3365
|
+
status.rootId
|
|
1119
3366
|
]);
|
|
1120
3367
|
useEffect(() => {
|
|
1121
3368
|
if (!ready && autoCreate) {
|
|
1122
|
-
|
|
3369
|
+
initEngine().catch((err) => {
|
|
1123
3370
|
pushError({
|
|
1124
|
-
message: "Failed to
|
|
3371
|
+
message: "Failed to initialize the engine",
|
|
1125
3372
|
cause: err,
|
|
1126
|
-
phase: "
|
|
3373
|
+
phase: "engine",
|
|
1127
3374
|
timestamp: performance.now()
|
|
1128
3375
|
});
|
|
1129
3376
|
});
|
|
1130
3377
|
}
|
|
1131
|
-
}, [ready, autoCreate,
|
|
3378
|
+
}, [ready, autoCreate, initEngine, pushError]);
|
|
1132
3379
|
const registerControllers = useCallback(async () => {
|
|
1133
3380
|
clearControllers();
|
|
3381
|
+
if (isRuntimeDebugEnabled()) {
|
|
3382
|
+
console.log("[vizij-runtime] registerControllers", {
|
|
3383
|
+
hasRig: Boolean(assetBundle.rig),
|
|
3384
|
+
hasPose: Boolean(assetBundle.pose?.graph),
|
|
3385
|
+
animationCount: assetBundle.animations?.length ?? 0,
|
|
3386
|
+
animationIds: (assetBundle.animations ?? []).map((anim) => anim.id),
|
|
3387
|
+
namespace
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
1134
3390
|
const baseOutputPaths = /* @__PURE__ */ new Set();
|
|
1135
3391
|
const namespacedOutputPaths = /* @__PURE__ */ new Set();
|
|
1136
3392
|
const recordOutputs = (paths) => {
|
|
@@ -1142,39 +3398,60 @@ function VizijRuntimeProviderInner({
|
|
|
1142
3398
|
namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
|
|
1143
3399
|
});
|
|
1144
3400
|
};
|
|
3401
|
+
const graphConfigs = [];
|
|
3402
|
+
rigInputMapRef.current = {};
|
|
3403
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
3404
|
+
poseControlBridgeValuesRef.current.clear();
|
|
1145
3405
|
const rigAsset = assetBundle.rig;
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
3406
|
+
if (rigAsset) {
|
|
3407
|
+
const rigSpec = resolveGraphSpec(
|
|
3408
|
+
rigAsset,
|
|
3409
|
+
`${rigAsset.id ?? "rig"} graph`
|
|
3410
|
+
);
|
|
3411
|
+
if (!rigSpec) {
|
|
3412
|
+
pushError({
|
|
3413
|
+
message: "Rig graph is missing a usable spec or IR payload.",
|
|
3414
|
+
phase: "registration",
|
|
3415
|
+
timestamp: performance.now()
|
|
3416
|
+
});
|
|
3417
|
+
} else {
|
|
3418
|
+
const rigOutputs = collectOutputPaths(rigSpec);
|
|
3419
|
+
const rigInputs = collectInputPaths(rigSpec);
|
|
3420
|
+
const rigPoseControlInputIds = /* @__PURE__ */ new Set();
|
|
3421
|
+
rigInputs.forEach((path) => {
|
|
3422
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
3423
|
+
path.trim()
|
|
3424
|
+
);
|
|
3425
|
+
const inputId = (poseControlMatch?.[1] ?? "").trim();
|
|
3426
|
+
if (inputId.length > 0) {
|
|
3427
|
+
rigPoseControlInputIds.add(inputId);
|
|
3428
|
+
}
|
|
3429
|
+
});
|
|
3430
|
+
rigInputMapRef.current = collectInputPathMap(rigSpec);
|
|
3431
|
+
rigPoseControlInputIdsRef.current = rigPoseControlInputIds;
|
|
3432
|
+
if (isRuntimeDebugEnabled()) {
|
|
3433
|
+
const blinkKeys = Object.keys(rigInputMapRef.current).filter(
|
|
3434
|
+
(key) => key.toLowerCase().includes("blink")
|
|
3435
|
+
);
|
|
3436
|
+
const blinkMappings = blinkKeys.slice(0, 20).map((key) => `${key} => ${rigInputMapRef.current[key] ?? "?"}`);
|
|
3437
|
+
console.log("[vizij-runtime] rig input map sample", {
|
|
3438
|
+
blink: rigInputMapRef.current["blink"] ?? null,
|
|
3439
|
+
blinkKeys: blinkKeys.slice(0, 12),
|
|
3440
|
+
blinkMappings: blinkMappings.join(" | ")
|
|
3441
|
+
});
|
|
3442
|
+
}
|
|
3443
|
+
recordOutputs(rigOutputs);
|
|
3444
|
+
const rigSubs = rigAsset.subscriptions ?? {
|
|
3445
|
+
inputs: rigInputs,
|
|
3446
|
+
outputs: rigOutputs
|
|
3447
|
+
};
|
|
3448
|
+
graphConfigs.push({
|
|
3449
|
+
id: namespaceControllerId(rigAsset.id, namespace, "graph"),
|
|
3450
|
+
spec: stripNulls(namespaceGraphSpec(rigSpec, namespace)),
|
|
3451
|
+
subs: namespaceSubscriptions(rigSubs, namespace)
|
|
3452
|
+
});
|
|
1176
3453
|
}
|
|
1177
|
-
|
|
3454
|
+
}
|
|
1178
3455
|
const poseGraphAsset = assetBundle.pose?.graph;
|
|
1179
3456
|
if (poseGraphAsset) {
|
|
1180
3457
|
const poseSpec = resolveGraphSpec(
|
|
@@ -1191,7 +3468,7 @@ function VizijRuntimeProviderInner({
|
|
|
1191
3468
|
};
|
|
1192
3469
|
graphConfigs.push({
|
|
1193
3470
|
id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
|
|
1194
|
-
spec: namespaceGraphSpec(poseSpec, namespace),
|
|
3471
|
+
spec: stripNulls(namespaceGraphSpec(poseSpec, namespace)),
|
|
1195
3472
|
subs: namespaceSubscriptions(poseSubs, namespace)
|
|
1196
3473
|
});
|
|
1197
3474
|
} else {
|
|
@@ -1200,6 +3477,31 @@ function VizijRuntimeProviderInner({
|
|
|
1200
3477
|
);
|
|
1201
3478
|
}
|
|
1202
3479
|
}
|
|
3480
|
+
for (const animation of assetBundle.animations ?? []) {
|
|
3481
|
+
const bridgeOutputs = collectAnimationClipOutputPaths(
|
|
3482
|
+
animation.clip,
|
|
3483
|
+
faceId ?? void 0,
|
|
3484
|
+
rigInputMapRef.current
|
|
3485
|
+
);
|
|
3486
|
+
if (isRuntimeDebugEnabled()) {
|
|
3487
|
+
console.log("[vizij-runtime] animation output routing", {
|
|
3488
|
+
animationId: animation.id,
|
|
3489
|
+
bridgeOutputs,
|
|
3490
|
+
bridgeOutputsText: bridgeOutputs.join(" | ")
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
recordOutputs(bridgeOutputs);
|
|
3494
|
+
}
|
|
3495
|
+
for (const program of resolvedProgramAssets) {
|
|
3496
|
+
const programSpec = resolveGraphSpec(
|
|
3497
|
+
program.graph,
|
|
3498
|
+
`${program.id ?? "program"} graph (outputs)`
|
|
3499
|
+
);
|
|
3500
|
+
if (!programSpec) {
|
|
3501
|
+
continue;
|
|
3502
|
+
}
|
|
3503
|
+
recordOutputs(collectOutputPaths(programSpec));
|
|
3504
|
+
}
|
|
1203
3505
|
outputPathsRef.current = namespacedOutputPaths;
|
|
1204
3506
|
baseOutputPathsRef.current = baseOutputPaths;
|
|
1205
3507
|
namespacedOutputPathsRef.current = namespacedOutputPaths;
|
|
@@ -1232,20 +3534,24 @@ function VizijRuntimeProviderInner({
|
|
|
1232
3534
|
});
|
|
1233
3535
|
}
|
|
1234
3536
|
registeredGraphsRef.current = graphIds;
|
|
3537
|
+
if (isRuntimeDebugEnabled()) {
|
|
3538
|
+
console.log("[vizij-runtime] registered graph ids", graphIds);
|
|
3539
|
+
}
|
|
1235
3540
|
const animationIds = [];
|
|
3541
|
+
const hostClips = [];
|
|
1236
3542
|
for (const anim of assetBundle.animations ?? []) {
|
|
1237
3543
|
try {
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
animation: anim.clip,
|
|
1243
|
-
...anim.setup ?? {}
|
|
1244
|
-
}
|
|
1245
|
-
};
|
|
1246
|
-
const id = registerAnimation(config);
|
|
1247
|
-
animationIds.push(id);
|
|
3544
|
+
const stored = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
|
|
3545
|
+
hostClips.push({ id: anim.id, stored });
|
|
3546
|
+
registerAnimation({ id: anim.id });
|
|
3547
|
+
animationIds.push(anim.id);
|
|
1248
3548
|
} catch (err) {
|
|
3549
|
+
if (isRuntimeDebugEnabled()) {
|
|
3550
|
+
console.warn("[vizij-runtime] failed animation registration", {
|
|
3551
|
+
animationId: anim.id,
|
|
3552
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
1249
3555
|
pushError({
|
|
1250
3556
|
message: `Failed to register animation ${anim.id}`,
|
|
1251
3557
|
cause: err,
|
|
@@ -1254,6 +3560,7 @@ function VizijRuntimeProviderInner({
|
|
|
1254
3560
|
});
|
|
1255
3561
|
}
|
|
1256
3562
|
}
|
|
3563
|
+
ensureAnimationHost()?.setClips(hostClips);
|
|
1257
3564
|
registeredAnimationsRef.current = animationIds;
|
|
1258
3565
|
if (assetBundle.initialInputs) {
|
|
1259
3566
|
Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
|
|
@@ -1270,6 +3577,13 @@ function VizijRuntimeProviderInner({
|
|
|
1270
3577
|
});
|
|
1271
3578
|
}
|
|
1272
3579
|
const controllers = listControllers();
|
|
3580
|
+
if (isRuntimeDebugEnabled()) {
|
|
3581
|
+
console.log("[vizij-runtime] controllers after register", {
|
|
3582
|
+
controllers,
|
|
3583
|
+
graphIds,
|
|
3584
|
+
animationIds
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
1273
3587
|
reportStatus((prev) => ({
|
|
1274
3588
|
...prev,
|
|
1275
3589
|
ready: true,
|
|
@@ -1280,6 +3594,7 @@ function VizijRuntimeProviderInner({
|
|
|
1280
3594
|
}, [
|
|
1281
3595
|
assetBundle,
|
|
1282
3596
|
clearControllers,
|
|
3597
|
+
ensureAnimationHost,
|
|
1283
3598
|
listControllers,
|
|
1284
3599
|
mergeStrategy,
|
|
1285
3600
|
namespace,
|
|
@@ -1289,12 +3604,18 @@ function VizijRuntimeProviderInner({
|
|
|
1289
3604
|
registerGraph,
|
|
1290
3605
|
registerMergedGraph,
|
|
1291
3606
|
reportStatus,
|
|
3607
|
+
resolvedProgramAssets,
|
|
1292
3608
|
setInput
|
|
1293
3609
|
]);
|
|
1294
3610
|
useEffect(() => {
|
|
1295
3611
|
if (!ready || status.loading) {
|
|
1296
3612
|
return;
|
|
1297
3613
|
}
|
|
3614
|
+
const plan = pendingPlanRef.current;
|
|
3615
|
+
const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
|
|
3616
|
+
if (plan && !plan.reregisterGraphs && hasRegistered) {
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
1298
3619
|
registerControllers().catch((err) => {
|
|
1299
3620
|
pushError({
|
|
1300
3621
|
message: "Failed to register controllers",
|
|
@@ -1303,37 +3624,79 @@ function VizijRuntimeProviderInner({
|
|
|
1303
3624
|
timestamp: performance.now()
|
|
1304
3625
|
});
|
|
1305
3626
|
});
|
|
1306
|
-
}, [ready, status.loading, registerControllers, pushError]);
|
|
3627
|
+
}, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
|
|
1307
3628
|
useEffect(() => {
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
const writes = frame.merged_writes ?? [];
|
|
1312
|
-
if (!writes.length) {
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
const setWorldValues = store.getState().setValues;
|
|
1316
|
-
const namespaceValue = status.namespace;
|
|
1317
|
-
const batched = [];
|
|
1318
|
-
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
1319
|
-
const baseOutputs = baseOutputPathsRef.current;
|
|
1320
|
-
writes.forEach((write) => {
|
|
1321
|
-
const path = normalisePath(write.path);
|
|
1322
|
-
if (!namespacedOutputs.has(path)) {
|
|
3629
|
+
applyEngineChangesRef.current = (changes) => {
|
|
3630
|
+
const entries = Object.entries(changes);
|
|
3631
|
+
if (!entries.length) {
|
|
1323
3632
|
return;
|
|
1324
3633
|
}
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
3634
|
+
const setWorldValues = store.getState().setValues;
|
|
3635
|
+
const namespaceValue = status.namespace;
|
|
3636
|
+
const currentValues = store.getState().values;
|
|
3637
|
+
const rigInputPathMap = rigInputMapRef.current;
|
|
3638
|
+
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
3639
|
+
const batched = [];
|
|
3640
|
+
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
3641
|
+
const baseOutputs = baseOutputPathsRef.current;
|
|
3642
|
+
entries.forEach(([changedPath, changedValue]) => {
|
|
3643
|
+
if (changedValue === null || isBuiltInPath(changedPath)) {
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
const path = normalisePath(changedPath);
|
|
3647
|
+
const basePath = stripNamespace(path, namespaceValue);
|
|
3648
|
+
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
3649
|
+
if (!isTrackedOutput) {
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
const raw = valueJSONToRaw(changedValue);
|
|
3653
|
+
if (raw === void 0) {
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
3657
|
+
basePath
|
|
3658
|
+
);
|
|
3659
|
+
if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
|
|
3660
|
+
const inputId = (poseControlMatch[1] ?? "").trim();
|
|
3661
|
+
const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
|
|
3662
|
+
const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
|
|
3663
|
+
inputId,
|
|
3664
|
+
basePath,
|
|
3665
|
+
rigInputPathMap,
|
|
3666
|
+
hasNativePoseControlInput
|
|
3667
|
+
});
|
|
3668
|
+
if (mappedInputPath) {
|
|
3669
|
+
const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
|
|
3670
|
+
const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
|
|
3671
|
+
if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
|
|
3672
|
+
poseControlBridgeValuesRef.current.set(bridgeKey, raw);
|
|
3673
|
+
setInput(mappedInputPath, { float: raw });
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
const targetPath = baseOutputs.has(basePath) ? basePath : path;
|
|
3678
|
+
const currentValue = currentValues.get(
|
|
3679
|
+
getLookup(namespaceValue, targetPath)
|
|
3680
|
+
);
|
|
3681
|
+
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
3682
|
+
id: targetPath,
|
|
3683
|
+
namespace: namespaceValue,
|
|
3684
|
+
value: raw,
|
|
3685
|
+
currentValue
|
|
3686
|
+
}) : {
|
|
3687
|
+
id: targetPath,
|
|
3688
|
+
namespace: namespaceValue,
|
|
3689
|
+
value: raw
|
|
3690
|
+
};
|
|
3691
|
+
if (nextWrite) {
|
|
3692
|
+
batched.push(nextWrite);
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
if (batched.length > 0) {
|
|
3696
|
+
setWorldValues(batched);
|
|
1328
3697
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
batched.push({ id: targetPath, namespace: namespaceValue, value: raw });
|
|
1332
|
-
});
|
|
1333
|
-
if (batched.length > 0) {
|
|
1334
|
-
setWorldValues(batched);
|
|
1335
|
-
}
|
|
1336
|
-
}, [frame, status.namespace, store]);
|
|
3698
|
+
};
|
|
3699
|
+
}, [status.namespace, store, transformOutputWrite]);
|
|
1337
3700
|
const stagePoseNeutral = useCallback(
|
|
1338
3701
|
(force = false) => {
|
|
1339
3702
|
const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
|
|
@@ -1399,165 +3762,583 @@ function VizijRuntimeProviderInner({
|
|
|
1399
3762
|
});
|
|
1400
3763
|
toDelete.forEach((key) => map.delete(key));
|
|
1401
3764
|
},
|
|
1402
|
-
[setInput]
|
|
3765
|
+
[setInput]
|
|
3766
|
+
);
|
|
3767
|
+
const resolveClipById = useCallback(
|
|
3768
|
+
(id) => {
|
|
3769
|
+
return assetBundle.animations?.find((anim) => anim.id === id);
|
|
3770
|
+
},
|
|
3771
|
+
[assetBundle.animations]
|
|
3772
|
+
);
|
|
3773
|
+
const resolveClipPromise = useCallback((state) => {
|
|
3774
|
+
state.resolve?.();
|
|
3775
|
+
state.resolve = null;
|
|
3776
|
+
state.completion = null;
|
|
3777
|
+
}, []);
|
|
3778
|
+
const ensureClipPromise = useCallback((state) => {
|
|
3779
|
+
if (state.completion) {
|
|
3780
|
+
return state.completion;
|
|
3781
|
+
}
|
|
3782
|
+
const completion = new Promise((resolve) => {
|
|
3783
|
+
state.resolve = resolve;
|
|
3784
|
+
});
|
|
3785
|
+
state.completion = completion;
|
|
3786
|
+
return completion;
|
|
3787
|
+
}, []);
|
|
3788
|
+
const ensureClipState = useCallback(
|
|
3789
|
+
(id) => {
|
|
3790
|
+
const clip = resolveClipById(id);
|
|
3791
|
+
if (!clip) {
|
|
3792
|
+
return null;
|
|
3793
|
+
}
|
|
3794
|
+
const duration = resolveClipDurationSeconds(
|
|
3795
|
+
clip.clip
|
|
3796
|
+
);
|
|
3797
|
+
const existing = clipPlaybackRef.current.get(id);
|
|
3798
|
+
if (existing) {
|
|
3799
|
+
existing.duration = duration;
|
|
3800
|
+
return { clip, state: existing };
|
|
3801
|
+
}
|
|
3802
|
+
const state = {
|
|
3803
|
+
id,
|
|
3804
|
+
duration,
|
|
3805
|
+
speed: 1,
|
|
3806
|
+
weight: 1,
|
|
3807
|
+
loop: true,
|
|
3808
|
+
playing: false,
|
|
3809
|
+
resolve: null,
|
|
3810
|
+
completion: null
|
|
3811
|
+
};
|
|
3812
|
+
clipPlaybackRef.current.set(id, state);
|
|
3813
|
+
return { clip, state };
|
|
3814
|
+
},
|
|
3815
|
+
[resolveClipById]
|
|
3816
|
+
);
|
|
3817
|
+
const animateValue = useCallback(
|
|
3818
|
+
(path, target, options) => {
|
|
3819
|
+
const targetValue = valueAsNumber2(target);
|
|
3820
|
+
const basePath = stripNamespace(
|
|
3821
|
+
normalisePath(path),
|
|
3822
|
+
namespaceRef.current
|
|
3823
|
+
);
|
|
3824
|
+
const poseValues = useLegacyPoseWeightFallback && targetValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
|
|
3825
|
+
if (poseValues && targetValue != null) {
|
|
3826
|
+
const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
|
|
3827
|
+
const rigMap = rigInputMapRef.current;
|
|
3828
|
+
return Promise.all(
|
|
3829
|
+
Object.entries(poseValues).flatMap(([inputId, poseValue]) => {
|
|
3830
|
+
if (!Number.isFinite(poseValue)) {
|
|
3831
|
+
return [];
|
|
3832
|
+
}
|
|
3833
|
+
const controlPath = resolvePoseControlInputPath({
|
|
3834
|
+
inputId,
|
|
3835
|
+
basePath: buildRigInputPath(
|
|
3836
|
+
poseFaceId,
|
|
3837
|
+
`/pose/control/${inputId}`
|
|
3838
|
+
),
|
|
3839
|
+
rigInputPathMap: rigMap,
|
|
3840
|
+
hasNativePoseControlInput: true
|
|
3841
|
+
}) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
|
|
3842
|
+
return [
|
|
3843
|
+
animateValue(
|
|
3844
|
+
controlPath,
|
|
3845
|
+
{ float: Number(poseValue) * targetValue },
|
|
3846
|
+
options
|
|
3847
|
+
)
|
|
3848
|
+
];
|
|
3849
|
+
})
|
|
3850
|
+
).then(() => void 0);
|
|
3851
|
+
}
|
|
3852
|
+
const easing = resolveEasing(options?.easing);
|
|
3853
|
+
const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
|
|
3854
|
+
cancelAnimation(path);
|
|
3855
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
3856
|
+
const current = getPathSnapshot(namespacedPath);
|
|
3857
|
+
const fromValue = valueAsNumber2(current);
|
|
3858
|
+
const toValue = valueAsNumber2(target);
|
|
3859
|
+
if (fromValue == null || toValue == null || duration === 0) {
|
|
3860
|
+
setInput(path, target);
|
|
3861
|
+
return Promise.resolve();
|
|
3862
|
+
}
|
|
3863
|
+
return new Promise((resolve) => {
|
|
3864
|
+
animationTweensRef.current.set(path, {
|
|
3865
|
+
// Keep the raw path here so tween updates go through setInput() once
|
|
3866
|
+
// and pick up the active namespace exactly once.
|
|
3867
|
+
path,
|
|
3868
|
+
from: fromValue,
|
|
3869
|
+
to: toValue,
|
|
3870
|
+
duration,
|
|
3871
|
+
elapsed: 0,
|
|
3872
|
+
easing,
|
|
3873
|
+
resolve
|
|
3874
|
+
});
|
|
3875
|
+
markActivity();
|
|
3876
|
+
});
|
|
3877
|
+
},
|
|
3878
|
+
[
|
|
3879
|
+
assetBundle.pose?.config?.faceId,
|
|
3880
|
+
cancelAnimation,
|
|
3881
|
+
faceId,
|
|
3882
|
+
getPathSnapshot,
|
|
3883
|
+
markActivity,
|
|
3884
|
+
poseWeightFallbackMap,
|
|
3885
|
+
setInput,
|
|
3886
|
+
useLegacyPoseWeightFallback
|
|
3887
|
+
]
|
|
3888
|
+
);
|
|
3889
|
+
const warnAnimationGap = useCallback((key, message) => {
|
|
3890
|
+
if (animationGapWarnedRef.current.has(key)) {
|
|
3891
|
+
return;
|
|
3892
|
+
}
|
|
3893
|
+
animationGapWarnedRef.current.add(key);
|
|
3894
|
+
console.warn(`[vizij-runtime] ${message}`);
|
|
3895
|
+
}, []);
|
|
3896
|
+
const syncAnimationsSource = useCallback(() => {
|
|
3897
|
+
const host = animationHostRef.current;
|
|
3898
|
+
setAnimationsSourceRegistered(host ? host.hasPlaying() : false);
|
|
3899
|
+
updateLoopMode();
|
|
3900
|
+
}, [setAnimationsSourceRegistered, updateLoopMode]);
|
|
3901
|
+
const readPlayerStates = useCallback(() => {
|
|
3902
|
+
const device = deviceSlot.current?.device;
|
|
3903
|
+
if (!device) {
|
|
3904
|
+
return [];
|
|
3905
|
+
}
|
|
3906
|
+
const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
|
|
3907
|
+
return decodePlayerStates(raw);
|
|
3908
|
+
}, [deviceSlot]);
|
|
3909
|
+
const settleFinishedClips = useCallback(() => {
|
|
3910
|
+
let pending = false;
|
|
3911
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3912
|
+
if (state.completion && !state.loop) {
|
|
3913
|
+
pending = true;
|
|
3914
|
+
}
|
|
3915
|
+
});
|
|
3916
|
+
const host = animationHostRef.current;
|
|
3917
|
+
if (!pending || !host) {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
const states = readPlayerStates();
|
|
3921
|
+
if (states.length === 0) {
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
clipPlaybackRef.current.forEach((state, id) => {
|
|
3925
|
+
if (!state.completion || state.loop) {
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
const playerId = host.playerIdOf(id);
|
|
3929
|
+
if (playerId === null) {
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
const feedback = states.find((entry) => entry.player === playerId);
|
|
3933
|
+
if (!feedback) {
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
const duration = feedback.duration > 0 ? feedback.duration : state.duration;
|
|
3937
|
+
const atEnd = duration > 0 && feedback.time >= duration - 1e-3;
|
|
3938
|
+
if (feedback.state === "stopped" || atEnd) {
|
|
3939
|
+
state.playing = false;
|
|
3940
|
+
resolveClipPromise(state);
|
|
3941
|
+
host.pause(id);
|
|
3942
|
+
syncAnimationsSource();
|
|
3943
|
+
}
|
|
3944
|
+
});
|
|
3945
|
+
}, [readPlayerStates, resolveClipPromise, syncAnimationsSource]);
|
|
3946
|
+
const playAnimation = useCallback(
|
|
3947
|
+
(id, options) => {
|
|
3948
|
+
const ensured = ensureClipState(id);
|
|
3949
|
+
if (!ensured) {
|
|
3950
|
+
return Promise.reject(
|
|
3951
|
+
new Error(`Animation ${id} is not part of the current asset bundle.`)
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
const { state } = ensured;
|
|
3955
|
+
const host = ensureAnimationHost();
|
|
3956
|
+
state.speed = options?.speed ?? state.speed;
|
|
3957
|
+
state.weight = options?.weight ?? state.weight;
|
|
3958
|
+
state.playing = true;
|
|
3959
|
+
const completion = ensureClipPromise(state);
|
|
3960
|
+
clipPlaybackRef.current.set(id, state);
|
|
3961
|
+
if (host) {
|
|
3962
|
+
if (options?.reset === true) {
|
|
3963
|
+
host.seek(id, 0);
|
|
3964
|
+
}
|
|
3965
|
+
host.setSpeed(id, state.speed);
|
|
3966
|
+
host.setWeight(id, state.weight);
|
|
3967
|
+
host.setLoop(id, state.loop ? "loop" : "once");
|
|
3968
|
+
void host.play(id).catch((err) => {
|
|
3969
|
+
pushError({
|
|
3970
|
+
message: `Failed to start animation ${id} on the device`,
|
|
3971
|
+
cause: err,
|
|
3972
|
+
phase: "animation",
|
|
3973
|
+
timestamp: performance.now()
|
|
3974
|
+
});
|
|
3975
|
+
});
|
|
3976
|
+
} else {
|
|
3977
|
+
warnAnimationGap(
|
|
3978
|
+
"module-not-ready",
|
|
3979
|
+
"playAnimation called before the animation module finished loading; playback starts once it is ready."
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
syncAnimationsSource();
|
|
3983
|
+
markActivity();
|
|
3984
|
+
return completion;
|
|
3985
|
+
},
|
|
3986
|
+
[
|
|
3987
|
+
ensureAnimationHost,
|
|
3988
|
+
ensureClipPromise,
|
|
3989
|
+
ensureClipState,
|
|
3990
|
+
markActivity,
|
|
3991
|
+
pushError,
|
|
3992
|
+
syncAnimationsSource,
|
|
3993
|
+
warnAnimationGap
|
|
3994
|
+
]
|
|
3995
|
+
);
|
|
3996
|
+
const pauseAnimation = useCallback(
|
|
3997
|
+
(id) => {
|
|
3998
|
+
const state = clipPlaybackRef.current.get(id);
|
|
3999
|
+
if (!state || !state.playing) {
|
|
4000
|
+
return;
|
|
4001
|
+
}
|
|
4002
|
+
state.playing = false;
|
|
4003
|
+
animationHostRef.current?.pause(id);
|
|
4004
|
+
syncAnimationsSource();
|
|
4005
|
+
},
|
|
4006
|
+
[syncAnimationsSource]
|
|
4007
|
+
);
|
|
4008
|
+
const seekAnimation = useCallback((id, timeSeconds) => {
|
|
4009
|
+
if (!clipPlaybackRef.current.has(id)) {
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
animationHostRef.current?.seek(id, Math.max(0, timeSeconds));
|
|
4013
|
+
}, []);
|
|
4014
|
+
const setAnimationLoop = useCallback(
|
|
4015
|
+
(id, enabled) => {
|
|
4016
|
+
const ensured = ensureClipState(id);
|
|
4017
|
+
if (!ensured) {
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
4020
|
+
ensured.state.loop = Boolean(enabled);
|
|
4021
|
+
animationHostRef.current?.setLoop(id, enabled ? "loop" : "once");
|
|
4022
|
+
updateLoopMode();
|
|
4023
|
+
},
|
|
4024
|
+
[ensureClipState, updateLoopMode]
|
|
4025
|
+
);
|
|
4026
|
+
const getAnimationState = useCallback(
|
|
4027
|
+
(id) => {
|
|
4028
|
+
const state = clipPlaybackRef.current.get(id);
|
|
4029
|
+
if (!state) {
|
|
4030
|
+
return null;
|
|
4031
|
+
}
|
|
4032
|
+
const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
|
|
4033
|
+
const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
|
|
4034
|
+
return {
|
|
4035
|
+
time: feedback?.time ?? 0,
|
|
4036
|
+
duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
|
|
4037
|
+
playing: feedback ? feedback.state === "playing" : state.playing,
|
|
4038
|
+
loop: state.loop,
|
|
4039
|
+
speed: feedback?.speed ?? state.speed
|
|
4040
|
+
};
|
|
4041
|
+
},
|
|
4042
|
+
[readPlayerStates]
|
|
4043
|
+
);
|
|
4044
|
+
const stopAnimation = useCallback(
|
|
4045
|
+
(id, options) => {
|
|
4046
|
+
const state = clipPlaybackRef.current.get(id);
|
|
4047
|
+
if (state) {
|
|
4048
|
+
clipPlaybackRef.current.delete(id);
|
|
4049
|
+
state.playing = false;
|
|
4050
|
+
resolveClipPromise(state);
|
|
4051
|
+
}
|
|
4052
|
+
const host = animationHostRef.current;
|
|
4053
|
+
if (options?.clearOutputs === false) {
|
|
4054
|
+
host?.pause(id);
|
|
4055
|
+
syncAnimationsSource();
|
|
4056
|
+
} else {
|
|
4057
|
+
host?.stop(id);
|
|
4058
|
+
const unsubscribe = subscribeToStep(() => {
|
|
4059
|
+
unsubscribe();
|
|
4060
|
+
syncAnimationsSource();
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
},
|
|
4064
|
+
[resolveClipPromise, subscribeToStep, syncAnimationsSource]
|
|
4065
|
+
);
|
|
4066
|
+
const refreshControllerStatus = useCallback(() => {
|
|
4067
|
+
const controllers = listControllers();
|
|
4068
|
+
reportStatus((prev) => ({
|
|
4069
|
+
...prev,
|
|
4070
|
+
controllers,
|
|
4071
|
+
outputPaths: Array.from(outputPathsRef.current)
|
|
4072
|
+
}));
|
|
4073
|
+
onRegisterControllers?.(controllers);
|
|
4074
|
+
}, [listControllers, onRegisterControllers, reportStatus]);
|
|
4075
|
+
const resolveProgramById = useCallback(
|
|
4076
|
+
(id) => {
|
|
4077
|
+
return resolvedProgramAssets.find((program) => program.id === id);
|
|
4078
|
+
},
|
|
4079
|
+
[resolvedProgramAssets]
|
|
1403
4080
|
);
|
|
1404
|
-
const
|
|
1405
|
-
(
|
|
1406
|
-
const
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
4081
|
+
const buildProgramRegistrationConfig = useCallback(
|
|
4082
|
+
(program) => {
|
|
4083
|
+
const graphSpec = resolveGraphSpec(
|
|
4084
|
+
program.graph,
|
|
4085
|
+
`${program.id ?? "program"} graph`
|
|
4086
|
+
);
|
|
4087
|
+
if (!graphSpec) {
|
|
4088
|
+
return null;
|
|
4089
|
+
}
|
|
4090
|
+
const outputs = collectOutputPaths(graphSpec);
|
|
4091
|
+
const inputs = collectInputPaths(graphSpec);
|
|
4092
|
+
const subs = program.graph.subscriptions ?? {
|
|
4093
|
+
inputs,
|
|
4094
|
+
outputs
|
|
4095
|
+
};
|
|
4096
|
+
return {
|
|
4097
|
+
id: namespaceControllerId(program.id, namespace, "graph"),
|
|
4098
|
+
spec: stripNulls(namespaceGraphSpec(graphSpec, namespace)),
|
|
4099
|
+
subs: namespaceSubscriptions(subs, namespace)
|
|
4100
|
+
};
|
|
4101
|
+
},
|
|
4102
|
+
[namespace]
|
|
4103
|
+
);
|
|
4104
|
+
const deriveProgramResetValues = useCallback(
|
|
4105
|
+
(program) => {
|
|
4106
|
+
if (program.resetValues) {
|
|
4107
|
+
return Object.entries(program.resetValues).filter(([, value]) => Number.isFinite(value)).map(([path, value]) => ({ path, value }));
|
|
4108
|
+
}
|
|
4109
|
+
const graphSpec = resolveGraphSpec(
|
|
4110
|
+
program.graph,
|
|
4111
|
+
`${program.id ?? "program"} graph (reset)`
|
|
4112
|
+
);
|
|
4113
|
+
if (!graphSpec) {
|
|
4114
|
+
return [];
|
|
1429
4115
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
4116
|
+
return collectOutputPaths(graphSpec).filter((path) => path.trim().length > 0).map((path) => {
|
|
4117
|
+
const defaultValue = inputConstraintsRef.current[path]?.defaultValue ?? 0;
|
|
4118
|
+
return {
|
|
4119
|
+
path,
|
|
4120
|
+
value: Number.isFinite(defaultValue) && defaultValue != null ? defaultValue : 0
|
|
4121
|
+
};
|
|
4122
|
+
});
|
|
1432
4123
|
},
|
|
1433
4124
|
[]
|
|
1434
4125
|
);
|
|
1435
|
-
const
|
|
1436
|
-
(
|
|
1437
|
-
|
|
4126
|
+
const syncProgramPlaybackControllers = useCallback(() => {
|
|
4127
|
+
if (!ready) {
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
const availableProgramIds = new Set(
|
|
4131
|
+
resolvedProgramAssets.map((program) => program.id)
|
|
4132
|
+
);
|
|
4133
|
+
Array.from(programPlaybackRef.current.keys()).forEach((id) => {
|
|
4134
|
+
if (availableProgramIds.has(id)) {
|
|
1438
4135
|
return;
|
|
1439
4136
|
}
|
|
1440
|
-
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
4137
|
+
programPlaybackRef.current.delete(id);
|
|
4138
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
4139
|
+
if (controllerId) {
|
|
4140
|
+
try {
|
|
4141
|
+
removeGraph(controllerId);
|
|
4142
|
+
} catch (err) {
|
|
4143
|
+
pushError({
|
|
4144
|
+
message: `Failed to remove program ${id}`,
|
|
4145
|
+
cause: err,
|
|
4146
|
+
phase: "registration",
|
|
4147
|
+
timestamp: performance.now()
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
programControllerIdsRef.current.delete(id);
|
|
4151
|
+
}
|
|
4152
|
+
});
|
|
4153
|
+
programPlaybackRef.current.forEach((state, id) => {
|
|
4154
|
+
const program = resolveProgramById(id);
|
|
4155
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
4156
|
+
if (!program) {
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
if (state.state !== "playing") {
|
|
4160
|
+
if (!controllerId) {
|
|
1450
4161
|
return;
|
|
1451
4162
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
4163
|
+
try {
|
|
4164
|
+
removeGraph(controllerId);
|
|
4165
|
+
} catch (err) {
|
|
4166
|
+
pushError({
|
|
4167
|
+
message: `Failed to pause program ${id}`,
|
|
4168
|
+
cause: err,
|
|
4169
|
+
phase: "registration",
|
|
4170
|
+
timestamp: performance.now()
|
|
4171
|
+
});
|
|
1458
4172
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
4173
|
+
programControllerIdsRef.current.delete(id);
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
if (controllerId) {
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
const config = buildProgramRegistrationConfig(program);
|
|
4180
|
+
if (!config) {
|
|
4181
|
+
pushError({
|
|
4182
|
+
message: `Program ${id} is missing a usable graph payload.`,
|
|
4183
|
+
phase: "registration",
|
|
4184
|
+
timestamp: performance.now()
|
|
1464
4185
|
});
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
try {
|
|
4189
|
+
const nextControllerId = registerGraph(config);
|
|
4190
|
+
programControllerIdsRef.current.set(id, nextControllerId);
|
|
4191
|
+
} catch (err) {
|
|
4192
|
+
pushError({
|
|
4193
|
+
message: `Failed to register program ${id}`,
|
|
4194
|
+
cause: err,
|
|
4195
|
+
phase: "registration",
|
|
4196
|
+
timestamp: performance.now()
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
});
|
|
4200
|
+
refreshControllerStatus();
|
|
4201
|
+
}, [
|
|
4202
|
+
buildProgramRegistrationConfig,
|
|
4203
|
+
pushError,
|
|
4204
|
+
ready,
|
|
4205
|
+
refreshControllerStatus,
|
|
4206
|
+
registerGraph,
|
|
4207
|
+
removeGraph,
|
|
4208
|
+
resolvedProgramAssets,
|
|
4209
|
+
resolveProgramById
|
|
4210
|
+
]);
|
|
4211
|
+
const playProgram = useCallback(
|
|
4212
|
+
(id) => {
|
|
4213
|
+
const program = resolveProgramById(id);
|
|
4214
|
+
if (!program) {
|
|
4215
|
+
throw new Error(
|
|
4216
|
+
`Program ${id} is not part of the current asset bundle.`
|
|
4217
|
+
);
|
|
4218
|
+
}
|
|
4219
|
+
deriveProgramInputSeedValues({
|
|
4220
|
+
program,
|
|
4221
|
+
namespace,
|
|
4222
|
+
inputConstraints: inputConstraintsRef.current,
|
|
4223
|
+
getPathSnapshot,
|
|
4224
|
+
stagedInputs: stagedInputsRef.current
|
|
4225
|
+
}).forEach(({ path, value }) => {
|
|
4226
|
+
setInput(path, value);
|
|
1468
4227
|
});
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
if (clip) {
|
|
1473
|
-
const clipData = clip.clip;
|
|
1474
|
-
const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
|
|
1475
|
-
tracks.forEach((track) => {
|
|
1476
|
-
const path = `animation/${clip.id}/${track.channel}`;
|
|
1477
|
-
setInput(path, { float: 0 });
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
4228
|
+
programPlaybackRef.current.set(id, {
|
|
4229
|
+
id,
|
|
4230
|
+
state: "playing"
|
|
1480
4231
|
});
|
|
4232
|
+
syncProgramPlaybackControllers();
|
|
4233
|
+
markActivity();
|
|
1481
4234
|
},
|
|
1482
|
-
[
|
|
4235
|
+
[
|
|
4236
|
+
getPathSnapshot,
|
|
4237
|
+
markActivity,
|
|
4238
|
+
namespace,
|
|
4239
|
+
resolveProgramById,
|
|
4240
|
+
setInput,
|
|
4241
|
+
syncProgramPlaybackControllers
|
|
4242
|
+
]
|
|
1483
4243
|
);
|
|
1484
|
-
const
|
|
1485
|
-
(
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
cancelAnimation(path);
|
|
1489
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
1490
|
-
const current = getPathSnapshot(namespacedPath);
|
|
1491
|
-
const fromValue = valueAsNumber2(current);
|
|
1492
|
-
const toValue = valueAsNumber2(target);
|
|
1493
|
-
if (fromValue == null || toValue == null || duration === 0) {
|
|
1494
|
-
setInput(path, target);
|
|
1495
|
-
return Promise.resolve();
|
|
4244
|
+
const pauseProgram = useCallback(
|
|
4245
|
+
(id) => {
|
|
4246
|
+
if (!resolveProgramById(id)) {
|
|
4247
|
+
return;
|
|
1496
4248
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
from: fromValue,
|
|
1501
|
-
to: toValue,
|
|
1502
|
-
duration,
|
|
1503
|
-
elapsed: 0,
|
|
1504
|
-
easing,
|
|
1505
|
-
resolve
|
|
1506
|
-
});
|
|
1507
|
-
markActivity();
|
|
4249
|
+
programPlaybackRef.current.set(id, {
|
|
4250
|
+
id,
|
|
4251
|
+
state: "paused"
|
|
1508
4252
|
});
|
|
4253
|
+
syncProgramPlaybackControllers();
|
|
4254
|
+
updateLoopMode();
|
|
1509
4255
|
},
|
|
1510
|
-
[
|
|
4256
|
+
[resolveProgramById, syncProgramPlaybackControllers, updateLoopMode]
|
|
1511
4257
|
);
|
|
1512
|
-
const
|
|
4258
|
+
const stopProgram = useCallback(
|
|
1513
4259
|
(id, options) => {
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
4260
|
+
const program = resolveProgramById(id);
|
|
4261
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
4262
|
+
if (controllerId) {
|
|
4263
|
+
try {
|
|
4264
|
+
removeGraph(controllerId);
|
|
4265
|
+
} catch (err) {
|
|
4266
|
+
pushError({
|
|
4267
|
+
message: `Failed to stop program ${id}`,
|
|
4268
|
+
cause: err,
|
|
4269
|
+
phase: "registration",
|
|
4270
|
+
timestamp: performance.now()
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
programControllerIdsRef.current.delete(id);
|
|
1522
4274
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
const clipData = clip.clip;
|
|
1527
|
-
clipPlaybackRef.current.set(id, {
|
|
1528
|
-
id,
|
|
1529
|
-
time: options?.reset ? 0 : 0,
|
|
1530
|
-
duration: Number(clipData?.duration ?? 0),
|
|
1531
|
-
speed: Number.isFinite(speed) && speed > 0 ? speed : 1,
|
|
1532
|
-
weight,
|
|
1533
|
-
resolve
|
|
1534
|
-
});
|
|
1535
|
-
markActivity();
|
|
4275
|
+
programPlaybackRef.current.set(id, {
|
|
4276
|
+
id,
|
|
4277
|
+
state: "stopped"
|
|
1536
4278
|
});
|
|
4279
|
+
if (program && options?.resetOutputs !== false) {
|
|
4280
|
+
deriveProgramResetValues(program).forEach(({ path, value }) => {
|
|
4281
|
+
setInput(path, { float: value });
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
refreshControllerStatus();
|
|
4285
|
+
updateLoopMode();
|
|
1537
4286
|
},
|
|
1538
|
-
[
|
|
4287
|
+
[
|
|
4288
|
+
deriveProgramResetValues,
|
|
4289
|
+
pushError,
|
|
4290
|
+
refreshControllerStatus,
|
|
4291
|
+
removeGraph,
|
|
4292
|
+
resolveProgramById,
|
|
4293
|
+
setInput,
|
|
4294
|
+
updateLoopMode
|
|
4295
|
+
]
|
|
1539
4296
|
);
|
|
1540
|
-
const
|
|
4297
|
+
const getProgramState = useCallback(
|
|
1541
4298
|
(id) => {
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
clipPlaybackRef.current.delete(id);
|
|
1546
|
-
state.resolve();
|
|
4299
|
+
const state = programPlaybackRef.current.get(id);
|
|
4300
|
+
if (!state) {
|
|
4301
|
+
return null;
|
|
1547
4302
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
4303
|
+
return { state: state.state };
|
|
4304
|
+
},
|
|
4305
|
+
[]
|
|
4306
|
+
);
|
|
4307
|
+
useEffect(() => {
|
|
4308
|
+
if (!ready || status.loading) {
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4311
|
+
syncProgramPlaybackControllers();
|
|
4312
|
+
}, [
|
|
4313
|
+
graphUpdateToken,
|
|
4314
|
+
ready,
|
|
4315
|
+
resolvedProgramAssets,
|
|
4316
|
+
status.loading,
|
|
4317
|
+
syncProgramPlaybackControllers
|
|
4318
|
+
]);
|
|
4319
|
+
const setAnimationActive = useCallback(
|
|
4320
|
+
(active) => {
|
|
4321
|
+
const next = Boolean(active);
|
|
4322
|
+
if (animationSystemActiveRef.current === next) {
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
animationSystemActiveRef.current = next;
|
|
4326
|
+
if (!next) {
|
|
4327
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
4328
|
+
state.playing = false;
|
|
1554
4329
|
});
|
|
1555
4330
|
}
|
|
4331
|
+
updateLoopMode();
|
|
1556
4332
|
},
|
|
1557
|
-
[
|
|
4333
|
+
[updateLoopMode]
|
|
4334
|
+
);
|
|
4335
|
+
const isAnimationActive = useCallback(
|
|
4336
|
+
() => animationSystemActiveRef.current,
|
|
4337
|
+
[]
|
|
1558
4338
|
);
|
|
1559
4339
|
const registerInputDriver = useCallback(
|
|
1560
4340
|
(id, factory) => {
|
|
4341
|
+
inputDriverIdsRef.current.add(id);
|
|
1561
4342
|
const driver = factory({
|
|
1562
4343
|
setInput,
|
|
1563
4344
|
setRendererValue,
|
|
@@ -1599,6 +4380,8 @@ function VizijRuntimeProviderInner({
|
|
|
1599
4380
|
phase: "driver",
|
|
1600
4381
|
timestamp: performance.now()
|
|
1601
4382
|
});
|
|
4383
|
+
} finally {
|
|
4384
|
+
inputDriverIdsRef.current.delete(id);
|
|
1602
4385
|
}
|
|
1603
4386
|
}
|
|
1604
4387
|
};
|
|
@@ -1606,22 +4389,15 @@ function VizijRuntimeProviderInner({
|
|
|
1606
4389
|
},
|
|
1607
4390
|
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
1608
4391
|
);
|
|
1609
|
-
const advanceAnimations = useCallback(
|
|
1610
|
-
(dt) => {
|
|
1611
|
-
advanceAnimationTweens(dt);
|
|
1612
|
-
advanceClipPlayback(dt);
|
|
1613
|
-
},
|
|
1614
|
-
[advanceAnimationTweens, advanceClipPlayback]
|
|
1615
|
-
);
|
|
1616
4392
|
const flushStagedInputs = useCallback(() => {
|
|
1617
4393
|
if (stagedInputsRef.current.size === 0) {
|
|
1618
4394
|
return;
|
|
1619
4395
|
}
|
|
1620
4396
|
stagedInputsRef.current.forEach(({ value, shape }, path) => {
|
|
1621
|
-
|
|
4397
|
+
deviceSetInput(path, value, shape);
|
|
1622
4398
|
});
|
|
1623
4399
|
stagedInputsRef.current.clear();
|
|
1624
|
-
}, [
|
|
4400
|
+
}, [deviceSetInput]);
|
|
1625
4401
|
const step = useCallback(
|
|
1626
4402
|
(dt, opts) => {
|
|
1627
4403
|
if (dt > 0 && Number.isFinite(dt)) {
|
|
@@ -1629,13 +4405,19 @@ function VizijRuntimeProviderInner({
|
|
|
1629
4405
|
const alpha = 0.1;
|
|
1630
4406
|
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
1631
4407
|
}
|
|
1632
|
-
|
|
4408
|
+
advanceAnimationTweens(dt);
|
|
1633
4409
|
flushStagedInputs();
|
|
1634
|
-
if (
|
|
4410
|
+
if (driveRuntimeRef.current || opts?.forceRuntime) {
|
|
1635
4411
|
stepRuntime(dt);
|
|
1636
4412
|
}
|
|
4413
|
+
settleFinishedClips();
|
|
1637
4414
|
},
|
|
1638
|
-
[
|
|
4415
|
+
[
|
|
4416
|
+
advanceAnimationTweens,
|
|
4417
|
+
flushStagedInputs,
|
|
4418
|
+
settleFinishedClips,
|
|
4419
|
+
stepRuntime
|
|
4420
|
+
]
|
|
1639
4421
|
);
|
|
1640
4422
|
useEffect(() => {
|
|
1641
4423
|
if (loopMode !== "active") {
|
|
@@ -1650,7 +4432,7 @@ function VizijRuntimeProviderInner({
|
|
|
1650
4432
|
if (lastTime == null) {
|
|
1651
4433
|
lastTime = timestamp;
|
|
1652
4434
|
}
|
|
1653
|
-
const dt = Math.max(0, (timestamp - lastTime) / 1e3);
|
|
4435
|
+
const dt = inferImplicitPause(Math.max(0, (timestamp - lastTime) / 1e3));
|
|
1654
4436
|
lastTime = timestamp;
|
|
1655
4437
|
step(dt || 0);
|
|
1656
4438
|
requestLoopMode(computeDesiredLoopMode());
|
|
@@ -1681,7 +4463,7 @@ function VizijRuntimeProviderInner({
|
|
|
1681
4463
|
return;
|
|
1682
4464
|
}
|
|
1683
4465
|
const current = now();
|
|
1684
|
-
const dt = Math.max(0, (current - lastTime) / 1e3);
|
|
4466
|
+
const dt = inferImplicitPause(Math.max(0, (current - lastTime) / 1e3));
|
|
1685
4467
|
lastTime = current;
|
|
1686
4468
|
step(dt || 0);
|
|
1687
4469
|
requestLoopMode(computeDesiredLoopMode());
|
|
@@ -1695,6 +4477,8 @@ function VizijRuntimeProviderInner({
|
|
|
1695
4477
|
return () => {
|
|
1696
4478
|
animationTweensRef.current.clear();
|
|
1697
4479
|
clipPlaybackRef.current.clear();
|
|
4480
|
+
programPlaybackRef.current.clear();
|
|
4481
|
+
programControllerIdsRef.current.clear();
|
|
1698
4482
|
};
|
|
1699
4483
|
}, []);
|
|
1700
4484
|
useEffect(() => {
|
|
@@ -1710,35 +4494,95 @@ function VizijRuntimeProviderInner({
|
|
|
1710
4494
|
}, 500);
|
|
1711
4495
|
return () => window.clearInterval(id);
|
|
1712
4496
|
}, [reportStatus]);
|
|
4497
|
+
const setGraphBundle = useCallback(
|
|
4498
|
+
(bundle, options) => {
|
|
4499
|
+
const baseAssetBundle = latestEffectiveAssetBundleRef.current;
|
|
4500
|
+
const nextAssetBundle = applyRuntimeGraphBundle(baseAssetBundle, bundle);
|
|
4501
|
+
const plan = resolveRuntimeUpdatePlan(
|
|
4502
|
+
baseAssetBundle,
|
|
4503
|
+
nextAssetBundle,
|
|
4504
|
+
options?.tier ?? updateTierRef.current
|
|
4505
|
+
);
|
|
4506
|
+
pendingPlanRef.current = plan;
|
|
4507
|
+
previousBundleRef.current = nextAssetBundle;
|
|
4508
|
+
latestEffectiveAssetBundleRef.current = nextAssetBundle;
|
|
4509
|
+
suppressNextBundlePlanRef.current = true;
|
|
4510
|
+
setAssetBundleOverride(nextAssetBundle);
|
|
4511
|
+
if (plan.reregisterGraphs) {
|
|
4512
|
+
setGraphUpdateToken((prev) => prev + 1);
|
|
4513
|
+
}
|
|
4514
|
+
if (plan.reloadAssets) {
|
|
4515
|
+
reportStatus((prev) => ({
|
|
4516
|
+
...prev,
|
|
4517
|
+
loading: true,
|
|
4518
|
+
ready: false
|
|
4519
|
+
}));
|
|
4520
|
+
} else {
|
|
4521
|
+
reportStatus((prev) => ({
|
|
4522
|
+
...prev,
|
|
4523
|
+
loading: false
|
|
4524
|
+
}));
|
|
4525
|
+
}
|
|
4526
|
+
},
|
|
4527
|
+
[reportStatus]
|
|
4528
|
+
);
|
|
1713
4529
|
const contextValue = useMemo(
|
|
1714
4530
|
() => ({
|
|
1715
4531
|
...status,
|
|
1716
4532
|
assetBundle,
|
|
1717
4533
|
setInput,
|
|
4534
|
+
getValueSnapshot: getPathSnapshot,
|
|
4535
|
+
getStoreSnapshot,
|
|
4536
|
+
subscribeToStoreChanges,
|
|
4537
|
+
subscribeToStep,
|
|
4538
|
+
setGraphBundle,
|
|
1718
4539
|
setValue: setRendererValue,
|
|
1719
4540
|
stagePoseNeutral,
|
|
1720
4541
|
animateValue,
|
|
1721
4542
|
cancelAnimation,
|
|
1722
4543
|
registerInputDriver,
|
|
1723
4544
|
playAnimation,
|
|
4545
|
+
pauseAnimation,
|
|
4546
|
+
seekAnimation,
|
|
4547
|
+
setAnimationLoop,
|
|
4548
|
+
getAnimationState,
|
|
1724
4549
|
stopAnimation,
|
|
4550
|
+
playProgram,
|
|
4551
|
+
pauseProgram,
|
|
4552
|
+
stopProgram,
|
|
4553
|
+
getProgramState,
|
|
4554
|
+
setAnimationActive,
|
|
4555
|
+
isAnimationActive,
|
|
1725
4556
|
step,
|
|
1726
|
-
advanceAnimations,
|
|
1727
4557
|
inputConstraints
|
|
1728
4558
|
}),
|
|
1729
4559
|
[
|
|
1730
4560
|
status,
|
|
1731
4561
|
assetBundle,
|
|
1732
4562
|
setInput,
|
|
4563
|
+
getPathSnapshot,
|
|
4564
|
+
getStoreSnapshot,
|
|
4565
|
+
subscribeToStoreChanges,
|
|
4566
|
+
subscribeToStep,
|
|
4567
|
+
setGraphBundle,
|
|
1733
4568
|
setRendererValue,
|
|
1734
4569
|
stagePoseNeutral,
|
|
1735
4570
|
animateValue,
|
|
1736
4571
|
cancelAnimation,
|
|
1737
4572
|
registerInputDriver,
|
|
1738
4573
|
playAnimation,
|
|
4574
|
+
pauseAnimation,
|
|
4575
|
+
seekAnimation,
|
|
4576
|
+
setAnimationLoop,
|
|
4577
|
+
getAnimationState,
|
|
1739
4578
|
stopAnimation,
|
|
4579
|
+
playProgram,
|
|
4580
|
+
pauseProgram,
|
|
4581
|
+
stopProgram,
|
|
4582
|
+
getProgramState,
|
|
4583
|
+
setAnimationActive,
|
|
4584
|
+
isAnimationActive,
|
|
1740
4585
|
step,
|
|
1741
|
-
advanceAnimations,
|
|
1742
4586
|
inputConstraints
|
|
1743
4587
|
]
|
|
1744
4588
|
);
|
|
@@ -1750,9 +4594,9 @@ import { memo } from "react";
|
|
|
1750
4594
|
import { Vizij } from "@vizij/render";
|
|
1751
4595
|
|
|
1752
4596
|
// src/hooks/useVizijRuntime.ts
|
|
1753
|
-
import { useContext
|
|
4597
|
+
import { useContext } from "react";
|
|
1754
4598
|
function useVizijRuntime() {
|
|
1755
|
-
const ctx =
|
|
4599
|
+
const ctx = useContext(VizijRuntimeContext);
|
|
1756
4600
|
if (!ctx) {
|
|
1757
4601
|
throw new Error(
|
|
1758
4602
|
"useVizijRuntime must be used within a VizijRuntimeProvider."
|
|
@@ -1782,17 +4626,23 @@ function VizijRuntimeFaceInner({
|
|
|
1782
4626
|
}
|
|
1783
4627
|
var VizijRuntimeFace = memo(VizijRuntimeFaceInner);
|
|
1784
4628
|
|
|
4629
|
+
// src/hooks/useOptionalVizijRuntime.ts
|
|
4630
|
+
import { useContext as useContext2 } from "react";
|
|
4631
|
+
function useOptionalVizijRuntime() {
|
|
4632
|
+
return useContext2(VizijRuntimeContext);
|
|
4633
|
+
}
|
|
4634
|
+
|
|
1785
4635
|
// src/hooks/useVizijOutputs.ts
|
|
1786
4636
|
import {
|
|
1787
4637
|
useVizijStore
|
|
1788
4638
|
} from "@vizij/render";
|
|
1789
|
-
import { getLookup } from "@vizij/utils";
|
|
4639
|
+
import { getLookup as getLookup2 } from "@vizij/utils";
|
|
1790
4640
|
function useVizijOutputs(paths) {
|
|
1791
4641
|
const { namespace } = useVizijRuntime();
|
|
1792
4642
|
return useVizijStore((state) => {
|
|
1793
4643
|
const result = {};
|
|
1794
4644
|
paths.forEach((path) => {
|
|
1795
|
-
const lookup =
|
|
4645
|
+
const lookup = getLookup2(namespace, path);
|
|
1796
4646
|
result[path] = state.values.get(lookup);
|
|
1797
4647
|
});
|
|
1798
4648
|
return result;
|
|
@@ -1804,11 +4654,11 @@ import { useCallback as useCallback2 } from "react";
|
|
|
1804
4654
|
import {
|
|
1805
4655
|
useVizijStore as useVizijStore2
|
|
1806
4656
|
} from "@vizij/render";
|
|
1807
|
-
import { getLookup as
|
|
4657
|
+
import { getLookup as getLookup3 } from "@vizij/utils";
|
|
1808
4658
|
function useRigInput(path) {
|
|
1809
4659
|
const { namespace, setInput } = useVizijRuntime();
|
|
1810
4660
|
const value = useVizijStore2((state) => {
|
|
1811
|
-
return state.values.get(
|
|
4661
|
+
return state.values.get(getLookup3(namespace, path));
|
|
1812
4662
|
});
|
|
1813
4663
|
const setter = useCallback2(
|
|
1814
4664
|
(next, shape) => {
|
|
@@ -1818,9 +4668,422 @@ function useRigInput(path) {
|
|
|
1818
4668
|
);
|
|
1819
4669
|
return [value, setter];
|
|
1820
4670
|
}
|
|
4671
|
+
|
|
4672
|
+
// src/utils/faceControls.ts
|
|
4673
|
+
var DEFAULT_GAZE_CONTROL = {
|
|
4674
|
+
min: -1,
|
|
4675
|
+
max: 1,
|
|
4676
|
+
defaultValue: 0
|
|
4677
|
+
};
|
|
4678
|
+
var DEFAULT_BLINK_CONTROL = {
|
|
4679
|
+
min: 0,
|
|
4680
|
+
max: 1,
|
|
4681
|
+
defaultValue: 0
|
|
4682
|
+
};
|
|
4683
|
+
var STANDARD_VIZIJ_EYE_PATHS = {
|
|
4684
|
+
leftX: "/standard/vizij/left_eye/pos/x",
|
|
4685
|
+
leftY: "/standard/vizij/left_eye/pos/y",
|
|
4686
|
+
rightX: "/standard/vizij/right_eye/pos/x",
|
|
4687
|
+
rightY: "/standard/vizij/right_eye/pos/y",
|
|
4688
|
+
leftUpper: "/standard/vizij/left_eye_top_eyelid/pos/y",
|
|
4689
|
+
rightUpper: "/standard/vizij/right_eye_top_eyelid/pos/y"
|
|
4690
|
+
};
|
|
4691
|
+
var LEGACY_STANDARD_EYE_PATHS = {
|
|
4692
|
+
leftX: "/standard/left_eye/pos/x",
|
|
4693
|
+
leftY: "/standard/left_eye/pos/y",
|
|
4694
|
+
rightX: "/standard/right_eye/pos/x",
|
|
4695
|
+
rightY: "/standard/right_eye/pos/y",
|
|
4696
|
+
leftUpper: "/standard/left_eye_top_eyelid/pos/y",
|
|
4697
|
+
rightUpper: "/standard/right_eye_top_eyelid/pos/y"
|
|
4698
|
+
};
|
|
4699
|
+
var PROPSRIG_EYE_PATHS = {
|
|
4700
|
+
leftX: "/propsrig/l_eye/translation/x",
|
|
4701
|
+
leftY: "/propsrig/l_eye/translation/y",
|
|
4702
|
+
rightX: "/propsrig/r_eye/translation/x",
|
|
4703
|
+
rightY: "/propsrig/r_eye/translation/y"
|
|
4704
|
+
};
|
|
4705
|
+
function clamp(value, min, max) {
|
|
4706
|
+
return Math.min(Math.max(value, min), max);
|
|
4707
|
+
}
|
|
4708
|
+
function normalizeInputPath(path) {
|
|
4709
|
+
const trimmed = path?.trim() ?? "";
|
|
4710
|
+
if (!trimmed) {
|
|
4711
|
+
return null;
|
|
4712
|
+
}
|
|
4713
|
+
if (trimmed.startsWith("/")) {
|
|
4714
|
+
return trimmed;
|
|
4715
|
+
}
|
|
4716
|
+
const rigMatch = trimmed.match(/^rig\/[^/]+(\/.*)$/);
|
|
4717
|
+
if (rigMatch?.[1]) {
|
|
4718
|
+
return rigMatch[1];
|
|
4719
|
+
}
|
|
4720
|
+
return `/${trimmed}`;
|
|
4721
|
+
}
|
|
4722
|
+
function buildControlsLookup(assetBundle) {
|
|
4723
|
+
const metadata = assetBundle.rig?.inputMetadata ?? [];
|
|
4724
|
+
const metadataByPath = /* @__PURE__ */ new Map();
|
|
4725
|
+
const pathSet = /* @__PURE__ */ new Set();
|
|
4726
|
+
metadata.forEach((entry) => {
|
|
4727
|
+
const normalized = normalizeInputPath(entry.path);
|
|
4728
|
+
if (!normalized) {
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
pathSet.add(normalized);
|
|
4732
|
+
if (!metadataByPath.has(normalized)) {
|
|
4733
|
+
metadataByPath.set(normalized, entry);
|
|
4734
|
+
}
|
|
4735
|
+
});
|
|
4736
|
+
return { pathSet, metadataByPath };
|
|
4737
|
+
}
|
|
4738
|
+
function resolveConstraint(absolutePath, relativePath, inputConstraints) {
|
|
4739
|
+
if (!inputConstraints) {
|
|
4740
|
+
return null;
|
|
4741
|
+
}
|
|
4742
|
+
const normalizedRelative = relativePath.replace(/^\//, "");
|
|
4743
|
+
const candidates = [
|
|
4744
|
+
absolutePath,
|
|
4745
|
+
relativePath,
|
|
4746
|
+
normalizedRelative,
|
|
4747
|
+
absolutePath.replace(/^rig\/[^/]+\//, "")
|
|
4748
|
+
];
|
|
4749
|
+
for (const candidate of candidates) {
|
|
4750
|
+
const constraint = inputConstraints[candidate];
|
|
4751
|
+
if (constraint) {
|
|
4752
|
+
return constraint;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
return null;
|
|
4756
|
+
}
|
|
4757
|
+
function createControl(faceId, lookup, relativePath, inputConstraints, fallback) {
|
|
4758
|
+
const normalizedRelative = normalizeInputPath(relativePath);
|
|
4759
|
+
if (!normalizedRelative || !lookup.pathSet.has(normalizedRelative)) {
|
|
4760
|
+
return null;
|
|
4761
|
+
}
|
|
4762
|
+
const absolutePath = buildRigInputPath(faceId, normalizedRelative);
|
|
4763
|
+
const metadata = lookup.metadataByPath.get(normalizedRelative);
|
|
4764
|
+
const constraint = resolveConstraint(
|
|
4765
|
+
absolutePath,
|
|
4766
|
+
normalizedRelative,
|
|
4767
|
+
inputConstraints
|
|
4768
|
+
);
|
|
4769
|
+
const min = Number.isFinite(Number(constraint?.min ?? metadata?.range?.min)) ? Number(constraint?.min ?? metadata?.range?.min) : fallback.min;
|
|
4770
|
+
const max = Number.isFinite(Number(constraint?.max ?? metadata?.range?.max)) ? Number(constraint?.max ?? metadata?.range?.max) : fallback.max;
|
|
4771
|
+
const midpoint = min + (max - min) / 2;
|
|
4772
|
+
const defaultValue = Number.isFinite(
|
|
4773
|
+
Number(constraint?.defaultValue ?? metadata?.defaultValue)
|
|
4774
|
+
) ? Number(constraint?.defaultValue ?? metadata?.defaultValue) : Number.isFinite(midpoint) ? midpoint : fallback.defaultValue;
|
|
4775
|
+
return {
|
|
4776
|
+
path: absolutePath,
|
|
4777
|
+
min,
|
|
4778
|
+
max,
|
|
4779
|
+
defaultValue
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
function hasAll(controls, keys) {
|
|
4783
|
+
return keys.every((key) => Boolean(controls[key]));
|
|
4784
|
+
}
|
|
4785
|
+
function resolveFaceControls(assetBundle, runtimeFaceId, inputConstraints) {
|
|
4786
|
+
const faceId = assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? runtimeFaceId ?? "face";
|
|
4787
|
+
const lookup = buildControlsLookup(assetBundle);
|
|
4788
|
+
const standardVizijControls = {
|
|
4789
|
+
leftX: createControl(
|
|
4790
|
+
faceId,
|
|
4791
|
+
lookup,
|
|
4792
|
+
STANDARD_VIZIJ_EYE_PATHS.leftX,
|
|
4793
|
+
inputConstraints,
|
|
4794
|
+
DEFAULT_GAZE_CONTROL
|
|
4795
|
+
),
|
|
4796
|
+
leftY: createControl(
|
|
4797
|
+
faceId,
|
|
4798
|
+
lookup,
|
|
4799
|
+
STANDARD_VIZIJ_EYE_PATHS.leftY,
|
|
4800
|
+
inputConstraints,
|
|
4801
|
+
DEFAULT_GAZE_CONTROL
|
|
4802
|
+
),
|
|
4803
|
+
rightX: createControl(
|
|
4804
|
+
faceId,
|
|
4805
|
+
lookup,
|
|
4806
|
+
STANDARD_VIZIJ_EYE_PATHS.rightX,
|
|
4807
|
+
inputConstraints,
|
|
4808
|
+
DEFAULT_GAZE_CONTROL
|
|
4809
|
+
),
|
|
4810
|
+
rightY: createControl(
|
|
4811
|
+
faceId,
|
|
4812
|
+
lookup,
|
|
4813
|
+
STANDARD_VIZIJ_EYE_PATHS.rightY,
|
|
4814
|
+
inputConstraints,
|
|
4815
|
+
DEFAULT_GAZE_CONTROL
|
|
4816
|
+
),
|
|
4817
|
+
leftUpper: createControl(
|
|
4818
|
+
faceId,
|
|
4819
|
+
lookup,
|
|
4820
|
+
STANDARD_VIZIJ_EYE_PATHS.leftUpper,
|
|
4821
|
+
inputConstraints,
|
|
4822
|
+
DEFAULT_GAZE_CONTROL
|
|
4823
|
+
),
|
|
4824
|
+
rightUpper: createControl(
|
|
4825
|
+
faceId,
|
|
4826
|
+
lookup,
|
|
4827
|
+
STANDARD_VIZIJ_EYE_PATHS.rightUpper,
|
|
4828
|
+
inputConstraints,
|
|
4829
|
+
DEFAULT_GAZE_CONTROL
|
|
4830
|
+
)
|
|
4831
|
+
};
|
|
4832
|
+
const legacyStandardControls = {
|
|
4833
|
+
leftX: createControl(
|
|
4834
|
+
faceId,
|
|
4835
|
+
lookup,
|
|
4836
|
+
LEGACY_STANDARD_EYE_PATHS.leftX,
|
|
4837
|
+
inputConstraints,
|
|
4838
|
+
DEFAULT_GAZE_CONTROL
|
|
4839
|
+
),
|
|
4840
|
+
leftY: createControl(
|
|
4841
|
+
faceId,
|
|
4842
|
+
lookup,
|
|
4843
|
+
LEGACY_STANDARD_EYE_PATHS.leftY,
|
|
4844
|
+
inputConstraints,
|
|
4845
|
+
DEFAULT_GAZE_CONTROL
|
|
4846
|
+
),
|
|
4847
|
+
rightX: createControl(
|
|
4848
|
+
faceId,
|
|
4849
|
+
lookup,
|
|
4850
|
+
LEGACY_STANDARD_EYE_PATHS.rightX,
|
|
4851
|
+
inputConstraints,
|
|
4852
|
+
DEFAULT_GAZE_CONTROL
|
|
4853
|
+
),
|
|
4854
|
+
rightY: createControl(
|
|
4855
|
+
faceId,
|
|
4856
|
+
lookup,
|
|
4857
|
+
LEGACY_STANDARD_EYE_PATHS.rightY,
|
|
4858
|
+
inputConstraints,
|
|
4859
|
+
DEFAULT_GAZE_CONTROL
|
|
4860
|
+
),
|
|
4861
|
+
leftUpper: createControl(
|
|
4862
|
+
faceId,
|
|
4863
|
+
lookup,
|
|
4864
|
+
LEGACY_STANDARD_EYE_PATHS.leftUpper,
|
|
4865
|
+
inputConstraints,
|
|
4866
|
+
DEFAULT_GAZE_CONTROL
|
|
4867
|
+
),
|
|
4868
|
+
rightUpper: createControl(
|
|
4869
|
+
faceId,
|
|
4870
|
+
lookup,
|
|
4871
|
+
LEGACY_STANDARD_EYE_PATHS.rightUpper,
|
|
4872
|
+
inputConstraints,
|
|
4873
|
+
DEFAULT_GAZE_CONTROL
|
|
4874
|
+
)
|
|
4875
|
+
};
|
|
4876
|
+
const propsrigControls = {
|
|
4877
|
+
leftX: createControl(
|
|
4878
|
+
faceId,
|
|
4879
|
+
lookup,
|
|
4880
|
+
PROPSRIG_EYE_PATHS.leftX,
|
|
4881
|
+
inputConstraints,
|
|
4882
|
+
DEFAULT_GAZE_CONTROL
|
|
4883
|
+
),
|
|
4884
|
+
leftY: createControl(
|
|
4885
|
+
faceId,
|
|
4886
|
+
lookup,
|
|
4887
|
+
PROPSRIG_EYE_PATHS.leftY,
|
|
4888
|
+
inputConstraints,
|
|
4889
|
+
DEFAULT_GAZE_CONTROL
|
|
4890
|
+
),
|
|
4891
|
+
rightX: createControl(
|
|
4892
|
+
faceId,
|
|
4893
|
+
lookup,
|
|
4894
|
+
PROPSRIG_EYE_PATHS.rightX,
|
|
4895
|
+
inputConstraints,
|
|
4896
|
+
DEFAULT_GAZE_CONTROL
|
|
4897
|
+
),
|
|
4898
|
+
rightY: createControl(
|
|
4899
|
+
faceId,
|
|
4900
|
+
lookup,
|
|
4901
|
+
PROPSRIG_EYE_PATHS.rightY,
|
|
4902
|
+
inputConstraints,
|
|
4903
|
+
DEFAULT_GAZE_CONTROL
|
|
4904
|
+
)
|
|
4905
|
+
};
|
|
4906
|
+
const coupledGazeControls = {
|
|
4907
|
+
leftX: createControl(
|
|
4908
|
+
faceId,
|
|
4909
|
+
lookup,
|
|
4910
|
+
"/gaze/left_right",
|
|
4911
|
+
inputConstraints,
|
|
4912
|
+
DEFAULT_GAZE_CONTROL
|
|
4913
|
+
),
|
|
4914
|
+
leftY: createControl(
|
|
4915
|
+
faceId,
|
|
4916
|
+
lookup,
|
|
4917
|
+
"/gaze/up_down",
|
|
4918
|
+
inputConstraints,
|
|
4919
|
+
DEFAULT_GAZE_CONTROL
|
|
4920
|
+
),
|
|
4921
|
+
rightX: createControl(
|
|
4922
|
+
faceId,
|
|
4923
|
+
lookup,
|
|
4924
|
+
"/gaze/left_right_copy",
|
|
4925
|
+
inputConstraints,
|
|
4926
|
+
DEFAULT_GAZE_CONTROL
|
|
4927
|
+
) ?? createControl(
|
|
4928
|
+
faceId,
|
|
4929
|
+
lookup,
|
|
4930
|
+
"/gaze/left_right",
|
|
4931
|
+
inputConstraints,
|
|
4932
|
+
DEFAULT_GAZE_CONTROL
|
|
4933
|
+
),
|
|
4934
|
+
rightY: createControl(
|
|
4935
|
+
faceId,
|
|
4936
|
+
lookup,
|
|
4937
|
+
"/gaze/up_down_copy",
|
|
4938
|
+
inputConstraints,
|
|
4939
|
+
DEFAULT_GAZE_CONTROL
|
|
4940
|
+
) ?? createControl(
|
|
4941
|
+
faceId,
|
|
4942
|
+
lookup,
|
|
4943
|
+
"/gaze/up_down",
|
|
4944
|
+
inputConstraints,
|
|
4945
|
+
DEFAULT_GAZE_CONTROL
|
|
4946
|
+
)
|
|
4947
|
+
};
|
|
4948
|
+
const blinkFromLids = createControl(
|
|
4949
|
+
faceId,
|
|
4950
|
+
lookup,
|
|
4951
|
+
"/lids/blink",
|
|
4952
|
+
inputConstraints,
|
|
4953
|
+
DEFAULT_BLINK_CONTROL
|
|
4954
|
+
);
|
|
4955
|
+
const blinkDirect = createControl(
|
|
4956
|
+
faceId,
|
|
4957
|
+
lookup,
|
|
4958
|
+
"/blink",
|
|
4959
|
+
inputConstraints,
|
|
4960
|
+
DEFAULT_BLINK_CONTROL
|
|
4961
|
+
);
|
|
4962
|
+
if (hasAll(standardVizijControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
4963
|
+
return {
|
|
4964
|
+
faceId,
|
|
4965
|
+
gazeSource: "standard-vizij",
|
|
4966
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
4967
|
+
eyes: {
|
|
4968
|
+
leftX: standardVizijControls.leftX,
|
|
4969
|
+
leftY: standardVizijControls.leftY,
|
|
4970
|
+
rightX: standardVizijControls.rightX,
|
|
4971
|
+
rightY: standardVizijControls.rightY
|
|
4972
|
+
},
|
|
4973
|
+
eyelids: {
|
|
4974
|
+
leftUpper: standardVizijControls.leftUpper,
|
|
4975
|
+
rightUpper: standardVizijControls.rightUpper
|
|
4976
|
+
},
|
|
4977
|
+
blink: blinkFromLids ?? blinkDirect
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
if (hasAll(legacyStandardControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
4981
|
+
return {
|
|
4982
|
+
faceId,
|
|
4983
|
+
gazeSource: "standard",
|
|
4984
|
+
blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
|
|
4985
|
+
eyes: {
|
|
4986
|
+
leftX: legacyStandardControls.leftX,
|
|
4987
|
+
leftY: legacyStandardControls.leftY,
|
|
4988
|
+
rightX: legacyStandardControls.rightX,
|
|
4989
|
+
rightY: legacyStandardControls.rightY
|
|
4990
|
+
},
|
|
4991
|
+
eyelids: {
|
|
4992
|
+
leftUpper: legacyStandardControls.leftUpper,
|
|
4993
|
+
rightUpper: legacyStandardControls.rightUpper
|
|
4994
|
+
},
|
|
4995
|
+
blink: blinkDirect ?? blinkFromLids
|
|
4996
|
+
};
|
|
4997
|
+
}
|
|
4998
|
+
if (hasAll(propsrigControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
4999
|
+
return {
|
|
5000
|
+
faceId,
|
|
5001
|
+
gazeSource: "propsrig",
|
|
5002
|
+
blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
|
|
5003
|
+
eyes: {
|
|
5004
|
+
leftX: propsrigControls.leftX,
|
|
5005
|
+
leftY: propsrigControls.leftY,
|
|
5006
|
+
rightX: propsrigControls.rightX,
|
|
5007
|
+
rightY: propsrigControls.rightY
|
|
5008
|
+
},
|
|
5009
|
+
eyelids: {
|
|
5010
|
+
leftUpper: null,
|
|
5011
|
+
rightUpper: null
|
|
5012
|
+
},
|
|
5013
|
+
blink: blinkDirect ?? blinkFromLids
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
if (hasAll(coupledGazeControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
5017
|
+
return {
|
|
5018
|
+
faceId,
|
|
5019
|
+
gazeSource: "coupled-gaze",
|
|
5020
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
5021
|
+
eyes: {
|
|
5022
|
+
leftX: coupledGazeControls.leftX,
|
|
5023
|
+
leftY: coupledGazeControls.leftY,
|
|
5024
|
+
rightX: coupledGazeControls.rightX,
|
|
5025
|
+
rightY: coupledGazeControls.rightY
|
|
5026
|
+
},
|
|
5027
|
+
eyelids: {
|
|
5028
|
+
leftUpper: null,
|
|
5029
|
+
rightUpper: null
|
|
5030
|
+
},
|
|
5031
|
+
blink: blinkFromLids ?? blinkDirect
|
|
5032
|
+
};
|
|
5033
|
+
}
|
|
5034
|
+
return {
|
|
5035
|
+
faceId,
|
|
5036
|
+
gazeSource: "none",
|
|
5037
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
5038
|
+
eyes: {
|
|
5039
|
+
leftX: null,
|
|
5040
|
+
leftY: null,
|
|
5041
|
+
rightX: null,
|
|
5042
|
+
rightY: null
|
|
5043
|
+
},
|
|
5044
|
+
eyelids: {
|
|
5045
|
+
leftUpper: null,
|
|
5046
|
+
rightUpper: null
|
|
5047
|
+
},
|
|
5048
|
+
blink: blinkFromLids ?? blinkDirect
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
function mapNormalizedControlValue(control, normalizedValue) {
|
|
5052
|
+
const value = clamp(normalizedValue, -1, 1);
|
|
5053
|
+
if (value === 0) {
|
|
5054
|
+
return control.defaultValue;
|
|
5055
|
+
}
|
|
5056
|
+
if (value > 0) {
|
|
5057
|
+
return control.defaultValue + (control.max - control.defaultValue) * value;
|
|
5058
|
+
}
|
|
5059
|
+
return control.defaultValue + (control.defaultValue - control.min) * value;
|
|
5060
|
+
}
|
|
5061
|
+
function mapUnitControlValue(control, unitValue) {
|
|
5062
|
+
const value = clamp(unitValue, 0, 1);
|
|
5063
|
+
return control.defaultValue + (control.max - control.defaultValue) * value;
|
|
5064
|
+
}
|
|
1821
5065
|
export {
|
|
5066
|
+
EMOTION_POSE_KEYS,
|
|
5067
|
+
EXPRESSIVE_EMOTION_POSE_KEYS,
|
|
5068
|
+
POSE_WEIGHT_INPUT_PATH_PREFIX,
|
|
5069
|
+
VISEME_POSE_KEYS,
|
|
1822
5070
|
VizijRuntimeFace,
|
|
1823
5071
|
VizijRuntimeProvider,
|
|
5072
|
+
buildPoseWeightInputPathSegment,
|
|
5073
|
+
buildPoseWeightPathMap,
|
|
5074
|
+
buildPoseWeightRelativePath,
|
|
5075
|
+
buildRigInputPath,
|
|
5076
|
+
buildSemanticPoseWeightPathMap,
|
|
5077
|
+
filterPosesBySemanticKind,
|
|
5078
|
+
getPoseSemanticKey,
|
|
5079
|
+
mapNormalizedControlValue,
|
|
5080
|
+
mapUnitControlValue,
|
|
5081
|
+
normalizePoseSemanticKey,
|
|
5082
|
+
resolveFaceControls,
|
|
5083
|
+
resolvePoseMembership,
|
|
5084
|
+
resolvePoseSemantics,
|
|
5085
|
+
resolveRuntimeUpdatePlan,
|
|
5086
|
+
useOptionalVizijRuntime,
|
|
1824
5087
|
useRigInput,
|
|
1825
5088
|
useVizijOutputs,
|
|
1826
5089
|
useVizijRuntime
|