@vizij/runtime-react 0.1.0 → 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 +9 -3
- package/dist/index.cjs +2432 -184
- package/dist/index.d.cts +142 -7
- package/dist/index.d.ts +142 -7
- package/dist/index.js +2411 -182
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -21,11 +21,143 @@ import {
|
|
|
21
21
|
} from "@vizij/orchestrator-react";
|
|
22
22
|
import { compileIrGraph } from "@vizij/node-graph-authoring";
|
|
23
23
|
import { valueAsNumber as valueAsNumber2 } from "@vizij/value-json";
|
|
24
|
+
import { getLookup } from "@vizij/utils";
|
|
24
25
|
|
|
25
26
|
// src/context.ts
|
|
26
27
|
import { createContext } from "react";
|
|
27
28
|
var VizijRuntimeContext = createContext(null);
|
|
28
29
|
|
|
30
|
+
// src/updatePolicy.ts
|
|
31
|
+
function applyRuntimeGraphBundle(base, bundle) {
|
|
32
|
+
const next = {
|
|
33
|
+
...base
|
|
34
|
+
};
|
|
35
|
+
const hasRigOverride = Object.prototype.hasOwnProperty.call(bundle, "rig");
|
|
36
|
+
const hasPoseOverride = Object.prototype.hasOwnProperty.call(bundle, "pose");
|
|
37
|
+
const hasAnimationsOverride = Object.prototype.hasOwnProperty.call(
|
|
38
|
+
bundle,
|
|
39
|
+
"animations"
|
|
40
|
+
);
|
|
41
|
+
const hasProgramsOverride = Object.prototype.hasOwnProperty.call(
|
|
42
|
+
bundle,
|
|
43
|
+
"programs"
|
|
44
|
+
);
|
|
45
|
+
if (hasRigOverride) {
|
|
46
|
+
if (bundle.rig) {
|
|
47
|
+
next.rig = bundle.rig;
|
|
48
|
+
} else {
|
|
49
|
+
delete next.rig;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (hasPoseOverride) {
|
|
53
|
+
if (bundle.pose) {
|
|
54
|
+
next.pose = bundle.pose;
|
|
55
|
+
} else {
|
|
56
|
+
delete next.pose;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (hasAnimationsOverride) {
|
|
60
|
+
next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
|
|
61
|
+
}
|
|
62
|
+
if (hasProgramsOverride) {
|
|
63
|
+
next.programs = Array.isArray(bundle.programs) ? bundle.programs : void 0;
|
|
64
|
+
}
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
67
|
+
function normalizeSpecPayload(value) {
|
|
68
|
+
if (!value) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return JSON.stringify(value);
|
|
73
|
+
} catch {
|
|
74
|
+
return String(value);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function glbSignature(glb) {
|
|
78
|
+
if (glb.kind === "url") {
|
|
79
|
+
return `url:${glb.src}`;
|
|
80
|
+
}
|
|
81
|
+
if (glb.kind === "blob") {
|
|
82
|
+
return `blob:${glb.blob?.size ?? 0}`;
|
|
83
|
+
}
|
|
84
|
+
return `world:${normalizeSpecPayload(glb.world)}`;
|
|
85
|
+
}
|
|
86
|
+
function graphSignature(graph) {
|
|
87
|
+
if (!graph) {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
const id = graph.id ?? "";
|
|
91
|
+
return `${id}:${normalizeSpecPayload(graph.spec ?? graph.ir ?? null)}`;
|
|
92
|
+
}
|
|
93
|
+
function poseSignature(pose) {
|
|
94
|
+
if (!pose) {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
const graph = pose.graph;
|
|
98
|
+
const config = pose.config;
|
|
99
|
+
const graphPart = graph ? graphSignature({ id: graph.id, spec: graph.spec }) : "";
|
|
100
|
+
const configPart = config ? normalizeSpecPayload(config) : "";
|
|
101
|
+
return `${graphPart}:${configPart}`;
|
|
102
|
+
}
|
|
103
|
+
function animationsSignature(animations) {
|
|
104
|
+
if (!Array.isArray(animations) || animations.length === 0) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
return animations.map((animation) => {
|
|
108
|
+
const id = animation.id ?? "";
|
|
109
|
+
const clipSignature = normalizeSpecPayload(animation.clip ?? null);
|
|
110
|
+
const setupSignature = normalizeSpecPayload(animation.setup ?? null);
|
|
111
|
+
const weightSignature = animation.weight == null ? "" : String(animation.weight);
|
|
112
|
+
return `${id}:${clipSignature}:${setupSignature}:${weightSignature}`;
|
|
113
|
+
}).sort().join("|");
|
|
114
|
+
}
|
|
115
|
+
function programsSignature(programs) {
|
|
116
|
+
if (!Array.isArray(programs) || programs.length === 0) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
return programs.map((program) => {
|
|
120
|
+
const id = program.id ?? "";
|
|
121
|
+
const label = program.label ?? "";
|
|
122
|
+
const graphId = program.graph?.id ?? "";
|
|
123
|
+
const graphPayload = normalizeSpecPayload(
|
|
124
|
+
program.graph?.spec ?? program.graph?.ir ?? null
|
|
125
|
+
);
|
|
126
|
+
const resetValues = normalizeSpecPayload(program.resetValues ?? null);
|
|
127
|
+
return `${id}:${label}:${graphId}:${graphPayload}:${resetValues}`;
|
|
128
|
+
}).sort().join("|");
|
|
129
|
+
}
|
|
130
|
+
function resolveRuntimeUpdatePlan(previous, next, tier) {
|
|
131
|
+
if (!previous) {
|
|
132
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
133
|
+
}
|
|
134
|
+
const glbChanged = glbSignature(previous.glb) !== glbSignature(next.glb);
|
|
135
|
+
const rigChanged = graphSignature(previous.rig) !== graphSignature(next.rig);
|
|
136
|
+
const poseChanged = poseSignature(previous.pose) !== poseSignature(next.pose);
|
|
137
|
+
const rigReferenceChanged = previous.rig?.id !== next.rig?.id || previous.rig?.spec !== next.rig?.spec || previous.rig?.ir !== next.rig?.ir;
|
|
138
|
+
const poseReferenceChanged = previous.pose?.graph?.id !== next.pose?.graph?.id || previous.pose?.graph?.spec !== next.pose?.graph?.spec || previous.pose?.config !== next.pose?.config;
|
|
139
|
+
const graphsChanged = rigChanged || poseChanged || rigReferenceChanged || poseReferenceChanged;
|
|
140
|
+
const animationsChanged = animationsSignature(previous.animations) !== animationsSignature(next.animations);
|
|
141
|
+
const programsChanged = programsSignature(previous.programs) !== programsSignature(next.programs);
|
|
142
|
+
const controllersChanged = graphsChanged || animationsChanged || programsChanged;
|
|
143
|
+
if (tier === "assets") {
|
|
144
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
145
|
+
}
|
|
146
|
+
if (tier === "graphs") {
|
|
147
|
+
if (glbChanged) {
|
|
148
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
149
|
+
}
|
|
150
|
+
return { reloadAssets: false, reregisterGraphs: controllersChanged };
|
|
151
|
+
}
|
|
152
|
+
if (glbChanged) {
|
|
153
|
+
return { reloadAssets: true, reregisterGraphs: false };
|
|
154
|
+
}
|
|
155
|
+
if (controllersChanged) {
|
|
156
|
+
return { reloadAssets: false, reregisterGraphs: true };
|
|
157
|
+
}
|
|
158
|
+
return { reloadAssets: false, reregisterGraphs: false };
|
|
159
|
+
}
|
|
160
|
+
|
|
29
161
|
// src/utils/graph.ts
|
|
30
162
|
function getNodes(spec) {
|
|
31
163
|
if (!spec || typeof spec !== "object") {
|
|
@@ -70,6 +202,12 @@ function collectInputPaths(spec) {
|
|
|
70
202
|
}
|
|
71
203
|
function collectInputPathMap(spec) {
|
|
72
204
|
const map = {};
|
|
205
|
+
const addVariant = (key, path, force = false) => {
|
|
206
|
+
if (!key || !force && map[key]) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
map[key] = path;
|
|
210
|
+
};
|
|
73
211
|
const nodes = getNodes(spec);
|
|
74
212
|
nodes.forEach((node) => {
|
|
75
213
|
if (String(node.type ?? "").toLowerCase() !== "input") {
|
|
@@ -81,11 +219,653 @@ function collectInputPathMap(spec) {
|
|
|
81
219
|
}
|
|
82
220
|
const id = String(node.id ?? "");
|
|
83
221
|
const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
|
|
84
|
-
|
|
222
|
+
const trimmedPath = path.trim();
|
|
223
|
+
addVariant(key, trimmedPath);
|
|
224
|
+
if (key.startsWith("direct_")) {
|
|
225
|
+
addVariant(key.slice("direct_".length), trimmedPath, true);
|
|
226
|
+
}
|
|
227
|
+
if (key.startsWith("pose_control_")) {
|
|
228
|
+
addVariant(key.slice("pose_control_".length), trimmedPath);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return map;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/utils/posePaths.ts
|
|
235
|
+
var POSE_WEIGHT_INPUT_PATH_PREFIX = "/poses/";
|
|
236
|
+
var VISEME_POSE_KEYS = [
|
|
237
|
+
"a",
|
|
238
|
+
"at",
|
|
239
|
+
"b",
|
|
240
|
+
"e",
|
|
241
|
+
"e_2",
|
|
242
|
+
"f",
|
|
243
|
+
"i",
|
|
244
|
+
"k",
|
|
245
|
+
"m",
|
|
246
|
+
"o",
|
|
247
|
+
"o_2",
|
|
248
|
+
"p",
|
|
249
|
+
"r",
|
|
250
|
+
"s",
|
|
251
|
+
"t",
|
|
252
|
+
"t_2",
|
|
253
|
+
"u"
|
|
254
|
+
];
|
|
255
|
+
var EXPRESSIVE_EMOTION_POSE_KEYS = [
|
|
256
|
+
"concerned",
|
|
257
|
+
"happy",
|
|
258
|
+
"sad",
|
|
259
|
+
"sleepy",
|
|
260
|
+
"surprise"
|
|
261
|
+
];
|
|
262
|
+
var EMOTION_POSE_KEYS = [
|
|
263
|
+
"concerned",
|
|
264
|
+
"happy",
|
|
265
|
+
"neutral",
|
|
266
|
+
"sad",
|
|
267
|
+
"sleepy",
|
|
268
|
+
"surprise",
|
|
269
|
+
"angry"
|
|
270
|
+
];
|
|
271
|
+
var VISEME_GROUP_NEEDLES = ["viseme", "phoneme", "lip", "mouth"];
|
|
272
|
+
var EMOTION_GROUP_NEEDLES = ["emotion", "expression", "mood", "affect"];
|
|
273
|
+
var VISEME_POSE_KEY_SET = new Set(VISEME_POSE_KEYS);
|
|
274
|
+
var EMOTION_POSE_KEY_SET = new Set(EMOTION_POSE_KEYS);
|
|
275
|
+
var POSE_KEY_ALIASES = {
|
|
276
|
+
concern: "concerned",
|
|
277
|
+
surprised: "surprise"
|
|
278
|
+
};
|
|
279
|
+
function buildRigInputPath(faceId, path) {
|
|
280
|
+
let trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
281
|
+
if (!trimmed) {
|
|
282
|
+
return `rig/${faceId}`;
|
|
283
|
+
}
|
|
284
|
+
while (trimmed.startsWith("rig/")) {
|
|
285
|
+
const segments = trimmed.split("/");
|
|
286
|
+
if (segments.length >= 3) {
|
|
287
|
+
const existingFaceId = segments[1];
|
|
288
|
+
const remainder = segments.slice(2).join("/");
|
|
289
|
+
if (existingFaceId === faceId) {
|
|
290
|
+
return trimmed;
|
|
291
|
+
}
|
|
292
|
+
trimmed = remainder || "";
|
|
293
|
+
} else {
|
|
294
|
+
trimmed = segments.slice(1).join("/");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const suffix = trimmed ? `/${trimmed}` : "";
|
|
298
|
+
return `rig/${faceId}${suffix}`;
|
|
299
|
+
}
|
|
300
|
+
function normalizePoseWeightPathSegment(value) {
|
|
301
|
+
const trimmed = value?.trim() ?? "";
|
|
302
|
+
if (!trimmed) {
|
|
303
|
+
return "pose";
|
|
304
|
+
}
|
|
305
|
+
const normalized = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
306
|
+
return normalized || "pose";
|
|
307
|
+
}
|
|
308
|
+
function buildPoseWeightInputPathSegment(poseId) {
|
|
309
|
+
return normalizePoseWeightPathSegment(poseId);
|
|
310
|
+
}
|
|
311
|
+
function buildPoseWeightRelativePath(poseId) {
|
|
312
|
+
return `${POSE_WEIGHT_INPUT_PATH_PREFIX}${buildPoseWeightInputPathSegment(
|
|
313
|
+
poseId
|
|
314
|
+
)}.weight`;
|
|
315
|
+
}
|
|
316
|
+
function buildPoseWeightPathMap(poses, faceId) {
|
|
317
|
+
const faceSegment = faceId?.trim() || "face";
|
|
318
|
+
const map = /* @__PURE__ */ new Map();
|
|
319
|
+
poses.forEach((pose) => {
|
|
320
|
+
map.set(
|
|
321
|
+
pose.id,
|
|
322
|
+
buildRigInputPath(faceSegment, buildPoseWeightRelativePath(pose.id))
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
return map;
|
|
326
|
+
}
|
|
327
|
+
function normalizePoseSemanticKey(value) {
|
|
328
|
+
const trimmed = value?.trim() ?? "";
|
|
329
|
+
if (!trimmed) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
333
|
+
if (!normalized) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
return POSE_KEY_ALIASES[normalized] ?? normalized;
|
|
337
|
+
}
|
|
338
|
+
function derivePoseSemanticKeyFromId(poseId) {
|
|
339
|
+
const trimmed = poseId?.trim() ?? "";
|
|
340
|
+
if (!trimmed) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const stripped = trimmed.replace(/^pose_d_/i, "").replace(/^pose_/i, "").replace(/^d_/i, "").replace(/_d$/i, "");
|
|
344
|
+
return normalizePoseSemanticKey(stripped);
|
|
345
|
+
}
|
|
346
|
+
function getPoseSemanticKey(pose) {
|
|
347
|
+
return normalizePoseSemanticKey(pose.name) ?? derivePoseSemanticKeyFromId(pose.id) ?? null;
|
|
348
|
+
}
|
|
349
|
+
function normalizePoseGroupPath(value) {
|
|
350
|
+
const trimmed = value?.trim() ?? "";
|
|
351
|
+
if (!trimmed) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return trimmed.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
|
|
355
|
+
}
|
|
356
|
+
function sanitizePoseGroupId(value, fallback) {
|
|
357
|
+
const normalized = (value ?? "").trim().replace(/[^a-zA-Z0-9_/-]+/g, "_").replace(/^\/+|\/+$/g, "").replace(/\/+/g, "_");
|
|
358
|
+
if (!normalized) {
|
|
359
|
+
return fallback.replace(/\//g, "_");
|
|
360
|
+
}
|
|
361
|
+
return normalized;
|
|
362
|
+
}
|
|
363
|
+
function buildPoseGroupLookup(groups) {
|
|
364
|
+
const byId = /* @__PURE__ */ new Map();
|
|
365
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
366
|
+
const orderById = /* @__PURE__ */ new Map();
|
|
367
|
+
(groups ?? []).forEach((group, index) => {
|
|
368
|
+
const path = normalizePoseGroupPath(group.path ?? group.name ?? group.id);
|
|
369
|
+
if (!path) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const id = sanitizePoseGroupId(group.id, path);
|
|
373
|
+
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;
|
|
374
|
+
const normalized = {
|
|
375
|
+
...group,
|
|
376
|
+
id,
|
|
377
|
+
path,
|
|
378
|
+
name: humanizedName
|
|
379
|
+
};
|
|
380
|
+
byId.set(id, normalized);
|
|
381
|
+
if (!orderById.has(id)) {
|
|
382
|
+
orderById.set(id, index);
|
|
383
|
+
}
|
|
384
|
+
if (!byPath.has(path)) {
|
|
385
|
+
byPath.set(path, normalized);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return { byId, byPath, orderById };
|
|
389
|
+
}
|
|
390
|
+
function orderPoseMembershipIds(groupIds, groups) {
|
|
391
|
+
const { orderById } = buildPoseGroupLookup(groups);
|
|
392
|
+
const unique = Array.from(
|
|
393
|
+
new Set(
|
|
394
|
+
Array.from(groupIds).map((groupId) => groupId.trim()).filter((groupId) => groupId.length > 0)
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
unique.sort((left, right) => {
|
|
398
|
+
const leftIndex = orderById.get(left);
|
|
399
|
+
const rightIndex = orderById.get(right);
|
|
400
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) {
|
|
401
|
+
return leftIndex - rightIndex;
|
|
402
|
+
}
|
|
403
|
+
if (leftIndex !== void 0) {
|
|
404
|
+
return -1;
|
|
405
|
+
}
|
|
406
|
+
if (rightIndex !== void 0) {
|
|
407
|
+
return 1;
|
|
408
|
+
}
|
|
409
|
+
const leftPath = normalizePoseGroupPath(left) ?? left;
|
|
410
|
+
const rightPath = normalizePoseGroupPath(right) ?? right;
|
|
411
|
+
const byPath = leftPath.localeCompare(rightPath);
|
|
412
|
+
if (byPath !== 0) {
|
|
413
|
+
return byPath;
|
|
414
|
+
}
|
|
415
|
+
return left.localeCompare(right);
|
|
416
|
+
});
|
|
417
|
+
return unique;
|
|
418
|
+
}
|
|
419
|
+
function valueHasNeedle(value, needles) {
|
|
420
|
+
const normalized = normalizePoseSemanticKey(value);
|
|
421
|
+
if (!normalized) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return needles.some((needle) => normalized.includes(needle));
|
|
425
|
+
}
|
|
426
|
+
function resolvePoseMembership(pose, groups) {
|
|
427
|
+
const { byId, byPath } = buildPoseGroupLookup(groups);
|
|
428
|
+
const resolvedGroupIds = [];
|
|
429
|
+
const pathById = /* @__PURE__ */ new Map();
|
|
430
|
+
const addMembership = (groupId, path) => {
|
|
431
|
+
if (!groupId) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (!resolvedGroupIds.includes(groupId)) {
|
|
435
|
+
resolvedGroupIds.push(groupId);
|
|
436
|
+
}
|
|
437
|
+
if (!path) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const existingPath = pathById.get(groupId);
|
|
441
|
+
const normalizedGroupIdPath = normalizePoseGroupPath(groupId);
|
|
442
|
+
const normalizedExistingPath = normalizePoseGroupPath(existingPath) ?? existingPath ?? null;
|
|
443
|
+
const normalizedIncomingPath = normalizePoseGroupPath(path) ?? path ?? null;
|
|
444
|
+
const shouldPromotePath = normalizedExistingPath === null || normalizedGroupIdPath !== null && normalizedExistingPath === normalizedGroupIdPath && normalizedIncomingPath !== normalizedGroupIdPath;
|
|
445
|
+
if (shouldPromotePath) {
|
|
446
|
+
pathById.set(groupId, path);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
const addByPath = (rawPath) => {
|
|
450
|
+
const normalizedPath = normalizePoseGroupPath(rawPath);
|
|
451
|
+
if (!normalizedPath) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const existing = byPath.get(normalizedPath);
|
|
455
|
+
if (existing) {
|
|
456
|
+
addMembership(existing.id, existing.path);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
addMembership(sanitizePoseGroupId(null, normalizedPath), normalizedPath);
|
|
460
|
+
};
|
|
461
|
+
const addById = (rawId) => {
|
|
462
|
+
const trimmed = rawId?.trim() ?? "";
|
|
463
|
+
if (!trimmed) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const normalizedPath = normalizePoseGroupPath(trimmed);
|
|
467
|
+
const normalizedId = sanitizePoseGroupId(trimmed, trimmed);
|
|
468
|
+
const matchedById = byId.get(trimmed) ?? byId.get(normalizedId);
|
|
469
|
+
if (matchedById) {
|
|
470
|
+
addMembership(matchedById.id, matchedById.path);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (normalizedPath) {
|
|
474
|
+
const matchedByPath = byPath.get(normalizedPath);
|
|
475
|
+
if (matchedByPath) {
|
|
476
|
+
addMembership(matchedByPath.id, matchedByPath.path);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
addMembership(
|
|
481
|
+
normalizedId,
|
|
482
|
+
normalizedPath && normalizedPath.length > 0 ? normalizedPath : null
|
|
483
|
+
);
|
|
484
|
+
};
|
|
485
|
+
pose.groupIds?.forEach((groupId) => addById(groupId));
|
|
486
|
+
addById(pose.groupId);
|
|
487
|
+
addByPath(pose.group);
|
|
488
|
+
const orderedGroupIds = orderPoseMembershipIds(resolvedGroupIds, groups);
|
|
489
|
+
const primaryGroupId = orderedGroupIds[0] ?? null;
|
|
490
|
+
const primaryGroupPath = primaryGroupId ? byId.get(primaryGroupId)?.path ?? pathById.get(primaryGroupId) ?? null : null;
|
|
491
|
+
const groupPathsById = {};
|
|
492
|
+
orderedGroupIds.forEach((groupId) => {
|
|
493
|
+
const path = byId.get(groupId)?.path ?? pathById.get(groupId) ?? null;
|
|
494
|
+
if (path) {
|
|
495
|
+
groupPathsById[groupId] = path;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
return {
|
|
499
|
+
groupIds: orderedGroupIds,
|
|
500
|
+
primaryGroupId,
|
|
501
|
+
primaryGroupPath,
|
|
502
|
+
groupPathsById
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function poseMatchesGroupKind(pose, groups, needles) {
|
|
506
|
+
const membership = resolvePoseMembership(pose, groups);
|
|
507
|
+
if (valueHasNeedle(pose.group, needles) || valueHasNeedle(pose.groupId, needles)) {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
if (pose.groupIds?.some((groupId) => valueHasNeedle(groupId, needles))) {
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
return membership.groupIds.some((groupId) => {
|
|
514
|
+
const path = membership.groupPathsById[groupId] ?? null;
|
|
515
|
+
const group = (groups ?? []).find((entry) => entry.id === groupId) ?? null;
|
|
516
|
+
return valueHasNeedle(groupId, needles) || valueHasNeedle(path, needles) || valueHasNeedle(group?.name, needles);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function resolvePoseSemantics(pose, groups) {
|
|
520
|
+
const membership = resolvePoseMembership(pose, groups);
|
|
521
|
+
const key = getPoseSemanticKey(pose);
|
|
522
|
+
const looksLikeVisemeGroup = poseMatchesGroupKind(
|
|
523
|
+
pose,
|
|
524
|
+
groups,
|
|
525
|
+
VISEME_GROUP_NEEDLES
|
|
526
|
+
);
|
|
527
|
+
const looksLikeEmotionGroup = poseMatchesGroupKind(
|
|
528
|
+
pose,
|
|
529
|
+
groups,
|
|
530
|
+
EMOTION_GROUP_NEEDLES
|
|
531
|
+
);
|
|
532
|
+
let kind = "other";
|
|
533
|
+
if (looksLikeVisemeGroup || key && VISEME_POSE_KEY_SET.has(key)) {
|
|
534
|
+
kind = "viseme";
|
|
535
|
+
} else if (looksLikeEmotionGroup || key && EMOTION_POSE_KEY_SET.has(key)) {
|
|
536
|
+
kind = "emotion";
|
|
537
|
+
}
|
|
538
|
+
return { key, kind, membership };
|
|
539
|
+
}
|
|
540
|
+
function filterPosesBySemanticKind(poses, groups, kind) {
|
|
541
|
+
return poses.filter(
|
|
542
|
+
(pose) => resolvePoseSemantics(pose, groups).kind === kind
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
function buildSemanticPoseWeightPathMap(poses, groups, faceId, kind) {
|
|
546
|
+
const map = /* @__PURE__ */ new Map();
|
|
547
|
+
const pathMap = buildPoseWeightPathMap(poses, faceId);
|
|
548
|
+
poses.forEach((pose) => {
|
|
549
|
+
const semantics = resolvePoseSemantics(pose, groups);
|
|
550
|
+
const path = pathMap.get(pose.id);
|
|
551
|
+
if (semantics.kind !== kind || !semantics.key || !path || map.has(semantics.key)) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
map.set(semantics.key, path);
|
|
85
555
|
});
|
|
86
556
|
return map;
|
|
87
557
|
}
|
|
88
558
|
|
|
559
|
+
// src/utils/poseRuntime.ts
|
|
560
|
+
function shouldUseLegacyPoseWeightFallback(hasPoseGraph) {
|
|
561
|
+
return !hasPoseGraph;
|
|
562
|
+
}
|
|
563
|
+
function resolvePoseControlInputPath({
|
|
564
|
+
inputId,
|
|
565
|
+
basePath,
|
|
566
|
+
rigInputPathMap,
|
|
567
|
+
hasNativePoseControlInput
|
|
568
|
+
}) {
|
|
569
|
+
if (!inputId.trim()) {
|
|
570
|
+
return void 0;
|
|
571
|
+
}
|
|
572
|
+
return rigInputPathMap[inputId] ?? rigInputPathMap[`pose_control_${inputId}`] ?? rigInputPathMap[`direct_${inputId}`] ?? (hasNativePoseControlInput ? basePath : void 0);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/utils/clipPlayback.ts
|
|
576
|
+
var EPSILON = 1e-6;
|
|
577
|
+
function toFiniteNumber(value, fallback) {
|
|
578
|
+
const parsed = Number(value);
|
|
579
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
580
|
+
}
|
|
581
|
+
function normaliseInterpolation(interpolation) {
|
|
582
|
+
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
583
|
+
if (mode === "step") {
|
|
584
|
+
return "step";
|
|
585
|
+
}
|
|
586
|
+
if (mode === "cubic" || mode === "cubicspline") {
|
|
587
|
+
return "cubic";
|
|
588
|
+
}
|
|
589
|
+
return "linear";
|
|
590
|
+
}
|
|
591
|
+
function asNumericKeyframe(keyframe) {
|
|
592
|
+
if (!keyframe || typeof keyframe !== "object") {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
const time = Number(keyframe.time);
|
|
596
|
+
const value = Number(keyframe.value);
|
|
597
|
+
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
const inTangentRaw = keyframe.inTangent;
|
|
601
|
+
const outTangentRaw = keyframe.outTangent;
|
|
602
|
+
const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
|
|
603
|
+
const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
|
|
604
|
+
return {
|
|
605
|
+
time,
|
|
606
|
+
value,
|
|
607
|
+
inTangent,
|
|
608
|
+
outTangent
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function getNumericKeyframes(track) {
|
|
612
|
+
const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
|
|
613
|
+
const numeric = keyframes.map((keyframe) => asNumericKeyframe(keyframe)).filter((keyframe) => Boolean(keyframe));
|
|
614
|
+
if (numeric.length <= 1) {
|
|
615
|
+
return numeric;
|
|
616
|
+
}
|
|
617
|
+
return [...numeric].sort((a, b) => a.time - b.time);
|
|
618
|
+
}
|
|
619
|
+
function resolveTangent(value, fallback) {
|
|
620
|
+
const parsed = Number(value);
|
|
621
|
+
if (Number.isFinite(parsed)) {
|
|
622
|
+
return parsed;
|
|
623
|
+
}
|
|
624
|
+
return fallback;
|
|
625
|
+
}
|
|
626
|
+
function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
|
|
627
|
+
const t = factor;
|
|
628
|
+
const t2 = t * t;
|
|
629
|
+
const t3 = t2 * t;
|
|
630
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
631
|
+
const h10 = t3 - 2 * t2 + t;
|
|
632
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
633
|
+
const h11 = t3 - t2;
|
|
634
|
+
return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
|
|
635
|
+
}
|
|
636
|
+
function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
637
|
+
const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
|
|
638
|
+
if (!clip || typeof clip !== "object") {
|
|
639
|
+
return fallback;
|
|
640
|
+
}
|
|
641
|
+
const clipDuration = Number(clip.duration);
|
|
642
|
+
if (Number.isFinite(clipDuration) && clipDuration > 0) {
|
|
643
|
+
return clipDuration;
|
|
644
|
+
}
|
|
645
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
646
|
+
let maxTime = 0;
|
|
647
|
+
tracks.forEach((track) => {
|
|
648
|
+
const keyframes = getNumericKeyframes(track);
|
|
649
|
+
const last = keyframes[keyframes.length - 1];
|
|
650
|
+
if (last && last.time > maxTime) {
|
|
651
|
+
maxTime = last.time;
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
return maxTime > 0 ? maxTime : fallback;
|
|
655
|
+
}
|
|
656
|
+
function clampAnimationTime(time, duration) {
|
|
657
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
658
|
+
return 0;
|
|
659
|
+
}
|
|
660
|
+
if (!Number.isFinite(time)) {
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
if (time <= 0) {
|
|
664
|
+
return 0;
|
|
665
|
+
}
|
|
666
|
+
if (time >= duration) {
|
|
667
|
+
return duration;
|
|
668
|
+
}
|
|
669
|
+
return time;
|
|
670
|
+
}
|
|
671
|
+
function advanceClipTime(state, dt) {
|
|
672
|
+
const duration = Math.max(0, toFiniteNumber(state.duration, 0));
|
|
673
|
+
const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
|
|
674
|
+
const currentTime = clampAnimationTime(state.time, duration);
|
|
675
|
+
const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
|
|
676
|
+
if (!state.playing || delta <= 0) {
|
|
677
|
+
return { time: currentTime, completed: false };
|
|
678
|
+
}
|
|
679
|
+
if (duration <= 0) {
|
|
680
|
+
return { time: 0, completed: true };
|
|
681
|
+
}
|
|
682
|
+
const nextTime = currentTime + delta;
|
|
683
|
+
if (state.loop) {
|
|
684
|
+
if (nextTime < duration) {
|
|
685
|
+
return { time: nextTime, completed: false };
|
|
686
|
+
}
|
|
687
|
+
const wrapped = (nextTime % duration + duration) % duration;
|
|
688
|
+
return { time: wrapped, completed: false };
|
|
689
|
+
}
|
|
690
|
+
if (nextTime >= duration - EPSILON) {
|
|
691
|
+
return { time: duration, completed: true };
|
|
692
|
+
}
|
|
693
|
+
return { time: nextTime, completed: false };
|
|
694
|
+
}
|
|
695
|
+
function sampleTrackAtTime(track, timeSeconds) {
|
|
696
|
+
const keyframes = getNumericKeyframes(track);
|
|
697
|
+
if (keyframes.length === 0) {
|
|
698
|
+
return 0;
|
|
699
|
+
}
|
|
700
|
+
if (keyframes.length === 1) {
|
|
701
|
+
return keyframes[0].value;
|
|
702
|
+
}
|
|
703
|
+
const mode = normaliseInterpolation(track.interpolation);
|
|
704
|
+
const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
|
|
705
|
+
const first = keyframes[0];
|
|
706
|
+
if (time <= first.time + EPSILON) {
|
|
707
|
+
return first.value;
|
|
708
|
+
}
|
|
709
|
+
const last = keyframes[keyframes.length - 1];
|
|
710
|
+
if (time >= last.time - EPSILON) {
|
|
711
|
+
return last.value;
|
|
712
|
+
}
|
|
713
|
+
for (let i = 0; i < keyframes.length - 1; i += 1) {
|
|
714
|
+
const current = keyframes[i];
|
|
715
|
+
const next = keyframes[i + 1];
|
|
716
|
+
const start = current.time;
|
|
717
|
+
const end = next.time;
|
|
718
|
+
const duration = end - start;
|
|
719
|
+
if (duration <= EPSILON) {
|
|
720
|
+
if (time <= end + EPSILON) {
|
|
721
|
+
return next.value;
|
|
722
|
+
}
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (Math.abs(time - end) <= EPSILON) {
|
|
726
|
+
return next.value;
|
|
727
|
+
}
|
|
728
|
+
if (time < end) {
|
|
729
|
+
const factor = (time - start) / duration;
|
|
730
|
+
if (mode === "step") {
|
|
731
|
+
return current.value;
|
|
732
|
+
}
|
|
733
|
+
if (mode === "cubic") {
|
|
734
|
+
const slope = (next.value - current.value) / duration;
|
|
735
|
+
const outTangent = resolveTangent(current.outTangent, slope);
|
|
736
|
+
const inTangent = resolveTangent(next.inTangent, slope);
|
|
737
|
+
return sampleHermite(
|
|
738
|
+
current.value,
|
|
739
|
+
next.value,
|
|
740
|
+
outTangent,
|
|
741
|
+
inTangent,
|
|
742
|
+
factor,
|
|
743
|
+
duration
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
return current.value + (next.value - current.value) * factor;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return last.value;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/utils/animationBridge.ts
|
|
753
|
+
function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
|
|
754
|
+
const normalizedChannel = channel.trim().replace(/^\/+/, "");
|
|
755
|
+
if (!normalizedChannel) {
|
|
756
|
+
return [];
|
|
757
|
+
}
|
|
758
|
+
const outputPaths = /* @__PURE__ */ new Set([normalizedChannel]);
|
|
759
|
+
if (normalizedChannel.startsWith("animation/")) {
|
|
760
|
+
return Array.from(outputPaths).sort(
|
|
761
|
+
(left, right) => left.localeCompare(right)
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
if (rigInputMap && Object.keys(rigInputMap).length > 0) {
|
|
765
|
+
const candidateKeys = /* @__PURE__ */ new Set([normalizedChannel]);
|
|
766
|
+
const rigChannelMatch2 = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
|
|
767
|
+
if (rigChannelMatch2?.[1]) {
|
|
768
|
+
candidateKeys.add(rigChannelMatch2[1]);
|
|
769
|
+
}
|
|
770
|
+
candidateKeys.forEach((key) => {
|
|
771
|
+
const mapped = rigInputMap[key];
|
|
772
|
+
const normalized = mapped?.trim().replace(/^\/+/, "");
|
|
773
|
+
if (normalized) {
|
|
774
|
+
outputPaths.add(normalized);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
const suffix = normalizedChannel.includes("/") ? normalizedChannel : `/${normalizedChannel}`;
|
|
778
|
+
Object.values(rigInputMap).forEach((mappedPath) => {
|
|
779
|
+
const normalized = mappedPath?.trim().replace(/^\/+/, "");
|
|
780
|
+
if (!normalized) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (normalized === normalizedChannel || normalized.endsWith(suffix)) {
|
|
784
|
+
outputPaths.add(normalized);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
if (!faceId) {
|
|
789
|
+
return Array.from(outputPaths).sort(
|
|
790
|
+
(left, right) => left.localeCompare(right)
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
const rigChannelMatch = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
|
|
794
|
+
if (rigChannelMatch?.[1]) {
|
|
795
|
+
outputPaths.add(`rig/${faceId}/${rigChannelMatch[1]}`);
|
|
796
|
+
} else if (!normalizedChannel.startsWith("rig/")) {
|
|
797
|
+
outputPaths.add(`rig/${faceId}/${normalizedChannel}`);
|
|
798
|
+
}
|
|
799
|
+
return Array.from(outputPaths).sort(
|
|
800
|
+
(left, right) => left.localeCompare(right)
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
|
|
804
|
+
const outputPaths = /* @__PURE__ */ new Set();
|
|
805
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
806
|
+
tracks.forEach((track) => {
|
|
807
|
+
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
808
|
+
if (!channel) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
812
|
+
(path) => {
|
|
813
|
+
outputPaths.add(path);
|
|
814
|
+
}
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
return Array.from(outputPaths).sort(
|
|
818
|
+
(left, right) => left.localeCompare(right)
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
|
|
822
|
+
const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
|
|
823
|
+
const outputValues = /* @__PURE__ */ new Map();
|
|
824
|
+
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
825
|
+
tracks.forEach((track) => {
|
|
826
|
+
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
827
|
+
if (!channel) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const sampledValue = sampleTrackAtTime(
|
|
831
|
+
track,
|
|
832
|
+
timeSeconds
|
|
833
|
+
);
|
|
834
|
+
const weightedValue = sampledValue * appliedWeight;
|
|
835
|
+
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
836
|
+
(path) => {
|
|
837
|
+
outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
});
|
|
841
|
+
return outputValues;
|
|
842
|
+
}
|
|
843
|
+
function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
|
|
844
|
+
const operations = [];
|
|
845
|
+
const changedPaths = /* @__PURE__ */ new Set();
|
|
846
|
+
previousAggregate.forEach((previousValue, path) => {
|
|
847
|
+
const nextValue = nextAggregate.get(path);
|
|
848
|
+
if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
849
|
+
changedPaths.add(path);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
nextAggregate.forEach((nextValue, path) => {
|
|
853
|
+
const previousValue = previousAggregate.get(path);
|
|
854
|
+
if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
855
|
+
changedPaths.add(path);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
changedPaths.forEach((path) => {
|
|
859
|
+
const nextValue = nextAggregate.get(path);
|
|
860
|
+
if (nextValue === void 0) {
|
|
861
|
+
operations.push({ kind: "clear", path });
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
operations.push({ kind: "set", path, value: nextValue });
|
|
865
|
+
});
|
|
866
|
+
return operations;
|
|
867
|
+
}
|
|
868
|
+
|
|
89
869
|
// src/utils/valueConversion.ts
|
|
90
870
|
import {
|
|
91
871
|
isNormalizedValue,
|
|
@@ -219,6 +999,19 @@ var DEFAULT_MERGE = {
|
|
|
219
999
|
intermediate: "add"
|
|
220
1000
|
};
|
|
221
1001
|
var DEFAULT_DURATION = 0.35;
|
|
1002
|
+
var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
|
|
1003
|
+
var DEV_MODE = (() => {
|
|
1004
|
+
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
1005
|
+
return typeof nodeEnv === "string" && nodeEnv === "development";
|
|
1006
|
+
})();
|
|
1007
|
+
function isRuntimeDebugEnabled() {
|
|
1008
|
+
if (DEV_MODE) {
|
|
1009
|
+
return true;
|
|
1010
|
+
}
|
|
1011
|
+
return Boolean(
|
|
1012
|
+
globalThis.__VIZIJ_RUNTIME_DEBUG__
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
222
1015
|
var EASINGS = {
|
|
223
1016
|
linear: (t) => t,
|
|
224
1017
|
easeIn: (t) => t * t,
|
|
@@ -235,12 +1028,19 @@ function resolveEasing(easing) {
|
|
|
235
1028
|
return EASINGS.linear;
|
|
236
1029
|
}
|
|
237
1030
|
function findRootId(world) {
|
|
1031
|
+
let fallback = null;
|
|
238
1032
|
for (const entry of Object.values(world)) {
|
|
239
|
-
if (entry
|
|
1033
|
+
if (!entry || typeof entry !== "object" || entry.type !== "group") {
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (entry.rootBounds && entry.id) {
|
|
240
1037
|
return entry.id;
|
|
241
1038
|
}
|
|
1039
|
+
if (!fallback && entry.id) {
|
|
1040
|
+
fallback = entry.id;
|
|
1041
|
+
}
|
|
242
1042
|
}
|
|
243
|
-
return
|
|
1043
|
+
return fallback;
|
|
244
1044
|
}
|
|
245
1045
|
function normalisePath(path) {
|
|
246
1046
|
if (!path) {
|
|
@@ -410,6 +1210,30 @@ function namespaceGraphSpec(spec, namespace) {
|
|
|
410
1210
|
nodes: nextNodes
|
|
411
1211
|
};
|
|
412
1212
|
}
|
|
1213
|
+
function stripNulls(value) {
|
|
1214
|
+
if (value === null) {
|
|
1215
|
+
return void 0;
|
|
1216
|
+
}
|
|
1217
|
+
if (Array.isArray(value)) {
|
|
1218
|
+
const next2 = value.map((entry) => stripNulls(entry)).filter((entry) => entry !== void 0 && entry !== null);
|
|
1219
|
+
return next2;
|
|
1220
|
+
}
|
|
1221
|
+
if (typeof value !== "object" || value === void 0) {
|
|
1222
|
+
return value;
|
|
1223
|
+
}
|
|
1224
|
+
const next = {};
|
|
1225
|
+
Object.entries(value).forEach(([key, entry]) => {
|
|
1226
|
+
if (entry === null) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const cleaned = stripNulls(entry);
|
|
1230
|
+
if (cleaned === void 0) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
next[key] = cleaned;
|
|
1234
|
+
});
|
|
1235
|
+
return next;
|
|
1236
|
+
}
|
|
413
1237
|
var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
414
1238
|
function pickBundleGraph(bundle, preferredKinds) {
|
|
415
1239
|
if (!bundle?.graphs || bundle.graphs.length === 0) {
|
|
@@ -580,26 +1404,49 @@ function convertExtractedAnimations(clips) {
|
|
|
580
1404
|
}
|
|
581
1405
|
const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
|
|
582
1406
|
const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
|
|
583
|
-
|
|
1407
|
+
const interpolationRaw = typeof track.interpolation === "string" ? track.interpolation.trim().toLowerCase() : "linear";
|
|
1408
|
+
const interpolation = interpolationRaw === "step" ? "step" : interpolationRaw === "cubic" || interpolationRaw === "cubicspline" ? "cubic" : "linear";
|
|
1409
|
+
const isCubic = interpolation === "cubic";
|
|
1410
|
+
const hasTripletTangents = isCubic && values.length === times.length * valueSize * 3;
|
|
1411
|
+
const hasFlatValues = values.length === times.length * valueSize;
|
|
1412
|
+
if (!hasTripletTangents && !hasFlatValues) {
|
|
584
1413
|
return;
|
|
585
1414
|
}
|
|
586
1415
|
const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
|
|
587
1416
|
const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
|
|
588
1417
|
const keyframes = [];
|
|
589
1418
|
times.forEach((time, index) => {
|
|
590
|
-
const
|
|
591
|
-
const
|
|
1419
|
+
const flatBase = index * valueSize + componentIndex;
|
|
1420
|
+
const valueBase = hasTripletTangents ? index * valueSize * 3 + valueSize + componentIndex : flatBase;
|
|
1421
|
+
const value = values[valueBase];
|
|
592
1422
|
if (!Number.isFinite(value)) {
|
|
593
1423
|
return;
|
|
594
1424
|
}
|
|
595
|
-
|
|
1425
|
+
const keyframe = {
|
|
1426
|
+
time,
|
|
1427
|
+
value
|
|
1428
|
+
};
|
|
1429
|
+
if (hasTripletTangents) {
|
|
1430
|
+
const inBase = index * valueSize * 3 + componentIndex;
|
|
1431
|
+
const outBase = index * valueSize * 3 + valueSize * 2 + componentIndex;
|
|
1432
|
+
const inTangent = values[inBase];
|
|
1433
|
+
const outTangent = values[outBase];
|
|
1434
|
+
if (Number.isFinite(inTangent)) {
|
|
1435
|
+
keyframe.inTangent = inTangent;
|
|
1436
|
+
}
|
|
1437
|
+
if (Number.isFinite(outTangent)) {
|
|
1438
|
+
keyframe.outTangent = outTangent;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
keyframes.push(keyframe);
|
|
596
1442
|
});
|
|
597
1443
|
if (keyframes.length === 0) {
|
|
598
1444
|
return;
|
|
599
1445
|
}
|
|
600
1446
|
convertedTracks.push({
|
|
601
1447
|
channel: channelId,
|
|
602
|
-
keyframes
|
|
1448
|
+
keyframes,
|
|
1449
|
+
interpolation
|
|
603
1450
|
});
|
|
604
1451
|
});
|
|
605
1452
|
if (convertedTracks.length === 0) {
|
|
@@ -666,29 +1513,91 @@ function mergeAnimationLists(explicit, fromBundle) {
|
|
|
666
1513
|
}
|
|
667
1514
|
return changed ? merged : explicit;
|
|
668
1515
|
}
|
|
669
|
-
function
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1516
|
+
function extractProgramResetValues(value) {
|
|
1517
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1518
|
+
return void 0;
|
|
1519
|
+
}
|
|
1520
|
+
const entries = Object.entries(value).filter(
|
|
1521
|
+
([, rawValue]) => Number.isFinite(Number(rawValue))
|
|
673
1522
|
);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1523
|
+
if (entries.length === 0) {
|
|
1524
|
+
return void 0;
|
|
1525
|
+
}
|
|
1526
|
+
return Object.fromEntries(
|
|
1527
|
+
entries.map(([path, rawValue]) => [path, Number(rawValue)])
|
|
679
1528
|
);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1529
|
+
}
|
|
1530
|
+
function convertBundlePrograms(entries) {
|
|
1531
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1532
|
+
return [];
|
|
1533
|
+
}
|
|
1534
|
+
return entries.filter((entry) => normaliseBundleKind(entry?.kind) === "motiongraph").map((entry) => {
|
|
1535
|
+
const graph = convertBundleGraph(entry);
|
|
1536
|
+
if (!graph) {
|
|
1537
|
+
return null;
|
|
689
1538
|
}
|
|
690
|
-
|
|
691
|
-
|
|
1539
|
+
const metadata = entry.metadata && typeof entry.metadata === "object" && !Array.isArray(entry.metadata) ? entry.metadata : void 0;
|
|
1540
|
+
return {
|
|
1541
|
+
id: entry.id,
|
|
1542
|
+
label: typeof entry.label === "string" ? entry.label : void 0,
|
|
1543
|
+
graph,
|
|
1544
|
+
metadata,
|
|
1545
|
+
resetValues: extractProgramResetValues(metadata?.resetValues)
|
|
1546
|
+
};
|
|
1547
|
+
}).filter(Boolean);
|
|
1548
|
+
}
|
|
1549
|
+
function mergeProgramLists(explicit, fromBundle) {
|
|
1550
|
+
if (!explicit?.length && fromBundle.length === 0) {
|
|
1551
|
+
return void 0;
|
|
1552
|
+
}
|
|
1553
|
+
if (!explicit?.length) {
|
|
1554
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
1555
|
+
}
|
|
1556
|
+
if (fromBundle.length === 0) {
|
|
1557
|
+
return explicit;
|
|
1558
|
+
}
|
|
1559
|
+
const seen = new Set(explicit.map((program) => program.id));
|
|
1560
|
+
let changed = false;
|
|
1561
|
+
const merged = [...explicit];
|
|
1562
|
+
for (const program of fromBundle) {
|
|
1563
|
+
if (!program.id || seen.has(program.id)) {
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
merged.push(program);
|
|
1567
|
+
seen.add(program.id);
|
|
1568
|
+
changed = true;
|
|
1569
|
+
}
|
|
1570
|
+
return changed ? merged : explicit;
|
|
1571
|
+
}
|
|
1572
|
+
function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
1573
|
+
const resolvedBundle = base.bundle ?? extracted ?? null;
|
|
1574
|
+
const rigFromBundle = convertBundleGraph(
|
|
1575
|
+
pickBundleGraph(resolvedBundle, ["rig"])
|
|
1576
|
+
);
|
|
1577
|
+
const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
|
|
1578
|
+
const basePose = base.pose;
|
|
1579
|
+
const hasBasePoseGraphOverride = Boolean(
|
|
1580
|
+
basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
|
|
1581
|
+
);
|
|
1582
|
+
const hasBasePoseConfigOverride = Boolean(
|
|
1583
|
+
basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
|
|
1584
|
+
);
|
|
1585
|
+
const poseStageFilter = basePose?.stageNeutralFilter;
|
|
1586
|
+
const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
1587
|
+
pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
|
|
1588
|
+
);
|
|
1589
|
+
const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
1590
|
+
const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
1591
|
+
let resolvedPose = basePose;
|
|
1592
|
+
if (basePose) {
|
|
1593
|
+
const nextPose = { ...basePose };
|
|
1594
|
+
let changed = false;
|
|
1595
|
+
if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
|
|
1596
|
+
nextPose.graph = resolvedPoseGraph;
|
|
1597
|
+
changed = true;
|
|
1598
|
+
}
|
|
1599
|
+
if (resolvedPoseConfig && basePose.config !== resolvedPoseConfig) {
|
|
1600
|
+
nextPose.config = resolvedPoseConfig;
|
|
692
1601
|
changed = true;
|
|
693
1602
|
}
|
|
694
1603
|
if (!resolvedPoseGraph && !basePose.graph) {
|
|
@@ -717,6 +1626,10 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
717
1626
|
animationsFromAsset
|
|
718
1627
|
);
|
|
719
1628
|
}
|
|
1629
|
+
const programsFromBundle = mergeProgramLists(
|
|
1630
|
+
base.programs,
|
|
1631
|
+
convertBundlePrograms(resolvedBundle?.graphs)
|
|
1632
|
+
);
|
|
720
1633
|
const merged = {
|
|
721
1634
|
...base
|
|
722
1635
|
};
|
|
@@ -727,14 +1640,106 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
727
1640
|
}
|
|
728
1641
|
merged.pose = resolvedPose;
|
|
729
1642
|
merged.animations = resolvedAnimations;
|
|
1643
|
+
merged.programs = programsFromBundle;
|
|
730
1644
|
merged.bundle = resolvedBundle;
|
|
731
1645
|
return merged;
|
|
732
1646
|
}
|
|
1647
|
+
function normalizeStoredAnimationInterpolation(interpolation) {
|
|
1648
|
+
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
1649
|
+
if (mode === "step") {
|
|
1650
|
+
return "step";
|
|
1651
|
+
}
|
|
1652
|
+
if (mode === "cubic" || mode === "cubicspline") {
|
|
1653
|
+
return "cubic";
|
|
1654
|
+
}
|
|
1655
|
+
return "linear";
|
|
1656
|
+
}
|
|
1657
|
+
function buildStoredAnimationTransitions(mode) {
|
|
1658
|
+
if (mode === "cubic") {
|
|
1659
|
+
return void 0;
|
|
1660
|
+
}
|
|
1661
|
+
if (mode === "step") {
|
|
1662
|
+
return {
|
|
1663
|
+
in: { x: 1, y: 1 },
|
|
1664
|
+
out: { x: 1, y: 0 }
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
in: { x: 1, y: 1 },
|
|
1669
|
+
out: { x: 0, y: 0 }
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
function toStoredAnimationClip(fallbackId, clip) {
|
|
1673
|
+
const clipId = typeof clip.id === "string" && clip.id.trim().length > 0 ? clip.id.trim() : fallbackId;
|
|
1674
|
+
const clipName = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : clipId;
|
|
1675
|
+
const durationSeconds = resolveClipDurationSeconds(clip, 0);
|
|
1676
|
+
const durationMs = Math.max(1, Math.round(durationSeconds * 1e3));
|
|
1677
|
+
const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, trackIndex) => {
|
|
1678
|
+
const channel = typeof rawTrack.channel === "string" ? rawTrack.channel.trim() : "";
|
|
1679
|
+
if (!channel) {
|
|
1680
|
+
return null;
|
|
1681
|
+
}
|
|
1682
|
+
const keyframes = (Array.isArray(rawTrack.keyframes) ? rawTrack.keyframes : []).map((keyframe) => {
|
|
1683
|
+
const time = Number(keyframe.time);
|
|
1684
|
+
const value = Number(keyframe.value);
|
|
1685
|
+
const keyframeId = keyframe["id"];
|
|
1686
|
+
const keyframeInterpolation = keyframe["interpolation"];
|
|
1687
|
+
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
1688
|
+
return null;
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
id: typeof keyframeId === "string" && keyframeId.trim().length > 0 ? keyframeId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}:point-${time.toFixed(6)}`,
|
|
1692
|
+
time,
|
|
1693
|
+
value,
|
|
1694
|
+
mode: normalizeStoredAnimationInterpolation(
|
|
1695
|
+
keyframeInterpolation ?? rawTrack.interpolation
|
|
1696
|
+
)
|
|
1697
|
+
};
|
|
1698
|
+
}).filter(Boolean);
|
|
1699
|
+
if (keyframes.length === 0) {
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1702
|
+
keyframes.sort((left, right) => {
|
|
1703
|
+
if (left.time !== right.time) {
|
|
1704
|
+
return left.time - right.time;
|
|
1705
|
+
}
|
|
1706
|
+
return left.id.localeCompare(right.id);
|
|
1707
|
+
});
|
|
1708
|
+
const rawTrackId = rawTrack["id"];
|
|
1709
|
+
const rawTrackName = rawTrack["name"];
|
|
1710
|
+
const trackId = typeof rawTrackId === "string" && rawTrackId.trim().length > 0 ? rawTrackId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}`;
|
|
1711
|
+
const trackName = typeof rawTrackName === "string" && rawTrackName.trim().length > 0 ? rawTrackName.trim() : channel.replace(/^\/+/, "") || trackId;
|
|
1712
|
+
const denominator = durationSeconds > 0 ? durationSeconds : 1;
|
|
1713
|
+
return {
|
|
1714
|
+
id: trackId,
|
|
1715
|
+
name: trackName,
|
|
1716
|
+
animatableId: channel,
|
|
1717
|
+
points: keyframes.map((keyframe) => {
|
|
1718
|
+
const stamp = Math.max(0, Math.min(1, keyframe.time / denominator));
|
|
1719
|
+
const transitions = buildStoredAnimationTransitions(keyframe.mode);
|
|
1720
|
+
return {
|
|
1721
|
+
id: keyframe.id,
|
|
1722
|
+
stamp,
|
|
1723
|
+
value: keyframe.value,
|
|
1724
|
+
...transitions ? { transitions } : {}
|
|
1725
|
+
};
|
|
1726
|
+
})
|
|
1727
|
+
};
|
|
1728
|
+
}).filter(Boolean);
|
|
1729
|
+
return {
|
|
1730
|
+
id: clipId,
|
|
1731
|
+
name: clipName,
|
|
1732
|
+
duration: durationMs,
|
|
1733
|
+
groups: {},
|
|
1734
|
+
tracks
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
733
1737
|
function VizijRuntimeProvider({
|
|
734
1738
|
assetBundle,
|
|
735
1739
|
children,
|
|
736
1740
|
namespace: namespaceProp,
|
|
737
1741
|
faceId: faceIdProp,
|
|
1742
|
+
updateTier = "auto",
|
|
738
1743
|
autoCreate = true,
|
|
739
1744
|
createOptions,
|
|
740
1745
|
autostart = false,
|
|
@@ -742,6 +1747,7 @@ function VizijRuntimeProvider({
|
|
|
742
1747
|
mergeStrategy,
|
|
743
1748
|
onRegisterControllers,
|
|
744
1749
|
onStatusChange,
|
|
1750
|
+
transformOutputWrite,
|
|
745
1751
|
orchestratorScope = "auto"
|
|
746
1752
|
}) {
|
|
747
1753
|
const storeRef = useRef(null);
|
|
@@ -762,6 +1768,7 @@ function VizijRuntimeProvider({
|
|
|
762
1768
|
assetBundle,
|
|
763
1769
|
namespace: namespaceProp,
|
|
764
1770
|
faceId: faceIdProp,
|
|
1771
|
+
updateTier,
|
|
765
1772
|
autoCreate,
|
|
766
1773
|
autostart,
|
|
767
1774
|
driveOrchestrator,
|
|
@@ -769,6 +1776,7 @@ function VizijRuntimeProvider({
|
|
|
769
1776
|
mergeStrategy,
|
|
770
1777
|
onRegisterControllers,
|
|
771
1778
|
onStatusChange,
|
|
1779
|
+
transformOutputWrite,
|
|
772
1780
|
store: storeRef.current,
|
|
773
1781
|
children
|
|
774
1782
|
}
|
|
@@ -790,9 +1798,11 @@ function VizijRuntimeProviderInner({
|
|
|
790
1798
|
assetBundle: initialAssetBundle,
|
|
791
1799
|
namespace: namespaceProp,
|
|
792
1800
|
faceId: faceIdProp,
|
|
1801
|
+
updateTier,
|
|
793
1802
|
mergeStrategy,
|
|
794
1803
|
onRegisterControllers,
|
|
795
1804
|
onStatusChange,
|
|
1805
|
+
transformOutputWrite,
|
|
796
1806
|
store,
|
|
797
1807
|
children,
|
|
798
1808
|
autoCreate,
|
|
@@ -800,35 +1810,66 @@ function VizijRuntimeProviderInner({
|
|
|
800
1810
|
createOptions,
|
|
801
1811
|
driveOrchestrator
|
|
802
1812
|
}) {
|
|
1813
|
+
const [assetBundleOverride, setAssetBundleOverride] = useState(null);
|
|
1814
|
+
const [graphUpdateToken, setGraphUpdateToken] = useState(0);
|
|
1815
|
+
const effectiveAssetBundle = assetBundleOverride ?? initialAssetBundle;
|
|
1816
|
+
const latestEffectiveAssetBundleRef = useRef(effectiveAssetBundle);
|
|
803
1817
|
const [extractedBundle, setExtractedBundle] = useState(() => {
|
|
804
|
-
if (
|
|
805
|
-
return
|
|
1818
|
+
if (effectiveAssetBundle.bundle) {
|
|
1819
|
+
return effectiveAssetBundle.bundle;
|
|
806
1820
|
}
|
|
807
|
-
if (
|
|
808
|
-
return
|
|
1821
|
+
if (effectiveAssetBundle.glb.kind === "world" && effectiveAssetBundle.glb.bundle) {
|
|
1822
|
+
return effectiveAssetBundle.glb.bundle;
|
|
809
1823
|
}
|
|
810
1824
|
return null;
|
|
811
1825
|
});
|
|
812
1826
|
const [extractedAnimations, setExtractedAnimations] = useState([]);
|
|
1827
|
+
const previousBundleRef = useRef(null);
|
|
1828
|
+
const suppressNextBundlePlanRef = useRef(false);
|
|
1829
|
+
const pendingPlanRef = useRef(null);
|
|
1830
|
+
const updateTierRef = useRef(updateTier);
|
|
813
1831
|
useEffect(() => {
|
|
814
|
-
if (
|
|
815
|
-
setExtractedBundle(
|
|
1832
|
+
if (effectiveAssetBundle.bundle) {
|
|
1833
|
+
setExtractedBundle(effectiveAssetBundle.bundle);
|
|
816
1834
|
return;
|
|
817
1835
|
}
|
|
818
|
-
if (
|
|
819
|
-
setExtractedBundle(
|
|
1836
|
+
if (effectiveAssetBundle.glb.kind === "world") {
|
|
1837
|
+
setExtractedBundle(effectiveAssetBundle.glb.bundle ?? null);
|
|
820
1838
|
} else {
|
|
821
1839
|
setExtractedBundle(null);
|
|
822
1840
|
}
|
|
823
|
-
}, [
|
|
1841
|
+
}, [effectiveAssetBundle]);
|
|
1842
|
+
useEffect(() => {
|
|
1843
|
+
updateTierRef.current = updateTier;
|
|
1844
|
+
}, [updateTier]);
|
|
824
1845
|
const assetBundle = useMemo(
|
|
825
1846
|
() => mergeAssetBundle(
|
|
826
|
-
|
|
1847
|
+
effectiveAssetBundle,
|
|
827
1848
|
extractedBundle,
|
|
828
1849
|
extractedAnimations
|
|
829
1850
|
),
|
|
830
|
-
[
|
|
1851
|
+
[effectiveAssetBundle, extractedBundle, extractedAnimations]
|
|
831
1852
|
);
|
|
1853
|
+
useEffect(() => {
|
|
1854
|
+
latestEffectiveAssetBundleRef.current = effectiveAssetBundle;
|
|
1855
|
+
}, [effectiveAssetBundle]);
|
|
1856
|
+
useEffect(() => {
|
|
1857
|
+
if (suppressNextBundlePlanRef.current) {
|
|
1858
|
+
suppressNextBundlePlanRef.current = false;
|
|
1859
|
+
previousBundleRef.current = effectiveAssetBundle;
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
const plan = resolveRuntimeUpdatePlan(
|
|
1863
|
+
previousBundleRef.current,
|
|
1864
|
+
effectiveAssetBundle,
|
|
1865
|
+
updateTierRef.current
|
|
1866
|
+
);
|
|
1867
|
+
pendingPlanRef.current = plan;
|
|
1868
|
+
previousBundleRef.current = effectiveAssetBundle;
|
|
1869
|
+
if (plan.reregisterGraphs) {
|
|
1870
|
+
setGraphUpdateToken((prev) => prev + 1);
|
|
1871
|
+
}
|
|
1872
|
+
}, [effectiveAssetBundle]);
|
|
832
1873
|
const {
|
|
833
1874
|
ready,
|
|
834
1875
|
createOrchestrator,
|
|
@@ -837,6 +1878,7 @@ function VizijRuntimeProviderInner({
|
|
|
837
1878
|
registerAnimation,
|
|
838
1879
|
removeGraph,
|
|
839
1880
|
removeAnimation,
|
|
1881
|
+
removeInput,
|
|
840
1882
|
listControllers,
|
|
841
1883
|
setInput: orchestratorSetInput,
|
|
842
1884
|
getPathSnapshot,
|
|
@@ -864,13 +1906,57 @@ function VizijRuntimeProviderInner({
|
|
|
864
1906
|
const namespaceRef = useRef(namespace);
|
|
865
1907
|
const driveOrchestratorRef = useRef(driveOrchestrator);
|
|
866
1908
|
const rigInputMapRef = useRef({});
|
|
1909
|
+
const rigPoseControlInputIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
867
1910
|
const registeredGraphsRef = useRef([]);
|
|
868
1911
|
const registeredAnimationsRef = useRef([]);
|
|
869
1912
|
const mergedGraphRef = useRef(null);
|
|
1913
|
+
const poseControlBridgeValuesRef = useRef(/* @__PURE__ */ new Map());
|
|
1914
|
+
const poseWeightFallbackMap = useMemo(() => {
|
|
1915
|
+
const map = /* @__PURE__ */ new Map();
|
|
1916
|
+
const poseConfig = assetBundle.pose?.config;
|
|
1917
|
+
if (!poseConfig) {
|
|
1918
|
+
return map;
|
|
1919
|
+
}
|
|
1920
|
+
const posePaths = buildPoseWeightPathMap(
|
|
1921
|
+
poseConfig.poses ?? [],
|
|
1922
|
+
poseConfig.faceId ?? faceId ?? "face"
|
|
1923
|
+
);
|
|
1924
|
+
(poseConfig.poses ?? []).forEach((pose) => {
|
|
1925
|
+
const posePath = posePaths.get(pose.id);
|
|
1926
|
+
if (!posePath) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
const values = Object.fromEntries(
|
|
1930
|
+
Object.entries(pose.values ?? {}).filter(
|
|
1931
|
+
([, value]) => Number.isFinite(value)
|
|
1932
|
+
)
|
|
1933
|
+
);
|
|
1934
|
+
map.set(posePath, values);
|
|
1935
|
+
});
|
|
1936
|
+
return map;
|
|
1937
|
+
}, [assetBundle.pose?.config, faceId]);
|
|
1938
|
+
const useLegacyPoseWeightFallback = useMemo(
|
|
1939
|
+
() => shouldUseLegacyPoseWeightFallback(Boolean(assetBundle.pose?.graph)),
|
|
1940
|
+
[assetBundle.pose?.graph]
|
|
1941
|
+
);
|
|
1942
|
+
const resolvedProgramAssets = useMemo(
|
|
1943
|
+
() => assetBundle.programs && assetBundle.programs.length > 0 ? assetBundle.programs : convertBundlePrograms(assetBundle.bundle?.graphs),
|
|
1944
|
+
[assetBundle.bundle?.graphs, assetBundle.programs]
|
|
1945
|
+
);
|
|
870
1946
|
const [inputConstraints, setInputConstraints] = useState({});
|
|
1947
|
+
const inputConstraintsRef = useRef({});
|
|
871
1948
|
const avgStepDtRef = useRef(null);
|
|
872
1949
|
const animationTweensRef = useRef(/* @__PURE__ */ new Map());
|
|
873
1950
|
const clipPlaybackRef = useRef(/* @__PURE__ */ new Map());
|
|
1951
|
+
const programPlaybackRef = useRef(
|
|
1952
|
+
/* @__PURE__ */ new Map()
|
|
1953
|
+
);
|
|
1954
|
+
const programControllerIdsRef = useRef(/* @__PURE__ */ new Map());
|
|
1955
|
+
const clipOutputValuesRef = useRef(
|
|
1956
|
+
/* @__PURE__ */ new Map()
|
|
1957
|
+
);
|
|
1958
|
+
const clipAggregateValuesRef = useRef(/* @__PURE__ */ new Map());
|
|
1959
|
+
const animationSystemActiveRef = useRef(true);
|
|
874
1960
|
const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
|
|
875
1961
|
const autostartRef = useRef(autostart);
|
|
876
1962
|
const lastActivityTimeRef = useRef(now());
|
|
@@ -888,6 +1974,7 @@ function VizijRuntimeProviderInner({
|
|
|
888
1974
|
useEffect(() => {
|
|
889
1975
|
const rigAsset = assetBundle.rig;
|
|
890
1976
|
if (!rigAsset) {
|
|
1977
|
+
inputConstraintsRef.current = {};
|
|
891
1978
|
setInputConstraints({});
|
|
892
1979
|
return;
|
|
893
1980
|
}
|
|
@@ -900,15 +1987,8 @@ function VizijRuntimeProviderInner({
|
|
|
900
1987
|
rigAsset.inputMetadata,
|
|
901
1988
|
namespace
|
|
902
1989
|
);
|
|
1990
|
+
inputConstraintsRef.current = constraints;
|
|
903
1991
|
setInputConstraints(constraints);
|
|
904
|
-
const isDevEnv = typeof globalThis !== "undefined" && Boolean(globalThis?.process?.env?.NODE_ENV !== "production");
|
|
905
|
-
if (isDevEnv) {
|
|
906
|
-
const size = Object.keys(constraints).length;
|
|
907
|
-
console.log("[vizij-runtime] input constraints computed", size, {
|
|
908
|
-
namespace,
|
|
909
|
-
rigId: rigAsset.id
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
1992
|
}, [assetBundle.rig, namespace]);
|
|
913
1993
|
const requestLoopMode = useCallback((mode) => {
|
|
914
1994
|
if (!runtimeMountedRef.current) {
|
|
@@ -917,7 +1997,28 @@ function VizijRuntimeProviderInner({
|
|
|
917
1997
|
setLoopMode((prev) => prev === mode ? prev : mode);
|
|
918
1998
|
}, []);
|
|
919
1999
|
const hasActiveAnimations = useCallback(() => {
|
|
920
|
-
|
|
2000
|
+
if (animationTweensRef.current.size > 0) {
|
|
2001
|
+
return true;
|
|
2002
|
+
}
|
|
2003
|
+
if (!animationSystemActiveRef.current) {
|
|
2004
|
+
for (const state of programPlaybackRef.current.values()) {
|
|
2005
|
+
if (state.state === "playing") {
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
for (const state of clipPlaybackRef.current.values()) {
|
|
2012
|
+
if (state.playing) {
|
|
2013
|
+
return true;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
for (const state of programPlaybackRef.current.values()) {
|
|
2017
|
+
if (state.state === "playing") {
|
|
2018
|
+
return true;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return false;
|
|
921
2022
|
}, []);
|
|
922
2023
|
const computeDesiredLoopMode = useCallback(() => {
|
|
923
2024
|
const hasAnimations = hasActiveAnimations();
|
|
@@ -939,11 +2040,50 @@ function VizijRuntimeProviderInner({
|
|
|
939
2040
|
}, [updateLoopMode]);
|
|
940
2041
|
const setInput = useCallback(
|
|
941
2042
|
(path, value, shape) => {
|
|
2043
|
+
const numericValue = valueAsNumber2(value);
|
|
2044
|
+
const basePath = stripNamespace(
|
|
2045
|
+
normalisePath(path),
|
|
2046
|
+
namespaceRef.current
|
|
2047
|
+
);
|
|
2048
|
+
const poseValues = useLegacyPoseWeightFallback && numericValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
|
|
2049
|
+
if (poseValues && numericValue != null) {
|
|
2050
|
+
const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
|
|
2051
|
+
const rigMap = rigInputMapRef.current;
|
|
2052
|
+
Object.entries(poseValues).forEach(([inputId, poseValue]) => {
|
|
2053
|
+
if (!Number.isFinite(poseValue)) {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
const controlPath = resolvePoseControlInputPath({
|
|
2057
|
+
inputId,
|
|
2058
|
+
basePath: buildRigInputPath(
|
|
2059
|
+
poseFaceId,
|
|
2060
|
+
`/pose/control/${inputId}`
|
|
2061
|
+
),
|
|
2062
|
+
rigInputPathMap: rigMap,
|
|
2063
|
+
hasNativePoseControlInput: true
|
|
2064
|
+
}) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
|
|
2065
|
+
setInput(controlPath, { float: Number(poseValue) * numericValue });
|
|
2066
|
+
});
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
942
2069
|
markActivity();
|
|
943
2070
|
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2071
|
+
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2072
|
+
console.log("[vizij-runtime] stage input", {
|
|
2073
|
+
path,
|
|
2074
|
+
namespacedPath,
|
|
2075
|
+
value
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
944
2078
|
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
945
2079
|
},
|
|
946
|
-
[
|
|
2080
|
+
[
|
|
2081
|
+
assetBundle.pose?.config?.faceId,
|
|
2082
|
+
faceId,
|
|
2083
|
+
markActivity,
|
|
2084
|
+
poseWeightFallbackMap,
|
|
2085
|
+
useLegacyPoseWeightFallback
|
|
2086
|
+
]
|
|
947
2087
|
);
|
|
948
2088
|
const reportStatus = useCallback(
|
|
949
2089
|
(updater) => {
|
|
@@ -1007,10 +2147,14 @@ function VizijRuntimeProviderInner({
|
|
|
1007
2147
|
});
|
|
1008
2148
|
registeredGraphsRef.current = [];
|
|
1009
2149
|
registeredAnimationsRef.current = [];
|
|
2150
|
+
programControllerIdsRef.current.clear();
|
|
1010
2151
|
mergedGraphRef.current = null;
|
|
1011
2152
|
outputPathsRef.current = /* @__PURE__ */ new Set();
|
|
1012
2153
|
baseOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
1013
2154
|
namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2155
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2156
|
+
clipOutputValuesRef.current.clear();
|
|
2157
|
+
clipAggregateValuesRef.current.clear();
|
|
1014
2158
|
}, [listControllers, removeAnimation, removeGraph, pushError]);
|
|
1015
2159
|
useEffect(() => {
|
|
1016
2160
|
namespaceRef.current = namespace;
|
|
@@ -1023,10 +2167,19 @@ function VizijRuntimeProviderInner({
|
|
|
1023
2167
|
useEffect(() => {
|
|
1024
2168
|
driveOrchestratorRef.current = driveOrchestrator;
|
|
1025
2169
|
}, [driveOrchestrator]);
|
|
1026
|
-
const glbAsset =
|
|
1027
|
-
const baseBundle =
|
|
2170
|
+
const glbAsset = effectiveAssetBundle.glb;
|
|
2171
|
+
const baseBundle = effectiveAssetBundle.bundle ?? null;
|
|
1028
2172
|
useEffect(() => {
|
|
1029
2173
|
let cancelled = false;
|
|
2174
|
+
const plan = pendingPlanRef.current;
|
|
2175
|
+
if (plan && !plan.reloadAssets && status.rootId !== null) {
|
|
2176
|
+
reportStatus(
|
|
2177
|
+
(prev) => prev.loading ? { ...prev, loading: false } : prev
|
|
2178
|
+
);
|
|
2179
|
+
return () => {
|
|
2180
|
+
cancelled = true;
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
1030
2183
|
resetErrors();
|
|
1031
2184
|
reportStatus((prev) => ({
|
|
1032
2185
|
...prev,
|
|
@@ -1078,6 +2231,12 @@ function VizijRuntimeProviderInner({
|
|
|
1078
2231
|
setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
|
|
1079
2232
|
const rootId = findRootId(world);
|
|
1080
2233
|
store.getState().addWorldElements(world, animatables, true);
|
|
2234
|
+
if (pendingPlanRef.current?.reloadAssets) {
|
|
2235
|
+
pendingPlanRef.current = {
|
|
2236
|
+
...pendingPlanRef.current,
|
|
2237
|
+
reloadAssets: false
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
1081
2240
|
reportStatus((prev) => ({
|
|
1082
2241
|
...prev,
|
|
1083
2242
|
loading: false,
|
|
@@ -1115,7 +2274,8 @@ function VizijRuntimeProviderInner({
|
|
|
1115
2274
|
reportStatus,
|
|
1116
2275
|
resetErrors,
|
|
1117
2276
|
setExtractedBundle,
|
|
1118
|
-
setExtractedAnimations
|
|
2277
|
+
setExtractedAnimations,
|
|
2278
|
+
status.rootId
|
|
1119
2279
|
]);
|
|
1120
2280
|
useEffect(() => {
|
|
1121
2281
|
if (!ready && autoCreate) {
|
|
@@ -1131,6 +2291,15 @@ function VizijRuntimeProviderInner({
|
|
|
1131
2291
|
}, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
|
|
1132
2292
|
const registerControllers = useCallback(async () => {
|
|
1133
2293
|
clearControllers();
|
|
2294
|
+
if (isRuntimeDebugEnabled()) {
|
|
2295
|
+
console.log("[vizij-runtime] registerControllers", {
|
|
2296
|
+
hasRig: Boolean(assetBundle.rig),
|
|
2297
|
+
hasPose: Boolean(assetBundle.pose?.graph),
|
|
2298
|
+
animationCount: assetBundle.animations?.length ?? 0,
|
|
2299
|
+
animationIds: (assetBundle.animations ?? []).map((anim) => anim.id),
|
|
2300
|
+
namespace
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
1134
2303
|
const baseOutputPaths = /* @__PURE__ */ new Set();
|
|
1135
2304
|
const namespacedOutputPaths = /* @__PURE__ */ new Set();
|
|
1136
2305
|
const recordOutputs = (paths) => {
|
|
@@ -1142,39 +2311,60 @@ function VizijRuntimeProviderInner({
|
|
|
1142
2311
|
namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
|
|
1143
2312
|
});
|
|
1144
2313
|
};
|
|
2314
|
+
const graphConfigs = [];
|
|
2315
|
+
rigInputMapRef.current = {};
|
|
2316
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2317
|
+
poseControlBridgeValuesRef.current.clear();
|
|
1145
2318
|
const rigAsset = assetBundle.rig;
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
2319
|
+
if (rigAsset) {
|
|
2320
|
+
const rigSpec = resolveGraphSpec(
|
|
2321
|
+
rigAsset,
|
|
2322
|
+
`${rigAsset.id ?? "rig"} graph`
|
|
2323
|
+
);
|
|
2324
|
+
if (!rigSpec) {
|
|
2325
|
+
pushError({
|
|
2326
|
+
message: "Rig graph is missing a usable spec or IR payload.",
|
|
2327
|
+
phase: "registration",
|
|
2328
|
+
timestamp: performance.now()
|
|
2329
|
+
});
|
|
2330
|
+
} else {
|
|
2331
|
+
const rigOutputs = collectOutputPaths(rigSpec);
|
|
2332
|
+
const rigInputs = collectInputPaths(rigSpec);
|
|
2333
|
+
const rigPoseControlInputIds = /* @__PURE__ */ new Set();
|
|
2334
|
+
rigInputs.forEach((path) => {
|
|
2335
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
2336
|
+
path.trim()
|
|
2337
|
+
);
|
|
2338
|
+
const inputId = (poseControlMatch?.[1] ?? "").trim();
|
|
2339
|
+
if (inputId.length > 0) {
|
|
2340
|
+
rigPoseControlInputIds.add(inputId);
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
rigInputMapRef.current = collectInputPathMap(rigSpec);
|
|
2344
|
+
rigPoseControlInputIdsRef.current = rigPoseControlInputIds;
|
|
2345
|
+
if (isRuntimeDebugEnabled()) {
|
|
2346
|
+
const blinkKeys = Object.keys(rigInputMapRef.current).filter(
|
|
2347
|
+
(key) => key.toLowerCase().includes("blink")
|
|
2348
|
+
);
|
|
2349
|
+
const blinkMappings = blinkKeys.slice(0, 20).map((key) => `${key} => ${rigInputMapRef.current[key] ?? "?"}`);
|
|
2350
|
+
console.log("[vizij-runtime] rig input map sample", {
|
|
2351
|
+
blink: rigInputMapRef.current["blink"] ?? null,
|
|
2352
|
+
blinkKeys: blinkKeys.slice(0, 12),
|
|
2353
|
+
blinkMappings: blinkMappings.join(" | ")
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
recordOutputs(rigOutputs);
|
|
2357
|
+
const rigSubs = rigAsset.subscriptions ?? {
|
|
2358
|
+
inputs: rigInputs,
|
|
2359
|
+
outputs: rigOutputs
|
|
2360
|
+
};
|
|
2361
|
+
graphConfigs.push({
|
|
2362
|
+
id: namespaceControllerId(rigAsset.id, namespace, "graph"),
|
|
2363
|
+
spec: stripNulls(namespaceGraphSpec(rigSpec, namespace)),
|
|
2364
|
+
subs: namespaceSubscriptions(rigSubs, namespace)
|
|
2365
|
+
});
|
|
1176
2366
|
}
|
|
1177
|
-
|
|
2367
|
+
}
|
|
1178
2368
|
const poseGraphAsset = assetBundle.pose?.graph;
|
|
1179
2369
|
if (poseGraphAsset) {
|
|
1180
2370
|
const poseSpec = resolveGraphSpec(
|
|
@@ -1191,7 +2381,7 @@ function VizijRuntimeProviderInner({
|
|
|
1191
2381
|
};
|
|
1192
2382
|
graphConfigs.push({
|
|
1193
2383
|
id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
|
|
1194
|
-
spec: namespaceGraphSpec(poseSpec, namespace),
|
|
2384
|
+
spec: stripNulls(namespaceGraphSpec(poseSpec, namespace)),
|
|
1195
2385
|
subs: namespaceSubscriptions(poseSubs, namespace)
|
|
1196
2386
|
});
|
|
1197
2387
|
} else {
|
|
@@ -1200,6 +2390,31 @@ function VizijRuntimeProviderInner({
|
|
|
1200
2390
|
);
|
|
1201
2391
|
}
|
|
1202
2392
|
}
|
|
2393
|
+
for (const animation of assetBundle.animations ?? []) {
|
|
2394
|
+
const bridgeOutputs = collectAnimationClipOutputPaths(
|
|
2395
|
+
animation.clip,
|
|
2396
|
+
faceId ?? void 0,
|
|
2397
|
+
rigInputMapRef.current
|
|
2398
|
+
);
|
|
2399
|
+
if (isRuntimeDebugEnabled()) {
|
|
2400
|
+
console.log("[vizij-runtime] animation output routing", {
|
|
2401
|
+
animationId: animation.id,
|
|
2402
|
+
bridgeOutputs,
|
|
2403
|
+
bridgeOutputsText: bridgeOutputs.join(" | ")
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
recordOutputs(bridgeOutputs);
|
|
2407
|
+
}
|
|
2408
|
+
for (const program of resolvedProgramAssets) {
|
|
2409
|
+
const programSpec = resolveGraphSpec(
|
|
2410
|
+
program.graph,
|
|
2411
|
+
`${program.id ?? "program"} graph (outputs)`
|
|
2412
|
+
);
|
|
2413
|
+
if (!programSpec) {
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2416
|
+
recordOutputs(collectOutputPaths(programSpec));
|
|
2417
|
+
}
|
|
1203
2418
|
outputPathsRef.current = namespacedOutputPaths;
|
|
1204
2419
|
baseOutputPathsRef.current = baseOutputPaths;
|
|
1205
2420
|
namespacedOutputPathsRef.current = namespacedOutputPaths;
|
|
@@ -1232,20 +2447,30 @@ function VizijRuntimeProviderInner({
|
|
|
1232
2447
|
});
|
|
1233
2448
|
}
|
|
1234
2449
|
registeredGraphsRef.current = graphIds;
|
|
2450
|
+
if (isRuntimeDebugEnabled()) {
|
|
2451
|
+
console.log("[vizij-runtime] registered graph ids", graphIds);
|
|
2452
|
+
}
|
|
1235
2453
|
const animationIds = [];
|
|
1236
2454
|
for (const anim of assetBundle.animations ?? []) {
|
|
1237
2455
|
try {
|
|
1238
2456
|
const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
|
|
2457
|
+
const animationPayload = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
|
|
1239
2458
|
const config = {
|
|
1240
2459
|
id: controllerId,
|
|
1241
2460
|
setup: {
|
|
1242
|
-
|
|
1243
|
-
|
|
2461
|
+
...anim.setup ?? {},
|
|
2462
|
+
animation: animationPayload
|
|
1244
2463
|
}
|
|
1245
2464
|
};
|
|
1246
2465
|
const id = registerAnimation(config);
|
|
1247
2466
|
animationIds.push(id);
|
|
1248
2467
|
} catch (err) {
|
|
2468
|
+
if (isRuntimeDebugEnabled()) {
|
|
2469
|
+
console.warn("[vizij-runtime] failed animation registration", {
|
|
2470
|
+
animationId: anim.id,
|
|
2471
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
1249
2474
|
pushError({
|
|
1250
2475
|
message: `Failed to register animation ${anim.id}`,
|
|
1251
2476
|
cause: err,
|
|
@@ -1270,6 +2495,13 @@ function VizijRuntimeProviderInner({
|
|
|
1270
2495
|
});
|
|
1271
2496
|
}
|
|
1272
2497
|
const controllers = listControllers();
|
|
2498
|
+
if (isRuntimeDebugEnabled()) {
|
|
2499
|
+
console.log("[vizij-runtime] controllers after register", {
|
|
2500
|
+
controllers,
|
|
2501
|
+
graphIds,
|
|
2502
|
+
animationIds
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
1273
2505
|
reportStatus((prev) => ({
|
|
1274
2506
|
...prev,
|
|
1275
2507
|
ready: true,
|
|
@@ -1289,12 +2521,18 @@ function VizijRuntimeProviderInner({
|
|
|
1289
2521
|
registerGraph,
|
|
1290
2522
|
registerMergedGraph,
|
|
1291
2523
|
reportStatus,
|
|
2524
|
+
resolvedProgramAssets,
|
|
1292
2525
|
setInput
|
|
1293
2526
|
]);
|
|
1294
2527
|
useEffect(() => {
|
|
1295
2528
|
if (!ready || status.loading) {
|
|
1296
2529
|
return;
|
|
1297
2530
|
}
|
|
2531
|
+
const plan = pendingPlanRef.current;
|
|
2532
|
+
const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
|
|
2533
|
+
if (plan && !plan.reregisterGraphs && hasRegistered) {
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
1298
2536
|
registerControllers().catch((err) => {
|
|
1299
2537
|
pushError({
|
|
1300
2538
|
message: "Failed to register controllers",
|
|
@@ -1303,7 +2541,7 @@ function VizijRuntimeProviderInner({
|
|
|
1303
2541
|
timestamp: performance.now()
|
|
1304
2542
|
});
|
|
1305
2543
|
});
|
|
1306
|
-
}, [ready, status.loading, registerControllers, pushError]);
|
|
2544
|
+
}, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
|
|
1307
2545
|
useEffect(() => {
|
|
1308
2546
|
if (!frame) {
|
|
1309
2547
|
return;
|
|
@@ -1314,26 +2552,66 @@ function VizijRuntimeProviderInner({
|
|
|
1314
2552
|
}
|
|
1315
2553
|
const setWorldValues = store.getState().setValues;
|
|
1316
2554
|
const namespaceValue = status.namespace;
|
|
2555
|
+
const currentValues = store.getState().values;
|
|
2556
|
+
const rigInputPathMap = rigInputMapRef.current;
|
|
2557
|
+
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
1317
2558
|
const batched = [];
|
|
1318
2559
|
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
1319
2560
|
const baseOutputs = baseOutputPathsRef.current;
|
|
1320
2561
|
writes.forEach((write) => {
|
|
1321
2562
|
const path = normalisePath(write.path);
|
|
1322
|
-
|
|
2563
|
+
const basePath = stripNamespace(path, namespaceValue);
|
|
2564
|
+
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
2565
|
+
if (!isTrackedOutput) {
|
|
1323
2566
|
return;
|
|
1324
2567
|
}
|
|
1325
2568
|
const raw = valueJSONToRaw(write.value);
|
|
1326
2569
|
if (raw === void 0) {
|
|
1327
2570
|
return;
|
|
1328
2571
|
}
|
|
1329
|
-
const
|
|
2572
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
2573
|
+
basePath
|
|
2574
|
+
);
|
|
2575
|
+
if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
|
|
2576
|
+
const inputId = (poseControlMatch[1] ?? "").trim();
|
|
2577
|
+
const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
|
|
2578
|
+
const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
|
|
2579
|
+
inputId,
|
|
2580
|
+
basePath,
|
|
2581
|
+
rigInputPathMap,
|
|
2582
|
+
hasNativePoseControlInput
|
|
2583
|
+
});
|
|
2584
|
+
if (mappedInputPath) {
|
|
2585
|
+
const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
|
|
2586
|
+
const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
|
|
2587
|
+
if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
|
|
2588
|
+
poseControlBridgeValuesRef.current.set(bridgeKey, raw);
|
|
2589
|
+
setInput(mappedInputPath, { float: raw });
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
1330
2593
|
const targetPath = baseOutputs.has(basePath) ? basePath : path;
|
|
1331
|
-
|
|
2594
|
+
const currentValue = currentValues.get(
|
|
2595
|
+
getLookup(namespaceValue, targetPath)
|
|
2596
|
+
);
|
|
2597
|
+
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
2598
|
+
id: targetPath,
|
|
2599
|
+
namespace: namespaceValue,
|
|
2600
|
+
value: raw,
|
|
2601
|
+
currentValue
|
|
2602
|
+
}) : {
|
|
2603
|
+
id: targetPath,
|
|
2604
|
+
namespace: namespaceValue,
|
|
2605
|
+
value: raw
|
|
2606
|
+
};
|
|
2607
|
+
if (nextWrite) {
|
|
2608
|
+
batched.push(nextWrite);
|
|
2609
|
+
}
|
|
1332
2610
|
});
|
|
1333
2611
|
if (batched.length > 0) {
|
|
1334
2612
|
setWorldValues(batched);
|
|
1335
2613
|
}
|
|
1336
|
-
}, [frame, status.namespace, store]);
|
|
2614
|
+
}, [frame, status.namespace, store, transformOutputWrite]);
|
|
1337
2615
|
const stagePoseNeutral = useCallback(
|
|
1338
2616
|
(force = false) => {
|
|
1339
2617
|
const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
|
|
@@ -1401,88 +2679,237 @@ function VizijRuntimeProviderInner({
|
|
|
1401
2679
|
},
|
|
1402
2680
|
[setInput]
|
|
1403
2681
|
);
|
|
1404
|
-
const
|
|
1405
|
-
(
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2682
|
+
const resolveClipById = useCallback(
|
|
2683
|
+
(id) => {
|
|
2684
|
+
return assetBundle.animations?.find((anim) => anim.id === id);
|
|
2685
|
+
},
|
|
2686
|
+
[assetBundle.animations]
|
|
2687
|
+
);
|
|
2688
|
+
const resolveClipPromise = useCallback((state) => {
|
|
2689
|
+
state.resolve?.();
|
|
2690
|
+
state.resolve = null;
|
|
2691
|
+
state.completion = null;
|
|
2692
|
+
}, []);
|
|
2693
|
+
const ensureClipPromise = useCallback((state) => {
|
|
2694
|
+
if (state.completion) {
|
|
2695
|
+
return state.completion;
|
|
2696
|
+
}
|
|
2697
|
+
const completion = new Promise((resolve) => {
|
|
2698
|
+
state.resolve = resolve;
|
|
2699
|
+
});
|
|
2700
|
+
state.completion = completion;
|
|
2701
|
+
return completion;
|
|
2702
|
+
}, []);
|
|
2703
|
+
const setAnimationInput = useCallback(
|
|
2704
|
+
(path, value, options) => {
|
|
2705
|
+
setInput(path, { float: value });
|
|
2706
|
+
if (!options?.immediate) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2710
|
+
const staged = stagedInputsRef.current.get(namespacedPath);
|
|
2711
|
+
if (staged) {
|
|
2712
|
+
orchestratorSetInput(namespacedPath, staged.value, staged.shape);
|
|
2713
|
+
stagedInputsRef.current.delete(namespacedPath);
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
orchestratorSetInput(namespacedPath, { float: value });
|
|
2717
|
+
},
|
|
2718
|
+
[orchestratorSetInput, setInput]
|
|
2719
|
+
);
|
|
2720
|
+
const clearAnimationInput = useCallback(
|
|
2721
|
+
(path) => {
|
|
2722
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2723
|
+
stagedInputsRef.current.delete(namespacedPath);
|
|
2724
|
+
removeInput(namespacedPath);
|
|
2725
|
+
},
|
|
2726
|
+
[removeInput]
|
|
2727
|
+
);
|
|
2728
|
+
const buildClipOutputValues = useCallback(
|
|
2729
|
+
(clip, state) => sampleAnimationClipOutputValues(
|
|
2730
|
+
clip.clip,
|
|
2731
|
+
state.time,
|
|
2732
|
+
state.weight,
|
|
2733
|
+
faceId ?? void 0,
|
|
2734
|
+
rigInputMapRef.current
|
|
2735
|
+
),
|
|
2736
|
+
[faceId]
|
|
2737
|
+
);
|
|
2738
|
+
const computeClipAggregateValues = useCallback(() => {
|
|
2739
|
+
const aggregate = /* @__PURE__ */ new Map();
|
|
2740
|
+
clipOutputValuesRef.current.forEach((outputValues) => {
|
|
2741
|
+
outputValues.forEach((value, path) => {
|
|
2742
|
+
aggregate.set(path, (aggregate.get(path) ?? 0) + value);
|
|
2743
|
+
});
|
|
2744
|
+
});
|
|
2745
|
+
return aggregate;
|
|
2746
|
+
}, []);
|
|
2747
|
+
const stageClipAggregateValues = useCallback(
|
|
2748
|
+
(nextAggregate, options) => {
|
|
2749
|
+
diffAnimationAggregateValues(
|
|
2750
|
+
clipAggregateValuesRef.current,
|
|
2751
|
+
nextAggregate,
|
|
2752
|
+
POSE_CONTROL_BRIDGE_EPSILON
|
|
2753
|
+
).forEach((operation) => {
|
|
2754
|
+
if (operation.kind === "clear") {
|
|
2755
|
+
clearAnimationInput(operation.path);
|
|
2756
|
+
return;
|
|
1428
2757
|
}
|
|
2758
|
+
setAnimationInput(operation.path, operation.value, options);
|
|
2759
|
+
});
|
|
2760
|
+
clipAggregateValuesRef.current = nextAggregate;
|
|
2761
|
+
},
|
|
2762
|
+
[clearAnimationInput, setAnimationInput]
|
|
2763
|
+
);
|
|
2764
|
+
const syncClipOutputs = useCallback(
|
|
2765
|
+
(options) => {
|
|
2766
|
+
stageClipAggregateValues(computeClipAggregateValues(), options);
|
|
2767
|
+
},
|
|
2768
|
+
[computeClipAggregateValues, stageClipAggregateValues]
|
|
2769
|
+
);
|
|
2770
|
+
const writeClipOutputs = useCallback(
|
|
2771
|
+
(clip, state, options) => {
|
|
2772
|
+
if (!animationSystemActiveRef.current) {
|
|
2773
|
+
return;
|
|
1429
2774
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
2775
|
+
clipOutputValuesRef.current.set(
|
|
2776
|
+
clip.id,
|
|
2777
|
+
buildClipOutputValues(clip, state)
|
|
2778
|
+
);
|
|
2779
|
+
syncClipOutputs(options);
|
|
2780
|
+
},
|
|
2781
|
+
[buildClipOutputValues, syncClipOutputs]
|
|
2782
|
+
);
|
|
2783
|
+
const clearClipOutputs = useCallback(
|
|
2784
|
+
(clipId, options) => {
|
|
2785
|
+
if (!clipOutputValuesRef.current.has(clipId)) {
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
clipOutputValuesRef.current.delete(clipId);
|
|
2789
|
+
syncClipOutputs(options);
|
|
2790
|
+
},
|
|
2791
|
+
[syncClipOutputs]
|
|
2792
|
+
);
|
|
2793
|
+
const createClipPlaybackState = useCallback(
|
|
2794
|
+
(clip) => {
|
|
2795
|
+
const duration = resolveClipDurationSeconds(
|
|
2796
|
+
clip.clip
|
|
2797
|
+
);
|
|
2798
|
+
return {
|
|
2799
|
+
id: clip.id,
|
|
2800
|
+
time: 0,
|
|
2801
|
+
duration,
|
|
2802
|
+
speed: 1,
|
|
2803
|
+
weight: Number.isFinite(clip.weight) ? Number(clip.weight) : 1,
|
|
2804
|
+
loop: false,
|
|
2805
|
+
playing: false,
|
|
2806
|
+
resolve: null,
|
|
2807
|
+
completion: null
|
|
2808
|
+
};
|
|
1432
2809
|
},
|
|
1433
2810
|
[]
|
|
1434
2811
|
);
|
|
2812
|
+
const ensureClipPlaybackState = useCallback(
|
|
2813
|
+
(id) => {
|
|
2814
|
+
const clip = resolveClipById(id);
|
|
2815
|
+
if (!clip) {
|
|
2816
|
+
return null;
|
|
2817
|
+
}
|
|
2818
|
+
const existing = clipPlaybackRef.current.get(id);
|
|
2819
|
+
if (existing) {
|
|
2820
|
+
existing.duration = resolveClipDurationSeconds(
|
|
2821
|
+
clip.clip,
|
|
2822
|
+
existing.duration
|
|
2823
|
+
);
|
|
2824
|
+
existing.time = clampAnimationTime(existing.time, existing.duration);
|
|
2825
|
+
return { clip, state: existing };
|
|
2826
|
+
}
|
|
2827
|
+
const next = createClipPlaybackState(clip);
|
|
2828
|
+
clipPlaybackRef.current.set(id, next);
|
|
2829
|
+
return { clip, state: next };
|
|
2830
|
+
},
|
|
2831
|
+
[createClipPlaybackState, resolveClipById]
|
|
2832
|
+
);
|
|
1435
2833
|
const advanceClipPlayback = useCallback(
|
|
1436
2834
|
(dt) => {
|
|
1437
2835
|
if (clipPlaybackRef.current.size === 0) {
|
|
1438
2836
|
return;
|
|
1439
2837
|
}
|
|
1440
|
-
const map = clipPlaybackRef.current;
|
|
1441
2838
|
const toDelete = [];
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
const clip = assetBundle.animations?.find(
|
|
1445
|
-
(anim) => anim.id === state.id
|
|
1446
|
-
);
|
|
2839
|
+
clipPlaybackRef.current.forEach((state, key) => {
|
|
2840
|
+
const clip = resolveClipById(state.id);
|
|
1447
2841
|
if (!clip) {
|
|
1448
2842
|
toDelete.push(key);
|
|
1449
|
-
state
|
|
2843
|
+
resolveClipPromise(state);
|
|
1450
2844
|
return;
|
|
1451
2845
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
2846
|
+
state.duration = resolveClipDurationSeconds(
|
|
2847
|
+
clip.clip,
|
|
2848
|
+
state.duration
|
|
2849
|
+
);
|
|
2850
|
+
const { time, completed } = advanceClipTime(
|
|
2851
|
+
{
|
|
2852
|
+
time: state.time,
|
|
2853
|
+
duration: state.duration,
|
|
2854
|
+
speed: state.speed,
|
|
2855
|
+
loop: state.loop,
|
|
2856
|
+
playing: state.playing
|
|
2857
|
+
},
|
|
2858
|
+
dt
|
|
2859
|
+
);
|
|
2860
|
+
state.time = clampAnimationTime(time, state.duration);
|
|
2861
|
+
if (state.playing || completed) {
|
|
2862
|
+
writeClipOutputs(clip, state);
|
|
1458
2863
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
const path = `animation/${clip.id}/${track.channel}`;
|
|
1463
|
-
setInput(path, { float: value });
|
|
1464
|
-
});
|
|
1465
|
-
if (toDelete.includes(key)) {
|
|
1466
|
-
state.resolve();
|
|
2864
|
+
if (completed) {
|
|
2865
|
+
toDelete.push(key);
|
|
2866
|
+
resolveClipPromise(state);
|
|
1467
2867
|
}
|
|
1468
2868
|
});
|
|
1469
2869
|
toDelete.forEach((key) => {
|
|
1470
2870
|
clipPlaybackRef.current.delete(key);
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
const clipData = clip.clip;
|
|
1474
|
-
const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
|
|
1475
|
-
tracks.forEach((track) => {
|
|
1476
|
-
const path = `animation/${clip.id}/${track.channel}`;
|
|
1477
|
-
setInput(path, { float: 0 });
|
|
1478
|
-
});
|
|
2871
|
+
if (animationSystemActiveRef.current) {
|
|
2872
|
+
clearClipOutputs(key);
|
|
1479
2873
|
}
|
|
1480
2874
|
});
|
|
1481
2875
|
},
|
|
1482
|
-
[
|
|
2876
|
+
[clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
|
|
1483
2877
|
);
|
|
1484
2878
|
const animateValue = useCallback(
|
|
1485
2879
|
(path, target, options) => {
|
|
2880
|
+
const targetValue = valueAsNumber2(target);
|
|
2881
|
+
const basePath = stripNamespace(
|
|
2882
|
+
normalisePath(path),
|
|
2883
|
+
namespaceRef.current
|
|
2884
|
+
);
|
|
2885
|
+
const poseValues = useLegacyPoseWeightFallback && targetValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
|
|
2886
|
+
if (poseValues && targetValue != null) {
|
|
2887
|
+
const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
|
|
2888
|
+
const rigMap = rigInputMapRef.current;
|
|
2889
|
+
return Promise.all(
|
|
2890
|
+
Object.entries(poseValues).flatMap(([inputId, poseValue]) => {
|
|
2891
|
+
if (!Number.isFinite(poseValue)) {
|
|
2892
|
+
return [];
|
|
2893
|
+
}
|
|
2894
|
+
const controlPath = resolvePoseControlInputPath({
|
|
2895
|
+
inputId,
|
|
2896
|
+
basePath: buildRigInputPath(
|
|
2897
|
+
poseFaceId,
|
|
2898
|
+
`/pose/control/${inputId}`
|
|
2899
|
+
),
|
|
2900
|
+
rigInputPathMap: rigMap,
|
|
2901
|
+
hasNativePoseControlInput: true
|
|
2902
|
+
}) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
|
|
2903
|
+
return [
|
|
2904
|
+
animateValue(
|
|
2905
|
+
controlPath,
|
|
2906
|
+
{ float: Number(poseValue) * targetValue },
|
|
2907
|
+
options
|
|
2908
|
+
)
|
|
2909
|
+
];
|
|
2910
|
+
})
|
|
2911
|
+
).then(() => void 0);
|
|
2912
|
+
}
|
|
1486
2913
|
const easing = resolveEasing(options?.easing);
|
|
1487
2914
|
const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
|
|
1488
2915
|
cancelAnimation(path);
|
|
@@ -1496,7 +2923,9 @@ function VizijRuntimeProviderInner({
|
|
|
1496
2923
|
}
|
|
1497
2924
|
return new Promise((resolve) => {
|
|
1498
2925
|
animationTweensRef.current.set(path, {
|
|
1499
|
-
path
|
|
2926
|
+
// Keep the raw path here so tween updates go through setInput() once
|
|
2927
|
+
// and pick up the active namespace exactly once.
|
|
2928
|
+
path,
|
|
1500
2929
|
from: fromValue,
|
|
1501
2930
|
to: toValue,
|
|
1502
2931
|
duration,
|
|
@@ -1507,54 +2936,377 @@ function VizijRuntimeProviderInner({
|
|
|
1507
2936
|
markActivity();
|
|
1508
2937
|
});
|
|
1509
2938
|
},
|
|
1510
|
-
[
|
|
2939
|
+
[
|
|
2940
|
+
assetBundle.pose?.config?.faceId,
|
|
2941
|
+
cancelAnimation,
|
|
2942
|
+
faceId,
|
|
2943
|
+
getPathSnapshot,
|
|
2944
|
+
markActivity,
|
|
2945
|
+
poseWeightFallbackMap,
|
|
2946
|
+
setInput,
|
|
2947
|
+
useLegacyPoseWeightFallback
|
|
2948
|
+
]
|
|
1511
2949
|
);
|
|
1512
2950
|
const playAnimation = useCallback(
|
|
1513
2951
|
(id, options) => {
|
|
1514
|
-
const
|
|
1515
|
-
if (!
|
|
2952
|
+
const ensured = ensureClipPlaybackState(id);
|
|
2953
|
+
if (!ensured) {
|
|
1516
2954
|
return Promise.reject(
|
|
1517
2955
|
new Error(`Animation ${id} is not part of the current asset bundle.`)
|
|
1518
2956
|
);
|
|
1519
2957
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
2958
|
+
const { clip, state } = ensured;
|
|
2959
|
+
const shouldReset = options?.reset === true;
|
|
2960
|
+
if (shouldReset) {
|
|
2961
|
+
resolveClipPromise(state);
|
|
2962
|
+
state.time = 0;
|
|
1522
2963
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
2964
|
+
const speed = options?.speed ?? state.speed ?? 1;
|
|
2965
|
+
const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
|
|
2966
|
+
state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
2967
|
+
state.weight = Number.isFinite(weight) ? Number(weight) : 1;
|
|
2968
|
+
state.duration = resolveClipDurationSeconds(
|
|
2969
|
+
clip.clip,
|
|
2970
|
+
state.duration
|
|
2971
|
+
);
|
|
2972
|
+
state.time = clampAnimationTime(state.time, state.duration);
|
|
2973
|
+
state.playing = true;
|
|
2974
|
+
const completion = ensureClipPromise(state);
|
|
2975
|
+
clipPlaybackRef.current.set(id, state);
|
|
2976
|
+
writeClipOutputs(clip, state);
|
|
2977
|
+
markActivity();
|
|
2978
|
+
return completion;
|
|
1537
2979
|
},
|
|
1538
|
-
[
|
|
2980
|
+
[
|
|
2981
|
+
ensureClipPlaybackState,
|
|
2982
|
+
ensureClipPromise,
|
|
2983
|
+
markActivity,
|
|
2984
|
+
resolveClipPromise,
|
|
2985
|
+
writeClipOutputs
|
|
2986
|
+
]
|
|
1539
2987
|
);
|
|
1540
|
-
const
|
|
2988
|
+
const pauseAnimation = useCallback(
|
|
1541
2989
|
(id) => {
|
|
1542
|
-
const
|
|
2990
|
+
const state = clipPlaybackRef.current.get(id);
|
|
2991
|
+
if (!state || !state.playing) {
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
state.playing = false;
|
|
2995
|
+
updateLoopMode();
|
|
2996
|
+
},
|
|
2997
|
+
[updateLoopMode]
|
|
2998
|
+
);
|
|
2999
|
+
const seekAnimation = useCallback(
|
|
3000
|
+
(id, timeSeconds) => {
|
|
3001
|
+
const ensured = ensureClipPlaybackState(id);
|
|
3002
|
+
if (!ensured) {
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
const { clip, state } = ensured;
|
|
3006
|
+
state.time = clampAnimationTime(timeSeconds, state.duration);
|
|
3007
|
+
clipPlaybackRef.current.set(id, state);
|
|
3008
|
+
writeClipOutputs(clip, state, { immediate: true });
|
|
3009
|
+
},
|
|
3010
|
+
[ensureClipPlaybackState, writeClipOutputs]
|
|
3011
|
+
);
|
|
3012
|
+
const setAnimationLoop = useCallback(
|
|
3013
|
+
(id, enabled) => {
|
|
3014
|
+
const ensured = ensureClipPlaybackState(id);
|
|
3015
|
+
if (!ensured) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
ensured.state.loop = Boolean(enabled);
|
|
3019
|
+
clipPlaybackRef.current.set(id, ensured.state);
|
|
3020
|
+
updateLoopMode();
|
|
3021
|
+
},
|
|
3022
|
+
[ensureClipPlaybackState, updateLoopMode]
|
|
3023
|
+
);
|
|
3024
|
+
const getAnimationState = useCallback(
|
|
3025
|
+
(id) => {
|
|
3026
|
+
const state = clipPlaybackRef.current.get(id);
|
|
3027
|
+
if (!state) {
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
time: state.time,
|
|
3032
|
+
duration: state.duration,
|
|
3033
|
+
playing: state.playing,
|
|
3034
|
+
loop: state.loop,
|
|
3035
|
+
speed: state.speed
|
|
3036
|
+
};
|
|
3037
|
+
},
|
|
3038
|
+
[]
|
|
3039
|
+
);
|
|
3040
|
+
const stopAnimation = useCallback(
|
|
3041
|
+
(id, options) => {
|
|
1543
3042
|
const state = clipPlaybackRef.current.get(id);
|
|
1544
3043
|
if (state) {
|
|
1545
3044
|
clipPlaybackRef.current.delete(id);
|
|
1546
|
-
state.
|
|
3045
|
+
state.playing = false;
|
|
3046
|
+
resolveClipPromise(state);
|
|
1547
3047
|
}
|
|
1548
|
-
if (
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
3048
|
+
if (options?.clearOutputs !== false) {
|
|
3049
|
+
clearClipOutputs(id);
|
|
3050
|
+
}
|
|
3051
|
+
updateLoopMode();
|
|
3052
|
+
},
|
|
3053
|
+
[clearClipOutputs, resolveClipPromise, updateLoopMode]
|
|
3054
|
+
);
|
|
3055
|
+
const refreshControllerStatus = useCallback(() => {
|
|
3056
|
+
const controllers = listControllers();
|
|
3057
|
+
reportStatus((prev) => ({
|
|
3058
|
+
...prev,
|
|
3059
|
+
controllers,
|
|
3060
|
+
outputPaths: Array.from(outputPathsRef.current)
|
|
3061
|
+
}));
|
|
3062
|
+
onRegisterControllers?.(controllers);
|
|
3063
|
+
}, [listControllers, onRegisterControllers, reportStatus]);
|
|
3064
|
+
const resolveProgramById = useCallback(
|
|
3065
|
+
(id) => {
|
|
3066
|
+
return resolvedProgramAssets.find((program) => program.id === id);
|
|
3067
|
+
},
|
|
3068
|
+
[resolvedProgramAssets]
|
|
3069
|
+
);
|
|
3070
|
+
const buildProgramRegistrationConfig = useCallback(
|
|
3071
|
+
(program) => {
|
|
3072
|
+
const graphSpec = resolveGraphSpec(
|
|
3073
|
+
program.graph,
|
|
3074
|
+
`${program.id ?? "program"} graph`
|
|
3075
|
+
);
|
|
3076
|
+
if (!graphSpec) {
|
|
3077
|
+
return null;
|
|
3078
|
+
}
|
|
3079
|
+
const outputs = collectOutputPaths(graphSpec);
|
|
3080
|
+
const inputs = collectInputPaths(graphSpec);
|
|
3081
|
+
const subs = program.graph.subscriptions ?? {
|
|
3082
|
+
inputs,
|
|
3083
|
+
outputs
|
|
3084
|
+
};
|
|
3085
|
+
return {
|
|
3086
|
+
id: namespaceControllerId(program.id, namespace, "graph"),
|
|
3087
|
+
spec: stripNulls(namespaceGraphSpec(graphSpec, namespace)),
|
|
3088
|
+
subs: namespaceSubscriptions(subs, namespace)
|
|
3089
|
+
};
|
|
3090
|
+
},
|
|
3091
|
+
[namespace]
|
|
3092
|
+
);
|
|
3093
|
+
const deriveProgramResetValues = useCallback(
|
|
3094
|
+
(program) => {
|
|
3095
|
+
if (program.resetValues) {
|
|
3096
|
+
return Object.entries(program.resetValues).filter(([, value]) => Number.isFinite(value)).map(([path, value]) => ({ path, value }));
|
|
3097
|
+
}
|
|
3098
|
+
const graphSpec = resolveGraphSpec(
|
|
3099
|
+
program.graph,
|
|
3100
|
+
`${program.id ?? "program"} graph (reset)`
|
|
3101
|
+
);
|
|
3102
|
+
if (!graphSpec) {
|
|
3103
|
+
return [];
|
|
3104
|
+
}
|
|
3105
|
+
return collectOutputPaths(graphSpec).filter((path) => path.trim().length > 0).map((path) => {
|
|
3106
|
+
const defaultValue = inputConstraintsRef.current[path]?.defaultValue ?? 0;
|
|
3107
|
+
return {
|
|
3108
|
+
path,
|
|
3109
|
+
value: Number.isFinite(defaultValue) && defaultValue != null ? defaultValue : 0
|
|
3110
|
+
};
|
|
3111
|
+
});
|
|
3112
|
+
},
|
|
3113
|
+
[]
|
|
3114
|
+
);
|
|
3115
|
+
const syncProgramPlaybackControllers = useCallback(() => {
|
|
3116
|
+
if (!ready) {
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
3119
|
+
const availableProgramIds = new Set(
|
|
3120
|
+
resolvedProgramAssets.map((program) => program.id)
|
|
3121
|
+
);
|
|
3122
|
+
Array.from(programPlaybackRef.current.keys()).forEach((id) => {
|
|
3123
|
+
if (availableProgramIds.has(id)) {
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
programPlaybackRef.current.delete(id);
|
|
3127
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
3128
|
+
if (controllerId) {
|
|
3129
|
+
try {
|
|
3130
|
+
removeGraph(controllerId);
|
|
3131
|
+
} catch (err) {
|
|
3132
|
+
pushError({
|
|
3133
|
+
message: `Failed to remove program ${id}`,
|
|
3134
|
+
cause: err,
|
|
3135
|
+
phase: "registration",
|
|
3136
|
+
timestamp: performance.now()
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
programControllerIdsRef.current.delete(id);
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
programPlaybackRef.current.forEach((state, id) => {
|
|
3143
|
+
const program = resolveProgramById(id);
|
|
3144
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
3145
|
+
if (!program) {
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (state.state !== "playing") {
|
|
3149
|
+
if (!controllerId) {
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
try {
|
|
3153
|
+
removeGraph(controllerId);
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
pushError({
|
|
3156
|
+
message: `Failed to pause program ${id}`,
|
|
3157
|
+
cause: err,
|
|
3158
|
+
phase: "registration",
|
|
3159
|
+
timestamp: performance.now()
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
programControllerIdsRef.current.delete(id);
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
if (controllerId) {
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
const config = buildProgramRegistrationConfig(program);
|
|
3169
|
+
if (!config) {
|
|
3170
|
+
pushError({
|
|
3171
|
+
message: `Program ${id} is missing a usable graph payload.`,
|
|
3172
|
+
phase: "registration",
|
|
3173
|
+
timestamp: performance.now()
|
|
3174
|
+
});
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
try {
|
|
3178
|
+
const nextControllerId = registerGraph(config);
|
|
3179
|
+
programControllerIdsRef.current.set(id, nextControllerId);
|
|
3180
|
+
} catch (err) {
|
|
3181
|
+
pushError({
|
|
3182
|
+
message: `Failed to register program ${id}`,
|
|
3183
|
+
cause: err,
|
|
3184
|
+
phase: "registration",
|
|
3185
|
+
timestamp: performance.now()
|
|
1554
3186
|
});
|
|
1555
3187
|
}
|
|
3188
|
+
});
|
|
3189
|
+
refreshControllerStatus();
|
|
3190
|
+
}, [
|
|
3191
|
+
buildProgramRegistrationConfig,
|
|
3192
|
+
pushError,
|
|
3193
|
+
ready,
|
|
3194
|
+
refreshControllerStatus,
|
|
3195
|
+
registerGraph,
|
|
3196
|
+
removeGraph,
|
|
3197
|
+
resolvedProgramAssets,
|
|
3198
|
+
resolveProgramById
|
|
3199
|
+
]);
|
|
3200
|
+
const playProgram = useCallback(
|
|
3201
|
+
(id) => {
|
|
3202
|
+
if (!resolveProgramById(id)) {
|
|
3203
|
+
throw new Error(
|
|
3204
|
+
`Program ${id} is not part of the current asset bundle.`
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
programPlaybackRef.current.set(id, {
|
|
3208
|
+
id,
|
|
3209
|
+
state: "playing"
|
|
3210
|
+
});
|
|
3211
|
+
syncProgramPlaybackControllers();
|
|
3212
|
+
markActivity();
|
|
1556
3213
|
},
|
|
1557
|
-
[
|
|
3214
|
+
[markActivity, resolveProgramById, syncProgramPlaybackControllers]
|
|
3215
|
+
);
|
|
3216
|
+
const pauseProgram = useCallback(
|
|
3217
|
+
(id) => {
|
|
3218
|
+
if (!resolveProgramById(id)) {
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
programPlaybackRef.current.set(id, {
|
|
3222
|
+
id,
|
|
3223
|
+
state: "paused"
|
|
3224
|
+
});
|
|
3225
|
+
syncProgramPlaybackControllers();
|
|
3226
|
+
updateLoopMode();
|
|
3227
|
+
},
|
|
3228
|
+
[resolveProgramById, syncProgramPlaybackControllers, updateLoopMode]
|
|
3229
|
+
);
|
|
3230
|
+
const stopProgram = useCallback(
|
|
3231
|
+
(id, options) => {
|
|
3232
|
+
const program = resolveProgramById(id);
|
|
3233
|
+
const controllerId = programControllerIdsRef.current.get(id);
|
|
3234
|
+
if (controllerId) {
|
|
3235
|
+
try {
|
|
3236
|
+
removeGraph(controllerId);
|
|
3237
|
+
} catch (err) {
|
|
3238
|
+
pushError({
|
|
3239
|
+
message: `Failed to stop program ${id}`,
|
|
3240
|
+
cause: err,
|
|
3241
|
+
phase: "registration",
|
|
3242
|
+
timestamp: performance.now()
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
programControllerIdsRef.current.delete(id);
|
|
3246
|
+
}
|
|
3247
|
+
programPlaybackRef.current.set(id, {
|
|
3248
|
+
id,
|
|
3249
|
+
state: "stopped"
|
|
3250
|
+
});
|
|
3251
|
+
if (program && options?.resetOutputs !== false) {
|
|
3252
|
+
deriveProgramResetValues(program).forEach(({ path, value }) => {
|
|
3253
|
+
setInput(path, { float: value });
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
refreshControllerStatus();
|
|
3257
|
+
updateLoopMode();
|
|
3258
|
+
},
|
|
3259
|
+
[
|
|
3260
|
+
deriveProgramResetValues,
|
|
3261
|
+
pushError,
|
|
3262
|
+
refreshControllerStatus,
|
|
3263
|
+
removeGraph,
|
|
3264
|
+
resolveProgramById,
|
|
3265
|
+
setInput,
|
|
3266
|
+
updateLoopMode
|
|
3267
|
+
]
|
|
3268
|
+
);
|
|
3269
|
+
const getProgramState = useCallback(
|
|
3270
|
+
(id) => {
|
|
3271
|
+
const state = programPlaybackRef.current.get(id);
|
|
3272
|
+
if (!state) {
|
|
3273
|
+
return null;
|
|
3274
|
+
}
|
|
3275
|
+
return { state: state.state };
|
|
3276
|
+
},
|
|
3277
|
+
[]
|
|
3278
|
+
);
|
|
3279
|
+
useEffect(() => {
|
|
3280
|
+
if (!ready || status.loading) {
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
syncProgramPlaybackControllers();
|
|
3284
|
+
}, [
|
|
3285
|
+
graphUpdateToken,
|
|
3286
|
+
ready,
|
|
3287
|
+
resolvedProgramAssets,
|
|
3288
|
+
status.loading,
|
|
3289
|
+
syncProgramPlaybackControllers
|
|
3290
|
+
]);
|
|
3291
|
+
const setAnimationActive = useCallback(
|
|
3292
|
+
(active) => {
|
|
3293
|
+
const next = Boolean(active);
|
|
3294
|
+
if (animationSystemActiveRef.current === next) {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
animationSystemActiveRef.current = next;
|
|
3298
|
+
if (!next) {
|
|
3299
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3300
|
+
state.playing = false;
|
|
3301
|
+
});
|
|
3302
|
+
}
|
|
3303
|
+
updateLoopMode();
|
|
3304
|
+
},
|
|
3305
|
+
[updateLoopMode]
|
|
3306
|
+
);
|
|
3307
|
+
const isAnimationActive = useCallback(
|
|
3308
|
+
() => animationSystemActiveRef.current,
|
|
3309
|
+
[]
|
|
1558
3310
|
);
|
|
1559
3311
|
const registerInputDriver = useCallback(
|
|
1560
3312
|
(id, factory) => {
|
|
@@ -1695,6 +3447,10 @@ function VizijRuntimeProviderInner({
|
|
|
1695
3447
|
return () => {
|
|
1696
3448
|
animationTweensRef.current.clear();
|
|
1697
3449
|
clipPlaybackRef.current.clear();
|
|
3450
|
+
programPlaybackRef.current.clear();
|
|
3451
|
+
programControllerIdsRef.current.clear();
|
|
3452
|
+
clipOutputValuesRef.current.clear();
|
|
3453
|
+
clipAggregateValuesRef.current.clear();
|
|
1698
3454
|
};
|
|
1699
3455
|
}, []);
|
|
1700
3456
|
useEffect(() => {
|
|
@@ -1710,18 +3466,61 @@ function VizijRuntimeProviderInner({
|
|
|
1710
3466
|
}, 500);
|
|
1711
3467
|
return () => window.clearInterval(id);
|
|
1712
3468
|
}, [reportStatus]);
|
|
3469
|
+
const setGraphBundle = useCallback(
|
|
3470
|
+
(bundle, options) => {
|
|
3471
|
+
const baseAssetBundle = latestEffectiveAssetBundleRef.current;
|
|
3472
|
+
const nextAssetBundle = applyRuntimeGraphBundle(baseAssetBundle, bundle);
|
|
3473
|
+
const plan = resolveRuntimeUpdatePlan(
|
|
3474
|
+
baseAssetBundle,
|
|
3475
|
+
nextAssetBundle,
|
|
3476
|
+
options?.tier ?? updateTierRef.current
|
|
3477
|
+
);
|
|
3478
|
+
pendingPlanRef.current = plan;
|
|
3479
|
+
previousBundleRef.current = nextAssetBundle;
|
|
3480
|
+
latestEffectiveAssetBundleRef.current = nextAssetBundle;
|
|
3481
|
+
suppressNextBundlePlanRef.current = true;
|
|
3482
|
+
setAssetBundleOverride(nextAssetBundle);
|
|
3483
|
+
if (plan.reregisterGraphs) {
|
|
3484
|
+
setGraphUpdateToken((prev) => prev + 1);
|
|
3485
|
+
}
|
|
3486
|
+
if (plan.reloadAssets) {
|
|
3487
|
+
reportStatus((prev) => ({
|
|
3488
|
+
...prev,
|
|
3489
|
+
loading: true,
|
|
3490
|
+
ready: false
|
|
3491
|
+
}));
|
|
3492
|
+
} else {
|
|
3493
|
+
reportStatus((prev) => ({
|
|
3494
|
+
...prev,
|
|
3495
|
+
loading: false
|
|
3496
|
+
}));
|
|
3497
|
+
}
|
|
3498
|
+
},
|
|
3499
|
+
[reportStatus]
|
|
3500
|
+
);
|
|
1713
3501
|
const contextValue = useMemo(
|
|
1714
3502
|
() => ({
|
|
1715
3503
|
...status,
|
|
1716
3504
|
assetBundle,
|
|
1717
3505
|
setInput,
|
|
3506
|
+
setGraphBundle,
|
|
1718
3507
|
setValue: setRendererValue,
|
|
1719
3508
|
stagePoseNeutral,
|
|
1720
3509
|
animateValue,
|
|
1721
3510
|
cancelAnimation,
|
|
1722
3511
|
registerInputDriver,
|
|
1723
3512
|
playAnimation,
|
|
3513
|
+
pauseAnimation,
|
|
3514
|
+
seekAnimation,
|
|
3515
|
+
setAnimationLoop,
|
|
3516
|
+
getAnimationState,
|
|
1724
3517
|
stopAnimation,
|
|
3518
|
+
playProgram,
|
|
3519
|
+
pauseProgram,
|
|
3520
|
+
stopProgram,
|
|
3521
|
+
getProgramState,
|
|
3522
|
+
setAnimationActive,
|
|
3523
|
+
isAnimationActive,
|
|
1725
3524
|
step,
|
|
1726
3525
|
advanceAnimations,
|
|
1727
3526
|
inputConstraints
|
|
@@ -1730,13 +3529,24 @@ function VizijRuntimeProviderInner({
|
|
|
1730
3529
|
status,
|
|
1731
3530
|
assetBundle,
|
|
1732
3531
|
setInput,
|
|
3532
|
+
setGraphBundle,
|
|
1733
3533
|
setRendererValue,
|
|
1734
3534
|
stagePoseNeutral,
|
|
1735
3535
|
animateValue,
|
|
1736
3536
|
cancelAnimation,
|
|
1737
3537
|
registerInputDriver,
|
|
1738
3538
|
playAnimation,
|
|
3539
|
+
pauseAnimation,
|
|
3540
|
+
seekAnimation,
|
|
3541
|
+
setAnimationLoop,
|
|
3542
|
+
getAnimationState,
|
|
1739
3543
|
stopAnimation,
|
|
3544
|
+
playProgram,
|
|
3545
|
+
pauseProgram,
|
|
3546
|
+
stopProgram,
|
|
3547
|
+
getProgramState,
|
|
3548
|
+
setAnimationActive,
|
|
3549
|
+
isAnimationActive,
|
|
1740
3550
|
step,
|
|
1741
3551
|
advanceAnimations,
|
|
1742
3552
|
inputConstraints
|
|
@@ -1782,17 +3592,23 @@ function VizijRuntimeFaceInner({
|
|
|
1782
3592
|
}
|
|
1783
3593
|
var VizijRuntimeFace = memo(VizijRuntimeFaceInner);
|
|
1784
3594
|
|
|
3595
|
+
// src/hooks/useOptionalVizijRuntime.ts
|
|
3596
|
+
import { useContext as useContext3 } from "react";
|
|
3597
|
+
function useOptionalVizijRuntime() {
|
|
3598
|
+
return useContext3(VizijRuntimeContext);
|
|
3599
|
+
}
|
|
3600
|
+
|
|
1785
3601
|
// src/hooks/useVizijOutputs.ts
|
|
1786
3602
|
import {
|
|
1787
3603
|
useVizijStore
|
|
1788
3604
|
} from "@vizij/render";
|
|
1789
|
-
import { getLookup } from "@vizij/utils";
|
|
3605
|
+
import { getLookup as getLookup2 } from "@vizij/utils";
|
|
1790
3606
|
function useVizijOutputs(paths) {
|
|
1791
3607
|
const { namespace } = useVizijRuntime();
|
|
1792
3608
|
return useVizijStore((state) => {
|
|
1793
3609
|
const result = {};
|
|
1794
3610
|
paths.forEach((path) => {
|
|
1795
|
-
const lookup =
|
|
3611
|
+
const lookup = getLookup2(namespace, path);
|
|
1796
3612
|
result[path] = state.values.get(lookup);
|
|
1797
3613
|
});
|
|
1798
3614
|
return result;
|
|
@@ -1804,11 +3620,11 @@ import { useCallback as useCallback2 } from "react";
|
|
|
1804
3620
|
import {
|
|
1805
3621
|
useVizijStore as useVizijStore2
|
|
1806
3622
|
} from "@vizij/render";
|
|
1807
|
-
import { getLookup as
|
|
3623
|
+
import { getLookup as getLookup3 } from "@vizij/utils";
|
|
1808
3624
|
function useRigInput(path) {
|
|
1809
3625
|
const { namespace, setInput } = useVizijRuntime();
|
|
1810
3626
|
const value = useVizijStore2((state) => {
|
|
1811
|
-
return state.values.get(
|
|
3627
|
+
return state.values.get(getLookup3(namespace, path));
|
|
1812
3628
|
});
|
|
1813
3629
|
const setter = useCallback2(
|
|
1814
3630
|
(next, shape) => {
|
|
@@ -1818,9 +3634,422 @@ function useRigInput(path) {
|
|
|
1818
3634
|
);
|
|
1819
3635
|
return [value, setter];
|
|
1820
3636
|
}
|
|
3637
|
+
|
|
3638
|
+
// src/utils/faceControls.ts
|
|
3639
|
+
var DEFAULT_GAZE_CONTROL = {
|
|
3640
|
+
min: -1,
|
|
3641
|
+
max: 1,
|
|
3642
|
+
defaultValue: 0
|
|
3643
|
+
};
|
|
3644
|
+
var DEFAULT_BLINK_CONTROL = {
|
|
3645
|
+
min: 0,
|
|
3646
|
+
max: 1,
|
|
3647
|
+
defaultValue: 0
|
|
3648
|
+
};
|
|
3649
|
+
var STANDARD_VIZIJ_EYE_PATHS = {
|
|
3650
|
+
leftX: "/standard/vizij/left_eye/pos/x",
|
|
3651
|
+
leftY: "/standard/vizij/left_eye/pos/y",
|
|
3652
|
+
rightX: "/standard/vizij/right_eye/pos/x",
|
|
3653
|
+
rightY: "/standard/vizij/right_eye/pos/y",
|
|
3654
|
+
leftUpper: "/standard/vizij/left_eye_top_eyelid/pos/y",
|
|
3655
|
+
rightUpper: "/standard/vizij/right_eye_top_eyelid/pos/y"
|
|
3656
|
+
};
|
|
3657
|
+
var LEGACY_STANDARD_EYE_PATHS = {
|
|
3658
|
+
leftX: "/standard/left_eye/pos/x",
|
|
3659
|
+
leftY: "/standard/left_eye/pos/y",
|
|
3660
|
+
rightX: "/standard/right_eye/pos/x",
|
|
3661
|
+
rightY: "/standard/right_eye/pos/y",
|
|
3662
|
+
leftUpper: "/standard/left_eye_top_eyelid/pos/y",
|
|
3663
|
+
rightUpper: "/standard/right_eye_top_eyelid/pos/y"
|
|
3664
|
+
};
|
|
3665
|
+
var PROPSRIG_EYE_PATHS = {
|
|
3666
|
+
leftX: "/propsrig/l_eye/translation/x",
|
|
3667
|
+
leftY: "/propsrig/l_eye/translation/y",
|
|
3668
|
+
rightX: "/propsrig/r_eye/translation/x",
|
|
3669
|
+
rightY: "/propsrig/r_eye/translation/y"
|
|
3670
|
+
};
|
|
3671
|
+
function clamp(value, min, max) {
|
|
3672
|
+
return Math.min(Math.max(value, min), max);
|
|
3673
|
+
}
|
|
3674
|
+
function normalizeInputPath(path) {
|
|
3675
|
+
const trimmed = path?.trim() ?? "";
|
|
3676
|
+
if (!trimmed) {
|
|
3677
|
+
return null;
|
|
3678
|
+
}
|
|
3679
|
+
if (trimmed.startsWith("/")) {
|
|
3680
|
+
return trimmed;
|
|
3681
|
+
}
|
|
3682
|
+
const rigMatch = trimmed.match(/^rig\/[^/]+(\/.*)$/);
|
|
3683
|
+
if (rigMatch?.[1]) {
|
|
3684
|
+
return rigMatch[1];
|
|
3685
|
+
}
|
|
3686
|
+
return `/${trimmed}`;
|
|
3687
|
+
}
|
|
3688
|
+
function buildControlsLookup(assetBundle) {
|
|
3689
|
+
const metadata = assetBundle.rig?.inputMetadata ?? [];
|
|
3690
|
+
const metadataByPath = /* @__PURE__ */ new Map();
|
|
3691
|
+
const pathSet = /* @__PURE__ */ new Set();
|
|
3692
|
+
metadata.forEach((entry) => {
|
|
3693
|
+
const normalized = normalizeInputPath(entry.path);
|
|
3694
|
+
if (!normalized) {
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3697
|
+
pathSet.add(normalized);
|
|
3698
|
+
if (!metadataByPath.has(normalized)) {
|
|
3699
|
+
metadataByPath.set(normalized, entry);
|
|
3700
|
+
}
|
|
3701
|
+
});
|
|
3702
|
+
return { pathSet, metadataByPath };
|
|
3703
|
+
}
|
|
3704
|
+
function resolveConstraint(absolutePath, relativePath, inputConstraints) {
|
|
3705
|
+
if (!inputConstraints) {
|
|
3706
|
+
return null;
|
|
3707
|
+
}
|
|
3708
|
+
const normalizedRelative = relativePath.replace(/^\//, "");
|
|
3709
|
+
const candidates = [
|
|
3710
|
+
absolutePath,
|
|
3711
|
+
relativePath,
|
|
3712
|
+
normalizedRelative,
|
|
3713
|
+
absolutePath.replace(/^rig\/[^/]+\//, "")
|
|
3714
|
+
];
|
|
3715
|
+
for (const candidate of candidates) {
|
|
3716
|
+
const constraint = inputConstraints[candidate];
|
|
3717
|
+
if (constraint) {
|
|
3718
|
+
return constraint;
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
return null;
|
|
3722
|
+
}
|
|
3723
|
+
function createControl(faceId, lookup, relativePath, inputConstraints, fallback) {
|
|
3724
|
+
const normalizedRelative = normalizeInputPath(relativePath);
|
|
3725
|
+
if (!normalizedRelative || !lookup.pathSet.has(normalizedRelative)) {
|
|
3726
|
+
return null;
|
|
3727
|
+
}
|
|
3728
|
+
const absolutePath = buildRigInputPath(faceId, normalizedRelative);
|
|
3729
|
+
const metadata = lookup.metadataByPath.get(normalizedRelative);
|
|
3730
|
+
const constraint = resolveConstraint(
|
|
3731
|
+
absolutePath,
|
|
3732
|
+
normalizedRelative,
|
|
3733
|
+
inputConstraints
|
|
3734
|
+
);
|
|
3735
|
+
const min = Number.isFinite(Number(constraint?.min ?? metadata?.range?.min)) ? Number(constraint?.min ?? metadata?.range?.min) : fallback.min;
|
|
3736
|
+
const max = Number.isFinite(Number(constraint?.max ?? metadata?.range?.max)) ? Number(constraint?.max ?? metadata?.range?.max) : fallback.max;
|
|
3737
|
+
const midpoint = min + (max - min) / 2;
|
|
3738
|
+
const defaultValue = Number.isFinite(
|
|
3739
|
+
Number(constraint?.defaultValue ?? metadata?.defaultValue)
|
|
3740
|
+
) ? Number(constraint?.defaultValue ?? metadata?.defaultValue) : Number.isFinite(midpoint) ? midpoint : fallback.defaultValue;
|
|
3741
|
+
return {
|
|
3742
|
+
path: absolutePath,
|
|
3743
|
+
min,
|
|
3744
|
+
max,
|
|
3745
|
+
defaultValue
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
function hasAll(controls, keys) {
|
|
3749
|
+
return keys.every((key) => Boolean(controls[key]));
|
|
3750
|
+
}
|
|
3751
|
+
function resolveFaceControls(assetBundle, runtimeFaceId, inputConstraints) {
|
|
3752
|
+
const faceId = assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? runtimeFaceId ?? "face";
|
|
3753
|
+
const lookup = buildControlsLookup(assetBundle);
|
|
3754
|
+
const standardVizijControls = {
|
|
3755
|
+
leftX: createControl(
|
|
3756
|
+
faceId,
|
|
3757
|
+
lookup,
|
|
3758
|
+
STANDARD_VIZIJ_EYE_PATHS.leftX,
|
|
3759
|
+
inputConstraints,
|
|
3760
|
+
DEFAULT_GAZE_CONTROL
|
|
3761
|
+
),
|
|
3762
|
+
leftY: createControl(
|
|
3763
|
+
faceId,
|
|
3764
|
+
lookup,
|
|
3765
|
+
STANDARD_VIZIJ_EYE_PATHS.leftY,
|
|
3766
|
+
inputConstraints,
|
|
3767
|
+
DEFAULT_GAZE_CONTROL
|
|
3768
|
+
),
|
|
3769
|
+
rightX: createControl(
|
|
3770
|
+
faceId,
|
|
3771
|
+
lookup,
|
|
3772
|
+
STANDARD_VIZIJ_EYE_PATHS.rightX,
|
|
3773
|
+
inputConstraints,
|
|
3774
|
+
DEFAULT_GAZE_CONTROL
|
|
3775
|
+
),
|
|
3776
|
+
rightY: createControl(
|
|
3777
|
+
faceId,
|
|
3778
|
+
lookup,
|
|
3779
|
+
STANDARD_VIZIJ_EYE_PATHS.rightY,
|
|
3780
|
+
inputConstraints,
|
|
3781
|
+
DEFAULT_GAZE_CONTROL
|
|
3782
|
+
),
|
|
3783
|
+
leftUpper: createControl(
|
|
3784
|
+
faceId,
|
|
3785
|
+
lookup,
|
|
3786
|
+
STANDARD_VIZIJ_EYE_PATHS.leftUpper,
|
|
3787
|
+
inputConstraints,
|
|
3788
|
+
DEFAULT_GAZE_CONTROL
|
|
3789
|
+
),
|
|
3790
|
+
rightUpper: createControl(
|
|
3791
|
+
faceId,
|
|
3792
|
+
lookup,
|
|
3793
|
+
STANDARD_VIZIJ_EYE_PATHS.rightUpper,
|
|
3794
|
+
inputConstraints,
|
|
3795
|
+
DEFAULT_GAZE_CONTROL
|
|
3796
|
+
)
|
|
3797
|
+
};
|
|
3798
|
+
const legacyStandardControls = {
|
|
3799
|
+
leftX: createControl(
|
|
3800
|
+
faceId,
|
|
3801
|
+
lookup,
|
|
3802
|
+
LEGACY_STANDARD_EYE_PATHS.leftX,
|
|
3803
|
+
inputConstraints,
|
|
3804
|
+
DEFAULT_GAZE_CONTROL
|
|
3805
|
+
),
|
|
3806
|
+
leftY: createControl(
|
|
3807
|
+
faceId,
|
|
3808
|
+
lookup,
|
|
3809
|
+
LEGACY_STANDARD_EYE_PATHS.leftY,
|
|
3810
|
+
inputConstraints,
|
|
3811
|
+
DEFAULT_GAZE_CONTROL
|
|
3812
|
+
),
|
|
3813
|
+
rightX: createControl(
|
|
3814
|
+
faceId,
|
|
3815
|
+
lookup,
|
|
3816
|
+
LEGACY_STANDARD_EYE_PATHS.rightX,
|
|
3817
|
+
inputConstraints,
|
|
3818
|
+
DEFAULT_GAZE_CONTROL
|
|
3819
|
+
),
|
|
3820
|
+
rightY: createControl(
|
|
3821
|
+
faceId,
|
|
3822
|
+
lookup,
|
|
3823
|
+
LEGACY_STANDARD_EYE_PATHS.rightY,
|
|
3824
|
+
inputConstraints,
|
|
3825
|
+
DEFAULT_GAZE_CONTROL
|
|
3826
|
+
),
|
|
3827
|
+
leftUpper: createControl(
|
|
3828
|
+
faceId,
|
|
3829
|
+
lookup,
|
|
3830
|
+
LEGACY_STANDARD_EYE_PATHS.leftUpper,
|
|
3831
|
+
inputConstraints,
|
|
3832
|
+
DEFAULT_GAZE_CONTROL
|
|
3833
|
+
),
|
|
3834
|
+
rightUpper: createControl(
|
|
3835
|
+
faceId,
|
|
3836
|
+
lookup,
|
|
3837
|
+
LEGACY_STANDARD_EYE_PATHS.rightUpper,
|
|
3838
|
+
inputConstraints,
|
|
3839
|
+
DEFAULT_GAZE_CONTROL
|
|
3840
|
+
)
|
|
3841
|
+
};
|
|
3842
|
+
const propsrigControls = {
|
|
3843
|
+
leftX: createControl(
|
|
3844
|
+
faceId,
|
|
3845
|
+
lookup,
|
|
3846
|
+
PROPSRIG_EYE_PATHS.leftX,
|
|
3847
|
+
inputConstraints,
|
|
3848
|
+
DEFAULT_GAZE_CONTROL
|
|
3849
|
+
),
|
|
3850
|
+
leftY: createControl(
|
|
3851
|
+
faceId,
|
|
3852
|
+
lookup,
|
|
3853
|
+
PROPSRIG_EYE_PATHS.leftY,
|
|
3854
|
+
inputConstraints,
|
|
3855
|
+
DEFAULT_GAZE_CONTROL
|
|
3856
|
+
),
|
|
3857
|
+
rightX: createControl(
|
|
3858
|
+
faceId,
|
|
3859
|
+
lookup,
|
|
3860
|
+
PROPSRIG_EYE_PATHS.rightX,
|
|
3861
|
+
inputConstraints,
|
|
3862
|
+
DEFAULT_GAZE_CONTROL
|
|
3863
|
+
),
|
|
3864
|
+
rightY: createControl(
|
|
3865
|
+
faceId,
|
|
3866
|
+
lookup,
|
|
3867
|
+
PROPSRIG_EYE_PATHS.rightY,
|
|
3868
|
+
inputConstraints,
|
|
3869
|
+
DEFAULT_GAZE_CONTROL
|
|
3870
|
+
)
|
|
3871
|
+
};
|
|
3872
|
+
const coupledGazeControls = {
|
|
3873
|
+
leftX: createControl(
|
|
3874
|
+
faceId,
|
|
3875
|
+
lookup,
|
|
3876
|
+
"/gaze/left_right",
|
|
3877
|
+
inputConstraints,
|
|
3878
|
+
DEFAULT_GAZE_CONTROL
|
|
3879
|
+
),
|
|
3880
|
+
leftY: createControl(
|
|
3881
|
+
faceId,
|
|
3882
|
+
lookup,
|
|
3883
|
+
"/gaze/up_down",
|
|
3884
|
+
inputConstraints,
|
|
3885
|
+
DEFAULT_GAZE_CONTROL
|
|
3886
|
+
),
|
|
3887
|
+
rightX: createControl(
|
|
3888
|
+
faceId,
|
|
3889
|
+
lookup,
|
|
3890
|
+
"/gaze/left_right_copy",
|
|
3891
|
+
inputConstraints,
|
|
3892
|
+
DEFAULT_GAZE_CONTROL
|
|
3893
|
+
) ?? createControl(
|
|
3894
|
+
faceId,
|
|
3895
|
+
lookup,
|
|
3896
|
+
"/gaze/left_right",
|
|
3897
|
+
inputConstraints,
|
|
3898
|
+
DEFAULT_GAZE_CONTROL
|
|
3899
|
+
),
|
|
3900
|
+
rightY: createControl(
|
|
3901
|
+
faceId,
|
|
3902
|
+
lookup,
|
|
3903
|
+
"/gaze/up_down_copy",
|
|
3904
|
+
inputConstraints,
|
|
3905
|
+
DEFAULT_GAZE_CONTROL
|
|
3906
|
+
) ?? createControl(
|
|
3907
|
+
faceId,
|
|
3908
|
+
lookup,
|
|
3909
|
+
"/gaze/up_down",
|
|
3910
|
+
inputConstraints,
|
|
3911
|
+
DEFAULT_GAZE_CONTROL
|
|
3912
|
+
)
|
|
3913
|
+
};
|
|
3914
|
+
const blinkFromLids = createControl(
|
|
3915
|
+
faceId,
|
|
3916
|
+
lookup,
|
|
3917
|
+
"/lids/blink",
|
|
3918
|
+
inputConstraints,
|
|
3919
|
+
DEFAULT_BLINK_CONTROL
|
|
3920
|
+
);
|
|
3921
|
+
const blinkDirect = createControl(
|
|
3922
|
+
faceId,
|
|
3923
|
+
lookup,
|
|
3924
|
+
"/blink",
|
|
3925
|
+
inputConstraints,
|
|
3926
|
+
DEFAULT_BLINK_CONTROL
|
|
3927
|
+
);
|
|
3928
|
+
if (hasAll(standardVizijControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3929
|
+
return {
|
|
3930
|
+
faceId,
|
|
3931
|
+
gazeSource: "standard-vizij",
|
|
3932
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
3933
|
+
eyes: {
|
|
3934
|
+
leftX: standardVizijControls.leftX,
|
|
3935
|
+
leftY: standardVizijControls.leftY,
|
|
3936
|
+
rightX: standardVizijControls.rightX,
|
|
3937
|
+
rightY: standardVizijControls.rightY
|
|
3938
|
+
},
|
|
3939
|
+
eyelids: {
|
|
3940
|
+
leftUpper: standardVizijControls.leftUpper,
|
|
3941
|
+
rightUpper: standardVizijControls.rightUpper
|
|
3942
|
+
},
|
|
3943
|
+
blink: blinkFromLids ?? blinkDirect
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
if (hasAll(legacyStandardControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3947
|
+
return {
|
|
3948
|
+
faceId,
|
|
3949
|
+
gazeSource: "standard",
|
|
3950
|
+
blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
|
|
3951
|
+
eyes: {
|
|
3952
|
+
leftX: legacyStandardControls.leftX,
|
|
3953
|
+
leftY: legacyStandardControls.leftY,
|
|
3954
|
+
rightX: legacyStandardControls.rightX,
|
|
3955
|
+
rightY: legacyStandardControls.rightY
|
|
3956
|
+
},
|
|
3957
|
+
eyelids: {
|
|
3958
|
+
leftUpper: legacyStandardControls.leftUpper,
|
|
3959
|
+
rightUpper: legacyStandardControls.rightUpper
|
|
3960
|
+
},
|
|
3961
|
+
blink: blinkDirect ?? blinkFromLids
|
|
3962
|
+
};
|
|
3963
|
+
}
|
|
3964
|
+
if (hasAll(propsrigControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3965
|
+
return {
|
|
3966
|
+
faceId,
|
|
3967
|
+
gazeSource: "propsrig",
|
|
3968
|
+
blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
|
|
3969
|
+
eyes: {
|
|
3970
|
+
leftX: propsrigControls.leftX,
|
|
3971
|
+
leftY: propsrigControls.leftY,
|
|
3972
|
+
rightX: propsrigControls.rightX,
|
|
3973
|
+
rightY: propsrigControls.rightY
|
|
3974
|
+
},
|
|
3975
|
+
eyelids: {
|
|
3976
|
+
leftUpper: null,
|
|
3977
|
+
rightUpper: null
|
|
3978
|
+
},
|
|
3979
|
+
blink: blinkDirect ?? blinkFromLids
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
if (hasAll(coupledGazeControls, ["leftX", "leftY", "rightX", "rightY"])) {
|
|
3983
|
+
return {
|
|
3984
|
+
faceId,
|
|
3985
|
+
gazeSource: "coupled-gaze",
|
|
3986
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
3987
|
+
eyes: {
|
|
3988
|
+
leftX: coupledGazeControls.leftX,
|
|
3989
|
+
leftY: coupledGazeControls.leftY,
|
|
3990
|
+
rightX: coupledGazeControls.rightX,
|
|
3991
|
+
rightY: coupledGazeControls.rightY
|
|
3992
|
+
},
|
|
3993
|
+
eyelids: {
|
|
3994
|
+
leftUpper: null,
|
|
3995
|
+
rightUpper: null
|
|
3996
|
+
},
|
|
3997
|
+
blink: blinkFromLids ?? blinkDirect
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
4000
|
+
return {
|
|
4001
|
+
faceId,
|
|
4002
|
+
gazeSource: "none",
|
|
4003
|
+
blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
|
|
4004
|
+
eyes: {
|
|
4005
|
+
leftX: null,
|
|
4006
|
+
leftY: null,
|
|
4007
|
+
rightX: null,
|
|
4008
|
+
rightY: null
|
|
4009
|
+
},
|
|
4010
|
+
eyelids: {
|
|
4011
|
+
leftUpper: null,
|
|
4012
|
+
rightUpper: null
|
|
4013
|
+
},
|
|
4014
|
+
blink: blinkFromLids ?? blinkDirect
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
function mapNormalizedControlValue(control, normalizedValue) {
|
|
4018
|
+
const value = clamp(normalizedValue, -1, 1);
|
|
4019
|
+
if (value === 0) {
|
|
4020
|
+
return control.defaultValue;
|
|
4021
|
+
}
|
|
4022
|
+
if (value > 0) {
|
|
4023
|
+
return control.defaultValue + (control.max - control.defaultValue) * value;
|
|
4024
|
+
}
|
|
4025
|
+
return control.defaultValue + (control.defaultValue - control.min) * value;
|
|
4026
|
+
}
|
|
4027
|
+
function mapUnitControlValue(control, unitValue) {
|
|
4028
|
+
const value = clamp(unitValue, 0, 1);
|
|
4029
|
+
return control.defaultValue + (control.max - control.defaultValue) * value;
|
|
4030
|
+
}
|
|
1821
4031
|
export {
|
|
4032
|
+
EMOTION_POSE_KEYS,
|
|
4033
|
+
EXPRESSIVE_EMOTION_POSE_KEYS,
|
|
4034
|
+
POSE_WEIGHT_INPUT_PATH_PREFIX,
|
|
4035
|
+
VISEME_POSE_KEYS,
|
|
1822
4036
|
VizijRuntimeFace,
|
|
1823
4037
|
VizijRuntimeProvider,
|
|
4038
|
+
buildPoseWeightInputPathSegment,
|
|
4039
|
+
buildPoseWeightPathMap,
|
|
4040
|
+
buildPoseWeightRelativePath,
|
|
4041
|
+
buildRigInputPath,
|
|
4042
|
+
buildSemanticPoseWeightPathMap,
|
|
4043
|
+
filterPosesBySemanticKind,
|
|
4044
|
+
getPoseSemanticKey,
|
|
4045
|
+
mapNormalizedControlValue,
|
|
4046
|
+
mapUnitControlValue,
|
|
4047
|
+
normalizePoseSemanticKey,
|
|
4048
|
+
resolveFaceControls,
|
|
4049
|
+
resolvePoseMembership,
|
|
4050
|
+
resolvePoseSemantics,
|
|
4051
|
+
resolveRuntimeUpdatePlan,
|
|
4052
|
+
useOptionalVizijRuntime,
|
|
1824
4053
|
useRigInput,
|
|
1825
4054
|
useVizijOutputs,
|
|
1826
4055
|
useVizijRuntime
|