@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.cjs CHANGED
@@ -3937,7 +3937,8 @@ var _Loom3 = class _Loom3 {
3937
3937
  __publicField(this, "bones", {});
3938
3938
  __publicField(this, "mixWeights", {});
3939
3939
  // Viseme state
3940
- __publicField(this, "visemeValues", new Array(15).fill(0));
3940
+ __publicField(this, "visemeValues", []);
3941
+ __publicField(this, "visemeJawScales", []);
3941
3942
  __publicField(this, "bakedAnimations");
3942
3943
  __publicField(this, "hairPhysics");
3943
3944
  // Internal animation loop
@@ -3950,6 +3951,7 @@ var _Loom3 = class _Loom3 {
3950
3951
  const basePreset = config.presetType ? getPreset(config.presetType) : CC4_PRESET;
3951
3952
  this.config = extendPresetWithProfile(basePreset, config.profile);
3952
3953
  this.mixWeights = { ...this.config.auMixDefaults };
3954
+ this.syncVisemeRuntimeState();
3953
3955
  this.animation = animation || new AnimationThree();
3954
3956
  this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
3955
3957
  this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
@@ -4482,6 +4484,7 @@ var _Loom3 = class _Loom3 {
4482
4484
  if (visemeIndex < 0 || visemeIndex >= this.config.visemeKeys.length) return;
4483
4485
  const val = clamp012(value);
4484
4486
  this.visemeValues[visemeIndex] = val;
4487
+ this.visemeJawScales[visemeIndex] = jawScale;
4485
4488
  const targets = this.resolvedVisemeTargets[visemeIndex];
4486
4489
  if (targets && targets.length > 0) {
4487
4490
  this.applyMorphTargets(targets, val);
@@ -4494,7 +4497,7 @@ var _Loom3 = class _Loom3 {
4494
4497
  this.setMorph(morphKey, val, visemeMeshNames);
4495
4498
  }
4496
4499
  }
4497
- const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * val * jawScale;
4500
+ const jawAmount = this.getVisemeJawAmount(visemeIndex) * val * jawScale;
4498
4501
  if (Math.abs(jawScale) > 1e-6 && Math.abs(jawAmount) > 1e-6) {
4499
4502
  this.updateBoneRotation("JAW", "pitch", jawAmount);
4500
4503
  }
@@ -4509,9 +4512,10 @@ var _Loom3 = class _Loom3 {
4509
4512
  const morphKey = this.config.visemeKeys[visemeIndex];
4510
4513
  const target = clamp012(to);
4511
4514
  this.visemeValues[visemeIndex] = target;
4515
+ this.visemeJawScales[visemeIndex] = jawScale;
4512
4516
  const visemeMeshNames = this.getMeshNamesForViseme();
4513
4517
  const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
4514
- const jawAmount = _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] * target * jawScale;
4518
+ const jawAmount = this.getVisemeJawAmount(visemeIndex) * target * jawScale;
4515
4519
  if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
4516
4520
  return morphHandle;
4517
4521
  }
@@ -4565,6 +4569,9 @@ var _Loom3 = class _Loom3 {
4565
4569
  }
4566
4570
  resetToNeutral() {
4567
4571
  this.auValues = {};
4572
+ this.visemeValues = new Array(this.config.visemeKeys.length).fill(0);
4573
+ this.visemeJawScales = new Array(this.config.visemeKeys.length).fill(1);
4574
+ this.translations = {};
4568
4575
  this.initBoneRotations();
4569
4576
  this.clearTransitions();
4570
4577
  for (const m of this.meshes) {
@@ -4580,6 +4587,33 @@ var _Loom3 = class _Loom3 {
4580
4587
  entry.obj.quaternion.copy(entry.baseQuat);
4581
4588
  });
4582
4589
  }
4590
+ reinitializeRuntimeStateFromCurrentControls(staleMorphTargets = []) {
4591
+ this.clearTransitions();
4592
+ this.resetMorphTargetHandles(staleMorphTargets);
4593
+ this.translations = {};
4594
+ this.initBoneRotations();
4595
+ Object.values(this.bones).forEach((entry) => {
4596
+ if (!entry) return;
4597
+ entry.obj.position.copy(entry.basePos);
4598
+ entry.obj.quaternion.copy(entry.baseQuat);
4599
+ entry.obj.updateMatrixWorld(false);
4600
+ });
4601
+ for (const [auIdStr, value] of Object.entries(this.auValues)) {
4602
+ if (value <= 0) continue;
4603
+ const auId = Number(auIdStr);
4604
+ if (Number.isNaN(auId)) continue;
4605
+ this.setAU(auId, value, this.auBalances[auId]);
4606
+ }
4607
+ for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
4608
+ const value = this.visemeValues[visemeIndex] ?? 0;
4609
+ if (value <= 0) continue;
4610
+ this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
4611
+ }
4612
+ if (this.model) {
4613
+ this.flushPendingComposites();
4614
+ this.model.updateMatrixWorld(true);
4615
+ }
4616
+ }
4583
4617
  // ============================================================================
4584
4618
  // MESH CONTROL
4585
4619
  // ============================================================================
@@ -4810,12 +4844,20 @@ var _Loom3 = class _Loom3 {
4810
4844
  }
4811
4845
  setProfile(profile) {
4812
4846
  this.config = profile;
4847
+ this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
4848
+ this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
4813
4849
  this.mixWeights = { ...profile.auMixDefaults };
4850
+ this.syncVisemeRuntimeState();
4851
+ let staleMorphTargets = [];
4814
4852
  if (this.model) {
4853
+ staleMorphTargets = this.collectResolvedExpressionMorphTargets();
4854
+ this.bones = this.resolveBones(this.model);
4855
+ this.missingBoneWarnings.clear();
4815
4856
  this.rebuildMorphTargetsCache();
4816
4857
  }
4817
4858
  this.hairPhysics.refreshMeshSelection();
4818
4859
  this.applyHairPhysicsProfileConfig();
4860
+ this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
4819
4861
  }
4820
4862
  getProfile() {
4821
4863
  return this.config;
@@ -4953,6 +4995,39 @@ var _Loom3 = class _Loom3 {
4953
4995
  getMorphIndexCacheKey(index, meshNames) {
4954
4996
  return meshNames?.length ? `idx:${index}@${meshNames.join(",")}` : `idx:${index}`;
4955
4997
  }
4998
+ syncVisemeRuntimeState() {
4999
+ const visemeCount = this.config.visemeKeys.length;
5000
+ this.visemeValues = Array.from(
5001
+ { length: visemeCount },
5002
+ (_, index) => this.visemeValues[index] ?? 0
5003
+ );
5004
+ this.visemeJawScales = Array.from(
5005
+ { length: visemeCount },
5006
+ (_, index) => this.visemeJawScales[index] ?? 1
5007
+ );
5008
+ }
5009
+ getVisemeJawAmount(visemeIndex) {
5010
+ return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
5011
+ }
5012
+ collectResolvedExpressionMorphTargets() {
5013
+ const targets = [];
5014
+ for (const resolved of this.resolvedAUMorphTargets.values()) {
5015
+ targets.push(...resolved.left, ...resolved.right, ...resolved.center);
5016
+ }
5017
+ for (const resolved of this.resolvedVisemeTargets) {
5018
+ if (resolved?.length) {
5019
+ targets.push(...resolved);
5020
+ }
5021
+ }
5022
+ return targets;
5023
+ }
5024
+ resetMorphTargetHandles(targets) {
5025
+ for (const { infl, idx } of targets) {
5026
+ if (idx < infl.length) {
5027
+ infl[idx] = 0;
5028
+ }
5029
+ }
5030
+ }
4956
5031
  isMixedAU(id) {
4957
5032
  const morphs = this.config.auToMorphs[id];
4958
5033
  const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
@@ -5094,12 +5169,25 @@ var _Loom3 = class _Loom3 {
5094
5169
  }
5095
5170
  resolveBones(root) {
5096
5171
  const resolved = {};
5172
+ const previousBones = this.bones;
5097
5173
  const snapshot = (obj) => ({
5098
5174
  obj,
5099
5175
  basePos: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
5100
5176
  baseQuat: obj.quaternion.clone(),
5101
5177
  baseEuler: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, order: obj.rotation.order }
5102
5178
  });
5179
+ const snapshotPreservingBasePose = (obj) => {
5180
+ const existing = Object.values(previousBones).find((entry) => entry?.obj === obj);
5181
+ if (!existing) {
5182
+ return snapshot(obj);
5183
+ }
5184
+ return {
5185
+ obj,
5186
+ basePos: { ...existing.basePos },
5187
+ baseQuat: existing.baseQuat.clone(),
5188
+ baseEuler: { ...existing.baseEuler }
5189
+ };
5190
+ };
5103
5191
  const prefix = this.config.bonePrefix || "";
5104
5192
  const suffix = this.config.boneSuffix || "";
5105
5193
  const suffixRegex = this.config.suffixPattern ? new RegExp(this.config.suffixPattern) : null;
@@ -5130,19 +5218,19 @@ var _Loom3 = class _Loom3 {
5130
5218
  for (const [key, nodeName] of Object.entries(this.config.boneNodes)) {
5131
5219
  const node = findNode(nodeName);
5132
5220
  if (node) {
5133
- resolved[key] = snapshot(node);
5221
+ resolved[key] = snapshotPreservingBasePose(node);
5134
5222
  }
5135
5223
  }
5136
5224
  if (!resolved.EYE_L && this.config.eyeMeshNodes) {
5137
5225
  const node = findNode(this.config.eyeMeshNodes.LEFT);
5138
5226
  if (node) {
5139
- resolved.EYE_L = snapshot(node);
5227
+ resolved.EYE_L = snapshotPreservingBasePose(node);
5140
5228
  }
5141
5229
  }
5142
5230
  if (!resolved.EYE_R && this.config.eyeMeshNodes) {
5143
5231
  const node = findNode(this.config.eyeMeshNodes.RIGHT);
5144
5232
  if (node) {
5145
- resolved.EYE_R = snapshot(node);
5233
+ resolved.EYE_R = snapshotPreservingBasePose(node);
5146
5234
  }
5147
5235
  }
5148
5236
  return resolved;