@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as THREE2 from 'three';
|
|
2
|
-
import { Vector3, Clock, Box3, BufferAttribute, Quaternion,
|
|
2
|
+
import { Vector3, Clock, Box3, BufferAttribute, Quaternion, PropertyBinding, NumberKeyframeTrack, QuaternionKeyframeTrack, VectorKeyframeTrack, AnimationClip, AnimationUtils, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopPingPong, LoopOnce, LoopRepeat, AnimationMixer, Mesh } from 'three';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -39,6 +39,223 @@ function getCompositeAxisBinding(nodeKey, axisConfig, direction, getValue, auToB
|
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// src/mappings/visemeSystem.ts
|
|
43
|
+
function hasOwn(value, key) {
|
|
44
|
+
return Boolean(value && Object.prototype.hasOwnProperty.call(value, key));
|
|
45
|
+
}
|
|
46
|
+
function toSlotId(label, index) {
|
|
47
|
+
const normalized = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
48
|
+
return normalized || `viseme-${index}`;
|
|
49
|
+
}
|
|
50
|
+
function bindingTargets(binding) {
|
|
51
|
+
if (!binding) return [];
|
|
52
|
+
const targets = binding.targets?.map((target) => target.morph).filter((morph) => morph !== "");
|
|
53
|
+
if (targets && targets.length > 0) return targets;
|
|
54
|
+
return binding.morph !== void 0 && binding.morph !== "" ? [binding.morph] : [];
|
|
55
|
+
}
|
|
56
|
+
function getProfileVisemeSlots(profile) {
|
|
57
|
+
if (profile.visemeSlots && profile.visemeSlots.length > 0) {
|
|
58
|
+
return [...profile.visemeSlots].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
59
|
+
}
|
|
60
|
+
return (profile.visemeKeys || []).map((key, index) => {
|
|
61
|
+
const label = typeof key === "string" && key ? key : `Viseme ${index}`;
|
|
62
|
+
return {
|
|
63
|
+
id: toSlotId(label, index),
|
|
64
|
+
label,
|
|
65
|
+
order: index,
|
|
66
|
+
defaultJawAmount: profile.visemeJawAmounts?.[index]
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function getVisemeSlotIndex(profile, slotId) {
|
|
71
|
+
return getProfileVisemeSlots(profile).findIndex((slot) => slot.id === slotId);
|
|
72
|
+
}
|
|
73
|
+
function compileVisemeKeys(profile) {
|
|
74
|
+
const slots = getProfileVisemeSlots(profile);
|
|
75
|
+
if (!profile.visemeBindings) return [...profile.visemeKeys || []];
|
|
76
|
+
return slots.map((slot, index) => {
|
|
77
|
+
const target = bindingTargets(profile.visemeBindings?.[slot.id])[0];
|
|
78
|
+
return target ?? profile.visemeKeys?.[index] ?? "";
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function getVisemeJawAmounts(profile) {
|
|
82
|
+
const slots = getProfileVisemeSlots(profile);
|
|
83
|
+
if (slots.length === 0) return profile.visemeJawAmounts ? [...profile.visemeJawAmounts] : void 0;
|
|
84
|
+
return slots.map((slot, index) => slot.defaultJawAmount ?? profile.visemeJawAmounts?.[index] ?? 0);
|
|
85
|
+
}
|
|
86
|
+
function resolveVisemeMeshCategory(profile) {
|
|
87
|
+
const morphToMesh = profile.morphToMesh || {};
|
|
88
|
+
if (profile.visemeMeshCategory) return profile.visemeMeshCategory;
|
|
89
|
+
if (hasOwn(morphToMesh, "viseme")) return "viseme";
|
|
90
|
+
return "face";
|
|
91
|
+
}
|
|
92
|
+
function getMeshNamesForVisemeProfile(profile) {
|
|
93
|
+
const morphToMesh = profile.morphToMesh || {};
|
|
94
|
+
const category = resolveVisemeMeshCategory(profile);
|
|
95
|
+
if (hasOwn(morphToMesh, category)) {
|
|
96
|
+
return Array.isArray(morphToMesh[category]) ? [...morphToMesh[category]] : [];
|
|
97
|
+
}
|
|
98
|
+
return profile.visemeMeshCategory ? [] : [...morphToMesh.face || []];
|
|
99
|
+
}
|
|
100
|
+
function getMeshNamesForAUProfile(profile, auId) {
|
|
101
|
+
const morphToMesh = profile.morphToMesh || {};
|
|
102
|
+
const facePart = profile.auInfo?.[String(auId)]?.facePart;
|
|
103
|
+
const category = facePart ? profile.auFacePartToMeshCategory?.[facePart] : void 0;
|
|
104
|
+
if (category) return Array.isArray(morphToMesh[category]) ? [...morphToMesh[category]] : [];
|
|
105
|
+
return [...morphToMesh.face || []];
|
|
106
|
+
}
|
|
107
|
+
function compileMatcher(pattern) {
|
|
108
|
+
try {
|
|
109
|
+
return new RegExp(pattern, "i");
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function classifyVisemeMorph(morphName, profile) {
|
|
115
|
+
const slots = getProfileVisemeSlots(profile);
|
|
116
|
+
const matches = [];
|
|
117
|
+
for (const slot of slots) {
|
|
118
|
+
const explicitTargets = bindingTargets(profile.visemeBindings?.[slot.id]);
|
|
119
|
+
if (explicitTargets.some((target) => String(target).toLowerCase() === morphName.toLowerCase())) {
|
|
120
|
+
matches.push({
|
|
121
|
+
slotId: slot.id,
|
|
122
|
+
label: slot.label,
|
|
123
|
+
confidence: 1,
|
|
124
|
+
reason: "explicit"
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const pattern of slot.matchers || []) {
|
|
129
|
+
const matcher = compileMatcher(pattern);
|
|
130
|
+
if (matcher?.test(morphName)) {
|
|
131
|
+
matches.push({
|
|
132
|
+
slotId: slot.id,
|
|
133
|
+
label: slot.label,
|
|
134
|
+
confidence: 0.75,
|
|
135
|
+
reason: "regex",
|
|
136
|
+
pattern
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return matches;
|
|
142
|
+
}
|
|
143
|
+
function buildMappingEditorModel(profile, morphNames = []) {
|
|
144
|
+
const sections = /* @__PURE__ */ new Map();
|
|
145
|
+
const configuredSections = profile.mappingSections || [];
|
|
146
|
+
const configuredById = new Map(configuredSections.map((section) => [section.id, section]));
|
|
147
|
+
let nextOrder = configuredSections.length;
|
|
148
|
+
for (const section of configuredSections) {
|
|
149
|
+
sections.set(section.id, { ...section });
|
|
150
|
+
}
|
|
151
|
+
const getOrder = (id, fallback) => {
|
|
152
|
+
const configured = configuredById.get(id);
|
|
153
|
+
if (configured) return configured.order;
|
|
154
|
+
if (fallback !== void 0) return fallback;
|
|
155
|
+
const order = nextOrder;
|
|
156
|
+
nextOrder += 1;
|
|
157
|
+
return order;
|
|
158
|
+
};
|
|
159
|
+
const auSectionOrders = /* @__PURE__ */ new Map();
|
|
160
|
+
for (const [auId, info] of Object.entries(profile.auInfo || {})) {
|
|
161
|
+
const label = info.facePart || "Unmapped";
|
|
162
|
+
auSectionOrders.set(label, Math.min(auSectionOrders.get(label) ?? Number.MAX_SAFE_INTEGER, Number(auId)));
|
|
163
|
+
}
|
|
164
|
+
if (configuredSections.length === 0 && auSectionOrders.size > 0) {
|
|
165
|
+
nextOrder = Math.max(...auSectionOrders.values()) + 1;
|
|
166
|
+
}
|
|
167
|
+
for (const info of Object.values(profile.auInfo || {})) {
|
|
168
|
+
const label = info.facePart || "Unmapped";
|
|
169
|
+
const configured = configuredById.get(label);
|
|
170
|
+
const meshCategory = profile.auFacePartToMeshCategory?.[label] || "face";
|
|
171
|
+
sections.set(label, {
|
|
172
|
+
...configured,
|
|
173
|
+
id: label,
|
|
174
|
+
label: configured?.label || label,
|
|
175
|
+
kind: "au",
|
|
176
|
+
order: getOrder(label, auSectionOrders.get(label)),
|
|
177
|
+
meshCategory: configured?.meshCategory || meshCategory,
|
|
178
|
+
facePart: label
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const configuredVisemes = configuredById.get("Visemes");
|
|
182
|
+
sections.set("Visemes", {
|
|
183
|
+
...configuredVisemes,
|
|
184
|
+
id: "Visemes",
|
|
185
|
+
label: configuredVisemes?.label || "Visemes",
|
|
186
|
+
kind: "viseme",
|
|
187
|
+
order: getOrder("Visemes"),
|
|
188
|
+
meshCategory: configuredVisemes?.meshCategory || resolveVisemeMeshCategory(profile)
|
|
189
|
+
});
|
|
190
|
+
if (hasOwn(profile.morphToMesh, "hair")) {
|
|
191
|
+
const configuredHair = configuredById.get("Hair");
|
|
192
|
+
sections.set("Hair", {
|
|
193
|
+
...configuredHair,
|
|
194
|
+
id: "Hair",
|
|
195
|
+
label: configuredHair?.label || "Hair",
|
|
196
|
+
kind: "hair",
|
|
197
|
+
order: getOrder("Hair"),
|
|
198
|
+
meshCategory: configuredHair?.meshCategory || "hair"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const configuredUnmapped = configuredById.get("Unmapped");
|
|
202
|
+
sections.set("Unmapped", {
|
|
203
|
+
...configuredUnmapped,
|
|
204
|
+
id: "Unmapped",
|
|
205
|
+
label: configuredUnmapped?.label || "Unmapped",
|
|
206
|
+
kind: "unmapped",
|
|
207
|
+
order: getOrder("Unmapped"),
|
|
208
|
+
meshCategory: configuredUnmapped?.meshCategory || "face"
|
|
209
|
+
});
|
|
210
|
+
const candidates = morphNames.map((morph) => {
|
|
211
|
+
const matches = classifyVisemeMorph(morph, profile);
|
|
212
|
+
if (matches.length === 0) {
|
|
213
|
+
return { morph, sectionId: "Unmapped", kind: "unmapped", matches };
|
|
214
|
+
}
|
|
215
|
+
const explicit = matches.filter((match) => match.reason === "explicit");
|
|
216
|
+
if (explicit.length > 0) {
|
|
217
|
+
return { morph, sectionId: "Visemes", kind: "explicit", matches: explicit };
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
morph,
|
|
221
|
+
sectionId: "Visemes",
|
|
222
|
+
kind: matches.length > 1 ? "conflict" : "candidate",
|
|
223
|
+
matches
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
sections: Array.from(sections.values()).sort((a, b) => a.order - b.order || a.label.localeCompare(b.label)),
|
|
228
|
+
candidates
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function mapProviderVisemeToSlot(profile, event) {
|
|
232
|
+
const slots = getProfileVisemeSlots(profile);
|
|
233
|
+
const provider = event.provider.toLowerCase();
|
|
234
|
+
if (event.id !== void 0) {
|
|
235
|
+
const id = String(event.id);
|
|
236
|
+
const index = slots.findIndex(
|
|
237
|
+
(slot) => (slot.providerIds?.[provider] || []).some((candidate) => String(candidate) === id)
|
|
238
|
+
);
|
|
239
|
+
if (index >= 0) {
|
|
240
|
+
return { slotId: slots[index].id, index, confidence: 1, reason: "provider" };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (event.phoneme) {
|
|
244
|
+
const phoneme = event.phoneme.toLowerCase();
|
|
245
|
+
const index = slots.findIndex(
|
|
246
|
+
(slot) => (slot.phonemes || []).some((candidate) => candidate.toLowerCase() === phoneme)
|
|
247
|
+
);
|
|
248
|
+
if (index >= 0) {
|
|
249
|
+
return { slotId: slots[index].id, index, confidence: 0.8, reason: "phoneme" };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const restIndex = slots.findIndex((slot) => slot.id === "rest" || slot.features?.lipClosed === 1);
|
|
253
|
+
if (restIndex >= 0) {
|
|
254
|
+
return { slotId: slots[restIndex].id, index: restIndex, confidence: 0.25, reason: "rest" };
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
42
259
|
// src/engines/three/balanceUtils.ts
|
|
43
260
|
function clampBalance(value) {
|
|
44
261
|
if (!Number.isFinite(value)) return 0;
|
|
@@ -272,14 +489,19 @@ var Y_AXIS = new Vector3(0, 1, 0);
|
|
|
272
489
|
var Z_AXIS = new Vector3(0, 0, 1);
|
|
273
490
|
var CLIP_EVENT_METADATA_KEY = "__loom3ClipEvents";
|
|
274
491
|
var CLIP_EVENT_EPSILON = 1e-4;
|
|
492
|
+
var ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX = "__loom3_additive_delta";
|
|
275
493
|
var BakedAnimationController = class {
|
|
276
494
|
constructor(host) {
|
|
277
495
|
__publicField(this, "host");
|
|
496
|
+
// Clip-backed snippets need a later mixer pass so they can override baked additive tracks.
|
|
278
497
|
__publicField(this, "animationMixer", null);
|
|
498
|
+
__publicField(this, "clipAnimationMixer", null);
|
|
279
499
|
__publicField(this, "mixerFinishedListenerAttached", false);
|
|
500
|
+
__publicField(this, "clipMixerFinishedListenerAttached", false);
|
|
280
501
|
__publicField(this, "animationClips", []);
|
|
281
502
|
__publicField(this, "bakedSourceClips", /* @__PURE__ */ new Map());
|
|
282
503
|
__publicField(this, "bakedRuntimeActions", /* @__PURE__ */ new Map());
|
|
504
|
+
__publicField(this, "bakedAdditiveRuntimeClips", /* @__PURE__ */ new Map());
|
|
283
505
|
__publicField(this, "bakedActionGroups", /* @__PURE__ */ new Map());
|
|
284
506
|
__publicField(this, "bakedRuntimeClipToSource", /* @__PURE__ */ new Map());
|
|
285
507
|
__publicField(this, "animationActions", /* @__PURE__ */ new Map());
|
|
@@ -304,6 +526,173 @@ var BakedAnimationController = class {
|
|
|
304
526
|
action.__actionId = actionId;
|
|
305
527
|
return actionId;
|
|
306
528
|
}
|
|
529
|
+
clearActionId(action) {
|
|
530
|
+
if (!action) return;
|
|
531
|
+
const actionId = this.getActionId(action);
|
|
532
|
+
if (actionId) {
|
|
533
|
+
this.actionIdToClip.delete(actionId);
|
|
534
|
+
}
|
|
535
|
+
this.actionIds.delete(action);
|
|
536
|
+
delete action.__actionId;
|
|
537
|
+
}
|
|
538
|
+
uncacheClip(clip, mixer = this.animationMixer) {
|
|
539
|
+
if (!clip || !mixer) return;
|
|
540
|
+
try {
|
|
541
|
+
mixer.uncacheClip(clip);
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
uncacheAction(action, mixer = this.animationMixer) {
|
|
546
|
+
if (!action || !mixer) return;
|
|
547
|
+
try {
|
|
548
|
+
const clip = action.getClip();
|
|
549
|
+
if (clip) {
|
|
550
|
+
mixer.uncacheAction(clip);
|
|
551
|
+
mixer.uncacheClip(clip);
|
|
552
|
+
}
|
|
553
|
+
} catch {
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
releaseBakedRuntimeAction(runtimeClipName) {
|
|
557
|
+
const action = this.bakedRuntimeActions.get(runtimeClipName);
|
|
558
|
+
if (!action) return;
|
|
559
|
+
try {
|
|
560
|
+
action.stop();
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
this.uncacheAction(action);
|
|
564
|
+
this.clearActionId(action);
|
|
565
|
+
this.bakedRuntimeActions.delete(runtimeClipName);
|
|
566
|
+
}
|
|
567
|
+
clearBakedAdditiveRuntimeClip(runtimeClipName) {
|
|
568
|
+
const clip = this.bakedAdditiveRuntimeClips.get(runtimeClipName);
|
|
569
|
+
if (!clip) return;
|
|
570
|
+
this.uncacheClip(clip);
|
|
571
|
+
this.bakedAdditiveRuntimeClips.delete(runtimeClipName);
|
|
572
|
+
}
|
|
573
|
+
clearAllBakedAdditiveRuntimeClips() {
|
|
574
|
+
for (const runtimeClipName of Array.from(this.bakedAdditiveRuntimeClips.keys())) {
|
|
575
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClipName);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
resolveTrackTarget(model, parsed) {
|
|
579
|
+
const targetKey = parsed.objectName === "bones" && parsed.objectIndex ? parsed.objectIndex : parsed.nodeName;
|
|
580
|
+
if (!targetKey) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
return model.getObjectByProperty("uuid", targetKey) ?? PropertyBinding.findNode(model, targetKey) ?? null;
|
|
584
|
+
}
|
|
585
|
+
getMorphTrackBaseValue(target, propertyIndex) {
|
|
586
|
+
if (!target) {
|
|
587
|
+
return 0;
|
|
588
|
+
}
|
|
589
|
+
const meshTarget = target;
|
|
590
|
+
const influences = meshTarget.morphTargetInfluences;
|
|
591
|
+
if (!influences) {
|
|
592
|
+
return 0;
|
|
593
|
+
}
|
|
594
|
+
let morphIndex;
|
|
595
|
+
if (typeof propertyIndex === "number" && Number.isInteger(propertyIndex)) {
|
|
596
|
+
morphIndex = propertyIndex;
|
|
597
|
+
} else if (typeof propertyIndex === "string") {
|
|
598
|
+
if (/^\d+$/.test(propertyIndex)) {
|
|
599
|
+
morphIndex = Number(propertyIndex);
|
|
600
|
+
} else {
|
|
601
|
+
morphIndex = meshTarget.morphTargetDictionary?.[propertyIndex];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (morphIndex === void 0) {
|
|
605
|
+
return 0;
|
|
606
|
+
}
|
|
607
|
+
return influences[morphIndex] ?? 0;
|
|
608
|
+
}
|
|
609
|
+
canCreateFirstFrameReferenceTrack(track) {
|
|
610
|
+
const valueSize = track.getValueSize();
|
|
611
|
+
if (!Number.isFinite(valueSize) || valueSize <= 0 || track.values.length < valueSize) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return track.ValueTypeName === "number" || track.ValueTypeName === "quaternion" || track.ValueTypeName === "vector";
|
|
615
|
+
}
|
|
616
|
+
createFirstFrameReferenceTrack(track) {
|
|
617
|
+
const valueSize = track.getValueSize();
|
|
618
|
+
if (!this.canCreateFirstFrameReferenceTrack(track)) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
const values = Array.from(track.values.slice(0, valueSize));
|
|
622
|
+
if (track.ValueTypeName === "number") {
|
|
623
|
+
return new NumberKeyframeTrack(track.name, [0], values);
|
|
624
|
+
}
|
|
625
|
+
if (track.ValueTypeName === "quaternion") {
|
|
626
|
+
return new QuaternionKeyframeTrack(track.name, [0], values);
|
|
627
|
+
}
|
|
628
|
+
if (track.ValueTypeName === "vector") {
|
|
629
|
+
return new VectorKeyframeTrack(track.name, [0], values);
|
|
630
|
+
}
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
createAdditiveReferenceTrack(track, model) {
|
|
634
|
+
const trackName = typeof track?.name === "string" ? track.name : "";
|
|
635
|
+
if (!trackName) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
let parsed;
|
|
639
|
+
try {
|
|
640
|
+
parsed = PropertyBinding.parseTrackName(trackName);
|
|
641
|
+
} catch {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
const target = this.resolveTrackTarget(model, parsed);
|
|
645
|
+
if (parsed.propertyName === "morphTargetInfluences") {
|
|
646
|
+
return new NumberKeyframeTrack(
|
|
647
|
+
track.name,
|
|
648
|
+
[0],
|
|
649
|
+
[this.getMorphTrackBaseValue(target, parsed.propertyIndex)]
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return this.createFirstFrameReferenceTrack(track);
|
|
653
|
+
}
|
|
654
|
+
createAdditiveRuntimeClip(runtimeClip) {
|
|
655
|
+
const model = this.host.getModel();
|
|
656
|
+
if (!model) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
const additiveTracks = [];
|
|
660
|
+
const referenceTracks = [];
|
|
661
|
+
for (const track of runtimeClip.tracks) {
|
|
662
|
+
const referenceTrack = this.createAdditiveReferenceTrack(track, model);
|
|
663
|
+
if (!referenceTrack) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
additiveTracks.push(track.clone());
|
|
667
|
+
referenceTracks.push(referenceTrack);
|
|
668
|
+
}
|
|
669
|
+
const additiveClip = new AnimationClip(
|
|
670
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}`,
|
|
671
|
+
runtimeClip.duration,
|
|
672
|
+
additiveTracks
|
|
673
|
+
);
|
|
674
|
+
if (additiveTracks.length > 0) {
|
|
675
|
+
const referenceClip = new AnimationClip(
|
|
676
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}_reference`,
|
|
677
|
+
0,
|
|
678
|
+
referenceTracks
|
|
679
|
+
);
|
|
680
|
+
AnimationUtils.makeClipAdditive(additiveClip, 0, referenceClip);
|
|
681
|
+
}
|
|
682
|
+
return additiveClip;
|
|
683
|
+
}
|
|
684
|
+
getOrCreateBakedAdditiveRuntimeClip(runtimeClip) {
|
|
685
|
+
const cached = this.bakedAdditiveRuntimeClips.get(runtimeClip.name);
|
|
686
|
+
if (cached) {
|
|
687
|
+
return cached;
|
|
688
|
+
}
|
|
689
|
+
const additiveClip = this.createAdditiveRuntimeClip(runtimeClip);
|
|
690
|
+
if (!additiveClip) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
this.bakedAdditiveRuntimeClips.set(runtimeClip.name, additiveClip);
|
|
694
|
+
return additiveClip;
|
|
695
|
+
}
|
|
307
696
|
setClipEventMetadata(clip, metadata) {
|
|
308
697
|
const userData = clip.userData ?? (clip.userData = {});
|
|
309
698
|
userData[CLIP_EVENT_METADATA_KEY] = metadata;
|
|
@@ -589,21 +978,28 @@ var BakedAnimationController = class {
|
|
|
589
978
|
}
|
|
590
979
|
return 0;
|
|
591
980
|
}
|
|
592
|
-
getOrCreateBakedRuntimeAction(sourceClipName, channel) {
|
|
981
|
+
getOrCreateBakedRuntimeAction(sourceClipName, channel, blendMode = "replace") {
|
|
593
982
|
const bakedClip = this.getBakedSourceClip(sourceClipName);
|
|
594
983
|
const runtimeClip = bakedClip?.runtimeClips.find((entry) => entry.channel === channel)?.clip;
|
|
595
984
|
if (!runtimeClip) {
|
|
596
985
|
return null;
|
|
597
986
|
}
|
|
987
|
+
const desiredClip = blendMode === "additive" ? this.getOrCreateBakedAdditiveRuntimeClip(runtimeClip) : runtimeClip;
|
|
988
|
+
if (!desiredClip) {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
598
991
|
const existing = this.bakedRuntimeActions.get(runtimeClip.name);
|
|
599
|
-
if (existing) {
|
|
992
|
+
if (existing?.getClip() === desiredClip) {
|
|
600
993
|
return existing;
|
|
601
994
|
}
|
|
602
995
|
this.ensureMixer();
|
|
603
996
|
if (!this.animationMixer) {
|
|
604
997
|
return null;
|
|
605
998
|
}
|
|
606
|
-
|
|
999
|
+
if (existing) {
|
|
1000
|
+
this.releaseBakedRuntimeAction(runtimeClip.name);
|
|
1001
|
+
}
|
|
1002
|
+
const action = this.animationMixer.clipAction(desiredClip);
|
|
607
1003
|
this.bakedRuntimeActions.set(runtimeClip.name, action);
|
|
608
1004
|
return action;
|
|
609
1005
|
}
|
|
@@ -621,7 +1017,15 @@ var BakedAnimationController = class {
|
|
|
621
1017
|
}
|
|
622
1018
|
const channelActions = /* @__PURE__ */ new Map();
|
|
623
1019
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
624
|
-
const
|
|
1020
|
+
const channelBlendMode = resolveBakedChannelBlendMode(
|
|
1021
|
+
runtimeClip.channel,
|
|
1022
|
+
playbackState.requestedBlendMode
|
|
1023
|
+
) ?? "replace";
|
|
1024
|
+
const action = this.getOrCreateBakedRuntimeAction(
|
|
1025
|
+
clipName,
|
|
1026
|
+
runtimeClip.channel,
|
|
1027
|
+
channelBlendMode
|
|
1028
|
+
);
|
|
625
1029
|
if (action) {
|
|
626
1030
|
channelActions.set(runtimeClip.channel, action);
|
|
627
1031
|
}
|
|
@@ -649,12 +1053,7 @@ var BakedAnimationController = class {
|
|
|
649
1053
|
if (typeof this.host.getMeshNamesForAU === "function") {
|
|
650
1054
|
return this.host.getMeshNamesForAU(auId) || [];
|
|
651
1055
|
}
|
|
652
|
-
|
|
653
|
-
if (facePart) {
|
|
654
|
-
const category = config.auFacePartToMeshCategory?.[facePart];
|
|
655
|
-
if (category) return config.morphToMesh?.[category] || [];
|
|
656
|
-
}
|
|
657
|
-
return config.morphToMesh?.face || [];
|
|
1056
|
+
return getMeshNamesForAUProfile(config, auId);
|
|
658
1057
|
}
|
|
659
1058
|
getMeshNamesForViseme(config, explicitMeshNames) {
|
|
660
1059
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
@@ -663,18 +1062,41 @@ var BakedAnimationController = class {
|
|
|
663
1062
|
if (typeof this.host.getMeshNamesForViseme === "function") {
|
|
664
1063
|
return this.host.getMeshNamesForViseme() || [];
|
|
665
1064
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
1065
|
+
return getMeshNamesForVisemeProfile(config);
|
|
1066
|
+
}
|
|
1067
|
+
hasActiveAdditivePlayback() {
|
|
1068
|
+
for (const [clipName, group] of this.bakedActionGroups) {
|
|
1069
|
+
const state = this.playbackState.get(clipName);
|
|
1070
|
+
if (state?.blendMode !== "additive") {
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
for (const action of group.channelActions.values()) {
|
|
1074
|
+
if (action.isRunning() && !action.paused) {
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
for (const [clipName, action] of this.animationActions) {
|
|
1080
|
+
const state = this.playbackState.get(clipName);
|
|
1081
|
+
if (state?.blendMode !== "additive") {
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (action.isRunning() && !action.paused) {
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return false;
|
|
670
1089
|
}
|
|
671
1090
|
update(dtSeconds) {
|
|
672
1091
|
if (this.animationMixer) {
|
|
1092
|
+
this.animationMixer.update(dtSeconds);
|
|
1093
|
+
}
|
|
1094
|
+
if (this.clipAnimationMixer) {
|
|
673
1095
|
const snapshots = Array.from(this.clipMonitors.values()).map((monitor) => ({
|
|
674
1096
|
actionId: monitor.actionId,
|
|
675
1097
|
previousTime: monitor.action.time
|
|
676
1098
|
}));
|
|
677
|
-
this.
|
|
1099
|
+
this.clipAnimationMixer.update(dtSeconds);
|
|
678
1100
|
for (const { actionId, previousTime } of snapshots) {
|
|
679
1101
|
const monitor = this.clipMonitors.get(actionId);
|
|
680
1102
|
if (!monitor) continue;
|
|
@@ -693,16 +1115,27 @@ var BakedAnimationController = class {
|
|
|
693
1115
|
}
|
|
694
1116
|
}
|
|
695
1117
|
}
|
|
1118
|
+
if (this.hasActiveAdditivePlayback()) {
|
|
1119
|
+
this.host.reapplyProceduralState?.();
|
|
1120
|
+
}
|
|
696
1121
|
}
|
|
697
1122
|
dispose() {
|
|
698
1123
|
this.stopAllAnimations();
|
|
1124
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
699
1125
|
if (this.animationMixer) {
|
|
700
1126
|
this.animationMixer.stopAllAction();
|
|
701
1127
|
this.animationMixer = null;
|
|
702
1128
|
}
|
|
1129
|
+
if (this.clipAnimationMixer) {
|
|
1130
|
+
this.clipAnimationMixer.stopAllAction();
|
|
1131
|
+
this.clipAnimationMixer = null;
|
|
1132
|
+
}
|
|
1133
|
+
this.mixerFinishedListenerAttached = false;
|
|
1134
|
+
this.clipMixerFinishedListenerAttached = false;
|
|
703
1135
|
this.animationClips = [];
|
|
704
1136
|
this.bakedSourceClips.clear();
|
|
705
1137
|
this.bakedRuntimeActions.clear();
|
|
1138
|
+
this.bakedAdditiveRuntimeClips.clear();
|
|
706
1139
|
this.bakedActionGroups.clear();
|
|
707
1140
|
this.bakedRuntimeClipToSource.clear();
|
|
708
1141
|
this.animationActions.clear();
|
|
@@ -725,6 +1158,8 @@ var BakedAnimationController = class {
|
|
|
725
1158
|
if (this.animationMixer) {
|
|
726
1159
|
for (const bakedClip of this.bakedSourceClips.values()) {
|
|
727
1160
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1161
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1162
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
728
1163
|
try {
|
|
729
1164
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
730
1165
|
} catch {
|
|
@@ -736,6 +1171,7 @@ var BakedAnimationController = class {
|
|
|
736
1171
|
}
|
|
737
1172
|
}
|
|
738
1173
|
}
|
|
1174
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
739
1175
|
for (const clipName of this.bakedSourceClips.keys()) {
|
|
740
1176
|
this.playbackState.delete(clipName);
|
|
741
1177
|
this.clipSources.delete(clipName);
|
|
@@ -776,6 +1212,8 @@ var BakedAnimationController = class {
|
|
|
776
1212
|
if (this.animationMixer) {
|
|
777
1213
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
778
1214
|
const action = this.bakedRuntimeActions.get(runtimeClip.clip.name);
|
|
1215
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1216
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
779
1217
|
try {
|
|
780
1218
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
781
1219
|
} catch {
|
|
@@ -784,7 +1222,6 @@ var BakedAnimationController = class {
|
|
|
784
1222
|
this.animationMixer.uncacheClip(runtimeClip.clip);
|
|
785
1223
|
} catch {
|
|
786
1224
|
}
|
|
787
|
-
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
788
1225
|
this.bakedRuntimeClipToSource.delete(runtimeClip.clip.name);
|
|
789
1226
|
const actionId = this.getActionId(action);
|
|
790
1227
|
if (actionId && action) {
|
|
@@ -793,6 +1230,10 @@ var BakedAnimationController = class {
|
|
|
793
1230
|
}
|
|
794
1231
|
}
|
|
795
1232
|
}
|
|
1233
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1234
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
1235
|
+
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
1236
|
+
}
|
|
796
1237
|
this.animationClips = this.animationClips.filter((entry) => entry.name !== clipName);
|
|
797
1238
|
this.bakedSourceClips.delete(clipName);
|
|
798
1239
|
this.bakedActionGroups.delete(clipName);
|
|
@@ -853,12 +1294,12 @@ var BakedAnimationController = class {
|
|
|
853
1294
|
const actionId = this.getActionId(action);
|
|
854
1295
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
855
1296
|
action.stop();
|
|
856
|
-
if (!isBaked && this.
|
|
1297
|
+
if (!isBaked && this.clipAnimationMixer) {
|
|
857
1298
|
try {
|
|
858
1299
|
const clip = action.getClip();
|
|
859
1300
|
if (clip) {
|
|
860
|
-
this.
|
|
861
|
-
this.
|
|
1301
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1302
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
862
1303
|
}
|
|
863
1304
|
} catch {
|
|
864
1305
|
}
|
|
@@ -880,11 +1321,11 @@ var BakedAnimationController = class {
|
|
|
880
1321
|
const actionId = this.getActionId(clipAction);
|
|
881
1322
|
try {
|
|
882
1323
|
clipAction.stop();
|
|
883
|
-
if (this.
|
|
1324
|
+
if (this.clipAnimationMixer) {
|
|
884
1325
|
const clip = clipAction.getClip();
|
|
885
1326
|
if (clip) {
|
|
886
|
-
this.
|
|
887
|
-
this.
|
|
1327
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1328
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
888
1329
|
}
|
|
889
1330
|
}
|
|
890
1331
|
} catch {
|
|
@@ -1088,8 +1529,23 @@ var BakedAnimationController = class {
|
|
|
1088
1529
|
next.blendMode = this.getBakedAggregateBlendMode(clipName, next);
|
|
1089
1530
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1090
1531
|
if (bakedGroup) {
|
|
1091
|
-
for (const [channel,
|
|
1532
|
+
for (const [channel, currentAction] of Array.from(bakedGroup.channelActions)) {
|
|
1533
|
+
const channelBlendMode = resolveBakedChannelBlendMode(channel, next.requestedBlendMode) ?? "replace";
|
|
1534
|
+
const previousTime = currentAction.time;
|
|
1535
|
+
const wasActive = currentAction.isRunning() || currentAction.paused;
|
|
1536
|
+
const wasPaused = currentAction.paused;
|
|
1537
|
+
const action2 = this.getOrCreateBakedRuntimeAction(clipName, channel, channelBlendMode);
|
|
1538
|
+
if (!action2) {
|
|
1539
|
+
bakedGroup.channelActions.delete(channel);
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1092
1542
|
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
1543
|
+
action2.time = Math.max(0, Math.min(action2.getClip().duration, previousTime));
|
|
1544
|
+
if (action2 !== currentAction && wasActive) {
|
|
1545
|
+
action2.play();
|
|
1546
|
+
}
|
|
1547
|
+
action2.paused = wasPaused;
|
|
1548
|
+
bakedGroup.channelActions.set(channel, action2);
|
|
1093
1549
|
}
|
|
1094
1550
|
}
|
|
1095
1551
|
this.setPlaybackState(clipName, next);
|
|
@@ -1104,6 +1560,10 @@ var BakedAnimationController = class {
|
|
|
1104
1560
|
seekAnimation(clipName, time) {
|
|
1105
1561
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1106
1562
|
if (bakedGroup) {
|
|
1563
|
+
const state = this.getPlaybackStateSnapshot(clipName, {
|
|
1564
|
+
loop: true,
|
|
1565
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
1566
|
+
});
|
|
1107
1567
|
const duration2 = this.getBakedSourceClip(clipName)?.sourceClip.duration ?? 0;
|
|
1108
1568
|
const clamped = Math.max(0, Math.min(duration2, Number.isFinite(time) ? time : 0));
|
|
1109
1569
|
for (const action2 of bakedGroup.channelActions.values()) {
|
|
@@ -1111,6 +1571,10 @@ var BakedAnimationController = class {
|
|
|
1111
1571
|
}
|
|
1112
1572
|
try {
|
|
1113
1573
|
this.animationMixer?.update(0);
|
|
1574
|
+
this.clipAnimationMixer?.update(0);
|
|
1575
|
+
if (state.blendMode === "additive") {
|
|
1576
|
+
this.host.reapplyProceduralState?.();
|
|
1577
|
+
}
|
|
1114
1578
|
} catch {
|
|
1115
1579
|
}
|
|
1116
1580
|
return;
|
|
@@ -1120,7 +1584,11 @@ var BakedAnimationController = class {
|
|
|
1120
1584
|
const duration = action.getClip().duration;
|
|
1121
1585
|
action.time = Math.max(0, Math.min(duration, Number.isFinite(time) ? time : 0));
|
|
1122
1586
|
try {
|
|
1123
|
-
this.
|
|
1587
|
+
this.clipAnimationMixer?.update(0);
|
|
1588
|
+
const state = this.playbackState.get(clipName);
|
|
1589
|
+
if (state?.blendMode === "additive") {
|
|
1590
|
+
this.host.reapplyProceduralState?.();
|
|
1591
|
+
}
|
|
1124
1592
|
} catch {
|
|
1125
1593
|
}
|
|
1126
1594
|
}
|
|
@@ -1128,6 +1596,9 @@ var BakedAnimationController = class {
|
|
|
1128
1596
|
if (this.animationMixer) {
|
|
1129
1597
|
this.animationMixer.timeScale = timeScale;
|
|
1130
1598
|
}
|
|
1599
|
+
if (this.clipAnimationMixer) {
|
|
1600
|
+
this.clipAnimationMixer.timeScale = timeScale;
|
|
1601
|
+
}
|
|
1131
1602
|
}
|
|
1132
1603
|
getAnimationState(clipName) {
|
|
1133
1604
|
const bakedClip = this.getBakedSourceClip(clipName);
|
|
@@ -1457,8 +1928,8 @@ var BakedAnimationController = class {
|
|
|
1457
1928
|
return clip;
|
|
1458
1929
|
}
|
|
1459
1930
|
playClip(clip, options) {
|
|
1460
|
-
this.
|
|
1461
|
-
if (!
|
|
1931
|
+
const mixer = this.ensureClipMixer();
|
|
1932
|
+
if (!mixer) {
|
|
1462
1933
|
console.warn("[Loom3] playClip: No model loaded, cannot create mixer");
|
|
1463
1934
|
return null;
|
|
1464
1935
|
}
|
|
@@ -1476,7 +1947,7 @@ var BakedAnimationController = class {
|
|
|
1476
1947
|
actionId = this.setActionId(action, clip.name);
|
|
1477
1948
|
}
|
|
1478
1949
|
if (!action) {
|
|
1479
|
-
action =
|
|
1950
|
+
action = mixer.clipAction(clip);
|
|
1480
1951
|
actionId = this.setActionId(action, clip.name);
|
|
1481
1952
|
}
|
|
1482
1953
|
const existingClip = this.animationClips.find((c) => c.name === clip.name);
|
|
@@ -1537,15 +2008,13 @@ var BakedAnimationController = class {
|
|
|
1537
2008
|
},
|
|
1538
2009
|
stop: () => {
|
|
1539
2010
|
action.stop();
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
} catch {
|
|
1548
|
-
}
|
|
2011
|
+
try {
|
|
2012
|
+
mixer.uncacheAction(clip);
|
|
2013
|
+
} catch {
|
|
2014
|
+
}
|
|
2015
|
+
try {
|
|
2016
|
+
mixer.uncacheClip(clip);
|
|
2017
|
+
} catch {
|
|
1549
2018
|
}
|
|
1550
2019
|
this.clipActions.delete(clip.name);
|
|
1551
2020
|
this.animationActions.delete(clip.name);
|
|
@@ -1586,7 +2055,7 @@ var BakedAnimationController = class {
|
|
|
1586
2055
|
const clamped = Math.max(0, Math.min(clip.duration, t));
|
|
1587
2056
|
action.time = clamped;
|
|
1588
2057
|
try {
|
|
1589
|
-
|
|
2058
|
+
mixer.update(0);
|
|
1590
2059
|
} catch {
|
|
1591
2060
|
}
|
|
1592
2061
|
this.syncClipMonitorTime(monitor, clamped, true);
|
|
@@ -1619,16 +2088,16 @@ var BakedAnimationController = class {
|
|
|
1619
2088
|
return this.playClip(clip, { ...options, source: options?.source ?? "clip" });
|
|
1620
2089
|
}
|
|
1621
2090
|
cleanupSnippet(name) {
|
|
1622
|
-
if (!this.
|
|
2091
|
+
if (!this.host.getModel()) return;
|
|
1623
2092
|
for (const [clipName, action] of Array.from(this.clipActions.entries())) {
|
|
1624
2093
|
if (clipName === name || clipName.startsWith(`${name}_`)) {
|
|
1625
2094
|
const actionId = this.getActionId(action);
|
|
1626
2095
|
try {
|
|
1627
2096
|
action.stop();
|
|
1628
2097
|
const clip = action.getClip();
|
|
1629
|
-
if (clip) {
|
|
1630
|
-
this.
|
|
1631
|
-
this.
|
|
2098
|
+
if (clip && this.clipAnimationMixer) {
|
|
2099
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
2100
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1632
2101
|
}
|
|
1633
2102
|
} catch {
|
|
1634
2103
|
}
|
|
@@ -1656,7 +2125,10 @@ var BakedAnimationController = class {
|
|
|
1656
2125
|
clipActions: Array.from(this.clipActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1657
2126
|
animationActions: Array.from(this.animationActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1658
2127
|
clipHandles: Array.from(this.clipHandles.entries()).map(([k, h]) => ({ name: k, actionId: h.actionId })),
|
|
1659
|
-
mixerActions:
|
|
2128
|
+
mixerActions: [
|
|
2129
|
+
...this.animationMixer?._actions || [],
|
|
2130
|
+
...this.clipAnimationMixer?._actions || []
|
|
2131
|
+
].map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.getActionId(a) }))
|
|
1660
2132
|
});
|
|
1661
2133
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
1662
2134
|
const apply = (action) => {
|
|
@@ -1719,7 +2191,7 @@ var BakedAnimationController = class {
|
|
|
1719
2191
|
}
|
|
1720
2192
|
addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
|
|
1721
2193
|
const config = this.host.getConfig();
|
|
1722
|
-
const hasExplicitMeshes =
|
|
2194
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1723
2195
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1724
2196
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1725
2197
|
const addTrackForMesh = (mesh) => {
|
|
@@ -1743,7 +2215,7 @@ var BakedAnimationController = class {
|
|
|
1743
2215
|
addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
|
|
1744
2216
|
if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
|
|
1745
2217
|
const config = this.host.getConfig();
|
|
1746
|
-
const hasExplicitMeshes =
|
|
2218
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1747
2219
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1748
2220
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1749
2221
|
const addTrackForMesh = (mesh) => {
|
|
@@ -1770,35 +2242,48 @@ var BakedAnimationController = class {
|
|
|
1770
2242
|
this.animationMixer = new AnimationMixer(model);
|
|
1771
2243
|
}
|
|
1772
2244
|
if (this.animationMixer && !this.mixerFinishedListenerAttached) {
|
|
1773
|
-
this.animationMixer.addEventListener("finished", (event) =>
|
|
1774
|
-
const action = event.action;
|
|
1775
|
-
const actionId = this.getActionId(action);
|
|
1776
|
-
if (actionId) {
|
|
1777
|
-
const monitor = this.clipMonitors.get(actionId);
|
|
1778
|
-
if (monitor) {
|
|
1779
|
-
monitor.finishedPending = true;
|
|
1780
|
-
return;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
const clip = action.getClip();
|
|
1784
|
-
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
1785
|
-
if (bakedRuntime) {
|
|
1786
|
-
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
1787
|
-
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
1788
|
-
group.resolveFinished();
|
|
1789
|
-
}
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
1793
|
-
if (callback) {
|
|
1794
|
-
callback();
|
|
1795
|
-
this.animationFinishedCallbacks.delete(clip.name);
|
|
1796
|
-
}
|
|
1797
|
-
});
|
|
2245
|
+
this.animationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
1798
2246
|
this.mixerFinishedListenerAttached = true;
|
|
1799
2247
|
}
|
|
1800
2248
|
return this.animationMixer;
|
|
1801
2249
|
}
|
|
2250
|
+
ensureClipMixer() {
|
|
2251
|
+
const model = this.host.getModel();
|
|
2252
|
+
if (!model) return null;
|
|
2253
|
+
if (!this.clipAnimationMixer) {
|
|
2254
|
+
this.clipAnimationMixer = new AnimationMixer(model);
|
|
2255
|
+
}
|
|
2256
|
+
if (this.clipAnimationMixer && !this.clipMixerFinishedListenerAttached) {
|
|
2257
|
+
this.clipAnimationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
2258
|
+
this.clipMixerFinishedListenerAttached = true;
|
|
2259
|
+
}
|
|
2260
|
+
return this.clipAnimationMixer;
|
|
2261
|
+
}
|
|
2262
|
+
handleMixerFinished(event) {
|
|
2263
|
+
const action = event.action;
|
|
2264
|
+
const actionId = this.getActionId(action);
|
|
2265
|
+
if (actionId) {
|
|
2266
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
2267
|
+
if (monitor) {
|
|
2268
|
+
monitor.finishedPending = true;
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
const clip = action.getClip();
|
|
2273
|
+
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
2274
|
+
if (bakedRuntime) {
|
|
2275
|
+
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
2276
|
+
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
2277
|
+
group.resolveFinished();
|
|
2278
|
+
}
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
2282
|
+
if (callback) {
|
|
2283
|
+
callback();
|
|
2284
|
+
this.animationFinishedCallbacks.delete(clip.name);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
1802
2287
|
createAnimationHandle(clipName, action, finishedPromise) {
|
|
1803
2288
|
return {
|
|
1804
2289
|
actionId: this.getActionId(action),
|
|
@@ -2377,6 +2862,159 @@ var VISEME_JAW_AMOUNTS = [
|
|
|
2377
2862
|
0.5
|
|
2378
2863
|
// 14: W_OO
|
|
2379
2864
|
];
|
|
2865
|
+
var CC4_VISEME_SYSTEM_ID = "cc4-arkit-15";
|
|
2866
|
+
var CC4_VISEME_SLOTS = [
|
|
2867
|
+
{
|
|
2868
|
+
id: "ae",
|
|
2869
|
+
label: "AE",
|
|
2870
|
+
order: 0,
|
|
2871
|
+
providerIds: { azure: [4], sapi: [4] },
|
|
2872
|
+
phonemes: ["AE", "EH", "EY", "UH"],
|
|
2873
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ae|eh|ey)([_ .-]|$)"],
|
|
2874
|
+
features: { jawOpen: 0.75, lipSpread: 0.35 },
|
|
2875
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[0]
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
id: "ah",
|
|
2879
|
+
label: "Ah",
|
|
2880
|
+
order: 1,
|
|
2881
|
+
providerIds: { azure: [1, 2, 9, 11, 12], sapi: [1, 2, 9, 11, 12] },
|
|
2882
|
+
phonemes: ["AA", "AE", "AH", "AX", "AW", "AY", "HH"],
|
|
2883
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ah|aa|aah|open)([_ .-]|$)"],
|
|
2884
|
+
features: { jawOpen: 0.8 },
|
|
2885
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[1]
|
|
2886
|
+
},
|
|
2887
|
+
{
|
|
2888
|
+
id: "b-m-p",
|
|
2889
|
+
label: "B_M_P",
|
|
2890
|
+
order: 2,
|
|
2891
|
+
providerIds: { azure: [0, 21], sapi: [0, 21] },
|
|
2892
|
+
phonemes: ["B", "M", "P"],
|
|
2893
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(b[_ .-]?m[_ .-]?p|bmp|closed|sil|rest)([_ .-]|$)"],
|
|
2894
|
+
features: { jawOpen: 0, lipClosed: 1 },
|
|
2895
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[2]
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
id: "ch-j",
|
|
2899
|
+
label: "Ch_J",
|
|
2900
|
+
order: 3,
|
|
2901
|
+
providerIds: { azure: [16], sapi: [16] },
|
|
2902
|
+
phonemes: ["CH", "JH", "SH", "ZH"],
|
|
2903
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ch|j|sh|zh)([_ .-]|$)"],
|
|
2904
|
+
features: { jawOpen: 0.3, fricative: 0.6 },
|
|
2905
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[3]
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
id: "ee",
|
|
2909
|
+
label: "EE",
|
|
2910
|
+
order: 4,
|
|
2911
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2912
|
+
phonemes: ["IY", "IH", "IX", "Y"],
|
|
2913
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ee|iy|y)([_ .-]|$)"],
|
|
2914
|
+
features: { jawOpen: 0.2, lipSpread: 0.8 },
|
|
2915
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[4]
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
id: "er",
|
|
2919
|
+
label: "Er",
|
|
2920
|
+
order: 5,
|
|
2921
|
+
providerIds: { azure: [5], sapi: [5] },
|
|
2922
|
+
phonemes: ["ER"],
|
|
2923
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(er)([_ .-]|$)"],
|
|
2924
|
+
features: { jawOpen: 0.35, lipRound: 0.35 },
|
|
2925
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[5]
|
|
2926
|
+
},
|
|
2927
|
+
{
|
|
2928
|
+
id: "f-v",
|
|
2929
|
+
label: "F_V",
|
|
2930
|
+
order: 6,
|
|
2931
|
+
providerIds: { azure: [18], sapi: [18] },
|
|
2932
|
+
phonemes: ["F", "V"],
|
|
2933
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(f[_ .-]?v|fv)([_ .-]|$)"],
|
|
2934
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2935
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[6]
|
|
2936
|
+
},
|
|
2937
|
+
{
|
|
2938
|
+
id: "ih",
|
|
2939
|
+
label: "Ih",
|
|
2940
|
+
order: 7,
|
|
2941
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2942
|
+
phonemes: ["IH", "IX"],
|
|
2943
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ih|ix)([_ .-]|$)"],
|
|
2944
|
+
features: { jawOpen: 0.2, lipSpread: 0.55 },
|
|
2945
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[7]
|
|
2946
|
+
},
|
|
2947
|
+
{
|
|
2948
|
+
id: "k-g-h-ng",
|
|
2949
|
+
label: "K_G_H_NG",
|
|
2950
|
+
order: 8,
|
|
2951
|
+
providerIds: { azure: [20], sapi: [20] },
|
|
2952
|
+
phonemes: ["K", "G", "NG"],
|
|
2953
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(k[_ .-]?g[_ .-]?h?[_ .-]?ng|kg|ng)([_ .-]|$)"],
|
|
2954
|
+
features: { jawOpen: 0.35 },
|
|
2955
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[8]
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
id: "oh",
|
|
2959
|
+
label: "Oh",
|
|
2960
|
+
order: 9,
|
|
2961
|
+
providerIds: { azure: [3, 8, 10], sapi: [3, 8, 10] },
|
|
2962
|
+
phonemes: ["AO", "OW", "OY"],
|
|
2963
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(oh|ao|ow|oy)([_ .-]|$)"],
|
|
2964
|
+
features: { jawOpen: 0.6, lipRound: 0.8 },
|
|
2965
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[9]
|
|
2966
|
+
},
|
|
2967
|
+
{
|
|
2968
|
+
id: "r",
|
|
2969
|
+
label: "R",
|
|
2970
|
+
order: 10,
|
|
2971
|
+
providerIds: { azure: [13], sapi: [13] },
|
|
2972
|
+
phonemes: ["R"],
|
|
2973
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(r)([_ .-]|$)"],
|
|
2974
|
+
features: { jawOpen: 0.35, lipRound: 0.5 },
|
|
2975
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[10]
|
|
2976
|
+
},
|
|
2977
|
+
{
|
|
2978
|
+
id: "s-z",
|
|
2979
|
+
label: "S_Z",
|
|
2980
|
+
order: 11,
|
|
2981
|
+
providerIds: { azure: [15], sapi: [15] },
|
|
2982
|
+
phonemes: ["S", "Z"],
|
|
2983
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(s[_ .-]?z|sz)([_ .-]|$)"],
|
|
2984
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2985
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[11]
|
|
2986
|
+
},
|
|
2987
|
+
{
|
|
2988
|
+
id: "t-l-d-n",
|
|
2989
|
+
label: "T_L_D_N",
|
|
2990
|
+
order: 12,
|
|
2991
|
+
providerIds: { azure: [14, 19], sapi: [14, 19] },
|
|
2992
|
+
phonemes: ["T", "L", "D", "N"],
|
|
2993
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(t[_ .-]?l[_ .-]?d[_ .-]?n|tldn|l)([_ .-]|$)"],
|
|
2994
|
+
features: { jawOpen: 0.3, tongueTip: 1 },
|
|
2995
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[12]
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
id: "th",
|
|
2999
|
+
label: "Th",
|
|
3000
|
+
order: 13,
|
|
3001
|
+
providerIds: { azure: [17], sapi: [17] },
|
|
3002
|
+
phonemes: ["TH", "DH"],
|
|
3003
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(th|dh)([_ .-]|$)"],
|
|
3004
|
+
features: { jawOpen: 0.15, tongueTip: 0.8, fricative: 0.8 },
|
|
3005
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[13]
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
id: "w-oo",
|
|
3009
|
+
label: "W_OO",
|
|
3010
|
+
order: 14,
|
|
3011
|
+
providerIds: { azure: [7], sapi: [7] },
|
|
3012
|
+
phonemes: ["W", "UW"],
|
|
3013
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(w[_ .-]?oo|woo|uw|oo)([_ .-]|$)"],
|
|
3014
|
+
features: { jawOpen: 0.5, lipRound: 1 },
|
|
3015
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[14]
|
|
3016
|
+
}
|
|
3017
|
+
];
|
|
2380
3018
|
var isMixedAU = (id) => {
|
|
2381
3019
|
const morphs = AU_TO_MORPHS[id];
|
|
2382
3020
|
const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
|
|
@@ -2688,6 +3326,23 @@ var AU_FACEPART_TO_MESH_CATEGORY = {
|
|
|
2688
3326
|
Eyelids: "eye",
|
|
2689
3327
|
Tongue: "tongue"
|
|
2690
3328
|
};
|
|
3329
|
+
var CC4_MAPPING_SECTIONS = [
|
|
3330
|
+
{ id: "Forehead", label: "Forehead", kind: "au", order: 0, meshCategory: "face", facePart: "Forehead" },
|
|
3331
|
+
{ id: "Eyelids", label: "Eyelids", kind: "au", order: 1, meshCategory: "eye", facePart: "Eyelids" },
|
|
3332
|
+
{ id: "Eyes", label: "Eyes", kind: "au", order: 2, meshCategory: "eye", facePart: "Eyes" },
|
|
3333
|
+
{ id: "Cheeks", label: "Cheeks", kind: "au", order: 3, meshCategory: "face", facePart: "Cheeks" },
|
|
3334
|
+
{ id: "Nose", label: "Nose", kind: "au", order: 4, meshCategory: "face", facePart: "Nose" },
|
|
3335
|
+
{ id: "Mouth", label: "Mouth", kind: "au", order: 5, meshCategory: "face", facePart: "Mouth" },
|
|
3336
|
+
{ id: "Chin", label: "Chin", kind: "au", order: 6, meshCategory: "face", facePart: "Chin" },
|
|
3337
|
+
{ id: "Jaw", label: "Jaw", kind: "au", order: 7, meshCategory: "face", facePart: "Jaw" },
|
|
3338
|
+
{ id: "Tongue", label: "Tongue", kind: "au", order: 8, meshCategory: "tongue", facePart: "Tongue" },
|
|
3339
|
+
{ id: "Head", label: "Head", kind: "au", order: 9, meshCategory: "face", facePart: "Head" },
|
|
3340
|
+
{ id: "Joint Controls", label: "Joint Controls", kind: "au", order: 10, meshCategory: "face", facePart: "Joint Controls" },
|
|
3341
|
+
{ id: "Eye", label: "Eye", kind: "au", order: 11, meshCategory: "eye", facePart: "Eye" },
|
|
3342
|
+
{ id: "Hair", label: "Hair", kind: "hair", order: 12, meshCategory: "hair" },
|
|
3343
|
+
{ id: "Visemes", label: "Visemes", kind: "viseme", order: 13, meshCategory: "viseme" },
|
|
3344
|
+
{ id: "Unmapped", label: "Unmapped", kind: "unmapped", order: 14, meshCategory: "face" }
|
|
3345
|
+
];
|
|
2691
3346
|
var CC4_HAIR_PHYSICS = {
|
|
2692
3347
|
stiffness: 7.5,
|
|
2693
3348
|
damping: 0.18,
|
|
@@ -2734,7 +3389,10 @@ var CC4_PRESET = {
|
|
|
2734
3389
|
suffixPattern: CC4_SUFFIX_PATTERN,
|
|
2735
3390
|
morphToMesh: MORPH_TO_MESH,
|
|
2736
3391
|
auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
|
|
3392
|
+
mappingSections: CC4_MAPPING_SECTIONS,
|
|
2737
3393
|
visemeKeys: VISEME_KEYS,
|
|
3394
|
+
visemeSystemId: CC4_VISEME_SYSTEM_ID,
|
|
3395
|
+
visemeSlots: CC4_VISEME_SLOTS,
|
|
2738
3396
|
visemeMeshCategory: "viseme",
|
|
2739
3397
|
visemeJawAmounts: VISEME_JAW_AMOUNTS,
|
|
2740
3398
|
auMixDefaults: AU_MIX_DEFAULTS,
|
|
@@ -3621,8 +4279,13 @@ function extendPresetWithProfile(base, extension) {
|
|
|
3621
4279
|
boneNodes: mergeRecord(base.boneNodes, extension.boneNodes),
|
|
3622
4280
|
morphToMesh: mergeRecord(base.morphToMesh, extension.morphToMesh),
|
|
3623
4281
|
auFacePartToMeshCategory: base.auFacePartToMeshCategory || extension.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, extension.auFacePartToMeshCategory || {}) : void 0,
|
|
4282
|
+
mappingSections: extension.mappingSections ? [...extension.mappingSections] : base.mappingSections ? [...base.mappingSections] : void 0,
|
|
3624
4283
|
visemeKeys: extension.visemeKeys ? [...extension.visemeKeys] : [...base.visemeKeys],
|
|
4284
|
+
visemeSystemId: extension.visemeSystemId ?? base.visemeSystemId,
|
|
4285
|
+
visemeSlots: extension.visemeSlots ? [...extension.visemeSlots] : base.visemeSlots ? [...base.visemeSlots] : void 0,
|
|
4286
|
+
visemeBindings: base.visemeBindings || extension.visemeBindings ? mergeRecord(base.visemeBindings || {}, extension.visemeBindings || {}) : void 0,
|
|
3625
4287
|
visemeMeshCategory: extension.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
4288
|
+
visemeJawAmounts: extension.visemeJawAmounts ? [...extension.visemeJawAmounts] : base.visemeJawAmounts ? [...base.visemeJawAmounts] : void 0,
|
|
3626
4289
|
auMixDefaults: base.auMixDefaults || extension.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, extension.auMixDefaults || {}) : void 0,
|
|
3627
4290
|
auInfo: base.auInfo || extension.auInfo ? mergeRecord(base.auInfo || {}, extension.auInfo || {}) : void 0,
|
|
3628
4291
|
eyeMeshNodes: extension.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
@@ -4584,7 +5247,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4584
5247
|
getCompositeRotations: () => this.compositeRotations,
|
|
4585
5248
|
computeSideValues: (base, balance) => this.computeSideValues(base, balance),
|
|
4586
5249
|
getAUMixWeight: (auId) => this.getAUMixWeight(auId),
|
|
4587
|
-
isMixedAU: (auId) => this.isMixedAU(auId)
|
|
5250
|
+
isMixedAU: (auId) => this.isMixedAU(auId),
|
|
5251
|
+
reapplyProceduralState: () => this.reapplyProceduralStateAfterBakedUpdate()
|
|
4588
5252
|
});
|
|
4589
5253
|
this.hairPhysics = new HairPhysicsController({
|
|
4590
5254
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
@@ -5188,6 +5852,21 @@ var _Loom3 = class _Loom3 {
|
|
|
5188
5852
|
const jawHandle = this.transitionBoneRotation("JAW", "pitch", jawAmount, durationMs);
|
|
5189
5853
|
return this.combineHandles([morphHandle, jawHandle]);
|
|
5190
5854
|
}
|
|
5855
|
+
setVisemeById(slotId, value, jawScale = 1) {
|
|
5856
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5857
|
+
if (index < 0) return;
|
|
5858
|
+
this.setViseme(index, value, jawScale);
|
|
5859
|
+
}
|
|
5860
|
+
transitionVisemeById(slotId, to, durationMs = 80, jawScale = 1) {
|
|
5861
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5862
|
+
if (index < 0) {
|
|
5863
|
+
return { promise: Promise.resolve(), pause: () => {
|
|
5864
|
+
}, resume: () => {
|
|
5865
|
+
}, cancel: () => {
|
|
5866
|
+
} };
|
|
5867
|
+
}
|
|
5868
|
+
return this.transitionViseme(index, to, durationMs, jawScale);
|
|
5869
|
+
}
|
|
5191
5870
|
// ============================================================================
|
|
5192
5871
|
// MIX WEIGHT CONTROL
|
|
5193
5872
|
// ============================================================================
|
|
@@ -5280,6 +5959,30 @@ var _Loom3 = class _Loom3 {
|
|
|
5280
5959
|
this.model.updateMatrixWorld(true);
|
|
5281
5960
|
}
|
|
5282
5961
|
}
|
|
5962
|
+
reapplyProceduralStateAfterBakedUpdate() {
|
|
5963
|
+
if (!this.model) {
|
|
5964
|
+
return;
|
|
5965
|
+
}
|
|
5966
|
+
let hasActiveOverrides = false;
|
|
5967
|
+
for (const [auIdStr, value] of Object.entries(this.auValues)) {
|
|
5968
|
+
if (value <= 0) continue;
|
|
5969
|
+
const auId = Number(auIdStr);
|
|
5970
|
+
if (Number.isNaN(auId)) continue;
|
|
5971
|
+
hasActiveOverrides = true;
|
|
5972
|
+
this.setAU(auId, value, this.auBalances[auId]);
|
|
5973
|
+
}
|
|
5974
|
+
for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
|
|
5975
|
+
const value = this.visemeValues[visemeIndex] ?? 0;
|
|
5976
|
+
if (value <= 0) continue;
|
|
5977
|
+
hasActiveOverrides = true;
|
|
5978
|
+
this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
|
|
5979
|
+
}
|
|
5980
|
+
if (!hasActiveOverrides) {
|
|
5981
|
+
return;
|
|
5982
|
+
}
|
|
5983
|
+
this.flushPendingComposites();
|
|
5984
|
+
this.model.updateMatrixWorld(true);
|
|
5985
|
+
}
|
|
5283
5986
|
// ============================================================================
|
|
5284
5987
|
// MESH CONTROL
|
|
5285
5988
|
// ============================================================================
|
|
@@ -5533,21 +6236,10 @@ var _Loom3 = class _Loom3 {
|
|
|
5533
6236
|
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
5534
6237
|
*/
|
|
5535
6238
|
getMeshNamesForAU(auId) {
|
|
5536
|
-
|
|
5537
|
-
const info = this.config.auInfo?.[String(auId)];
|
|
5538
|
-
const facePart = info?.facePart;
|
|
5539
|
-
if (facePart) {
|
|
5540
|
-
const category = this.config.auFacePartToMeshCategory?.[facePart];
|
|
5541
|
-
if (category) {
|
|
5542
|
-
return m?.[category] || [];
|
|
5543
|
-
}
|
|
5544
|
-
}
|
|
5545
|
-
return m?.face || [];
|
|
6239
|
+
return getMeshNamesForAUProfile(this.config, auId);
|
|
5546
6240
|
}
|
|
5547
6241
|
getMeshNamesForViseme() {
|
|
5548
|
-
|
|
5549
|
-
const category = this.config.visemeMeshCategory || (m?.viseme ? "viseme" : "face");
|
|
5550
|
-
return m?.[category] || m?.face || [];
|
|
6242
|
+
return getMeshNamesForVisemeProfile(this.config);
|
|
5551
6243
|
}
|
|
5552
6244
|
// ============================================================================
|
|
5553
6245
|
// HAIR PHYSICS
|
|
@@ -5815,7 +6507,7 @@ var _Loom3 = class _Loom3 {
|
|
|
5815
6507
|
);
|
|
5816
6508
|
}
|
|
5817
6509
|
getVisemeJawAmount(visemeIndex) {
|
|
5818
|
-
return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
6510
|
+
return getVisemeJawAmounts(this.config)?.[visemeIndex] ?? this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
5819
6511
|
}
|
|
5820
6512
|
collectResolvedExpressionMorphTargets() {
|
|
5821
6513
|
const targets = [];
|
|
@@ -6293,7 +6985,11 @@ var PROFILE_OVERRIDE_KEYS = [
|
|
|
6293
6985
|
"rightMorphSuffixes",
|
|
6294
6986
|
"morphToMesh",
|
|
6295
6987
|
"auFacePartToMeshCategory",
|
|
6988
|
+
"mappingSections",
|
|
6296
6989
|
"visemeKeys",
|
|
6990
|
+
"visemeSystemId",
|
|
6991
|
+
"visemeSlots",
|
|
6992
|
+
"visemeBindings",
|
|
6297
6993
|
"visemeMeshCategory",
|
|
6298
6994
|
"visemeJawAmounts",
|
|
6299
6995
|
"auMixDefaults",
|
|
@@ -7393,6 +8089,33 @@ function validateMappingConfig(config) {
|
|
|
7393
8089
|
}
|
|
7394
8090
|
visemeSeen.add(key);
|
|
7395
8091
|
}
|
|
8092
|
+
const morphCategories = new Set(Object.keys(config.morphToMesh || {}));
|
|
8093
|
+
if (config.visemeMeshCategory && !morphCategories.has(config.visemeMeshCategory)) {
|
|
8094
|
+
push(
|
|
8095
|
+
"error",
|
|
8096
|
+
"VISEME_MESH_CATEGORY_MISSING",
|
|
8097
|
+
`visemeMeshCategory "${config.visemeMeshCategory}" is not present in morphToMesh`,
|
|
8098
|
+
{ category: config.visemeMeshCategory }
|
|
8099
|
+
);
|
|
8100
|
+
}
|
|
8101
|
+
for (const [facePart, category] of Object.entries(config.auFacePartToMeshCategory || {})) {
|
|
8102
|
+
if (!morphCategories.has(category)) {
|
|
8103
|
+
push(
|
|
8104
|
+
"error",
|
|
8105
|
+
"AU_MESH_CATEGORY_MISSING",
|
|
8106
|
+
`AU facePart "${facePart}" routes to missing morphToMesh category "${category}"`,
|
|
8107
|
+
{ facePart, category }
|
|
8108
|
+
);
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
if (config.visemeJawAmounts && config.visemeJawAmounts.length !== (config.visemeKeys || []).length) {
|
|
8112
|
+
push(
|
|
8113
|
+
"warning",
|
|
8114
|
+
"VISEME_JAW_AMOUNT_LENGTH_MISMATCH",
|
|
8115
|
+
"visemeJawAmounts length does not match visemeKeys length",
|
|
8116
|
+
{ visemeKeys: (config.visemeKeys || []).length, visemeJawAmounts: config.visemeJawAmounts.length }
|
|
8117
|
+
);
|
|
8118
|
+
}
|
|
7396
8119
|
if (config.auMixDefaults) {
|
|
7397
8120
|
for (const key of Object.keys(config.auMixDefaults)) {
|
|
7398
8121
|
const auId = Number(key);
|
|
@@ -7846,6 +8569,6 @@ async function analyzeModel(options) {
|
|
|
7846
8569
|
};
|
|
7847
8570
|
}
|
|
7848
8571
|
|
|
7849
|
-
export { AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, FISH_AU_MAPPING_CONFIG, HairPhysics, Loom3, Loom3 as Loom3Three, Loom3 as LoomLargeThree, MORPH_TO_MESH, VISEME_JAW_AMOUNTS, VISEME_KEYS, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, suggestBestPreset, validateMappingConfig, validateMappings };
|
|
8572
|
+
export { AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MAPPING_SECTIONS, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, CC4_VISEME_SLOTS, CC4_VISEME_SYSTEM_ID, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, FISH_AU_MAPPING_CONFIG, HairPhysics, Loom3, Loom3 as Loom3Three, Loom3 as LoomLargeThree, MORPH_TO_MESH, VISEME_JAW_AMOUNTS, VISEME_KEYS, analyzeModel, applyCharacterProfileToPreset, buildMappingEditorModel, collectMorphMeshes, compileVisemeKeys, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getMeshNamesForAUProfile, getMeshNamesForVisemeProfile, getModelForwardDirection, getPreset, getPresetWithProfile, getProfileVisemeSlots, getVisemeJawAmounts, getVisemeSlotIndex, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mapProviderVisemeToSlot, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, resolveVisemeMeshCategory, suggestBestPreset, validateMappingConfig, validateMappings };
|
|
7850
8573
|
//# sourceMappingURL=index.js.map
|
|
7851
8574
|
//# sourceMappingURL=index.js.map
|