@vizij/runtime-react 0.0.14 → 0.2.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 +18 -4
- package/dist/index.cjs +4077 -0
- package/dist/index.d.cts +342 -0
- package/dist/index.d.ts +173 -9
- package/dist/index.js +3035 -346
- package/package.json +15 -11
- package/dist/index.d.mts +0 -178
- package/dist/index.mjs +0 -1363
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4077 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
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,
|
|
27
|
+
VizijRuntimeFace: () => VizijRuntimeFace,
|
|
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,
|
|
44
|
+
useRigInput: () => useRigInput,
|
|
45
|
+
useVizijOutputs: () => useVizijOutputs,
|
|
46
|
+
useVizijRuntime: () => useVizijRuntime
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/VizijRuntimeProvider.tsx
|
|
51
|
+
var import_react2 = require("react");
|
|
52
|
+
var import_render = require("@vizij/render");
|
|
53
|
+
var import_orchestrator_react = require("@vizij/orchestrator-react");
|
|
54
|
+
var import_node_graph_authoring = require("@vizij/node-graph-authoring");
|
|
55
|
+
var import_value_json2 = require("@vizij/value-json");
|
|
56
|
+
var import_utils = require("@vizij/utils");
|
|
57
|
+
|
|
58
|
+
// src/context.ts
|
|
59
|
+
var import_react = require("react");
|
|
60
|
+
var VizijRuntimeContext = (0, import_react.createContext)(null);
|
|
61
|
+
|
|
62
|
+
// src/updatePolicy.ts
|
|
63
|
+
function applyRuntimeGraphBundle(base, bundle) {
|
|
64
|
+
const next = {
|
|
65
|
+
...base
|
|
66
|
+
};
|
|
67
|
+
const hasRigOverride = Object.prototype.hasOwnProperty.call(bundle, "rig");
|
|
68
|
+
const hasPoseOverride = Object.prototype.hasOwnProperty.call(bundle, "pose");
|
|
69
|
+
const hasAnimationsOverride = Object.prototype.hasOwnProperty.call(
|
|
70
|
+
bundle,
|
|
71
|
+
"animations"
|
|
72
|
+
);
|
|
73
|
+
const hasProgramsOverride = Object.prototype.hasOwnProperty.call(
|
|
74
|
+
bundle,
|
|
75
|
+
"programs"
|
|
76
|
+
);
|
|
77
|
+
if (hasRigOverride) {
|
|
78
|
+
if (bundle.rig) {
|
|
79
|
+
next.rig = bundle.rig;
|
|
80
|
+
} else {
|
|
81
|
+
delete next.rig;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (hasPoseOverride) {
|
|
85
|
+
if (bundle.pose) {
|
|
86
|
+
next.pose = bundle.pose;
|
|
87
|
+
} else {
|
|
88
|
+
delete next.pose;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (hasAnimationsOverride) {
|
|
92
|
+
next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
|
|
93
|
+
}
|
|
94
|
+
if (hasProgramsOverride) {
|
|
95
|
+
next.programs = Array.isArray(bundle.programs) ? bundle.programs : void 0;
|
|
96
|
+
}
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
function normalizeSpecPayload(value) {
|
|
100
|
+
if (!value) {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return JSON.stringify(value);
|
|
105
|
+
} catch {
|
|
106
|
+
return String(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function glbSignature(glb) {
|
|
110
|
+
if (glb.kind === "url") {
|
|
111
|
+
return `url:${glb.src}`;
|
|
112
|
+
}
|
|
113
|
+
if (glb.kind === "blob") {
|
|
114
|
+
return `blob:${glb.blob?.size ?? 0}`;
|
|
115
|
+
}
|
|
116
|
+
return `world:${normalizeSpecPayload(glb.world)}`;
|
|
117
|
+
}
|
|
118
|
+
function graphSignature(graph) {
|
|
119
|
+
if (!graph) {
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
const id = graph.id ?? "";
|
|
123
|
+
return `${id}:${normalizeSpecPayload(graph.spec ?? graph.ir ?? null)}`;
|
|
124
|
+
}
|
|
125
|
+
function poseSignature(pose) {
|
|
126
|
+
if (!pose) {
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
const graph = pose.graph;
|
|
130
|
+
const config = pose.config;
|
|
131
|
+
const graphPart = graph ? graphSignature({ id: graph.id, spec: graph.spec }) : "";
|
|
132
|
+
const configPart = config ? normalizeSpecPayload(config) : "";
|
|
133
|
+
return `${graphPart}:${configPart}`;
|
|
134
|
+
}
|
|
135
|
+
function animationsSignature(animations) {
|
|
136
|
+
if (!Array.isArray(animations) || animations.length === 0) {
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
return animations.map((animation) => {
|
|
140
|
+
const id = animation.id ?? "";
|
|
141
|
+
const clipSignature = normalizeSpecPayload(animation.clip ?? null);
|
|
142
|
+
const setupSignature = normalizeSpecPayload(animation.setup ?? null);
|
|
143
|
+
const weightSignature = animation.weight == null ? "" : String(animation.weight);
|
|
144
|
+
return `${id}:${clipSignature}:${setupSignature}:${weightSignature}`;
|
|
145
|
+
}).sort().join("|");
|
|
146
|
+
}
|
|
147
|
+
function programsSignature(programs) {
|
|
148
|
+
if (!Array.isArray(programs) || programs.length === 0) {
|
|
149
|
+
return "";
|
|
150
|
+
}
|
|
151
|
+
return programs.map((program) => {
|
|
152
|
+
const id = program.id ?? "";
|
|
153
|
+
const label = program.label ?? "";
|
|
154
|
+
const graphId = program.graph?.id ?? "";
|
|
155
|
+
const graphPayload = normalizeSpecPayload(
|
|
156
|
+
program.graph?.spec ?? program.graph?.ir ?? null
|
|
157
|
+
);
|
|
158
|
+
const resetValues = normalizeSpecPayload(program.resetValues ?? null);
|
|
159
|
+
return `${id}:${label}:${graphId}:${graphPayload}:${resetValues}`;
|
|
160
|
+
}).sort().join("|");
|
|
161
|
+
}
|
|
162
|
+
function resolveRuntimeUpdatePlan(previous, next, tier) {
|
|
163
|
+
if (!previous) {
|
|
164
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
165
|
+
}
|
|
166
|
+
const glbChanged = glbSignature(previous.glb) !== glbSignature(next.glb);
|
|
167
|
+
const rigChanged = graphSignature(previous.rig) !== graphSignature(next.rig);
|
|
168
|
+
const poseChanged = poseSignature(previous.pose) !== poseSignature(next.pose);
|
|
169
|
+
const rigReferenceChanged = previous.rig?.id !== next.rig?.id || previous.rig?.spec !== next.rig?.spec || previous.rig?.ir !== next.rig?.ir;
|
|
170
|
+
const poseReferenceChanged = previous.pose?.graph?.id !== next.pose?.graph?.id || previous.pose?.graph?.spec !== next.pose?.graph?.spec || previous.pose?.config !== next.pose?.config;
|
|
171
|
+
const graphsChanged = rigChanged || poseChanged || rigReferenceChanged || poseReferenceChanged;
|
|
172
|
+
const animationsChanged = animationsSignature(previous.animations) !== animationsSignature(next.animations);
|
|
173
|
+
const programsChanged = programsSignature(previous.programs) !== programsSignature(next.programs);
|
|
174
|
+
const controllersChanged = graphsChanged || animationsChanged || programsChanged;
|
|
175
|
+
if (tier === "assets") {
|
|
176
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
177
|
+
}
|
|
178
|
+
if (tier === "graphs") {
|
|
179
|
+
if (glbChanged) {
|
|
180
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
181
|
+
}
|
|
182
|
+
return { reloadAssets: false, reregisterGraphs: controllersChanged };
|
|
183
|
+
}
|
|
184
|
+
if (glbChanged) {
|
|
185
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
186
|
+
}
|
|
187
|
+
if (controllersChanged) {
|
|
188
|
+
return { reloadAssets: false, reregisterGraphs: true };
|
|
189
|
+
}
|
|
190
|
+
return { reloadAssets: false, reregisterGraphs: false };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/utils/graph.ts
|
|
194
|
+
function getNodes(spec) {
|
|
195
|
+
if (!spec || typeof spec !== "object") {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const maybeNodes = spec.nodes;
|
|
199
|
+
if (!Array.isArray(maybeNodes)) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
return maybeNodes;
|
|
203
|
+
}
|
|
204
|
+
function collectOutputPaths(spec) {
|
|
205
|
+
const nodes = getNodes(spec);
|
|
206
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
207
|
+
nodes.forEach((node) => {
|
|
208
|
+
if (typeof node !== "object" || !node) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (String(node.type ?? "").toLowerCase() !== "output") {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const path = node.params?.path;
|
|
215
|
+
if (typeof path === "string" && path.trim()) {
|
|
216
|
+
outputs.add(path.trim());
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
return Array.from(outputs);
|
|
220
|
+
}
|
|
221
|
+
function collectInputPaths(spec) {
|
|
222
|
+
const nodes = getNodes(spec);
|
|
223
|
+
const inputs = /* @__PURE__ */ new Set();
|
|
224
|
+
nodes.forEach((node) => {
|
|
225
|
+
if (String(node.type ?? "").toLowerCase() !== "input") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const path = node.params?.path;
|
|
229
|
+
if (typeof path === "string" && path.trim()) {
|
|
230
|
+
inputs.add(path.trim());
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return Array.from(inputs);
|
|
234
|
+
}
|
|
235
|
+
function collectInputPathMap(spec) {
|
|
236
|
+
const map = {};
|
|
237
|
+
const addVariant = (key, path, force = false) => {
|
|
238
|
+
if (!key || !force && map[key]) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
map[key] = path;
|
|
242
|
+
};
|
|
243
|
+
const nodes = getNodes(spec);
|
|
244
|
+
nodes.forEach((node) => {
|
|
245
|
+
if (String(node.type ?? "").toLowerCase() !== "input") {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const path = node.params?.path;
|
|
249
|
+
if (typeof path !== "string" || !path.trim()) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const id = String(node.id ?? "");
|
|
253
|
+
const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
|
|
254
|
+
const trimmedPath = path.trim();
|
|
255
|
+
addVariant(key, trimmedPath);
|
|
256
|
+
if (key.startsWith("direct_")) {
|
|
257
|
+
addVariant(key.slice("direct_".length), trimmedPath, true);
|
|
258
|
+
}
|
|
259
|
+
if (key.startsWith("pose_control_")) {
|
|
260
|
+
addVariant(key.slice("pose_control_".length), trimmedPath);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return map;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/utils/posePaths.ts
|
|
267
|
+
var POSE_WEIGHT_INPUT_PATH_PREFIX = "/poses/";
|
|
268
|
+
var VISEME_POSE_KEYS = [
|
|
269
|
+
"a",
|
|
270
|
+
"at",
|
|
271
|
+
"b",
|
|
272
|
+
"e",
|
|
273
|
+
"e_2",
|
|
274
|
+
"f",
|
|
275
|
+
"i",
|
|
276
|
+
"k",
|
|
277
|
+
"m",
|
|
278
|
+
"o",
|
|
279
|
+
"o_2",
|
|
280
|
+
"p",
|
|
281
|
+
"r",
|
|
282
|
+
"s",
|
|
283
|
+
"t",
|
|
284
|
+
"t_2",
|
|
285
|
+
"u"
|
|
286
|
+
];
|
|
287
|
+
var EXPRESSIVE_EMOTION_POSE_KEYS = [
|
|
288
|
+
"concerned",
|
|
289
|
+
"happy",
|
|
290
|
+
"sad",
|
|
291
|
+
"sleepy",
|
|
292
|
+
"surprise"
|
|
293
|
+
];
|
|
294
|
+
var EMOTION_POSE_KEYS = [
|
|
295
|
+
"concerned",
|
|
296
|
+
"happy",
|
|
297
|
+
"neutral",
|
|
298
|
+
"sad",
|
|
299
|
+
"sleepy",
|
|
300
|
+
"surprise",
|
|
301
|
+
"angry"
|
|
302
|
+
];
|
|
303
|
+
var VISEME_GROUP_NEEDLES = ["viseme", "phoneme", "lip", "mouth"];
|
|
304
|
+
var EMOTION_GROUP_NEEDLES = ["emotion", "expression", "mood", "affect"];
|
|
305
|
+
var VISEME_POSE_KEY_SET = new Set(VISEME_POSE_KEYS);
|
|
306
|
+
var EMOTION_POSE_KEY_SET = new Set(EMOTION_POSE_KEYS);
|
|
307
|
+
var POSE_KEY_ALIASES = {
|
|
308
|
+
concern: "concerned",
|
|
309
|
+
surprised: "surprise"
|
|
310
|
+
};
|
|
311
|
+
function buildRigInputPath(faceId, path) {
|
|
312
|
+
let trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
313
|
+
if (!trimmed) {
|
|
314
|
+
return `rig/${faceId}`;
|
|
315
|
+
}
|
|
316
|
+
while (trimmed.startsWith("rig/")) {
|
|
317
|
+
const segments = trimmed.split("/");
|
|
318
|
+
if (segments.length >= 3) {
|
|
319
|
+
const existingFaceId = segments[1];
|
|
320
|
+
const remainder = segments.slice(2).join("/");
|
|
321
|
+
if (existingFaceId === faceId) {
|
|
322
|
+
return trimmed;
|
|
323
|
+
}
|
|
324
|
+
trimmed = remainder || "";
|
|
325
|
+
} else {
|
|
326
|
+
trimmed = segments.slice(1).join("/");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const suffix = trimmed ? `/${trimmed}` : "";
|
|
330
|
+
return `rig/${faceId}${suffix}`;
|
|
331
|
+
}
|
|
332
|
+
function normalizePoseWeightPathSegment(value) {
|
|
333
|
+
const trimmed = value?.trim() ?? "";
|
|
334
|
+
if (!trimmed) {
|
|
335
|
+
return "pose";
|
|
336
|
+
}
|
|
337
|
+
const normalized = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
338
|
+
return normalized || "pose";
|
|
339
|
+
}
|
|
340
|
+
function buildPoseWeightInputPathSegment(poseId) {
|
|
341
|
+
return normalizePoseWeightPathSegment(poseId);
|
|
342
|
+
}
|
|
343
|
+
function buildPoseWeightRelativePath(poseId) {
|
|
344
|
+
return `${POSE_WEIGHT_INPUT_PATH_PREFIX}${buildPoseWeightInputPathSegment(
|
|
345
|
+
poseId
|
|
346
|
+
)}.weight`;
|
|
347
|
+
}
|
|
348
|
+
function buildPoseWeightPathMap(poses, faceId) {
|
|
349
|
+
const faceSegment = faceId?.trim() || "face";
|
|
350
|
+
const map = /* @__PURE__ */ new Map();
|
|
351
|
+
poses.forEach((pose) => {
|
|
352
|
+
map.set(
|
|
353
|
+
pose.id,
|
|
354
|
+
buildRigInputPath(faceSegment, buildPoseWeightRelativePath(pose.id))
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
return map;
|
|
358
|
+
}
|
|
359
|
+
function normalizePoseSemanticKey(value) {
|
|
360
|
+
const trimmed = value?.trim() ?? "";
|
|
361
|
+
if (!trimmed) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
365
|
+
if (!normalized) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return POSE_KEY_ALIASES[normalized] ?? normalized;
|
|
369
|
+
}
|
|
370
|
+
function derivePoseSemanticKeyFromId(poseId) {
|
|
371
|
+
const trimmed = poseId?.trim() ?? "";
|
|
372
|
+
if (!trimmed) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const stripped = trimmed.replace(/^pose_d_/i, "").replace(/^pose_/i, "").replace(/^d_/i, "").replace(/_d$/i, "");
|
|
376
|
+
return normalizePoseSemanticKey(stripped);
|
|
377
|
+
}
|
|
378
|
+
function getPoseSemanticKey(pose) {
|
|
379
|
+
return normalizePoseSemanticKey(pose.name) ?? derivePoseSemanticKeyFromId(pose.id) ?? null;
|
|
380
|
+
}
|
|
381
|
+
function normalizePoseGroupPath(value) {
|
|
382
|
+
const trimmed = value?.trim() ?? "";
|
|
383
|
+
if (!trimmed) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
return trimmed.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
|
|
387
|
+
}
|
|
388
|
+
function sanitizePoseGroupId(value, fallback) {
|
|
389
|
+
const normalized = (value ?? "").trim().replace(/[^a-zA-Z0-9_/-]+/g, "_").replace(/^\/+|\/+$/g, "").replace(/\/+/g, "_");
|
|
390
|
+
if (!normalized) {
|
|
391
|
+
return fallback.replace(/\//g, "_");
|
|
392
|
+
}
|
|
393
|
+
return normalized;
|
|
394
|
+
}
|
|
395
|
+
function buildPoseGroupLookup(groups) {
|
|
396
|
+
const byId = /* @__PURE__ */ new Map();
|
|
397
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
398
|
+
const orderById = /* @__PURE__ */ new Map();
|
|
399
|
+
(groups ?? []).forEach((group, index) => {
|
|
400
|
+
const path = normalizePoseGroupPath(group.path ?? group.name ?? group.id);
|
|
401
|
+
if (!path) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const id = sanitizePoseGroupId(group.id, path);
|
|
405
|
+
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;
|
|
406
|
+
const normalized = {
|
|
407
|
+
...group,
|
|
408
|
+
id,
|
|
409
|
+
path,
|
|
410
|
+
name: humanizedName
|
|
411
|
+
};
|
|
412
|
+
byId.set(id, normalized);
|
|
413
|
+
if (!orderById.has(id)) {
|
|
414
|
+
orderById.set(id, index);
|
|
415
|
+
}
|
|
416
|
+
if (!byPath.has(path)) {
|
|
417
|
+
byPath.set(path, normalized);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return { byId, byPath, orderById };
|
|
421
|
+
}
|
|
422
|
+
function orderPoseMembershipIds(groupIds, groups) {
|
|
423
|
+
const { orderById } = buildPoseGroupLookup(groups);
|
|
424
|
+
const unique = Array.from(
|
|
425
|
+
new Set(
|
|
426
|
+
Array.from(groupIds).map((groupId) => groupId.trim()).filter((groupId) => groupId.length > 0)
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
unique.sort((left, right) => {
|
|
430
|
+
const leftIndex = orderById.get(left);
|
|
431
|
+
const rightIndex = orderById.get(right);
|
|
432
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) {
|
|
433
|
+
return leftIndex - rightIndex;
|
|
434
|
+
}
|
|
435
|
+
if (leftIndex !== void 0) {
|
|
436
|
+
return -1;
|
|
437
|
+
}
|
|
438
|
+
if (rightIndex !== void 0) {
|
|
439
|
+
return 1;
|
|
440
|
+
}
|
|
441
|
+
const leftPath = normalizePoseGroupPath(left) ?? left;
|
|
442
|
+
const rightPath = normalizePoseGroupPath(right) ?? right;
|
|
443
|
+
const byPath = leftPath.localeCompare(rightPath);
|
|
444
|
+
if (byPath !== 0) {
|
|
445
|
+
return byPath;
|
|
446
|
+
}
|
|
447
|
+
return left.localeCompare(right);
|
|
448
|
+
});
|
|
449
|
+
return unique;
|
|
450
|
+
}
|
|
451
|
+
function valueHasNeedle(value, needles) {
|
|
452
|
+
const normalized = normalizePoseSemanticKey(value);
|
|
453
|
+
if (!normalized) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
return needles.some((needle) => normalized.includes(needle));
|
|
457
|
+
}
|
|
458
|
+
function resolvePoseMembership(pose, groups) {
|
|
459
|
+
const { byId, byPath } = buildPoseGroupLookup(groups);
|
|
460
|
+
const resolvedGroupIds = [];
|
|
461
|
+
const pathById = /* @__PURE__ */ new Map();
|
|
462
|
+
const addMembership = (groupId, path) => {
|
|
463
|
+
if (!groupId) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (!resolvedGroupIds.includes(groupId)) {
|
|
467
|
+
resolvedGroupIds.push(groupId);
|
|
468
|
+
}
|
|
469
|
+
if (!path) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const existingPath = pathById.get(groupId);
|
|
473
|
+
const normalizedGroupIdPath = normalizePoseGroupPath(groupId);
|
|
474
|
+
const normalizedExistingPath = normalizePoseGroupPath(existingPath) ?? existingPath ?? null;
|
|
475
|
+
const normalizedIncomingPath = normalizePoseGroupPath(path) ?? path ?? null;
|
|
476
|
+
const shouldPromotePath = normalizedExistingPath === null || normalizedGroupIdPath !== null && normalizedExistingPath === normalizedGroupIdPath && normalizedIncomingPath !== normalizedGroupIdPath;
|
|
477
|
+
if (shouldPromotePath) {
|
|
478
|
+
pathById.set(groupId, path);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const addByPath = (rawPath) => {
|
|
482
|
+
const normalizedPath = normalizePoseGroupPath(rawPath);
|
|
483
|
+
if (!normalizedPath) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const existing = byPath.get(normalizedPath);
|
|
487
|
+
if (existing) {
|
|
488
|
+
addMembership(existing.id, existing.path);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
addMembership(sanitizePoseGroupId(null, normalizedPath), normalizedPath);
|
|
492
|
+
};
|
|
493
|
+
const addById = (rawId) => {
|
|
494
|
+
const trimmed = rawId?.trim() ?? "";
|
|
495
|
+
if (!trimmed) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const normalizedPath = normalizePoseGroupPath(trimmed);
|
|
499
|
+
const normalizedId = sanitizePoseGroupId(trimmed, trimmed);
|
|
500
|
+
const matchedById = byId.get(trimmed) ?? byId.get(normalizedId);
|
|
501
|
+
if (matchedById) {
|
|
502
|
+
addMembership(matchedById.id, matchedById.path);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (normalizedPath) {
|
|
506
|
+
const matchedByPath = byPath.get(normalizedPath);
|
|
507
|
+
if (matchedByPath) {
|
|
508
|
+
addMembership(matchedByPath.id, matchedByPath.path);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
addMembership(
|
|
513
|
+
normalizedId,
|
|
514
|
+
normalizedPath && normalizedPath.length > 0 ? normalizedPath : null
|
|
515
|
+
);
|
|
516
|
+
};
|
|
517
|
+
pose.groupIds?.forEach((groupId) => addById(groupId));
|
|
518
|
+
addById(pose.groupId);
|
|
519
|
+
addByPath(pose.group);
|
|
520
|
+
const orderedGroupIds = orderPoseMembershipIds(resolvedGroupIds, groups);
|
|
521
|
+
const primaryGroupId = orderedGroupIds[0] ?? null;
|
|
522
|
+
const primaryGroupPath = primaryGroupId ? byId.get(primaryGroupId)?.path ?? pathById.get(primaryGroupId) ?? null : null;
|
|
523
|
+
const groupPathsById = {};
|
|
524
|
+
orderedGroupIds.forEach((groupId) => {
|
|
525
|
+
const path = byId.get(groupId)?.path ?? pathById.get(groupId) ?? null;
|
|
526
|
+
if (path) {
|
|
527
|
+
groupPathsById[groupId] = path;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
return {
|
|
531
|
+
groupIds: orderedGroupIds,
|
|
532
|
+
primaryGroupId,
|
|
533
|
+
primaryGroupPath,
|
|
534
|
+
groupPathsById
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function poseMatchesGroupKind(pose, groups, needles) {
|
|
538
|
+
const membership = resolvePoseMembership(pose, groups);
|
|
539
|
+
if (valueHasNeedle(pose.group, needles) || valueHasNeedle(pose.groupId, needles)) {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
if (pose.groupIds?.some((groupId) => valueHasNeedle(groupId, needles))) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
return membership.groupIds.some((groupId) => {
|
|
546
|
+
const path = membership.groupPathsById[groupId] ?? null;
|
|
547
|
+
const group = (groups ?? []).find((entry) => entry.id === groupId) ?? null;
|
|
548
|
+
return valueHasNeedle(groupId, needles) || valueHasNeedle(path, needles) || valueHasNeedle(group?.name, needles);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function resolvePoseSemantics(pose, groups) {
|
|
552
|
+
const membership = resolvePoseMembership(pose, groups);
|
|
553
|
+
const key = getPoseSemanticKey(pose);
|
|
554
|
+
const looksLikeVisemeGroup = poseMatchesGroupKind(
|
|
555
|
+
pose,
|
|
556
|
+
groups,
|
|
557
|
+
VISEME_GROUP_NEEDLES
|
|
558
|
+
);
|
|
559
|
+
const looksLikeEmotionGroup = poseMatchesGroupKind(
|
|
560
|
+
pose,
|
|
561
|
+
groups,
|
|
562
|
+
EMOTION_GROUP_NEEDLES
|
|
563
|
+
);
|
|
564
|
+
let kind = "other";
|
|
565
|
+
if (looksLikeVisemeGroup || key && VISEME_POSE_KEY_SET.has(key)) {
|
|
566
|
+
kind = "viseme";
|
|
567
|
+
} else if (looksLikeEmotionGroup || key && EMOTION_POSE_KEY_SET.has(key)) {
|
|
568
|
+
kind = "emotion";
|
|
569
|
+
}
|
|
570
|
+
return { key, kind, membership };
|
|
571
|
+
}
|
|
572
|
+
function filterPosesBySemanticKind(poses, groups, kind) {
|
|
573
|
+
return poses.filter(
|
|
574
|
+
(pose) => resolvePoseSemantics(pose, groups).kind === kind
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
function buildSemanticPoseWeightPathMap(poses, groups, faceId, kind) {
|
|
578
|
+
const map = /* @__PURE__ */ new Map();
|
|
579
|
+
const pathMap = buildPoseWeightPathMap(poses, faceId);
|
|
580
|
+
poses.forEach((pose) => {
|
|
581
|
+
const semantics = resolvePoseSemantics(pose, groups);
|
|
582
|
+
const path = pathMap.get(pose.id);
|
|
583
|
+
if (semantics.kind !== kind || !semantics.key || !path || map.has(semantics.key)) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
map.set(semantics.key, path);
|
|
587
|
+
});
|
|
588
|
+
return map;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/utils/poseRuntime.ts
|
|
592
|
+
function shouldUseLegacyPoseWeightFallback(hasPoseGraph) {
|
|
593
|
+
return !hasPoseGraph;
|
|
594
|
+
}
|
|
595
|
+
function resolvePoseControlInputPath({
|
|
596
|
+
inputId,
|
|
597
|
+
basePath,
|
|
598
|
+
rigInputPathMap,
|
|
599
|
+
hasNativePoseControlInput
|
|
600
|
+
}) {
|
|
601
|
+
if (!inputId.trim()) {
|
|
602
|
+
return void 0;
|
|
603
|
+
}
|
|
604
|
+
return rigInputPathMap[inputId] ?? rigInputPathMap[`pose_control_${inputId}`] ?? rigInputPathMap[`direct_${inputId}`] ?? (hasNativePoseControlInput ? basePath : void 0);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/utils/clipPlayback.ts
|
|
608
|
+
var EPSILON = 1e-6;
|
|
609
|
+
function toFiniteNumber(value, fallback) {
|
|
610
|
+
const parsed = Number(value);
|
|
611
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
612
|
+
}
|
|
613
|
+
function normaliseInterpolation(interpolation) {
|
|
614
|
+
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
615
|
+
if (mode === "step") {
|
|
616
|
+
return "step";
|
|
617
|
+
}
|
|
618
|
+
if (mode === "cubic" || mode === "cubicspline") {
|
|
619
|
+
return "cubic";
|
|
620
|
+
}
|
|
621
|
+
return "linear";
|
|
622
|
+
}
|
|
623
|
+
function asNumericKeyframe(keyframe) {
|
|
624
|
+
if (!keyframe || typeof keyframe !== "object") {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const time = Number(keyframe.time);
|
|
628
|
+
const value = Number(keyframe.value);
|
|
629
|
+
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
const inTangentRaw = keyframe.inTangent;
|
|
633
|
+
const outTangentRaw = keyframe.outTangent;
|
|
634
|
+
const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
|
|
635
|
+
const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
|
|
636
|
+
return {
|
|
637
|
+
time,
|
|
638
|
+
value,
|
|
639
|
+
inTangent,
|
|
640
|
+
outTangent
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function getNumericKeyframes(track) {
|
|
644
|
+
const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
|
|
645
|
+
const numeric = keyframes.map((keyframe) => asNumericKeyframe(keyframe)).filter((keyframe) => Boolean(keyframe));
|
|
646
|
+
if (numeric.length <= 1) {
|
|
647
|
+
return numeric;
|
|
648
|
+
}
|
|
649
|
+
return [...numeric].sort((a, b) => a.time - b.time);
|
|
650
|
+
}
|
|
651
|
+
function resolveTangent(value, fallback) {
|
|
652
|
+
const parsed = Number(value);
|
|
653
|
+
if (Number.isFinite(parsed)) {
|
|
654
|
+
return parsed;
|
|
655
|
+
}
|
|
656
|
+
return fallback;
|
|
657
|
+
}
|
|
658
|
+
function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
|
|
659
|
+
const t = factor;
|
|
660
|
+
const t2 = t * t;
|
|
661
|
+
const t3 = t2 * t;
|
|
662
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
663
|
+
const h10 = t3 - 2 * t2 + t;
|
|
664
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
665
|
+
const h11 = t3 - t2;
|
|
666
|
+
return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
|
|
667
|
+
}
|
|
668
|
+
function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
669
|
+
const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
|
|
670
|
+
if (!clip || typeof clip !== "object") {
|
|
671
|
+
return fallback;
|
|
672
|
+
}
|
|
673
|
+
const clipDuration = Number(clip.duration);
|
|
674
|
+
if (Number.isFinite(clipDuration) && clipDuration > 0) {
|
|
675
|
+
return clipDuration;
|
|
676
|
+
}
|
|
677
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
678
|
+
let maxTime = 0;
|
|
679
|
+
tracks.forEach((track) => {
|
|
680
|
+
const keyframes = getNumericKeyframes(track);
|
|
681
|
+
const last = keyframes[keyframes.length - 1];
|
|
682
|
+
if (last && last.time > maxTime) {
|
|
683
|
+
maxTime = last.time;
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
return maxTime > 0 ? maxTime : fallback;
|
|
687
|
+
}
|
|
688
|
+
function clampAnimationTime(time, duration) {
|
|
689
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
690
|
+
return 0;
|
|
691
|
+
}
|
|
692
|
+
if (!Number.isFinite(time)) {
|
|
693
|
+
return 0;
|
|
694
|
+
}
|
|
695
|
+
if (time <= 0) {
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
if (time >= duration) {
|
|
699
|
+
return duration;
|
|
700
|
+
}
|
|
701
|
+
return time;
|
|
702
|
+
}
|
|
703
|
+
function advanceClipTime(state, dt) {
|
|
704
|
+
const duration = Math.max(0, toFiniteNumber(state.duration, 0));
|
|
705
|
+
const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
|
|
706
|
+
const currentTime = clampAnimationTime(state.time, duration);
|
|
707
|
+
const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
|
|
708
|
+
if (!state.playing || delta <= 0) {
|
|
709
|
+
return { time: currentTime, completed: false };
|
|
710
|
+
}
|
|
711
|
+
if (duration <= 0) {
|
|
712
|
+
return { time: 0, completed: true };
|
|
713
|
+
}
|
|
714
|
+
const nextTime = currentTime + delta;
|
|
715
|
+
if (state.loop) {
|
|
716
|
+
if (nextTime < duration) {
|
|
717
|
+
return { time: nextTime, completed: false };
|
|
718
|
+
}
|
|
719
|
+
const wrapped = (nextTime % duration + duration) % duration;
|
|
720
|
+
return { time: wrapped, completed: false };
|
|
721
|
+
}
|
|
722
|
+
if (nextTime >= duration - EPSILON) {
|
|
723
|
+
return { time: duration, completed: true };
|
|
724
|
+
}
|
|
725
|
+
return { time: nextTime, completed: false };
|
|
726
|
+
}
|
|
727
|
+
function sampleTrackAtTime(track, timeSeconds) {
|
|
728
|
+
const keyframes = getNumericKeyframes(track);
|
|
729
|
+
if (keyframes.length === 0) {
|
|
730
|
+
return 0;
|
|
731
|
+
}
|
|
732
|
+
if (keyframes.length === 1) {
|
|
733
|
+
return keyframes[0].value;
|
|
734
|
+
}
|
|
735
|
+
const mode = normaliseInterpolation(track.interpolation);
|
|
736
|
+
const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
|
|
737
|
+
const first = keyframes[0];
|
|
738
|
+
if (time <= first.time + EPSILON) {
|
|
739
|
+
return first.value;
|
|
740
|
+
}
|
|
741
|
+
const last = keyframes[keyframes.length - 1];
|
|
742
|
+
if (time >= last.time - EPSILON) {
|
|
743
|
+
return last.value;
|
|
744
|
+
}
|
|
745
|
+
for (let i = 0; i < keyframes.length - 1; i += 1) {
|
|
746
|
+
const current = keyframes[i];
|
|
747
|
+
const next = keyframes[i + 1];
|
|
748
|
+
const start = current.time;
|
|
749
|
+
const end = next.time;
|
|
750
|
+
const duration = end - start;
|
|
751
|
+
if (duration <= EPSILON) {
|
|
752
|
+
if (time <= end + EPSILON) {
|
|
753
|
+
return next.value;
|
|
754
|
+
}
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (Math.abs(time - end) <= EPSILON) {
|
|
758
|
+
return next.value;
|
|
759
|
+
}
|
|
760
|
+
if (time < end) {
|
|
761
|
+
const factor = (time - start) / duration;
|
|
762
|
+
if (mode === "step") {
|
|
763
|
+
return current.value;
|
|
764
|
+
}
|
|
765
|
+
if (mode === "cubic") {
|
|
766
|
+
const slope = (next.value - current.value) / duration;
|
|
767
|
+
const outTangent = resolveTangent(current.outTangent, slope);
|
|
768
|
+
const inTangent = resolveTangent(next.inTangent, slope);
|
|
769
|
+
return sampleHermite(
|
|
770
|
+
current.value,
|
|
771
|
+
next.value,
|
|
772
|
+
outTangent,
|
|
773
|
+
inTangent,
|
|
774
|
+
factor,
|
|
775
|
+
duration
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
return current.value + (next.value - current.value) * factor;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return last.value;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/utils/animationBridge.ts
|
|
785
|
+
function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
|
|
786
|
+
const normalizedChannel = channel.trim().replace(/^\/+/, "");
|
|
787
|
+
if (!normalizedChannel) {
|
|
788
|
+
return [];
|
|
789
|
+
}
|
|
790
|
+
const outputPaths = /* @__PURE__ */ new Set([normalizedChannel]);
|
|
791
|
+
if (normalizedChannel.startsWith("animation/")) {
|
|
792
|
+
return Array.from(outputPaths).sort(
|
|
793
|
+
(left, right) => left.localeCompare(right)
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
if (rigInputMap && Object.keys(rigInputMap).length > 0) {
|
|
797
|
+
const candidateKeys = /* @__PURE__ */ new Set([normalizedChannel]);
|
|
798
|
+
const rigChannelMatch2 = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
|
|
799
|
+
if (rigChannelMatch2?.[1]) {
|
|
800
|
+
candidateKeys.add(rigChannelMatch2[1]);
|
|
801
|
+
}
|
|
802
|
+
candidateKeys.forEach((key) => {
|
|
803
|
+
const mapped = rigInputMap[key];
|
|
804
|
+
const normalized = mapped?.trim().replace(/^\/+/, "");
|
|
805
|
+
if (normalized) {
|
|
806
|
+
outputPaths.add(normalized);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
const suffix = normalizedChannel.includes("/") ? normalizedChannel : `/${normalizedChannel}`;
|
|
810
|
+
Object.values(rigInputMap).forEach((mappedPath) => {
|
|
811
|
+
const normalized = mappedPath?.trim().replace(/^\/+/, "");
|
|
812
|
+
if (!normalized) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (normalized === normalizedChannel || normalized.endsWith(suffix)) {
|
|
816
|
+
outputPaths.add(normalized);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
if (!faceId) {
|
|
821
|
+
return Array.from(outputPaths).sort(
|
|
822
|
+
(left, right) => left.localeCompare(right)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
const rigChannelMatch = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
|
|
826
|
+
if (rigChannelMatch?.[1]) {
|
|
827
|
+
outputPaths.add(`rig/${faceId}/${rigChannelMatch[1]}`);
|
|
828
|
+
} else if (!normalizedChannel.startsWith("rig/")) {
|
|
829
|
+
outputPaths.add(`rig/${faceId}/${normalizedChannel}`);
|
|
830
|
+
}
|
|
831
|
+
return Array.from(outputPaths).sort(
|
|
832
|
+
(left, right) => left.localeCompare(right)
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
|
|
836
|
+
const outputPaths = /* @__PURE__ */ new Set();
|
|
837
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
838
|
+
tracks.forEach((track) => {
|
|
839
|
+
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
840
|
+
if (!channel) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
844
|
+
(path) => {
|
|
845
|
+
outputPaths.add(path);
|
|
846
|
+
}
|
|
847
|
+
);
|
|
848
|
+
});
|
|
849
|
+
return Array.from(outputPaths).sort(
|
|
850
|
+
(left, right) => left.localeCompare(right)
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
|
|
854
|
+
const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
|
|
855
|
+
const outputValues = /* @__PURE__ */ new Map();
|
|
856
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
857
|
+
tracks.forEach((track) => {
|
|
858
|
+
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
859
|
+
if (!channel) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const sampledValue = sampleTrackAtTime(
|
|
863
|
+
track,
|
|
864
|
+
timeSeconds
|
|
865
|
+
);
|
|
866
|
+
const weightedValue = sampledValue * appliedWeight;
|
|
867
|
+
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
868
|
+
(path) => {
|
|
869
|
+
outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
|
|
870
|
+
}
|
|
871
|
+
);
|
|
872
|
+
});
|
|
873
|
+
return outputValues;
|
|
874
|
+
}
|
|
875
|
+
function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
|
|
876
|
+
const operations = [];
|
|
877
|
+
const changedPaths = /* @__PURE__ */ new Set();
|
|
878
|
+
previousAggregate.forEach((previousValue, path) => {
|
|
879
|
+
const nextValue = nextAggregate.get(path);
|
|
880
|
+
if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
881
|
+
changedPaths.add(path);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
nextAggregate.forEach((nextValue, path) => {
|
|
885
|
+
const previousValue = previousAggregate.get(path);
|
|
886
|
+
if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
887
|
+
changedPaths.add(path);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
changedPaths.forEach((path) => {
|
|
891
|
+
const nextValue = nextAggregate.get(path);
|
|
892
|
+
if (nextValue === void 0) {
|
|
893
|
+
operations.push({ kind: "clear", path });
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
operations.push({ kind: "set", path, value: nextValue });
|
|
897
|
+
});
|
|
898
|
+
return operations;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/utils/valueConversion.ts
|
|
902
|
+
var import_value_json = require("@vizij/value-json");
|
|
903
|
+
function numericArrayToRaw(arr) {
|
|
904
|
+
const normalised = arr.map((entry) => Number(entry ?? 0));
|
|
905
|
+
switch (normalised.length) {
|
|
906
|
+
case 2:
|
|
907
|
+
return {
|
|
908
|
+
x: normalised[0],
|
|
909
|
+
y: normalised[1],
|
|
910
|
+
r: normalised[0],
|
|
911
|
+
g: normalised[1]
|
|
912
|
+
};
|
|
913
|
+
case 3:
|
|
914
|
+
return {
|
|
915
|
+
x: normalised[0],
|
|
916
|
+
y: normalised[1],
|
|
917
|
+
z: normalised[2],
|
|
918
|
+
r: normalised[0],
|
|
919
|
+
g: normalised[1],
|
|
920
|
+
b: normalised[2]
|
|
921
|
+
};
|
|
922
|
+
case 4:
|
|
923
|
+
return {
|
|
924
|
+
x: normalised[0],
|
|
925
|
+
y: normalised[1],
|
|
926
|
+
z: normalised[2],
|
|
927
|
+
w: normalised[3],
|
|
928
|
+
r: normalised[0],
|
|
929
|
+
g: normalised[1],
|
|
930
|
+
b: normalised[2],
|
|
931
|
+
a: normalised[3]
|
|
932
|
+
};
|
|
933
|
+
default:
|
|
934
|
+
return normalised;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function valueJSONToRaw(value) {
|
|
938
|
+
if (value == null) {
|
|
939
|
+
return void 0;
|
|
940
|
+
}
|
|
941
|
+
if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
|
|
942
|
+
return value;
|
|
943
|
+
}
|
|
944
|
+
if (Array.isArray(value)) {
|
|
945
|
+
return value.map((entry) => valueJSONToRaw(entry));
|
|
946
|
+
}
|
|
947
|
+
if (typeof value === "object" && !("type" in value)) {
|
|
948
|
+
const entries = Object.entries(value).map(([key, entry]) => [
|
|
949
|
+
key,
|
|
950
|
+
valueJSONToRaw(entry)
|
|
951
|
+
]);
|
|
952
|
+
return Object.fromEntries(entries);
|
|
953
|
+
}
|
|
954
|
+
if (!(0, import_value_json.isNormalizedValue)(value)) {
|
|
955
|
+
return void 0;
|
|
956
|
+
}
|
|
957
|
+
switch (value.type) {
|
|
958
|
+
case "float": {
|
|
959
|
+
const num = (0, import_value_json.valueAsNumber)(value);
|
|
960
|
+
return typeof num === "number" ? num : void 0;
|
|
961
|
+
}
|
|
962
|
+
case "bool": {
|
|
963
|
+
const boolVal = (0, import_value_json.valueAsBool)(value);
|
|
964
|
+
return typeof boolVal === "boolean" ? boolVal : void 0;
|
|
965
|
+
}
|
|
966
|
+
case "text": {
|
|
967
|
+
const text = (0, import_value_json.valueAsText)(value);
|
|
968
|
+
return typeof text === "string" ? text : void 0;
|
|
969
|
+
}
|
|
970
|
+
case "vec2":
|
|
971
|
+
case "vec3":
|
|
972
|
+
case "vec4":
|
|
973
|
+
case "quat":
|
|
974
|
+
case "vector": {
|
|
975
|
+
const vec = (0, import_value_json.valueAsVector)(value);
|
|
976
|
+
return vec ? numericArrayToRaw(vec) : void 0;
|
|
977
|
+
}
|
|
978
|
+
case "colorrgba": {
|
|
979
|
+
const color = (0, import_value_json.valueAsColorRgba)(value);
|
|
980
|
+
if (!color) {
|
|
981
|
+
return void 0;
|
|
982
|
+
}
|
|
983
|
+
const [r = 0, g = 0, b = 0, a = 1] = color;
|
|
984
|
+
return { r, g, b, a };
|
|
985
|
+
}
|
|
986
|
+
case "transform": {
|
|
987
|
+
const transform = (0, import_value_json.valueAsTransform)(value);
|
|
988
|
+
if (!transform) {
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
translation: numericArrayToRaw(transform.translation),
|
|
993
|
+
rotation: numericArrayToRaw(transform.rotation),
|
|
994
|
+
scale: numericArrayToRaw(transform.scale)
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
case "record": {
|
|
998
|
+
const entries = Object.entries(value.data ?? {}).map(([key, entry]) => [
|
|
999
|
+
key,
|
|
1000
|
+
valueJSONToRaw(entry)
|
|
1001
|
+
]);
|
|
1002
|
+
return Object.fromEntries(entries);
|
|
1003
|
+
}
|
|
1004
|
+
case "enum": {
|
|
1005
|
+
const [tag, inner] = value.data;
|
|
1006
|
+
return {
|
|
1007
|
+
tag,
|
|
1008
|
+
value: valueJSONToRaw(inner)
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
default:
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// src/VizijRuntimeProvider.tsx
|
|
1017
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1018
|
+
var ACTIVE_GRACE_MS = 250;
|
|
1019
|
+
var VISIBLE_IDLE_FPS = 30;
|
|
1020
|
+
var HIDDEN_IDLE_FPS = 1;
|
|
1021
|
+
var DEFAULT_MERGE = {
|
|
1022
|
+
outputs: "add",
|
|
1023
|
+
intermediate: "add"
|
|
1024
|
+
};
|
|
1025
|
+
var DEFAULT_DURATION = 0.35;
|
|
1026
|
+
var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
|
|
1027
|
+
var DEV_MODE = (() => {
|
|
1028
|
+
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
1029
|
+
return typeof nodeEnv === "string" && nodeEnv === "development";
|
|
1030
|
+
})();
|
|
1031
|
+
function isRuntimeDebugEnabled() {
|
|
1032
|
+
if (DEV_MODE) {
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
return Boolean(
|
|
1036
|
+
globalThis.__VIZIJ_RUNTIME_DEBUG__
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
var EASINGS = {
|
|
1040
|
+
linear: (t) => t,
|
|
1041
|
+
easeIn: (t) => t * t,
|
|
1042
|
+
easeOut: (t) => 1 - (1 - t) * (1 - t),
|
|
1043
|
+
easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
|
|
1044
|
+
};
|
|
1045
|
+
function resolveEasing(easing) {
|
|
1046
|
+
if (typeof easing === "function") {
|
|
1047
|
+
return easing;
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof easing === "string" && easing in EASINGS) {
|
|
1050
|
+
return EASINGS[easing];
|
|
1051
|
+
}
|
|
1052
|
+
return EASINGS.linear;
|
|
1053
|
+
}
|
|
1054
|
+
function findRootId(world) {
|
|
1055
|
+
let fallback = null;
|
|
1056
|
+
for (const entry of Object.values(world)) {
|
|
1057
|
+
if (!entry || typeof entry !== "object" || entry.type !== "group") {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (entry.rootBounds && entry.id) {
|
|
1061
|
+
return entry.id;
|
|
1062
|
+
}
|
|
1063
|
+
if (!fallback && entry.id) {
|
|
1064
|
+
fallback = entry.id;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return fallback;
|
|
1068
|
+
}
|
|
1069
|
+
function normalisePath(path) {
|
|
1070
|
+
if (!path) {
|
|
1071
|
+
return path;
|
|
1072
|
+
}
|
|
1073
|
+
return path.startsWith("debug/") ? path.slice("debug/".length) : path;
|
|
1074
|
+
}
|
|
1075
|
+
function normaliseBundleKind(kind) {
|
|
1076
|
+
return typeof kind === "string" ? kind.toLowerCase() : "";
|
|
1077
|
+
}
|
|
1078
|
+
function addConstraintVariant(map, key, constraint) {
|
|
1079
|
+
if (!key) return;
|
|
1080
|
+
if (!map[key]) {
|
|
1081
|
+
map[key] = constraint;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function stripRigFacePrefix(path) {
|
|
1085
|
+
const trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
1086
|
+
const match = /^rig\/[^/]+\/(.+)$/.exec(trimmed);
|
|
1087
|
+
if (match && match[1]) {
|
|
1088
|
+
return match[1];
|
|
1089
|
+
}
|
|
1090
|
+
if (trimmed.startsWith("rig/")) {
|
|
1091
|
+
return trimmed.slice("rig/".length);
|
|
1092
|
+
}
|
|
1093
|
+
return trimmed;
|
|
1094
|
+
}
|
|
1095
|
+
function extractInputConstraints(spec, extraInputs, namespace) {
|
|
1096
|
+
if (!spec || typeof spec !== "object") {
|
|
1097
|
+
return {};
|
|
1098
|
+
}
|
|
1099
|
+
const inputs = [];
|
|
1100
|
+
if (Array.isArray(extraInputs)) {
|
|
1101
|
+
inputs.push(...extraInputs);
|
|
1102
|
+
}
|
|
1103
|
+
const entries = spec.metadata?.vizij?.inputs;
|
|
1104
|
+
if (Array.isArray(entries)) {
|
|
1105
|
+
entries.forEach((entry) => {
|
|
1106
|
+
if (entry && typeof entry === "object") {
|
|
1107
|
+
inputs.push(entry);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
if (inputs.length === 0) {
|
|
1112
|
+
return {};
|
|
1113
|
+
}
|
|
1114
|
+
const map = {};
|
|
1115
|
+
inputs.forEach((entry) => {
|
|
1116
|
+
const path = entry.path;
|
|
1117
|
+
if (typeof path !== "string") return;
|
|
1118
|
+
const namespaced = namespaceTypedPath(path, namespace);
|
|
1119
|
+
const stripped = stripRigFacePrefix(path);
|
|
1120
|
+
const strippedNamespaced = stripped ? namespaceTypedPath(stripped, namespace) : stripped;
|
|
1121
|
+
const min = entry.range?.min;
|
|
1122
|
+
const max = entry.range?.max;
|
|
1123
|
+
const defaultValue = entry.defaultValue;
|
|
1124
|
+
const constraint = {
|
|
1125
|
+
...Number.isFinite(Number(min)) ? { min: Number(min) } : {},
|
|
1126
|
+
...Number.isFinite(Number(max)) ? { max: Number(max) } : {},
|
|
1127
|
+
...Number.isFinite(Number(defaultValue)) ? { defaultValue: Number(defaultValue) } : {}
|
|
1128
|
+
};
|
|
1129
|
+
addConstraintVariant(map, namespaced, constraint);
|
|
1130
|
+
addConstraintVariant(map, path, constraint);
|
|
1131
|
+
if (stripped) {
|
|
1132
|
+
addConstraintVariant(map, stripped, constraint);
|
|
1133
|
+
}
|
|
1134
|
+
if (strippedNamespaced) {
|
|
1135
|
+
addConstraintVariant(map, strippedNamespaced, constraint);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
return map;
|
|
1139
|
+
}
|
|
1140
|
+
function namespaceTypedPath(path, namespace) {
|
|
1141
|
+
const trimmed = typeof path === "string" ? path.trim() : "";
|
|
1142
|
+
if (!trimmed) {
|
|
1143
|
+
return trimmed;
|
|
1144
|
+
}
|
|
1145
|
+
const prefix = `${namespace}/`;
|
|
1146
|
+
if (trimmed.startsWith(prefix)) {
|
|
1147
|
+
return trimmed;
|
|
1148
|
+
}
|
|
1149
|
+
if (trimmed.startsWith("debug/")) {
|
|
1150
|
+
const rest = trimmed.slice("debug/".length);
|
|
1151
|
+
const namespacedRest = namespaceTypedPath(rest, namespace);
|
|
1152
|
+
return namespacedRest.startsWith("debug/") ? namespacedRest : `debug/${namespacedRest}`;
|
|
1153
|
+
}
|
|
1154
|
+
return `${prefix}${trimmed}`;
|
|
1155
|
+
}
|
|
1156
|
+
function stripNamespace(path, namespace) {
|
|
1157
|
+
const prefix = `${namespace}/`;
|
|
1158
|
+
if (path.startsWith(prefix)) {
|
|
1159
|
+
return path.slice(prefix.length);
|
|
1160
|
+
}
|
|
1161
|
+
const debugPrefix = `debug/${prefix}`;
|
|
1162
|
+
if (path.startsWith(debugPrefix)) {
|
|
1163
|
+
return path.slice(debugPrefix.length);
|
|
1164
|
+
}
|
|
1165
|
+
if (path.startsWith("debug/")) {
|
|
1166
|
+
return path.slice("debug/".length);
|
|
1167
|
+
}
|
|
1168
|
+
return path;
|
|
1169
|
+
}
|
|
1170
|
+
function namespaceControllerId(id, namespace, kind = "graph") {
|
|
1171
|
+
if (!id) {
|
|
1172
|
+
return void 0;
|
|
1173
|
+
}
|
|
1174
|
+
const trimmed = id.trim();
|
|
1175
|
+
if (!trimmed) {
|
|
1176
|
+
return void 0;
|
|
1177
|
+
}
|
|
1178
|
+
const prefix = `${namespace}/${kind}/`;
|
|
1179
|
+
if (trimmed.startsWith(prefix)) {
|
|
1180
|
+
return trimmed;
|
|
1181
|
+
}
|
|
1182
|
+
return `${prefix}${trimmed}`;
|
|
1183
|
+
}
|
|
1184
|
+
function namespaceSubscriptions(subs, namespace) {
|
|
1185
|
+
if (!subs) {
|
|
1186
|
+
return void 0;
|
|
1187
|
+
}
|
|
1188
|
+
const inputs = Array.isArray(subs.inputs) ? subs.inputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
|
|
1189
|
+
const outputs = Array.isArray(subs.outputs) ? subs.outputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
|
|
1190
|
+
if (!inputs && !outputs) {
|
|
1191
|
+
return subs;
|
|
1192
|
+
}
|
|
1193
|
+
return {
|
|
1194
|
+
...subs,
|
|
1195
|
+
...inputs ? { inputs } : {},
|
|
1196
|
+
...outputs ? { outputs } : {}
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function namespaceGraphSpec(spec, namespace) {
|
|
1200
|
+
if (!spec || typeof spec !== "object") {
|
|
1201
|
+
return spec;
|
|
1202
|
+
}
|
|
1203
|
+
const nodes = spec.nodes;
|
|
1204
|
+
if (!Array.isArray(nodes)) {
|
|
1205
|
+
return spec;
|
|
1206
|
+
}
|
|
1207
|
+
let changed = false;
|
|
1208
|
+
const nextNodes = nodes.map((node) => {
|
|
1209
|
+
if (!node || typeof node !== "object") {
|
|
1210
|
+
return node;
|
|
1211
|
+
}
|
|
1212
|
+
const path = node.params?.path;
|
|
1213
|
+
if (typeof path !== "string") {
|
|
1214
|
+
return node;
|
|
1215
|
+
}
|
|
1216
|
+
const namespacedPath = namespaceTypedPath(path, namespace);
|
|
1217
|
+
if (namespacedPath === path) {
|
|
1218
|
+
return node;
|
|
1219
|
+
}
|
|
1220
|
+
changed = true;
|
|
1221
|
+
return {
|
|
1222
|
+
...node,
|
|
1223
|
+
params: {
|
|
1224
|
+
...node.params ?? {},
|
|
1225
|
+
path: namespacedPath
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
});
|
|
1229
|
+
if (!changed) {
|
|
1230
|
+
return spec;
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
...spec,
|
|
1234
|
+
nodes: nextNodes
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function stripNulls(value) {
|
|
1238
|
+
if (value === null) {
|
|
1239
|
+
return void 0;
|
|
1240
|
+
}
|
|
1241
|
+
if (Array.isArray(value)) {
|
|
1242
|
+
const next2 = value.map((entry) => stripNulls(entry)).filter((entry) => entry !== void 0 && entry !== null);
|
|
1243
|
+
return next2;
|
|
1244
|
+
}
|
|
1245
|
+
if (typeof value !== "object" || value === void 0) {
|
|
1246
|
+
return value;
|
|
1247
|
+
}
|
|
1248
|
+
const next = {};
|
|
1249
|
+
Object.entries(value).forEach(([key, entry]) => {
|
|
1250
|
+
if (entry === null) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const cleaned = stripNulls(entry);
|
|
1254
|
+
if (cleaned === void 0) {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
next[key] = cleaned;
|
|
1258
|
+
});
|
|
1259
|
+
return next;
|
|
1260
|
+
}
|
|
1261
|
+
var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1262
|
+
function pickBundleGraph(bundle, preferredKinds) {
|
|
1263
|
+
if (!bundle?.graphs || bundle.graphs.length === 0) {
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
const preferred = preferredKinds.map((kind) => kind.toLowerCase());
|
|
1267
|
+
for (const entry of bundle.graphs) {
|
|
1268
|
+
if (!entry || typeof entry !== "object") {
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
const kind = normaliseBundleKind(entry.kind);
|
|
1272
|
+
if (preferred.includes(kind)) {
|
|
1273
|
+
return entry;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (bundle.graphs.length === 1) {
|
|
1277
|
+
return bundle.graphs[0] ?? null;
|
|
1278
|
+
}
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
function extractIrGraph(payload) {
|
|
1282
|
+
if (!payload || typeof payload !== "object") {
|
|
1283
|
+
return void 0;
|
|
1284
|
+
}
|
|
1285
|
+
return payload;
|
|
1286
|
+
}
|
|
1287
|
+
function convertBundleGraph(entry) {
|
|
1288
|
+
if (!entry || !entry.id) {
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
const rawSpec = entry.spec;
|
|
1292
|
+
const inputMetadata = extractVizijInputMetadata(
|
|
1293
|
+
rawSpec
|
|
1294
|
+
);
|
|
1295
|
+
const spec = rawSpec ? stripVizijMetadata(rawSpec) : void 0;
|
|
1296
|
+
const ir = extractIrGraph(entry.ir);
|
|
1297
|
+
if (!spec && !ir) {
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
return {
|
|
1301
|
+
id: entry.id,
|
|
1302
|
+
spec,
|
|
1303
|
+
ir: ir ?? null,
|
|
1304
|
+
inputMetadata
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
function resolveGraphSpec(asset, context) {
|
|
1308
|
+
if (asset.spec) {
|
|
1309
|
+
return stripVizijMetadata(asset.spec);
|
|
1310
|
+
}
|
|
1311
|
+
if (asset.ir) {
|
|
1312
|
+
try {
|
|
1313
|
+
const compiled = (0, import_node_graph_authoring.compileIrGraph)(asset.ir, { preferLegacySpec: false });
|
|
1314
|
+
if (compiled.issues && compiled.issues.length > 0) {
|
|
1315
|
+
console.warn(
|
|
1316
|
+
`[vizij-runtime] IR compile for graph "${context}" reported issues`,
|
|
1317
|
+
compiled.issues
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
return stripVizijMetadata(compiled.spec);
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
console.warn(
|
|
1323
|
+
`[vizij-runtime] Failed to compile IR graph "${context}"`,
|
|
1324
|
+
error
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
function stripVizijMetadata(spec) {
|
|
1331
|
+
if (!spec || typeof spec !== "object") {
|
|
1332
|
+
return spec;
|
|
1333
|
+
}
|
|
1334
|
+
const cloned = {
|
|
1335
|
+
...spec,
|
|
1336
|
+
nodes: spec.nodes ? spec.nodes.map((node) => ({ ...node })) : spec.nodes,
|
|
1337
|
+
edges: spec.edges ? spec.edges.map((edge) => ({ ...edge })) : spec.edges
|
|
1338
|
+
};
|
|
1339
|
+
if (cloned.metadata && typeof cloned.metadata === "object") {
|
|
1340
|
+
const metadata = { ...cloned.metadata };
|
|
1341
|
+
if ("vizij" in metadata) {
|
|
1342
|
+
delete metadata.vizij;
|
|
1343
|
+
}
|
|
1344
|
+
if (Object.keys(metadata).length === 0) {
|
|
1345
|
+
delete cloned.metadata;
|
|
1346
|
+
} else {
|
|
1347
|
+
cloned.metadata = metadata;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return cloned;
|
|
1351
|
+
}
|
|
1352
|
+
function extractVizijInputMetadata(spec) {
|
|
1353
|
+
if (!spec || typeof spec !== "object") {
|
|
1354
|
+
return [];
|
|
1355
|
+
}
|
|
1356
|
+
const inputs = spec.metadata?.vizij?.inputs;
|
|
1357
|
+
if (!Array.isArray(inputs)) {
|
|
1358
|
+
return [];
|
|
1359
|
+
}
|
|
1360
|
+
return inputs.map((entry) => {
|
|
1361
|
+
if (!entry || typeof entry !== "object") {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
return entry;
|
|
1365
|
+
}).filter(Boolean);
|
|
1366
|
+
}
|
|
1367
|
+
function convertBundleAnimations(entries) {
|
|
1368
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1369
|
+
return [];
|
|
1370
|
+
}
|
|
1371
|
+
return entries.filter(
|
|
1372
|
+
(entry) => Boolean(entry && typeof entry.id === "string" && entry.clip)
|
|
1373
|
+
).map((entry) => ({
|
|
1374
|
+
id: entry.id,
|
|
1375
|
+
clip: entry.clip
|
|
1376
|
+
}));
|
|
1377
|
+
}
|
|
1378
|
+
function resolveChannelId(track) {
|
|
1379
|
+
if (typeof track.componentId !== "string" || track.componentId.length === 0) {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
if (typeof track.component === "string" && track.component.length > 0) {
|
|
1383
|
+
return `${track.componentId}:${track.component}`;
|
|
1384
|
+
}
|
|
1385
|
+
const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : void 0;
|
|
1386
|
+
const valueSize = track.valueSize != null ? Number(track.valueSize) : void 0;
|
|
1387
|
+
if (Number.isInteger(rawIndex) && Number.isFinite(rawIndex) && rawIndex >= 0 && Number.isFinite(valueSize) && valueSize > 1) {
|
|
1388
|
+
return `${track.componentId}:${rawIndex}`;
|
|
1389
|
+
}
|
|
1390
|
+
return track.componentId;
|
|
1391
|
+
}
|
|
1392
|
+
function convertExtractedAnimations(clips) {
|
|
1393
|
+
if (!Array.isArray(clips) || clips.length === 0) {
|
|
1394
|
+
return [];
|
|
1395
|
+
}
|
|
1396
|
+
const assets = [];
|
|
1397
|
+
clips.forEach((clip) => {
|
|
1398
|
+
const clipTracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
1399
|
+
if (clipTracks.length === 0) {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
const convertedTracks = [];
|
|
1403
|
+
clipTracks.forEach((track) => {
|
|
1404
|
+
const channelId = resolveChannelId(track);
|
|
1405
|
+
if (!channelId) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const rawTimes = Array.isArray(track.times) ? track.times : [];
|
|
1409
|
+
const rawValues = Array.isArray(track.values) ? track.values : [];
|
|
1410
|
+
if (rawTimes.length === 0 || rawValues.length === 0) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const times = [];
|
|
1414
|
+
for (const entry of rawTimes) {
|
|
1415
|
+
const time = Number(entry);
|
|
1416
|
+
if (!Number.isFinite(time)) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
times.push(time);
|
|
1420
|
+
}
|
|
1421
|
+
const values = [];
|
|
1422
|
+
for (const entry of rawValues) {
|
|
1423
|
+
const value = Number(entry);
|
|
1424
|
+
if (!Number.isFinite(value)) {
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
values.push(value);
|
|
1428
|
+
}
|
|
1429
|
+
const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
|
|
1430
|
+
const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
|
|
1431
|
+
const interpolationRaw = typeof track.interpolation === "string" ? track.interpolation.trim().toLowerCase() : "linear";
|
|
1432
|
+
const interpolation = interpolationRaw === "step" ? "step" : interpolationRaw === "cubic" || interpolationRaw === "cubicspline" ? "cubic" : "linear";
|
|
1433
|
+
const isCubic = interpolation === "cubic";
|
|
1434
|
+
const hasTripletTangents = isCubic && values.length === times.length * valueSize * 3;
|
|
1435
|
+
const hasFlatValues = values.length === times.length * valueSize;
|
|
1436
|
+
if (!hasTripletTangents && !hasFlatValues) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
|
|
1440
|
+
const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
|
|
1441
|
+
const keyframes = [];
|
|
1442
|
+
times.forEach((time, index) => {
|
|
1443
|
+
const flatBase = index * valueSize + componentIndex;
|
|
1444
|
+
const valueBase = hasTripletTangents ? index * valueSize * 3 + valueSize + componentIndex : flatBase;
|
|
1445
|
+
const value = values[valueBase];
|
|
1446
|
+
if (!Number.isFinite(value)) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const keyframe = {
|
|
1450
|
+
time,
|
|
1451
|
+
value
|
|
1452
|
+
};
|
|
1453
|
+
if (hasTripletTangents) {
|
|
1454
|
+
const inBase = index * valueSize * 3 + componentIndex;
|
|
1455
|
+
const outBase = index * valueSize * 3 + valueSize * 2 + componentIndex;
|
|
1456
|
+
const inTangent = values[inBase];
|
|
1457
|
+
const outTangent = values[outBase];
|
|
1458
|
+
if (Number.isFinite(inTangent)) {
|
|
1459
|
+
keyframe.inTangent = inTangent;
|
|
1460
|
+
}
|
|
1461
|
+
if (Number.isFinite(outTangent)) {
|
|
1462
|
+
keyframe.outTangent = outTangent;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
keyframes.push(keyframe);
|
|
1466
|
+
});
|
|
1467
|
+
if (keyframes.length === 0) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
convertedTracks.push({
|
|
1471
|
+
channel: channelId,
|
|
1472
|
+
keyframes,
|
|
1473
|
+
interpolation
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
if (convertedTracks.length === 0) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
const durationFromTracks = convertedTracks.reduce((maxTime, track) => {
|
|
1480
|
+
const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
|
|
1481
|
+
if (keyframes.length === 0) {
|
|
1482
|
+
return maxTime;
|
|
1483
|
+
}
|
|
1484
|
+
const lastKeyframe = keyframes[keyframes.length - 1];
|
|
1485
|
+
const time = Number(lastKeyframe?.time ?? 0);
|
|
1486
|
+
if (!Number.isFinite(time)) {
|
|
1487
|
+
return maxTime;
|
|
1488
|
+
}
|
|
1489
|
+
return time > maxTime ? time : maxTime;
|
|
1490
|
+
}, 0);
|
|
1491
|
+
const duration = typeof clip.duration === "number" && Number.isFinite(clip.duration) ? clip.duration : durationFromTracks;
|
|
1492
|
+
const clipId = typeof clip.id === "string" && clip.id.length > 0 ? clip.id : typeof clip.name === "string" && clip.name.length > 0 ? clip.name : `gltf-animation-${assets.length}`;
|
|
1493
|
+
const metadata = clip.metadata && typeof clip.metadata === "object" && !Array.isArray(clip.metadata) ? clip.metadata : void 0;
|
|
1494
|
+
assets.push({
|
|
1495
|
+
id: clipId,
|
|
1496
|
+
clip: {
|
|
1497
|
+
id: clipId,
|
|
1498
|
+
name: typeof clip.name === "string" ? clip.name : clipId,
|
|
1499
|
+
duration,
|
|
1500
|
+
tracks: convertedTracks,
|
|
1501
|
+
metadata
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
return assets;
|
|
1506
|
+
}
|
|
1507
|
+
function pickExtractedAnimations(asset) {
|
|
1508
|
+
if (!asset || typeof asset !== "object") {
|
|
1509
|
+
return void 0;
|
|
1510
|
+
}
|
|
1511
|
+
const animations = asset.animations;
|
|
1512
|
+
if (!Array.isArray(animations)) {
|
|
1513
|
+
return void 0;
|
|
1514
|
+
}
|
|
1515
|
+
return animations;
|
|
1516
|
+
}
|
|
1517
|
+
function mergeAnimationLists(explicit, fromBundle) {
|
|
1518
|
+
if (!explicit?.length && fromBundle.length === 0) {
|
|
1519
|
+
return void 0;
|
|
1520
|
+
}
|
|
1521
|
+
if (!explicit?.length) {
|
|
1522
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
1523
|
+
}
|
|
1524
|
+
if (fromBundle.length === 0) {
|
|
1525
|
+
return explicit;
|
|
1526
|
+
}
|
|
1527
|
+
const seen = new Set(explicit.map((anim) => anim.id));
|
|
1528
|
+
let changed = false;
|
|
1529
|
+
const merged = [...explicit];
|
|
1530
|
+
for (const anim of fromBundle) {
|
|
1531
|
+
if (!anim.id || seen.has(anim.id)) {
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
merged.push(anim);
|
|
1535
|
+
seen.add(anim.id);
|
|
1536
|
+
changed = true;
|
|
1537
|
+
}
|
|
1538
|
+
return changed ? merged : explicit;
|
|
1539
|
+
}
|
|
1540
|
+
function extractProgramResetValues(value) {
|
|
1541
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1542
|
+
return void 0;
|
|
1543
|
+
}
|
|
1544
|
+
const entries = Object.entries(value).filter(
|
|
1545
|
+
([, rawValue]) => Number.isFinite(Number(rawValue))
|
|
1546
|
+
);
|
|
1547
|
+
if (entries.length === 0) {
|
|
1548
|
+
return void 0;
|
|
1549
|
+
}
|
|
1550
|
+
return Object.fromEntries(
|
|
1551
|
+
entries.map(([path, rawValue]) => [path, Number(rawValue)])
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
function convertBundlePrograms(entries) {
|
|
1555
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1556
|
+
return [];
|
|
1557
|
+
}
|
|
1558
|
+
return entries.filter((entry) => normaliseBundleKind(entry?.kind) === "motiongraph").map((entry) => {
|
|
1559
|
+
const graph = convertBundleGraph(entry);
|
|
1560
|
+
if (!graph) {
|
|
1561
|
+
return null;
|
|
1562
|
+
}
|
|
1563
|
+
const metadata = entry.metadata && typeof entry.metadata === "object" && !Array.isArray(entry.metadata) ? entry.metadata : void 0;
|
|
1564
|
+
return {
|
|
1565
|
+
id: entry.id,
|
|
1566
|
+
label: typeof entry.label === "string" ? entry.label : void 0,
|
|
1567
|
+
graph,
|
|
1568
|
+
metadata,
|
|
1569
|
+
resetValues: extractProgramResetValues(metadata?.resetValues)
|
|
1570
|
+
};
|
|
1571
|
+
}).filter(Boolean);
|
|
1572
|
+
}
|
|
1573
|
+
function mergeProgramLists(explicit, fromBundle) {
|
|
1574
|
+
if (!explicit?.length && fromBundle.length === 0) {
|
|
1575
|
+
return void 0;
|
|
1576
|
+
}
|
|
1577
|
+
if (!explicit?.length) {
|
|
1578
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
1579
|
+
}
|
|
1580
|
+
if (fromBundle.length === 0) {
|
|
1581
|
+
return explicit;
|
|
1582
|
+
}
|
|
1583
|
+
const seen = new Set(explicit.map((program) => program.id));
|
|
1584
|
+
let changed = false;
|
|
1585
|
+
const merged = [...explicit];
|
|
1586
|
+
for (const program of fromBundle) {
|
|
1587
|
+
if (!program.id || seen.has(program.id)) {
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
merged.push(program);
|
|
1591
|
+
seen.add(program.id);
|
|
1592
|
+
changed = true;
|
|
1593
|
+
}
|
|
1594
|
+
return changed ? merged : explicit;
|
|
1595
|
+
}
|
|
1596
|
+
function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
1597
|
+
const resolvedBundle = base.bundle ?? extracted ?? null;
|
|
1598
|
+
const rigFromBundle = convertBundleGraph(
|
|
1599
|
+
pickBundleGraph(resolvedBundle, ["rig"])
|
|
1600
|
+
);
|
|
1601
|
+
const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
|
|
1602
|
+
const basePose = base.pose;
|
|
1603
|
+
const hasBasePoseGraphOverride = Boolean(
|
|
1604
|
+
basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
|
|
1605
|
+
);
|
|
1606
|
+
const hasBasePoseConfigOverride = Boolean(
|
|
1607
|
+
basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
|
|
1608
|
+
);
|
|
1609
|
+
const poseStageFilter = basePose?.stageNeutralFilter;
|
|
1610
|
+
const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
1611
|
+
pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
|
|
1612
|
+
);
|
|
1613
|
+
const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
1614
|
+
const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
1615
|
+
let resolvedPose = basePose;
|
|
1616
|
+
if (basePose) {
|
|
1617
|
+
const nextPose = { ...basePose };
|
|
1618
|
+
let changed = false;
|
|
1619
|
+
if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
|
|
1620
|
+
nextPose.graph = resolvedPoseGraph;
|
|
1621
|
+
changed = true;
|
|
1622
|
+
}
|
|
1623
|
+
if (resolvedPoseConfig && basePose.config !== resolvedPoseConfig) {
|
|
1624
|
+
nextPose.config = resolvedPoseConfig;
|
|
1625
|
+
changed = true;
|
|
1626
|
+
}
|
|
1627
|
+
if (!resolvedPoseGraph && !basePose.graph) {
|
|
1628
|
+
}
|
|
1629
|
+
if (!resolvedPoseConfig && !basePose.config) {
|
|
1630
|
+
}
|
|
1631
|
+
resolvedPose = changed ? nextPose : basePose;
|
|
1632
|
+
} else if (resolvedPoseGraph || resolvedPoseConfig || typeof poseStageFilter === "function") {
|
|
1633
|
+
resolvedPose = {
|
|
1634
|
+
...resolvedPoseGraph ? { graph: resolvedPoseGraph } : {},
|
|
1635
|
+
...resolvedPoseConfig ? { config: resolvedPoseConfig } : {},
|
|
1636
|
+
...typeof poseStageFilter === "function" ? { stageNeutralFilter: poseStageFilter } : {}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
const animationsFromBundle = convertBundleAnimations(
|
|
1640
|
+
resolvedBundle?.animations
|
|
1641
|
+
);
|
|
1642
|
+
let resolvedAnimations = mergeAnimationLists(
|
|
1643
|
+
base.animations,
|
|
1644
|
+
animationsFromBundle
|
|
1645
|
+
);
|
|
1646
|
+
const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
|
|
1647
|
+
if (animationsFromAsset.length > 0) {
|
|
1648
|
+
resolvedAnimations = mergeAnimationLists(
|
|
1649
|
+
resolvedAnimations,
|
|
1650
|
+
animationsFromAsset
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
const programsFromBundle = mergeProgramLists(
|
|
1654
|
+
base.programs,
|
|
1655
|
+
convertBundlePrograms(resolvedBundle?.graphs)
|
|
1656
|
+
);
|
|
1657
|
+
const merged = {
|
|
1658
|
+
...base
|
|
1659
|
+
};
|
|
1660
|
+
if (resolvedRig) {
|
|
1661
|
+
merged.rig = resolvedRig;
|
|
1662
|
+
} else {
|
|
1663
|
+
merged.rig = void 0;
|
|
1664
|
+
}
|
|
1665
|
+
merged.pose = resolvedPose;
|
|
1666
|
+
merged.animations = resolvedAnimations;
|
|
1667
|
+
merged.programs = programsFromBundle;
|
|
1668
|
+
merged.bundle = resolvedBundle;
|
|
1669
|
+
return merged;
|
|
1670
|
+
}
|
|
1671
|
+
function normalizeStoredAnimationInterpolation(interpolation) {
|
|
1672
|
+
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
1673
|
+
if (mode === "step") {
|
|
1674
|
+
return "step";
|
|
1675
|
+
}
|
|
1676
|
+
if (mode === "cubic" || mode === "cubicspline") {
|
|
1677
|
+
return "cubic";
|
|
1678
|
+
}
|
|
1679
|
+
return "linear";
|
|
1680
|
+
}
|
|
1681
|
+
function buildStoredAnimationTransitions(mode) {
|
|
1682
|
+
if (mode === "cubic") {
|
|
1683
|
+
return void 0;
|
|
1684
|
+
}
|
|
1685
|
+
if (mode === "step") {
|
|
1686
|
+
return {
|
|
1687
|
+
in: { x: 1, y: 1 },
|
|
1688
|
+
out: { x: 1, y: 0 }
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
return {
|
|
1692
|
+
in: { x: 1, y: 1 },
|
|
1693
|
+
out: { x: 0, y: 0 }
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
function toStoredAnimationClip(fallbackId, clip) {
|
|
1697
|
+
const clipId = typeof clip.id === "string" && clip.id.trim().length > 0 ? clip.id.trim() : fallbackId;
|
|
1698
|
+
const clipName = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : clipId;
|
|
1699
|
+
const durationSeconds = resolveClipDurationSeconds(clip, 0);
|
|
1700
|
+
const durationMs = Math.max(1, Math.round(durationSeconds * 1e3));
|
|
1701
|
+
const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, trackIndex) => {
|
|
1702
|
+
const channel = typeof rawTrack.channel === "string" ? rawTrack.channel.trim() : "";
|
|
1703
|
+
if (!channel) {
|
|
1704
|
+
return null;
|
|
1705
|
+
}
|
|
1706
|
+
const keyframes = (Array.isArray(rawTrack.keyframes) ? rawTrack.keyframes : []).map((keyframe) => {
|
|
1707
|
+
const time = Number(keyframe.time);
|
|
1708
|
+
const value = Number(keyframe.value);
|
|
1709
|
+
const keyframeId = keyframe["id"];
|
|
1710
|
+
const keyframeInterpolation = keyframe["interpolation"];
|
|
1711
|
+
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
return {
|
|
1715
|
+
id: typeof keyframeId === "string" && keyframeId.trim().length > 0 ? keyframeId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}:point-${time.toFixed(6)}`,
|
|
1716
|
+
time,
|
|
1717
|
+
value,
|
|
1718
|
+
mode: normalizeStoredAnimationInterpolation(
|
|
1719
|
+
keyframeInterpolation ?? rawTrack.interpolation
|
|
1720
|
+
)
|
|
1721
|
+
};
|
|
1722
|
+
}).filter(Boolean);
|
|
1723
|
+
if (keyframes.length === 0) {
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
keyframes.sort((left, right) => {
|
|
1727
|
+
if (left.time !== right.time) {
|
|
1728
|
+
return left.time - right.time;
|
|
1729
|
+
}
|
|
1730
|
+
return left.id.localeCompare(right.id);
|
|
1731
|
+
});
|
|
1732
|
+
const rawTrackId = rawTrack["id"];
|
|
1733
|
+
const rawTrackName = rawTrack["name"];
|
|
1734
|
+
const trackId = typeof rawTrackId === "string" && rawTrackId.trim().length > 0 ? rawTrackId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}`;
|
|
1735
|
+
const trackName = typeof rawTrackName === "string" && rawTrackName.trim().length > 0 ? rawTrackName.trim() : channel.replace(/^\/+/, "") || trackId;
|
|
1736
|
+
const denominator = durationSeconds > 0 ? durationSeconds : 1;
|
|
1737
|
+
return {
|
|
1738
|
+
id: trackId,
|
|
1739
|
+
name: trackName,
|
|
1740
|
+
animatableId: channel,
|
|
1741
|
+
points: keyframes.map((keyframe) => {
|
|
1742
|
+
const stamp = Math.max(0, Math.min(1, keyframe.time / denominator));
|
|
1743
|
+
const transitions = buildStoredAnimationTransitions(keyframe.mode);
|
|
1744
|
+
return {
|
|
1745
|
+
id: keyframe.id,
|
|
1746
|
+
stamp,
|
|
1747
|
+
value: keyframe.value,
|
|
1748
|
+
...transitions ? { transitions } : {}
|
|
1749
|
+
};
|
|
1750
|
+
})
|
|
1751
|
+
};
|
|
1752
|
+
}).filter(Boolean);
|
|
1753
|
+
return {
|
|
1754
|
+
id: clipId,
|
|
1755
|
+
name: clipName,
|
|
1756
|
+
duration: durationMs,
|
|
1757
|
+
groups: {},
|
|
1758
|
+
tracks
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
function VizijRuntimeProvider({
|
|
1762
|
+
assetBundle,
|
|
1763
|
+
children,
|
|
1764
|
+
namespace: namespaceProp,
|
|
1765
|
+
faceId: faceIdProp,
|
|
1766
|
+
updateTier = "auto",
|
|
1767
|
+
autoCreate = true,
|
|
1768
|
+
createOptions,
|
|
1769
|
+
autostart = false,
|
|
1770
|
+
driveOrchestrator = true,
|
|
1771
|
+
mergeStrategy,
|
|
1772
|
+
onRegisterControllers,
|
|
1773
|
+
onStatusChange,
|
|
1774
|
+
transformOutputWrite,
|
|
1775
|
+
orchestratorScope = "auto"
|
|
1776
|
+
}) {
|
|
1777
|
+
const storeRef = (0, import_react2.useRef)(null);
|
|
1778
|
+
if (!storeRef.current) {
|
|
1779
|
+
storeRef.current = (0, import_render.createVizijStore)();
|
|
1780
|
+
}
|
|
1781
|
+
const parentOrchestrator = (0, import_react2.useContext)(import_orchestrator_react.OrchestratorContext);
|
|
1782
|
+
const hasParentOrchestrator = Boolean(parentOrchestrator);
|
|
1783
|
+
const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
|
|
1784
|
+
if (orchestratorScope === "shared" && !hasParentOrchestrator) {
|
|
1785
|
+
console.warn(
|
|
1786
|
+
'[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
const runtimeTree = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1790
|
+
VizijRuntimeProviderInner,
|
|
1791
|
+
{
|
|
1792
|
+
assetBundle,
|
|
1793
|
+
namespace: namespaceProp,
|
|
1794
|
+
faceId: faceIdProp,
|
|
1795
|
+
updateTier,
|
|
1796
|
+
autoCreate,
|
|
1797
|
+
autostart,
|
|
1798
|
+
driveOrchestrator,
|
|
1799
|
+
createOptions,
|
|
1800
|
+
mergeStrategy,
|
|
1801
|
+
onRegisterControllers,
|
|
1802
|
+
onStatusChange,
|
|
1803
|
+
transformOutputWrite,
|
|
1804
|
+
store: storeRef.current,
|
|
1805
|
+
children
|
|
1806
|
+
}
|
|
1807
|
+
) });
|
|
1808
|
+
if (!shouldProvideOrchestrator) {
|
|
1809
|
+
return runtimeTree;
|
|
1810
|
+
}
|
|
1811
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1812
|
+
import_orchestrator_react.OrchestratorProvider,
|
|
1813
|
+
{
|
|
1814
|
+
autoCreate,
|
|
1815
|
+
createOptions,
|
|
1816
|
+
autostart: false,
|
|
1817
|
+
children: runtimeTree
|
|
1818
|
+
}
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
function VizijRuntimeProviderInner({
|
|
1822
|
+
assetBundle: initialAssetBundle,
|
|
1823
|
+
namespace: namespaceProp,
|
|
1824
|
+
faceId: faceIdProp,
|
|
1825
|
+
updateTier,
|
|
1826
|
+
mergeStrategy,
|
|
1827
|
+
onRegisterControllers,
|
|
1828
|
+
onStatusChange,
|
|
1829
|
+
transformOutputWrite,
|
|
1830
|
+
store,
|
|
1831
|
+
children,
|
|
1832
|
+
autoCreate,
|
|
1833
|
+
autostart,
|
|
1834
|
+
createOptions,
|
|
1835
|
+
driveOrchestrator
|
|
1836
|
+
}) {
|
|
1837
|
+
const [assetBundleOverride, setAssetBundleOverride] = (0, import_react2.useState)(null);
|
|
1838
|
+
const [graphUpdateToken, setGraphUpdateToken] = (0, import_react2.useState)(0);
|
|
1839
|
+
const effectiveAssetBundle = assetBundleOverride ?? initialAssetBundle;
|
|
1840
|
+
const latestEffectiveAssetBundleRef = (0, import_react2.useRef)(effectiveAssetBundle);
|
|
1841
|
+
const [extractedBundle, setExtractedBundle] = (0, import_react2.useState)(() => {
|
|
1842
|
+
if (effectiveAssetBundle.bundle) {
|
|
1843
|
+
return effectiveAssetBundle.bundle;
|
|
1844
|
+
}
|
|
1845
|
+
if (effectiveAssetBundle.glb.kind === "world" && effectiveAssetBundle.glb.bundle) {
|
|
1846
|
+
return effectiveAssetBundle.glb.bundle;
|
|
1847
|
+
}
|
|
1848
|
+
return null;
|
|
1849
|
+
});
|
|
1850
|
+
const [extractedAnimations, setExtractedAnimations] = (0, import_react2.useState)([]);
|
|
1851
|
+
const previousBundleRef = (0, import_react2.useRef)(null);
|
|
1852
|
+
const suppressNextBundlePlanRef = (0, import_react2.useRef)(false);
|
|
1853
|
+
const pendingPlanRef = (0, import_react2.useRef)(null);
|
|
1854
|
+
const updateTierRef = (0, import_react2.useRef)(updateTier);
|
|
1855
|
+
(0, import_react2.useEffect)(() => {
|
|
1856
|
+
if (effectiveAssetBundle.bundle) {
|
|
1857
|
+
setExtractedBundle(effectiveAssetBundle.bundle);
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
if (effectiveAssetBundle.glb.kind === "world") {
|
|
1861
|
+
setExtractedBundle(effectiveAssetBundle.glb.bundle ?? null);
|
|
1862
|
+
} else {
|
|
1863
|
+
setExtractedBundle(null);
|
|
1864
|
+
}
|
|
1865
|
+
}, [effectiveAssetBundle]);
|
|
1866
|
+
(0, import_react2.useEffect)(() => {
|
|
1867
|
+
updateTierRef.current = updateTier;
|
|
1868
|
+
}, [updateTier]);
|
|
1869
|
+
const assetBundle = (0, import_react2.useMemo)(
|
|
1870
|
+
() => mergeAssetBundle(
|
|
1871
|
+
effectiveAssetBundle,
|
|
1872
|
+
extractedBundle,
|
|
1873
|
+
extractedAnimations
|
|
1874
|
+
),
|
|
1875
|
+
[effectiveAssetBundle, extractedBundle, extractedAnimations]
|
|
1876
|
+
);
|
|
1877
|
+
(0, import_react2.useEffect)(() => {
|
|
1878
|
+
latestEffectiveAssetBundleRef.current = effectiveAssetBundle;
|
|
1879
|
+
}, [effectiveAssetBundle]);
|
|
1880
|
+
(0, import_react2.useEffect)(() => {
|
|
1881
|
+
if (suppressNextBundlePlanRef.current) {
|
|
1882
|
+
suppressNextBundlePlanRef.current = false;
|
|
1883
|
+
previousBundleRef.current = effectiveAssetBundle;
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const plan = resolveRuntimeUpdatePlan(
|
|
1887
|
+
previousBundleRef.current,
|
|
1888
|
+
effectiveAssetBundle,
|
|
1889
|
+
updateTierRef.current
|
|
1890
|
+
);
|
|
1891
|
+
pendingPlanRef.current = plan;
|
|
1892
|
+
previousBundleRef.current = effectiveAssetBundle;
|
|
1893
|
+
if (plan.reregisterGraphs) {
|
|
1894
|
+
setGraphUpdateToken((prev) => prev + 1);
|
|
1895
|
+
}
|
|
1896
|
+
}, [effectiveAssetBundle]);
|
|
1897
|
+
const {
|
|
1898
|
+
ready,
|
|
1899
|
+
createOrchestrator,
|
|
1900
|
+
registerGraph,
|
|
1901
|
+
registerMergedGraph,
|
|
1902
|
+
registerAnimation,
|
|
1903
|
+
removeGraph,
|
|
1904
|
+
removeAnimation,
|
|
1905
|
+
removeInput,
|
|
1906
|
+
listControllers,
|
|
1907
|
+
setInput: orchestratorSetInput,
|
|
1908
|
+
getPathSnapshot,
|
|
1909
|
+
step: stepRuntime
|
|
1910
|
+
} = (0, import_orchestrator_react.useOrchestrator)();
|
|
1911
|
+
const frame = (0, import_orchestrator_react.useOrchFrame)();
|
|
1912
|
+
const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
|
|
1913
|
+
const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
|
|
1914
|
+
const [status, setStatus] = (0, import_react2.useState)({
|
|
1915
|
+
loading: true,
|
|
1916
|
+
ready: false,
|
|
1917
|
+
error: null,
|
|
1918
|
+
errors: [],
|
|
1919
|
+
namespace,
|
|
1920
|
+
faceId,
|
|
1921
|
+
rootId: null,
|
|
1922
|
+
outputPaths: [],
|
|
1923
|
+
stepHz: void 0,
|
|
1924
|
+
controllers: { graphs: [], anims: [] }
|
|
1925
|
+
});
|
|
1926
|
+
const errorsRef = (0, import_react2.useRef)([]);
|
|
1927
|
+
const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1928
|
+
const baseOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1929
|
+
const namespacedOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1930
|
+
const namespaceRef = (0, import_react2.useRef)(namespace);
|
|
1931
|
+
const driveOrchestratorRef = (0, import_react2.useRef)(driveOrchestrator);
|
|
1932
|
+
const rigInputMapRef = (0, import_react2.useRef)({});
|
|
1933
|
+
const rigPoseControlInputIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1934
|
+
const registeredGraphsRef = (0, import_react2.useRef)([]);
|
|
1935
|
+
const registeredAnimationsRef = (0, import_react2.useRef)([]);
|
|
1936
|
+
const mergedGraphRef = (0, import_react2.useRef)(null);
|
|
1937
|
+
const poseControlBridgeValuesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1938
|
+
const poseWeightFallbackMap = (0, import_react2.useMemo)(() => {
|
|
1939
|
+
const map = /* @__PURE__ */ new Map();
|
|
1940
|
+
const poseConfig = assetBundle.pose?.config;
|
|
1941
|
+
if (!poseConfig) {
|
|
1942
|
+
return map;
|
|
1943
|
+
}
|
|
1944
|
+
const posePaths = buildPoseWeightPathMap(
|
|
1945
|
+
poseConfig.poses ?? [],
|
|
1946
|
+
poseConfig.faceId ?? faceId ?? "face"
|
|
1947
|
+
);
|
|
1948
|
+
(poseConfig.poses ?? []).forEach((pose) => {
|
|
1949
|
+
const posePath = posePaths.get(pose.id);
|
|
1950
|
+
if (!posePath) {
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
const values = Object.fromEntries(
|
|
1954
|
+
Object.entries(pose.values ?? {}).filter(
|
|
1955
|
+
([, value]) => Number.isFinite(value)
|
|
1956
|
+
)
|
|
1957
|
+
);
|
|
1958
|
+
map.set(posePath, values);
|
|
1959
|
+
});
|
|
1960
|
+
return map;
|
|
1961
|
+
}, [assetBundle.pose?.config, faceId]);
|
|
1962
|
+
const useLegacyPoseWeightFallback = (0, import_react2.useMemo)(
|
|
1963
|
+
() => shouldUseLegacyPoseWeightFallback(Boolean(assetBundle.pose?.graph)),
|
|
1964
|
+
[assetBundle.pose?.graph]
|
|
1965
|
+
);
|
|
1966
|
+
const resolvedProgramAssets = (0, import_react2.useMemo)(
|
|
1967
|
+
() => assetBundle.programs && assetBundle.programs.length > 0 ? assetBundle.programs : convertBundlePrograms(assetBundle.bundle?.graphs),
|
|
1968
|
+
[assetBundle.bundle?.graphs, assetBundle.programs]
|
|
1969
|
+
);
|
|
1970
|
+
const [inputConstraints, setInputConstraints] = (0, import_react2.useState)({});
|
|
1971
|
+
const inputConstraintsRef = (0, import_react2.useRef)({});
|
|
1972
|
+
const avgStepDtRef = (0, import_react2.useRef)(null);
|
|
1973
|
+
const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1974
|
+
const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1975
|
+
const programPlaybackRef = (0, import_react2.useRef)(
|
|
1976
|
+
/* @__PURE__ */ new Map()
|
|
1977
|
+
);
|
|
1978
|
+
const programControllerIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1979
|
+
const clipOutputValuesRef = (0, import_react2.useRef)(
|
|
1980
|
+
/* @__PURE__ */ new Map()
|
|
1981
|
+
);
|
|
1982
|
+
const clipAggregateValuesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1983
|
+
const animationSystemActiveRef = (0, import_react2.useRef)(true);
|
|
1984
|
+
const stagedInputsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1985
|
+
const autostartRef = (0, import_react2.useRef)(autostart);
|
|
1986
|
+
const lastActivityTimeRef = (0, import_react2.useRef)(now());
|
|
1987
|
+
const [loopMode, setLoopMode] = (0, import_react2.useState)("stopped");
|
|
1988
|
+
const loopModeRef = (0, import_react2.useRef)("stopped");
|
|
1989
|
+
(0, import_react2.useEffect)(() => {
|
|
1990
|
+
loopModeRef.current = loopMode;
|
|
1991
|
+
}, [loopMode]);
|
|
1992
|
+
const runtimeMountedRef = (0, import_react2.useRef)(true);
|
|
1993
|
+
(0, import_react2.useEffect)(() => {
|
|
1994
|
+
return () => {
|
|
1995
|
+
runtimeMountedRef.current = false;
|
|
1996
|
+
};
|
|
1997
|
+
}, []);
|
|
1998
|
+
(0, import_react2.useEffect)(() => {
|
|
1999
|
+
const rigAsset = assetBundle.rig;
|
|
2000
|
+
if (!rigAsset) {
|
|
2001
|
+
inputConstraintsRef.current = {};
|
|
2002
|
+
setInputConstraints({});
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
const rigSpec = resolveGraphSpec(
|
|
2006
|
+
rigAsset,
|
|
2007
|
+
`${rigAsset.id ?? "rig"} graph (constraints)`
|
|
2008
|
+
);
|
|
2009
|
+
const constraints = extractInputConstraints(
|
|
2010
|
+
rigSpec,
|
|
2011
|
+
rigAsset.inputMetadata,
|
|
2012
|
+
namespace
|
|
2013
|
+
);
|
|
2014
|
+
inputConstraintsRef.current = constraints;
|
|
2015
|
+
setInputConstraints(constraints);
|
|
2016
|
+
}, [assetBundle.rig, namespace]);
|
|
2017
|
+
const requestLoopMode = (0, import_react2.useCallback)((mode) => {
|
|
2018
|
+
if (!runtimeMountedRef.current) {
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
setLoopMode((prev) => prev === mode ? prev : mode);
|
|
2022
|
+
}, []);
|
|
2023
|
+
const hasActiveAnimations = (0, import_react2.useCallback)(() => {
|
|
2024
|
+
if (animationTweensRef.current.size > 0) {
|
|
2025
|
+
return true;
|
|
2026
|
+
}
|
|
2027
|
+
if (!animationSystemActiveRef.current) {
|
|
2028
|
+
for (const state of programPlaybackRef.current.values()) {
|
|
2029
|
+
if (state.state === "playing") {
|
|
2030
|
+
return true;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
return false;
|
|
2034
|
+
}
|
|
2035
|
+
for (const state of clipPlaybackRef.current.values()) {
|
|
2036
|
+
if (state.playing) {
|
|
2037
|
+
return true;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
for (const state of programPlaybackRef.current.values()) {
|
|
2041
|
+
if (state.state === "playing") {
|
|
2042
|
+
return true;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
return false;
|
|
2046
|
+
}, []);
|
|
2047
|
+
const computeDesiredLoopMode = (0, import_react2.useCallback)(() => {
|
|
2048
|
+
const hasAnimations = hasActiveAnimations();
|
|
2049
|
+
const recentlyActive = now() - lastActivityTimeRef.current <= ACTIVE_GRACE_MS;
|
|
2050
|
+
if (autostartRef.current && (hasAnimations || recentlyActive)) {
|
|
2051
|
+
return "active";
|
|
2052
|
+
}
|
|
2053
|
+
if (autostartRef.current) {
|
|
2054
|
+
return "idle-visible";
|
|
2055
|
+
}
|
|
2056
|
+
return "idle-hidden";
|
|
2057
|
+
}, [hasActiveAnimations]);
|
|
2058
|
+
const updateLoopMode = (0, import_react2.useCallback)(() => {
|
|
2059
|
+
requestLoopMode(computeDesiredLoopMode());
|
|
2060
|
+
}, [computeDesiredLoopMode, requestLoopMode]);
|
|
2061
|
+
const markActivity = (0, import_react2.useCallback)(() => {
|
|
2062
|
+
lastActivityTimeRef.current = now();
|
|
2063
|
+
updateLoopMode();
|
|
2064
|
+
}, [updateLoopMode]);
|
|
2065
|
+
const setInput = (0, import_react2.useCallback)(
|
|
2066
|
+
(path, value, shape) => {
|
|
2067
|
+
const numericValue = (0, import_value_json2.valueAsNumber)(value);
|
|
2068
|
+
const basePath = stripNamespace(
|
|
2069
|
+
normalisePath(path),
|
|
2070
|
+
namespaceRef.current
|
|
2071
|
+
);
|
|
2072
|
+
const poseValues = useLegacyPoseWeightFallback && numericValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
|
|
2073
|
+
if (poseValues && numericValue != null) {
|
|
2074
|
+
const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
|
|
2075
|
+
const rigMap = rigInputMapRef.current;
|
|
2076
|
+
Object.entries(poseValues).forEach(([inputId, poseValue]) => {
|
|
2077
|
+
if (!Number.isFinite(poseValue)) {
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
const controlPath = resolvePoseControlInputPath({
|
|
2081
|
+
inputId,
|
|
2082
|
+
basePath: buildRigInputPath(
|
|
2083
|
+
poseFaceId,
|
|
2084
|
+
`/pose/control/${inputId}`
|
|
2085
|
+
),
|
|
2086
|
+
rigInputPathMap: rigMap,
|
|
2087
|
+
hasNativePoseControlInput: true
|
|
2088
|
+
}) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
|
|
2089
|
+
setInput(controlPath, { float: Number(poseValue) * numericValue });
|
|
2090
|
+
});
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
markActivity();
|
|
2094
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2095
|
+
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2096
|
+
console.log("[vizij-runtime] stage input", {
|
|
2097
|
+
path,
|
|
2098
|
+
namespacedPath,
|
|
2099
|
+
value
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
2103
|
+
},
|
|
2104
|
+
[
|
|
2105
|
+
assetBundle.pose?.config?.faceId,
|
|
2106
|
+
faceId,
|
|
2107
|
+
markActivity,
|
|
2108
|
+
poseWeightFallbackMap,
|
|
2109
|
+
useLegacyPoseWeightFallback
|
|
2110
|
+
]
|
|
2111
|
+
);
|
|
2112
|
+
const reportStatus = (0, import_react2.useCallback)(
|
|
2113
|
+
(updater) => {
|
|
2114
|
+
setStatus((prev) => {
|
|
2115
|
+
const next = updater(prev);
|
|
2116
|
+
onStatusChange?.(next);
|
|
2117
|
+
return next;
|
|
2118
|
+
});
|
|
2119
|
+
},
|
|
2120
|
+
[onStatusChange]
|
|
2121
|
+
);
|
|
2122
|
+
const pushError = (0, import_react2.useCallback)(
|
|
2123
|
+
(error) => {
|
|
2124
|
+
errorsRef.current = [...errorsRef.current, error];
|
|
2125
|
+
reportStatus((prev) => ({
|
|
2126
|
+
...prev,
|
|
2127
|
+
error,
|
|
2128
|
+
errors: errorsRef.current
|
|
2129
|
+
}));
|
|
2130
|
+
console.warn("[vizij-runtime]", error.message, error.cause);
|
|
2131
|
+
},
|
|
2132
|
+
[reportStatus]
|
|
2133
|
+
);
|
|
2134
|
+
const resetErrors = (0, import_react2.useCallback)(() => {
|
|
2135
|
+
errorsRef.current = [];
|
|
2136
|
+
reportStatus((prev) => ({
|
|
2137
|
+
...prev,
|
|
2138
|
+
error: null,
|
|
2139
|
+
errors: []
|
|
2140
|
+
}));
|
|
2141
|
+
}, [reportStatus]);
|
|
2142
|
+
(0, import_react2.useEffect)(() => {
|
|
2143
|
+
autostartRef.current = autostart;
|
|
2144
|
+
updateLoopMode();
|
|
2145
|
+
}, [autostart, updateLoopMode]);
|
|
2146
|
+
const clearControllers = (0, import_react2.useCallback)(() => {
|
|
2147
|
+
const existing = listControllers();
|
|
2148
|
+
existing.graphs.forEach((id) => {
|
|
2149
|
+
try {
|
|
2150
|
+
removeGraph(id);
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
pushError({
|
|
2153
|
+
message: `Failed to remove graph ${id}`,
|
|
2154
|
+
cause: err,
|
|
2155
|
+
phase: "registration",
|
|
2156
|
+
timestamp: performance.now()
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
existing.anims.forEach((id) => {
|
|
2161
|
+
try {
|
|
2162
|
+
removeAnimation(id);
|
|
2163
|
+
} catch (err) {
|
|
2164
|
+
pushError({
|
|
2165
|
+
message: `Failed to remove animation ${id}`,
|
|
2166
|
+
cause: err,
|
|
2167
|
+
phase: "registration",
|
|
2168
|
+
timestamp: performance.now()
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
});
|
|
2172
|
+
registeredGraphsRef.current = [];
|
|
2173
|
+
registeredAnimationsRef.current = [];
|
|
2174
|
+
programControllerIdsRef.current.clear();
|
|
2175
|
+
mergedGraphRef.current = null;
|
|
2176
|
+
outputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2177
|
+
baseOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2178
|
+
namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2179
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2180
|
+
clipOutputValuesRef.current.clear();
|
|
2181
|
+
clipAggregateValuesRef.current.clear();
|
|
2182
|
+
}, [listControllers, removeAnimation, removeGraph, pushError]);
|
|
2183
|
+
(0, import_react2.useEffect)(() => {
|
|
2184
|
+
namespaceRef.current = namespace;
|
|
2185
|
+
reportStatus((prev) => ({
|
|
2186
|
+
...prev,
|
|
2187
|
+
namespace,
|
|
2188
|
+
faceId
|
|
2189
|
+
}));
|
|
2190
|
+
}, [namespace, faceId, reportStatus]);
|
|
2191
|
+
(0, import_react2.useEffect)(() => {
|
|
2192
|
+
driveOrchestratorRef.current = driveOrchestrator;
|
|
2193
|
+
}, [driveOrchestrator]);
|
|
2194
|
+
const glbAsset = effectiveAssetBundle.glb;
|
|
2195
|
+
const baseBundle = effectiveAssetBundle.bundle ?? null;
|
|
2196
|
+
(0, import_react2.useEffect)(() => {
|
|
2197
|
+
let cancelled = false;
|
|
2198
|
+
const plan = pendingPlanRef.current;
|
|
2199
|
+
if (plan && !plan.reloadAssets && status.rootId !== null) {
|
|
2200
|
+
reportStatus(
|
|
2201
|
+
(prev) => prev.loading ? { ...prev, loading: false } : prev
|
|
2202
|
+
);
|
|
2203
|
+
return () => {
|
|
2204
|
+
cancelled = true;
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
resetErrors();
|
|
2208
|
+
reportStatus((prev) => ({
|
|
2209
|
+
...prev,
|
|
2210
|
+
loading: true,
|
|
2211
|
+
rootId: null,
|
|
2212
|
+
ready: false,
|
|
2213
|
+
outputPaths: [],
|
|
2214
|
+
controllers: { graphs: [], anims: [] }
|
|
2215
|
+
}));
|
|
2216
|
+
setExtractedAnimations([]);
|
|
2217
|
+
const loadAssets = async () => {
|
|
2218
|
+
try {
|
|
2219
|
+
let world;
|
|
2220
|
+
let animatables;
|
|
2221
|
+
let bundle = baseBundle;
|
|
2222
|
+
let gltfAnimations;
|
|
2223
|
+
if (glbAsset.kind === "url") {
|
|
2224
|
+
const loaded = await (0, import_render.loadGLTFWithBundle)(
|
|
2225
|
+
glbAsset.src,
|
|
2226
|
+
[namespace],
|
|
2227
|
+
glbAsset.aggressiveImport ?? false,
|
|
2228
|
+
glbAsset.rootBounds
|
|
2229
|
+
);
|
|
2230
|
+
world = loaded.world;
|
|
2231
|
+
animatables = loaded.animatables;
|
|
2232
|
+
bundle = loaded.bundle ?? bundle;
|
|
2233
|
+
gltfAnimations = pickExtractedAnimations(loaded);
|
|
2234
|
+
} else if (glbAsset.kind === "blob") {
|
|
2235
|
+
const loaded = await (0, import_render.loadGLTFFromBlobWithBundle)(
|
|
2236
|
+
glbAsset.blob,
|
|
2237
|
+
[namespace],
|
|
2238
|
+
glbAsset.aggressiveImport ?? false,
|
|
2239
|
+
glbAsset.rootBounds
|
|
2240
|
+
);
|
|
2241
|
+
world = loaded.world;
|
|
2242
|
+
animatables = loaded.animatables;
|
|
2243
|
+
bundle = loaded.bundle ?? bundle;
|
|
2244
|
+
gltfAnimations = pickExtractedAnimations(loaded);
|
|
2245
|
+
} else {
|
|
2246
|
+
world = glbAsset.world;
|
|
2247
|
+
animatables = glbAsset.animatables;
|
|
2248
|
+
bundle = glbAsset.bundle ?? bundle;
|
|
2249
|
+
gltfAnimations = void 0;
|
|
2250
|
+
}
|
|
2251
|
+
if (cancelled) {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
setExtractedBundle(bundle ?? null);
|
|
2255
|
+
setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
|
|
2256
|
+
const rootId = findRootId(world);
|
|
2257
|
+
store.getState().addWorldElements(world, animatables, true);
|
|
2258
|
+
if (pendingPlanRef.current?.reloadAssets) {
|
|
2259
|
+
pendingPlanRef.current = {
|
|
2260
|
+
...pendingPlanRef.current,
|
|
2261
|
+
reloadAssets: false
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
reportStatus((prev) => ({
|
|
2265
|
+
...prev,
|
|
2266
|
+
loading: false,
|
|
2267
|
+
rootId,
|
|
2268
|
+
namespace,
|
|
2269
|
+
faceId
|
|
2270
|
+
}));
|
|
2271
|
+
} catch (err) {
|
|
2272
|
+
if (cancelled) {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
pushError({
|
|
2276
|
+
message: "Failed to load Vizij assets",
|
|
2277
|
+
cause: err,
|
|
2278
|
+
phase: "assets",
|
|
2279
|
+
timestamp: performance.now()
|
|
2280
|
+
});
|
|
2281
|
+
reportStatus((prev) => ({
|
|
2282
|
+
...prev,
|
|
2283
|
+
loading: false
|
|
2284
|
+
}));
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
loadAssets();
|
|
2288
|
+
return () => {
|
|
2289
|
+
cancelled = true;
|
|
2290
|
+
};
|
|
2291
|
+
}, [
|
|
2292
|
+
glbAsset,
|
|
2293
|
+
baseBundle,
|
|
2294
|
+
namespace,
|
|
2295
|
+
faceId,
|
|
2296
|
+
store,
|
|
2297
|
+
pushError,
|
|
2298
|
+
reportStatus,
|
|
2299
|
+
resetErrors,
|
|
2300
|
+
setExtractedBundle,
|
|
2301
|
+
setExtractedAnimations,
|
|
2302
|
+
status.rootId
|
|
2303
|
+
]);
|
|
2304
|
+
(0, import_react2.useEffect)(() => {
|
|
2305
|
+
if (!ready && autoCreate) {
|
|
2306
|
+
createOrchestrator(createOptions).catch((err) => {
|
|
2307
|
+
pushError({
|
|
2308
|
+
message: "Failed to create orchestrator runtime",
|
|
2309
|
+
cause: err,
|
|
2310
|
+
phase: "orchestrator",
|
|
2311
|
+
timestamp: performance.now()
|
|
2312
|
+
});
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
}, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
|
|
2316
|
+
const registerControllers = (0, import_react2.useCallback)(async () => {
|
|
2317
|
+
clearControllers();
|
|
2318
|
+
if (isRuntimeDebugEnabled()) {
|
|
2319
|
+
console.log("[vizij-runtime] registerControllers", {
|
|
2320
|
+
hasRig: Boolean(assetBundle.rig),
|
|
2321
|
+
hasPose: Boolean(assetBundle.pose?.graph),
|
|
2322
|
+
animationCount: assetBundle.animations?.length ?? 0,
|
|
2323
|
+
animationIds: (assetBundle.animations ?? []).map((anim) => anim.id),
|
|
2324
|
+
namespace
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
const baseOutputPaths = /* @__PURE__ */ new Set();
|
|
2328
|
+
const namespacedOutputPaths = /* @__PURE__ */ new Set();
|
|
2329
|
+
const recordOutputs = (paths) => {
|
|
2330
|
+
paths.forEach((path) => {
|
|
2331
|
+
const trimmed = path.trim();
|
|
2332
|
+
if (!trimmed) return;
|
|
2333
|
+
const basePath = stripNamespace(trimmed, namespace);
|
|
2334
|
+
baseOutputPaths.add(basePath);
|
|
2335
|
+
namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
|
|
2336
|
+
});
|
|
2337
|
+
};
|
|
2338
|
+
const graphConfigs = [];
|
|
2339
|
+
rigInputMapRef.current = {};
|
|
2340
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2341
|
+
poseControlBridgeValuesRef.current.clear();
|
|
2342
|
+
const rigAsset = assetBundle.rig;
|
|
2343
|
+
if (rigAsset) {
|
|
2344
|
+
const rigSpec = resolveGraphSpec(
|
|
2345
|
+
rigAsset,
|
|
2346
|
+
`${rigAsset.id ?? "rig"} graph`
|
|
2347
|
+
);
|
|
2348
|
+
if (!rigSpec) {
|
|
2349
|
+
pushError({
|
|
2350
|
+
message: "Rig graph is missing a usable spec or IR payload.",
|
|
2351
|
+
phase: "registration",
|
|
2352
|
+
timestamp: performance.now()
|
|
2353
|
+
});
|
|
2354
|
+
} else {
|
|
2355
|
+
const rigOutputs = collectOutputPaths(rigSpec);
|
|
2356
|
+
const rigInputs = collectInputPaths(rigSpec);
|
|
2357
|
+
const rigPoseControlInputIds = /* @__PURE__ */ new Set();
|
|
2358
|
+
rigInputs.forEach((path) => {
|
|
2359
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
2360
|
+
path.trim()
|
|
2361
|
+
);
|
|
2362
|
+
const inputId = (poseControlMatch?.[1] ?? "").trim();
|
|
2363
|
+
if (inputId.length > 0) {
|
|
2364
|
+
rigPoseControlInputIds.add(inputId);
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
rigInputMapRef.current = collectInputPathMap(rigSpec);
|
|
2368
|
+
rigPoseControlInputIdsRef.current = rigPoseControlInputIds;
|
|
2369
|
+
if (isRuntimeDebugEnabled()) {
|
|
2370
|
+
const blinkKeys = Object.keys(rigInputMapRef.current).filter(
|
|
2371
|
+
(key) => key.toLowerCase().includes("blink")
|
|
2372
|
+
);
|
|
2373
|
+
const blinkMappings = blinkKeys.slice(0, 20).map((key) => `${key} => ${rigInputMapRef.current[key] ?? "?"}`);
|
|
2374
|
+
console.log("[vizij-runtime] rig input map sample", {
|
|
2375
|
+
blink: rigInputMapRef.current["blink"] ?? null,
|
|
2376
|
+
blinkKeys: blinkKeys.slice(0, 12),
|
|
2377
|
+
blinkMappings: blinkMappings.join(" | ")
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
recordOutputs(rigOutputs);
|
|
2381
|
+
const rigSubs = rigAsset.subscriptions ?? {
|
|
2382
|
+
inputs: rigInputs,
|
|
2383
|
+
outputs: rigOutputs
|
|
2384
|
+
};
|
|
2385
|
+
graphConfigs.push({
|
|
2386
|
+
id: namespaceControllerId(rigAsset.id, namespace, "graph"),
|
|
2387
|
+
spec: stripNulls(namespaceGraphSpec(rigSpec, namespace)),
|
|
2388
|
+
subs: namespaceSubscriptions(rigSubs, namespace)
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
const poseGraphAsset = assetBundle.pose?.graph;
|
|
2393
|
+
if (poseGraphAsset) {
|
|
2394
|
+
const poseSpec = resolveGraphSpec(
|
|
2395
|
+
poseGraphAsset,
|
|
2396
|
+
`${poseGraphAsset.id ?? "pose"} graph`
|
|
2397
|
+
);
|
|
2398
|
+
if (poseSpec) {
|
|
2399
|
+
const poseOutputs = collectOutputPaths(poseSpec);
|
|
2400
|
+
const poseInputs = collectInputPaths(poseSpec);
|
|
2401
|
+
recordOutputs(poseOutputs);
|
|
2402
|
+
const poseSubs = poseGraphAsset.subscriptions ?? {
|
|
2403
|
+
inputs: poseInputs,
|
|
2404
|
+
outputs: poseOutputs
|
|
2405
|
+
};
|
|
2406
|
+
graphConfigs.push({
|
|
2407
|
+
id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
|
|
2408
|
+
spec: stripNulls(namespaceGraphSpec(poseSpec, namespace)),
|
|
2409
|
+
subs: namespaceSubscriptions(poseSubs, namespace)
|
|
2410
|
+
});
|
|
2411
|
+
} else {
|
|
2412
|
+
console.warn(
|
|
2413
|
+
"[vizij-runtime] Pose graph is missing a usable spec or IR payload; skipping registration."
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
for (const animation of assetBundle.animations ?? []) {
|
|
2418
|
+
const bridgeOutputs = collectAnimationClipOutputPaths(
|
|
2419
|
+
animation.clip,
|
|
2420
|
+
faceId ?? void 0,
|
|
2421
|
+
rigInputMapRef.current
|
|
2422
|
+
);
|
|
2423
|
+
if (isRuntimeDebugEnabled()) {
|
|
2424
|
+
console.log("[vizij-runtime] animation output routing", {
|
|
2425
|
+
animationId: animation.id,
|
|
2426
|
+
bridgeOutputs,
|
|
2427
|
+
bridgeOutputsText: bridgeOutputs.join(" | ")
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
recordOutputs(bridgeOutputs);
|
|
2431
|
+
}
|
|
2432
|
+
for (const program of resolvedProgramAssets) {
|
|
2433
|
+
const programSpec = resolveGraphSpec(
|
|
2434
|
+
program.graph,
|
|
2435
|
+
`${program.id ?? "program"} graph (outputs)`
|
|
2436
|
+
);
|
|
2437
|
+
if (!programSpec) {
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
recordOutputs(collectOutputPaths(programSpec));
|
|
2441
|
+
}
|
|
2442
|
+
outputPathsRef.current = namespacedOutputPaths;
|
|
2443
|
+
baseOutputPathsRef.current = baseOutputPaths;
|
|
2444
|
+
namespacedOutputPathsRef.current = namespacedOutputPaths;
|
|
2445
|
+
const graphIds = [];
|
|
2446
|
+
try {
|
|
2447
|
+
if (graphConfigs.length > 1) {
|
|
2448
|
+
const mergedId = registerMergedGraph({
|
|
2449
|
+
id: namespaceControllerId(
|
|
2450
|
+
mergedGraphRef.current ?? `merged-${namespace}`,
|
|
2451
|
+
namespace,
|
|
2452
|
+
"merged"
|
|
2453
|
+
) ?? void 0,
|
|
2454
|
+
graphs: graphConfigs,
|
|
2455
|
+
strategy: mergeStrategy ?? DEFAULT_MERGE
|
|
2456
|
+
});
|
|
2457
|
+
mergedGraphRef.current = mergedId;
|
|
2458
|
+
graphIds.push(mergedId);
|
|
2459
|
+
} else {
|
|
2460
|
+
graphConfigs.forEach((cfg) => {
|
|
2461
|
+
const id = registerGraph(cfg);
|
|
2462
|
+
graphIds.push(id);
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
} catch (err) {
|
|
2466
|
+
pushError({
|
|
2467
|
+
message: "Failed to register rig graphs",
|
|
2468
|
+
cause: err,
|
|
2469
|
+
phase: "registration",
|
|
2470
|
+
timestamp: performance.now()
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
registeredGraphsRef.current = graphIds;
|
|
2474
|
+
if (isRuntimeDebugEnabled()) {
|
|
2475
|
+
console.log("[vizij-runtime] registered graph ids", graphIds);
|
|
2476
|
+
}
|
|
2477
|
+
const animationIds = [];
|
|
2478
|
+
for (const anim of assetBundle.animations ?? []) {
|
|
2479
|
+
try {
|
|
2480
|
+
const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
|
|
2481
|
+
const animationPayload = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
|
|
2482
|
+
const config = {
|
|
2483
|
+
id: controllerId,
|
|
2484
|
+
setup: {
|
|
2485
|
+
...anim.setup ?? {},
|
|
2486
|
+
animation: animationPayload
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
const id = registerAnimation(config);
|
|
2490
|
+
animationIds.push(id);
|
|
2491
|
+
} catch (err) {
|
|
2492
|
+
if (isRuntimeDebugEnabled()) {
|
|
2493
|
+
console.warn("[vizij-runtime] failed animation registration", {
|
|
2494
|
+
animationId: anim.id,
|
|
2495
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
pushError({
|
|
2499
|
+
message: `Failed to register animation ${anim.id}`,
|
|
2500
|
+
cause: err,
|
|
2501
|
+
phase: "animation",
|
|
2502
|
+
timestamp: performance.now()
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
registeredAnimationsRef.current = animationIds;
|
|
2507
|
+
if (assetBundle.initialInputs) {
|
|
2508
|
+
Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
|
|
2509
|
+
try {
|
|
2510
|
+
setInput(path, value);
|
|
2511
|
+
} catch (err) {
|
|
2512
|
+
pushError({
|
|
2513
|
+
message: `Failed to stage initial input ${path}`,
|
|
2514
|
+
cause: err,
|
|
2515
|
+
phase: "registration",
|
|
2516
|
+
timestamp: performance.now()
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
const controllers = listControllers();
|
|
2522
|
+
if (isRuntimeDebugEnabled()) {
|
|
2523
|
+
console.log("[vizij-runtime] controllers after register", {
|
|
2524
|
+
controllers,
|
|
2525
|
+
graphIds,
|
|
2526
|
+
animationIds
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
reportStatus((prev) => ({
|
|
2530
|
+
...prev,
|
|
2531
|
+
ready: true,
|
|
2532
|
+
controllers,
|
|
2533
|
+
outputPaths: Array.from(outputPathsRef.current)
|
|
2534
|
+
}));
|
|
2535
|
+
onRegisterControllers?.(controllers);
|
|
2536
|
+
}, [
|
|
2537
|
+
assetBundle,
|
|
2538
|
+
clearControllers,
|
|
2539
|
+
listControllers,
|
|
2540
|
+
mergeStrategy,
|
|
2541
|
+
namespace,
|
|
2542
|
+
onRegisterControllers,
|
|
2543
|
+
pushError,
|
|
2544
|
+
registerAnimation,
|
|
2545
|
+
registerGraph,
|
|
2546
|
+
registerMergedGraph,
|
|
2547
|
+
reportStatus,
|
|
2548
|
+
resolvedProgramAssets,
|
|
2549
|
+
setInput
|
|
2550
|
+
]);
|
|
2551
|
+
(0, import_react2.useEffect)(() => {
|
|
2552
|
+
if (!ready || status.loading) {
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
const plan = pendingPlanRef.current;
|
|
2556
|
+
const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
|
|
2557
|
+
if (plan && !plan.reregisterGraphs && hasRegistered) {
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
registerControllers().catch((err) => {
|
|
2561
|
+
pushError({
|
|
2562
|
+
message: "Failed to register controllers",
|
|
2563
|
+
cause: err,
|
|
2564
|
+
phase: "registration",
|
|
2565
|
+
timestamp: performance.now()
|
|
2566
|
+
});
|
|
2567
|
+
});
|
|
2568
|
+
}, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
|
|
2569
|
+
(0, import_react2.useEffect)(() => {
|
|
2570
|
+
if (!frame) {
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
const writes = frame.merged_writes ?? [];
|
|
2574
|
+
if (!writes.length) {
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
const setWorldValues = store.getState().setValues;
|
|
2578
|
+
const namespaceValue = status.namespace;
|
|
2579
|
+
const currentValues = store.getState().values;
|
|
2580
|
+
const rigInputPathMap = rigInputMapRef.current;
|
|
2581
|
+
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
2582
|
+
const batched = [];
|
|
2583
|
+
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
2584
|
+
const baseOutputs = baseOutputPathsRef.current;
|
|
2585
|
+
writes.forEach((write) => {
|
|
2586
|
+
const path = normalisePath(write.path);
|
|
2587
|
+
const basePath = stripNamespace(path, namespaceValue);
|
|
2588
|
+
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
2589
|
+
if (!isTrackedOutput) {
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
const raw = valueJSONToRaw(write.value);
|
|
2593
|
+
if (raw === void 0) {
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
2597
|
+
basePath
|
|
2598
|
+
);
|
|
2599
|
+
if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
|
|
2600
|
+
const inputId = (poseControlMatch[1] ?? "").trim();
|
|
2601
|
+
const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
|
|
2602
|
+
const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
|
|
2603
|
+
inputId,
|
|
2604
|
+
basePath,
|
|
2605
|
+
rigInputPathMap,
|
|
2606
|
+
hasNativePoseControlInput
|
|
2607
|
+
});
|
|
2608
|
+
if (mappedInputPath) {
|
|
2609
|
+
const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
|
|
2610
|
+
const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
|
|
2611
|
+
if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
|
|
2612
|
+
poseControlBridgeValuesRef.current.set(bridgeKey, raw);
|
|
2613
|
+
setInput(mappedInputPath, { float: raw });
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
const targetPath = baseOutputs.has(basePath) ? basePath : path;
|
|
2618
|
+
const currentValue = currentValues.get(
|
|
2619
|
+
(0, import_utils.getLookup)(namespaceValue, targetPath)
|
|
2620
|
+
);
|
|
2621
|
+
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
2622
|
+
id: targetPath,
|
|
2623
|
+
namespace: namespaceValue,
|
|
2624
|
+
value: raw,
|
|
2625
|
+
currentValue
|
|
2626
|
+
}) : {
|
|
2627
|
+
id: targetPath,
|
|
2628
|
+
namespace: namespaceValue,
|
|
2629
|
+
value: raw
|
|
2630
|
+
};
|
|
2631
|
+
if (nextWrite) {
|
|
2632
|
+
batched.push(nextWrite);
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
if (batched.length > 0) {
|
|
2636
|
+
setWorldValues(batched);
|
|
2637
|
+
}
|
|
2638
|
+
}, [frame, status.namespace, store, transformOutputWrite]);
|
|
2639
|
+
const stagePoseNeutral = (0, import_react2.useCallback)(
|
|
2640
|
+
(force = false) => {
|
|
2641
|
+
const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
|
|
2642
|
+
const rigMap = rigInputMapRef.current;
|
|
2643
|
+
const staged = /* @__PURE__ */ new Set();
|
|
2644
|
+
Object.entries(neutral).forEach(([id, value]) => {
|
|
2645
|
+
const path = rigMap[id];
|
|
2646
|
+
if (!path) {
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
const include = assetBundle.pose?.stageNeutralFilter;
|
|
2650
|
+
if (include && !include(id, path)) {
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
setInput(path, { float: Number.isFinite(value) ? value : 0 });
|
|
2654
|
+
staged.add(path);
|
|
2655
|
+
});
|
|
2656
|
+
if (force) {
|
|
2657
|
+
Object.entries(rigMap).forEach(([id, path]) => {
|
|
2658
|
+
if (staged.has(path)) {
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
const include = assetBundle.pose?.stageNeutralFilter;
|
|
2662
|
+
if (include && !include(id, path)) {
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
setInput(path, { float: 0 });
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
},
|
|
2669
|
+
[assetBundle.pose?.config?.neutralInputs, setInput]
|
|
2670
|
+
);
|
|
2671
|
+
const setRendererValue = (0, import_react2.useCallback)(
|
|
2672
|
+
(id, ns, value) => {
|
|
2673
|
+
store.getState().setValue(id, ns, value);
|
|
2674
|
+
},
|
|
2675
|
+
[store]
|
|
2676
|
+
);
|
|
2677
|
+
const cancelAnimation = (0, import_react2.useCallback)((path) => {
|
|
2678
|
+
if (animationTweensRef.current.has(path)) {
|
|
2679
|
+
const entry = animationTweensRef.current.get(path);
|
|
2680
|
+
animationTweensRef.current.delete(path);
|
|
2681
|
+
entry?.resolve();
|
|
2682
|
+
}
|
|
2683
|
+
}, []);
|
|
2684
|
+
const advanceAnimationTweens = (0, import_react2.useCallback)(
|
|
2685
|
+
(dt) => {
|
|
2686
|
+
if (animationTweensRef.current.size === 0) {
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
const map = animationTweensRef.current;
|
|
2690
|
+
const toDelete = [];
|
|
2691
|
+
map.forEach((state, key) => {
|
|
2692
|
+
state.elapsed += dt;
|
|
2693
|
+
const progress = state.duration === 0 ? 1 : Math.min(state.elapsed / state.duration, 1);
|
|
2694
|
+
const eased = state.easing(progress);
|
|
2695
|
+
const value = state.from + (state.to - state.from) * eased;
|
|
2696
|
+
setInput(state.path, { float: value });
|
|
2697
|
+
if (progress >= 1) {
|
|
2698
|
+
toDelete.push(key);
|
|
2699
|
+
state.resolve();
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
toDelete.forEach((key) => map.delete(key));
|
|
2703
|
+
},
|
|
2704
|
+
[setInput]
|
|
2705
|
+
);
|
|
2706
|
+
const resolveClipById = (0, import_react2.useCallback)(
|
|
2707
|
+
(id) => {
|
|
2708
|
+
return assetBundle.animations?.find((anim) => anim.id === id);
|
|
2709
|
+
},
|
|
2710
|
+
[assetBundle.animations]
|
|
2711
|
+
);
|
|
2712
|
+
const resolveClipPromise = (0, import_react2.useCallback)((state) => {
|
|
2713
|
+
state.resolve?.();
|
|
2714
|
+
state.resolve = null;
|
|
2715
|
+
state.completion = null;
|
|
2716
|
+
}, []);
|
|
2717
|
+
const ensureClipPromise = (0, import_react2.useCallback)((state) => {
|
|
2718
|
+
if (state.completion) {
|
|
2719
|
+
return state.completion;
|
|
2720
|
+
}
|
|
2721
|
+
const completion = new Promise((resolve) => {
|
|
2722
|
+
state.resolve = resolve;
|
|
2723
|
+
});
|
|
2724
|
+
state.completion = completion;
|
|
2725
|
+
return completion;
|
|
2726
|
+
}, []);
|
|
2727
|
+
const setAnimationInput = (0, import_react2.useCallback)(
|
|
2728
|
+
(path, value, options) => {
|
|
2729
|
+
setInput(path, { float: value });
|
|
2730
|
+
if (!options?.immediate) {
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2734
|
+
const staged = stagedInputsRef.current.get(namespacedPath);
|
|
2735
|
+
if (staged) {
|
|
2736
|
+
orchestratorSetInput(namespacedPath, staged.value, staged.shape);
|
|
2737
|
+
stagedInputsRef.current.delete(namespacedPath);
|
|
2738
|
+
return;
|
|
2739
|
+
}
|
|
2740
|
+
orchestratorSetInput(namespacedPath, { float: value });
|
|
2741
|
+
},
|
|
2742
|
+
[orchestratorSetInput, setInput]
|
|
2743
|
+
);
|
|
2744
|
+
const clearAnimationInput = (0, import_react2.useCallback)(
|
|
2745
|
+
(path) => {
|
|
2746
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2747
|
+
stagedInputsRef.current.delete(namespacedPath);
|
|
2748
|
+
removeInput(namespacedPath);
|
|
2749
|
+
},
|
|
2750
|
+
[removeInput]
|
|
2751
|
+
);
|
|
2752
|
+
const buildClipOutputValues = (0, import_react2.useCallback)(
|
|
2753
|
+
(clip, state) => sampleAnimationClipOutputValues(
|
|
2754
|
+
clip.clip,
|
|
2755
|
+
state.time,
|
|
2756
|
+
state.weight,
|
|
2757
|
+
faceId ?? void 0,
|
|
2758
|
+
rigInputMapRef.current
|
|
2759
|
+
),
|
|
2760
|
+
[faceId]
|
|
2761
|
+
);
|
|
2762
|
+
const computeClipAggregateValues = (0, import_react2.useCallback)(() => {
|
|
2763
|
+
const aggregate = /* @__PURE__ */ new Map();
|
|
2764
|
+
clipOutputValuesRef.current.forEach((outputValues) => {
|
|
2765
|
+
outputValues.forEach((value, path) => {
|
|
2766
|
+
aggregate.set(path, (aggregate.get(path) ?? 0) + value);
|
|
2767
|
+
});
|
|
2768
|
+
});
|
|
2769
|
+
return aggregate;
|
|
2770
|
+
}, []);
|
|
2771
|
+
const stageClipAggregateValues = (0, import_react2.useCallback)(
|
|
2772
|
+
(nextAggregate, options) => {
|
|
2773
|
+
diffAnimationAggregateValues(
|
|
2774
|
+
clipAggregateValuesRef.current,
|
|
2775
|
+
nextAggregate,
|
|
2776
|
+
POSE_CONTROL_BRIDGE_EPSILON
|
|
2777
|
+
).forEach((operation) => {
|
|
2778
|
+
if (operation.kind === "clear") {
|
|
2779
|
+
clearAnimationInput(operation.path);
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
setAnimationInput(operation.path, operation.value, options);
|
|
2783
|
+
});
|
|
2784
|
+
clipAggregateValuesRef.current = nextAggregate;
|
|
2785
|
+
},
|
|
2786
|
+
[clearAnimationInput, setAnimationInput]
|
|
2787
|
+
);
|
|
2788
|
+
const syncClipOutputs = (0, import_react2.useCallback)(
|
|
2789
|
+
(options) => {
|
|
2790
|
+
stageClipAggregateValues(computeClipAggregateValues(), options);
|
|
2791
|
+
},
|
|
2792
|
+
[computeClipAggregateValues, stageClipAggregateValues]
|
|
2793
|
+
);
|
|
2794
|
+
const writeClipOutputs = (0, import_react2.useCallback)(
|
|
2795
|
+
(clip, state, options) => {
|
|
2796
|
+
if (!animationSystemActiveRef.current) {
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
clipOutputValuesRef.current.set(
|
|
2800
|
+
clip.id,
|
|
2801
|
+
buildClipOutputValues(clip, state)
|
|
2802
|
+
);
|
|
2803
|
+
syncClipOutputs(options);
|
|
2804
|
+
},
|
|
2805
|
+
[buildClipOutputValues, syncClipOutputs]
|
|
2806
|
+
);
|
|
2807
|
+
const clearClipOutputs = (0, import_react2.useCallback)(
|
|
2808
|
+
(clipId, options) => {
|
|
2809
|
+
if (!clipOutputValuesRef.current.has(clipId)) {
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
clipOutputValuesRef.current.delete(clipId);
|
|
2813
|
+
syncClipOutputs(options);
|
|
2814
|
+
},
|
|
2815
|
+
[syncClipOutputs]
|
|
2816
|
+
);
|
|
2817
|
+
const createClipPlaybackState = (0, import_react2.useCallback)(
|
|
2818
|
+
(clip) => {
|
|
2819
|
+
const duration = resolveClipDurationSeconds(
|
|
2820
|
+
clip.clip
|
|
2821
|
+
);
|
|
2822
|
+
return {
|
|
2823
|
+
id: clip.id,
|
|
2824
|
+
time: 0,
|
|
2825
|
+
duration,
|
|
2826
|
+
speed: 1,
|
|
2827
|
+
weight: Number.isFinite(clip.weight) ? Number(clip.weight) : 1,
|
|
2828
|
+
loop: false,
|
|
2829
|
+
playing: false,
|
|
2830
|
+
resolve: null,
|
|
2831
|
+
completion: null
|
|
2832
|
+
};
|
|
2833
|
+
},
|
|
2834
|
+
[]
|
|
2835
|
+
);
|
|
2836
|
+
const ensureClipPlaybackState = (0, import_react2.useCallback)(
|
|
2837
|
+
(id) => {
|
|
2838
|
+
const clip = resolveClipById(id);
|
|
2839
|
+
if (!clip) {
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
const existing = clipPlaybackRef.current.get(id);
|
|
2843
|
+
if (existing) {
|
|
2844
|
+
existing.duration = resolveClipDurationSeconds(
|
|
2845
|
+
clip.clip,
|
|
2846
|
+
existing.duration
|
|
2847
|
+
);
|
|
2848
|
+
existing.time = clampAnimationTime(existing.time, existing.duration);
|
|
2849
|
+
return { clip, state: existing };
|
|
2850
|
+
}
|
|
2851
|
+
const next = createClipPlaybackState(clip);
|
|
2852
|
+
clipPlaybackRef.current.set(id, next);
|
|
2853
|
+
return { clip, state: next };
|
|
2854
|
+
},
|
|
2855
|
+
[createClipPlaybackState, resolveClipById]
|
|
2856
|
+
);
|
|
2857
|
+
const advanceClipPlayback = (0, import_react2.useCallback)(
|
|
2858
|
+
(dt) => {
|
|
2859
|
+
if (clipPlaybackRef.current.size === 0) {
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
const toDelete = [];
|
|
2863
|
+
clipPlaybackRef.current.forEach((state, key) => {
|
|
2864
|
+
const clip = resolveClipById(state.id);
|
|
2865
|
+
if (!clip) {
|
|
2866
|
+
toDelete.push(key);
|
|
2867
|
+
resolveClipPromise(state);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
state.duration = resolveClipDurationSeconds(
|
|
2871
|
+
clip.clip,
|
|
2872
|
+
state.duration
|
|
2873
|
+
);
|
|
2874
|
+
const { time, completed } = advanceClipTime(
|
|
2875
|
+
{
|
|
2876
|
+
time: state.time,
|
|
2877
|
+
duration: state.duration,
|
|
2878
|
+
speed: state.speed,
|
|
2879
|
+
loop: state.loop,
|
|
2880
|
+
playing: state.playing
|
|
2881
|
+
},
|
|
2882
|
+
dt
|
|
2883
|
+
);
|
|
2884
|
+
state.time = clampAnimationTime(time, state.duration);
|
|
2885
|
+
if (state.playing || completed) {
|
|
2886
|
+
writeClipOutputs(clip, state);
|
|
2887
|
+
}
|
|
2888
|
+
if (completed) {
|
|
2889
|
+
toDelete.push(key);
|
|
2890
|
+
resolveClipPromise(state);
|
|
2891
|
+
}
|
|
2892
|
+
});
|
|
2893
|
+
toDelete.forEach((key) => {
|
|
2894
|
+
clipPlaybackRef.current.delete(key);
|
|
2895
|
+
if (animationSystemActiveRef.current) {
|
|
2896
|
+
clearClipOutputs(key);
|
|
2897
|
+
}
|
|
2898
|
+
});
|
|
2899
|
+
},
|
|
2900
|
+
[clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
|
|
2901
|
+
);
|
|
2902
|
+
const animateValue = (0, import_react2.useCallback)(
|
|
2903
|
+
(path, target, options) => {
|
|
2904
|
+
const targetValue = (0, import_value_json2.valueAsNumber)(target);
|
|
2905
|
+
const basePath = stripNamespace(
|
|
2906
|
+
normalisePath(path),
|
|
2907
|
+
namespaceRef.current
|
|
2908
|
+
);
|
|
2909
|
+
const poseValues = useLegacyPoseWeightFallback && targetValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
|
|
2910
|
+
if (poseValues && targetValue != null) {
|
|
2911
|
+
const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
|
|
2912
|
+
const rigMap = rigInputMapRef.current;
|
|
2913
|
+
return Promise.all(
|
|
2914
|
+
Object.entries(poseValues).flatMap(([inputId, poseValue]) => {
|
|
2915
|
+
if (!Number.isFinite(poseValue)) {
|
|
2916
|
+
return [];
|
|
2917
|
+
}
|
|
2918
|
+
const controlPath = resolvePoseControlInputPath({
|
|
2919
|
+
inputId,
|
|
2920
|
+
basePath: buildRigInputPath(
|
|
2921
|
+
poseFaceId,
|
|
2922
|
+
`/pose/control/${inputId}`
|
|
2923
|
+
),
|
|
2924
|
+
rigInputPathMap: rigMap,
|
|
2925
|
+
hasNativePoseControlInput: true
|
|
2926
|
+
}) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
|
|
2927
|
+
return [
|
|
2928
|
+
animateValue(
|
|
2929
|
+
controlPath,
|
|
2930
|
+
{ float: Number(poseValue) * targetValue },
|
|
2931
|
+
options
|
|
2932
|
+
)
|
|
2933
|
+
];
|
|
2934
|
+
})
|
|
2935
|
+
).then(() => void 0);
|
|
2936
|
+
}
|
|
2937
|
+
const easing = resolveEasing(options?.easing);
|
|
2938
|
+
const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
|
|
2939
|
+
cancelAnimation(path);
|
|
2940
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2941
|
+
const current = getPathSnapshot(namespacedPath);
|
|
2942
|
+
const fromValue = (0, import_value_json2.valueAsNumber)(current);
|
|
2943
|
+
const toValue = (0, import_value_json2.valueAsNumber)(target);
|
|
2944
|
+
if (fromValue == null || toValue == null || duration === 0) {
|
|
2945
|
+
setInput(path, target);
|
|
2946
|
+
return Promise.resolve();
|
|
2947
|
+
}
|
|
2948
|
+
return new Promise((resolve) => {
|
|
2949
|
+
animationTweensRef.current.set(path, {
|
|
2950
|
+
// Keep the raw path here so tween updates go through setInput() once
|
|
2951
|
+
// and pick up the active namespace exactly once.
|
|
2952
|
+
path,
|
|
2953
|
+
from: fromValue,
|
|
2954
|
+
to: toValue,
|
|
2955
|
+
duration,
|
|
2956
|
+
elapsed: 0,
|
|
2957
|
+
easing,
|
|
2958
|
+
resolve
|
|
2959
|
+
});
|
|
2960
|
+
markActivity();
|
|
2961
|
+
});
|
|
2962
|
+
},
|
|
2963
|
+
[
|
|
2964
|
+
assetBundle.pose?.config?.faceId,
|
|
2965
|
+
cancelAnimation,
|
|
2966
|
+
faceId,
|
|
2967
|
+
getPathSnapshot,
|
|
2968
|
+
markActivity,
|
|
2969
|
+
poseWeightFallbackMap,
|
|
2970
|
+
setInput,
|
|
2971
|
+
useLegacyPoseWeightFallback
|
|
2972
|
+
]
|
|
2973
|
+
);
|
|
2974
|
+
const playAnimation = (0, import_react2.useCallback)(
|
|
2975
|
+
(id, options) => {
|
|
2976
|
+
const ensured = ensureClipPlaybackState(id);
|
|
2977
|
+
if (!ensured) {
|
|
2978
|
+
return Promise.reject(
|
|
2979
|
+
new Error(`Animation ${id} is not part of the current asset bundle.`)
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
const { clip, state } = ensured;
|
|
2983
|
+
const shouldReset = options?.reset === true;
|
|
2984
|
+
if (shouldReset) {
|
|
2985
|
+
resolveClipPromise(state);
|
|
2986
|
+
state.time = 0;
|
|
2987
|
+
}
|
|
2988
|
+
const speed = options?.speed ?? state.speed ?? 1;
|
|
2989
|
+
const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
|
|
2990
|
+
state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
2991
|
+
state.weight = Number.isFinite(weight) ? Number(weight) : 1;
|
|
2992
|
+
state.duration = resolveClipDurationSeconds(
|
|
2993
|
+
clip.clip,
|
|
2994
|
+
state.duration
|
|
2995
|
+
);
|
|
2996
|
+
state.time = clampAnimationTime(state.time, state.duration);
|
|
2997
|
+
state.playing = true;
|
|
2998
|
+
const completion = ensureClipPromise(state);
|
|
2999
|
+
clipPlaybackRef.current.set(id, state);
|
|
3000
|
+
writeClipOutputs(clip, state);
|
|
3001
|
+
markActivity();
|
|
3002
|
+
return completion;
|
|
3003
|
+
},
|
|
3004
|
+
[
|
|
3005
|
+
ensureClipPlaybackState,
|
|
3006
|
+
ensureClipPromise,
|
|
3007
|
+
markActivity,
|
|
3008
|
+
resolveClipPromise,
|
|
3009
|
+
writeClipOutputs
|
|
3010
|
+
]
|
|
3011
|
+
);
|
|
3012
|
+
const pauseAnimation = (0, import_react2.useCallback)(
|
|
3013
|
+
(id) => {
|
|
3014
|
+
const state = clipPlaybackRef.current.get(id);
|
|
3015
|
+
if (!state || !state.playing) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
state.playing = false;
|
|
3019
|
+
updateLoopMode();
|
|
3020
|
+
},
|
|
3021
|
+
[updateLoopMode]
|
|
3022
|
+
);
|
|
3023
|
+
const seekAnimation = (0, import_react2.useCallback)(
|
|
3024
|
+
(id, timeSeconds) => {
|
|
3025
|
+
const ensured = ensureClipPlaybackState(id);
|
|
3026
|
+
if (!ensured) {
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
const { clip, state } = ensured;
|
|
3030
|
+
state.time = clampAnimationTime(timeSeconds, state.duration);
|
|
3031
|
+
clipPlaybackRef.current.set(id, state);
|
|
3032
|
+
writeClipOutputs(clip, state, { immediate: true });
|
|
3033
|
+
},
|
|
3034
|
+
[ensureClipPlaybackState, writeClipOutputs]
|
|
3035
|
+
);
|
|
3036
|
+
const setAnimationLoop = (0, import_react2.useCallback)(
|
|
3037
|
+
(id, enabled) => {
|
|
3038
|
+
const ensured = ensureClipPlaybackState(id);
|
|
3039
|
+
if (!ensured) {
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
ensured.state.loop = Boolean(enabled);
|
|
3043
|
+
clipPlaybackRef.current.set(id, ensured.state);
|
|
3044
|
+
updateLoopMode();
|
|
3045
|
+
},
|
|
3046
|
+
[ensureClipPlaybackState, updateLoopMode]
|
|
3047
|
+
);
|
|
3048
|
+
const getAnimationState = (0, import_react2.useCallback)(
|
|
3049
|
+
(id) => {
|
|
3050
|
+
const state = clipPlaybackRef.current.get(id);
|
|
3051
|
+
if (!state) {
|
|
3052
|
+
return null;
|
|
3053
|
+
}
|
|
3054
|
+
return {
|
|
3055
|
+
time: state.time,
|
|
3056
|
+
duration: state.duration,
|
|
3057
|
+
playing: state.playing,
|
|
3058
|
+
loop: state.loop,
|
|
3059
|
+
speed: state.speed
|
|
3060
|
+
};
|
|
3061
|
+
},
|
|
3062
|
+
[]
|
|
3063
|
+
);
|
|
3064
|
+
const stopAnimation = (0, import_react2.useCallback)(
|
|
3065
|
+
(id, options) => {
|
|
3066
|
+
const state = clipPlaybackRef.current.get(id);
|
|
3067
|
+
if (state) {
|
|
3068
|
+
clipPlaybackRef.current.delete(id);
|
|
3069
|
+
state.playing = false;
|
|
3070
|
+
resolveClipPromise(state);
|
|
3071
|
+
}
|
|
3072
|
+
if (options?.clearOutputs !== false) {
|
|
3073
|
+
clearClipOutputs(id);
|
|
3074
|
+
}
|
|
3075
|
+
updateLoopMode();
|
|
3076
|
+
},
|
|
3077
|
+
[clearClipOutputs, resolveClipPromise, updateLoopMode]
|
|
3078
|
+
);
|
|
3079
|
+
const refreshControllerStatus = (0, import_react2.useCallback)(() => {
|
|
3080
|
+
const controllers = listControllers();
|
|
3081
|
+
reportStatus((prev) => ({
|
|
3082
|
+
...prev,
|
|
3083
|
+
controllers,
|
|
3084
|
+
outputPaths: Array.from(outputPathsRef.current)
|
|
3085
|
+
}));
|
|
3086
|
+
onRegisterControllers?.(controllers);
|
|
3087
|
+
}, [listControllers, onRegisterControllers, reportStatus]);
|
|
3088
|
+
const resolveProgramById = (0, import_react2.useCallback)(
|
|
3089
|
+
(id) => {
|
|
3090
|
+
return resolvedProgramAssets.find((program) => program.id === id);
|
|
3091
|
+
},
|
|
3092
|
+
[resolvedProgramAssets]
|
|
3093
|
+
);
|
|
3094
|
+
const buildProgramRegistrationConfig = (0, import_react2.useCallback)(
|
|
3095
|
+
(program) => {
|
|
3096
|
+
const graphSpec = resolveGraphSpec(
|
|
3097
|
+
program.graph,
|
|
3098
|
+
`${program.id ?? "program"} graph`
|
|
3099
|
+
);
|
|
3100
|
+
if (!graphSpec) {
|
|
3101
|
+
return null;
|
|
3102
|
+
}
|
|
3103
|
+
const outputs = collectOutputPaths(graphSpec);
|
|
3104
|
+
const inputs = collectInputPaths(graphSpec);
|
|
3105
|
+
const subs = program.graph.subscriptions ?? {
|
|
3106
|
+
inputs,
|
|
3107
|
+
outputs
|
|
3108
|
+
};
|
|
3109
|
+
return {
|
|
3110
|
+
id: namespaceControllerId(program.id, namespace, "graph"),
|
|
3111
|
+
spec: stripNulls(namespaceGraphSpec(graphSpec, namespace)),
|
|
3112
|
+
subs: namespaceSubscriptions(subs, namespace)
|
|
3113
|
+
};
|
|
3114
|
+
},
|
|
3115
|
+
[namespace]
|
|
3116
|
+
);
|
|
3117
|
+
const deriveProgramResetValues = (0, import_react2.useCallback)(
|
|
3118
|
+
(program) => {
|
|
3119
|
+
if (program.resetValues) {
|
|
3120
|
+
return Object.entries(program.resetValues).filter(([, value]) => Number.isFinite(value)).map(([path, value]) => ({ path, value }));
|
|
3121
|
+
}
|
|
3122
|
+
const graphSpec = resolveGraphSpec(
|
|
3123
|
+
program.graph,
|
|
3124
|
+
`${program.id ?? "program"} graph (reset)`
|
|
3125
|
+
);
|
|
3126
|
+
if (!graphSpec) {
|
|
3127
|
+
return [];
|
|
3128
|
+
}
|
|
3129
|
+
return collectOutputPaths(graphSpec).filter((path) => path.trim().length > 0).map((path) => {
|
|
3130
|
+
const defaultValue = inputConstraintsRef.current[path]?.defaultValue ?? 0;
|
|
3131
|
+
return {
|
|
3132
|
+
path,
|
|
3133
|
+
value: Number.isFinite(defaultValue) && defaultValue != null ? defaultValue : 0
|
|
3134
|
+
};
|
|
3135
|
+
});
|
|
3136
|
+
},
|
|
3137
|
+
[]
|
|
3138
|
+
);
|
|
3139
|
+
const syncProgramPlaybackControllers = (0, import_react2.useCallback)(() => {
|
|
3140
|
+
if (!ready) {
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
const availableProgramIds = new Set(
|
|
3144
|
+
resolvedProgramAssets.map((program) => program.id)
|
|
3145
|
+
);
|
|
3146
|
+
Array.from(programPlaybackRef.current.keys()).forEach((id) => {
|
|
3147
|
+
if (availableProgramIds.has(id)) {
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
programPlaybackRef.current.delete(id);
|
|
3151
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
3152
|
+
if (controllerId) {
|
|
3153
|
+
try {
|
|
3154
|
+
removeGraph(controllerId);
|
|
3155
|
+
} catch (err) {
|
|
3156
|
+
pushError({
|
|
3157
|
+
message: `Failed to remove program ${id}`,
|
|
3158
|
+
cause: err,
|
|
3159
|
+
phase: "registration",
|
|
3160
|
+
timestamp: performance.now()
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
programControllerIdsRef.current.delete(id);
|
|
3164
|
+
}
|
|
3165
|
+
});
|
|
3166
|
+
programPlaybackRef.current.forEach((state, id) => {
|
|
3167
|
+
const program = resolveProgramById(id);
|
|
3168
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
3169
|
+
if (!program) {
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
if (state.state !== "playing") {
|
|
3173
|
+
if (!controllerId) {
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
try {
|
|
3177
|
+
removeGraph(controllerId);
|
|
3178
|
+
} catch (err) {
|
|
3179
|
+
pushError({
|
|
3180
|
+
message: `Failed to pause program ${id}`,
|
|
3181
|
+
cause: err,
|
|
3182
|
+
phase: "registration",
|
|
3183
|
+
timestamp: performance.now()
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
programControllerIdsRef.current.delete(id);
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
if (controllerId) {
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
const config = buildProgramRegistrationConfig(program);
|
|
3193
|
+
if (!config) {
|
|
3194
|
+
pushError({
|
|
3195
|
+
message: `Program ${id} is missing a usable graph payload.`,
|
|
3196
|
+
phase: "registration",
|
|
3197
|
+
timestamp: performance.now()
|
|
3198
|
+
});
|
|
3199
|
+
return;
|
|
3200
|
+
}
|
|
3201
|
+
try {
|
|
3202
|
+
const nextControllerId = registerGraph(config);
|
|
3203
|
+
programControllerIdsRef.current.set(id, nextControllerId);
|
|
3204
|
+
} catch (err) {
|
|
3205
|
+
pushError({
|
|
3206
|
+
message: `Failed to register program ${id}`,
|
|
3207
|
+
cause: err,
|
|
3208
|
+
phase: "registration",
|
|
3209
|
+
timestamp: performance.now()
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
});
|
|
3213
|
+
refreshControllerStatus();
|
|
3214
|
+
}, [
|
|
3215
|
+
buildProgramRegistrationConfig,
|
|
3216
|
+
pushError,
|
|
3217
|
+
ready,
|
|
3218
|
+
refreshControllerStatus,
|
|
3219
|
+
registerGraph,
|
|
3220
|
+
removeGraph,
|
|
3221
|
+
resolvedProgramAssets,
|
|
3222
|
+
resolveProgramById
|
|
3223
|
+
]);
|
|
3224
|
+
const playProgram = (0, import_react2.useCallback)(
|
|
3225
|
+
(id) => {
|
|
3226
|
+
if (!resolveProgramById(id)) {
|
|
3227
|
+
throw new Error(
|
|
3228
|
+
`Program ${id} is not part of the current asset bundle.`
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
programPlaybackRef.current.set(id, {
|
|
3232
|
+
id,
|
|
3233
|
+
state: "playing"
|
|
3234
|
+
});
|
|
3235
|
+
syncProgramPlaybackControllers();
|
|
3236
|
+
markActivity();
|
|
3237
|
+
},
|
|
3238
|
+
[markActivity, resolveProgramById, syncProgramPlaybackControllers]
|
|
3239
|
+
);
|
|
3240
|
+
const pauseProgram = (0, import_react2.useCallback)(
|
|
3241
|
+
(id) => {
|
|
3242
|
+
if (!resolveProgramById(id)) {
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
programPlaybackRef.current.set(id, {
|
|
3246
|
+
id,
|
|
3247
|
+
state: "paused"
|
|
3248
|
+
});
|
|
3249
|
+
syncProgramPlaybackControllers();
|
|
3250
|
+
updateLoopMode();
|
|
3251
|
+
},
|
|
3252
|
+
[resolveProgramById, syncProgramPlaybackControllers, updateLoopMode]
|
|
3253
|
+
);
|
|
3254
|
+
const stopProgram = (0, import_react2.useCallback)(
|
|
3255
|
+
(id, options) => {
|
|
3256
|
+
const program = resolveProgramById(id);
|
|
3257
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
3258
|
+
if (controllerId) {
|
|
3259
|
+
try {
|
|
3260
|
+
removeGraph(controllerId);
|
|
3261
|
+
} catch (err) {
|
|
3262
|
+
pushError({
|
|
3263
|
+
message: `Failed to stop program ${id}`,
|
|
3264
|
+
cause: err,
|
|
3265
|
+
phase: "registration",
|
|
3266
|
+
timestamp: performance.now()
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
programControllerIdsRef.current.delete(id);
|
|
3270
|
+
}
|
|
3271
|
+
programPlaybackRef.current.set(id, {
|
|
3272
|
+
id,
|
|
3273
|
+
state: "stopped"
|
|
3274
|
+
});
|
|
3275
|
+
if (program && options?.resetOutputs !== false) {
|
|
3276
|
+
deriveProgramResetValues(program).forEach(({ path, value }) => {
|
|
3277
|
+
setInput(path, { float: value });
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
refreshControllerStatus();
|
|
3281
|
+
updateLoopMode();
|
|
3282
|
+
},
|
|
3283
|
+
[
|
|
3284
|
+
deriveProgramResetValues,
|
|
3285
|
+
pushError,
|
|
3286
|
+
refreshControllerStatus,
|
|
3287
|
+
removeGraph,
|
|
3288
|
+
resolveProgramById,
|
|
3289
|
+
setInput,
|
|
3290
|
+
updateLoopMode
|
|
3291
|
+
]
|
|
3292
|
+
);
|
|
3293
|
+
const getProgramState = (0, import_react2.useCallback)(
|
|
3294
|
+
(id) => {
|
|
3295
|
+
const state = programPlaybackRef.current.get(id);
|
|
3296
|
+
if (!state) {
|
|
3297
|
+
return null;
|
|
3298
|
+
}
|
|
3299
|
+
return { state: state.state };
|
|
3300
|
+
},
|
|
3301
|
+
[]
|
|
3302
|
+
);
|
|
3303
|
+
(0, import_react2.useEffect)(() => {
|
|
3304
|
+
if (!ready || status.loading) {
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
syncProgramPlaybackControllers();
|
|
3308
|
+
}, [
|
|
3309
|
+
graphUpdateToken,
|
|
3310
|
+
ready,
|
|
3311
|
+
resolvedProgramAssets,
|
|
3312
|
+
status.loading,
|
|
3313
|
+
syncProgramPlaybackControllers
|
|
3314
|
+
]);
|
|
3315
|
+
const setAnimationActive = (0, import_react2.useCallback)(
|
|
3316
|
+
(active) => {
|
|
3317
|
+
const next = Boolean(active);
|
|
3318
|
+
if (animationSystemActiveRef.current === next) {
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
animationSystemActiveRef.current = next;
|
|
3322
|
+
if (!next) {
|
|
3323
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3324
|
+
state.playing = false;
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
updateLoopMode();
|
|
3328
|
+
},
|
|
3329
|
+
[updateLoopMode]
|
|
3330
|
+
);
|
|
3331
|
+
const isAnimationActive = (0, import_react2.useCallback)(
|
|
3332
|
+
() => animationSystemActiveRef.current,
|
|
3333
|
+
[]
|
|
3334
|
+
);
|
|
3335
|
+
const registerInputDriver = (0, import_react2.useCallback)(
|
|
3336
|
+
(id, factory) => {
|
|
3337
|
+
const driver = factory({
|
|
3338
|
+
setInput,
|
|
3339
|
+
setRendererValue,
|
|
3340
|
+
namespace,
|
|
3341
|
+
faceId
|
|
3342
|
+
});
|
|
3343
|
+
const wrapped = {
|
|
3344
|
+
start: () => {
|
|
3345
|
+
try {
|
|
3346
|
+
driver.start();
|
|
3347
|
+
} catch (err) {
|
|
3348
|
+
pushError({
|
|
3349
|
+
message: `Input driver ${id} failed to start`,
|
|
3350
|
+
cause: err,
|
|
3351
|
+
phase: "driver",
|
|
3352
|
+
timestamp: performance.now()
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
},
|
|
3356
|
+
stop: () => {
|
|
3357
|
+
try {
|
|
3358
|
+
driver.stop();
|
|
3359
|
+
} catch (err) {
|
|
3360
|
+
pushError({
|
|
3361
|
+
message: `Input driver ${id} failed to stop`,
|
|
3362
|
+
cause: err,
|
|
3363
|
+
phase: "driver",
|
|
3364
|
+
timestamp: performance.now()
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
},
|
|
3368
|
+
dispose: () => {
|
|
3369
|
+
try {
|
|
3370
|
+
driver.dispose();
|
|
3371
|
+
} catch (err) {
|
|
3372
|
+
pushError({
|
|
3373
|
+
message: `Input driver ${id} failed to dispose`,
|
|
3374
|
+
cause: err,
|
|
3375
|
+
phase: "driver",
|
|
3376
|
+
timestamp: performance.now()
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
};
|
|
3381
|
+
return wrapped;
|
|
3382
|
+
},
|
|
3383
|
+
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
3384
|
+
);
|
|
3385
|
+
const advanceAnimations = (0, import_react2.useCallback)(
|
|
3386
|
+
(dt) => {
|
|
3387
|
+
advanceAnimationTweens(dt);
|
|
3388
|
+
advanceClipPlayback(dt);
|
|
3389
|
+
},
|
|
3390
|
+
[advanceAnimationTweens, advanceClipPlayback]
|
|
3391
|
+
);
|
|
3392
|
+
const flushStagedInputs = (0, import_react2.useCallback)(() => {
|
|
3393
|
+
if (stagedInputsRef.current.size === 0) {
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
stagedInputsRef.current.forEach(({ value, shape }, path) => {
|
|
3397
|
+
orchestratorSetInput(path, value, shape);
|
|
3398
|
+
});
|
|
3399
|
+
stagedInputsRef.current.clear();
|
|
3400
|
+
}, [orchestratorSetInput]);
|
|
3401
|
+
const step = (0, import_react2.useCallback)(
|
|
3402
|
+
(dt, opts) => {
|
|
3403
|
+
if (dt > 0 && Number.isFinite(dt)) {
|
|
3404
|
+
const prev = avgStepDtRef.current ?? dt;
|
|
3405
|
+
const alpha = 0.1;
|
|
3406
|
+
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
3407
|
+
}
|
|
3408
|
+
advanceAnimations(dt);
|
|
3409
|
+
flushStagedInputs();
|
|
3410
|
+
if (driveOrchestratorRef.current || opts?.forceRuntime) {
|
|
3411
|
+
stepRuntime(dt);
|
|
3412
|
+
}
|
|
3413
|
+
},
|
|
3414
|
+
[advanceAnimations, flushStagedInputs, stepRuntime]
|
|
3415
|
+
);
|
|
3416
|
+
(0, import_react2.useEffect)(() => {
|
|
3417
|
+
if (loopMode !== "active") {
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
let rafId = null;
|
|
3421
|
+
let lastTime = null;
|
|
3422
|
+
const tick = (timestamp) => {
|
|
3423
|
+
if (loopModeRef.current !== "active") {
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
if (lastTime == null) {
|
|
3427
|
+
lastTime = timestamp;
|
|
3428
|
+
}
|
|
3429
|
+
const dt = Math.max(0, (timestamp - lastTime) / 1e3);
|
|
3430
|
+
lastTime = timestamp;
|
|
3431
|
+
step(dt || 0);
|
|
3432
|
+
requestLoopMode(computeDesiredLoopMode());
|
|
3433
|
+
rafId = requestAnimationFrame(tick);
|
|
3434
|
+
};
|
|
3435
|
+
rafId = requestAnimationFrame(tick);
|
|
3436
|
+
return () => {
|
|
3437
|
+
if (rafId !== null) {
|
|
3438
|
+
cancelAnimationFrame(rafId);
|
|
3439
|
+
}
|
|
3440
|
+
};
|
|
3441
|
+
}, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
|
|
3442
|
+
(0, import_react2.useEffect)(() => {
|
|
3443
|
+
if (loopMode !== "idle-visible" && loopMode !== "idle-hidden") {
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
if (typeof window === "undefined") {
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
const fps = loopMode === "idle-visible" ? VISIBLE_IDLE_FPS : HIDDEN_IDLE_FPS;
|
|
3450
|
+
if (fps <= 0) {
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
let lastTime = now();
|
|
3454
|
+
const interval = 1e3 / fps;
|
|
3455
|
+
const tick = () => {
|
|
3456
|
+
if (loopModeRef.current !== "idle-visible" && loopModeRef.current !== "idle-hidden") {
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
const current = now();
|
|
3460
|
+
const dt = Math.max(0, (current - lastTime) / 1e3);
|
|
3461
|
+
lastTime = current;
|
|
3462
|
+
step(dt || 0);
|
|
3463
|
+
requestLoopMode(computeDesiredLoopMode());
|
|
3464
|
+
};
|
|
3465
|
+
const intervalId = window.setInterval(tick, interval);
|
|
3466
|
+
return () => {
|
|
3467
|
+
window.clearInterval(intervalId);
|
|
3468
|
+
};
|
|
3469
|
+
}, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
|
|
3470
|
+
(0, import_react2.useEffect)(() => {
|
|
3471
|
+
return () => {
|
|
3472
|
+
animationTweensRef.current.clear();
|
|
3473
|
+
clipPlaybackRef.current.clear();
|
|
3474
|
+
programPlaybackRef.current.clear();
|
|
3475
|
+
programControllerIdsRef.current.clear();
|
|
3476
|
+
clipOutputValuesRef.current.clear();
|
|
3477
|
+
clipAggregateValuesRef.current.clear();
|
|
3478
|
+
};
|
|
3479
|
+
}, []);
|
|
3480
|
+
(0, import_react2.useEffect)(() => {
|
|
3481
|
+
if (typeof window === "undefined") {
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
const id = window.setInterval(() => {
|
|
3485
|
+
const avg = avgStepDtRef.current;
|
|
3486
|
+
const stepHz = avg && Number.isFinite(avg) && avg > 0 ? 1 / avg : void 0;
|
|
3487
|
+
reportStatus(
|
|
3488
|
+
(prev) => prev.stepHz === stepHz ? prev : { ...prev, stepHz }
|
|
3489
|
+
);
|
|
3490
|
+
}, 500);
|
|
3491
|
+
return () => window.clearInterval(id);
|
|
3492
|
+
}, [reportStatus]);
|
|
3493
|
+
const setGraphBundle = (0, import_react2.useCallback)(
|
|
3494
|
+
(bundle, options) => {
|
|
3495
|
+
const baseAssetBundle = latestEffectiveAssetBundleRef.current;
|
|
3496
|
+
const nextAssetBundle = applyRuntimeGraphBundle(baseAssetBundle, bundle);
|
|
3497
|
+
const plan = resolveRuntimeUpdatePlan(
|
|
3498
|
+
baseAssetBundle,
|
|
3499
|
+
nextAssetBundle,
|
|
3500
|
+
options?.tier ?? updateTierRef.current
|
|
3501
|
+
);
|
|
3502
|
+
pendingPlanRef.current = plan;
|
|
3503
|
+
previousBundleRef.current = nextAssetBundle;
|
|
3504
|
+
latestEffectiveAssetBundleRef.current = nextAssetBundle;
|
|
3505
|
+
suppressNextBundlePlanRef.current = true;
|
|
3506
|
+
setAssetBundleOverride(nextAssetBundle);
|
|
3507
|
+
if (plan.reregisterGraphs) {
|
|
3508
|
+
setGraphUpdateToken((prev) => prev + 1);
|
|
3509
|
+
}
|
|
3510
|
+
if (plan.reloadAssets) {
|
|
3511
|
+
reportStatus((prev) => ({
|
|
3512
|
+
...prev,
|
|
3513
|
+
loading: true,
|
|
3514
|
+
ready: false
|
|
3515
|
+
}));
|
|
3516
|
+
} else {
|
|
3517
|
+
reportStatus((prev) => ({
|
|
3518
|
+
...prev,
|
|
3519
|
+
loading: false
|
|
3520
|
+
}));
|
|
3521
|
+
}
|
|
3522
|
+
},
|
|
3523
|
+
[reportStatus]
|
|
3524
|
+
);
|
|
3525
|
+
const contextValue = (0, import_react2.useMemo)(
|
|
3526
|
+
() => ({
|
|
3527
|
+
...status,
|
|
3528
|
+
assetBundle,
|
|
3529
|
+
setInput,
|
|
3530
|
+
setGraphBundle,
|
|
3531
|
+
setValue: setRendererValue,
|
|
3532
|
+
stagePoseNeutral,
|
|
3533
|
+
animateValue,
|
|
3534
|
+
cancelAnimation,
|
|
3535
|
+
registerInputDriver,
|
|
3536
|
+
playAnimation,
|
|
3537
|
+
pauseAnimation,
|
|
3538
|
+
seekAnimation,
|
|
3539
|
+
setAnimationLoop,
|
|
3540
|
+
getAnimationState,
|
|
3541
|
+
stopAnimation,
|
|
3542
|
+
playProgram,
|
|
3543
|
+
pauseProgram,
|
|
3544
|
+
stopProgram,
|
|
3545
|
+
getProgramState,
|
|
3546
|
+
setAnimationActive,
|
|
3547
|
+
isAnimationActive,
|
|
3548
|
+
step,
|
|
3549
|
+
advanceAnimations,
|
|
3550
|
+
inputConstraints
|
|
3551
|
+
}),
|
|
3552
|
+
[
|
|
3553
|
+
status,
|
|
3554
|
+
assetBundle,
|
|
3555
|
+
setInput,
|
|
3556
|
+
setGraphBundle,
|
|
3557
|
+
setRendererValue,
|
|
3558
|
+
stagePoseNeutral,
|
|
3559
|
+
animateValue,
|
|
3560
|
+
cancelAnimation,
|
|
3561
|
+
registerInputDriver,
|
|
3562
|
+
playAnimation,
|
|
3563
|
+
pauseAnimation,
|
|
3564
|
+
seekAnimation,
|
|
3565
|
+
setAnimationLoop,
|
|
3566
|
+
getAnimationState,
|
|
3567
|
+
stopAnimation,
|
|
3568
|
+
playProgram,
|
|
3569
|
+
pauseProgram,
|
|
3570
|
+
stopProgram,
|
|
3571
|
+
getProgramState,
|
|
3572
|
+
setAnimationActive,
|
|
3573
|
+
isAnimationActive,
|
|
3574
|
+
step,
|
|
3575
|
+
advanceAnimations,
|
|
3576
|
+
inputConstraints
|
|
3577
|
+
]
|
|
3578
|
+
);
|
|
3579
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VizijRuntimeContext.Provider, { value: contextValue, children });
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// src/VizijRuntimeFace.tsx
|
|
3583
|
+
var import_react4 = require("react");
|
|
3584
|
+
var import_render2 = require("@vizij/render");
|
|
3585
|
+
|
|
3586
|
+
// src/hooks/useVizijRuntime.ts
|
|
3587
|
+
var import_react3 = require("react");
|
|
3588
|
+
function useVizijRuntime() {
|
|
3589
|
+
const ctx = (0, import_react3.useContext)(VizijRuntimeContext);
|
|
3590
|
+
if (!ctx) {
|
|
3591
|
+
throw new Error(
|
|
3592
|
+
"useVizijRuntime must be used within a VizijRuntimeProvider."
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
return ctx;
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
// src/VizijRuntimeFace.tsx
|
|
3599
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
3600
|
+
function VizijRuntimeFaceInner({
|
|
3601
|
+
namespaceOverride,
|
|
3602
|
+
...props
|
|
3603
|
+
}) {
|
|
3604
|
+
const { rootId, namespace } = useVizijRuntime();
|
|
3605
|
+
if (!rootId) {
|
|
3606
|
+
return null;
|
|
3607
|
+
}
|
|
3608
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
3609
|
+
import_render2.Vizij,
|
|
3610
|
+
{
|
|
3611
|
+
...props,
|
|
3612
|
+
rootId,
|
|
3613
|
+
namespace: namespaceOverride ?? namespace
|
|
3614
|
+
}
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
var VizijRuntimeFace = (0, import_react4.memo)(VizijRuntimeFaceInner);
|
|
3618
|
+
|
|
3619
|
+
// src/hooks/useOptionalVizijRuntime.ts
|
|
3620
|
+
var import_react5 = require("react");
|
|
3621
|
+
function useOptionalVizijRuntime() {
|
|
3622
|
+
return (0, import_react5.useContext)(VizijRuntimeContext);
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
// src/hooks/useVizijOutputs.ts
|
|
3626
|
+
var import_render3 = require("@vizij/render");
|
|
3627
|
+
var import_utils2 = require("@vizij/utils");
|
|
3628
|
+
function useVizijOutputs(paths) {
|
|
3629
|
+
const { namespace } = useVizijRuntime();
|
|
3630
|
+
return (0, import_render3.useVizijStore)((state) => {
|
|
3631
|
+
const result = {};
|
|
3632
|
+
paths.forEach((path) => {
|
|
3633
|
+
const lookup = (0, import_utils2.getLookup)(namespace, path);
|
|
3634
|
+
result[path] = state.values.get(lookup);
|
|
3635
|
+
});
|
|
3636
|
+
return result;
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// src/hooks/useRigInput.ts
|
|
3641
|
+
var import_react6 = require("react");
|
|
3642
|
+
var import_render4 = require("@vizij/render");
|
|
3643
|
+
var import_utils3 = require("@vizij/utils");
|
|
3644
|
+
function useRigInput(path) {
|
|
3645
|
+
const { namespace, setInput } = useVizijRuntime();
|
|
3646
|
+
const value = (0, import_render4.useVizijStore)((state) => {
|
|
3647
|
+
return state.values.get((0, import_utils3.getLookup)(namespace, path));
|
|
3648
|
+
});
|
|
3649
|
+
const setter = (0, import_react6.useCallback)(
|
|
3650
|
+
(next, shape) => {
|
|
3651
|
+
setInput(path, next, shape);
|
|
3652
|
+
},
|
|
3653
|
+
[path, setInput]
|
|
3654
|
+
);
|
|
3655
|
+
return [value, setter];
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
// src/utils/faceControls.ts
|
|
3659
|
+
var DEFAULT_GAZE_CONTROL = {
|
|
3660
|
+
min: -1,
|
|
3661
|
+
max: 1,
|
|
3662
|
+
defaultValue: 0
|
|
3663
|
+
};
|
|
3664
|
+
var DEFAULT_BLINK_CONTROL = {
|
|
3665
|
+
min: 0,
|
|
3666
|
+
max: 1,
|
|
3667
|
+
defaultValue: 0
|
|
3668
|
+
};
|
|
3669
|
+
var STANDARD_VIZIJ_EYE_PATHS = {
|
|
3670
|
+
leftX: "/standard/vizij/left_eye/pos/x",
|
|
3671
|
+
leftY: "/standard/vizij/left_eye/pos/y",
|
|
3672
|
+
rightX: "/standard/vizij/right_eye/pos/x",
|
|
3673
|
+
rightY: "/standard/vizij/right_eye/pos/y",
|
|
3674
|
+
leftUpper: "/standard/vizij/left_eye_top_eyelid/pos/y",
|
|
3675
|
+
rightUpper: "/standard/vizij/right_eye_top_eyelid/pos/y"
|
|
3676
|
+
};
|
|
3677
|
+
var LEGACY_STANDARD_EYE_PATHS = {
|
|
3678
|
+
leftX: "/standard/left_eye/pos/x",
|
|
3679
|
+
leftY: "/standard/left_eye/pos/y",
|
|
3680
|
+
rightX: "/standard/right_eye/pos/x",
|
|
3681
|
+
rightY: "/standard/right_eye/pos/y",
|
|
3682
|
+
leftUpper: "/standard/left_eye_top_eyelid/pos/y",
|
|
3683
|
+
rightUpper: "/standard/right_eye_top_eyelid/pos/y"
|
|
3684
|
+
};
|
|
3685
|
+
var PROPSRIG_EYE_PATHS = {
|
|
3686
|
+
leftX: "/propsrig/l_eye/translation/x",
|
|
3687
|
+
leftY: "/propsrig/l_eye/translation/y",
|
|
3688
|
+
rightX: "/propsrig/r_eye/translation/x",
|
|
3689
|
+
rightY: "/propsrig/r_eye/translation/y"
|
|
3690
|
+
};
|
|
3691
|
+
function clamp(value, min, max) {
|
|
3692
|
+
return Math.min(Math.max(value, min), max);
|
|
3693
|
+
}
|
|
3694
|
+
function normalizeInputPath(path) {
|
|
3695
|
+
const trimmed = path?.trim() ?? "";
|
|
3696
|
+
if (!trimmed) {
|
|
3697
|
+
return null;
|
|
3698
|
+
}
|
|
3699
|
+
if (trimmed.startsWith("/")) {
|
|
3700
|
+
return trimmed;
|
|
3701
|
+
}
|
|
3702
|
+
const rigMatch = trimmed.match(/^rig\/[^/]+(\/.*)$/);
|
|
3703
|
+
if (rigMatch?.[1]) {
|
|
3704
|
+
return rigMatch[1];
|
|
3705
|
+
}
|
|
3706
|
+
return `/${trimmed}`;
|
|
3707
|
+
}
|
|
3708
|
+
function buildControlsLookup(assetBundle) {
|
|
3709
|
+
const metadata = assetBundle.rig?.inputMetadata ?? [];
|
|
3710
|
+
const metadataByPath = /* @__PURE__ */ new Map();
|
|
3711
|
+
const pathSet = /* @__PURE__ */ new Set();
|
|
3712
|
+
metadata.forEach((entry) => {
|
|
3713
|
+
const normalized = normalizeInputPath(entry.path);
|
|
3714
|
+
if (!normalized) {
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
pathSet.add(normalized);
|
|
3718
|
+
if (!metadataByPath.has(normalized)) {
|
|
3719
|
+
metadataByPath.set(normalized, entry);
|
|
3720
|
+
}
|
|
3721
|
+
});
|
|
3722
|
+
return { pathSet, metadataByPath };
|
|
3723
|
+
}
|
|
3724
|
+
function resolveConstraint(absolutePath, relativePath, inputConstraints) {
|
|
3725
|
+
if (!inputConstraints) {
|
|
3726
|
+
return null;
|
|
3727
|
+
}
|
|
3728
|
+
const normalizedRelative = relativePath.replace(/^\//, "");
|
|
3729
|
+
const candidates = [
|
|
3730
|
+
absolutePath,
|
|
3731
|
+
relativePath,
|
|
3732
|
+
normalizedRelative,
|
|
3733
|
+
absolutePath.replace(/^rig\/[^/]+\//, "")
|
|
3734
|
+
];
|
|
3735
|
+
for (const candidate of candidates) {
|
|
3736
|
+
const constraint = inputConstraints[candidate];
|
|
3737
|
+
if (constraint) {
|
|
3738
|
+
return constraint;
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
return null;
|
|
3742
|
+
}
|
|
3743
|
+
function createControl(faceId, lookup, relativePath, inputConstraints, fallback) {
|
|
3744
|
+
const normalizedRelative = normalizeInputPath(relativePath);
|
|
3745
|
+
if (!normalizedRelative || !lookup.pathSet.has(normalizedRelative)) {
|
|
3746
|
+
return null;
|
|
3747
|
+
}
|
|
3748
|
+
const absolutePath = buildRigInputPath(faceId, normalizedRelative);
|
|
3749
|
+
const metadata = lookup.metadataByPath.get(normalizedRelative);
|
|
3750
|
+
const constraint = resolveConstraint(
|
|
3751
|
+
absolutePath,
|
|
3752
|
+
normalizedRelative,
|
|
3753
|
+
inputConstraints
|
|
3754
|
+
);
|
|
3755
|
+
const min = Number.isFinite(Number(constraint?.min ?? metadata?.range?.min)) ? Number(constraint?.min ?? metadata?.range?.min) : fallback.min;
|
|
3756
|
+
const max = Number.isFinite(Number(constraint?.max ?? metadata?.range?.max)) ? Number(constraint?.max ?? metadata?.range?.max) : fallback.max;
|
|
3757
|
+
const midpoint = min + (max - min) / 2;
|
|
3758
|
+
const defaultValue = Number.isFinite(
|
|
3759
|
+
Number(constraint?.defaultValue ?? metadata?.defaultValue)
|
|
3760
|
+
) ? Number(constraint?.defaultValue ?? metadata?.defaultValue) : Number.isFinite(midpoint) ? midpoint : fallback.defaultValue;
|
|
3761
|
+
return {
|
|
3762
|
+
path: absolutePath,
|
|
3763
|
+
min,
|
|
3764
|
+
max,
|
|
3765
|
+
defaultValue
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
function hasAll(controls, keys) {
|
|
3769
|
+
return keys.every((key) => Boolean(controls[key]));
|
|
3770
|
+
}
|
|
3771
|
+
function resolveFaceControls(assetBundle, runtimeFaceId, inputConstraints) {
|
|
3772
|
+
const faceId = assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? runtimeFaceId ?? "face";
|
|
3773
|
+
const lookup = buildControlsLookup(assetBundle);
|
|
3774
|
+
const standardVizijControls = {
|
|
3775
|
+
leftX: createControl(
|
|
3776
|
+
faceId,
|
|
3777
|
+
lookup,
|
|
3778
|
+
STANDARD_VIZIJ_EYE_PATHS.leftX,
|
|
3779
|
+
inputConstraints,
|
|
3780
|
+
DEFAULT_GAZE_CONTROL
|
|
3781
|
+
),
|
|
3782
|
+
leftY: createControl(
|
|
3783
|
+
faceId,
|
|
3784
|
+
lookup,
|
|
3785
|
+
STANDARD_VIZIJ_EYE_PATHS.leftY,
|
|
3786
|
+
inputConstraints,
|
|
3787
|
+
DEFAULT_GAZE_CONTROL
|
|
3788
|
+
),
|
|
3789
|
+
rightX: createControl(
|
|
3790
|
+
faceId,
|
|
3791
|
+
lookup,
|
|
3792
|
+
STANDARD_VIZIJ_EYE_PATHS.rightX,
|
|
3793
|
+
inputConstraints,
|
|
3794
|
+
DEFAULT_GAZE_CONTROL
|
|
3795
|
+
),
|
|
3796
|
+
rightY: createControl(
|
|
3797
|
+
faceId,
|
|
3798
|
+
lookup,
|
|
3799
|
+
STANDARD_VIZIJ_EYE_PATHS.rightY,
|
|
3800
|
+
inputConstraints,
|
|
3801
|
+
DEFAULT_GAZE_CONTROL
|
|
3802
|
+
),
|
|
3803
|
+
leftUpper: createControl(
|
|
3804
|
+
faceId,
|
|
3805
|
+
lookup,
|
|
3806
|
+
STANDARD_VIZIJ_EYE_PATHS.leftUpper,
|
|
3807
|
+
inputConstraints,
|
|
3808
|
+
DEFAULT_GAZE_CONTROL
|
|
3809
|
+
),
|
|
3810
|
+
rightUpper: createControl(
|
|
3811
|
+
faceId,
|
|
3812
|
+
lookup,
|
|
3813
|
+
STANDARD_VIZIJ_EYE_PATHS.rightUpper,
|
|
3814
|
+
inputConstraints,
|
|
3815
|
+
DEFAULT_GAZE_CONTROL
|
|
3816
|
+
)
|
|
3817
|
+
};
|
|
3818
|
+
const legacyStandardControls = {
|
|
3819
|
+
leftX: createControl(
|
|
3820
|
+
faceId,
|
|
3821
|
+
lookup,
|
|
3822
|
+
LEGACY_STANDARD_EYE_PATHS.leftX,
|
|
3823
|
+
inputConstraints,
|
|
3824
|
+
DEFAULT_GAZE_CONTROL
|
|
3825
|
+
),
|
|
3826
|
+
leftY: createControl(
|
|
3827
|
+
faceId,
|
|
3828
|
+
lookup,
|
|
3829
|
+
LEGACY_STANDARD_EYE_PATHS.leftY,
|
|
3830
|
+
inputConstraints,
|
|
3831
|
+
DEFAULT_GAZE_CONTROL
|
|
3832
|
+
),
|
|
3833
|
+
rightX: createControl(
|
|
3834
|
+
faceId,
|
|
3835
|
+
lookup,
|
|
3836
|
+
LEGACY_STANDARD_EYE_PATHS.rightX,
|
|
3837
|
+
inputConstraints,
|
|
3838
|
+
DEFAULT_GAZE_CONTROL
|
|
3839
|
+
),
|
|
3840
|
+
rightY: createControl(
|
|
3841
|
+
faceId,
|
|
3842
|
+
lookup,
|
|
3843
|
+
LEGACY_STANDARD_EYE_PATHS.rightY,
|
|
3844
|
+
inputConstraints,
|
|
3845
|
+
DEFAULT_GAZE_CONTROL
|
|
3846
|
+
),
|
|
3847
|
+
leftUpper: createControl(
|
|
3848
|
+
faceId,
|
|
3849
|
+
lookup,
|
|
3850
|
+
LEGACY_STANDARD_EYE_PATHS.leftUpper,
|
|
3851
|
+
inputConstraints,
|
|
3852
|
+
DEFAULT_GAZE_CONTROL
|
|
3853
|
+
),
|
|
3854
|
+
rightUpper: createControl(
|
|
3855
|
+
faceId,
|
|
3856
|
+
lookup,
|
|
3857
|
+
LEGACY_STANDARD_EYE_PATHS.rightUpper,
|
|
3858
|
+
inputConstraints,
|
|
3859
|
+
DEFAULT_GAZE_CONTROL
|
|
3860
|
+
)
|
|
3861
|
+
};
|
|
3862
|
+
const propsrigControls = {
|
|
3863
|
+
leftX: createControl(
|
|
3864
|
+
faceId,
|
|
3865
|
+
lookup,
|
|
3866
|
+
PROPSRIG_EYE_PATHS.leftX,
|
|
3867
|
+
inputConstraints,
|
|
3868
|
+
DEFAULT_GAZE_CONTROL
|
|
3869
|
+
),
|
|
3870
|
+
leftY: createControl(
|
|
3871
|
+
faceId,
|
|
3872
|
+
lookup,
|
|
3873
|
+
PROPSRIG_EYE_PATHS.leftY,
|
|
3874
|
+
inputConstraints,
|
|
3875
|
+
DEFAULT_GAZE_CONTROL
|
|
3876
|
+
),
|
|
3877
|
+
rightX: createControl(
|
|
3878
|
+
faceId,
|
|
3879
|
+
lookup,
|
|
3880
|
+
PROPSRIG_EYE_PATHS.rightX,
|
|
3881
|
+
inputConstraints,
|
|
3882
|
+
DEFAULT_GAZE_CONTROL
|
|
3883
|
+
),
|
|
3884
|
+
rightY: createControl(
|
|
3885
|
+
faceId,
|
|
3886
|
+
lookup,
|
|
3887
|
+
PROPSRIG_EYE_PATHS.rightY,
|
|
3888
|
+
inputConstraints,
|
|
3889
|
+
DEFAULT_GAZE_CONTROL
|
|
3890
|
+
)
|
|
3891
|
+
};
|
|
3892
|
+
const coupledGazeControls = {
|
|
3893
|
+
leftX: createControl(
|
|
3894
|
+
faceId,
|
|
3895
|
+
lookup,
|
|
3896
|
+
"/gaze/left_right",
|
|
3897
|
+
inputConstraints,
|
|
3898
|
+
DEFAULT_GAZE_CONTROL
|
|
3899
|
+
),
|
|
3900
|
+
leftY: createControl(
|
|
3901
|
+
faceId,
|
|
3902
|
+
lookup,
|
|
3903
|
+
"/gaze/up_down",
|
|
3904
|
+
inputConstraints,
|
|
3905
|
+
DEFAULT_GAZE_CONTROL
|
|
3906
|
+
),
|
|
3907
|
+
rightX: createControl(
|
|
3908
|
+
faceId,
|
|
3909
|
+
lookup,
|
|
3910
|
+
"/gaze/left_right_copy",
|
|
3911
|
+
inputConstraints,
|
|
3912
|
+
DEFAULT_GAZE_CONTROL
|
|
3913
|
+
) ?? createControl(
|
|
3914
|
+
faceId,
|
|
3915
|
+
lookup,
|
|
3916
|
+
"/gaze/left_right",
|
|
3917
|
+
inputConstraints,
|
|
3918
|
+
DEFAULT_GAZE_CONTROL
|
|
3919
|
+
),
|
|
3920
|
+
rightY: createControl(
|
|
3921
|
+
faceId,
|
|
3922
|
+
lookup,
|
|
3923
|
+
"/gaze/up_down_copy",
|
|
3924
|
+
inputConstraints,
|
|
3925
|
+
DEFAULT_GAZE_CONTROL
|
|
3926
|
+
) ?? createControl(
|
|
3927
|
+
faceId,
|
|
3928
|
+
lookup,
|
|
3929
|
+
"/gaze/up_down",
|
|
3930
|
+
inputConstraints,
|
|
3931
|
+
DEFAULT_GAZE_CONTROL
|
|
3932
|
+
)
|
|
3933
|
+
};
|
|
3934
|
+
const blinkFromLids = createControl(
|
|
3935
|
+
faceId,
|
|
3936
|
+
lookup,
|
|
3937
|
+
"/lids/blink",
|
|
3938
|
+
inputConstraints,
|
|
3939
|
+
DEFAULT_BLINK_CONTROL
|
|
3940
|
+
);
|
|
3941
|
+
const blinkDirect = createControl(
|
|
3942
|
+
faceId,
|
|
3943
|
+
lookup,
|
|
3944
|
+
"/blink",
|
|
3945
|
+
inputConstraints,
|
|
3946
|
+
DEFAULT_BLINK_CONTROL
|
|
3947
|
+
);
|
|
3948
|
+
if (hasAll(standardVizijControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3949
|
+
return {
|
|
3950
|
+
faceId,
|
|
3951
|
+
gazeSource: "standard-vizij",
|
|
3952
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
3953
|
+
eyes: {
|
|
3954
|
+
leftX: standardVizijControls.leftX,
|
|
3955
|
+
leftY: standardVizijControls.leftY,
|
|
3956
|
+
rightX: standardVizijControls.rightX,
|
|
3957
|
+
rightY: standardVizijControls.rightY
|
|
3958
|
+
},
|
|
3959
|
+
eyelids: {
|
|
3960
|
+
leftUpper: standardVizijControls.leftUpper,
|
|
3961
|
+
rightUpper: standardVizijControls.rightUpper
|
|
3962
|
+
},
|
|
3963
|
+
blink: blinkFromLids ?? blinkDirect
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
if (hasAll(legacyStandardControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3967
|
+
return {
|
|
3968
|
+
faceId,
|
|
3969
|
+
gazeSource: "standard",
|
|
3970
|
+
blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
|
|
3971
|
+
eyes: {
|
|
3972
|
+
leftX: legacyStandardControls.leftX,
|
|
3973
|
+
leftY: legacyStandardControls.leftY,
|
|
3974
|
+
rightX: legacyStandardControls.rightX,
|
|
3975
|
+
rightY: legacyStandardControls.rightY
|
|
3976
|
+
},
|
|
3977
|
+
eyelids: {
|
|
3978
|
+
leftUpper: legacyStandardControls.leftUpper,
|
|
3979
|
+
rightUpper: legacyStandardControls.rightUpper
|
|
3980
|
+
},
|
|
3981
|
+
blink: blinkDirect ?? blinkFromLids
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
if (hasAll(propsrigControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3985
|
+
return {
|
|
3986
|
+
faceId,
|
|
3987
|
+
gazeSource: "propsrig",
|
|
3988
|
+
blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
|
|
3989
|
+
eyes: {
|
|
3990
|
+
leftX: propsrigControls.leftX,
|
|
3991
|
+
leftY: propsrigControls.leftY,
|
|
3992
|
+
rightX: propsrigControls.rightX,
|
|
3993
|
+
rightY: propsrigControls.rightY
|
|
3994
|
+
},
|
|
3995
|
+
eyelids: {
|
|
3996
|
+
leftUpper: null,
|
|
3997
|
+
rightUpper: null
|
|
3998
|
+
},
|
|
3999
|
+
blink: blinkDirect ?? blinkFromLids
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
if (hasAll(coupledGazeControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
4003
|
+
return {
|
|
4004
|
+
faceId,
|
|
4005
|
+
gazeSource: "coupled-gaze",
|
|
4006
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
4007
|
+
eyes: {
|
|
4008
|
+
leftX: coupledGazeControls.leftX,
|
|
4009
|
+
leftY: coupledGazeControls.leftY,
|
|
4010
|
+
rightX: coupledGazeControls.rightX,
|
|
4011
|
+
rightY: coupledGazeControls.rightY
|
|
4012
|
+
},
|
|
4013
|
+
eyelids: {
|
|
4014
|
+
leftUpper: null,
|
|
4015
|
+
rightUpper: null
|
|
4016
|
+
},
|
|
4017
|
+
blink: blinkFromLids ?? blinkDirect
|
|
4018
|
+
};
|
|
4019
|
+
}
|
|
4020
|
+
return {
|
|
4021
|
+
faceId,
|
|
4022
|
+
gazeSource: "none",
|
|
4023
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
4024
|
+
eyes: {
|
|
4025
|
+
leftX: null,
|
|
4026
|
+
leftY: null,
|
|
4027
|
+
rightX: null,
|
|
4028
|
+
rightY: null
|
|
4029
|
+
},
|
|
4030
|
+
eyelids: {
|
|
4031
|
+
leftUpper: null,
|
|
4032
|
+
rightUpper: null
|
|
4033
|
+
},
|
|
4034
|
+
blink: blinkFromLids ?? blinkDirect
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
function mapNormalizedControlValue(control, normalizedValue) {
|
|
4038
|
+
const value = clamp(normalizedValue, -1, 1);
|
|
4039
|
+
if (value === 0) {
|
|
4040
|
+
return control.defaultValue;
|
|
4041
|
+
}
|
|
4042
|
+
if (value > 0) {
|
|
4043
|
+
return control.defaultValue + (control.max - control.defaultValue) * value;
|
|
4044
|
+
}
|
|
4045
|
+
return control.defaultValue + (control.defaultValue - control.min) * value;
|
|
4046
|
+
}
|
|
4047
|
+
function mapUnitControlValue(control, unitValue) {
|
|
4048
|
+
const value = clamp(unitValue, 0, 1);
|
|
4049
|
+
return control.defaultValue + (control.max - control.defaultValue) * value;
|
|
4050
|
+
}
|
|
4051
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4052
|
+
0 && (module.exports = {
|
|
4053
|
+
EMOTION_POSE_KEYS,
|
|
4054
|
+
EXPRESSIVE_EMOTION_POSE_KEYS,
|
|
4055
|
+
POSE_WEIGHT_INPUT_PATH_PREFIX,
|
|
4056
|
+
VISEME_POSE_KEYS,
|
|
4057
|
+
VizijRuntimeFace,
|
|
4058
|
+
VizijRuntimeProvider,
|
|
4059
|
+
buildPoseWeightInputPathSegment,
|
|
4060
|
+
buildPoseWeightPathMap,
|
|
4061
|
+
buildPoseWeightRelativePath,
|
|
4062
|
+
buildRigInputPath,
|
|
4063
|
+
buildSemanticPoseWeightPathMap,
|
|
4064
|
+
filterPosesBySemanticKind,
|
|
4065
|
+
getPoseSemanticKey,
|
|
4066
|
+
mapNormalizedControlValue,
|
|
4067
|
+
mapUnitControlValue,
|
|
4068
|
+
normalizePoseSemanticKey,
|
|
4069
|
+
resolveFaceControls,
|
|
4070
|
+
resolvePoseMembership,
|
|
4071
|
+
resolvePoseSemantics,
|
|
4072
|
+
resolveRuntimeUpdatePlan,
|
|
4073
|
+
useOptionalVizijRuntime,
|
|
4074
|
+
useRigInput,
|
|
4075
|
+
useVizijOutputs,
|
|
4076
|
+
useVizijRuntime
|
|
4077
|
+
});
|