@lovelace_lol/loom3 1.0.3 → 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/dist/index.cjs +92 -23
- 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 +92 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -658,8 +658,19 @@ interface Profile {
|
|
|
658
658
|
rightMorphSuffixes?: string[];
|
|
659
659
|
/** Morph category to mesh names (e.g., 'face' → ['CC_Base_Body_1']) */
|
|
660
660
|
morphToMesh: Record<string, string[]>;
|
|
661
|
+
/**
|
|
662
|
+
* Optional map from AU `facePart` labels (from `auInfo`) to `morphToMesh` categories.
|
|
663
|
+
* This makes AU mesh routing fully preset/profile configurable.
|
|
664
|
+
* Example: { Eye: 'eye', Eyes: 'eye', Eyelids: 'eye', Tongue: 'tongue' }.
|
|
665
|
+
*/
|
|
666
|
+
auFacePartToMeshCategory?: Record<string, string>;
|
|
661
667
|
/** Viseme targets in order (typically 15 phoneme positions) */
|
|
662
668
|
visemeKeys: MorphTargetRef[];
|
|
669
|
+
/**
|
|
670
|
+
* Optional `morphToMesh` category to use for viseme morph routing.
|
|
671
|
+
* Falls back to `morphToMesh.viseme` (if present), then `morphToMesh.face`.
|
|
672
|
+
*/
|
|
673
|
+
visemeMeshCategory?: string;
|
|
663
674
|
/** Optional: Jaw opening amounts per viseme index (0-1). Used for auto-generating jaw rotation in clips. */
|
|
664
675
|
visemeJawAmounts?: number[];
|
|
665
676
|
/** Optional: Default mix weights for bone/morph blending (0 = morph only, 1 = bone only) */
|
|
@@ -1516,9 +1527,10 @@ declare class Loom3 implements LoomLarge {
|
|
|
1516
1527
|
getProfile(): Profile;
|
|
1517
1528
|
/**
|
|
1518
1529
|
* Get the mesh names that should receive morph influences for a given AU.
|
|
1519
|
-
*
|
|
1530
|
+
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
1520
1531
|
*/
|
|
1521
1532
|
getMeshNamesForAU(auId: number): string[];
|
|
1533
|
+
getMeshNamesForViseme(): string[];
|
|
1522
1534
|
registerHairObjects(objects: Object3D[]): Array<{
|
|
1523
1535
|
name: string;
|
|
1524
1536
|
isMesh: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -658,8 +658,19 @@ interface Profile {
|
|
|
658
658
|
rightMorphSuffixes?: string[];
|
|
659
659
|
/** Morph category to mesh names (e.g., 'face' → ['CC_Base_Body_1']) */
|
|
660
660
|
morphToMesh: Record<string, string[]>;
|
|
661
|
+
/**
|
|
662
|
+
* Optional map from AU `facePart` labels (from `auInfo`) to `morphToMesh` categories.
|
|
663
|
+
* This makes AU mesh routing fully preset/profile configurable.
|
|
664
|
+
* Example: { Eye: 'eye', Eyes: 'eye', Eyelids: 'eye', Tongue: 'tongue' }.
|
|
665
|
+
*/
|
|
666
|
+
auFacePartToMeshCategory?: Record<string, string>;
|
|
661
667
|
/** Viseme targets in order (typically 15 phoneme positions) */
|
|
662
668
|
visemeKeys: MorphTargetRef[];
|
|
669
|
+
/**
|
|
670
|
+
* Optional `morphToMesh` category to use for viseme morph routing.
|
|
671
|
+
* Falls back to `morphToMesh.viseme` (if present), then `morphToMesh.face`.
|
|
672
|
+
*/
|
|
673
|
+
visemeMeshCategory?: string;
|
|
663
674
|
/** Optional: Jaw opening amounts per viseme index (0-1). Used for auto-generating jaw rotation in clips. */
|
|
664
675
|
visemeJawAmounts?: number[];
|
|
665
676
|
/** Optional: Default mix weights for bone/morph blending (0 = morph only, 1 = bone only) */
|
|
@@ -1516,9 +1527,10 @@ declare class Loom3 implements LoomLarge {
|
|
|
1516
1527
|
getProfile(): Profile;
|
|
1517
1528
|
/**
|
|
1518
1529
|
* Get the mesh names that should receive morph influences for a given AU.
|
|
1519
|
-
*
|
|
1530
|
+
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
1520
1531
|
*/
|
|
1521
1532
|
getMeshNamesForAU(auId: number): string[];
|
|
1533
|
+
getMeshNamesForViseme(): string[];
|
|
1522
1534
|
registerHairObjects(objects: Object3D[]): Array<{
|
|
1523
1535
|
name: string;
|
|
1524
1536
|
isMesh: boolean;
|
package/dist/index.js
CHANGED
|
@@ -137,8 +137,22 @@ var BakedAnimationController = class {
|
|
|
137
137
|
return this.host.getMeshNamesForAU(auId) || [];
|
|
138
138
|
}
|
|
139
139
|
const facePart = config.auInfo?.[String(auId)]?.facePart;
|
|
140
|
-
if (facePart
|
|
141
|
-
|
|
140
|
+
if (facePart) {
|
|
141
|
+
const category = config.auFacePartToMeshCategory?.[facePart];
|
|
142
|
+
if (category) return config.morphToMesh?.[category] || [];
|
|
143
|
+
}
|
|
144
|
+
return config.morphToMesh?.face || [];
|
|
145
|
+
}
|
|
146
|
+
getMeshNamesForViseme(config, explicitMeshNames) {
|
|
147
|
+
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
148
|
+
return explicitMeshNames;
|
|
149
|
+
}
|
|
150
|
+
if (typeof this.host.getMeshNamesForViseme === "function") {
|
|
151
|
+
return this.host.getMeshNamesForViseme() || [];
|
|
152
|
+
}
|
|
153
|
+
const category = config.visemeMeshCategory || (config.morphToMesh?.viseme ? "viseme" : "face");
|
|
154
|
+
const visemeMeshes = config.morphToMesh?.[category];
|
|
155
|
+
if (visemeMeshes && visemeMeshes.length > 0) return visemeMeshes;
|
|
142
156
|
return config.morphToMesh?.face || [];
|
|
143
157
|
}
|
|
144
158
|
update(dtSeconds) {
|
|
@@ -434,11 +448,12 @@ var BakedAnimationController = class {
|
|
|
434
448
|
if (isNumericAU(curveId)) {
|
|
435
449
|
const auId = Number(curveId);
|
|
436
450
|
if (isVisemeIndex(curveId)) {
|
|
451
|
+
const visemeMeshNames = this.getMeshNamesForViseme(config, meshNames);
|
|
437
452
|
const visemeKey = config.visemeKeys[auId];
|
|
438
453
|
if (typeof visemeKey === "number") {
|
|
439
|
-
this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale,
|
|
454
|
+
this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
|
|
440
455
|
} else if (visemeKey) {
|
|
441
|
-
this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale,
|
|
456
|
+
this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
|
|
442
457
|
}
|
|
443
458
|
} else {
|
|
444
459
|
const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
|
|
@@ -1763,6 +1778,12 @@ var MORPH_TO_MESH = {
|
|
|
1763
1778
|
tongue: ["CC_Base_Tongue", "CC_Base_Tongue_1"],
|
|
1764
1779
|
hair: ["Side_part_wavy", "Side_part_wavy_1", "Side_part_wavy_2"]
|
|
1765
1780
|
};
|
|
1781
|
+
var AU_FACEPART_TO_MESH_CATEGORY = {
|
|
1782
|
+
Eye: "eye",
|
|
1783
|
+
Eyes: "eye",
|
|
1784
|
+
Eyelids: "eye",
|
|
1785
|
+
Tongue: "tongue"
|
|
1786
|
+
};
|
|
1766
1787
|
var CC4_HAIR_PHYSICS = {
|
|
1767
1788
|
stiffness: 7.5,
|
|
1768
1789
|
damping: 0.18,
|
|
@@ -1808,7 +1829,9 @@ var CC4_PRESET = {
|
|
|
1808
1829
|
bonePrefix: CC4_BONE_PREFIX,
|
|
1809
1830
|
suffixPattern: CC4_SUFFIX_PATTERN,
|
|
1810
1831
|
morphToMesh: MORPH_TO_MESH,
|
|
1832
|
+
auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
|
|
1811
1833
|
visemeKeys: VISEME_KEYS,
|
|
1834
|
+
visemeMeshCategory: "viseme",
|
|
1812
1835
|
visemeJawAmounts: VISEME_JAW_AMOUNTS,
|
|
1813
1836
|
auMixDefaults: AU_MIX_DEFAULTS,
|
|
1814
1837
|
auInfo: AU_INFO,
|
|
@@ -2128,9 +2151,10 @@ var HairPhysicsController = class {
|
|
|
2128
2151
|
if (meshName) {
|
|
2129
2152
|
targetMesh = this.registeredHairObjects.get(meshName);
|
|
2130
2153
|
} else {
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2154
|
+
const hairMeshNames = this.getHairMeshNames();
|
|
2155
|
+
for (const name of hairMeshNames) {
|
|
2156
|
+
const mesh = this.registeredHairObjects.get(name) || this.host.getMeshByName(name);
|
|
2157
|
+
if (mesh) {
|
|
2134
2158
|
targetMesh = mesh;
|
|
2135
2159
|
break;
|
|
2136
2160
|
}
|
|
@@ -2224,6 +2248,15 @@ var HairPhysicsController = class {
|
|
|
2224
2248
|
}
|
|
2225
2249
|
getHairMeshNames() {
|
|
2226
2250
|
if (this.cachedHairMeshNames) return this.cachedHairMeshNames;
|
|
2251
|
+
if (typeof this.host.getSelectedHairMeshNames === "function") {
|
|
2252
|
+
const selectedHairMeshNames = this.host.getSelectedHairMeshNames() || [];
|
|
2253
|
+
const resolved = selectedHairMeshNames.filter((name) => {
|
|
2254
|
+
const mesh = this.registeredHairObjects.get(name) || this.host.getMeshByName(name);
|
|
2255
|
+
return !!mesh;
|
|
2256
|
+
});
|
|
2257
|
+
this.cachedHairMeshNames = Array.from(new Set(resolved));
|
|
2258
|
+
return this.cachedHairMeshNames;
|
|
2259
|
+
}
|
|
2227
2260
|
const names = [];
|
|
2228
2261
|
this.registeredHairObjects.forEach((mesh, name) => {
|
|
2229
2262
|
const info = CC4_MESHES[name];
|
|
@@ -2239,6 +2272,18 @@ var HairPhysicsController = class {
|
|
|
2239
2272
|
this.cachedHairMeshNames = names;
|
|
2240
2273
|
return names;
|
|
2241
2274
|
}
|
|
2275
|
+
refreshMeshSelection() {
|
|
2276
|
+
this.cachedHairMeshNames = null;
|
|
2277
|
+
this.idleClipDirty = true;
|
|
2278
|
+
this.gravityClipDirty = true;
|
|
2279
|
+
this.impulseClipDirty = true;
|
|
2280
|
+
this.warnMissingHairMorphTargets();
|
|
2281
|
+
if (this.hairPhysicsEnabled) {
|
|
2282
|
+
this.startIdleClip();
|
|
2283
|
+
this.startGravityClip();
|
|
2284
|
+
this.buildImpulseClips();
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2242
2287
|
supportsMixerClips() {
|
|
2243
2288
|
return typeof this.host.buildClip === "function";
|
|
2244
2289
|
}
|
|
@@ -2253,7 +2298,11 @@ var HairPhysicsController = class {
|
|
|
2253
2298
|
return;
|
|
2254
2299
|
}
|
|
2255
2300
|
const hairMeshNames = this.getHairMeshNames();
|
|
2256
|
-
if (hairMeshNames.length === 0)
|
|
2301
|
+
if (hairMeshNames.length === 0) {
|
|
2302
|
+
this.stopIdleClip();
|
|
2303
|
+
this.idleClipDirty = false;
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2257
2306
|
if (!this.idleClipDirty && this.idleClipHandle) return;
|
|
2258
2307
|
this.stopIdleClip();
|
|
2259
2308
|
const duration = Math.max(0.5, cfg.idleClipDuration);
|
|
@@ -2271,7 +2320,11 @@ var HairPhysicsController = class {
|
|
|
2271
2320
|
startGravityClip() {
|
|
2272
2321
|
if (!this.hairPhysicsEnabled || !this.supportsMixerClips()) return;
|
|
2273
2322
|
const hairMeshNames = this.getHairMeshNames();
|
|
2274
|
-
if (hairMeshNames.length === 0)
|
|
2323
|
+
if (hairMeshNames.length === 0) {
|
|
2324
|
+
this.stopGravityClip();
|
|
2325
|
+
this.gravityClipDirty = false;
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2275
2328
|
if (!this.gravityClipDirty && this.gravityClipHandle) return;
|
|
2276
2329
|
this.stopGravityClip();
|
|
2277
2330
|
const morphTargets = this.hairPhysicsConfig.morphTargets;
|
|
@@ -2324,7 +2377,11 @@ var HairPhysicsController = class {
|
|
|
2324
2377
|
buildImpulseClips() {
|
|
2325
2378
|
if (!this.hairPhysicsEnabled || !this.supportsMixerClips()) return;
|
|
2326
2379
|
const hairMeshNames = this.getHairMeshNames();
|
|
2327
|
-
if (hairMeshNames.length === 0)
|
|
2380
|
+
if (hairMeshNames.length === 0) {
|
|
2381
|
+
this.stopImpulseClips();
|
|
2382
|
+
this.impulseClipDirty = false;
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2328
2385
|
if (!this.impulseClipDirty && this.impulseClips.left && this.impulseClips.right && this.impulseClips.front) {
|
|
2329
2386
|
return;
|
|
2330
2387
|
}
|
|
@@ -2660,7 +2717,9 @@ function resolveProfile(base, override) {
|
|
|
2660
2717
|
auToBones: mergeRecord(base.auToBones, override.auToBones),
|
|
2661
2718
|
boneNodes: mergeRecord(base.boneNodes, override.boneNodes),
|
|
2662
2719
|
morphToMesh: mergeRecord(base.morphToMesh, override.morphToMesh),
|
|
2720
|
+
auFacePartToMeshCategory: base.auFacePartToMeshCategory || override.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, override.auFacePartToMeshCategory || {}) : void 0,
|
|
2663
2721
|
visemeKeys: override.visemeKeys ? [...override.visemeKeys] : [...base.visemeKeys],
|
|
2722
|
+
visemeMeshCategory: override.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
2664
2723
|
auMixDefaults: base.auMixDefaults || override.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, override.auMixDefaults || {}) : void 0,
|
|
2665
2724
|
auInfo: base.auInfo || override.auInfo ? mergeRecord(base.auInfo || {}, override.auInfo || {}) : void 0,
|
|
2666
2725
|
eyeMeshNodes: override.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
@@ -3498,6 +3557,7 @@ var _Loom3 = class _Loom3 {
|
|
|
3498
3557
|
this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
|
|
3499
3558
|
this.hairPhysics = new HairPhysicsController({
|
|
3500
3559
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
3560
|
+
getSelectedHairMeshNames: () => this.config.morphToMesh?.hair || [],
|
|
3501
3561
|
buildClip: (clipName, curves, options) => this.buildClip(clipName, curves, options),
|
|
3502
3562
|
cleanupSnippet: (name) => this.cleanupSnippet(name)
|
|
3503
3563
|
});
|
|
@@ -3506,6 +3566,7 @@ var _Loom3 = class _Loom3 {
|
|
|
3506
3566
|
getMeshes: () => this.meshes,
|
|
3507
3567
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
3508
3568
|
getMeshNamesForAU: (auId) => this.getMeshNamesForAU(auId),
|
|
3569
|
+
getMeshNamesForViseme: () => this.getMeshNamesForViseme(),
|
|
3509
3570
|
getBones: () => this.bones,
|
|
3510
3571
|
getConfig: () => this.config,
|
|
3511
3572
|
getCompositeRotations: () => this.compositeRotations,
|
|
@@ -3602,7 +3663,8 @@ var _Loom3 = class _Loom3 {
|
|
|
3602
3663
|
}
|
|
3603
3664
|
for (let i = 0; i < (this.config.visemeKeys || []).length; i += 1) {
|
|
3604
3665
|
const key = this.config.visemeKeys[i];
|
|
3605
|
-
const
|
|
3666
|
+
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
3667
|
+
const targets = typeof key === "number" ? this.resolveMorphTargetsByIndex(key, visemeMeshNames) : this.resolveMorphTargets(key, visemeMeshNames);
|
|
3606
3668
|
this.resolvedVisemeTargets[i] = targets;
|
|
3607
3669
|
}
|
|
3608
3670
|
}
|
|
@@ -4034,10 +4096,11 @@ var _Loom3 = class _Loom3 {
|
|
|
4034
4096
|
this.applyMorphTargets(targets, val);
|
|
4035
4097
|
} else {
|
|
4036
4098
|
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
4099
|
+
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
4037
4100
|
if (typeof morphKey === "number") {
|
|
4038
|
-
this.setMorphInfluence(morphKey, val);
|
|
4101
|
+
this.setMorphInfluence(morphKey, val, visemeMeshNames);
|
|
4039
4102
|
} else if (typeof morphKey === "string") {
|
|
4040
|
-
this.setMorph(morphKey, val);
|
|
4103
|
+
this.setMorph(morphKey, val, visemeMeshNames);
|
|
4041
4104
|
}
|
|
4042
4105
|
}
|
|
4043
4106
|
const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * val * jawScale;
|
|
@@ -4055,7 +4118,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4055
4118
|
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
4056
4119
|
const target = clamp012(to);
|
|
4057
4120
|
this.visemeValues[visemeIndex] = target;
|
|
4058
|
-
const
|
|
4121
|
+
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
4122
|
+
const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
|
|
4059
4123
|
const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * target * jawScale;
|
|
4060
4124
|
if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
|
|
4061
4125
|
return morphHandle;
|
|
@@ -4359,6 +4423,7 @@ var _Loom3 = class _Loom3 {
|
|
|
4359
4423
|
if (this.model) {
|
|
4360
4424
|
this.rebuildMorphTargetsCache();
|
|
4361
4425
|
}
|
|
4426
|
+
this.hairPhysics.refreshMeshSelection();
|
|
4362
4427
|
this.applyHairPhysicsProfileConfig();
|
|
4363
4428
|
}
|
|
4364
4429
|
getProfile() {
|
|
@@ -4366,20 +4431,24 @@ var _Loom3 = class _Loom3 {
|
|
|
4366
4431
|
}
|
|
4367
4432
|
/**
|
|
4368
4433
|
* Get the mesh names that should receive morph influences for a given AU.
|
|
4369
|
-
*
|
|
4434
|
+
* Routing is driven by `auFacePartToMeshCategory` in profile config.
|
|
4370
4435
|
*/
|
|
4371
4436
|
getMeshNamesForAU(auId) {
|
|
4372
4437
|
const m = this.config.morphToMesh;
|
|
4373
4438
|
const info = this.config.auInfo?.[String(auId)];
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
default:
|
|
4381
|
-
return m?.face || [];
|
|
4439
|
+
const facePart = info?.facePart;
|
|
4440
|
+
if (facePart) {
|
|
4441
|
+
const category = this.config.auFacePartToMeshCategory?.[facePart];
|
|
4442
|
+
if (category) {
|
|
4443
|
+
return m?.[category] || [];
|
|
4444
|
+
}
|
|
4382
4445
|
}
|
|
4446
|
+
return m?.face || [];
|
|
4447
|
+
}
|
|
4448
|
+
getMeshNamesForViseme() {
|
|
4449
|
+
const m = this.config.morphToMesh;
|
|
4450
|
+
const category = this.config.visemeMeshCategory || (m?.viseme ? "viseme" : "face");
|
|
4451
|
+
return m?.[category] || m?.face || [];
|
|
4383
4452
|
}
|
|
4384
4453
|
// ============================================================================
|
|
4385
4454
|
// HAIR PHYSICS
|