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