@lovelace_lol/loom3 1.0.34 → 1.0.35

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.d.cts CHANGED
@@ -1360,6 +1360,7 @@ declare class Loom3 implements LoomLarge {
1360
1360
  private bones;
1361
1361
  private mixWeights;
1362
1362
  private visemeValues;
1363
+ private visemeJawScales;
1363
1364
  private static readonly VISEME_JAW_AMOUNTS;
1364
1365
  private bakedAnimations;
1365
1366
  private hairPhysics;
@@ -1453,6 +1454,7 @@ declare class Loom3 implements LoomLarge {
1453
1454
  clearTransitions(): void;
1454
1455
  getActiveTransitionCount(): number;
1455
1456
  resetToNeutral(): void;
1457
+ private reinitializeRuntimeStateFromCurrentControls;
1456
1458
  getMeshList(): MeshInfo[];
1457
1459
  /** Get all morph targets grouped by mesh name */
1458
1460
  getMorphTargets(): Record<string, string[]>;
@@ -1556,6 +1558,10 @@ declare class Loom3 implements LoomLarge {
1556
1558
  private getMorphValueByIndex;
1557
1559
  private getMorphKeyCacheKey;
1558
1560
  private getMorphIndexCacheKey;
1561
+ private syncVisemeRuntimeState;
1562
+ private getVisemeJawAmount;
1563
+ private collectResolvedExpressionMorphTargets;
1564
+ private resetMorphTargetHandles;
1559
1565
  private isMixedAU;
1560
1566
  private getEffectiveBoneAUValue;
1561
1567
  private getCompositeAxisValueForNode;
package/dist/index.d.ts CHANGED
@@ -1360,6 +1360,7 @@ declare class Loom3 implements LoomLarge {
1360
1360
  private bones;
1361
1361
  private mixWeights;
1362
1362
  private visemeValues;
1363
+ private visemeJawScales;
1363
1364
  private static readonly VISEME_JAW_AMOUNTS;
1364
1365
  private bakedAnimations;
1365
1366
  private hairPhysics;
@@ -1453,6 +1454,7 @@ declare class Loom3 implements LoomLarge {
1453
1454
  clearTransitions(): void;
1454
1455
  getActiveTransitionCount(): number;
1455
1456
  resetToNeutral(): void;
1457
+ private reinitializeRuntimeStateFromCurrentControls;
1456
1458
  getMeshList(): MeshInfo[];
1457
1459
  /** Get all morph targets grouped by mesh name */
1458
1460
  getMorphTargets(): Record<string, string[]>;
@@ -1556,6 +1558,10 @@ declare class Loom3 implements LoomLarge {
1556
1558
  private getMorphValueByIndex;
1557
1559
  private getMorphKeyCacheKey;
1558
1560
  private getMorphIndexCacheKey;
1561
+ private syncVisemeRuntimeState;
1562
+ private getVisemeJawAmount;
1563
+ private collectResolvedExpressionMorphTargets;
1564
+ private resetMorphTargetHandles;
1559
1565
  private isMixedAU;
1560
1566
  private getEffectiveBoneAUValue;
1561
1567
  private getCompositeAxisValueForNode;
package/dist/index.js CHANGED
@@ -3916,7 +3916,8 @@ var _Loom3 = class _Loom3 {
3916
3916
  __publicField(this, "bones", {});
3917
3917
  __publicField(this, "mixWeights", {});
3918
3918
  // Viseme state
3919
- __publicField(this, "visemeValues", new Array(15).fill(0));
3919
+ __publicField(this, "visemeValues", []);
3920
+ __publicField(this, "visemeJawScales", []);
3920
3921
  __publicField(this, "bakedAnimations");
3921
3922
  __publicField(this, "hairPhysics");
3922
3923
  // Internal animation loop
@@ -3929,6 +3930,7 @@ var _Loom3 = class _Loom3 {
3929
3930
  const basePreset = config.presetType ? getPreset(config.presetType) : CC4_PRESET;
3930
3931
  this.config = extendPresetWithProfile(basePreset, config.profile);
3931
3932
  this.mixWeights = { ...this.config.auMixDefaults };
3933
+ this.syncVisemeRuntimeState();
3932
3934
  this.animation = animation || new AnimationThree();
3933
3935
  this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
3934
3936
  this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
@@ -4461,6 +4463,7 @@ var _Loom3 = class _Loom3 {
4461
4463
  if (visemeIndex < 0 || visemeIndex >= this.config.visemeKeys.length) return;
4462
4464
  const val = clamp012(value);
4463
4465
  this.visemeValues[visemeIndex] = val;
4466
+ this.visemeJawScales[visemeIndex] = jawScale;
4464
4467
  const targets = this.resolvedVisemeTargets[visemeIndex];
4465
4468
  if (targets && targets.length > 0) {
4466
4469
  this.applyMorphTargets(targets, val);
@@ -4473,7 +4476,7 @@ var _Loom3 = class _Loom3 {
4473
4476
  this.setMorph(morphKey, val, visemeMeshNames);
4474
4477
  }
4475
4478
  }
4476
- const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * val * jawScale;
4479
+ const jawAmount = this.getVisemeJawAmount(visemeIndex) * val * jawScale;
4477
4480
  if (Math.abs(jawScale) > 1e-6 && Math.abs(jawAmount) > 1e-6) {
4478
4481
  this.updateBoneRotation("JAW", "pitch", jawAmount);
4479
4482
  }
@@ -4488,9 +4491,10 @@ var _Loom3 = class _Loom3 {
4488
4491
  const morphKey = this.config.visemeKeys[visemeIndex];
4489
4492
  const target = clamp012(to);
4490
4493
  this.visemeValues[visemeIndex] = target;
4494
+ this.visemeJawScales[visemeIndex] = jawScale;
4491
4495
  const visemeMeshNames = this.getMeshNamesForViseme();
4492
4496
  const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
4493
- const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * target * jawScale;
4497
+ const jawAmount = this.getVisemeJawAmount(visemeIndex) * target * jawScale;
4494
4498
  if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
4495
4499
  return morphHandle;
4496
4500
  }
@@ -4544,6 +4548,9 @@ var _Loom3 = class _Loom3 {
4544
4548
  }
4545
4549
  resetToNeutral() {
4546
4550
  this.auValues = {};
4551
+ this.visemeValues = new Array(this.config.visemeKeys.length).fill(0);
4552
+ this.visemeJawScales = new Array(this.config.visemeKeys.length).fill(1);
4553
+ this.translations = {};
4547
4554
  this.initBoneRotations();
4548
4555
  this.clearTransitions();
4549
4556
  for (const m of this.meshes) {
@@ -4559,6 +4566,33 @@ var _Loom3 = class _Loom3 {
4559
4566
  entry.obj.quaternion.copy(entry.baseQuat);
4560
4567
  });
4561
4568
  }
4569
+ reinitializeRuntimeStateFromCurrentControls(staleMorphTargets = []) {
4570
+ this.clearTransitions();
4571
+ this.resetMorphTargetHandles(staleMorphTargets);
4572
+ this.translations = {};
4573
+ this.initBoneRotations();
4574
+ Object.values(this.bones).forEach((entry) => {
4575
+ if (!entry) return;
4576
+ entry.obj.position.copy(entry.basePos);
4577
+ entry.obj.quaternion.copy(entry.baseQuat);
4578
+ entry.obj.updateMatrixWorld(false);
4579
+ });
4580
+ for (const [auIdStr, value] of Object.entries(this.auValues)) {
4581
+ if (value <= 0) continue;
4582
+ const auId = Number(auIdStr);
4583
+ if (Number.isNaN(auId)) continue;
4584
+ this.setAU(auId, value, this.auBalances[auId]);
4585
+ }
4586
+ for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
4587
+ const value = this.visemeValues[visemeIndex] ?? 0;
4588
+ if (value <= 0) continue;
4589
+ this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
4590
+ }
4591
+ if (this.model) {
4592
+ this.flushPendingComposites();
4593
+ this.model.updateMatrixWorld(true);
4594
+ }
4595
+ }
4562
4596
  // ============================================================================
4563
4597
  // MESH CONTROL
4564
4598
  // ============================================================================
@@ -4789,12 +4823,20 @@ var _Loom3 = class _Loom3 {
4789
4823
  }
4790
4824
  setProfile(profile) {
4791
4825
  this.config = profile;
4826
+ this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
4827
+ this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
4792
4828
  this.mixWeights = { ...profile.auMixDefaults };
4829
+ this.syncVisemeRuntimeState();
4830
+ let staleMorphTargets = [];
4793
4831
  if (this.model) {
4832
+ staleMorphTargets = this.collectResolvedExpressionMorphTargets();
4833
+ this.bones = this.resolveBones(this.model);
4834
+ this.missingBoneWarnings.clear();
4794
4835
  this.rebuildMorphTargetsCache();
4795
4836
  }
4796
4837
  this.hairPhysics.refreshMeshSelection();
4797
4838
  this.applyHairPhysicsProfileConfig();
4839
+ this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
4798
4840
  }
4799
4841
  getProfile() {
4800
4842
  return this.config;
@@ -4932,6 +4974,39 @@ var _Loom3 = class _Loom3 {
4932
4974
  getMorphIndexCacheKey(index, meshNames) {
4933
4975
  return meshNames?.length ? `idx:${index}@${meshNames.join(",")}` : `idx:${index}`;
4934
4976
  }
4977
+ syncVisemeRuntimeState() {
4978
+ const visemeCount = this.config.visemeKeys.length;
4979
+ this.visemeValues = Array.from(
4980
+ { length: visemeCount },
4981
+ (_, index) => this.visemeValues[index] ?? 0
4982
+ );
4983
+ this.visemeJawScales = Array.from(
4984
+ { length: visemeCount },
4985
+ (_, index) => this.visemeJawScales[index] ?? 1
4986
+ );
4987
+ }
4988
+ getVisemeJawAmount(visemeIndex) {
4989
+ return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
4990
+ }
4991
+ collectResolvedExpressionMorphTargets() {
4992
+ const targets = [];
4993
+ for (const resolved of this.resolvedAUMorphTargets.values()) {
4994
+ targets.push(...resolved.left, ...resolved.right, ...resolved.center);
4995
+ }
4996
+ for (const resolved of this.resolvedVisemeTargets) {
4997
+ if (resolved?.length) {
4998
+ targets.push(...resolved);
4999
+ }
5000
+ }
5001
+ return targets;
5002
+ }
5003
+ resetMorphTargetHandles(targets) {
5004
+ for (const { infl, idx } of targets) {
5005
+ if (idx < infl.length) {
5006
+ infl[idx] = 0;
5007
+ }
5008
+ }
5009
+ }
4935
5010
  isMixedAU(id) {
4936
5011
  const morphs = this.config.auToMorphs[id];
4937
5012
  const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
@@ -5073,12 +5148,25 @@ var _Loom3 = class _Loom3 {
5073
5148
  }
5074
5149
  resolveBones(root) {
5075
5150
  const resolved = {};
5151
+ const previousBones = this.bones;
5076
5152
  const snapshot = (obj) => ({
5077
5153
  obj,
5078
5154
  basePos: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
5079
5155
  baseQuat: obj.quaternion.clone(),
5080
5156
  baseEuler: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, order: obj.rotation.order }
5081
5157
  });
5158
+ const snapshotPreservingBasePose = (obj) => {
5159
+ const existing = Object.values(previousBones).find((entry) => entry?.obj === obj);
5160
+ if (!existing) {
5161
+ return snapshot(obj);
5162
+ }
5163
+ return {
5164
+ obj,
5165
+ basePos: { ...existing.basePos },
5166
+ baseQuat: existing.baseQuat.clone(),
5167
+ baseEuler: { ...existing.baseEuler }
5168
+ };
5169
+ };
5082
5170
  const prefix = this.config.bonePrefix || "";
5083
5171
  const suffix = this.config.boneSuffix || "";
5084
5172
  const suffixRegex = this.config.suffixPattern ? new RegExp(this.config.suffixPattern) : null;
@@ -5109,19 +5197,19 @@ var _Loom3 = class _Loom3 {
5109
5197
  for (const [key, nodeName] of Object.entries(this.config.boneNodes)) {
5110
5198
  const node = findNode(nodeName);
5111
5199
  if (node) {
5112
- resolved[key] = snapshot(node);
5200
+ resolved[key] = snapshotPreservingBasePose(node);
5113
5201
  }
5114
5202
  }
5115
5203
  if (!resolved.EYE_L && this.config.eyeMeshNodes) {
5116
5204
  const node = findNode(this.config.eyeMeshNodes.LEFT);
5117
5205
  if (node) {
5118
- resolved.EYE_L = snapshot(node);
5206
+ resolved.EYE_L = snapshotPreservingBasePose(node);
5119
5207
  }
5120
5208
  }
5121
5209
  if (!resolved.EYE_R && this.config.eyeMeshNodes) {
5122
5210
  const node = findNode(this.config.eyeMeshNodes.RIGHT);
5123
5211
  if (node) {
5124
- resolved.EYE_R = snapshot(node);
5212
+ resolved.EYE_R = snapshotPreservingBasePose(node);
5125
5213
  }
5126
5214
  }
5127
5215
  return resolved;