@lovelace_lol/loom3 1.0.41 → 1.0.43
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/dist/index.cjs +819 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -1
- package/dist/index.d.ts +122 -1
- package/dist/index.js +809 -86
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,223 @@ function getCompositeAxisBinding(nodeKey, axisConfig, direction, getValue, auToB
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// src/mappings/visemeSystem.ts
|
|
64
|
+
function hasOwn(value, key) {
|
|
65
|
+
return Boolean(value && Object.prototype.hasOwnProperty.call(value, key));
|
|
66
|
+
}
|
|
67
|
+
function toSlotId(label, index) {
|
|
68
|
+
const normalized = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
69
|
+
return normalized || `viseme-${index}`;
|
|
70
|
+
}
|
|
71
|
+
function bindingTargets(binding) {
|
|
72
|
+
if (!binding) return [];
|
|
73
|
+
const targets = binding.targets?.map((target) => target.morph).filter((morph) => morph !== "");
|
|
74
|
+
if (targets && targets.length > 0) return targets;
|
|
75
|
+
return binding.morph !== void 0 && binding.morph !== "" ? [binding.morph] : [];
|
|
76
|
+
}
|
|
77
|
+
function getProfileVisemeSlots(profile) {
|
|
78
|
+
if (profile.visemeSlots && profile.visemeSlots.length > 0) {
|
|
79
|
+
return [...profile.visemeSlots].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
80
|
+
}
|
|
81
|
+
return (profile.visemeKeys || []).map((key, index) => {
|
|
82
|
+
const label = typeof key === "string" && key ? key : `Viseme ${index}`;
|
|
83
|
+
return {
|
|
84
|
+
id: toSlotId(label, index),
|
|
85
|
+
label,
|
|
86
|
+
order: index,
|
|
87
|
+
defaultJawAmount: profile.visemeJawAmounts?.[index]
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function getVisemeSlotIndex(profile, slotId) {
|
|
92
|
+
return getProfileVisemeSlots(profile).findIndex((slot) => slot.id === slotId);
|
|
93
|
+
}
|
|
94
|
+
function compileVisemeKeys(profile) {
|
|
95
|
+
const slots = getProfileVisemeSlots(profile);
|
|
96
|
+
if (!profile.visemeBindings) return [...profile.visemeKeys || []];
|
|
97
|
+
return slots.map((slot, index) => {
|
|
98
|
+
const target = bindingTargets(profile.visemeBindings?.[slot.id])[0];
|
|
99
|
+
return target ?? profile.visemeKeys?.[index] ?? "";
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function getVisemeJawAmounts(profile) {
|
|
103
|
+
const slots = getProfileVisemeSlots(profile);
|
|
104
|
+
if (slots.length === 0) return profile.visemeJawAmounts ? [...profile.visemeJawAmounts] : void 0;
|
|
105
|
+
return slots.map((slot, index) => slot.defaultJawAmount ?? profile.visemeJawAmounts?.[index] ?? 0);
|
|
106
|
+
}
|
|
107
|
+
function resolveVisemeMeshCategory(profile) {
|
|
108
|
+
const morphToMesh = profile.morphToMesh || {};
|
|
109
|
+
if (profile.visemeMeshCategory) return profile.visemeMeshCategory;
|
|
110
|
+
if (hasOwn(morphToMesh, "viseme")) return "viseme";
|
|
111
|
+
return "face";
|
|
112
|
+
}
|
|
113
|
+
function getMeshNamesForVisemeProfile(profile) {
|
|
114
|
+
const morphToMesh = profile.morphToMesh || {};
|
|
115
|
+
const category = resolveVisemeMeshCategory(profile);
|
|
116
|
+
if (hasOwn(morphToMesh, category)) {
|
|
117
|
+
return Array.isArray(morphToMesh[category]) ? [...morphToMesh[category]] : [];
|
|
118
|
+
}
|
|
119
|
+
return profile.visemeMeshCategory ? [] : [...morphToMesh.face || []];
|
|
120
|
+
}
|
|
121
|
+
function getMeshNamesForAUProfile(profile, auId) {
|
|
122
|
+
const morphToMesh = profile.morphToMesh || {};
|
|
123
|
+
const facePart = profile.auInfo?.[String(auId)]?.facePart;
|
|
124
|
+
const category = facePart ? profile.auFacePartToMeshCategory?.[facePart] : void 0;
|
|
125
|
+
if (category) return Array.isArray(morphToMesh[category]) ? [...morphToMesh[category]] : [];
|
|
126
|
+
return [...morphToMesh.face || []];
|
|
127
|
+
}
|
|
128
|
+
function compileMatcher(pattern) {
|
|
129
|
+
try {
|
|
130
|
+
return new RegExp(pattern, "i");
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function classifyVisemeMorph(morphName, profile) {
|
|
136
|
+
const slots = getProfileVisemeSlots(profile);
|
|
137
|
+
const matches = [];
|
|
138
|
+
for (const slot of slots) {
|
|
139
|
+
const explicitTargets = bindingTargets(profile.visemeBindings?.[slot.id]);
|
|
140
|
+
if (explicitTargets.some((target) => String(target).toLowerCase() === morphName.toLowerCase())) {
|
|
141
|
+
matches.push({
|
|
142
|
+
slotId: slot.id,
|
|
143
|
+
label: slot.label,
|
|
144
|
+
confidence: 1,
|
|
145
|
+
reason: "explicit"
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (const pattern of slot.matchers || []) {
|
|
150
|
+
const matcher = compileMatcher(pattern);
|
|
151
|
+
if (matcher?.test(morphName)) {
|
|
152
|
+
matches.push({
|
|
153
|
+
slotId: slot.id,
|
|
154
|
+
label: slot.label,
|
|
155
|
+
confidence: 0.75,
|
|
156
|
+
reason: "regex",
|
|
157
|
+
pattern
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return matches;
|
|
163
|
+
}
|
|
164
|
+
function buildMappingEditorModel(profile, morphNames = []) {
|
|
165
|
+
const sections = /* @__PURE__ */ new Map();
|
|
166
|
+
const configuredSections = profile.mappingSections || [];
|
|
167
|
+
const configuredById = new Map(configuredSections.map((section) => [section.id, section]));
|
|
168
|
+
let nextOrder = configuredSections.length;
|
|
169
|
+
for (const section of configuredSections) {
|
|
170
|
+
sections.set(section.id, { ...section });
|
|
171
|
+
}
|
|
172
|
+
const getOrder = (id, fallback) => {
|
|
173
|
+
const configured = configuredById.get(id);
|
|
174
|
+
if (configured) return configured.order;
|
|
175
|
+
if (fallback !== void 0) return fallback;
|
|
176
|
+
const order = nextOrder;
|
|
177
|
+
nextOrder += 1;
|
|
178
|
+
return order;
|
|
179
|
+
};
|
|
180
|
+
const auSectionOrders = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const [auId, info] of Object.entries(profile.auInfo || {})) {
|
|
182
|
+
const label = info.facePart || "Unmapped";
|
|
183
|
+
auSectionOrders.set(label, Math.min(auSectionOrders.get(label) ?? Number.MAX_SAFE_INTEGER, Number(auId)));
|
|
184
|
+
}
|
|
185
|
+
if (configuredSections.length === 0 && auSectionOrders.size > 0) {
|
|
186
|
+
nextOrder = Math.max(...auSectionOrders.values()) + 1;
|
|
187
|
+
}
|
|
188
|
+
for (const info of Object.values(profile.auInfo || {})) {
|
|
189
|
+
const label = info.facePart || "Unmapped";
|
|
190
|
+
const configured = configuredById.get(label);
|
|
191
|
+
const meshCategory = profile.auFacePartToMeshCategory?.[label] || "face";
|
|
192
|
+
sections.set(label, {
|
|
193
|
+
...configured,
|
|
194
|
+
id: label,
|
|
195
|
+
label: configured?.label || label,
|
|
196
|
+
kind: "au",
|
|
197
|
+
order: getOrder(label, auSectionOrders.get(label)),
|
|
198
|
+
meshCategory: configured?.meshCategory || meshCategory,
|
|
199
|
+
facePart: label
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const configuredVisemes = configuredById.get("Visemes");
|
|
203
|
+
sections.set("Visemes", {
|
|
204
|
+
...configuredVisemes,
|
|
205
|
+
id: "Visemes",
|
|
206
|
+
label: configuredVisemes?.label || "Visemes",
|
|
207
|
+
kind: "viseme",
|
|
208
|
+
order: getOrder("Visemes"),
|
|
209
|
+
meshCategory: configuredVisemes?.meshCategory || resolveVisemeMeshCategory(profile)
|
|
210
|
+
});
|
|
211
|
+
if (hasOwn(profile.morphToMesh, "hair")) {
|
|
212
|
+
const configuredHair = configuredById.get("Hair");
|
|
213
|
+
sections.set("Hair", {
|
|
214
|
+
...configuredHair,
|
|
215
|
+
id: "Hair",
|
|
216
|
+
label: configuredHair?.label || "Hair",
|
|
217
|
+
kind: "hair",
|
|
218
|
+
order: getOrder("Hair"),
|
|
219
|
+
meshCategory: configuredHair?.meshCategory || "hair"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const configuredUnmapped = configuredById.get("Unmapped");
|
|
223
|
+
sections.set("Unmapped", {
|
|
224
|
+
...configuredUnmapped,
|
|
225
|
+
id: "Unmapped",
|
|
226
|
+
label: configuredUnmapped?.label || "Unmapped",
|
|
227
|
+
kind: "unmapped",
|
|
228
|
+
order: getOrder("Unmapped"),
|
|
229
|
+
meshCategory: configuredUnmapped?.meshCategory || "face"
|
|
230
|
+
});
|
|
231
|
+
const candidates = morphNames.map((morph) => {
|
|
232
|
+
const matches = classifyVisemeMorph(morph, profile);
|
|
233
|
+
if (matches.length === 0) {
|
|
234
|
+
return { morph, sectionId: "Unmapped", kind: "unmapped", matches };
|
|
235
|
+
}
|
|
236
|
+
const explicit = matches.filter((match) => match.reason === "explicit");
|
|
237
|
+
if (explicit.length > 0) {
|
|
238
|
+
return { morph, sectionId: "Visemes", kind: "explicit", matches: explicit };
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
morph,
|
|
242
|
+
sectionId: "Visemes",
|
|
243
|
+
kind: matches.length > 1 ? "conflict" : "candidate",
|
|
244
|
+
matches
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
sections: Array.from(sections.values()).sort((a, b) => a.order - b.order || a.label.localeCompare(b.label)),
|
|
249
|
+
candidates
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function mapProviderVisemeToSlot(profile, event) {
|
|
253
|
+
const slots = getProfileVisemeSlots(profile);
|
|
254
|
+
const provider = event.provider.toLowerCase();
|
|
255
|
+
if (event.id !== void 0) {
|
|
256
|
+
const id = String(event.id);
|
|
257
|
+
const index = slots.findIndex(
|
|
258
|
+
(slot) => (slot.providerIds?.[provider] || []).some((candidate) => String(candidate) === id)
|
|
259
|
+
);
|
|
260
|
+
if (index >= 0) {
|
|
261
|
+
return { slotId: slots[index].id, index, confidence: 1, reason: "provider" };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (event.phoneme) {
|
|
265
|
+
const phoneme = event.phoneme.toLowerCase();
|
|
266
|
+
const index = slots.findIndex(
|
|
267
|
+
(slot) => (slot.phonemes || []).some((candidate) => candidate.toLowerCase() === phoneme)
|
|
268
|
+
);
|
|
269
|
+
if (index >= 0) {
|
|
270
|
+
return { slotId: slots[index].id, index, confidence: 0.8, reason: "phoneme" };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const restIndex = slots.findIndex((slot) => slot.id === "rest" || slot.features?.lipClosed === 1);
|
|
274
|
+
if (restIndex >= 0) {
|
|
275
|
+
return { slotId: slots[restIndex].id, index: restIndex, confidence: 0.25, reason: "rest" };
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
63
280
|
// src/engines/three/balanceUtils.ts
|
|
64
281
|
function clampBalance(value) {
|
|
65
282
|
if (!Number.isFinite(value)) return 0;
|
|
@@ -293,14 +510,19 @@ var Y_AXIS = new THREE2.Vector3(0, 1, 0);
|
|
|
293
510
|
var Z_AXIS = new THREE2.Vector3(0, 0, 1);
|
|
294
511
|
var CLIP_EVENT_METADATA_KEY = "__loom3ClipEvents";
|
|
295
512
|
var CLIP_EVENT_EPSILON = 1e-4;
|
|
513
|
+
var ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX = "__loom3_additive_delta";
|
|
296
514
|
var BakedAnimationController = class {
|
|
297
515
|
constructor(host) {
|
|
298
516
|
__publicField(this, "host");
|
|
517
|
+
// Clip-backed snippets need a later mixer pass so they can override baked additive tracks.
|
|
299
518
|
__publicField(this, "animationMixer", null);
|
|
519
|
+
__publicField(this, "clipAnimationMixer", null);
|
|
300
520
|
__publicField(this, "mixerFinishedListenerAttached", false);
|
|
521
|
+
__publicField(this, "clipMixerFinishedListenerAttached", false);
|
|
301
522
|
__publicField(this, "animationClips", []);
|
|
302
523
|
__publicField(this, "bakedSourceClips", /* @__PURE__ */ new Map());
|
|
303
524
|
__publicField(this, "bakedRuntimeActions", /* @__PURE__ */ new Map());
|
|
525
|
+
__publicField(this, "bakedAdditiveRuntimeClips", /* @__PURE__ */ new Map());
|
|
304
526
|
__publicField(this, "bakedActionGroups", /* @__PURE__ */ new Map());
|
|
305
527
|
__publicField(this, "bakedRuntimeClipToSource", /* @__PURE__ */ new Map());
|
|
306
528
|
__publicField(this, "animationActions", /* @__PURE__ */ new Map());
|
|
@@ -325,6 +547,173 @@ var BakedAnimationController = class {
|
|
|
325
547
|
action.__actionId = actionId;
|
|
326
548
|
return actionId;
|
|
327
549
|
}
|
|
550
|
+
clearActionId(action) {
|
|
551
|
+
if (!action) return;
|
|
552
|
+
const actionId = this.getActionId(action);
|
|
553
|
+
if (actionId) {
|
|
554
|
+
this.actionIdToClip.delete(actionId);
|
|
555
|
+
}
|
|
556
|
+
this.actionIds.delete(action);
|
|
557
|
+
delete action.__actionId;
|
|
558
|
+
}
|
|
559
|
+
uncacheClip(clip, mixer = this.animationMixer) {
|
|
560
|
+
if (!clip || !mixer) return;
|
|
561
|
+
try {
|
|
562
|
+
mixer.uncacheClip(clip);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
uncacheAction(action, mixer = this.animationMixer) {
|
|
567
|
+
if (!action || !mixer) return;
|
|
568
|
+
try {
|
|
569
|
+
const clip = action.getClip();
|
|
570
|
+
if (clip) {
|
|
571
|
+
mixer.uncacheAction(clip);
|
|
572
|
+
mixer.uncacheClip(clip);
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
releaseBakedRuntimeAction(runtimeClipName) {
|
|
578
|
+
const action = this.bakedRuntimeActions.get(runtimeClipName);
|
|
579
|
+
if (!action) return;
|
|
580
|
+
try {
|
|
581
|
+
action.stop();
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
this.uncacheAction(action);
|
|
585
|
+
this.clearActionId(action);
|
|
586
|
+
this.bakedRuntimeActions.delete(runtimeClipName);
|
|
587
|
+
}
|
|
588
|
+
clearBakedAdditiveRuntimeClip(runtimeClipName) {
|
|
589
|
+
const clip = this.bakedAdditiveRuntimeClips.get(runtimeClipName);
|
|
590
|
+
if (!clip) return;
|
|
591
|
+
this.uncacheClip(clip);
|
|
592
|
+
this.bakedAdditiveRuntimeClips.delete(runtimeClipName);
|
|
593
|
+
}
|
|
594
|
+
clearAllBakedAdditiveRuntimeClips() {
|
|
595
|
+
for (const runtimeClipName of Array.from(this.bakedAdditiveRuntimeClips.keys())) {
|
|
596
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClipName);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
resolveTrackTarget(model, parsed) {
|
|
600
|
+
const targetKey = parsed.objectName === "bones" && parsed.objectIndex ? parsed.objectIndex : parsed.nodeName;
|
|
601
|
+
if (!targetKey) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
return model.getObjectByProperty("uuid", targetKey) ?? THREE2.PropertyBinding.findNode(model, targetKey) ?? null;
|
|
605
|
+
}
|
|
606
|
+
getMorphTrackBaseValue(target, propertyIndex) {
|
|
607
|
+
if (!target) {
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
const meshTarget = target;
|
|
611
|
+
const influences = meshTarget.morphTargetInfluences;
|
|
612
|
+
if (!influences) {
|
|
613
|
+
return 0;
|
|
614
|
+
}
|
|
615
|
+
let morphIndex;
|
|
616
|
+
if (typeof propertyIndex === "number" && Number.isInteger(propertyIndex)) {
|
|
617
|
+
morphIndex = propertyIndex;
|
|
618
|
+
} else if (typeof propertyIndex === "string") {
|
|
619
|
+
if (/^\d+$/.test(propertyIndex)) {
|
|
620
|
+
morphIndex = Number(propertyIndex);
|
|
621
|
+
} else {
|
|
622
|
+
morphIndex = meshTarget.morphTargetDictionary?.[propertyIndex];
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (morphIndex === void 0) {
|
|
626
|
+
return 0;
|
|
627
|
+
}
|
|
628
|
+
return influences[morphIndex] ?? 0;
|
|
629
|
+
}
|
|
630
|
+
canCreateFirstFrameReferenceTrack(track) {
|
|
631
|
+
const valueSize = track.getValueSize();
|
|
632
|
+
if (!Number.isFinite(valueSize) || valueSize <= 0 || track.values.length < valueSize) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
return track.ValueTypeName === "number" || track.ValueTypeName === "quaternion" || track.ValueTypeName === "vector";
|
|
636
|
+
}
|
|
637
|
+
createFirstFrameReferenceTrack(track) {
|
|
638
|
+
const valueSize = track.getValueSize();
|
|
639
|
+
if (!this.canCreateFirstFrameReferenceTrack(track)) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const values = Array.from(track.values.slice(0, valueSize));
|
|
643
|
+
if (track.ValueTypeName === "number") {
|
|
644
|
+
return new THREE2.NumberKeyframeTrack(track.name, [0], values);
|
|
645
|
+
}
|
|
646
|
+
if (track.ValueTypeName === "quaternion") {
|
|
647
|
+
return new THREE2.QuaternionKeyframeTrack(track.name, [0], values);
|
|
648
|
+
}
|
|
649
|
+
if (track.ValueTypeName === "vector") {
|
|
650
|
+
return new THREE2.VectorKeyframeTrack(track.name, [0], values);
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
createAdditiveReferenceTrack(track, model) {
|
|
655
|
+
const trackName = typeof track?.name === "string" ? track.name : "";
|
|
656
|
+
if (!trackName) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
let parsed;
|
|
660
|
+
try {
|
|
661
|
+
parsed = THREE2.PropertyBinding.parseTrackName(trackName);
|
|
662
|
+
} catch {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
const target = this.resolveTrackTarget(model, parsed);
|
|
666
|
+
if (parsed.propertyName === "morphTargetInfluences") {
|
|
667
|
+
return new THREE2.NumberKeyframeTrack(
|
|
668
|
+
track.name,
|
|
669
|
+
[0],
|
|
670
|
+
[this.getMorphTrackBaseValue(target, parsed.propertyIndex)]
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return this.createFirstFrameReferenceTrack(track);
|
|
674
|
+
}
|
|
675
|
+
createAdditiveRuntimeClip(runtimeClip) {
|
|
676
|
+
const model = this.host.getModel();
|
|
677
|
+
if (!model) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
const additiveTracks = [];
|
|
681
|
+
const referenceTracks = [];
|
|
682
|
+
for (const track of runtimeClip.tracks) {
|
|
683
|
+
const referenceTrack = this.createAdditiveReferenceTrack(track, model);
|
|
684
|
+
if (!referenceTrack) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
additiveTracks.push(track.clone());
|
|
688
|
+
referenceTracks.push(referenceTrack);
|
|
689
|
+
}
|
|
690
|
+
const additiveClip = new THREE2.AnimationClip(
|
|
691
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}`,
|
|
692
|
+
runtimeClip.duration,
|
|
693
|
+
additiveTracks
|
|
694
|
+
);
|
|
695
|
+
if (additiveTracks.length > 0) {
|
|
696
|
+
const referenceClip = new THREE2.AnimationClip(
|
|
697
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}_reference`,
|
|
698
|
+
0,
|
|
699
|
+
referenceTracks
|
|
700
|
+
);
|
|
701
|
+
THREE2.AnimationUtils.makeClipAdditive(additiveClip, 0, referenceClip);
|
|
702
|
+
}
|
|
703
|
+
return additiveClip;
|
|
704
|
+
}
|
|
705
|
+
getOrCreateBakedAdditiveRuntimeClip(runtimeClip) {
|
|
706
|
+
const cached = this.bakedAdditiveRuntimeClips.get(runtimeClip.name);
|
|
707
|
+
if (cached) {
|
|
708
|
+
return cached;
|
|
709
|
+
}
|
|
710
|
+
const additiveClip = this.createAdditiveRuntimeClip(runtimeClip);
|
|
711
|
+
if (!additiveClip) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
this.bakedAdditiveRuntimeClips.set(runtimeClip.name, additiveClip);
|
|
715
|
+
return additiveClip;
|
|
716
|
+
}
|
|
328
717
|
setClipEventMetadata(clip, metadata) {
|
|
329
718
|
const userData = clip.userData ?? (clip.userData = {});
|
|
330
719
|
userData[CLIP_EVENT_METADATA_KEY] = metadata;
|
|
@@ -610,21 +999,28 @@ var BakedAnimationController = class {
|
|
|
610
999
|
}
|
|
611
1000
|
return 0;
|
|
612
1001
|
}
|
|
613
|
-
getOrCreateBakedRuntimeAction(sourceClipName, channel) {
|
|
1002
|
+
getOrCreateBakedRuntimeAction(sourceClipName, channel, blendMode = "replace") {
|
|
614
1003
|
const bakedClip = this.getBakedSourceClip(sourceClipName);
|
|
615
1004
|
const runtimeClip = bakedClip?.runtimeClips.find((entry) => entry.channel === channel)?.clip;
|
|
616
1005
|
if (!runtimeClip) {
|
|
617
1006
|
return null;
|
|
618
1007
|
}
|
|
1008
|
+
const desiredClip = blendMode === "additive" ? this.getOrCreateBakedAdditiveRuntimeClip(runtimeClip) : runtimeClip;
|
|
1009
|
+
if (!desiredClip) {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
619
1012
|
const existing = this.bakedRuntimeActions.get(runtimeClip.name);
|
|
620
|
-
if (existing) {
|
|
1013
|
+
if (existing?.getClip() === desiredClip) {
|
|
621
1014
|
return existing;
|
|
622
1015
|
}
|
|
623
1016
|
this.ensureMixer();
|
|
624
1017
|
if (!this.animationMixer) {
|
|
625
1018
|
return null;
|
|
626
1019
|
}
|
|
627
|
-
|
|
1020
|
+
if (existing) {
|
|
1021
|
+
this.releaseBakedRuntimeAction(runtimeClip.name);
|
|
1022
|
+
}
|
|
1023
|
+
const action = this.animationMixer.clipAction(desiredClip);
|
|
628
1024
|
this.bakedRuntimeActions.set(runtimeClip.name, action);
|
|
629
1025
|
return action;
|
|
630
1026
|
}
|
|
@@ -642,7 +1038,15 @@ var BakedAnimationController = class {
|
|
|
642
1038
|
}
|
|
643
1039
|
const channelActions = /* @__PURE__ */ new Map();
|
|
644
1040
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
645
|
-
const
|
|
1041
|
+
const channelBlendMode = resolveBakedChannelBlendMode(
|
|
1042
|
+
runtimeClip.channel,
|
|
1043
|
+
playbackState.requestedBlendMode
|
|
1044
|
+
) ?? "replace";
|
|
1045
|
+
const action = this.getOrCreateBakedRuntimeAction(
|
|
1046
|
+
clipName,
|
|
1047
|
+
runtimeClip.channel,
|
|
1048
|
+
channelBlendMode
|
|
1049
|
+
);
|
|
646
1050
|
if (action) {
|
|
647
1051
|
channelActions.set(runtimeClip.channel, action);
|
|
648
1052
|
}
|
|
@@ -670,12 +1074,7 @@ var BakedAnimationController = class {
|
|
|
670
1074
|
if (typeof this.host.getMeshNamesForAU === "function") {
|
|
671
1075
|
return this.host.getMeshNamesForAU(auId) || [];
|
|
672
1076
|
}
|
|
673
|
-
|
|
674
|
-
if (facePart) {
|
|
675
|
-
const category = config.auFacePartToMeshCategory?.[facePart];
|
|
676
|
-
if (category) return config.morphToMesh?.[category] || [];
|
|
677
|
-
}
|
|
678
|
-
return config.morphToMesh?.face || [];
|
|
1077
|
+
return getMeshNamesForAUProfile(config, auId);
|
|
679
1078
|
}
|
|
680
1079
|
getMeshNamesForViseme(config, explicitMeshNames) {
|
|
681
1080
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
@@ -684,18 +1083,41 @@ var BakedAnimationController = class {
|
|
|
684
1083
|
if (typeof this.host.getMeshNamesForViseme === "function") {
|
|
685
1084
|
return this.host.getMeshNamesForViseme() || [];
|
|
686
1085
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1086
|
+
return getMeshNamesForVisemeProfile(config);
|
|
1087
|
+
}
|
|
1088
|
+
hasActiveAdditivePlayback() {
|
|
1089
|
+
for (const [clipName, group] of this.bakedActionGroups) {
|
|
1090
|
+
const state = this.playbackState.get(clipName);
|
|
1091
|
+
if (state?.blendMode !== "additive") {
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
for (const action of group.channelActions.values()) {
|
|
1095
|
+
if (action.isRunning() && !action.paused) {
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
for (const [clipName, action] of this.animationActions) {
|
|
1101
|
+
const state = this.playbackState.get(clipName);
|
|
1102
|
+
if (state?.blendMode !== "additive") {
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (action.isRunning() && !action.paused) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return false;
|
|
691
1110
|
}
|
|
692
1111
|
update(dtSeconds) {
|
|
693
1112
|
if (this.animationMixer) {
|
|
1113
|
+
this.animationMixer.update(dtSeconds);
|
|
1114
|
+
}
|
|
1115
|
+
if (this.clipAnimationMixer) {
|
|
694
1116
|
const snapshots = Array.from(this.clipMonitors.values()).map((monitor) => ({
|
|
695
1117
|
actionId: monitor.actionId,
|
|
696
1118
|
previousTime: monitor.action.time
|
|
697
1119
|
}));
|
|
698
|
-
this.
|
|
1120
|
+
this.clipAnimationMixer.update(dtSeconds);
|
|
699
1121
|
for (const { actionId, previousTime } of snapshots) {
|
|
700
1122
|
const monitor = this.clipMonitors.get(actionId);
|
|
701
1123
|
if (!monitor) continue;
|
|
@@ -714,16 +1136,27 @@ var BakedAnimationController = class {
|
|
|
714
1136
|
}
|
|
715
1137
|
}
|
|
716
1138
|
}
|
|
1139
|
+
if (this.hasActiveAdditivePlayback()) {
|
|
1140
|
+
this.host.reapplyProceduralState?.();
|
|
1141
|
+
}
|
|
717
1142
|
}
|
|
718
1143
|
dispose() {
|
|
719
1144
|
this.stopAllAnimations();
|
|
1145
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
720
1146
|
if (this.animationMixer) {
|
|
721
1147
|
this.animationMixer.stopAllAction();
|
|
722
1148
|
this.animationMixer = null;
|
|
723
1149
|
}
|
|
1150
|
+
if (this.clipAnimationMixer) {
|
|
1151
|
+
this.clipAnimationMixer.stopAllAction();
|
|
1152
|
+
this.clipAnimationMixer = null;
|
|
1153
|
+
}
|
|
1154
|
+
this.mixerFinishedListenerAttached = false;
|
|
1155
|
+
this.clipMixerFinishedListenerAttached = false;
|
|
724
1156
|
this.animationClips = [];
|
|
725
1157
|
this.bakedSourceClips.clear();
|
|
726
1158
|
this.bakedRuntimeActions.clear();
|
|
1159
|
+
this.bakedAdditiveRuntimeClips.clear();
|
|
727
1160
|
this.bakedActionGroups.clear();
|
|
728
1161
|
this.bakedRuntimeClipToSource.clear();
|
|
729
1162
|
this.animationActions.clear();
|
|
@@ -746,6 +1179,8 @@ var BakedAnimationController = class {
|
|
|
746
1179
|
if (this.animationMixer) {
|
|
747
1180
|
for (const bakedClip of this.bakedSourceClips.values()) {
|
|
748
1181
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1182
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1183
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
749
1184
|
try {
|
|
750
1185
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
751
1186
|
} catch {
|
|
@@ -757,6 +1192,7 @@ var BakedAnimationController = class {
|
|
|
757
1192
|
}
|
|
758
1193
|
}
|
|
759
1194
|
}
|
|
1195
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
760
1196
|
for (const clipName of this.bakedSourceClips.keys()) {
|
|
761
1197
|
this.playbackState.delete(clipName);
|
|
762
1198
|
this.clipSources.delete(clipName);
|
|
@@ -797,6 +1233,8 @@ var BakedAnimationController = class {
|
|
|
797
1233
|
if (this.animationMixer) {
|
|
798
1234
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
799
1235
|
const action = this.bakedRuntimeActions.get(runtimeClip.clip.name);
|
|
1236
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1237
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
800
1238
|
try {
|
|
801
1239
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
802
1240
|
} catch {
|
|
@@ -805,7 +1243,6 @@ var BakedAnimationController = class {
|
|
|
805
1243
|
this.animationMixer.uncacheClip(runtimeClip.clip);
|
|
806
1244
|
} catch {
|
|
807
1245
|
}
|
|
808
|
-
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
809
1246
|
this.bakedRuntimeClipToSource.delete(runtimeClip.clip.name);
|
|
810
1247
|
const actionId = this.getActionId(action);
|
|
811
1248
|
if (actionId && action) {
|
|
@@ -814,6 +1251,10 @@ var BakedAnimationController = class {
|
|
|
814
1251
|
}
|
|
815
1252
|
}
|
|
816
1253
|
}
|
|
1254
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1255
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
1256
|
+
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
1257
|
+
}
|
|
817
1258
|
this.animationClips = this.animationClips.filter((entry) => entry.name !== clipName);
|
|
818
1259
|
this.bakedSourceClips.delete(clipName);
|
|
819
1260
|
this.bakedActionGroups.delete(clipName);
|
|
@@ -874,12 +1315,12 @@ var BakedAnimationController = class {
|
|
|
874
1315
|
const actionId = this.getActionId(action);
|
|
875
1316
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
876
1317
|
action.stop();
|
|
877
|
-
if (!isBaked && this.
|
|
1318
|
+
if (!isBaked && this.clipAnimationMixer) {
|
|
878
1319
|
try {
|
|
879
1320
|
const clip = action.getClip();
|
|
880
1321
|
if (clip) {
|
|
881
|
-
this.
|
|
882
|
-
this.
|
|
1322
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1323
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
883
1324
|
}
|
|
884
1325
|
} catch {
|
|
885
1326
|
}
|
|
@@ -901,11 +1342,11 @@ var BakedAnimationController = class {
|
|
|
901
1342
|
const actionId = this.getActionId(clipAction);
|
|
902
1343
|
try {
|
|
903
1344
|
clipAction.stop();
|
|
904
|
-
if (this.
|
|
1345
|
+
if (this.clipAnimationMixer) {
|
|
905
1346
|
const clip = clipAction.getClip();
|
|
906
1347
|
if (clip) {
|
|
907
|
-
this.
|
|
908
|
-
this.
|
|
1348
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1349
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
909
1350
|
}
|
|
910
1351
|
}
|
|
911
1352
|
} catch {
|
|
@@ -1109,8 +1550,23 @@ var BakedAnimationController = class {
|
|
|
1109
1550
|
next.blendMode = this.getBakedAggregateBlendMode(clipName, next);
|
|
1110
1551
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1111
1552
|
if (bakedGroup) {
|
|
1112
|
-
for (const [channel,
|
|
1553
|
+
for (const [channel, currentAction] of Array.from(bakedGroup.channelActions)) {
|
|
1554
|
+
const channelBlendMode = resolveBakedChannelBlendMode(channel, next.requestedBlendMode) ?? "replace";
|
|
1555
|
+
const previousTime = currentAction.time;
|
|
1556
|
+
const wasActive = currentAction.isRunning() || currentAction.paused;
|
|
1557
|
+
const wasPaused = currentAction.paused;
|
|
1558
|
+
const action2 = this.getOrCreateBakedRuntimeAction(clipName, channel, channelBlendMode);
|
|
1559
|
+
if (!action2) {
|
|
1560
|
+
bakedGroup.channelActions.delete(channel);
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1113
1563
|
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
1564
|
+
action2.time = Math.max(0, Math.min(action2.getClip().duration, previousTime));
|
|
1565
|
+
if (action2 !== currentAction && wasActive) {
|
|
1566
|
+
action2.play();
|
|
1567
|
+
}
|
|
1568
|
+
action2.paused = wasPaused;
|
|
1569
|
+
bakedGroup.channelActions.set(channel, action2);
|
|
1114
1570
|
}
|
|
1115
1571
|
}
|
|
1116
1572
|
this.setPlaybackState(clipName, next);
|
|
@@ -1125,6 +1581,10 @@ var BakedAnimationController = class {
|
|
|
1125
1581
|
seekAnimation(clipName, time) {
|
|
1126
1582
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1127
1583
|
if (bakedGroup) {
|
|
1584
|
+
const state = this.getPlaybackStateSnapshot(clipName, {
|
|
1585
|
+
loop: true,
|
|
1586
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
1587
|
+
});
|
|
1128
1588
|
const duration2 = this.getBakedSourceClip(clipName)?.sourceClip.duration ?? 0;
|
|
1129
1589
|
const clamped = Math.max(0, Math.min(duration2, Number.isFinite(time) ? time : 0));
|
|
1130
1590
|
for (const action2 of bakedGroup.channelActions.values()) {
|
|
@@ -1132,6 +1592,10 @@ var BakedAnimationController = class {
|
|
|
1132
1592
|
}
|
|
1133
1593
|
try {
|
|
1134
1594
|
this.animationMixer?.update(0);
|
|
1595
|
+
this.clipAnimationMixer?.update(0);
|
|
1596
|
+
if (state.blendMode === "additive") {
|
|
1597
|
+
this.host.reapplyProceduralState?.();
|
|
1598
|
+
}
|
|
1135
1599
|
} catch {
|
|
1136
1600
|
}
|
|
1137
1601
|
return;
|
|
@@ -1141,7 +1605,11 @@ var BakedAnimationController = class {
|
|
|
1141
1605
|
const duration = action.getClip().duration;
|
|
1142
1606
|
action.time = Math.max(0, Math.min(duration, Number.isFinite(time) ? time : 0));
|
|
1143
1607
|
try {
|
|
1144
|
-
this.
|
|
1608
|
+
this.clipAnimationMixer?.update(0);
|
|
1609
|
+
const state = this.playbackState.get(clipName);
|
|
1610
|
+
if (state?.blendMode === "additive") {
|
|
1611
|
+
this.host.reapplyProceduralState?.();
|
|
1612
|
+
}
|
|
1145
1613
|
} catch {
|
|
1146
1614
|
}
|
|
1147
1615
|
}
|
|
@@ -1149,6 +1617,9 @@ var BakedAnimationController = class {
|
|
|
1149
1617
|
if (this.animationMixer) {
|
|
1150
1618
|
this.animationMixer.timeScale = timeScale;
|
|
1151
1619
|
}
|
|
1620
|
+
if (this.clipAnimationMixer) {
|
|
1621
|
+
this.clipAnimationMixer.timeScale = timeScale;
|
|
1622
|
+
}
|
|
1152
1623
|
}
|
|
1153
1624
|
getAnimationState(clipName) {
|
|
1154
1625
|
const bakedClip = this.getBakedSourceClip(clipName);
|
|
@@ -1478,8 +1949,8 @@ var BakedAnimationController = class {
|
|
|
1478
1949
|
return clip;
|
|
1479
1950
|
}
|
|
1480
1951
|
playClip(clip, options) {
|
|
1481
|
-
this.
|
|
1482
|
-
if (!
|
|
1952
|
+
const mixer = this.ensureClipMixer();
|
|
1953
|
+
if (!mixer) {
|
|
1483
1954
|
console.warn("[Loom3] playClip: No model loaded, cannot create mixer");
|
|
1484
1955
|
return null;
|
|
1485
1956
|
}
|
|
@@ -1497,7 +1968,7 @@ var BakedAnimationController = class {
|
|
|
1497
1968
|
actionId = this.setActionId(action, clip.name);
|
|
1498
1969
|
}
|
|
1499
1970
|
if (!action) {
|
|
1500
|
-
action =
|
|
1971
|
+
action = mixer.clipAction(clip);
|
|
1501
1972
|
actionId = this.setActionId(action, clip.name);
|
|
1502
1973
|
}
|
|
1503
1974
|
const existingClip = this.animationClips.find((c) => c.name === clip.name);
|
|
@@ -1558,15 +2029,13 @@ var BakedAnimationController = class {
|
|
|
1558
2029
|
},
|
|
1559
2030
|
stop: () => {
|
|
1560
2031
|
action.stop();
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
} catch {
|
|
1569
|
-
}
|
|
2032
|
+
try {
|
|
2033
|
+
mixer.uncacheAction(clip);
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
mixer.uncacheClip(clip);
|
|
2038
|
+
} catch {
|
|
1570
2039
|
}
|
|
1571
2040
|
this.clipActions.delete(clip.name);
|
|
1572
2041
|
this.animationActions.delete(clip.name);
|
|
@@ -1607,7 +2076,7 @@ var BakedAnimationController = class {
|
|
|
1607
2076
|
const clamped = Math.max(0, Math.min(clip.duration, t));
|
|
1608
2077
|
action.time = clamped;
|
|
1609
2078
|
try {
|
|
1610
|
-
|
|
2079
|
+
mixer.update(0);
|
|
1611
2080
|
} catch {
|
|
1612
2081
|
}
|
|
1613
2082
|
this.syncClipMonitorTime(monitor, clamped, true);
|
|
@@ -1640,16 +2109,16 @@ var BakedAnimationController = class {
|
|
|
1640
2109
|
return this.playClip(clip, { ...options, source: options?.source ?? "clip" });
|
|
1641
2110
|
}
|
|
1642
2111
|
cleanupSnippet(name) {
|
|
1643
|
-
if (!this.
|
|
2112
|
+
if (!this.host.getModel()) return;
|
|
1644
2113
|
for (const [clipName, action] of Array.from(this.clipActions.entries())) {
|
|
1645
2114
|
if (clipName === name || clipName.startsWith(`${name}_`)) {
|
|
1646
2115
|
const actionId = this.getActionId(action);
|
|
1647
2116
|
try {
|
|
1648
2117
|
action.stop();
|
|
1649
2118
|
const clip = action.getClip();
|
|
1650
|
-
if (clip) {
|
|
1651
|
-
this.
|
|
1652
|
-
this.
|
|
2119
|
+
if (clip && this.clipAnimationMixer) {
|
|
2120
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
2121
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1653
2122
|
}
|
|
1654
2123
|
} catch {
|
|
1655
2124
|
}
|
|
@@ -1677,7 +2146,10 @@ var BakedAnimationController = class {
|
|
|
1677
2146
|
clipActions: Array.from(this.clipActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1678
2147
|
animationActions: Array.from(this.animationActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1679
2148
|
clipHandles: Array.from(this.clipHandles.entries()).map(([k, h]) => ({ name: k, actionId: h.actionId })),
|
|
1680
|
-
mixerActions:
|
|
2149
|
+
mixerActions: [
|
|
2150
|
+
...this.animationMixer?._actions || [],
|
|
2151
|
+
...this.clipAnimationMixer?._actions || []
|
|
2152
|
+
].map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.getActionId(a) }))
|
|
1681
2153
|
});
|
|
1682
2154
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
1683
2155
|
const apply = (action) => {
|
|
@@ -1740,7 +2212,7 @@ var BakedAnimationController = class {
|
|
|
1740
2212
|
}
|
|
1741
2213
|
addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
|
|
1742
2214
|
const config = this.host.getConfig();
|
|
1743
|
-
const hasExplicitMeshes =
|
|
2215
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1744
2216
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1745
2217
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1746
2218
|
const addTrackForMesh = (mesh) => {
|
|
@@ -1764,7 +2236,7 @@ var BakedAnimationController = class {
|
|
|
1764
2236
|
addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
|
|
1765
2237
|
if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
|
|
1766
2238
|
const config = this.host.getConfig();
|
|
1767
|
-
const hasExplicitMeshes =
|
|
2239
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1768
2240
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1769
2241
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1770
2242
|
const addTrackForMesh = (mesh) => {
|
|
@@ -1791,35 +2263,48 @@ var BakedAnimationController = class {
|
|
|
1791
2263
|
this.animationMixer = new THREE2.AnimationMixer(model);
|
|
1792
2264
|
}
|
|
1793
2265
|
if (this.animationMixer && !this.mixerFinishedListenerAttached) {
|
|
1794
|
-
this.animationMixer.addEventListener("finished", (event) =>
|
|
1795
|
-
const action = event.action;
|
|
1796
|
-
const actionId = this.getActionId(action);
|
|
1797
|
-
if (actionId) {
|
|
1798
|
-
const monitor = this.clipMonitors.get(actionId);
|
|
1799
|
-
if (monitor) {
|
|
1800
|
-
monitor.finishedPending = true;
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
const clip = action.getClip();
|
|
1805
|
-
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
1806
|
-
if (bakedRuntime) {
|
|
1807
|
-
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
1808
|
-
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
1809
|
-
group.resolveFinished();
|
|
1810
|
-
}
|
|
1811
|
-
return;
|
|
1812
|
-
}
|
|
1813
|
-
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
1814
|
-
if (callback) {
|
|
1815
|
-
callback();
|
|
1816
|
-
this.animationFinishedCallbacks.delete(clip.name);
|
|
1817
|
-
}
|
|
1818
|
-
});
|
|
2266
|
+
this.animationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
1819
2267
|
this.mixerFinishedListenerAttached = true;
|
|
1820
2268
|
}
|
|
1821
2269
|
return this.animationMixer;
|
|
1822
2270
|
}
|
|
2271
|
+
ensureClipMixer() {
|
|
2272
|
+
const model = this.host.getModel();
|
|
2273
|
+
if (!model) return null;
|
|
2274
|
+
if (!this.clipAnimationMixer) {
|
|
2275
|
+
this.clipAnimationMixer = new THREE2.AnimationMixer(model);
|
|
2276
|
+
}
|
|
2277
|
+
if (this.clipAnimationMixer && !this.clipMixerFinishedListenerAttached) {
|
|
2278
|
+
this.clipAnimationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
2279
|
+
this.clipMixerFinishedListenerAttached = true;
|
|
2280
|
+
}
|
|
2281
|
+
return this.clipAnimationMixer;
|
|
2282
|
+
}
|
|
2283
|
+
handleMixerFinished(event) {
|
|
2284
|
+
const action = event.action;
|
|
2285
|
+
const actionId = this.getActionId(action);
|
|
2286
|
+
if (actionId) {
|
|
2287
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
2288
|
+
if (monitor) {
|
|
2289
|
+
monitor.finishedPending = true;
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
const clip = action.getClip();
|
|
2294
|
+
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
2295
|
+
if (bakedRuntime) {
|
|
2296
|
+
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
2297
|
+
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
2298
|
+
group.resolveFinished();
|
|
2299
|
+
}
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
2303
|
+
if (callback) {
|
|
2304
|
+
callback();
|
|
2305
|
+
this.animationFinishedCallbacks.delete(clip.name);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
1823
2308
|
createAnimationHandle(clipName, action, finishedPromise) {
|
|
1824
2309
|
return {
|
|
1825
2310
|
actionId: this.getActionId(action),
|
|
@@ -2398,6 +2883,159 @@ var VISEME_JAW_AMOUNTS = [
|
|
|
2398
2883
|
0.5
|
|
2399
2884
|
// 14: W_OO
|
|
2400
2885
|
];
|
|
2886
|
+
var CC4_VISEME_SYSTEM_ID = "cc4-arkit-15";
|
|
2887
|
+
var CC4_VISEME_SLOTS = [
|
|
2888
|
+
{
|
|
2889
|
+
id: "ae",
|
|
2890
|
+
label: "AE",
|
|
2891
|
+
order: 0,
|
|
2892
|
+
providerIds: { azure: [4], sapi: [4] },
|
|
2893
|
+
phonemes: ["AE", "EH", "EY", "UH"],
|
|
2894
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ae|eh|ey)([_ .-]|$)"],
|
|
2895
|
+
features: { jawOpen: 0.75, lipSpread: 0.35 },
|
|
2896
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[0]
|
|
2897
|
+
},
|
|
2898
|
+
{
|
|
2899
|
+
id: "ah",
|
|
2900
|
+
label: "Ah",
|
|
2901
|
+
order: 1,
|
|
2902
|
+
providerIds: { azure: [1, 2, 9, 11, 12], sapi: [1, 2, 9, 11, 12] },
|
|
2903
|
+
phonemes: ["AA", "AE", "AH", "AX", "AW", "AY", "HH"],
|
|
2904
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ah|aa|aah|open)([_ .-]|$)"],
|
|
2905
|
+
features: { jawOpen: 0.8 },
|
|
2906
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[1]
|
|
2907
|
+
},
|
|
2908
|
+
{
|
|
2909
|
+
id: "b-m-p",
|
|
2910
|
+
label: "B_M_P",
|
|
2911
|
+
order: 2,
|
|
2912
|
+
providerIds: { azure: [0, 21], sapi: [0, 21] },
|
|
2913
|
+
phonemes: ["B", "M", "P"],
|
|
2914
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(b[_ .-]?m[_ .-]?p|bmp|closed|sil|rest)([_ .-]|$)"],
|
|
2915
|
+
features: { jawOpen: 0, lipClosed: 1 },
|
|
2916
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[2]
|
|
2917
|
+
},
|
|
2918
|
+
{
|
|
2919
|
+
id: "ch-j",
|
|
2920
|
+
label: "Ch_J",
|
|
2921
|
+
order: 3,
|
|
2922
|
+
providerIds: { azure: [16], sapi: [16] },
|
|
2923
|
+
phonemes: ["CH", "JH", "SH", "ZH"],
|
|
2924
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ch|j|sh|zh)([_ .-]|$)"],
|
|
2925
|
+
features: { jawOpen: 0.3, fricative: 0.6 },
|
|
2926
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[3]
|
|
2927
|
+
},
|
|
2928
|
+
{
|
|
2929
|
+
id: "ee",
|
|
2930
|
+
label: "EE",
|
|
2931
|
+
order: 4,
|
|
2932
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2933
|
+
phonemes: ["IY", "IH", "IX", "Y"],
|
|
2934
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ee|iy|y)([_ .-]|$)"],
|
|
2935
|
+
features: { jawOpen: 0.2, lipSpread: 0.8 },
|
|
2936
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[4]
|
|
2937
|
+
},
|
|
2938
|
+
{
|
|
2939
|
+
id: "er",
|
|
2940
|
+
label: "Er",
|
|
2941
|
+
order: 5,
|
|
2942
|
+
providerIds: { azure: [5], sapi: [5] },
|
|
2943
|
+
phonemes: ["ER"],
|
|
2944
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(er)([_ .-]|$)"],
|
|
2945
|
+
features: { jawOpen: 0.35, lipRound: 0.35 },
|
|
2946
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[5]
|
|
2947
|
+
},
|
|
2948
|
+
{
|
|
2949
|
+
id: "f-v",
|
|
2950
|
+
label: "F_V",
|
|
2951
|
+
order: 6,
|
|
2952
|
+
providerIds: { azure: [18], sapi: [18] },
|
|
2953
|
+
phonemes: ["F", "V"],
|
|
2954
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(f[_ .-]?v|fv)([_ .-]|$)"],
|
|
2955
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2956
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[6]
|
|
2957
|
+
},
|
|
2958
|
+
{
|
|
2959
|
+
id: "ih",
|
|
2960
|
+
label: "Ih",
|
|
2961
|
+
order: 7,
|
|
2962
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2963
|
+
phonemes: ["IH", "IX"],
|
|
2964
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ih|ix)([_ .-]|$)"],
|
|
2965
|
+
features: { jawOpen: 0.2, lipSpread: 0.55 },
|
|
2966
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[7]
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
id: "k-g-h-ng",
|
|
2970
|
+
label: "K_G_H_NG",
|
|
2971
|
+
order: 8,
|
|
2972
|
+
providerIds: { azure: [20], sapi: [20] },
|
|
2973
|
+
phonemes: ["K", "G", "NG"],
|
|
2974
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(k[_ .-]?g[_ .-]?h?[_ .-]?ng|kg|ng)([_ .-]|$)"],
|
|
2975
|
+
features: { jawOpen: 0.35 },
|
|
2976
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[8]
|
|
2977
|
+
},
|
|
2978
|
+
{
|
|
2979
|
+
id: "oh",
|
|
2980
|
+
label: "Oh",
|
|
2981
|
+
order: 9,
|
|
2982
|
+
providerIds: { azure: [3, 8, 10], sapi: [3, 8, 10] },
|
|
2983
|
+
phonemes: ["AO", "OW", "OY"],
|
|
2984
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(oh|ao|ow|oy)([_ .-]|$)"],
|
|
2985
|
+
features: { jawOpen: 0.6, lipRound: 0.8 },
|
|
2986
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[9]
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
id: "r",
|
|
2990
|
+
label: "R",
|
|
2991
|
+
order: 10,
|
|
2992
|
+
providerIds: { azure: [13], sapi: [13] },
|
|
2993
|
+
phonemes: ["R"],
|
|
2994
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(r)([_ .-]|$)"],
|
|
2995
|
+
features: { jawOpen: 0.35, lipRound: 0.5 },
|
|
2996
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[10]
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
id: "s-z",
|
|
3000
|
+
label: "S_Z",
|
|
3001
|
+
order: 11,
|
|
3002
|
+
providerIds: { azure: [15], sapi: [15] },
|
|
3003
|
+
phonemes: ["S", "Z"],
|
|
3004
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(s[_ .-]?z|sz)([_ .-]|$)"],
|
|
3005
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
3006
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[11]
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
id: "t-l-d-n",
|
|
3010
|
+
label: "T_L_D_N",
|
|
3011
|
+
order: 12,
|
|
3012
|
+
providerIds: { azure: [14, 19], sapi: [14, 19] },
|
|
3013
|
+
phonemes: ["T", "L", "D", "N"],
|
|
3014
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(t[_ .-]?l[_ .-]?d[_ .-]?n|tldn|l)([_ .-]|$)"],
|
|
3015
|
+
features: { jawOpen: 0.3, tongueTip: 1 },
|
|
3016
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[12]
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
id: "th",
|
|
3020
|
+
label: "Th",
|
|
3021
|
+
order: 13,
|
|
3022
|
+
providerIds: { azure: [17], sapi: [17] },
|
|
3023
|
+
phonemes: ["TH", "DH"],
|
|
3024
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(th|dh)([_ .-]|$)"],
|
|
3025
|
+
features: { jawOpen: 0.15, tongueTip: 0.8, fricative: 0.8 },
|
|
3026
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[13]
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
id: "w-oo",
|
|
3030
|
+
label: "W_OO",
|
|
3031
|
+
order: 14,
|
|
3032
|
+
providerIds: { azure: [7], sapi: [7] },
|
|
3033
|
+
phonemes: ["W", "UW"],
|
|
3034
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(w[_ .-]?oo|woo|uw|oo)([_ .-]|$)"],
|
|
3035
|
+
features: { jawOpen: 0.5, lipRound: 1 },
|
|
3036
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[14]
|
|
3037
|
+
}
|
|
3038
|
+
];
|
|
2401
3039
|
var isMixedAU = (id) => {
|
|
2402
3040
|
const morphs = AU_TO_MORPHS[id];
|
|
2403
3041
|
const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
|
|
@@ -2709,6 +3347,23 @@ var AU_FACEPART_TO_MESH_CATEGORY = {
|
|
|
2709
3347
|
Eyelids: "eye",
|
|
2710
3348
|
Tongue: "tongue"
|
|
2711
3349
|
};
|
|
3350
|
+
var CC4_MAPPING_SECTIONS = [
|
|
3351
|
+
{ id: "Forehead", label: "Forehead", kind: "au", order: 0, meshCategory: "face", facePart: "Forehead" },
|
|
3352
|
+
{ id: "Eyelids", label: "Eyelids", kind: "au", order: 1, meshCategory: "eye", facePart: "Eyelids" },
|
|
3353
|
+
{ id: "Eyes", label: "Eyes", kind: "au", order: 2, meshCategory: "eye", facePart: "Eyes" },
|
|
3354
|
+
{ id: "Cheeks", label: "Cheeks", kind: "au", order: 3, meshCategory: "face", facePart: "Cheeks" },
|
|
3355
|
+
{ id: "Nose", label: "Nose", kind: "au", order: 4, meshCategory: "face", facePart: "Nose" },
|
|
3356
|
+
{ id: "Mouth", label: "Mouth", kind: "au", order: 5, meshCategory: "face", facePart: "Mouth" },
|
|
3357
|
+
{ id: "Chin", label: "Chin", kind: "au", order: 6, meshCategory: "face", facePart: "Chin" },
|
|
3358
|
+
{ id: "Jaw", label: "Jaw", kind: "au", order: 7, meshCategory: "face", facePart: "Jaw" },
|
|
3359
|
+
{ id: "Tongue", label: "Tongue", kind: "au", order: 8, meshCategory: "tongue", facePart: "Tongue" },
|
|
3360
|
+
{ id: "Head", label: "Head", kind: "au", order: 9, meshCategory: "face", facePart: "Head" },
|
|
3361
|
+
{ id: "Joint Controls", label: "Joint Controls", kind: "au", order: 10, meshCategory: "face", facePart: "Joint Controls" },
|
|
3362
|
+
{ id: "Eye", label: "Eye", kind: "au", order: 11, meshCategory: "eye", facePart: "Eye" },
|
|
3363
|
+
{ id: "Hair", label: "Hair", kind: "hair", order: 12, meshCategory: "hair" },
|
|
3364
|
+
{ id: "Visemes", label: "Visemes", kind: "viseme", order: 13, meshCategory: "viseme" },
|
|
3365
|
+
{ id: "Unmapped", label: "Unmapped", kind: "unmapped", order: 14, meshCategory: "face" }
|
|
3366
|
+
];
|
|
2712
3367
|
var CC4_HAIR_PHYSICS = {
|
|
2713
3368
|
stiffness: 7.5,
|
|
2714
3369
|
damping: 0.18,
|
|
@@ -2755,7 +3410,10 @@ var CC4_PRESET = {
|
|
|
2755
3410
|
suffixPattern: CC4_SUFFIX_PATTERN,
|
|
2756
3411
|
morphToMesh: MORPH_TO_MESH,
|
|
2757
3412
|
auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
|
|
3413
|
+
mappingSections: CC4_MAPPING_SECTIONS,
|
|
2758
3414
|
visemeKeys: VISEME_KEYS,
|
|
3415
|
+
visemeSystemId: CC4_VISEME_SYSTEM_ID,
|
|
3416
|
+
visemeSlots: CC4_VISEME_SLOTS,
|
|
2759
3417
|
visemeMeshCategory: "viseme",
|
|
2760
3418
|
visemeJawAmounts: VISEME_JAW_AMOUNTS,
|
|
2761
3419
|
auMixDefaults: AU_MIX_DEFAULTS,
|
|
@@ -3642,8 +4300,13 @@ function extendPresetWithProfile(base, extension) {
|
|
|
3642
4300
|
boneNodes: mergeRecord(base.boneNodes, extension.boneNodes),
|
|
3643
4301
|
morphToMesh: mergeRecord(base.morphToMesh, extension.morphToMesh),
|
|
3644
4302
|
auFacePartToMeshCategory: base.auFacePartToMeshCategory || extension.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, extension.auFacePartToMeshCategory || {}) : void 0,
|
|
4303
|
+
mappingSections: extension.mappingSections ? [...extension.mappingSections] : base.mappingSections ? [...base.mappingSections] : void 0,
|
|
3645
4304
|
visemeKeys: extension.visemeKeys ? [...extension.visemeKeys] : [...base.visemeKeys],
|
|
4305
|
+
visemeSystemId: extension.visemeSystemId ?? base.visemeSystemId,
|
|
4306
|
+
visemeSlots: extension.visemeSlots ? [...extension.visemeSlots] : base.visemeSlots ? [...base.visemeSlots] : void 0,
|
|
4307
|
+
visemeBindings: base.visemeBindings || extension.visemeBindings ? mergeRecord(base.visemeBindings || {}, extension.visemeBindings || {}) : void 0,
|
|
3646
4308
|
visemeMeshCategory: extension.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
4309
|
+
visemeJawAmounts: extension.visemeJawAmounts ? [...extension.visemeJawAmounts] : base.visemeJawAmounts ? [...base.visemeJawAmounts] : void 0,
|
|
3647
4310
|
auMixDefaults: base.auMixDefaults || extension.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, extension.auMixDefaults || {}) : void 0,
|
|
3648
4311
|
auInfo: base.auInfo || extension.auInfo ? mergeRecord(base.auInfo || {}, extension.auInfo || {}) : void 0,
|
|
3649
4312
|
eyeMeshNodes: extension.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
@@ -4605,7 +5268,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4605
5268
|
getCompositeRotations: () => this.compositeRotations,
|
|
4606
5269
|
computeSideValues: (base, balance) => this.computeSideValues(base, balance),
|
|
4607
5270
|
getAUMixWeight: (auId) => this.getAUMixWeight(auId),
|
|
4608
|
-
isMixedAU: (auId) => this.isMixedAU(auId)
|
|
5271
|
+
isMixedAU: (auId) => this.isMixedAU(auId),
|
|
5272
|
+
reapplyProceduralState: () => this.reapplyProceduralStateAfterBakedUpdate()
|
|
4609
5273
|
});
|
|
4610
5274
|
this.hairPhysics = new HairPhysicsController({
|
|
4611
5275
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
@@ -5209,6 +5873,21 @@ var _Loom3 = class _Loom3 {
|
|
|
5209
5873
|
const jawHandle = this.transitionBoneRotation("JAW", "pitch", jawAmount, durationMs);
|
|
5210
5874
|
return this.combineHandles([morphHandle, jawHandle]);
|
|
5211
5875
|
}
|
|
5876
|
+
setVisemeById(slotId, value, jawScale = 1) {
|
|
5877
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5878
|
+
if (index < 0) return;
|
|
5879
|
+
this.setViseme(index, value, jawScale);
|
|
5880
|
+
}
|
|
5881
|
+
transitionVisemeById(slotId, to, durationMs = 80, jawScale = 1) {
|
|
5882
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5883
|
+
if (index < 0) {
|
|
5884
|
+
return { promise: Promise.resolve(), pause: () => {
|
|
5885
|
+
}, resume: () => {
|
|
5886
|
+
}, cancel: () => {
|
|
5887
|
+
} };
|
|
5888
|
+
}
|
|
5889
|
+
return this.transitionViseme(index, to, durationMs, jawScale);
|
|
5890
|
+
}
|
|
5212
5891
|
// ============================================================================
|
|
5213
5892
|
// MIX WEIGHT CONTROL
|
|
5214
5893
|
// ============================================================================
|
|
@@ -5301,6 +5980,30 @@ var _Loom3 = class _Loom3 {
|
|
|
5301
5980
|
this.model.updateMatrixWorld(true);
|
|
5302
5981
|
}
|
|
5303
5982
|
}
|
|
5983
|
+
reapplyProceduralStateAfterBakedUpdate() {
|
|
5984
|
+
if (!this.model) {
|
|
5985
|
+
return;
|
|
5986
|
+
}
|
|
5987
|
+
let hasActiveOverrides = false;
|
|
5988
|
+
for (const [auIdStr, value] of Object.entries(this.auValues)) {
|
|
5989
|
+
if (value <= 0) continue;
|
|
5990
|
+
const auId = Number(auIdStr);
|
|
5991
|
+
if (Number.isNaN(auId)) continue;
|
|
5992
|
+
hasActiveOverrides = true;
|
|
5993
|
+
this.setAU(auId, value, this.auBalances[auId]);
|
|
5994
|
+
}
|
|
5995
|
+
for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
|
|
5996
|
+
const value = this.visemeValues[visemeIndex] ?? 0;
|
|
5997
|
+
if (value <= 0) continue;
|
|
5998
|
+
hasActiveOverrides = true;
|
|
5999
|
+
this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
|
|
6000
|
+
}
|
|
6001
|
+
if (!hasActiveOverrides) {
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
this.flushPendingComposites();
|
|
6005
|
+
this.model.updateMatrixWorld(true);
|
|
6006
|
+
}
|
|
5304
6007
|
// ============================================================================
|
|
5305
6008
|
// MESH CONTROL
|
|
5306
6009
|
// ============================================================================
|
|
@@ -5554,21 +6257,10 @@ var _Loom3 = class _Loom3 {
|
|
|
5554
6257
|
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
5555
6258
|
*/
|
|
5556
6259
|
getMeshNamesForAU(auId) {
|
|
5557
|
-
|
|
5558
|
-
const info = this.config.auInfo?.[String(auId)];
|
|
5559
|
-
const facePart = info?.facePart;
|
|
5560
|
-
if (facePart) {
|
|
5561
|
-
const category = this.config.auFacePartToMeshCategory?.[facePart];
|
|
5562
|
-
if (category) {
|
|
5563
|
-
return m?.[category] || [];
|
|
5564
|
-
}
|
|
5565
|
-
}
|
|
5566
|
-
return m?.face || [];
|
|
6260
|
+
return getMeshNamesForAUProfile(this.config, auId);
|
|
5567
6261
|
}
|
|
5568
6262
|
getMeshNamesForViseme() {
|
|
5569
|
-
|
|
5570
|
-
const category = this.config.visemeMeshCategory || (m?.viseme ? "viseme" : "face");
|
|
5571
|
-
return m?.[category] || m?.face || [];
|
|
6263
|
+
return getMeshNamesForVisemeProfile(this.config);
|
|
5572
6264
|
}
|
|
5573
6265
|
// ============================================================================
|
|
5574
6266
|
// HAIR PHYSICS
|
|
@@ -5836,7 +6528,7 @@ var _Loom3 = class _Loom3 {
|
|
|
5836
6528
|
);
|
|
5837
6529
|
}
|
|
5838
6530
|
getVisemeJawAmount(visemeIndex) {
|
|
5839
|
-
return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
6531
|
+
return getVisemeJawAmounts(this.config)?.[visemeIndex] ?? this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
5840
6532
|
}
|
|
5841
6533
|
collectResolvedExpressionMorphTargets() {
|
|
5842
6534
|
const targets = [];
|
|
@@ -6314,7 +7006,11 @@ var PROFILE_OVERRIDE_KEYS = [
|
|
|
6314
7006
|
"rightMorphSuffixes",
|
|
6315
7007
|
"morphToMesh",
|
|
6316
7008
|
"auFacePartToMeshCategory",
|
|
7009
|
+
"mappingSections",
|
|
6317
7010
|
"visemeKeys",
|
|
7011
|
+
"visemeSystemId",
|
|
7012
|
+
"visemeSlots",
|
|
7013
|
+
"visemeBindings",
|
|
6318
7014
|
"visemeMeshCategory",
|
|
6319
7015
|
"visemeJawAmounts",
|
|
6320
7016
|
"auMixDefaults",
|
|
@@ -7414,6 +8110,33 @@ function validateMappingConfig(config) {
|
|
|
7414
8110
|
}
|
|
7415
8111
|
visemeSeen.add(key);
|
|
7416
8112
|
}
|
|
8113
|
+
const morphCategories = new Set(Object.keys(config.morphToMesh || {}));
|
|
8114
|
+
if (config.visemeMeshCategory && !morphCategories.has(config.visemeMeshCategory)) {
|
|
8115
|
+
push(
|
|
8116
|
+
"error",
|
|
8117
|
+
"VISEME_MESH_CATEGORY_MISSING",
|
|
8118
|
+
`visemeMeshCategory "${config.visemeMeshCategory}" is not present in morphToMesh`,
|
|
8119
|
+
{ category: config.visemeMeshCategory }
|
|
8120
|
+
);
|
|
8121
|
+
}
|
|
8122
|
+
for (const [facePart, category] of Object.entries(config.auFacePartToMeshCategory || {})) {
|
|
8123
|
+
if (!morphCategories.has(category)) {
|
|
8124
|
+
push(
|
|
8125
|
+
"error",
|
|
8126
|
+
"AU_MESH_CATEGORY_MISSING",
|
|
8127
|
+
`AU facePart "${facePart}" routes to missing morphToMesh category "${category}"`,
|
|
8128
|
+
{ facePart, category }
|
|
8129
|
+
);
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8132
|
+
if (config.visemeJawAmounts && config.visemeJawAmounts.length !== (config.visemeKeys || []).length) {
|
|
8133
|
+
push(
|
|
8134
|
+
"warning",
|
|
8135
|
+
"VISEME_JAW_AMOUNT_LENGTH_MISMATCH",
|
|
8136
|
+
"visemeJawAmounts length does not match visemeKeys length",
|
|
8137
|
+
{ visemeKeys: (config.visemeKeys || []).length, visemeJawAmounts: config.visemeJawAmounts.length }
|
|
8138
|
+
);
|
|
8139
|
+
}
|
|
7417
8140
|
if (config.auMixDefaults) {
|
|
7418
8141
|
for (const key of Object.keys(config.auMixDefaults)) {
|
|
7419
8142
|
const auId = Number(key);
|
|
@@ -7878,9 +8601,12 @@ exports.BONE_AU_TO_BINDINGS = BONE_AU_TO_BINDINGS;
|
|
|
7878
8601
|
exports.CC4_BONE_NODES = CC4_BONE_NODES;
|
|
7879
8602
|
exports.CC4_BONE_PREFIX = CC4_BONE_PREFIX;
|
|
7880
8603
|
exports.CC4_EYE_MESH_NODES = CC4_EYE_MESH_NODES;
|
|
8604
|
+
exports.CC4_MAPPING_SECTIONS = CC4_MAPPING_SECTIONS;
|
|
7881
8605
|
exports.CC4_MESHES = CC4_MESHES;
|
|
7882
8606
|
exports.CC4_PRESET = CC4_PRESET;
|
|
7883
8607
|
exports.CC4_SUFFIX_PATTERN = CC4_SUFFIX_PATTERN;
|
|
8608
|
+
exports.CC4_VISEME_SLOTS = CC4_VISEME_SLOTS;
|
|
8609
|
+
exports.CC4_VISEME_SYSTEM_ID = CC4_VISEME_SYSTEM_ID;
|
|
7884
8610
|
exports.COMPOSITE_ROTATIONS = COMPOSITE_ROTATIONS;
|
|
7885
8611
|
exports.CONTINUUM_LABELS = CONTINUUM_LABELS;
|
|
7886
8612
|
exports.CONTINUUM_PAIRS_MAP = CONTINUUM_PAIRS_MAP;
|
|
@@ -7895,7 +8621,9 @@ exports.VISEME_JAW_AMOUNTS = VISEME_JAW_AMOUNTS;
|
|
|
7895
8621
|
exports.VISEME_KEYS = VISEME_KEYS;
|
|
7896
8622
|
exports.analyzeModel = analyzeModel;
|
|
7897
8623
|
exports.applyCharacterProfileToPreset = applyCharacterProfileToPreset;
|
|
8624
|
+
exports.buildMappingEditorModel = buildMappingEditorModel;
|
|
7898
8625
|
exports.collectMorphMeshes = collectMorphMeshes;
|
|
8626
|
+
exports.compileVisemeKeys = compileVisemeKeys;
|
|
7899
8627
|
exports.computeCameraRelativeGazeOffset = computeCameraRelativeGazeOffset;
|
|
7900
8628
|
exports.detectFacingDirection = detectFacingDirection;
|
|
7901
8629
|
exports.extendCharacterConfigWithPreset = extendCharacterConfigWithPreset;
|
|
@@ -7906,18 +8634,25 @@ exports.extractProfileOverrides = extractProfileOverrides;
|
|
|
7906
8634
|
exports.findFaceCenter = findFaceCenter;
|
|
7907
8635
|
exports.fuzzyNameMatch = fuzzyNameMatch;
|
|
7908
8636
|
exports.generateMappingCorrections = generateMappingCorrections;
|
|
8637
|
+
exports.getMeshNamesForAUProfile = getMeshNamesForAUProfile;
|
|
8638
|
+
exports.getMeshNamesForVisemeProfile = getMeshNamesForVisemeProfile;
|
|
7909
8639
|
exports.getModelForwardDirection = getModelForwardDirection;
|
|
7910
8640
|
exports.getPreset = getPreset;
|
|
7911
8641
|
exports.getPresetWithProfile = getPresetWithProfile;
|
|
8642
|
+
exports.getProfileVisemeSlots = getProfileVisemeSlots;
|
|
8643
|
+
exports.getVisemeJawAmounts = getVisemeJawAmounts;
|
|
8644
|
+
exports.getVisemeSlotIndex = getVisemeSlotIndex;
|
|
7912
8645
|
exports.hasLeftRightMorphs = hasLeftRightMorphs;
|
|
7913
8646
|
exports.isMixedAU = isMixedAU;
|
|
7914
8647
|
exports.isPresetCompatible = isPresetCompatible;
|
|
8648
|
+
exports.mapProviderVisemeToSlot = mapProviderVisemeToSlot;
|
|
7915
8649
|
exports.mergeCharacterRegionsByName = mergeRegionsByName;
|
|
7916
8650
|
exports.resolveBoneName = resolveBoneName;
|
|
7917
8651
|
exports.resolveBoneNames = resolveBoneNames;
|
|
7918
8652
|
exports.resolveFaceCenter = resolveFaceCenter;
|
|
7919
8653
|
exports.resolvePreset = resolvePreset;
|
|
7920
8654
|
exports.resolvePresetWithOverrides = resolvePresetWithOverrides;
|
|
8655
|
+
exports.resolveVisemeMeshCategory = resolveVisemeMeshCategory;
|
|
7921
8656
|
exports.suggestBestPreset = suggestBestPreset;
|
|
7922
8657
|
exports.validateMappingConfig = validateMappingConfig;
|
|
7923
8658
|
exports.validateMappings = validateMappings;
|