@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.js
CHANGED
|
@@ -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;
|
|
@@ -649,12 +866,7 @@ var BakedAnimationController = class {
|
|
|
649
866
|
if (typeof this.host.getMeshNamesForAU === "function") {
|
|
650
867
|
return this.host.getMeshNamesForAU(auId) || [];
|
|
651
868
|
}
|
|
652
|
-
|
|
653
|
-
if (facePart) {
|
|
654
|
-
const category = config.auFacePartToMeshCategory?.[facePart];
|
|
655
|
-
if (category) return config.morphToMesh?.[category] || [];
|
|
656
|
-
}
|
|
657
|
-
return config.morphToMesh?.face || [];
|
|
869
|
+
return getMeshNamesForAUProfile(config, auId);
|
|
658
870
|
}
|
|
659
871
|
getMeshNamesForViseme(config, explicitMeshNames) {
|
|
660
872
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
@@ -663,10 +875,7 @@ var BakedAnimationController = class {
|
|
|
663
875
|
if (typeof this.host.getMeshNamesForViseme === "function") {
|
|
664
876
|
return this.host.getMeshNamesForViseme() || [];
|
|
665
877
|
}
|
|
666
|
-
|
|
667
|
-
const visemeMeshes = config.morphToMesh?.[category];
|
|
668
|
-
if (visemeMeshes && visemeMeshes.length > 0) return visemeMeshes;
|
|
669
|
-
return config.morphToMesh?.face || [];
|
|
878
|
+
return getMeshNamesForVisemeProfile(config);
|
|
670
879
|
}
|
|
671
880
|
update(dtSeconds) {
|
|
672
881
|
if (this.animationMixer) {
|
|
@@ -1719,7 +1928,7 @@ var BakedAnimationController = class {
|
|
|
1719
1928
|
}
|
|
1720
1929
|
addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
|
|
1721
1930
|
const config = this.host.getConfig();
|
|
1722
|
-
const hasExplicitMeshes =
|
|
1931
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1723
1932
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1724
1933
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1725
1934
|
const addTrackForMesh = (mesh) => {
|
|
@@ -1743,7 +1952,7 @@ var BakedAnimationController = class {
|
|
|
1743
1952
|
addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
|
|
1744
1953
|
if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
|
|
1745
1954
|
const config = this.host.getConfig();
|
|
1746
|
-
const hasExplicitMeshes =
|
|
1955
|
+
const hasExplicitMeshes = meshNames !== void 0;
|
|
1747
1956
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
1748
1957
|
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
1749
1958
|
const addTrackForMesh = (mesh) => {
|
|
@@ -2377,6 +2586,159 @@ var VISEME_JAW_AMOUNTS = [
|
|
|
2377
2586
|
0.5
|
|
2378
2587
|
// 14: W_OO
|
|
2379
2588
|
];
|
|
2589
|
+
var CC4_VISEME_SYSTEM_ID = "cc4-arkit-15";
|
|
2590
|
+
var CC4_VISEME_SLOTS = [
|
|
2591
|
+
{
|
|
2592
|
+
id: "ae",
|
|
2593
|
+
label: "AE",
|
|
2594
|
+
order: 0,
|
|
2595
|
+
providerIds: { azure: [4], sapi: [4] },
|
|
2596
|
+
phonemes: ["AE", "EH", "EY", "UH"],
|
|
2597
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ae|eh|ey)([_ .-]|$)"],
|
|
2598
|
+
features: { jawOpen: 0.75, lipSpread: 0.35 },
|
|
2599
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[0]
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
id: "ah",
|
|
2603
|
+
label: "Ah",
|
|
2604
|
+
order: 1,
|
|
2605
|
+
providerIds: { azure: [1, 2, 9, 11, 12], sapi: [1, 2, 9, 11, 12] },
|
|
2606
|
+
phonemes: ["AA", "AE", "AH", "AX", "AW", "AY", "HH"],
|
|
2607
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ah|aa|aah|open)([_ .-]|$)"],
|
|
2608
|
+
features: { jawOpen: 0.8 },
|
|
2609
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[1]
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
id: "b-m-p",
|
|
2613
|
+
label: "B_M_P",
|
|
2614
|
+
order: 2,
|
|
2615
|
+
providerIds: { azure: [0, 21], sapi: [0, 21] },
|
|
2616
|
+
phonemes: ["B", "M", "P"],
|
|
2617
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(b[_ .-]?m[_ .-]?p|bmp|closed|sil|rest)([_ .-]|$)"],
|
|
2618
|
+
features: { jawOpen: 0, lipClosed: 1 },
|
|
2619
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[2]
|
|
2620
|
+
},
|
|
2621
|
+
{
|
|
2622
|
+
id: "ch-j",
|
|
2623
|
+
label: "Ch_J",
|
|
2624
|
+
order: 3,
|
|
2625
|
+
providerIds: { azure: [16], sapi: [16] },
|
|
2626
|
+
phonemes: ["CH", "JH", "SH", "ZH"],
|
|
2627
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ch|j|sh|zh)([_ .-]|$)"],
|
|
2628
|
+
features: { jawOpen: 0.3, fricative: 0.6 },
|
|
2629
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[3]
|
|
2630
|
+
},
|
|
2631
|
+
{
|
|
2632
|
+
id: "ee",
|
|
2633
|
+
label: "EE",
|
|
2634
|
+
order: 4,
|
|
2635
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2636
|
+
phonemes: ["IY", "IH", "IX", "Y"],
|
|
2637
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ee|iy|y)([_ .-]|$)"],
|
|
2638
|
+
features: { jawOpen: 0.2, lipSpread: 0.8 },
|
|
2639
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[4]
|
|
2640
|
+
},
|
|
2641
|
+
{
|
|
2642
|
+
id: "er",
|
|
2643
|
+
label: "Er",
|
|
2644
|
+
order: 5,
|
|
2645
|
+
providerIds: { azure: [5], sapi: [5] },
|
|
2646
|
+
phonemes: ["ER"],
|
|
2647
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(er)([_ .-]|$)"],
|
|
2648
|
+
features: { jawOpen: 0.35, lipRound: 0.35 },
|
|
2649
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[5]
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
id: "f-v",
|
|
2653
|
+
label: "F_V",
|
|
2654
|
+
order: 6,
|
|
2655
|
+
providerIds: { azure: [18], sapi: [18] },
|
|
2656
|
+
phonemes: ["F", "V"],
|
|
2657
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(f[_ .-]?v|fv)([_ .-]|$)"],
|
|
2658
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2659
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[6]
|
|
2660
|
+
},
|
|
2661
|
+
{
|
|
2662
|
+
id: "ih",
|
|
2663
|
+
label: "Ih",
|
|
2664
|
+
order: 7,
|
|
2665
|
+
providerIds: { azure: [6], sapi: [6] },
|
|
2666
|
+
phonemes: ["IH", "IX"],
|
|
2667
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(ih|ix)([_ .-]|$)"],
|
|
2668
|
+
features: { jawOpen: 0.2, lipSpread: 0.55 },
|
|
2669
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[7]
|
|
2670
|
+
},
|
|
2671
|
+
{
|
|
2672
|
+
id: "k-g-h-ng",
|
|
2673
|
+
label: "K_G_H_NG",
|
|
2674
|
+
order: 8,
|
|
2675
|
+
providerIds: { azure: [20], sapi: [20] },
|
|
2676
|
+
phonemes: ["K", "G", "NG"],
|
|
2677
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(k[_ .-]?g[_ .-]?h?[_ .-]?ng|kg|ng)([_ .-]|$)"],
|
|
2678
|
+
features: { jawOpen: 0.35 },
|
|
2679
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[8]
|
|
2680
|
+
},
|
|
2681
|
+
{
|
|
2682
|
+
id: "oh",
|
|
2683
|
+
label: "Oh",
|
|
2684
|
+
order: 9,
|
|
2685
|
+
providerIds: { azure: [3, 8, 10], sapi: [3, 8, 10] },
|
|
2686
|
+
phonemes: ["AO", "OW", "OY"],
|
|
2687
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(oh|ao|ow|oy)([_ .-]|$)"],
|
|
2688
|
+
features: { jawOpen: 0.6, lipRound: 0.8 },
|
|
2689
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[9]
|
|
2690
|
+
},
|
|
2691
|
+
{
|
|
2692
|
+
id: "r",
|
|
2693
|
+
label: "R",
|
|
2694
|
+
order: 10,
|
|
2695
|
+
providerIds: { azure: [13], sapi: [13] },
|
|
2696
|
+
phonemes: ["R"],
|
|
2697
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(r)([_ .-]|$)"],
|
|
2698
|
+
features: { jawOpen: 0.35, lipRound: 0.5 },
|
|
2699
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[10]
|
|
2700
|
+
},
|
|
2701
|
+
{
|
|
2702
|
+
id: "s-z",
|
|
2703
|
+
label: "S_Z",
|
|
2704
|
+
order: 11,
|
|
2705
|
+
providerIds: { azure: [15], sapi: [15] },
|
|
2706
|
+
phonemes: ["S", "Z"],
|
|
2707
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(s[_ .-]?z|sz)([_ .-]|$)"],
|
|
2708
|
+
features: { jawOpen: 0.1, fricative: 1 },
|
|
2709
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[11]
|
|
2710
|
+
},
|
|
2711
|
+
{
|
|
2712
|
+
id: "t-l-d-n",
|
|
2713
|
+
label: "T_L_D_N",
|
|
2714
|
+
order: 12,
|
|
2715
|
+
providerIds: { azure: [14, 19], sapi: [14, 19] },
|
|
2716
|
+
phonemes: ["T", "L", "D", "N"],
|
|
2717
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(t[_ .-]?l[_ .-]?d[_ .-]?n|tldn|l)([_ .-]|$)"],
|
|
2718
|
+
features: { jawOpen: 0.3, tongueTip: 1 },
|
|
2719
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[12]
|
|
2720
|
+
},
|
|
2721
|
+
{
|
|
2722
|
+
id: "th",
|
|
2723
|
+
label: "Th",
|
|
2724
|
+
order: 13,
|
|
2725
|
+
providerIds: { azure: [17], sapi: [17] },
|
|
2726
|
+
phonemes: ["TH", "DH"],
|
|
2727
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(th|dh)([_ .-]|$)"],
|
|
2728
|
+
features: { jawOpen: 0.15, tongueTip: 0.8, fricative: 0.8 },
|
|
2729
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[13]
|
|
2730
|
+
},
|
|
2731
|
+
{
|
|
2732
|
+
id: "w-oo",
|
|
2733
|
+
label: "W_OO",
|
|
2734
|
+
order: 14,
|
|
2735
|
+
providerIds: { azure: [7], sapi: [7] },
|
|
2736
|
+
phonemes: ["W", "UW"],
|
|
2737
|
+
matchers: ["(^|[_ .-])(v|viseme)?[_ .-]?(w[_ .-]?oo|woo|uw|oo)([_ .-]|$)"],
|
|
2738
|
+
features: { jawOpen: 0.5, lipRound: 1 },
|
|
2739
|
+
defaultJawAmount: VISEME_JAW_AMOUNTS[14]
|
|
2740
|
+
}
|
|
2741
|
+
];
|
|
2380
2742
|
var isMixedAU = (id) => {
|
|
2381
2743
|
const morphs = AU_TO_MORPHS[id];
|
|
2382
2744
|
const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
|
|
@@ -2688,6 +3050,23 @@ var AU_FACEPART_TO_MESH_CATEGORY = {
|
|
|
2688
3050
|
Eyelids: "eye",
|
|
2689
3051
|
Tongue: "tongue"
|
|
2690
3052
|
};
|
|
3053
|
+
var CC4_MAPPING_SECTIONS = [
|
|
3054
|
+
{ id: "Forehead", label: "Forehead", kind: "au", order: 0, meshCategory: "face", facePart: "Forehead" },
|
|
3055
|
+
{ id: "Eyelids", label: "Eyelids", kind: "au", order: 1, meshCategory: "eye", facePart: "Eyelids" },
|
|
3056
|
+
{ id: "Eyes", label: "Eyes", kind: "au", order: 2, meshCategory: "eye", facePart: "Eyes" },
|
|
3057
|
+
{ id: "Cheeks", label: "Cheeks", kind: "au", order: 3, meshCategory: "face", facePart: "Cheeks" },
|
|
3058
|
+
{ id: "Nose", label: "Nose", kind: "au", order: 4, meshCategory: "face", facePart: "Nose" },
|
|
3059
|
+
{ id: "Mouth", label: "Mouth", kind: "au", order: 5, meshCategory: "face", facePart: "Mouth" },
|
|
3060
|
+
{ id: "Chin", label: "Chin", kind: "au", order: 6, meshCategory: "face", facePart: "Chin" },
|
|
3061
|
+
{ id: "Jaw", label: "Jaw", kind: "au", order: 7, meshCategory: "face", facePart: "Jaw" },
|
|
3062
|
+
{ id: "Tongue", label: "Tongue", kind: "au", order: 8, meshCategory: "tongue", facePart: "Tongue" },
|
|
3063
|
+
{ id: "Head", label: "Head", kind: "au", order: 9, meshCategory: "face", facePart: "Head" },
|
|
3064
|
+
{ id: "Joint Controls", label: "Joint Controls", kind: "au", order: 10, meshCategory: "face", facePart: "Joint Controls" },
|
|
3065
|
+
{ id: "Eye", label: "Eye", kind: "au", order: 11, meshCategory: "eye", facePart: "Eye" },
|
|
3066
|
+
{ id: "Hair", label: "Hair", kind: "hair", order: 12, meshCategory: "hair" },
|
|
3067
|
+
{ id: "Visemes", label: "Visemes", kind: "viseme", order: 13, meshCategory: "viseme" },
|
|
3068
|
+
{ id: "Unmapped", label: "Unmapped", kind: "unmapped", order: 14, meshCategory: "face" }
|
|
3069
|
+
];
|
|
2691
3070
|
var CC4_HAIR_PHYSICS = {
|
|
2692
3071
|
stiffness: 7.5,
|
|
2693
3072
|
damping: 0.18,
|
|
@@ -2734,7 +3113,10 @@ var CC4_PRESET = {
|
|
|
2734
3113
|
suffixPattern: CC4_SUFFIX_PATTERN,
|
|
2735
3114
|
morphToMesh: MORPH_TO_MESH,
|
|
2736
3115
|
auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
|
|
3116
|
+
mappingSections: CC4_MAPPING_SECTIONS,
|
|
2737
3117
|
visemeKeys: VISEME_KEYS,
|
|
3118
|
+
visemeSystemId: CC4_VISEME_SYSTEM_ID,
|
|
3119
|
+
visemeSlots: CC4_VISEME_SLOTS,
|
|
2738
3120
|
visemeMeshCategory: "viseme",
|
|
2739
3121
|
visemeJawAmounts: VISEME_JAW_AMOUNTS,
|
|
2740
3122
|
auMixDefaults: AU_MIX_DEFAULTS,
|
|
@@ -3621,8 +4003,13 @@ function extendPresetWithProfile(base, extension) {
|
|
|
3621
4003
|
boneNodes: mergeRecord(base.boneNodes, extension.boneNodes),
|
|
3622
4004
|
morphToMesh: mergeRecord(base.morphToMesh, extension.morphToMesh),
|
|
3623
4005
|
auFacePartToMeshCategory: base.auFacePartToMeshCategory || extension.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, extension.auFacePartToMeshCategory || {}) : void 0,
|
|
4006
|
+
mappingSections: extension.mappingSections ? [...extension.mappingSections] : base.mappingSections ? [...base.mappingSections] : void 0,
|
|
3624
4007
|
visemeKeys: extension.visemeKeys ? [...extension.visemeKeys] : [...base.visemeKeys],
|
|
4008
|
+
visemeSystemId: extension.visemeSystemId ?? base.visemeSystemId,
|
|
4009
|
+
visemeSlots: extension.visemeSlots ? [...extension.visemeSlots] : base.visemeSlots ? [...base.visemeSlots] : void 0,
|
|
4010
|
+
visemeBindings: base.visemeBindings || extension.visemeBindings ? mergeRecord(base.visemeBindings || {}, extension.visemeBindings || {}) : void 0,
|
|
3625
4011
|
visemeMeshCategory: extension.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
4012
|
+
visemeJawAmounts: extension.visemeJawAmounts ? [...extension.visemeJawAmounts] : base.visemeJawAmounts ? [...base.visemeJawAmounts] : void 0,
|
|
3626
4013
|
auMixDefaults: base.auMixDefaults || extension.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, extension.auMixDefaults || {}) : void 0,
|
|
3627
4014
|
auInfo: base.auInfo || extension.auInfo ? mergeRecord(base.auInfo || {}, extension.auInfo || {}) : void 0,
|
|
3628
4015
|
eyeMeshNodes: extension.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
@@ -5188,6 +5575,21 @@ var _Loom3 = class _Loom3 {
|
|
|
5188
5575
|
const jawHandle = this.transitionBoneRotation("JAW", "pitch", jawAmount, durationMs);
|
|
5189
5576
|
return this.combineHandles([morphHandle, jawHandle]);
|
|
5190
5577
|
}
|
|
5578
|
+
setVisemeById(slotId, value, jawScale = 1) {
|
|
5579
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5580
|
+
if (index < 0) return;
|
|
5581
|
+
this.setViseme(index, value, jawScale);
|
|
5582
|
+
}
|
|
5583
|
+
transitionVisemeById(slotId, to, durationMs = 80, jawScale = 1) {
|
|
5584
|
+
const index = getVisemeSlotIndex(this.config, slotId);
|
|
5585
|
+
if (index < 0) {
|
|
5586
|
+
return { promise: Promise.resolve(), pause: () => {
|
|
5587
|
+
}, resume: () => {
|
|
5588
|
+
}, cancel: () => {
|
|
5589
|
+
} };
|
|
5590
|
+
}
|
|
5591
|
+
return this.transitionViseme(index, to, durationMs, jawScale);
|
|
5592
|
+
}
|
|
5191
5593
|
// ============================================================================
|
|
5192
5594
|
// MIX WEIGHT CONTROL
|
|
5193
5595
|
// ============================================================================
|
|
@@ -5533,21 +5935,10 @@ var _Loom3 = class _Loom3 {
|
|
|
5533
5935
|
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
5534
5936
|
*/
|
|
5535
5937
|
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 || [];
|
|
5938
|
+
return getMeshNamesForAUProfile(this.config, auId);
|
|
5546
5939
|
}
|
|
5547
5940
|
getMeshNamesForViseme() {
|
|
5548
|
-
|
|
5549
|
-
const category = this.config.visemeMeshCategory || (m?.viseme ? "viseme" : "face");
|
|
5550
|
-
return m?.[category] || m?.face || [];
|
|
5941
|
+
return getMeshNamesForVisemeProfile(this.config);
|
|
5551
5942
|
}
|
|
5552
5943
|
// ============================================================================
|
|
5553
5944
|
// HAIR PHYSICS
|
|
@@ -5815,7 +6206,7 @@ var _Loom3 = class _Loom3 {
|
|
|
5815
6206
|
);
|
|
5816
6207
|
}
|
|
5817
6208
|
getVisemeJawAmount(visemeIndex) {
|
|
5818
|
-
return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
6209
|
+
return getVisemeJawAmounts(this.config)?.[visemeIndex] ?? this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
5819
6210
|
}
|
|
5820
6211
|
collectResolvedExpressionMorphTargets() {
|
|
5821
6212
|
const targets = [];
|
|
@@ -6293,7 +6684,11 @@ var PROFILE_OVERRIDE_KEYS = [
|
|
|
6293
6684
|
"rightMorphSuffixes",
|
|
6294
6685
|
"morphToMesh",
|
|
6295
6686
|
"auFacePartToMeshCategory",
|
|
6687
|
+
"mappingSections",
|
|
6296
6688
|
"visemeKeys",
|
|
6689
|
+
"visemeSystemId",
|
|
6690
|
+
"visemeSlots",
|
|
6691
|
+
"visemeBindings",
|
|
6297
6692
|
"visemeMeshCategory",
|
|
6298
6693
|
"visemeJawAmounts",
|
|
6299
6694
|
"auMixDefaults",
|
|
@@ -7393,6 +7788,33 @@ function validateMappingConfig(config) {
|
|
|
7393
7788
|
}
|
|
7394
7789
|
visemeSeen.add(key);
|
|
7395
7790
|
}
|
|
7791
|
+
const morphCategories = new Set(Object.keys(config.morphToMesh || {}));
|
|
7792
|
+
if (config.visemeMeshCategory && !morphCategories.has(config.visemeMeshCategory)) {
|
|
7793
|
+
push(
|
|
7794
|
+
"error",
|
|
7795
|
+
"VISEME_MESH_CATEGORY_MISSING",
|
|
7796
|
+
`visemeMeshCategory "${config.visemeMeshCategory}" is not present in morphToMesh`,
|
|
7797
|
+
{ category: config.visemeMeshCategory }
|
|
7798
|
+
);
|
|
7799
|
+
}
|
|
7800
|
+
for (const [facePart, category] of Object.entries(config.auFacePartToMeshCategory || {})) {
|
|
7801
|
+
if (!morphCategories.has(category)) {
|
|
7802
|
+
push(
|
|
7803
|
+
"error",
|
|
7804
|
+
"AU_MESH_CATEGORY_MISSING",
|
|
7805
|
+
`AU facePart "${facePart}" routes to missing morphToMesh category "${category}"`,
|
|
7806
|
+
{ facePart, category }
|
|
7807
|
+
);
|
|
7808
|
+
}
|
|
7809
|
+
}
|
|
7810
|
+
if (config.visemeJawAmounts && config.visemeJawAmounts.length !== (config.visemeKeys || []).length) {
|
|
7811
|
+
push(
|
|
7812
|
+
"warning",
|
|
7813
|
+
"VISEME_JAW_AMOUNT_LENGTH_MISMATCH",
|
|
7814
|
+
"visemeJawAmounts length does not match visemeKeys length",
|
|
7815
|
+
{ visemeKeys: (config.visemeKeys || []).length, visemeJawAmounts: config.visemeJawAmounts.length }
|
|
7816
|
+
);
|
|
7817
|
+
}
|
|
7396
7818
|
if (config.auMixDefaults) {
|
|
7397
7819
|
for (const key of Object.keys(config.auMixDefaults)) {
|
|
7398
7820
|
const auId = Number(key);
|
|
@@ -7846,6 +8268,6 @@ async function analyzeModel(options) {
|
|
|
7846
8268
|
};
|
|
7847
8269
|
}
|
|
7848
8270
|
|
|
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 };
|
|
8271
|
+
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
8272
|
//# sourceMappingURL=index.js.map
|
|
7851
8273
|
//# sourceMappingURL=index.js.map
|