@lovelace_lol/loom3 1.0.41 → 1.0.42
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 +460 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -1
- package/dist/index.d.ts +121 -1
- package/dist/index.js +449 -27
- 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;
|
|
@@ -670,12 +887,7 @@ var BakedAnimationController = class {
|
|
|
670
887
|
if (typeof this.host.getMeshNamesForAU === "function") {
|
|
671
888
|
return this.host.getMeshNamesForAU(auId) || [];
|
|
672
889
|
}
|
|
673
|
-
|
|
674
|
-
if (facePart) {
|
|
675
|
-
const category = config.auFacePartToMeshCategory?.[facePart];
|
|
676
|
-
if (category) return config.morphToMesh?.[category] || [];
|
|
677
|
-
}
|
|
678
|
-
return config.morphToMesh?.face || [];
|
|
890
|
+
return getMeshNamesForAUProfile(config, auId);
|
|
679
891
|
}
|
|
680
892
|
getMeshNamesForViseme(config, explicitMeshNames) {
|
|
681
893
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
@@ -684,10 +896,7 @@ var BakedAnimationController = class {
|
|
|
684
896
|
if (typeof this.host.getMeshNamesForViseme === "function") {
|
|
685
897
|
return this.host.getMeshNamesForViseme() || [];
|
|
686
898
|
}
|
|
687
|
-
|
|
688
|
-
const visemeMeshes = config.morphToMesh?.[category];
|
|
689
|
-
if (visemeMeshes && visemeMeshes.length > 0) return visemeMeshes;
|
|
690
|
-
return config.morphToMesh?.face || [];
|
|
899
|
+
return getMeshNamesForVisemeProfile(config);
|
|
691
900
|
}
|
|
692
901
|
update(dtSeconds) {
|
|
693
902
|
if (this.animationMixer) {
|
|
@@ -1740,7 +1949,7 @@ var BakedAnimationController = class {
|
|
|
1740
1949
|
}
|
|
1741
1950
|
addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
|
|
1742
1951
|
const config = this.host.getConfig();
|
|
1743
|
-
const hasExplicitMeshes =
|
|
1952
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1744
1953
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1745
1954
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1746
1955
|
const addTrackForMesh = (mesh) => {
|
|
@@ -1764,7 +1973,7 @@ var BakedAnimationController = class {
|
|
|
1764
1973
|
addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
|
|
1765
1974
|
if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
|
|
1766
1975
|
const config = this.host.getConfig();
|
|
1767
|
-
const hasExplicitMeshes =
|
|
1976
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1768
1977
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1769
1978
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1770
1979
|
const addTrackForMesh = (mesh) => {
|
|
@@ -2398,6 +2607,159 @@ var VISEME_JAW_AMOUNTS = [
|
|
|
2398
2607
|
0.5
|
|
2399
2608
|
// 14: W_OO
|
|
2400
2609
|
];
|
|
2610
|
+
var CC4_VISEME_SYSTEM_ID = "cc4-arkit-15";
|
|
2611
|
+
var CC4_VISEME_SLOTS = [
|
|
2612
|
+
{
|
|
2613
|
+
id: "ae",
|
|
2614
|
+
label: "AE",
|
|
2615
|
+
order: 0,
|
|
2616
|
+
providerIds: { azure: [4], sapi: [4] },
|
|
2617
|
+
phonemes: ["AE", "EH", "EY", "UH"],
|
|
2618
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ae|eh|ey)([_ .-]|$)"],
|
|
2619
|
+
features: { jawOpen: 0.75, lipSpread: 0.35 },
|
|
2620
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[0]
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
id: "ah",
|
|
2624
|
+
label: "Ah",
|
|
2625
|
+
order: 1,
|
|
2626
|
+
providerIds: { azure: [1, 2, 9, 11, 12], sapi: [1, 2, 9, 11, 12] },
|
|
2627
|
+
phonemes: ["AA", "AE", "AH", "AX", "AW", "AY", "HH"],
|
|
2628
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ah|aa|aah|open)([_ .-]|$)"],
|
|
2629
|
+
features: { jawOpen: 0.8 },
|
|
2630
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[1]
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
id: "b-m-p",
|
|
2634
|
+
label: "B_M_P",
|
|
2635
|
+
order: 2,
|
|
2636
|
+
providerIds: { azure: [0, 21], sapi: [0, 21] },
|
|
2637
|
+
phonemes: ["B", "M", "P"],
|
|
2638
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(b[_ .-]?m[_ .-]?p|bmp|closed|sil|rest)([_ .-]|$)"],
|
|
2639
|
+
features: { jawOpen: 0, lipClosed: 1 },
|
|
2640
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[2]
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
id: "ch-j",
|
|
2644
|
+
label: "Ch_J",
|
|
2645
|
+
order: 3,
|
|
2646
|
+
providerIds: { azure: [16], sapi: [16] },
|
|
2647
|
+
phonemes: ["CH", "JH", "SH", "ZH"],
|
|
2648
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ch|j|sh|zh)([_ .-]|$)"],
|
|
2649
|
+
features: { jawOpen: 0.3, fricative: 0.6 },
|
|
2650
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[3]
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
id: "ee",
|
|
2654
|
+
label: "EE",
|
|
2655
|
+
order: 4,
|
|
2656
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2657
|
+
phonemes: ["IY", "IH", "IX", "Y"],
|
|
2658
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ee|iy|y)([_ .-]|$)"],
|
|
2659
|
+
features: { jawOpen: 0.2, lipSpread: 0.8 },
|
|
2660
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[4]
|
|
2661
|
+
},
|
|
2662
|
+
{
|
|
2663
|
+
id: "er",
|
|
2664
|
+
label: "Er",
|
|
2665
|
+
order: 5,
|
|
2666
|
+
providerIds: { azure: [5], sapi: [5] },
|
|
2667
|
+
phonemes: ["ER"],
|
|
2668
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(er)([_ .-]|$)"],
|
|
2669
|
+
features: { jawOpen: 0.35, lipRound: 0.35 },
|
|
2670
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[5]
|
|
2671
|
+
},
|
|
2672
|
+
{
|
|
2673
|
+
id: "f-v",
|
|
2674
|
+
label: "F_V",
|
|
2675
|
+
order: 6,
|
|
2676
|
+
providerIds: { azure: [18], sapi: [18] },
|
|
2677
|
+
phonemes: ["F", "V"],
|
|
2678
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(f[_ .-]?v|fv)([_ .-]|$)"],
|
|
2679
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2680
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[6]
|
|
2681
|
+
},
|
|
2682
|
+
{
|
|
2683
|
+
id: "ih",
|
|
2684
|
+
label: "Ih",
|
|
2685
|
+
order: 7,
|
|
2686
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2687
|
+
phonemes: ["IH", "IX"],
|
|
2688
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ih|ix)([_ .-]|$)"],
|
|
2689
|
+
features: { jawOpen: 0.2, lipSpread: 0.55 },
|
|
2690
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[7]
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
id: "k-g-h-ng",
|
|
2694
|
+
label: "K_G_H_NG",
|
|
2695
|
+
order: 8,
|
|
2696
|
+
providerIds: { azure: [20], sapi: [20] },
|
|
2697
|
+
phonemes: ["K", "G", "NG"],
|
|
2698
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(k[_ .-]?g[_ .-]?h?[_ .-]?ng|kg|ng)([_ .-]|$)"],
|
|
2699
|
+
features: { jawOpen: 0.35 },
|
|
2700
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[8]
|
|
2701
|
+
},
|
|
2702
|
+
{
|
|
2703
|
+
id: "oh",
|
|
2704
|
+
label: "Oh",
|
|
2705
|
+
order: 9,
|
|
2706
|
+
providerIds: { azure: [3, 8, 10], sapi: [3, 8, 10] },
|
|
2707
|
+
phonemes: ["AO", "OW", "OY"],
|
|
2708
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(oh|ao|ow|oy)([_ .-]|$)"],
|
|
2709
|
+
features: { jawOpen: 0.6, lipRound: 0.8 },
|
|
2710
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[9]
|
|
2711
|
+
},
|
|
2712
|
+
{
|
|
2713
|
+
id: "r",
|
|
2714
|
+
label: "R",
|
|
2715
|
+
order: 10,
|
|
2716
|
+
providerIds: { azure: [13], sapi: [13] },
|
|
2717
|
+
phonemes: ["R"],
|
|
2718
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(r)([_ .-]|$)"],
|
|
2719
|
+
features: { jawOpen: 0.35, lipRound: 0.5 },
|
|
2720
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[10]
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
id: "s-z",
|
|
2724
|
+
label: "S_Z",
|
|
2725
|
+
order: 11,
|
|
2726
|
+
providerIds: { azure: [15], sapi: [15] },
|
|
2727
|
+
phonemes: ["S", "Z"],
|
|
2728
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(s[_ .-]?z|sz)([_ .-]|$)"],
|
|
2729
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2730
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[11]
|
|
2731
|
+
},
|
|
2732
|
+
{
|
|
2733
|
+
id: "t-l-d-n",
|
|
2734
|
+
label: "T_L_D_N",
|
|
2735
|
+
order: 12,
|
|
2736
|
+
providerIds: { azure: [14, 19], sapi: [14, 19] },
|
|
2737
|
+
phonemes: ["T", "L", "D", "N"],
|
|
2738
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(t[_ .-]?l[_ .-]?d[_ .-]?n|tldn|l)([_ .-]|$)"],
|
|
2739
|
+
features: { jawOpen: 0.3, tongueTip: 1 },
|
|
2740
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[12]
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
id: "th",
|
|
2744
|
+
label: "Th",
|
|
2745
|
+
order: 13,
|
|
2746
|
+
providerIds: { azure: [17], sapi: [17] },
|
|
2747
|
+
phonemes: ["TH", "DH"],
|
|
2748
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(th|dh)([_ .-]|$)"],
|
|
2749
|
+
features: { jawOpen: 0.15, tongueTip: 0.8, fricative: 0.8 },
|
|
2750
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[13]
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
2753
|
+
id: "w-oo",
|
|
2754
|
+
label: "W_OO",
|
|
2755
|
+
order: 14,
|
|
2756
|
+
providerIds: { azure: [7], sapi: [7] },
|
|
2757
|
+
phonemes: ["W", "UW"],
|
|
2758
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(w[_ .-]?oo|woo|uw|oo)([_ .-]|$)"],
|
|
2759
|
+
features: { jawOpen: 0.5, lipRound: 1 },
|
|
2760
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[14]
|
|
2761
|
+
}
|
|
2762
|
+
];
|
|
2401
2763
|
var isMixedAU = (id) => {
|
|
2402
2764
|
const morphs = AU_TO_MORPHS[id];
|
|
2403
2765
|
const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
|
|
@@ -2709,6 +3071,23 @@ var AU_FACEPART_TO_MESH_CATEGORY = {
|
|
|
2709
3071
|
Eyelids: "eye",
|
|
2710
3072
|
Tongue: "tongue"
|
|
2711
3073
|
};
|
|
3074
|
+
var CC4_MAPPING_SECTIONS = [
|
|
3075
|
+
{ id: "Forehead", label: "Forehead", kind: "au", order: 0, meshCategory: "face", facePart: "Forehead" },
|
|
3076
|
+
{ id: "Eyelids", label: "Eyelids", kind: "au", order: 1, meshCategory: "eye", facePart: "Eyelids" },
|
|
3077
|
+
{ id: "Eyes", label: "Eyes", kind: "au", order: 2, meshCategory: "eye", facePart: "Eyes" },
|
|
3078
|
+
{ id: "Cheeks", label: "Cheeks", kind: "au", order: 3, meshCategory: "face", facePart: "Cheeks" },
|
|
3079
|
+
{ id: "Nose", label: "Nose", kind: "au", order: 4, meshCategory: "face", facePart: "Nose" },
|
|
3080
|
+
{ id: "Mouth", label: "Mouth", kind: "au", order: 5, meshCategory: "face", facePart: "Mouth" },
|
|
3081
|
+
{ id: "Chin", label: "Chin", kind: "au", order: 6, meshCategory: "face", facePart: "Chin" },
|
|
3082
|
+
{ id: "Jaw", label: "Jaw", kind: "au", order: 7, meshCategory: "face", facePart: "Jaw" },
|
|
3083
|
+
{ id: "Tongue", label: "Tongue", kind: "au", order: 8, meshCategory: "tongue", facePart: "Tongue" },
|
|
3084
|
+
{ id: "Head", label: "Head", kind: "au", order: 9, meshCategory: "face", facePart: "Head" },
|
|
3085
|
+
{ id: "Joint Controls", label: "Joint Controls", kind: "au", order: 10, meshCategory: "face", facePart: "Joint Controls" },
|
|
3086
|
+
{ id: "Eye", label: "Eye", kind: "au", order: 11, meshCategory: "eye", facePart: "Eye" },
|
|
3087
|
+
{ id: "Hair", label: "Hair", kind: "hair", order: 12, meshCategory: "hair" },
|
|
3088
|
+
{ id: "Visemes", label: "Visemes", kind: "viseme", order: 13, meshCategory: "viseme" },
|
|
3089
|
+
{ id: "Unmapped", label: "Unmapped", kind: "unmapped", order: 14, meshCategory: "face" }
|
|
3090
|
+
];
|
|
2712
3091
|
var CC4_HAIR_PHYSICS = {
|
|
2713
3092
|
stiffness: 7.5,
|
|
2714
3093
|
damping: 0.18,
|
|
@@ -2755,7 +3134,10 @@ var CC4_PRESET = {
|
|
|
2755
3134
|
suffixPattern: CC4_SUFFIX_PATTERN,
|
|
2756
3135
|
morphToMesh: MORPH_TO_MESH,
|
|
2757
3136
|
auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
|
|
3137
|
+
mappingSections: CC4_MAPPING_SECTIONS,
|
|
2758
3138
|
visemeKeys: VISEME_KEYS,
|
|
3139
|
+
visemeSystemId: CC4_VISEME_SYSTEM_ID,
|
|
3140
|
+
visemeSlots: CC4_VISEME_SLOTS,
|
|
2759
3141
|
visemeMeshCategory: "viseme",
|
|
2760
3142
|
visemeJawAmounts: VISEME_JAW_AMOUNTS,
|
|
2761
3143
|
auMixDefaults: AU_MIX_DEFAULTS,
|
|
@@ -3642,8 +4024,13 @@ function extendPresetWithProfile(base, extension) {
|
|
|
3642
4024
|
boneNodes: mergeRecord(base.boneNodes, extension.boneNodes),
|
|
3643
4025
|
morphToMesh: mergeRecord(base.morphToMesh, extension.morphToMesh),
|
|
3644
4026
|
auFacePartToMeshCategory: base.auFacePartToMeshCategory || extension.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, extension.auFacePartToMeshCategory || {}) : void 0,
|
|
4027
|
+
mappingSections: extension.mappingSections ? [...extension.mappingSections] : base.mappingSections ? [...base.mappingSections] : void 0,
|
|
3645
4028
|
visemeKeys: extension.visemeKeys ? [...extension.visemeKeys] : [...base.visemeKeys],
|
|
4029
|
+
visemeSystemId: extension.visemeSystemId ?? base.visemeSystemId,
|
|
4030
|
+
visemeSlots: extension.visemeSlots ? [...extension.visemeSlots] : base.visemeSlots ? [...base.visemeSlots] : void 0,
|
|
4031
|
+
visemeBindings: base.visemeBindings || extension.visemeBindings ? mergeRecord(base.visemeBindings || {}, extension.visemeBindings || {}) : void 0,
|
|
3646
4032
|
visemeMeshCategory: extension.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
4033
|
+
visemeJawAmounts: extension.visemeJawAmounts ? [...extension.visemeJawAmounts] : base.visemeJawAmounts ? [...base.visemeJawAmounts] : void 0,
|
|
3647
4034
|
auMixDefaults: base.auMixDefaults || extension.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, extension.auMixDefaults || {}) : void 0,
|
|
3648
4035
|
auInfo: base.auInfo || extension.auInfo ? mergeRecord(base.auInfo || {}, extension.auInfo || {}) : void 0,
|
|
3649
4036
|
eyeMeshNodes: extension.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
@@ -5209,6 +5596,21 @@ var _Loom3 = class _Loom3 {
|
|
|
5209
5596
|
const jawHandle = this.transitionBoneRotation("JAW", "pitch", jawAmount, durationMs);
|
|
5210
5597
|
return this.combineHandles([morphHandle, jawHandle]);
|
|
5211
5598
|
}
|
|
5599
|
+
setVisemeById(slotId, value, jawScale = 1) {
|
|
5600
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5601
|
+
if (index < 0) return;
|
|
5602
|
+
this.setViseme(index, value, jawScale);
|
|
5603
|
+
}
|
|
5604
|
+
transitionVisemeById(slotId, to, durationMs = 80, jawScale = 1) {
|
|
5605
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5606
|
+
if (index < 0) {
|
|
5607
|
+
return { promise: Promise.resolve(), pause: () => {
|
|
5608
|
+
}, resume: () => {
|
|
5609
|
+
}, cancel: () => {
|
|
5610
|
+
} };
|
|
5611
|
+
}
|
|
5612
|
+
return this.transitionViseme(index, to, durationMs, jawScale);
|
|
5613
|
+
}
|
|
5212
5614
|
// ============================================================================
|
|
5213
5615
|
// MIX WEIGHT CONTROL
|
|
5214
5616
|
// ============================================================================
|
|
@@ -5554,21 +5956,10 @@ var _Loom3 = class _Loom3 {
|
|
|
5554
5956
|
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
5555
5957
|
*/
|
|
5556
5958
|
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 || [];
|
|
5959
|
+
return getMeshNamesForAUProfile(this.config, auId);
|
|
5567
5960
|
}
|
|
5568
5961
|
getMeshNamesForViseme() {
|
|
5569
|
-
|
|
5570
|
-
const category = this.config.visemeMeshCategory || (m?.viseme ? "viseme" : "face");
|
|
5571
|
-
return m?.[category] || m?.face || [];
|
|
5962
|
+
return getMeshNamesForVisemeProfile(this.config);
|
|
5572
5963
|
}
|
|
5573
5964
|
// ============================================================================
|
|
5574
5965
|
// HAIR PHYSICS
|
|
@@ -5836,7 +6227,7 @@ var _Loom3 = class _Loom3 {
|
|
|
5836
6227
|
);
|
|
5837
6228
|
}
|
|
5838
6229
|
getVisemeJawAmount(visemeIndex) {
|
|
5839
|
-
return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
6230
|
+
return getVisemeJawAmounts(this.config)?.[visemeIndex] ?? this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
5840
6231
|
}
|
|
5841
6232
|
collectResolvedExpressionMorphTargets() {
|
|
5842
6233
|
const targets = [];
|
|
@@ -6314,7 +6705,11 @@ var PROFILE_OVERRIDE_KEYS = [
|
|
|
6314
6705
|
"rightMorphSuffixes",
|
|
6315
6706
|
"morphToMesh",
|
|
6316
6707
|
"auFacePartToMeshCategory",
|
|
6708
|
+
"mappingSections",
|
|
6317
6709
|
"visemeKeys",
|
|
6710
|
+
"visemeSystemId",
|
|
6711
|
+
"visemeSlots",
|
|
6712
|
+
"visemeBindings",
|
|
6318
6713
|
"visemeMeshCategory",
|
|
6319
6714
|
"visemeJawAmounts",
|
|
6320
6715
|
"auMixDefaults",
|
|
@@ -7414,6 +7809,33 @@ function validateMappingConfig(config) {
|
|
|
7414
7809
|
}
|
|
7415
7810
|
visemeSeen.add(key);
|
|
7416
7811
|
}
|
|
7812
|
+
const morphCategories = new Set(Object.keys(config.morphToMesh || {}));
|
|
7813
|
+
if (config.visemeMeshCategory && !morphCategories.has(config.visemeMeshCategory)) {
|
|
7814
|
+
push(
|
|
7815
|
+
"error",
|
|
7816
|
+
"VISEME_MESH_CATEGORY_MISSING",
|
|
7817
|
+
`visemeMeshCategory "${config.visemeMeshCategory}" is not present in morphToMesh`,
|
|
7818
|
+
{ category: config.visemeMeshCategory }
|
|
7819
|
+
);
|
|
7820
|
+
}
|
|
7821
|
+
for (const [facePart, category] of Object.entries(config.auFacePartToMeshCategory || {})) {
|
|
7822
|
+
if (!morphCategories.has(category)) {
|
|
7823
|
+
push(
|
|
7824
|
+
"error",
|
|
7825
|
+
"AU_MESH_CATEGORY_MISSING",
|
|
7826
|
+
`AU facePart "${facePart}" routes to missing morphToMesh category "${category}"`,
|
|
7827
|
+
{ facePart, category }
|
|
7828
|
+
);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
if (config.visemeJawAmounts && config.visemeJawAmounts.length !== (config.visemeKeys || []).length) {
|
|
7832
|
+
push(
|
|
7833
|
+
"warning",
|
|
7834
|
+
"VISEME_JAW_AMOUNT_LENGTH_MISMATCH",
|
|
7835
|
+
"visemeJawAmounts length does not match visemeKeys length",
|
|
7836
|
+
{ visemeKeys: (config.visemeKeys || []).length, visemeJawAmounts: config.visemeJawAmounts.length }
|
|
7837
|
+
);
|
|
7838
|
+
}
|
|
7417
7839
|
if (config.auMixDefaults) {
|
|
7418
7840
|
for (const key of Object.keys(config.auMixDefaults)) {
|
|
7419
7841
|
const auId = Number(key);
|
|
@@ -7878,9 +8300,12 @@ exports.BONE_AU_TO_BINDINGS = BONE_AU_TO_BINDINGS;
|
|
|
7878
8300
|
exports.CC4_BONE_NODES = CC4_BONE_NODES;
|
|
7879
8301
|
exports.CC4_BONE_PREFIX = CC4_BONE_PREFIX;
|
|
7880
8302
|
exports.CC4_EYE_MESH_NODES = CC4_EYE_MESH_NODES;
|
|
8303
|
+
exports.CC4_MAPPING_SECTIONS = CC4_MAPPING_SECTIONS;
|
|
7881
8304
|
exports.CC4_MESHES = CC4_MESHES;
|
|
7882
8305
|
exports.CC4_PRESET = CC4_PRESET;
|
|
7883
8306
|
exports.CC4_SUFFIX_PATTERN = CC4_SUFFIX_PATTERN;
|
|
8307
|
+
exports.CC4_VISEME_SLOTS = CC4_VISEME_SLOTS;
|
|
8308
|
+
exports.CC4_VISEME_SYSTEM_ID = CC4_VISEME_SYSTEM_ID;
|
|
7884
8309
|
exports.COMPOSITE_ROTATIONS = COMPOSITE_ROTATIONS;
|
|
7885
8310
|
exports.CONTINUUM_LABELS = CONTINUUM_LABELS;
|
|
7886
8311
|
exports.CONTINUUM_PAIRS_MAP = CONTINUUM_PAIRS_MAP;
|
|
@@ -7895,7 +8320,9 @@ exports.VISEME_JAW_AMOUNTS = VISEME_JAW_AMOUNTS;
|
|
|
7895
8320
|
exports.VISEME_KEYS = VISEME_KEYS;
|
|
7896
8321
|
exports.analyzeModel = analyzeModel;
|
|
7897
8322
|
exports.applyCharacterProfileToPreset = applyCharacterProfileToPreset;
|
|
8323
|
+
exports.buildMappingEditorModel = buildMappingEditorModel;
|
|
7898
8324
|
exports.collectMorphMeshes = collectMorphMeshes;
|
|
8325
|
+
exports.compileVisemeKeys = compileVisemeKeys;
|
|
7899
8326
|
exports.computeCameraRelativeGazeOffset = computeCameraRelativeGazeOffset;
|
|
7900
8327
|
exports.detectFacingDirection = detectFacingDirection;
|
|
7901
8328
|
exports.extendCharacterConfigWithPreset = extendCharacterConfigWithPreset;
|
|
@@ -7906,18 +8333,25 @@ exports.extractProfileOverrides = extractProfileOverrides;
|
|
|
7906
8333
|
exports.findFaceCenter = findFaceCenter;
|
|
7907
8334
|
exports.fuzzyNameMatch = fuzzyNameMatch;
|
|
7908
8335
|
exports.generateMappingCorrections = generateMappingCorrections;
|
|
8336
|
+
exports.getMeshNamesForAUProfile = getMeshNamesForAUProfile;
|
|
8337
|
+
exports.getMeshNamesForVisemeProfile = getMeshNamesForVisemeProfile;
|
|
7909
8338
|
exports.getModelForwardDirection = getModelForwardDirection;
|
|
7910
8339
|
exports.getPreset = getPreset;
|
|
7911
8340
|
exports.getPresetWithProfile = getPresetWithProfile;
|
|
8341
|
+
exports.getProfileVisemeSlots = getProfileVisemeSlots;
|
|
8342
|
+
exports.getVisemeJawAmounts = getVisemeJawAmounts;
|
|
8343
|
+
exports.getVisemeSlotIndex = getVisemeSlotIndex;
|
|
7912
8344
|
exports.hasLeftRightMorphs = hasLeftRightMorphs;
|
|
7913
8345
|
exports.isMixedAU = isMixedAU;
|
|
7914
8346
|
exports.isPresetCompatible = isPresetCompatible;
|
|
8347
|
+
exports.mapProviderVisemeToSlot = mapProviderVisemeToSlot;
|
|
7915
8348
|
exports.mergeCharacterRegionsByName = mergeRegionsByName;
|
|
7916
8349
|
exports.resolveBoneName = resolveBoneName;
|
|
7917
8350
|
exports.resolveBoneNames = resolveBoneNames;
|
|
7918
8351
|
exports.resolveFaceCenter = resolveFaceCenter;
|
|
7919
8352
|
exports.resolvePreset = resolvePreset;
|
|
7920
8353
|
exports.resolvePresetWithOverrides = resolvePresetWithOverrides;
|
|
8354
|
+
exports.resolveVisemeMeshCategory = resolveVisemeMeshCategory;
|
|
7921
8355
|
exports.suggestBestPreset = suggestBestPreset;
|
|
7922
8356
|
exports.validateMappingConfig = validateMappingConfig;
|
|
7923
8357
|
exports.validateMappings = validateMappings;
|