@vizij/runtime-react 0.0.14 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/dist/index.cjs +4077 -0
- package/dist/index.d.cts +342 -0
- package/dist/index.d.ts +173 -9
- package/dist/index.js +3035 -346
- package/package.json +15 -11
- package/dist/index.d.mts +0 -178
- package/dist/index.mjs +0 -1363
package/dist/index.js
CHANGED
|
@@ -1,42 +1,162 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
VizijRuntimeFace: () => VizijRuntimeFace,
|
|
24
|
-
VizijRuntimeProvider: () => VizijRuntimeProvider,
|
|
25
|
-
useRigInput: () => useRigInput,
|
|
26
|
-
useVizijOutputs: () => useVizijOutputs,
|
|
27
|
-
useVizijRuntime: () => useVizijRuntime
|
|
28
|
-
});
|
|
29
|
-
module.exports = __toCommonJS(index_exports);
|
|
30
|
-
|
|
31
1
|
// src/VizijRuntimeProvider.tsx
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
VizijContext,
|
|
12
|
+
createVizijStore,
|
|
13
|
+
loadGLTFWithBundle,
|
|
14
|
+
loadGLTFFromBlobWithBundle
|
|
15
|
+
} from "@vizij/render";
|
|
16
|
+
import {
|
|
17
|
+
OrchestratorProvider,
|
|
18
|
+
OrchestratorContext,
|
|
19
|
+
useOrchestrator,
|
|
20
|
+
useOrchFrame
|
|
21
|
+
} from "@vizij/orchestrator-react";
|
|
22
|
+
import { compileIrGraph } from "@vizij/node-graph-authoring";
|
|
23
|
+
import { valueAsNumber as valueAsNumber2 } from "@vizij/value-json";
|
|
24
|
+
import { getLookup } from "@vizij/utils";
|
|
36
25
|
|
|
37
26
|
// src/context.ts
|
|
38
|
-
|
|
39
|
-
var VizijRuntimeContext =
|
|
27
|
+
import { createContext } from "react";
|
|
28
|
+
var VizijRuntimeContext = createContext(null);
|
|
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
|
+
}
|
|
40
160
|
|
|
41
161
|
// src/utils/graph.ts
|
|
42
162
|
function getNodes(spec) {
|
|
@@ -82,6 +202,12 @@ function collectInputPaths(spec) {
|
|
|
82
202
|
}
|
|
83
203
|
function collectInputPathMap(spec) {
|
|
84
204
|
const map = {};
|
|
205
|
+
const addVariant = (key, path, force = false) => {
|
|
206
|
+
if (!key || !force && map[key]) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
map[key] = path;
|
|
210
|
+
};
|
|
85
211
|
const nodes = getNodes(spec);
|
|
86
212
|
nodes.forEach((node) => {
|
|
87
213
|
if (String(node.type ?? "").toLowerCase() !== "input") {
|
|
@@ -93,13 +219,663 @@ function collectInputPathMap(spec) {
|
|
|
93
219
|
}
|
|
94
220
|
const id = String(node.id ?? "");
|
|
95
221
|
const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
|
|
96
|
-
|
|
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);
|
|
97
555
|
});
|
|
98
556
|
return map;
|
|
99
557
|
}
|
|
100
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
|
+
|
|
101
869
|
// src/utils/valueConversion.ts
|
|
102
|
-
|
|
870
|
+
import {
|
|
871
|
+
isNormalizedValue,
|
|
872
|
+
valueAsBool,
|
|
873
|
+
valueAsColorRgba,
|
|
874
|
+
valueAsNumber,
|
|
875
|
+
valueAsText,
|
|
876
|
+
valueAsTransform,
|
|
877
|
+
valueAsVector
|
|
878
|
+
} from "@vizij/value-json";
|
|
103
879
|
function numericArrayToRaw(arr) {
|
|
104
880
|
const normalised = arr.map((entry) => Number(entry ?? 0));
|
|
105
881
|
switch (normalised.length) {
|
|
@@ -151,20 +927,20 @@ function valueJSONToRaw(value) {
|
|
|
151
927
|
]);
|
|
152
928
|
return Object.fromEntries(entries);
|
|
153
929
|
}
|
|
154
|
-
if (!
|
|
930
|
+
if (!isNormalizedValue(value)) {
|
|
155
931
|
return void 0;
|
|
156
932
|
}
|
|
157
933
|
switch (value.type) {
|
|
158
934
|
case "float": {
|
|
159
|
-
const num =
|
|
935
|
+
const num = valueAsNumber(value);
|
|
160
936
|
return typeof num === "number" ? num : void 0;
|
|
161
937
|
}
|
|
162
938
|
case "bool": {
|
|
163
|
-
const boolVal =
|
|
939
|
+
const boolVal = valueAsBool(value);
|
|
164
940
|
return typeof boolVal === "boolean" ? boolVal : void 0;
|
|
165
941
|
}
|
|
166
942
|
case "text": {
|
|
167
|
-
const text =
|
|
943
|
+
const text = valueAsText(value);
|
|
168
944
|
return typeof text === "string" ? text : void 0;
|
|
169
945
|
}
|
|
170
946
|
case "vec2":
|
|
@@ -172,11 +948,11 @@ function valueJSONToRaw(value) {
|
|
|
172
948
|
case "vec4":
|
|
173
949
|
case "quat":
|
|
174
950
|
case "vector": {
|
|
175
|
-
const vec =
|
|
951
|
+
const vec = valueAsVector(value);
|
|
176
952
|
return vec ? numericArrayToRaw(vec) : void 0;
|
|
177
953
|
}
|
|
178
954
|
case "colorrgba": {
|
|
179
|
-
const color =
|
|
955
|
+
const color = valueAsColorRgba(value);
|
|
180
956
|
if (!color) {
|
|
181
957
|
return void 0;
|
|
182
958
|
}
|
|
@@ -184,7 +960,7 @@ function valueJSONToRaw(value) {
|
|
|
184
960
|
return { r, g, b, a };
|
|
185
961
|
}
|
|
186
962
|
case "transform": {
|
|
187
|
-
const transform =
|
|
963
|
+
const transform = valueAsTransform(value);
|
|
188
964
|
if (!transform) {
|
|
189
965
|
return void 0;
|
|
190
966
|
}
|
|
@@ -214,12 +990,28 @@ function valueJSONToRaw(value) {
|
|
|
214
990
|
}
|
|
215
991
|
|
|
216
992
|
// src/VizijRuntimeProvider.tsx
|
|
217
|
-
|
|
993
|
+
import { jsx } from "react/jsx-runtime";
|
|
994
|
+
var ACTIVE_GRACE_MS = 250;
|
|
995
|
+
var VISIBLE_IDLE_FPS = 30;
|
|
996
|
+
var HIDDEN_IDLE_FPS = 1;
|
|
218
997
|
var DEFAULT_MERGE = {
|
|
219
998
|
outputs: "add",
|
|
220
999
|
intermediate: "add"
|
|
221
1000
|
};
|
|
222
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
|
+
}
|
|
223
1015
|
var EASINGS = {
|
|
224
1016
|
linear: (t) => t,
|
|
225
1017
|
easeIn: (t) => t * t,
|
|
@@ -236,12 +1028,19 @@ function resolveEasing(easing) {
|
|
|
236
1028
|
return EASINGS.linear;
|
|
237
1029
|
}
|
|
238
1030
|
function findRootId(world) {
|
|
1031
|
+
let fallback = null;
|
|
239
1032
|
for (const entry of Object.values(world)) {
|
|
240
|
-
if (entry
|
|
1033
|
+
if (!entry || typeof entry !== "object" || entry.type !== "group") {
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (entry.rootBounds && entry.id) {
|
|
241
1037
|
return entry.id;
|
|
242
1038
|
}
|
|
1039
|
+
if (!fallback && entry.id) {
|
|
1040
|
+
fallback = entry.id;
|
|
1041
|
+
}
|
|
243
1042
|
}
|
|
244
|
-
return
|
|
1043
|
+
return fallback;
|
|
245
1044
|
}
|
|
246
1045
|
function normalisePath(path) {
|
|
247
1046
|
if (!path) {
|
|
@@ -252,34 +1051,295 @@ function normalisePath(path) {
|
|
|
252
1051
|
function normaliseBundleKind(kind) {
|
|
253
1052
|
return typeof kind === "string" ? kind.toLowerCase() : "";
|
|
254
1053
|
}
|
|
255
|
-
function
|
|
256
|
-
if (!
|
|
257
|
-
|
|
1054
|
+
function addConstraintVariant(map, key, constraint) {
|
|
1055
|
+
if (!key) return;
|
|
1056
|
+
if (!map[key]) {
|
|
1057
|
+
map[key] = constraint;
|
|
258
1058
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (preferred.includes(kind)) {
|
|
266
|
-
return entry;
|
|
267
|
-
}
|
|
1059
|
+
}
|
|
1060
|
+
function stripRigFacePrefix(path) {
|
|
1061
|
+
const trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
1062
|
+
const match = /^rig\/[^/]+\/(.+)$/.exec(trimmed);
|
|
1063
|
+
if (match && match[1]) {
|
|
1064
|
+
return match[1];
|
|
268
1065
|
}
|
|
269
|
-
if (
|
|
270
|
-
return
|
|
1066
|
+
if (trimmed.startsWith("rig/")) {
|
|
1067
|
+
return trimmed.slice("rig/".length);
|
|
271
1068
|
}
|
|
272
|
-
return
|
|
1069
|
+
return trimmed;
|
|
273
1070
|
}
|
|
274
|
-
function
|
|
275
|
-
if (!
|
|
276
|
-
return
|
|
1071
|
+
function extractInputConstraints(spec, extraInputs, namespace) {
|
|
1072
|
+
if (!spec || typeof spec !== "object") {
|
|
1073
|
+
return {};
|
|
277
1074
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
1075
|
+
const inputs = [];
|
|
1076
|
+
if (Array.isArray(extraInputs)) {
|
|
1077
|
+
inputs.push(...extraInputs);
|
|
1078
|
+
}
|
|
1079
|
+
const entries = spec.metadata?.vizij?.inputs;
|
|
1080
|
+
if (Array.isArray(entries)) {
|
|
1081
|
+
entries.forEach((entry) => {
|
|
1082
|
+
if (entry && typeof entry === "object") {
|
|
1083
|
+
inputs.push(entry);
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (inputs.length === 0) {
|
|
1088
|
+
return {};
|
|
1089
|
+
}
|
|
1090
|
+
const map = {};
|
|
1091
|
+
inputs.forEach((entry) => {
|
|
1092
|
+
const path = entry.path;
|
|
1093
|
+
if (typeof path !== "string") return;
|
|
1094
|
+
const namespaced = namespaceTypedPath(path, namespace);
|
|
1095
|
+
const stripped = stripRigFacePrefix(path);
|
|
1096
|
+
const strippedNamespaced = stripped ? namespaceTypedPath(stripped, namespace) : stripped;
|
|
1097
|
+
const min = entry.range?.min;
|
|
1098
|
+
const max = entry.range?.max;
|
|
1099
|
+
const defaultValue = entry.defaultValue;
|
|
1100
|
+
const constraint = {
|
|
1101
|
+
...Number.isFinite(Number(min)) ? { min: Number(min) } : {},
|
|
1102
|
+
...Number.isFinite(Number(max)) ? { max: Number(max) } : {},
|
|
1103
|
+
...Number.isFinite(Number(defaultValue)) ? { defaultValue: Number(defaultValue) } : {}
|
|
1104
|
+
};
|
|
1105
|
+
addConstraintVariant(map, namespaced, constraint);
|
|
1106
|
+
addConstraintVariant(map, path, constraint);
|
|
1107
|
+
if (stripped) {
|
|
1108
|
+
addConstraintVariant(map, stripped, constraint);
|
|
1109
|
+
}
|
|
1110
|
+
if (strippedNamespaced) {
|
|
1111
|
+
addConstraintVariant(map, strippedNamespaced, constraint);
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
return map;
|
|
1115
|
+
}
|
|
1116
|
+
function namespaceTypedPath(path, namespace) {
|
|
1117
|
+
const trimmed = typeof path === "string" ? path.trim() : "";
|
|
1118
|
+
if (!trimmed) {
|
|
1119
|
+
return trimmed;
|
|
1120
|
+
}
|
|
1121
|
+
const prefix = `${namespace}/`;
|
|
1122
|
+
if (trimmed.startsWith(prefix)) {
|
|
1123
|
+
return trimmed;
|
|
1124
|
+
}
|
|
1125
|
+
if (trimmed.startsWith("debug/")) {
|
|
1126
|
+
const rest = trimmed.slice("debug/".length);
|
|
1127
|
+
const namespacedRest = namespaceTypedPath(rest, namespace);
|
|
1128
|
+
return namespacedRest.startsWith("debug/") ? namespacedRest : `debug/${namespacedRest}`;
|
|
1129
|
+
}
|
|
1130
|
+
return `${prefix}${trimmed}`;
|
|
1131
|
+
}
|
|
1132
|
+
function stripNamespace(path, namespace) {
|
|
1133
|
+
const prefix = `${namespace}/`;
|
|
1134
|
+
if (path.startsWith(prefix)) {
|
|
1135
|
+
return path.slice(prefix.length);
|
|
1136
|
+
}
|
|
1137
|
+
const debugPrefix = `debug/${prefix}`;
|
|
1138
|
+
if (path.startsWith(debugPrefix)) {
|
|
1139
|
+
return path.slice(debugPrefix.length);
|
|
1140
|
+
}
|
|
1141
|
+
if (path.startsWith("debug/")) {
|
|
1142
|
+
return path.slice("debug/".length);
|
|
1143
|
+
}
|
|
1144
|
+
return path;
|
|
1145
|
+
}
|
|
1146
|
+
function namespaceControllerId(id, namespace, kind = "graph") {
|
|
1147
|
+
if (!id) {
|
|
1148
|
+
return void 0;
|
|
1149
|
+
}
|
|
1150
|
+
const trimmed = id.trim();
|
|
1151
|
+
if (!trimmed) {
|
|
1152
|
+
return void 0;
|
|
1153
|
+
}
|
|
1154
|
+
const prefix = `${namespace}/${kind}/`;
|
|
1155
|
+
if (trimmed.startsWith(prefix)) {
|
|
1156
|
+
return trimmed;
|
|
1157
|
+
}
|
|
1158
|
+
return `${prefix}${trimmed}`;
|
|
1159
|
+
}
|
|
1160
|
+
function namespaceSubscriptions(subs, namespace) {
|
|
1161
|
+
if (!subs) {
|
|
1162
|
+
return void 0;
|
|
1163
|
+
}
|
|
1164
|
+
const inputs = Array.isArray(subs.inputs) ? subs.inputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
|
|
1165
|
+
const outputs = Array.isArray(subs.outputs) ? subs.outputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
|
|
1166
|
+
if (!inputs && !outputs) {
|
|
1167
|
+
return subs;
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
...subs,
|
|
1171
|
+
...inputs ? { inputs } : {},
|
|
1172
|
+
...outputs ? { outputs } : {}
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function namespaceGraphSpec(spec, namespace) {
|
|
1176
|
+
if (!spec || typeof spec !== "object") {
|
|
1177
|
+
return spec;
|
|
1178
|
+
}
|
|
1179
|
+
const nodes = spec.nodes;
|
|
1180
|
+
if (!Array.isArray(nodes)) {
|
|
1181
|
+
return spec;
|
|
1182
|
+
}
|
|
1183
|
+
let changed = false;
|
|
1184
|
+
const nextNodes = nodes.map((node) => {
|
|
1185
|
+
if (!node || typeof node !== "object") {
|
|
1186
|
+
return node;
|
|
1187
|
+
}
|
|
1188
|
+
const path = node.params?.path;
|
|
1189
|
+
if (typeof path !== "string") {
|
|
1190
|
+
return node;
|
|
1191
|
+
}
|
|
1192
|
+
const namespacedPath = namespaceTypedPath(path, namespace);
|
|
1193
|
+
if (namespacedPath === path) {
|
|
1194
|
+
return node;
|
|
1195
|
+
}
|
|
1196
|
+
changed = true;
|
|
1197
|
+
return {
|
|
1198
|
+
...node,
|
|
1199
|
+
params: {
|
|
1200
|
+
...node.params ?? {},
|
|
1201
|
+
path: namespacedPath
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
});
|
|
1205
|
+
if (!changed) {
|
|
1206
|
+
return spec;
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
...spec,
|
|
1210
|
+
nodes: nextNodes
|
|
1211
|
+
};
|
|
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
|
+
}
|
|
1237
|
+
var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1238
|
+
function pickBundleGraph(bundle, preferredKinds) {
|
|
1239
|
+
if (!bundle?.graphs || bundle.graphs.length === 0) {
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
const preferred = preferredKinds.map((kind) => kind.toLowerCase());
|
|
1243
|
+
for (const entry of bundle.graphs) {
|
|
1244
|
+
if (!entry || typeof entry !== "object") {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const kind = normaliseBundleKind(entry.kind);
|
|
1248
|
+
if (preferred.includes(kind)) {
|
|
1249
|
+
return entry;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (bundle.graphs.length === 1) {
|
|
1253
|
+
return bundle.graphs[0] ?? null;
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
function extractIrGraph(payload) {
|
|
1258
|
+
if (!payload || typeof payload !== "object") {
|
|
1259
|
+
return void 0;
|
|
1260
|
+
}
|
|
1261
|
+
return payload;
|
|
1262
|
+
}
|
|
1263
|
+
function convertBundleGraph(entry) {
|
|
1264
|
+
if (!entry || !entry.id) {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
const rawSpec = entry.spec;
|
|
1268
|
+
const inputMetadata = extractVizijInputMetadata(
|
|
1269
|
+
rawSpec
|
|
1270
|
+
);
|
|
1271
|
+
const spec = rawSpec ? stripVizijMetadata(rawSpec) : void 0;
|
|
1272
|
+
const ir = extractIrGraph(entry.ir);
|
|
1273
|
+
if (!spec && !ir) {
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
return {
|
|
1277
|
+
id: entry.id,
|
|
1278
|
+
spec,
|
|
1279
|
+
ir: ir ?? null,
|
|
1280
|
+
inputMetadata
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
function resolveGraphSpec(asset, context) {
|
|
1284
|
+
if (asset.spec) {
|
|
1285
|
+
return stripVizijMetadata(asset.spec);
|
|
1286
|
+
}
|
|
1287
|
+
if (asset.ir) {
|
|
1288
|
+
try {
|
|
1289
|
+
const compiled = compileIrGraph(asset.ir, { preferLegacySpec: false });
|
|
1290
|
+
if (compiled.issues && compiled.issues.length > 0) {
|
|
1291
|
+
console.warn(
|
|
1292
|
+
`[vizij-runtime] IR compile for graph "${context}" reported issues`,
|
|
1293
|
+
compiled.issues
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
return stripVizijMetadata(compiled.spec);
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
console.warn(
|
|
1299
|
+
`[vizij-runtime] Failed to compile IR graph "${context}"`,
|
|
1300
|
+
error
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
function stripVizijMetadata(spec) {
|
|
1307
|
+
if (!spec || typeof spec !== "object") {
|
|
1308
|
+
return spec;
|
|
1309
|
+
}
|
|
1310
|
+
const cloned = {
|
|
1311
|
+
...spec,
|
|
1312
|
+
nodes: spec.nodes ? spec.nodes.map((node) => ({ ...node })) : spec.nodes,
|
|
1313
|
+
edges: spec.edges ? spec.edges.map((edge) => ({ ...edge })) : spec.edges
|
|
1314
|
+
};
|
|
1315
|
+
if (cloned.metadata && typeof cloned.metadata === "object") {
|
|
1316
|
+
const metadata = { ...cloned.metadata };
|
|
1317
|
+
if ("vizij" in metadata) {
|
|
1318
|
+
delete metadata.vizij;
|
|
1319
|
+
}
|
|
1320
|
+
if (Object.keys(metadata).length === 0) {
|
|
1321
|
+
delete cloned.metadata;
|
|
1322
|
+
} else {
|
|
1323
|
+
cloned.metadata = metadata;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return cloned;
|
|
1327
|
+
}
|
|
1328
|
+
function extractVizijInputMetadata(spec) {
|
|
1329
|
+
if (!spec || typeof spec !== "object") {
|
|
1330
|
+
return [];
|
|
1331
|
+
}
|
|
1332
|
+
const inputs = spec.metadata?.vizij?.inputs;
|
|
1333
|
+
if (!Array.isArray(inputs)) {
|
|
1334
|
+
return [];
|
|
1335
|
+
}
|
|
1336
|
+
return inputs.map((entry) => {
|
|
1337
|
+
if (!entry || typeof entry !== "object") {
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
return entry;
|
|
1341
|
+
}).filter(Boolean);
|
|
1342
|
+
}
|
|
283
1343
|
function convertBundleAnimations(entries) {
|
|
284
1344
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
285
1345
|
return [];
|
|
@@ -344,26 +1404,49 @@ function convertExtractedAnimations(clips) {
|
|
|
344
1404
|
}
|
|
345
1405
|
const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
|
|
346
1406
|
const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
|
|
347
|
-
|
|
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) {
|
|
348
1413
|
return;
|
|
349
1414
|
}
|
|
350
1415
|
const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
|
|
351
1416
|
const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
|
|
352
1417
|
const keyframes = [];
|
|
353
1418
|
times.forEach((time, index) => {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
1419
|
+
const flatBase = index * valueSize + componentIndex;
|
|
1420
|
+
const valueBase = hasTripletTangents ? index * valueSize * 3 + valueSize + componentIndex : flatBase;
|
|
1421
|
+
const value = values[valueBase];
|
|
356
1422
|
if (!Number.isFinite(value)) {
|
|
357
1423
|
return;
|
|
358
1424
|
}
|
|
359
|
-
|
|
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);
|
|
360
1442
|
});
|
|
361
1443
|
if (keyframes.length === 0) {
|
|
362
1444
|
return;
|
|
363
1445
|
}
|
|
364
1446
|
convertedTracks.push({
|
|
365
1447
|
channel: channelId,
|
|
366
|
-
keyframes
|
|
1448
|
+
keyframes,
|
|
1449
|
+
interpolation
|
|
367
1450
|
});
|
|
368
1451
|
});
|
|
369
1452
|
if (convertedTracks.length === 0) {
|
|
@@ -430,6 +1513,62 @@ function mergeAnimationLists(explicit, fromBundle) {
|
|
|
430
1513
|
}
|
|
431
1514
|
return changed ? merged : explicit;
|
|
432
1515
|
}
|
|
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))
|
|
1522
|
+
);
|
|
1523
|
+
if (entries.length === 0) {
|
|
1524
|
+
return void 0;
|
|
1525
|
+
}
|
|
1526
|
+
return Object.fromEntries(
|
|
1527
|
+
entries.map(([path, rawValue]) => [path, Number(rawValue)])
|
|
1528
|
+
);
|
|
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;
|
|
1538
|
+
}
|
|
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
|
+
}
|
|
433
1572
|
function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
434
1573
|
const resolvedBundle = base.bundle ?? extracted ?? null;
|
|
435
1574
|
const rigFromBundle = convertBundleGraph(
|
|
@@ -437,12 +1576,18 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
437
1576
|
);
|
|
438
1577
|
const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
|
|
439
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
|
+
);
|
|
440
1585
|
const poseStageFilter = basePose?.stageNeutralFilter;
|
|
441
|
-
const poseGraphFromBundle =
|
|
1586
|
+
const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
442
1587
|
pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
|
|
443
1588
|
);
|
|
444
|
-
const resolvedPoseGraph = basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
445
|
-
const resolvedPoseConfig = basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
1589
|
+
const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
1590
|
+
const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
446
1591
|
let resolvedPose = basePose;
|
|
447
1592
|
if (basePose) {
|
|
448
1593
|
const nextPose = { ...basePose };
|
|
@@ -481,6 +1626,10 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
481
1626
|
animationsFromAsset
|
|
482
1627
|
);
|
|
483
1628
|
}
|
|
1629
|
+
const programsFromBundle = mergeProgramLists(
|
|
1630
|
+
base.programs,
|
|
1631
|
+
convertBundlePrograms(resolvedBundle?.graphs)
|
|
1632
|
+
);
|
|
484
1633
|
const merged = {
|
|
485
1634
|
...base
|
|
486
1635
|
};
|
|
@@ -491,88 +1640,236 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
491
1640
|
}
|
|
492
1641
|
merged.pose = resolvedPose;
|
|
493
1642
|
merged.animations = resolvedAnimations;
|
|
1643
|
+
merged.programs = programsFromBundle;
|
|
494
1644
|
merged.bundle = resolvedBundle;
|
|
495
1645
|
return merged;
|
|
496
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
|
+
}
|
|
497
1737
|
function VizijRuntimeProvider({
|
|
498
1738
|
assetBundle,
|
|
499
1739
|
children,
|
|
500
1740
|
namespace: namespaceProp,
|
|
501
1741
|
faceId: faceIdProp,
|
|
1742
|
+
updateTier = "auto",
|
|
502
1743
|
autoCreate = true,
|
|
503
1744
|
createOptions,
|
|
504
1745
|
autostart = false,
|
|
1746
|
+
driveOrchestrator = true,
|
|
505
1747
|
mergeStrategy,
|
|
506
1748
|
onRegisterControllers,
|
|
507
|
-
onStatusChange
|
|
1749
|
+
onStatusChange,
|
|
1750
|
+
transformOutputWrite,
|
|
1751
|
+
orchestratorScope = "auto"
|
|
508
1752
|
}) {
|
|
509
|
-
const storeRef =
|
|
1753
|
+
const storeRef = useRef(null);
|
|
510
1754
|
if (!storeRef.current) {
|
|
511
|
-
storeRef.current =
|
|
1755
|
+
storeRef.current = createVizijStore();
|
|
512
1756
|
}
|
|
513
|
-
|
|
514
|
-
|
|
1757
|
+
const parentOrchestrator = useContext(OrchestratorContext);
|
|
1758
|
+
const hasParentOrchestrator = Boolean(parentOrchestrator);
|
|
1759
|
+
const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
|
|
1760
|
+
if (orchestratorScope === "shared" && !hasParentOrchestrator) {
|
|
1761
|
+
console.warn(
|
|
1762
|
+
'[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
const runtimeTree = /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
|
|
1766
|
+
VizijRuntimeProviderInner,
|
|
515
1767
|
{
|
|
1768
|
+
assetBundle,
|
|
1769
|
+
namespace: namespaceProp,
|
|
1770
|
+
faceId: faceIdProp,
|
|
1771
|
+
updateTier,
|
|
516
1772
|
autoCreate,
|
|
517
|
-
createOptions,
|
|
518
1773
|
autostart,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
onRegisterControllers,
|
|
528
|
-
onStatusChange,
|
|
529
|
-
store: storeRef.current,
|
|
530
|
-
children
|
|
531
|
-
}
|
|
532
|
-
)
|
|
1774
|
+
driveOrchestrator,
|
|
1775
|
+
createOptions,
|
|
1776
|
+
mergeStrategy,
|
|
1777
|
+
onRegisterControllers,
|
|
1778
|
+
onStatusChange,
|
|
1779
|
+
transformOutputWrite,
|
|
1780
|
+
store: storeRef.current,
|
|
1781
|
+
children
|
|
533
1782
|
}
|
|
534
1783
|
) });
|
|
1784
|
+
if (!shouldProvideOrchestrator) {
|
|
1785
|
+
return runtimeTree;
|
|
1786
|
+
}
|
|
1787
|
+
return /* @__PURE__ */ jsx(
|
|
1788
|
+
OrchestratorProvider,
|
|
1789
|
+
{
|
|
1790
|
+
autoCreate,
|
|
1791
|
+
createOptions,
|
|
1792
|
+
autostart: false,
|
|
1793
|
+
children: runtimeTree
|
|
1794
|
+
}
|
|
1795
|
+
);
|
|
535
1796
|
}
|
|
536
1797
|
function VizijRuntimeProviderInner({
|
|
537
1798
|
assetBundle: initialAssetBundle,
|
|
538
1799
|
namespace: namespaceProp,
|
|
539
1800
|
faceId: faceIdProp,
|
|
1801
|
+
updateTier,
|
|
540
1802
|
mergeStrategy,
|
|
541
1803
|
onRegisterControllers,
|
|
542
1804
|
onStatusChange,
|
|
1805
|
+
transformOutputWrite,
|
|
543
1806
|
store,
|
|
544
1807
|
children,
|
|
545
|
-
autoCreate
|
|
1808
|
+
autoCreate,
|
|
1809
|
+
autostart,
|
|
1810
|
+
createOptions,
|
|
1811
|
+
driveOrchestrator
|
|
546
1812
|
}) {
|
|
547
|
-
const [
|
|
548
|
-
|
|
549
|
-
|
|
1813
|
+
const [assetBundleOverride, setAssetBundleOverride] = useState(null);
|
|
1814
|
+
const [graphUpdateToken, setGraphUpdateToken] = useState(0);
|
|
1815
|
+
const effectiveAssetBundle = assetBundleOverride ?? initialAssetBundle;
|
|
1816
|
+
const latestEffectiveAssetBundleRef = useRef(effectiveAssetBundle);
|
|
1817
|
+
const [extractedBundle, setExtractedBundle] = useState(() => {
|
|
1818
|
+
if (effectiveAssetBundle.bundle) {
|
|
1819
|
+
return effectiveAssetBundle.bundle;
|
|
550
1820
|
}
|
|
551
|
-
if (
|
|
552
|
-
return
|
|
1821
|
+
if (effectiveAssetBundle.glb.kind === "world" && effectiveAssetBundle.glb.bundle) {
|
|
1822
|
+
return effectiveAssetBundle.glb.bundle;
|
|
553
1823
|
}
|
|
554
1824
|
return null;
|
|
555
1825
|
});
|
|
556
|
-
const [extractedAnimations, setExtractedAnimations] =
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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);
|
|
1831
|
+
useEffect(() => {
|
|
1832
|
+
if (effectiveAssetBundle.bundle) {
|
|
1833
|
+
setExtractedBundle(effectiveAssetBundle.bundle);
|
|
560
1834
|
return;
|
|
561
1835
|
}
|
|
562
|
-
if (
|
|
563
|
-
setExtractedBundle(
|
|
1836
|
+
if (effectiveAssetBundle.glb.kind === "world") {
|
|
1837
|
+
setExtractedBundle(effectiveAssetBundle.glb.bundle ?? null);
|
|
564
1838
|
} else {
|
|
565
1839
|
setExtractedBundle(null);
|
|
566
1840
|
}
|
|
567
|
-
}, [
|
|
568
|
-
|
|
1841
|
+
}, [effectiveAssetBundle]);
|
|
1842
|
+
useEffect(() => {
|
|
1843
|
+
updateTierRef.current = updateTier;
|
|
1844
|
+
}, [updateTier]);
|
|
1845
|
+
const assetBundle = useMemo(
|
|
569
1846
|
() => mergeAssetBundle(
|
|
570
|
-
|
|
1847
|
+
effectiveAssetBundle,
|
|
571
1848
|
extractedBundle,
|
|
572
1849
|
extractedAnimations
|
|
573
1850
|
),
|
|
574
|
-
[
|
|
1851
|
+
[effectiveAssetBundle, extractedBundle, extractedAnimations]
|
|
575
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]);
|
|
576
1873
|
const {
|
|
577
1874
|
ready,
|
|
578
1875
|
createOrchestrator,
|
|
@@ -581,15 +1878,16 @@ function VizijRuntimeProviderInner({
|
|
|
581
1878
|
registerAnimation,
|
|
582
1879
|
removeGraph,
|
|
583
1880
|
removeAnimation,
|
|
1881
|
+
removeInput,
|
|
584
1882
|
listControllers,
|
|
585
|
-
setInput,
|
|
1883
|
+
setInput: orchestratorSetInput,
|
|
586
1884
|
getPathSnapshot,
|
|
587
1885
|
step: stepRuntime
|
|
588
|
-
} =
|
|
589
|
-
const frame =
|
|
1886
|
+
} = useOrchestrator();
|
|
1887
|
+
const frame = useOrchFrame();
|
|
590
1888
|
const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
|
|
591
1889
|
const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
|
|
592
|
-
const [status, setStatus] =
|
|
1890
|
+
const [status, setStatus] = useState({
|
|
593
1891
|
loading: true,
|
|
594
1892
|
ready: false,
|
|
595
1893
|
error: null,
|
|
@@ -598,19 +1896,196 @@ function VizijRuntimeProviderInner({
|
|
|
598
1896
|
faceId,
|
|
599
1897
|
rootId: null,
|
|
600
1898
|
outputPaths: [],
|
|
1899
|
+
stepHz: void 0,
|
|
601
1900
|
controllers: { graphs: [], anims: [] }
|
|
602
1901
|
});
|
|
603
|
-
const errorsRef =
|
|
604
|
-
const outputPathsRef =
|
|
605
|
-
const
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
const
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
const
|
|
1902
|
+
const errorsRef = useRef([]);
|
|
1903
|
+
const outputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
1904
|
+
const baseOutputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
1905
|
+
const namespacedOutputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
1906
|
+
const namespaceRef = useRef(namespace);
|
|
1907
|
+
const driveOrchestratorRef = useRef(driveOrchestrator);
|
|
1908
|
+
const rigInputMapRef = useRef({});
|
|
1909
|
+
const rigPoseControlInputIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
1910
|
+
const registeredGraphsRef = useRef([]);
|
|
1911
|
+
const registeredAnimationsRef = useRef([]);
|
|
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
|
+
);
|
|
1946
|
+
const [inputConstraints, setInputConstraints] = useState({});
|
|
1947
|
+
const inputConstraintsRef = useRef({});
|
|
1948
|
+
const avgStepDtRef = useRef(null);
|
|
1949
|
+
const animationTweensRef = useRef(/* @__PURE__ */ new Map());
|
|
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);
|
|
1960
|
+
const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
|
|
1961
|
+
const autostartRef = useRef(autostart);
|
|
1962
|
+
const lastActivityTimeRef = useRef(now());
|
|
1963
|
+
const [loopMode, setLoopMode] = useState("stopped");
|
|
1964
|
+
const loopModeRef = useRef("stopped");
|
|
1965
|
+
useEffect(() => {
|
|
1966
|
+
loopModeRef.current = loopMode;
|
|
1967
|
+
}, [loopMode]);
|
|
1968
|
+
const runtimeMountedRef = useRef(true);
|
|
1969
|
+
useEffect(() => {
|
|
1970
|
+
return () => {
|
|
1971
|
+
runtimeMountedRef.current = false;
|
|
1972
|
+
};
|
|
1973
|
+
}, []);
|
|
1974
|
+
useEffect(() => {
|
|
1975
|
+
const rigAsset = assetBundle.rig;
|
|
1976
|
+
if (!rigAsset) {
|
|
1977
|
+
inputConstraintsRef.current = {};
|
|
1978
|
+
setInputConstraints({});
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
const rigSpec = resolveGraphSpec(
|
|
1982
|
+
rigAsset,
|
|
1983
|
+
`${rigAsset.id ?? "rig"} graph (constraints)`
|
|
1984
|
+
);
|
|
1985
|
+
const constraints = extractInputConstraints(
|
|
1986
|
+
rigSpec,
|
|
1987
|
+
rigAsset.inputMetadata,
|
|
1988
|
+
namespace
|
|
1989
|
+
);
|
|
1990
|
+
inputConstraintsRef.current = constraints;
|
|
1991
|
+
setInputConstraints(constraints);
|
|
1992
|
+
}, [assetBundle.rig, namespace]);
|
|
1993
|
+
const requestLoopMode = useCallback((mode) => {
|
|
1994
|
+
if (!runtimeMountedRef.current) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
setLoopMode((prev) => prev === mode ? prev : mode);
|
|
1998
|
+
}, []);
|
|
1999
|
+
const hasActiveAnimations = useCallback(() => {
|
|
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;
|
|
2022
|
+
}, []);
|
|
2023
|
+
const computeDesiredLoopMode = useCallback(() => {
|
|
2024
|
+
const hasAnimations = hasActiveAnimations();
|
|
2025
|
+
const recentlyActive = now() - lastActivityTimeRef.current <= ACTIVE_GRACE_MS;
|
|
2026
|
+
if (autostartRef.current && (hasAnimations || recentlyActive)) {
|
|
2027
|
+
return "active";
|
|
2028
|
+
}
|
|
2029
|
+
if (autostartRef.current) {
|
|
2030
|
+
return "idle-visible";
|
|
2031
|
+
}
|
|
2032
|
+
return "idle-hidden";
|
|
2033
|
+
}, [hasActiveAnimations]);
|
|
2034
|
+
const updateLoopMode = useCallback(() => {
|
|
2035
|
+
requestLoopMode(computeDesiredLoopMode());
|
|
2036
|
+
}, [computeDesiredLoopMode, requestLoopMode]);
|
|
2037
|
+
const markActivity = useCallback(() => {
|
|
2038
|
+
lastActivityTimeRef.current = now();
|
|
2039
|
+
updateLoopMode();
|
|
2040
|
+
}, [updateLoopMode]);
|
|
2041
|
+
const setInput = useCallback(
|
|
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
|
+
}
|
|
2069
|
+
markActivity();
|
|
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
|
+
}
|
|
2078
|
+
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
2079
|
+
},
|
|
2080
|
+
[
|
|
2081
|
+
assetBundle.pose?.config?.faceId,
|
|
2082
|
+
faceId,
|
|
2083
|
+
markActivity,
|
|
2084
|
+
poseWeightFallbackMap,
|
|
2085
|
+
useLegacyPoseWeightFallback
|
|
2086
|
+
]
|
|
2087
|
+
);
|
|
2088
|
+
const reportStatus = useCallback(
|
|
614
2089
|
(updater) => {
|
|
615
2090
|
setStatus((prev) => {
|
|
616
2091
|
const next = updater(prev);
|
|
@@ -620,7 +2095,7 @@ function VizijRuntimeProviderInner({
|
|
|
620
2095
|
},
|
|
621
2096
|
[onStatusChange]
|
|
622
2097
|
);
|
|
623
|
-
const pushError =
|
|
2098
|
+
const pushError = useCallback(
|
|
624
2099
|
(error) => {
|
|
625
2100
|
errorsRef.current = [...errorsRef.current, error];
|
|
626
2101
|
reportStatus((prev) => ({
|
|
@@ -632,7 +2107,7 @@ function VizijRuntimeProviderInner({
|
|
|
632
2107
|
},
|
|
633
2108
|
[reportStatus]
|
|
634
2109
|
);
|
|
635
|
-
const resetErrors =
|
|
2110
|
+
const resetErrors = useCallback(() => {
|
|
636
2111
|
errorsRef.current = [];
|
|
637
2112
|
reportStatus((prev) => ({
|
|
638
2113
|
...prev,
|
|
@@ -640,7 +2115,11 @@ function VizijRuntimeProviderInner({
|
|
|
640
2115
|
errors: []
|
|
641
2116
|
}));
|
|
642
2117
|
}, [reportStatus]);
|
|
643
|
-
|
|
2118
|
+
useEffect(() => {
|
|
2119
|
+
autostartRef.current = autostart;
|
|
2120
|
+
updateLoopMode();
|
|
2121
|
+
}, [autostart, updateLoopMode]);
|
|
2122
|
+
const clearControllers = useCallback(() => {
|
|
644
2123
|
const existing = listControllers();
|
|
645
2124
|
existing.graphs.forEach((id) => {
|
|
646
2125
|
try {
|
|
@@ -668,19 +2147,39 @@ function VizijRuntimeProviderInner({
|
|
|
668
2147
|
});
|
|
669
2148
|
registeredGraphsRef.current = [];
|
|
670
2149
|
registeredAnimationsRef.current = [];
|
|
2150
|
+
programControllerIdsRef.current.clear();
|
|
671
2151
|
mergedGraphRef.current = null;
|
|
2152
|
+
outputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2153
|
+
baseOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2154
|
+
namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2155
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2156
|
+
clipOutputValuesRef.current.clear();
|
|
2157
|
+
clipAggregateValuesRef.current.clear();
|
|
672
2158
|
}, [listControllers, removeAnimation, removeGraph, pushError]);
|
|
673
|
-
|
|
2159
|
+
useEffect(() => {
|
|
2160
|
+
namespaceRef.current = namespace;
|
|
674
2161
|
reportStatus((prev) => ({
|
|
675
2162
|
...prev,
|
|
676
2163
|
namespace,
|
|
677
2164
|
faceId
|
|
678
2165
|
}));
|
|
679
2166
|
}, [namespace, faceId, reportStatus]);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
2167
|
+
useEffect(() => {
|
|
2168
|
+
driveOrchestratorRef.current = driveOrchestrator;
|
|
2169
|
+
}, [driveOrchestrator]);
|
|
2170
|
+
const glbAsset = effectiveAssetBundle.glb;
|
|
2171
|
+
const baseBundle = effectiveAssetBundle.bundle ?? null;
|
|
2172
|
+
useEffect(() => {
|
|
683
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
|
+
}
|
|
684
2183
|
resetErrors();
|
|
685
2184
|
reportStatus((prev) => ({
|
|
686
2185
|
...prev,
|
|
@@ -698,7 +2197,7 @@ function VizijRuntimeProviderInner({
|
|
|
698
2197
|
let bundle = baseBundle;
|
|
699
2198
|
let gltfAnimations;
|
|
700
2199
|
if (glbAsset.kind === "url") {
|
|
701
|
-
const loaded = await
|
|
2200
|
+
const loaded = await loadGLTFWithBundle(
|
|
702
2201
|
glbAsset.src,
|
|
703
2202
|
[namespace],
|
|
704
2203
|
glbAsset.aggressiveImport ?? false,
|
|
@@ -709,7 +2208,7 @@ function VizijRuntimeProviderInner({
|
|
|
709
2208
|
bundle = loaded.bundle ?? bundle;
|
|
710
2209
|
gltfAnimations = pickExtractedAnimations(loaded);
|
|
711
2210
|
} else if (glbAsset.kind === "blob") {
|
|
712
|
-
const loaded = await
|
|
2211
|
+
const loaded = await loadGLTFFromBlobWithBundle(
|
|
713
2212
|
glbAsset.blob,
|
|
714
2213
|
[namespace],
|
|
715
2214
|
glbAsset.aggressiveImport ?? false,
|
|
@@ -732,6 +2231,12 @@ function VizijRuntimeProviderInner({
|
|
|
732
2231
|
setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
|
|
733
2232
|
const rootId = findRootId(world);
|
|
734
2233
|
store.getState().addWorldElements(world, animatables, true);
|
|
2234
|
+
if (pendingPlanRef.current?.reloadAssets) {
|
|
2235
|
+
pendingPlanRef.current = {
|
|
2236
|
+
...pendingPlanRef.current,
|
|
2237
|
+
reloadAssets: false
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
735
2240
|
reportStatus((prev) => ({
|
|
736
2241
|
...prev,
|
|
737
2242
|
loading: false,
|
|
@@ -769,11 +2274,12 @@ function VizijRuntimeProviderInner({
|
|
|
769
2274
|
reportStatus,
|
|
770
2275
|
resetErrors,
|
|
771
2276
|
setExtractedBundle,
|
|
772
|
-
setExtractedAnimations
|
|
2277
|
+
setExtractedAnimations,
|
|
2278
|
+
status.rootId
|
|
773
2279
|
]);
|
|
774
|
-
|
|
2280
|
+
useEffect(() => {
|
|
775
2281
|
if (!ready && autoCreate) {
|
|
776
|
-
createOrchestrator().catch((err) => {
|
|
2282
|
+
createOrchestrator(createOptions).catch((err) => {
|
|
777
2283
|
pushError({
|
|
778
2284
|
message: "Failed to create orchestrator runtime",
|
|
779
2285
|
cause: err,
|
|
@@ -782,50 +2288,145 @@ function VizijRuntimeProviderInner({
|
|
|
782
2288
|
});
|
|
783
2289
|
});
|
|
784
2290
|
}
|
|
785
|
-
}, [ready, autoCreate, createOrchestrator, pushError]);
|
|
786
|
-
const registerControllers =
|
|
787
|
-
const normalize = (spec) => spec;
|
|
2291
|
+
}, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
|
|
2292
|
+
const registerControllers = useCallback(async () => {
|
|
788
2293
|
clearControllers();
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
|
795
2301
|
});
|
|
796
|
-
return;
|
|
797
2302
|
}
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
inputs: rigInputs,
|
|
808
|
-
outputs: rigOutputs
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
const graphConfigs = [rigConfig];
|
|
812
|
-
if (assetBundle.pose?.graph) {
|
|
813
|
-
const poseSpec = normalize(assetBundle.pose.graph.spec);
|
|
814
|
-
const poseOutputs = collectOutputPaths(poseSpec);
|
|
815
|
-
const poseInputs = collectInputPaths(poseSpec);
|
|
816
|
-
graphConfigs.push({
|
|
817
|
-
id: assetBundle.pose.graph.id,
|
|
818
|
-
spec: poseSpec,
|
|
819
|
-
subs: assetBundle.pose.graph.subscriptions ?? {
|
|
820
|
-
inputs: poseInputs,
|
|
821
|
-
outputs: poseOutputs
|
|
822
|
-
}
|
|
2303
|
+
const baseOutputPaths = /* @__PURE__ */ new Set();
|
|
2304
|
+
const namespacedOutputPaths = /* @__PURE__ */ new Set();
|
|
2305
|
+
const recordOutputs = (paths) => {
|
|
2306
|
+
paths.forEach((path) => {
|
|
2307
|
+
const trimmed = path.trim();
|
|
2308
|
+
if (!trimmed) return;
|
|
2309
|
+
const basePath = stripNamespace(trimmed, namespace);
|
|
2310
|
+
baseOutputPaths.add(basePath);
|
|
2311
|
+
namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
|
|
823
2312
|
});
|
|
824
|
-
}
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
2313
|
+
};
|
|
2314
|
+
const graphConfigs = [];
|
|
2315
|
+
rigInputMapRef.current = {};
|
|
2316
|
+
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2317
|
+
poseControlBridgeValuesRef.current.clear();
|
|
2318
|
+
const rigAsset = assetBundle.rig;
|
|
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
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
const poseGraphAsset = assetBundle.pose?.graph;
|
|
2369
|
+
if (poseGraphAsset) {
|
|
2370
|
+
const poseSpec = resolveGraphSpec(
|
|
2371
|
+
poseGraphAsset,
|
|
2372
|
+
`${poseGraphAsset.id ?? "pose"} graph`
|
|
2373
|
+
);
|
|
2374
|
+
if (poseSpec) {
|
|
2375
|
+
const poseOutputs = collectOutputPaths(poseSpec);
|
|
2376
|
+
const poseInputs = collectInputPaths(poseSpec);
|
|
2377
|
+
recordOutputs(poseOutputs);
|
|
2378
|
+
const poseSubs = poseGraphAsset.subscriptions ?? {
|
|
2379
|
+
inputs: poseInputs,
|
|
2380
|
+
outputs: poseOutputs
|
|
2381
|
+
};
|
|
2382
|
+
graphConfigs.push({
|
|
2383
|
+
id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
|
|
2384
|
+
spec: stripNulls(namespaceGraphSpec(poseSpec, namespace)),
|
|
2385
|
+
subs: namespaceSubscriptions(poseSubs, namespace)
|
|
2386
|
+
});
|
|
2387
|
+
} else {
|
|
2388
|
+
console.warn(
|
|
2389
|
+
"[vizij-runtime] Pose graph is missing a usable spec or IR payload; skipping registration."
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
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
|
+
}
|
|
2418
|
+
outputPathsRef.current = namespacedOutputPaths;
|
|
2419
|
+
baseOutputPathsRef.current = baseOutputPaths;
|
|
2420
|
+
namespacedOutputPathsRef.current = namespacedOutputPaths;
|
|
2421
|
+
const graphIds = [];
|
|
2422
|
+
try {
|
|
2423
|
+
if (graphConfigs.length > 1) {
|
|
2424
|
+
const mergedId = registerMergedGraph({
|
|
2425
|
+
id: namespaceControllerId(
|
|
2426
|
+
mergedGraphRef.current ?? `merged-${namespace}`,
|
|
2427
|
+
namespace,
|
|
2428
|
+
"merged"
|
|
2429
|
+
) ?? void 0,
|
|
829
2430
|
graphs: graphConfigs,
|
|
830
2431
|
strategy: mergeStrategy ?? DEFAULT_MERGE
|
|
831
2432
|
});
|
|
@@ -846,19 +2447,30 @@ function VizijRuntimeProviderInner({
|
|
|
846
2447
|
});
|
|
847
2448
|
}
|
|
848
2449
|
registeredGraphsRef.current = graphIds;
|
|
2450
|
+
if (isRuntimeDebugEnabled()) {
|
|
2451
|
+
console.log("[vizij-runtime] registered graph ids", graphIds);
|
|
2452
|
+
}
|
|
849
2453
|
const animationIds = [];
|
|
850
2454
|
for (const anim of assetBundle.animations ?? []) {
|
|
851
2455
|
try {
|
|
2456
|
+
const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
|
|
2457
|
+
const animationPayload = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
|
|
852
2458
|
const config = {
|
|
853
|
-
id:
|
|
2459
|
+
id: controllerId,
|
|
854
2460
|
setup: {
|
|
855
|
-
|
|
856
|
-
|
|
2461
|
+
...anim.setup ?? {},
|
|
2462
|
+
animation: animationPayload
|
|
857
2463
|
}
|
|
858
2464
|
};
|
|
859
2465
|
const id = registerAnimation(config);
|
|
860
2466
|
animationIds.push(id);
|
|
861
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
|
+
}
|
|
862
2474
|
pushError({
|
|
863
2475
|
message: `Failed to register animation ${anim.id}`,
|
|
864
2476
|
cause: err,
|
|
@@ -883,6 +2495,13 @@ function VizijRuntimeProviderInner({
|
|
|
883
2495
|
});
|
|
884
2496
|
}
|
|
885
2497
|
const controllers = listControllers();
|
|
2498
|
+
if (isRuntimeDebugEnabled()) {
|
|
2499
|
+
console.log("[vizij-runtime] controllers after register", {
|
|
2500
|
+
controllers,
|
|
2501
|
+
graphIds,
|
|
2502
|
+
animationIds
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
886
2505
|
reportStatus((prev) => ({
|
|
887
2506
|
...prev,
|
|
888
2507
|
ready: true,
|
|
@@ -895,18 +2514,25 @@ function VizijRuntimeProviderInner({
|
|
|
895
2514
|
clearControllers,
|
|
896
2515
|
listControllers,
|
|
897
2516
|
mergeStrategy,
|
|
2517
|
+
namespace,
|
|
898
2518
|
onRegisterControllers,
|
|
899
2519
|
pushError,
|
|
900
2520
|
registerAnimation,
|
|
901
2521
|
registerGraph,
|
|
902
2522
|
registerMergedGraph,
|
|
903
2523
|
reportStatus,
|
|
2524
|
+
resolvedProgramAssets,
|
|
904
2525
|
setInput
|
|
905
2526
|
]);
|
|
906
|
-
|
|
2527
|
+
useEffect(() => {
|
|
907
2528
|
if (!ready || status.loading) {
|
|
908
2529
|
return;
|
|
909
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
|
+
}
|
|
910
2536
|
registerControllers().catch((err) => {
|
|
911
2537
|
pushError({
|
|
912
2538
|
message: "Failed to register controllers",
|
|
@@ -915,8 +2541,8 @@ function VizijRuntimeProviderInner({
|
|
|
915
2541
|
timestamp: performance.now()
|
|
916
2542
|
});
|
|
917
2543
|
});
|
|
918
|
-
}, [ready, status.loading, registerControllers, pushError]);
|
|
919
|
-
|
|
2544
|
+
}, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
|
|
2545
|
+
useEffect(() => {
|
|
920
2546
|
if (!frame) {
|
|
921
2547
|
return;
|
|
922
2548
|
}
|
|
@@ -924,21 +2550,69 @@ function VizijRuntimeProviderInner({
|
|
|
924
2550
|
if (!writes.length) {
|
|
925
2551
|
return;
|
|
926
2552
|
}
|
|
927
|
-
const
|
|
2553
|
+
const setWorldValues = store.getState().setValues;
|
|
928
2554
|
const namespaceValue = status.namespace;
|
|
2555
|
+
const currentValues = store.getState().values;
|
|
2556
|
+
const rigInputPathMap = rigInputMapRef.current;
|
|
2557
|
+
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
2558
|
+
const batched = [];
|
|
2559
|
+
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
2560
|
+
const baseOutputs = baseOutputPathsRef.current;
|
|
929
2561
|
writes.forEach((write) => {
|
|
930
2562
|
const path = normalisePath(write.path);
|
|
931
|
-
|
|
2563
|
+
const basePath = stripNamespace(path, namespaceValue);
|
|
2564
|
+
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
2565
|
+
if (!isTrackedOutput) {
|
|
932
2566
|
return;
|
|
933
2567
|
}
|
|
934
2568
|
const raw = valueJSONToRaw(write.value);
|
|
935
2569
|
if (raw === void 0) {
|
|
936
2570
|
return;
|
|
937
2571
|
}
|
|
938
|
-
|
|
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
|
+
}
|
|
2593
|
+
const targetPath = baseOutputs.has(basePath) ? basePath : path;
|
|
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
|
+
}
|
|
939
2610
|
});
|
|
940
|
-
|
|
941
|
-
|
|
2611
|
+
if (batched.length > 0) {
|
|
2612
|
+
setWorldValues(batched);
|
|
2613
|
+
}
|
|
2614
|
+
}, [frame, status.namespace, store, transformOutputWrite]);
|
|
2615
|
+
const stagePoseNeutral = useCallback(
|
|
942
2616
|
(force = false) => {
|
|
943
2617
|
const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
|
|
944
2618
|
const rigMap = rigInputMapRef.current;
|
|
@@ -970,41 +2644,20 @@ function VizijRuntimeProviderInner({
|
|
|
970
2644
|
},
|
|
971
2645
|
[assetBundle.pose?.config?.neutralInputs, setInput]
|
|
972
2646
|
);
|
|
973
|
-
const setRendererValue =
|
|
2647
|
+
const setRendererValue = useCallback(
|
|
974
2648
|
(id, ns, value) => {
|
|
975
2649
|
store.getState().setValue(id, ns, value);
|
|
976
2650
|
},
|
|
977
2651
|
[store]
|
|
978
2652
|
);
|
|
979
|
-
const cancelAnimation =
|
|
2653
|
+
const cancelAnimation = useCallback((path) => {
|
|
980
2654
|
if (animationTweensRef.current.has(path)) {
|
|
981
2655
|
const entry = animationTweensRef.current.get(path);
|
|
982
2656
|
animationTweensRef.current.delete(path);
|
|
983
2657
|
entry?.resolve();
|
|
984
2658
|
}
|
|
985
2659
|
}, []);
|
|
986
|
-
const
|
|
987
|
-
if (rafHandleRef.current !== null) {
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
const tick = (timestamp) => {
|
|
991
|
-
if (lastFrameTimeRef.current == null) {
|
|
992
|
-
lastFrameTimeRef.current = timestamp;
|
|
993
|
-
}
|
|
994
|
-
const dt = Math.max(0, (timestamp - lastFrameTimeRef.current) / 1e3);
|
|
995
|
-
lastFrameTimeRef.current = timestamp;
|
|
996
|
-
advanceAnimationTweens(dt);
|
|
997
|
-
advanceClipPlayback(dt);
|
|
998
|
-
if (animationTweensRef.current.size > 0 || clipPlaybackRef.current.size > 0) {
|
|
999
|
-
rafHandleRef.current = requestAnimationFrame(tick);
|
|
1000
|
-
} else {
|
|
1001
|
-
rafHandleRef.current = null;
|
|
1002
|
-
lastFrameTimeRef.current = null;
|
|
1003
|
-
}
|
|
1004
|
-
};
|
|
1005
|
-
rafHandleRef.current = requestAnimationFrame(tick);
|
|
1006
|
-
}, []);
|
|
1007
|
-
const advanceAnimationTweens = (0, import_react2.useCallback)(
|
|
2660
|
+
const advanceAnimationTweens = useCallback(
|
|
1008
2661
|
(dt) => {
|
|
1009
2662
|
if (animationTweensRef.current.size === 0) {
|
|
1010
2663
|
return;
|
|
@@ -1026,100 +2679,252 @@ function VizijRuntimeProviderInner({
|
|
|
1026
2679
|
},
|
|
1027
2680
|
[setInput]
|
|
1028
2681
|
);
|
|
1029
|
-
const
|
|
1030
|
-
(
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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;
|
|
1053
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;
|
|
2774
|
+
}
|
|
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;
|
|
1054
2787
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
+
};
|
|
1057
2809
|
},
|
|
1058
2810
|
[]
|
|
1059
2811
|
);
|
|
1060
|
-
const
|
|
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
|
+
);
|
|
2833
|
+
const advanceClipPlayback = useCallback(
|
|
1061
2834
|
(dt) => {
|
|
1062
2835
|
if (clipPlaybackRef.current.size === 0) {
|
|
1063
2836
|
return;
|
|
1064
2837
|
}
|
|
1065
|
-
const map = clipPlaybackRef.current;
|
|
1066
2838
|
const toDelete = [];
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const clip = assetBundle.animations?.find(
|
|
1070
|
-
(anim) => anim.id === state.id
|
|
1071
|
-
);
|
|
2839
|
+
clipPlaybackRef.current.forEach((state, key) => {
|
|
2840
|
+
const clip = resolveClipById(state.id);
|
|
1072
2841
|
if (!clip) {
|
|
1073
2842
|
toDelete.push(key);
|
|
1074
|
-
state
|
|
2843
|
+
resolveClipPromise(state);
|
|
1075
2844
|
return;
|
|
1076
2845
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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);
|
|
1083
2863
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
const path = `animation/${clip.id}/${track.channel}`;
|
|
1088
|
-
setInput(path, { float: value });
|
|
1089
|
-
});
|
|
1090
|
-
if (toDelete.includes(key)) {
|
|
1091
|
-
state.resolve();
|
|
2864
|
+
if (completed) {
|
|
2865
|
+
toDelete.push(key);
|
|
2866
|
+
resolveClipPromise(state);
|
|
1092
2867
|
}
|
|
1093
2868
|
});
|
|
1094
2869
|
toDelete.forEach((key) => {
|
|
1095
2870
|
clipPlaybackRef.current.delete(key);
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const clipData = clip.clip;
|
|
1099
|
-
const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
|
|
1100
|
-
tracks.forEach((track) => {
|
|
1101
|
-
const path = `animation/${clip.id}/${track.channel}`;
|
|
1102
|
-
setInput(path, { float: 0 });
|
|
1103
|
-
});
|
|
2871
|
+
if (animationSystemActiveRef.current) {
|
|
2872
|
+
clearClipOutputs(key);
|
|
1104
2873
|
}
|
|
1105
2874
|
});
|
|
1106
2875
|
},
|
|
1107
|
-
[
|
|
2876
|
+
[clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
|
|
1108
2877
|
);
|
|
1109
|
-
const animateValue =
|
|
2878
|
+
const animateValue = useCallback(
|
|
1110
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
|
+
}
|
|
1111
2913
|
const easing = resolveEasing(options?.easing);
|
|
1112
2914
|
const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
|
|
1113
2915
|
cancelAnimation(path);
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1116
|
-
const
|
|
2916
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2917
|
+
const current = getPathSnapshot(namespacedPath);
|
|
2918
|
+
const fromValue = valueAsNumber2(current);
|
|
2919
|
+
const toValue = valueAsNumber2(target);
|
|
1117
2920
|
if (fromValue == null || toValue == null || duration === 0) {
|
|
1118
2921
|
setInput(path, target);
|
|
1119
2922
|
return Promise.resolve();
|
|
1120
2923
|
}
|
|
1121
2924
|
return new Promise((resolve) => {
|
|
1122
2925
|
animationTweensRef.current.set(path, {
|
|
2926
|
+
// Keep the raw path here so tween updates go through setInput() once
|
|
2927
|
+
// and pick up the active namespace exactly once.
|
|
1123
2928
|
path,
|
|
1124
2929
|
from: fromValue,
|
|
1125
2930
|
to: toValue,
|
|
@@ -1128,59 +2933,382 @@ function VizijRuntimeProviderInner({
|
|
|
1128
2933
|
easing,
|
|
1129
2934
|
resolve
|
|
1130
2935
|
});
|
|
1131
|
-
|
|
2936
|
+
markActivity();
|
|
1132
2937
|
});
|
|
1133
2938
|
},
|
|
1134
|
-
[
|
|
2939
|
+
[
|
|
2940
|
+
assetBundle.pose?.config?.faceId,
|
|
2941
|
+
cancelAnimation,
|
|
2942
|
+
faceId,
|
|
2943
|
+
getPathSnapshot,
|
|
2944
|
+
markActivity,
|
|
2945
|
+
poseWeightFallbackMap,
|
|
2946
|
+
setInput,
|
|
2947
|
+
useLegacyPoseWeightFallback
|
|
2948
|
+
]
|
|
1135
2949
|
);
|
|
1136
|
-
const playAnimation =
|
|
2950
|
+
const playAnimation = useCallback(
|
|
1137
2951
|
(id, options) => {
|
|
1138
|
-
const
|
|
1139
|
-
if (!
|
|
2952
|
+
const ensured = ensureClipPlaybackState(id);
|
|
2953
|
+
if (!ensured) {
|
|
1140
2954
|
return Promise.reject(
|
|
1141
2955
|
new Error(`Animation ${id} is not part of the current asset bundle.`)
|
|
1142
2956
|
);
|
|
1143
2957
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
2958
|
+
const { clip, state } = ensured;
|
|
2959
|
+
const shouldReset = options?.reset === true;
|
|
2960
|
+
if (shouldReset) {
|
|
2961
|
+
resolveClipPromise(state);
|
|
2962
|
+
state.time = 0;
|
|
1146
2963
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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;
|
|
2979
|
+
},
|
|
2980
|
+
[
|
|
2981
|
+
ensureClipPlaybackState,
|
|
2982
|
+
ensureClipPromise,
|
|
2983
|
+
markActivity,
|
|
2984
|
+
resolveClipPromise,
|
|
2985
|
+
writeClipOutputs
|
|
2986
|
+
]
|
|
2987
|
+
);
|
|
2988
|
+
const pauseAnimation = useCallback(
|
|
2989
|
+
(id) => {
|
|
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();
|
|
1161
3021
|
},
|
|
1162
|
-
[
|
|
3022
|
+
[ensureClipPlaybackState, updateLoopMode]
|
|
1163
3023
|
);
|
|
1164
|
-
const
|
|
3024
|
+
const getAnimationState = useCallback(
|
|
1165
3025
|
(id) => {
|
|
1166
|
-
const
|
|
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) => {
|
|
1167
3042
|
const state = clipPlaybackRef.current.get(id);
|
|
1168
3043
|
if (state) {
|
|
1169
3044
|
clipPlaybackRef.current.delete(id);
|
|
1170
|
-
state.
|
|
3045
|
+
state.playing = false;
|
|
3046
|
+
resolveClipPromise(state);
|
|
1171
3047
|
}
|
|
1172
|
-
if (
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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()
|
|
3186
|
+
});
|
|
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();
|
|
3213
|
+
},
|
|
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 });
|
|
1178
3254
|
});
|
|
1179
3255
|
}
|
|
3256
|
+
refreshControllerStatus();
|
|
3257
|
+
updateLoopMode();
|
|
1180
3258
|
},
|
|
1181
|
-
[
|
|
3259
|
+
[
|
|
3260
|
+
deriveProgramResetValues,
|
|
3261
|
+
pushError,
|
|
3262
|
+
refreshControllerStatus,
|
|
3263
|
+
removeGraph,
|
|
3264
|
+
resolveProgramById,
|
|
3265
|
+
setInput,
|
|
3266
|
+
updateLoopMode
|
|
3267
|
+
]
|
|
1182
3268
|
);
|
|
1183
|
-
const
|
|
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
|
+
[]
|
|
3310
|
+
);
|
|
3311
|
+
const registerInputDriver = useCallback(
|
|
1184
3312
|
(id, factory) => {
|
|
1185
3313
|
const driver = factory({
|
|
1186
3314
|
setInput,
|
|
@@ -1230,72 +3358,211 @@ function VizijRuntimeProviderInner({
|
|
|
1230
3358
|
},
|
|
1231
3359
|
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
1232
3360
|
);
|
|
1233
|
-
const advanceAnimations =
|
|
3361
|
+
const advanceAnimations = useCallback(
|
|
1234
3362
|
(dt) => {
|
|
1235
3363
|
advanceAnimationTweens(dt);
|
|
1236
3364
|
advanceClipPlayback(dt);
|
|
1237
3365
|
},
|
|
1238
3366
|
[advanceAnimationTweens, advanceClipPlayback]
|
|
1239
3367
|
);
|
|
1240
|
-
const
|
|
1241
|
-
(
|
|
3368
|
+
const flushStagedInputs = useCallback(() => {
|
|
3369
|
+
if (stagedInputsRef.current.size === 0) {
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
stagedInputsRef.current.forEach(({ value, shape }, path) => {
|
|
3373
|
+
orchestratorSetInput(path, value, shape);
|
|
3374
|
+
});
|
|
3375
|
+
stagedInputsRef.current.clear();
|
|
3376
|
+
}, [orchestratorSetInput]);
|
|
3377
|
+
const step = useCallback(
|
|
3378
|
+
(dt, opts) => {
|
|
3379
|
+
if (dt > 0 && Number.isFinite(dt)) {
|
|
3380
|
+
const prev = avgStepDtRef.current ?? dt;
|
|
3381
|
+
const alpha = 0.1;
|
|
3382
|
+
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
3383
|
+
}
|
|
1242
3384
|
advanceAnimations(dt);
|
|
1243
|
-
|
|
3385
|
+
flushStagedInputs();
|
|
3386
|
+
if (driveOrchestratorRef.current || opts?.forceRuntime) {
|
|
3387
|
+
stepRuntime(dt);
|
|
3388
|
+
}
|
|
1244
3389
|
},
|
|
1245
|
-
[advanceAnimations, stepRuntime]
|
|
3390
|
+
[advanceAnimations, flushStagedInputs, stepRuntime]
|
|
1246
3391
|
);
|
|
1247
|
-
|
|
3392
|
+
useEffect(() => {
|
|
3393
|
+
if (loopMode !== "active") {
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
let rafId = null;
|
|
3397
|
+
let lastTime = null;
|
|
3398
|
+
const tick = (timestamp) => {
|
|
3399
|
+
if (loopModeRef.current !== "active") {
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
if (lastTime == null) {
|
|
3403
|
+
lastTime = timestamp;
|
|
3404
|
+
}
|
|
3405
|
+
const dt = Math.max(0, (timestamp - lastTime) / 1e3);
|
|
3406
|
+
lastTime = timestamp;
|
|
3407
|
+
step(dt || 0);
|
|
3408
|
+
requestLoopMode(computeDesiredLoopMode());
|
|
3409
|
+
rafId = requestAnimationFrame(tick);
|
|
3410
|
+
};
|
|
3411
|
+
rafId = requestAnimationFrame(tick);
|
|
1248
3412
|
return () => {
|
|
1249
|
-
if (
|
|
1250
|
-
cancelAnimationFrame(
|
|
1251
|
-
|
|
3413
|
+
if (rafId !== null) {
|
|
3414
|
+
cancelAnimationFrame(rafId);
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
}, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
|
|
3418
|
+
useEffect(() => {
|
|
3419
|
+
if (loopMode !== "idle-visible" && loopMode !== "idle-hidden") {
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
if (typeof window === "undefined") {
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
const fps = loopMode === "idle-visible" ? VISIBLE_IDLE_FPS : HIDDEN_IDLE_FPS;
|
|
3426
|
+
if (fps <= 0) {
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
let lastTime = now();
|
|
3430
|
+
const interval = 1e3 / fps;
|
|
3431
|
+
const tick = () => {
|
|
3432
|
+
if (loopModeRef.current !== "idle-visible" && loopModeRef.current !== "idle-hidden") {
|
|
3433
|
+
return;
|
|
1252
3434
|
}
|
|
1253
|
-
|
|
3435
|
+
const current = now();
|
|
3436
|
+
const dt = Math.max(0, (current - lastTime) / 1e3);
|
|
3437
|
+
lastTime = current;
|
|
3438
|
+
step(dt || 0);
|
|
3439
|
+
requestLoopMode(computeDesiredLoopMode());
|
|
3440
|
+
};
|
|
3441
|
+
const intervalId = window.setInterval(tick, interval);
|
|
3442
|
+
return () => {
|
|
3443
|
+
window.clearInterval(intervalId);
|
|
3444
|
+
};
|
|
3445
|
+
}, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
|
|
3446
|
+
useEffect(() => {
|
|
3447
|
+
return () => {
|
|
1254
3448
|
animationTweensRef.current.clear();
|
|
1255
3449
|
clipPlaybackRef.current.clear();
|
|
3450
|
+
programPlaybackRef.current.clear();
|
|
3451
|
+
programControllerIdsRef.current.clear();
|
|
3452
|
+
clipOutputValuesRef.current.clear();
|
|
3453
|
+
clipAggregateValuesRef.current.clear();
|
|
1256
3454
|
};
|
|
1257
3455
|
}, []);
|
|
1258
|
-
|
|
3456
|
+
useEffect(() => {
|
|
3457
|
+
if (typeof window === "undefined") {
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
const id = window.setInterval(() => {
|
|
3461
|
+
const avg = avgStepDtRef.current;
|
|
3462
|
+
const stepHz = avg && Number.isFinite(avg) && avg > 0 ? 1 / avg : void 0;
|
|
3463
|
+
reportStatus(
|
|
3464
|
+
(prev) => prev.stepHz === stepHz ? prev : { ...prev, stepHz }
|
|
3465
|
+
);
|
|
3466
|
+
}, 500);
|
|
3467
|
+
return () => window.clearInterval(id);
|
|
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
|
+
);
|
|
3501
|
+
const contextValue = useMemo(
|
|
1259
3502
|
() => ({
|
|
1260
3503
|
...status,
|
|
1261
3504
|
assetBundle,
|
|
1262
3505
|
setInput,
|
|
3506
|
+
setGraphBundle,
|
|
1263
3507
|
setValue: setRendererValue,
|
|
1264
3508
|
stagePoseNeutral,
|
|
1265
3509
|
animateValue,
|
|
1266
3510
|
cancelAnimation,
|
|
1267
3511
|
registerInputDriver,
|
|
1268
3512
|
playAnimation,
|
|
3513
|
+
pauseAnimation,
|
|
3514
|
+
seekAnimation,
|
|
3515
|
+
setAnimationLoop,
|
|
3516
|
+
getAnimationState,
|
|
1269
3517
|
stopAnimation,
|
|
3518
|
+
playProgram,
|
|
3519
|
+
pauseProgram,
|
|
3520
|
+
stopProgram,
|
|
3521
|
+
getProgramState,
|
|
3522
|
+
setAnimationActive,
|
|
3523
|
+
isAnimationActive,
|
|
1270
3524
|
step,
|
|
1271
|
-
advanceAnimations
|
|
3525
|
+
advanceAnimations,
|
|
3526
|
+
inputConstraints
|
|
1272
3527
|
}),
|
|
1273
3528
|
[
|
|
1274
3529
|
status,
|
|
1275
3530
|
assetBundle,
|
|
1276
3531
|
setInput,
|
|
3532
|
+
setGraphBundle,
|
|
1277
3533
|
setRendererValue,
|
|
1278
3534
|
stagePoseNeutral,
|
|
1279
3535
|
animateValue,
|
|
1280
3536
|
cancelAnimation,
|
|
1281
3537
|
registerInputDriver,
|
|
1282
3538
|
playAnimation,
|
|
3539
|
+
pauseAnimation,
|
|
3540
|
+
seekAnimation,
|
|
3541
|
+
setAnimationLoop,
|
|
3542
|
+
getAnimationState,
|
|
1283
3543
|
stopAnimation,
|
|
3544
|
+
playProgram,
|
|
3545
|
+
pauseProgram,
|
|
3546
|
+
stopProgram,
|
|
3547
|
+
getProgramState,
|
|
3548
|
+
setAnimationActive,
|
|
3549
|
+
isAnimationActive,
|
|
1284
3550
|
step,
|
|
1285
|
-
advanceAnimations
|
|
3551
|
+
advanceAnimations,
|
|
3552
|
+
inputConstraints
|
|
1286
3553
|
]
|
|
1287
3554
|
);
|
|
1288
|
-
return /* @__PURE__ */
|
|
3555
|
+
return /* @__PURE__ */ jsx(VizijRuntimeContext.Provider, { value: contextValue, children });
|
|
1289
3556
|
}
|
|
1290
3557
|
|
|
1291
3558
|
// src/VizijRuntimeFace.tsx
|
|
1292
|
-
|
|
1293
|
-
|
|
3559
|
+
import { memo } from "react";
|
|
3560
|
+
import { Vizij } from "@vizij/render";
|
|
1294
3561
|
|
|
1295
3562
|
// src/hooks/useVizijRuntime.ts
|
|
1296
|
-
|
|
3563
|
+
import { useContext as useContext2 } from "react";
|
|
1297
3564
|
function useVizijRuntime() {
|
|
1298
|
-
const ctx = (
|
|
3565
|
+
const ctx = useContext2(VizijRuntimeContext);
|
|
1299
3566
|
if (!ctx) {
|
|
1300
3567
|
throw new Error(
|
|
1301
3568
|
"useVizijRuntime must be used within a VizijRuntimeProvider."
|
|
@@ -1305,7 +3572,7 @@ function useVizijRuntime() {
|
|
|
1305
3572
|
}
|
|
1306
3573
|
|
|
1307
3574
|
// src/VizijRuntimeFace.tsx
|
|
1308
|
-
|
|
3575
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1309
3576
|
function VizijRuntimeFaceInner({
|
|
1310
3577
|
namespaceOverride,
|
|
1311
3578
|
...props
|
|
@@ -1314,8 +3581,8 @@ function VizijRuntimeFaceInner({
|
|
|
1314
3581
|
if (!rootId) {
|
|
1315
3582
|
return null;
|
|
1316
3583
|
}
|
|
1317
|
-
return /* @__PURE__ */ (
|
|
1318
|
-
|
|
3584
|
+
return /* @__PURE__ */ jsx2(
|
|
3585
|
+
Vizij,
|
|
1319
3586
|
{
|
|
1320
3587
|
...props,
|
|
1321
3588
|
rootId,
|
|
@@ -1323,17 +3590,25 @@ function VizijRuntimeFaceInner({
|
|
|
1323
3590
|
}
|
|
1324
3591
|
);
|
|
1325
3592
|
}
|
|
1326
|
-
var VizijRuntimeFace =
|
|
3593
|
+
var VizijRuntimeFace = memo(VizijRuntimeFaceInner);
|
|
3594
|
+
|
|
3595
|
+
// src/hooks/useOptionalVizijRuntime.ts
|
|
3596
|
+
import { useContext as useContext3 } from "react";
|
|
3597
|
+
function useOptionalVizijRuntime() {
|
|
3598
|
+
return useContext3(VizijRuntimeContext);
|
|
3599
|
+
}
|
|
1327
3600
|
|
|
1328
3601
|
// src/hooks/useVizijOutputs.ts
|
|
1329
|
-
|
|
1330
|
-
|
|
3602
|
+
import {
|
|
3603
|
+
useVizijStore
|
|
3604
|
+
} from "@vizij/render";
|
|
3605
|
+
import { getLookup as getLookup2 } from "@vizij/utils";
|
|
1331
3606
|
function useVizijOutputs(paths) {
|
|
1332
3607
|
const { namespace } = useVizijRuntime();
|
|
1333
|
-
return
|
|
3608
|
+
return useVizijStore((state) => {
|
|
1334
3609
|
const result = {};
|
|
1335
3610
|
paths.forEach((path) => {
|
|
1336
|
-
const lookup = (
|
|
3611
|
+
const lookup = getLookup2(namespace, path);
|
|
1337
3612
|
result[path] = state.values.get(lookup);
|
|
1338
3613
|
});
|
|
1339
3614
|
return result;
|
|
@@ -1341,15 +3616,17 @@ function useVizijOutputs(paths) {
|
|
|
1341
3616
|
}
|
|
1342
3617
|
|
|
1343
3618
|
// src/hooks/useRigInput.ts
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
3619
|
+
import { useCallback as useCallback2 } from "react";
|
|
3620
|
+
import {
|
|
3621
|
+
useVizijStore as useVizijStore2
|
|
3622
|
+
} from "@vizij/render";
|
|
3623
|
+
import { getLookup as getLookup3 } from "@vizij/utils";
|
|
1347
3624
|
function useRigInput(path) {
|
|
1348
3625
|
const { namespace, setInput } = useVizijRuntime();
|
|
1349
|
-
const value = (
|
|
1350
|
-
return state.values.get((
|
|
3626
|
+
const value = useVizijStore2((state) => {
|
|
3627
|
+
return state.values.get(getLookup3(namespace, path));
|
|
1351
3628
|
});
|
|
1352
|
-
const setter = (
|
|
3629
|
+
const setter = useCallback2(
|
|
1353
3630
|
(next, shape) => {
|
|
1354
3631
|
setInput(path, next, shape);
|
|
1355
3632
|
},
|
|
@@ -1357,11 +3634,423 @@ function useRigInput(path) {
|
|
|
1357
3634
|
);
|
|
1358
3635
|
return [value, setter];
|
|
1359
3636
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
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
|
+
}
|
|
4031
|
+
export {
|
|
4032
|
+
EMOTION_POSE_KEYS,
|
|
4033
|
+
EXPRESSIVE_EMOTION_POSE_KEYS,
|
|
4034
|
+
POSE_WEIGHT_INPUT_PATH_PREFIX,
|
|
4035
|
+
VISEME_POSE_KEYS,
|
|
1362
4036
|
VizijRuntimeFace,
|
|
1363
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,
|
|
1364
4053
|
useRigInput,
|
|
1365
4054
|
useVizijOutputs,
|
|
1366
4055
|
useVizijRuntime
|
|
1367
|
-
}
|
|
4056
|
+
};
|