@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 CHANGED
@@ -158,8 +158,22 @@ var BakedAnimationController = class {
158
158
  return this.host.getMeshNamesForAU(auId) || [];
159
159
  }
160
160
  const facePart = config.auInfo?.[String(auId)]?.facePart;
161
- if (facePart === "Tongue") return config.morphToMesh?.tongue || [];
162
- if (facePart === "Eye") return config.morphToMesh?.eye || [];
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;
163
177
  return config.morphToMesh?.face || [];
164
178
  }
165
179
  update(dtSeconds) {
@@ -455,11 +469,12 @@ var BakedAnimationController = class {
455
469
  if (isNumericAU(curveId)) {
456
470
  const auId = Number(curveId);
457
471
  if (isVisemeIndex(curveId)) {
472
+ const visemeMeshNames = this.getMeshNamesForViseme(config, meshNames);
458
473
  const visemeKey = config.visemeKeys[auId];
459
474
  if (typeof visemeKey === "number") {
460
- this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale, meshNames);
475
+ this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
461
476
  } else if (visemeKey) {
462
- this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, meshNames);
477
+ this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
463
478
  }
464
479
  } else {
465
480
  const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
@@ -1784,6 +1799,12 @@ var MORPH_TO_MESH = {
1784
1799
  tongue: ["CC_Base_Tongue", "CC_Base_Tongue_1"],
1785
1800
  hair: ["Side_part_wavy", "Side_part_wavy_1", "Side_part_wavy_2"]
1786
1801
  };
1802
+ var AU_FACEPART_TO_MESH_CATEGORY = {
1803
+ Eye: "eye",
1804
+ Eyes: "eye",
1805
+ Eyelids: "eye",
1806
+ Tongue: "tongue"
1807
+ };
1787
1808
  var CC4_HAIR_PHYSICS = {
1788
1809
  stiffness: 7.5,
1789
1810
  damping: 0.18,
@@ -1829,7 +1850,9 @@ var CC4_PRESET = {
1829
1850
  bonePrefix: CC4_BONE_PREFIX,
1830
1851
  suffixPattern: CC4_SUFFIX_PATTERN,
1831
1852
  morphToMesh: MORPH_TO_MESH,
1853
+ auFacePartToMeshCategory: AU_FACEPART_TO_MESH_CATEGORY,
1832
1854
  visemeKeys: VISEME_KEYS,
1855
+ visemeMeshCategory: "viseme",
1833
1856
  visemeJawAmounts: VISEME_JAW_AMOUNTS,
1834
1857
  auMixDefaults: AU_MIX_DEFAULTS,
1835
1858
  auInfo: AU_INFO,
@@ -2149,9 +2172,10 @@ var HairPhysicsController = class {
2149
2172
  if (meshName) {
2150
2173
  targetMesh = this.registeredHairObjects.get(meshName);
2151
2174
  } else {
2152
- for (const [name, mesh] of this.registeredHairObjects) {
2153
- const info = CC4_MESHES[name];
2154
- if (info?.category === "hair") {
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) {
2155
2179
  targetMesh = mesh;
2156
2180
  break;
2157
2181
  }
@@ -2245,6 +2269,15 @@ var HairPhysicsController = class {
2245
2269
  }
2246
2270
  getHairMeshNames() {
2247
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
+ }
2248
2281
  const names = [];
2249
2282
  this.registeredHairObjects.forEach((mesh, name) => {
2250
2283
  const info = CC4_MESHES[name];
@@ -2260,6 +2293,18 @@ var HairPhysicsController = class {
2260
2293
  this.cachedHairMeshNames = names;
2261
2294
  return names;
2262
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
+ }
2263
2308
  supportsMixerClips() {
2264
2309
  return typeof this.host.buildClip === "function";
2265
2310
  }
@@ -2274,7 +2319,11 @@ var HairPhysicsController = class {
2274
2319
  return;
2275
2320
  }
2276
2321
  const hairMeshNames = this.getHairMeshNames();
2277
- if (hairMeshNames.length === 0) return;
2322
+ if (hairMeshNames.length === 0) {
2323
+ this.stopIdleClip();
2324
+ this.idleClipDirty = false;
2325
+ return;
2326
+ }
2278
2327
  if (!this.idleClipDirty && this.idleClipHandle) return;
2279
2328
  this.stopIdleClip();
2280
2329
  const duration = Math.max(0.5, cfg.idleClipDuration);
@@ -2292,7 +2341,11 @@ var HairPhysicsController = class {
2292
2341
  startGravityClip() {
2293
2342
  if (!this.hairPhysicsEnabled || !this.supportsMixerClips()) return;
2294
2343
  const hairMeshNames = this.getHairMeshNames();
2295
- if (hairMeshNames.length === 0) return;
2344
+ if (hairMeshNames.length === 0) {
2345
+ this.stopGravityClip();
2346
+ this.gravityClipDirty = false;
2347
+ return;
2348
+ }
2296
2349
  if (!this.gravityClipDirty && this.gravityClipHandle) return;
2297
2350
  this.stopGravityClip();
2298
2351
  const morphTargets = this.hairPhysicsConfig.morphTargets;
@@ -2345,7 +2398,11 @@ var HairPhysicsController = class {
2345
2398
  buildImpulseClips() {
2346
2399
  if (!this.hairPhysicsEnabled || !this.supportsMixerClips()) return;
2347
2400
  const hairMeshNames = this.getHairMeshNames();
2348
- if (hairMeshNames.length === 0) return;
2401
+ if (hairMeshNames.length === 0) {
2402
+ this.stopImpulseClips();
2403
+ this.impulseClipDirty = false;
2404
+ return;
2405
+ }
2349
2406
  if (!this.impulseClipDirty && this.impulseClips.left && this.impulseClips.right && this.impulseClips.front) {
2350
2407
  return;
2351
2408
  }
@@ -2681,7 +2738,9 @@ function resolveProfile(base, override) {
2681
2738
  auToBones: mergeRecord(base.auToBones, override.auToBones),
2682
2739
  boneNodes: mergeRecord(base.boneNodes, override.boneNodes),
2683
2740
  morphToMesh: mergeRecord(base.morphToMesh, override.morphToMesh),
2741
+ auFacePartToMeshCategory: base.auFacePartToMeshCategory || override.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, override.auFacePartToMeshCategory || {}) : void 0,
2684
2742
  visemeKeys: override.visemeKeys ? [...override.visemeKeys] : [...base.visemeKeys],
2743
+ visemeMeshCategory: override.visemeMeshCategory ?? base.visemeMeshCategory,
2685
2744
  auMixDefaults: base.auMixDefaults || override.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, override.auMixDefaults || {}) : void 0,
2686
2745
  auInfo: base.auInfo || override.auInfo ? mergeRecord(base.auInfo || {}, override.auInfo || {}) : void 0,
2687
2746
  eyeMeshNodes: override.eyeMeshNodes ?? base.eyeMeshNodes,
@@ -3519,6 +3578,7 @@ var _Loom3 = class _Loom3 {
3519
3578
  this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
3520
3579
  this.hairPhysics = new HairPhysicsController({
3521
3580
  getMeshByName: (name) => this.meshByName.get(name),
3581
+ getSelectedHairMeshNames: () => this.config.morphToMesh?.hair || [],
3522
3582
  buildClip: (clipName, curves, options) => this.buildClip(clipName, curves, options),
3523
3583
  cleanupSnippet: (name) => this.cleanupSnippet(name)
3524
3584
  });
@@ -3527,6 +3587,7 @@ var _Loom3 = class _Loom3 {
3527
3587
  getMeshes: () => this.meshes,
3528
3588
  getMeshByName: (name) => this.meshByName.get(name),
3529
3589
  getMeshNamesForAU: (auId) => this.getMeshNamesForAU(auId),
3590
+ getMeshNamesForViseme: () => this.getMeshNamesForViseme(),
3530
3591
  getBones: () => this.bones,
3531
3592
  getConfig: () => this.config,
3532
3593
  getCompositeRotations: () => this.compositeRotations,
@@ -3623,7 +3684,8 @@ var _Loom3 = class _Loom3 {
3623
3684
  }
3624
3685
  for (let i = 0; i < (this.config.visemeKeys || []).length; i += 1) {
3625
3686
  const key = this.config.visemeKeys[i];
3626
- const targets = typeof key === "number" ? this.resolveMorphTargetsByIndex(key) : this.resolveMorphTargets(key);
3687
+ const visemeMeshNames = this.getMeshNamesForViseme();
3688
+ const targets = typeof key === "number" ? this.resolveMorphTargetsByIndex(key, visemeMeshNames) : this.resolveMorphTargets(key, visemeMeshNames);
3627
3689
  this.resolvedVisemeTargets[i] = targets;
3628
3690
  }
3629
3691
  }
@@ -4055,10 +4117,11 @@ var _Loom3 = class _Loom3 {
4055
4117
  this.applyMorphTargets(targets, val);
4056
4118
  } else {
4057
4119
  const morphKey = this.config.visemeKeys[visemeIndex];
4120
+ const visemeMeshNames = this.getMeshNamesForViseme();
4058
4121
  if (typeof morphKey === "number") {
4059
- this.setMorphInfluence(morphKey, val);
4122
+ this.setMorphInfluence(morphKey, val, visemeMeshNames);
4060
4123
  } else if (typeof morphKey === "string") {
4061
- this.setMorph(morphKey, val);
4124
+ this.setMorph(morphKey, val, visemeMeshNames);
4062
4125
  }
4063
4126
  }
4064
4127
  const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * val * jawScale;
@@ -4076,7 +4139,8 @@ var _Loom3 = class _Loom3 {
4076
4139
  const morphKey = this.config.visemeKeys[visemeIndex];
4077
4140
  const target = clamp012(to);
4078
4141
  this.visemeValues[visemeIndex] = target;
4079
- const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs) : this.transitionMorph(morphKey, target, durationMs);
4142
+ const visemeMeshNames = this.getMeshNamesForViseme();
4143
+ const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
4080
4144
  const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * target * jawScale;
4081
4145
  if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
4082
4146
  return morphHandle;
@@ -4380,6 +4444,7 @@ var _Loom3 = class _Loom3 {
4380
4444
  if (this.model) {
4381
4445
  this.rebuildMorphTargetsCache();
4382
4446
  }
4447
+ this.hairPhysics.refreshMeshSelection();
4383
4448
  this.applyHairPhysicsProfileConfig();
4384
4449
  }
4385
4450
  getProfile() {
@@ -4387,20 +4452,24 @@ var _Loom3 = class _Loom3 {
4387
4452
  }
4388
4453
  /**
4389
4454
  * Get the mesh names that should receive morph influences for a given AU.
4390
- * Routes by facePart: Tongue tongue, Eye → eye, everything else → face.
4455
+ * Routing is driven by `auFacePartToMeshCategory` in profile config.
4391
4456
  */
4392
4457
  getMeshNamesForAU(auId) {
4393
4458
  const m = this.config.morphToMesh;
4394
4459
  const info = this.config.auInfo?.[String(auId)];
4395
- if (!info?.facePart) return m?.face || [];
4396
- switch (info.facePart) {
4397
- case "Tongue":
4398
- return m?.tongue || [];
4399
- case "Eye":
4400
- return m?.eye || [];
4401
- default:
4402
- 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
+ }
4403
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 || [];
4404
4473
  }
4405
4474
  // ============================================================================
4406
4475
  // HAIR PHYSICS