@lovelace_lol/loom3 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +115 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +115 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Loom3
|
|
2
2
|
|
|
3
|
-
The missing character controller for Three.js
|
|
3
|
+
The missing character controller for Three.js! Loom3 allows you to bring humanoid and animal characters to life. Loom3 is based on the Facial Action Coding System (FACS) as the basis of its mappings, providing a morph and bone mapping library for controlling high-definition 3D characters in Three.js.
|
|
4
4
|
|
|
5
5
|
Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https://en.wikipedia.org/wiki/Facial_Action_Coding_System) Action Units to the morph targets and bone transforms found in Character Creator 4 (CC4) characters. Instead of manually figuring out which blend shapes correspond to which facial movements, you can simply say `setAU(12, 0.8)` and the library handles the rest.
|
|
6
6
|
|
package/dist/index.cjs
CHANGED
|
@@ -150,6 +150,32 @@ var BakedAnimationController = class {
|
|
|
150
150
|
__publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
|
|
151
151
|
this.host = host;
|
|
152
152
|
}
|
|
153
|
+
getMeshNamesForAU(auId, config, explicitMeshNames) {
|
|
154
|
+
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
155
|
+
return explicitMeshNames;
|
|
156
|
+
}
|
|
157
|
+
if (typeof this.host.getMeshNamesForAU === "function") {
|
|
158
|
+
return this.host.getMeshNamesForAU(auId) || [];
|
|
159
|
+
}
|
|
160
|
+
const facePart = config.auInfo?.[String(auId)]?.facePart;
|
|
161
|
+
if (facePart) {
|
|
162
|
+
const category = config.auFacePartToMeshCategory?.[facePart];
|
|
163
|
+
if (category) return config.morphToMesh?.[category] || [];
|
|
164
|
+
}
|
|
165
|
+
return config.morphToMesh?.face || [];
|
|
166
|
+
}
|
|
167
|
+
getMeshNamesForViseme(config, explicitMeshNames) {
|
|
168
|
+
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
169
|
+
return explicitMeshNames;
|
|
170
|
+
}
|
|
171
|
+
if (typeof this.host.getMeshNamesForViseme === "function") {
|
|
172
|
+
return this.host.getMeshNamesForViseme() || [];
|
|
173
|
+
}
|
|
174
|
+
const category = config.visemeMeshCategory || (config.morphToMesh?.viseme ? "viseme" : "face");
|
|
175
|
+
const visemeMeshes = config.morphToMesh?.[category];
|
|
176
|
+
if (visemeMeshes && visemeMeshes.length > 0) return visemeMeshes;
|
|
177
|
+
return config.morphToMesh?.face || [];
|
|
178
|
+
}
|
|
153
179
|
update(dtSeconds) {
|
|
154
180
|
if (this.animationMixer) {
|
|
155
181
|
this.animationMixer.update(dtSeconds);
|
|
@@ -443,13 +469,15 @@ var BakedAnimationController = class {
|
|
|
443
469
|
if (isNumericAU(curveId)) {
|
|
444
470
|
const auId = Number(curveId);
|
|
445
471
|
if (isVisemeIndex(curveId)) {
|
|
472
|
+
const visemeMeshNames = this.getMeshNamesForViseme(config, meshNames);
|
|
446
473
|
const visemeKey = config.visemeKeys[auId];
|
|
447
474
|
if (typeof visemeKey === "number") {
|
|
448
|
-
this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale,
|
|
475
|
+
this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
|
|
449
476
|
} else if (visemeKey) {
|
|
450
|
-
this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale,
|
|
477
|
+
this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
|
|
451
478
|
}
|
|
452
479
|
} else {
|
|
480
|
+
const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
|
|
453
481
|
const morphsBySide = config.auToMorphs[auId];
|
|
454
482
|
const mixWeight = this.host.isMixedAU(auId) ? this.host.getAUMixWeight(auId) : 1;
|
|
455
483
|
const leftKeys = morphsBySide?.left ?? [];
|
|
@@ -460,26 +488,26 @@ var BakedAnimationController = class {
|
|
|
460
488
|
let effectiveScale = intensityScale * mixWeight;
|
|
461
489
|
if (curveBalance > 0) effectiveScale *= 1 - curveBalance;
|
|
462
490
|
if (typeof morphKey === "number") {
|
|
463
|
-
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
491
|
+
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
464
492
|
} else {
|
|
465
|
-
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
493
|
+
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
466
494
|
}
|
|
467
495
|
}
|
|
468
496
|
for (const morphKey of rightKeys) {
|
|
469
497
|
let effectiveScale = intensityScale * mixWeight;
|
|
470
498
|
if (curveBalance < 0) effectiveScale *= 1 + curveBalance;
|
|
471
499
|
if (typeof morphKey === "number") {
|
|
472
|
-
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
500
|
+
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
473
501
|
} else {
|
|
474
|
-
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
502
|
+
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
475
503
|
}
|
|
476
504
|
}
|
|
477
505
|
for (const morphKey of centerKeys) {
|
|
478
506
|
const effectiveScale = intensityScale * mixWeight;
|
|
479
507
|
if (typeof morphKey === "number") {
|
|
480
|
-
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
508
|
+
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
481
509
|
} else {
|
|
482
|
-
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
510
|
+
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
483
511
|
}
|
|
484
512
|
}
|
|
485
513
|
}
|
|
@@ -867,11 +895,9 @@ var BakedAnimationController = class {
|
|
|
867
895
|
}
|
|
868
896
|
addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
|
|
869
897
|
const config = this.host.getConfig();
|
|
870
|
-
const meshes = this.host.getMeshes();
|
|
871
898
|
const hasExplicitMeshes = !!(meshNames && meshNames.length > 0);
|
|
872
899
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
873
|
-
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) :
|
|
874
|
-
let added = false;
|
|
900
|
+
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
875
901
|
const addTrackForMesh = (mesh) => {
|
|
876
902
|
const dict = mesh.morphTargetDictionary;
|
|
877
903
|
if (!dict || dict[morphKey] === void 0) return;
|
|
@@ -885,25 +911,17 @@ var BakedAnimationController = class {
|
|
|
885
911
|
const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
|
|
886
912
|
const track = new THREE.NumberKeyframeTrack(trackName, times, values);
|
|
887
913
|
tracks.push(track);
|
|
888
|
-
added = true;
|
|
889
914
|
};
|
|
890
915
|
for (const mesh of targetMeshes) {
|
|
891
916
|
addTrackForMesh(mesh);
|
|
892
917
|
}
|
|
893
|
-
if (!added && !hasExplicitMeshes && targetMeshes !== meshes) {
|
|
894
|
-
for (const mesh of meshes) {
|
|
895
|
-
addTrackForMesh(mesh);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
918
|
}
|
|
899
919
|
addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
|
|
900
920
|
if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
|
|
901
921
|
const config = this.host.getConfig();
|
|
902
|
-
const meshes = this.host.getMeshes();
|
|
903
922
|
const hasExplicitMeshes = !!(meshNames && meshNames.length > 0);
|
|
904
923
|
const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
|
|
905
|
-
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) :
|
|
906
|
-
let added = false;
|
|
924
|
+
const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
|
|
907
925
|
const addTrackForMesh = (mesh) => {
|
|
908
926
|
const infl = mesh.morphTargetInfluences;
|
|
909
927
|
if (!infl || morphIndex < 0 || morphIndex >= infl.length) return;
|
|
@@ -916,16 +934,10 @@ var BakedAnimationController = class {
|
|
|
916
934
|
const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
|
|
917
935
|
const track = new THREE.NumberKeyframeTrack(trackName, times, values);
|
|
918
936
|
tracks.push(track);
|
|
919
|
-
added = true;
|
|
920
937
|
};
|
|
921
938
|
for (const mesh of targetMeshes) {
|
|
922
939
|
addTrackForMesh(mesh);
|
|
923
940
|
}
|
|
924
|
-
if (!added && !hasExplicitMeshes && targetMeshes !== meshes) {
|
|
925
|
-
for (const mesh of meshes) {
|
|
926
|
-
addTrackForMesh(mesh);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
941
|
}
|
|
930
942
|
ensureMixer() {
|
|
931
943
|
const model = this.host.getModel();
|
|
@@ -1787,6 +1799,12 @@ var MORPH_TO_MESH = {
|
|
|
1787
1799
|
tongue: ["CC_Base_Tongue", "CC_Base_Tongue_1"],
|
|
1788
1800
|
hair: ["Side_part_wavy", "Side_part_wavy_1", "Side_part_wavy_2"]
|
|
1789
1801
|
};
|
|
1802
|
+
var AU_FACEPART_TO_MESH_CATEGORY = {
|
|
1803
|
+
Eye: "eye",
|
|
1804
|
+
Eyes: "eye",
|
|
1805
|
+
Eyelids: "eye",
|
|
1806
|
+
Tongue: "tongue"
|
|
1807
|
+
};
|
|
1790
1808
|
var CC4_HAIR_PHYSICS = {
|
|
1791
1809
|
stiffness: 7.5,
|
|
1792
1810
|
damping: 0.18,
|
|
@@ -1832,7 +1850,9 @@ var CC4_PRESET = {
|
|
|
1832
1850
|
bonePrefix: CC4_BONE_PREFIX,
|
|
1833
1851
|
suffixPattern: CC4_SUFFIX_PATTERN,
|
|
1834
1852
|
morphToMesh: MORPH_TO_MESH,
|
|
1853
|
+
auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
|
|
1835
1854
|
visemeKeys: VISEME_KEYS,
|
|
1855
|
+
visemeMeshCategory: "viseme",
|
|
1836
1856
|
visemeJawAmounts: VISEME_JAW_AMOUNTS,
|
|
1837
1857
|
auMixDefaults: AU_MIX_DEFAULTS,
|
|
1838
1858
|
auInfo: AU_INFO,
|
|
@@ -2152,9 +2172,10 @@ var HairPhysicsController = class {
|
|
|
2152
2172
|
if (meshName) {
|
|
2153
2173
|
targetMesh = this.registeredHairObjects.get(meshName);
|
|
2154
2174
|
} else {
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2175
|
+
const hairMeshNames = this.getHairMeshNames();
|
|
2176
|
+
for (const name of hairMeshNames) {
|
|
2177
|
+
const mesh = this.registeredHairObjects.get(name) || this.host.getMeshByName(name);
|
|
2178
|
+
if (mesh) {
|
|
2158
2179
|
targetMesh = mesh;
|
|
2159
2180
|
break;
|
|
2160
2181
|
}
|
|
@@ -2248,6 +2269,15 @@ var HairPhysicsController = class {
|
|
|
2248
2269
|
}
|
|
2249
2270
|
getHairMeshNames() {
|
|
2250
2271
|
if (this.cachedHairMeshNames) return this.cachedHairMeshNames;
|
|
2272
|
+
if (typeof this.host.getSelectedHairMeshNames === "function") {
|
|
2273
|
+
const selectedHairMeshNames = this.host.getSelectedHairMeshNames() || [];
|
|
2274
|
+
const resolved = selectedHairMeshNames.filter((name) => {
|
|
2275
|
+
const mesh = this.registeredHairObjects.get(name) || this.host.getMeshByName(name);
|
|
2276
|
+
return !!mesh;
|
|
2277
|
+
});
|
|
2278
|
+
this.cachedHairMeshNames = Array.from(new Set(resolved));
|
|
2279
|
+
return this.cachedHairMeshNames;
|
|
2280
|
+
}
|
|
2251
2281
|
const names = [];
|
|
2252
2282
|
this.registeredHairObjects.forEach((mesh, name) => {
|
|
2253
2283
|
const info = CC4_MESHES[name];
|
|
@@ -2263,6 +2293,18 @@ var HairPhysicsController = class {
|
|
|
2263
2293
|
this.cachedHairMeshNames = names;
|
|
2264
2294
|
return names;
|
|
2265
2295
|
}
|
|
2296
|
+
refreshMeshSelection() {
|
|
2297
|
+
this.cachedHairMeshNames = null;
|
|
2298
|
+
this.idleClipDirty = true;
|
|
2299
|
+
this.gravityClipDirty = true;
|
|
2300
|
+
this.impulseClipDirty = true;
|
|
2301
|
+
this.warnMissingHairMorphTargets();
|
|
2302
|
+
if (this.hairPhysicsEnabled) {
|
|
2303
|
+
this.startIdleClip();
|
|
2304
|
+
this.startGravityClip();
|
|
2305
|
+
this.buildImpulseClips();
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2266
2308
|
supportsMixerClips() {
|
|
2267
2309
|
return typeof this.host.buildClip === "function";
|
|
2268
2310
|
}
|
|
@@ -2277,7 +2319,11 @@ var HairPhysicsController = class {
|
|
|
2277
2319
|
return;
|
|
2278
2320
|
}
|
|
2279
2321
|
const hairMeshNames = this.getHairMeshNames();
|
|
2280
|
-
if (hairMeshNames.length === 0)
|
|
2322
|
+
if (hairMeshNames.length === 0) {
|
|
2323
|
+
this.stopIdleClip();
|
|
2324
|
+
this.idleClipDirty = false;
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2281
2327
|
if (!this.idleClipDirty && this.idleClipHandle) return;
|
|
2282
2328
|
this.stopIdleClip();
|
|
2283
2329
|
const duration = Math.max(0.5, cfg.idleClipDuration);
|
|
@@ -2295,7 +2341,11 @@ var HairPhysicsController = class {
|
|
|
2295
2341
|
startGravityClip() {
|
|
2296
2342
|
if (!this.hairPhysicsEnabled || !this.supportsMixerClips()) return;
|
|
2297
2343
|
const hairMeshNames = this.getHairMeshNames();
|
|
2298
|
-
if (hairMeshNames.length === 0)
|
|
2344
|
+
if (hairMeshNames.length === 0) {
|
|
2345
|
+
this.stopGravityClip();
|
|
2346
|
+
this.gravityClipDirty = false;
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2299
2349
|
if (!this.gravityClipDirty && this.gravityClipHandle) return;
|
|
2300
2350
|
this.stopGravityClip();
|
|
2301
2351
|
const morphTargets = this.hairPhysicsConfig.morphTargets;
|
|
@@ -2348,7 +2398,11 @@ var HairPhysicsController = class {
|
|
|
2348
2398
|
buildImpulseClips() {
|
|
2349
2399
|
if (!this.hairPhysicsEnabled || !this.supportsMixerClips()) return;
|
|
2350
2400
|
const hairMeshNames = this.getHairMeshNames();
|
|
2351
|
-
if (hairMeshNames.length === 0)
|
|
2401
|
+
if (hairMeshNames.length === 0) {
|
|
2402
|
+
this.stopImpulseClips();
|
|
2403
|
+
this.impulseClipDirty = false;
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2352
2406
|
if (!this.impulseClipDirty && this.impulseClips.left && this.impulseClips.right && this.impulseClips.front) {
|
|
2353
2407
|
return;
|
|
2354
2408
|
}
|
|
@@ -2684,7 +2738,9 @@ function resolveProfile(base, override) {
|
|
|
2684
2738
|
auToBones: mergeRecord(base.auToBones, override.auToBones),
|
|
2685
2739
|
boneNodes: mergeRecord(base.boneNodes, override.boneNodes),
|
|
2686
2740
|
morphToMesh: mergeRecord(base.morphToMesh, override.morphToMesh),
|
|
2741
|
+
auFacePartToMeshCategory: base.auFacePartToMeshCategory || override.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, override.auFacePartToMeshCategory || {}) : void 0,
|
|
2687
2742
|
visemeKeys: override.visemeKeys ? [...override.visemeKeys] : [...base.visemeKeys],
|
|
2743
|
+
visemeMeshCategory: override.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
2688
2744
|
auMixDefaults: base.auMixDefaults || override.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, override.auMixDefaults || {}) : void 0,
|
|
2689
2745
|
auInfo: base.auInfo || override.auInfo ? mergeRecord(base.auInfo || {}, override.auInfo || {}) : void 0,
|
|
2690
2746
|
eyeMeshNodes: override.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
@@ -3522,6 +3578,7 @@ var _Loom3 = class _Loom3 {
|
|
|
3522
3578
|
this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
|
|
3523
3579
|
this.hairPhysics = new HairPhysicsController({
|
|
3524
3580
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
3581
|
+
getSelectedHairMeshNames: () => this.config.morphToMesh?.hair || [],
|
|
3525
3582
|
buildClip: (clipName, curves, options) => this.buildClip(clipName, curves, options),
|
|
3526
3583
|
cleanupSnippet: (name) => this.cleanupSnippet(name)
|
|
3527
3584
|
});
|
|
@@ -3529,6 +3586,8 @@ var _Loom3 = class _Loom3 {
|
|
|
3529
3586
|
getModel: () => this.model,
|
|
3530
3587
|
getMeshes: () => this.meshes,
|
|
3531
3588
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
3589
|
+
getMeshNamesForAU: (auId) => this.getMeshNamesForAU(auId),
|
|
3590
|
+
getMeshNamesForViseme: () => this.getMeshNamesForViseme(),
|
|
3532
3591
|
getBones: () => this.bones,
|
|
3533
3592
|
getConfig: () => this.config,
|
|
3534
3593
|
getCompositeRotations: () => this.compositeRotations,
|
|
@@ -3574,15 +3633,11 @@ var _Loom3 = class _Loom3 {
|
|
|
3574
3633
|
this.resolvedFaceMeshes = this.resolveFaceMeshes(this.meshes);
|
|
3575
3634
|
this.faceMesh = this.resolvedFaceMeshes.length > 0 ? this.meshByName.get(this.resolvedFaceMeshes[0]) || null : null;
|
|
3576
3635
|
if (!this.config.morphToMesh?.face || this.config.morphToMesh.face.length === 0) {
|
|
3577
|
-
const
|
|
3578
|
-
|
|
3579
|
-
const dict = m.morphTargetDictionary;
|
|
3580
|
-
return Array.isArray(infl) && infl.length > 0 || dict && Object.keys(dict).length > 0;
|
|
3581
|
-
}).map((m) => m.name).filter(Boolean);
|
|
3582
|
-
if (morphMeshNames.length > 0) {
|
|
3636
|
+
const faceMeshNames = this.resolvedFaceMeshes.filter((name) => this.meshByName.has(name));
|
|
3637
|
+
if (faceMeshNames.length > 0) {
|
|
3583
3638
|
this.config.morphToMesh = {
|
|
3584
3639
|
...this.config.morphToMesh,
|
|
3585
|
-
face: Array.from(new Set(
|
|
3640
|
+
face: Array.from(new Set(faceMeshNames))
|
|
3586
3641
|
};
|
|
3587
3642
|
}
|
|
3588
3643
|
}
|
|
@@ -3629,7 +3684,8 @@ var _Loom3 = class _Loom3 {
|
|
|
3629
3684
|
}
|
|
3630
3685
|
for (let i = 0; i < (this.config.visemeKeys || []).length; i += 1) {
|
|
3631
3686
|
const key = this.config.visemeKeys[i];
|
|
3632
|
-
const
|
|
3687
|
+
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
3688
|
+
const targets = typeof key === "number" ? this.resolveMorphTargetsByIndex(key, visemeMeshNames) : this.resolveMorphTargets(key, visemeMeshNames);
|
|
3633
3689
|
this.resolvedVisemeTargets[i] = targets;
|
|
3634
3690
|
}
|
|
3635
3691
|
}
|
|
@@ -4061,10 +4117,11 @@ var _Loom3 = class _Loom3 {
|
|
|
4061
4117
|
this.applyMorphTargets(targets, val);
|
|
4062
4118
|
} else {
|
|
4063
4119
|
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
4120
|
+
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
4064
4121
|
if (typeof morphKey === "number") {
|
|
4065
|
-
this.setMorphInfluence(morphKey, val);
|
|
4122
|
+
this.setMorphInfluence(morphKey, val, visemeMeshNames);
|
|
4066
4123
|
} else if (typeof morphKey === "string") {
|
|
4067
|
-
this.setMorph(morphKey, val);
|
|
4124
|
+
this.setMorph(morphKey, val, visemeMeshNames);
|
|
4068
4125
|
}
|
|
4069
4126
|
}
|
|
4070
4127
|
const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * val * jawScale;
|
|
@@ -4082,7 +4139,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4082
4139
|
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
4083
4140
|
const target = clamp012(to);
|
|
4084
4141
|
this.visemeValues[visemeIndex] = target;
|
|
4085
|
-
const
|
|
4142
|
+
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
4143
|
+
const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
|
|
4086
4144
|
const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * target * jawScale;
|
|
4087
4145
|
if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
|
|
4088
4146
|
return morphHandle;
|
|
@@ -4386,6 +4444,7 @@ var _Loom3 = class _Loom3 {
|
|
|
4386
4444
|
if (this.model) {
|
|
4387
4445
|
this.rebuildMorphTargetsCache();
|
|
4388
4446
|
}
|
|
4447
|
+
this.hairPhysics.refreshMeshSelection();
|
|
4389
4448
|
this.applyHairPhysicsProfileConfig();
|
|
4390
4449
|
}
|
|
4391
4450
|
getProfile() {
|
|
@@ -4393,20 +4452,24 @@ var _Loom3 = class _Loom3 {
|
|
|
4393
4452
|
}
|
|
4394
4453
|
/**
|
|
4395
4454
|
* Get the mesh names that should receive morph influences for a given AU.
|
|
4396
|
-
*
|
|
4455
|
+
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
4397
4456
|
*/
|
|
4398
4457
|
getMeshNamesForAU(auId) {
|
|
4399
4458
|
const m = this.config.morphToMesh;
|
|
4400
4459
|
const info = this.config.auInfo?.[String(auId)];
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
default:
|
|
4408
|
-
return m?.face || [];
|
|
4460
|
+
const facePart = info?.facePart;
|
|
4461
|
+
if (facePart) {
|
|
4462
|
+
const category = this.config.auFacePartToMeshCategory?.[facePart];
|
|
4463
|
+
if (category) {
|
|
4464
|
+
return m?.[category] || [];
|
|
4465
|
+
}
|
|
4409
4466
|
}
|
|
4467
|
+
return m?.face || [];
|
|
4468
|
+
}
|
|
4469
|
+
getMeshNamesForViseme() {
|
|
4470
|
+
const m = this.config.morphToMesh;
|
|
4471
|
+
const category = this.config.visemeMeshCategory || (m?.viseme ? "viseme" : "face");
|
|
4472
|
+
return m?.[category] || m?.face || [];
|
|
4410
4473
|
}
|
|
4411
4474
|
// ============================================================================
|
|
4412
4475
|
// HAIR PHYSICS
|