@lovelace_lol/loom3 1.0.43 → 1.0.44

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
@@ -74,6 +74,9 @@ function bindingTargets(binding) {
74
74
  if (targets && targets.length > 0) return targets;
75
75
  return binding.morph !== void 0 && binding.morph !== "" ? [binding.morph] : [];
76
76
  }
77
+ function normalizeBindingWeight(weight) {
78
+ return Number.isFinite(weight) ? Math.max(0, weight ?? 1) : 1;
79
+ }
77
80
  function getProfileVisemeSlots(profile) {
78
81
  if (profile.visemeSlots && profile.visemeSlots.length > 0) {
79
82
  return [...profile.visemeSlots].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
@@ -99,6 +102,23 @@ function compileVisemeKeys(profile) {
99
102
  return target ?? profile.visemeKeys?.[index] ?? "";
100
103
  });
101
104
  }
105
+ function getVisemeBindingTargets(profile, visemeIndex) {
106
+ const slots = getProfileVisemeSlots(profile);
107
+ const slot = slots[visemeIndex];
108
+ const binding = slot ? profile.visemeBindings?.[slot.id] : void 0;
109
+ const boundTargets = binding?.targets?.filter((target) => target.morph !== void 0 && target.morph !== "").map((target) => ({
110
+ morph: target.morph,
111
+ weight: normalizeBindingWeight(target.weight)
112
+ }));
113
+ if (boundTargets && boundTargets.length > 0) {
114
+ return boundTargets;
115
+ }
116
+ if (binding?.morph !== void 0 && binding.morph !== "") {
117
+ return [{ morph: binding.morph, weight: 1 }];
118
+ }
119
+ const legacyTarget = profile.visemeKeys?.[visemeIndex];
120
+ return legacyTarget !== void 0 && legacyTarget !== "" ? [{ morph: legacyTarget, weight: 1 }] : [];
121
+ }
102
122
  function getVisemeJawAmounts(profile) {
103
123
  const slots = getProfileVisemeSlots(profile);
104
124
  if (slots.length === 0) return profile.visemeJawAmounts ? [...profile.visemeJawAmounts] : void 0;
@@ -1735,12 +1755,13 @@ var BakedAnimationController = class {
1735
1755
  const globalBalance = options?.balance ?? 0;
1736
1756
  const balanceMap = options?.balanceMap;
1737
1757
  const meshNames = options?.meshNames;
1758
+ const visemeSlotCount = getProfileVisemeSlots(config).length;
1738
1759
  let maxTime = 0;
1739
1760
  const isNumericAU = (id) => /^\d+$/.test(id);
1740
1761
  const isVisemeIndex = (id) => {
1741
1762
  if (options?.snippetCategory !== "visemeSnippet") return false;
1742
1763
  const num = Number(id);
1743
- return !Number.isNaN(num) && num >= 0 && num < config.visemeKeys.length;
1764
+ return !Number.isNaN(num) && num >= 0 && num < visemeSlotCount;
1744
1765
  };
1745
1766
  const sampleAt = (arr, t) => {
1746
1767
  if (!arr.length) return 0;
@@ -1778,11 +1799,13 @@ var BakedAnimationController = class {
1778
1799
  const auId = Number(curveId);
1779
1800
  if (isVisemeIndex(curveId)) {
1780
1801
  const visemeMeshNames = this.getMeshNamesForViseme(config, meshNames);
1781
- const visemeKey = config.visemeKeys[auId];
1782
- if (typeof visemeKey === "number") {
1783
- this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
1784
- } else if (visemeKey) {
1785
- this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
1802
+ for (const target of getVisemeBindingTargets(config, auId)) {
1803
+ const effectiveScale = intensityScale * target.weight;
1804
+ if (typeof target.morph === "number") {
1805
+ this.addMorphIndexTracks(tracks, target.morph, keyframes, effectiveScale, visemeMeshNames);
1806
+ } else if (target.morph) {
1807
+ this.addMorphTracks(tracks, target.morph, keyframes, effectiveScale, visemeMeshNames);
1808
+ }
1786
1809
  }
1787
1810
  } else {
1788
1811
  const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
@@ -1825,7 +1848,7 @@ var BakedAnimationController = class {
1825
1848
  }
1826
1849
  const autoVisemeJaw = options?.autoVisemeJaw !== false;
1827
1850
  const jawScale = options?.jawScale ?? 1;
1828
- const visemeJawAmounts = config.visemeJawAmounts;
1851
+ const visemeJawAmounts = getVisemeJawAmounts(config);
1829
1852
  if (autoVisemeJaw && jawScale > 0 && visemeJawAmounts && options?.snippetCategory === "visemeSnippet" && keyframeTimes.length > 0) {
1830
1853
  const bones = this.host.getBones();
1831
1854
  const jawEntry = bones["JAW"];
@@ -1833,7 +1856,7 @@ var BakedAnimationController = class {
1833
1856
  const jawValues = [];
1834
1857
  for (const t of keyframeTimes) {
1835
1858
  let jawAmount = 0;
1836
- for (let visemeIdx = 0; visemeIdx < config.visemeKeys.length; visemeIdx++) {
1859
+ for (let visemeIdx = 0; visemeIdx < visemeSlotCount; visemeIdx++) {
1837
1860
  const visemeCurve = curves[String(visemeIdx)];
1838
1861
  if (!visemeCurve) continue;
1839
1862
  const visemeValue = clampIntensity(sampleAt(visemeCurve, t) * intensityScale);
@@ -5361,10 +5384,15 @@ var _Loom3 = class _Loom3 {
5361
5384
  };
5362
5385
  this.resolvedAUMorphTargets.set(auId, resolved);
5363
5386
  }
5364
- for (let i = 0; i < (this.config.visemeKeys || []).length; i += 1) {
5365
- const key = this.config.visemeKeys[i];
5387
+ for (let i = 0; i < getProfileVisemeSlots(this.config).length; i += 1) {
5366
5388
  const visemeMeshNames = this.getMeshNamesForViseme();
5367
- const targets = typeof key === "number" ? this.resolveMorphTargetsByIndex(key, visemeMeshNames) : this.resolveMorphTargets(key, visemeMeshNames);
5389
+ const targets = [];
5390
+ for (const bindingTarget of getVisemeBindingTargets(this.config, i)) {
5391
+ const resolved = typeof bindingTarget.morph === "number" ? this.resolveMorphTargetsByIndex(bindingTarget.morph, visemeMeshNames) : this.resolveMorphTargets(bindingTarget.morph, visemeMeshNames);
5392
+ for (const target of resolved) {
5393
+ targets.push({ ...target, weight: bindingTarget.weight });
5394
+ }
5395
+ }
5368
5396
  this.resolvedVisemeTargets[i] = targets;
5369
5397
  }
5370
5398
  }
@@ -5832,46 +5860,33 @@ var _Loom3 = class _Loom3 {
5832
5860
  // VISEME CONTROL
5833
5861
  // ============================================================================
5834
5862
  setViseme(visemeIndex, value, jawScale = 1) {
5835
- if (visemeIndex < 0 || visemeIndex >= this.config.visemeKeys.length) return;
5863
+ if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) return;
5836
5864
  const val = clamp012(value);
5837
5865
  this.visemeValues[visemeIndex] = val;
5838
5866
  this.visemeJawScales[visemeIndex] = jawScale;
5839
- const targets = this.resolvedVisemeTargets[visemeIndex];
5840
- if (targets && targets.length > 0) {
5841
- this.applyMorphTargets(targets, val);
5842
- } else {
5843
- const morphKey = this.config.visemeKeys[visemeIndex];
5844
- const visemeMeshNames = this.getMeshNamesForViseme();
5845
- if (typeof morphKey === "number") {
5846
- this.setMorphInfluence(morphKey, val, visemeMeshNames);
5847
- } else if (typeof morphKey === "string") {
5848
- this.setMorph(morphKey, val, visemeMeshNames);
5849
- }
5850
- }
5851
- const jawAmount = this.getVisemeJawAmount(visemeIndex) * val * jawScale;
5852
- if (Math.abs(jawScale) > 1e-6 && Math.abs(jawAmount) > 1e-6) {
5853
- this.updateBoneRotation("JAW", "pitch", jawAmount);
5854
- }
5867
+ this.applyVisemeRuntimeState();
5855
5868
  }
5856
5869
  transitionViseme(visemeIndex, to, durationMs = 80, jawScale = 1) {
5857
- if (visemeIndex < 0 || visemeIndex >= this.config.visemeKeys.length) {
5870
+ if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) {
5858
5871
  return { promise: Promise.resolve(), pause: () => {
5859
5872
  }, resume: () => {
5860
5873
  }, cancel: () => {
5861
5874
  } };
5862
5875
  }
5863
- const morphKey = this.config.visemeKeys[visemeIndex];
5864
5876
  const target = clamp012(to);
5865
- this.visemeValues[visemeIndex] = target;
5877
+ const from = this.visemeValues[visemeIndex] ?? 0;
5866
5878
  this.visemeJawScales[visemeIndex] = jawScale;
5867
- const visemeMeshNames = this.getMeshNamesForViseme();
5868
- const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
5869
- const jawAmount = this.getVisemeJawAmount(visemeIndex) * target * jawScale;
5870
- if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
5871
- return morphHandle;
5872
- }
5873
- const jawHandle = this.transitionBoneRotation("JAW", "pitch", jawAmount, durationMs);
5874
- return this.combineHandles([morphHandle, jawHandle]);
5879
+ return this.animation.addTransition(
5880
+ `viseme_value_${visemeIndex}`,
5881
+ from,
5882
+ target,
5883
+ durationMs,
5884
+ (value) => {
5885
+ this.visemeValues[visemeIndex] = clamp012(value);
5886
+ this.visemeJawScales[visemeIndex] = jawScale;
5887
+ this.applyVisemeRuntimeState();
5888
+ }
5889
+ );
5875
5890
  }
5876
5891
  setVisemeById(slotId, value, jawScale = 1) {
5877
5892
  const index = getVisemeSlotIndex(this.config, slotId);
@@ -5935,8 +5950,9 @@ var _Loom3 = class _Loom3 {
5935
5950
  }
5936
5951
  resetToNeutral() {
5937
5952
  this.auValues = {};
5938
- this.visemeValues = new Array(this.config.visemeKeys.length).fill(0);
5939
- this.visemeJawScales = new Array(this.config.visemeKeys.length).fill(1);
5953
+ const visemeCount = getProfileVisemeSlots(this.config).length;
5954
+ this.visemeValues = new Array(visemeCount).fill(0);
5955
+ this.visemeJawScales = new Array(visemeCount).fill(1);
5940
5956
  this.translations = {};
5941
5957
  this.initBoneRotations();
5942
5958
  this.clearTransitions();
@@ -5970,11 +5986,7 @@ var _Loom3 = class _Loom3 {
5970
5986
  if (Number.isNaN(auId)) continue;
5971
5987
  this.setAU(auId, value, this.auBalances[auId]);
5972
5988
  }
5973
- for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
5974
- const value = this.visemeValues[visemeIndex] ?? 0;
5975
- if (value <= 0) continue;
5976
- this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
5977
- }
5989
+ this.applyVisemeRuntimeState();
5978
5990
  if (this.model) {
5979
5991
  this.flushPendingComposites();
5980
5992
  this.model.updateMatrixWorld(true);
@@ -6332,6 +6344,37 @@ var _Loom3 = class _Loom3 {
6332
6344
  target.infl[target.idx] = val;
6333
6345
  }
6334
6346
  }
6347
+ applyVisemeRuntimeState() {
6348
+ for (const targets of this.resolvedVisemeTargets) {
6349
+ for (const target of targets || []) {
6350
+ if (target.idx < target.infl.length) {
6351
+ target.infl[target.idx] = 0;
6352
+ }
6353
+ }
6354
+ }
6355
+ for (let index = 0; index < this.visemeValues.length; index += 1) {
6356
+ const value = clamp012(this.visemeValues[index] ?? 0);
6357
+ if (value <= 1e-6) continue;
6358
+ const targets = this.resolvedVisemeTargets[index] || [];
6359
+ for (const target of targets) {
6360
+ if (target.idx >= target.infl.length) continue;
6361
+ const weighted = clamp012(value * target.weight);
6362
+ target.infl[target.idx] = Math.max(target.infl[target.idx] ?? 0, weighted);
6363
+ }
6364
+ }
6365
+ this.updateBoneRotation("JAW", "pitch", this.getActiveVisemeJawAmount());
6366
+ }
6367
+ getActiveVisemeJawAmount() {
6368
+ let jawAmount = 0;
6369
+ for (let index = 0; index < this.visemeValues.length; index += 1) {
6370
+ const value = clamp012(this.visemeValues[index] ?? 0);
6371
+ if (value <= 1e-6) continue;
6372
+ const jawScale = this.visemeJawScales[index] ?? 1;
6373
+ if (Math.abs(jawScale) <= 1e-6) continue;
6374
+ jawAmount = Math.max(jawAmount, this.getVisemeJawAmount(index) * value * jawScale);
6375
+ }
6376
+ return jawAmount;
6377
+ }
6335
6378
  getMorphValue(key) {
6336
6379
  if (this.faceMesh) {
6337
6380
  const dict = this.faceMesh.morphTargetDictionary;
@@ -6517,7 +6560,7 @@ var _Loom3 = class _Loom3 {
6517
6560
  return meshNames?.length ? `idx:${index}@${meshNames.join(",")}` : `idx:${index}`;
6518
6561
  }
6519
6562
  syncVisemeRuntimeState() {
6520
- const visemeCount = this.config.visemeKeys.length;
6563
+ const visemeCount = getProfileVisemeSlots(this.config).length;
6521
6564
  this.visemeValues = Array.from(
6522
6565
  { length: visemeCount },
6523
6566
  (_, index) => this.visemeValues[index] ?? 0
@@ -8640,6 +8683,7 @@ exports.getModelForwardDirection = getModelForwardDirection;
8640
8683
  exports.getPreset = getPreset;
8641
8684
  exports.getPresetWithProfile = getPresetWithProfile;
8642
8685
  exports.getProfileVisemeSlots = getProfileVisemeSlots;
8686
+ exports.getVisemeBindingTargets = getVisemeBindingTargets;
8643
8687
  exports.getVisemeJawAmounts = getVisemeJawAmounts;
8644
8688
  exports.getVisemeSlotIndex = getVisemeSlotIndex;
8645
8689
  exports.hasLeftRightMorphs = hasLeftRightMorphs;