@lovelace_lol/loom3 1.0.1 → 1.0.3

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
@@ -519,6 +519,12 @@ interface ClipOptions {
519
519
  mixerWeight?: number;
520
520
  /** Left/right balance for bilateral AUs (-1 to 1, default: 0) */
521
521
  balance?: number;
522
+ /**
523
+ * Per-curve left/right balance overrides keyed by curve id (typically AU ids as strings).
524
+ * Example: { "43": 1, "12": 0.7 }.
525
+ * Falls back to `balance` when a curve id is not present.
526
+ */
527
+ balanceMap?: Record<string, number>;
522
528
  /** Jaw scale for viseme playback (default: 1.0) */
523
529
  jawScale?: number;
524
530
  /** Intensity scale multiplier (default: 1.0) */
package/dist/index.d.ts CHANGED
@@ -519,6 +519,12 @@ interface ClipOptions {
519
519
  mixerWeight?: number;
520
520
  /** Left/right balance for bilateral AUs (-1 to 1, default: 0) */
521
521
  balance?: number;
522
+ /**
523
+ * Per-curve left/right balance overrides keyed by curve id (typically AU ids as strings).
524
+ * Example: { "43": 1, "12": 0.7 }.
525
+ * Falls back to `balance` when a curve id is not present.
526
+ */
527
+ balanceMap?: Record<string, number>;
522
528
  /** Jaw scale for viseme playback (default: 1.0) */
523
529
  jawScale?: number;
524
530
  /** Intensity scale multiplier (default: 1.0) */
package/dist/index.js CHANGED
@@ -4,6 +4,20 @@ import { Vector3, Clock, Box3, Quaternion, LoopOnce, LoopPingPong, LoopRepeat, Q
4
4
  var __defProp = Object.defineProperty;
5
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
6
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
+
8
+ // src/engines/three/balanceUtils.ts
9
+ function clampBalance(value) {
10
+ if (!Number.isFinite(value)) return 0;
11
+ return Math.max(-1, Math.min(1, value));
12
+ }
13
+ function resolveCurveBalance(curveId, globalBalance, balanceMap) {
14
+ if (balanceMap && Object.prototype.hasOwnProperty.call(balanceMap, curveId)) {
15
+ return clampBalance(Number(balanceMap[curveId]));
16
+ }
17
+ return clampBalance(globalBalance);
18
+ }
19
+
20
+ // src/engines/three/AnimationThree.ts
7
21
  var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
8
22
  var AnimationThree = class {
9
23
  constructor() {
@@ -115,6 +129,18 @@ var BakedAnimationController = class {
115
129
  __publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
116
130
  this.host = host;
117
131
  }
132
+ getMeshNamesForAU(auId, config, explicitMeshNames) {
133
+ if (explicitMeshNames && explicitMeshNames.length > 0) {
134
+ return explicitMeshNames;
135
+ }
136
+ if (typeof this.host.getMeshNamesForAU === "function") {
137
+ return this.host.getMeshNamesForAU(auId) || [];
138
+ }
139
+ const facePart = config.auInfo?.[String(auId)]?.facePart;
140
+ if (facePart === "Tongue") return config.morphToMesh?.tongue || [];
141
+ if (facePart === "Eye") return config.morphToMesh?.eye || [];
142
+ return config.morphToMesh?.face || [];
143
+ }
118
144
  update(dtSeconds) {
119
145
  if (this.animationMixer) {
120
146
  this.animationMixer.update(dtSeconds);
@@ -363,7 +389,8 @@ var BakedAnimationController = class {
363
389
  }
364
390
  const tracks = [];
365
391
  const intensityScale = options?.intensityScale ?? 1;
366
- const balance = options?.balance ?? 0;
392
+ const globalBalance = options?.balance ?? 0;
393
+ const balanceMap = options?.balanceMap;
367
394
  const meshNames = options?.meshNames;
368
395
  let maxTime = 0;
369
396
  const isNumericAU = (id) => /^\d+$/.test(id);
@@ -414,35 +441,37 @@ var BakedAnimationController = class {
414
441
  this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, meshNames);
415
442
  }
416
443
  } else {
444
+ const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
417
445
  const morphsBySide = config.auToMorphs[auId];
418
446
  const mixWeight = this.host.isMixedAU(auId) ? this.host.getAUMixWeight(auId) : 1;
419
447
  const leftKeys = morphsBySide?.left ?? [];
420
448
  const rightKeys = morphsBySide?.right ?? [];
421
449
  const centerKeys = morphsBySide?.center ?? [];
450
+ const curveBalance = resolveCurveBalance(curveId, globalBalance, balanceMap);
422
451
  for (const morphKey of leftKeys) {
423
452
  let effectiveScale = intensityScale * mixWeight;
424
- if (balance > 0) effectiveScale *= 1 - balance;
453
+ if (curveBalance > 0) effectiveScale *= 1 - curveBalance;
425
454
  if (typeof morphKey === "number") {
426
- this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
455
+ this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
427
456
  } else {
428
- this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
457
+ this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
429
458
  }
430
459
  }
431
460
  for (const morphKey of rightKeys) {
432
461
  let effectiveScale = intensityScale * mixWeight;
433
- if (balance < 0) effectiveScale *= 1 + balance;
462
+ if (curveBalance < 0) effectiveScale *= 1 + curveBalance;
434
463
  if (typeof morphKey === "number") {
435
- this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
464
+ this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
436
465
  } else {
437
- this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
466
+ this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
438
467
  }
439
468
  }
440
469
  for (const morphKey of centerKeys) {
441
470
  const effectiveScale = intensityScale * mixWeight;
442
471
  if (typeof morphKey === "number") {
443
- this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
472
+ this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
444
473
  } else {
445
- this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
474
+ this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
446
475
  }
447
476
  }
448
477
  }
@@ -830,11 +859,9 @@ var BakedAnimationController = class {
830
859
  }
831
860
  addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
832
861
  const config = this.host.getConfig();
833
- const meshes = this.host.getMeshes();
834
862
  const hasExplicitMeshes = !!(meshNames && meshNames.length > 0);
835
863
  const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
836
- const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : meshes;
837
- let added = false;
864
+ const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
838
865
  const addTrackForMesh = (mesh) => {
839
866
  const dict = mesh.morphTargetDictionary;
840
867
  if (!dict || dict[morphKey] === void 0) return;
@@ -848,25 +875,17 @@ var BakedAnimationController = class {
848
875
  const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
849
876
  const track = new NumberKeyframeTrack(trackName, times, values);
850
877
  tracks.push(track);
851
- added = true;
852
878
  };
853
879
  for (const mesh of targetMeshes) {
854
880
  addTrackForMesh(mesh);
855
881
  }
856
- if (!added && !hasExplicitMeshes && targetMeshes !== meshes) {
857
- for (const mesh of meshes) {
858
- addTrackForMesh(mesh);
859
- }
860
- }
861
882
  }
862
883
  addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
863
884
  if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
864
885
  const config = this.host.getConfig();
865
- const meshes = this.host.getMeshes();
866
886
  const hasExplicitMeshes = !!(meshNames && meshNames.length > 0);
867
887
  const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
868
- const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : meshes;
869
- let added = false;
888
+ const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
870
889
  const addTrackForMesh = (mesh) => {
871
890
  const infl = mesh.morphTargetInfluences;
872
891
  if (!infl || morphIndex < 0 || morphIndex >= infl.length) return;
@@ -879,16 +898,10 @@ var BakedAnimationController = class {
879
898
  const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
880
899
  const track = new NumberKeyframeTrack(trackName, times, values);
881
900
  tracks.push(track);
882
- added = true;
883
901
  };
884
902
  for (const mesh of targetMeshes) {
885
903
  addTrackForMesh(mesh);
886
904
  }
887
- if (!added && !hasExplicitMeshes && targetMeshes !== meshes) {
888
- for (const mesh of meshes) {
889
- addTrackForMesh(mesh);
890
- }
891
- }
892
905
  }
893
906
  ensureMixer() {
894
907
  const model = this.host.getModel();
@@ -3492,6 +3505,7 @@ var _Loom3 = class _Loom3 {
3492
3505
  getModel: () => this.model,
3493
3506
  getMeshes: () => this.meshes,
3494
3507
  getMeshByName: (name) => this.meshByName.get(name),
3508
+ getMeshNamesForAU: (auId) => this.getMeshNamesForAU(auId),
3495
3509
  getBones: () => this.bones,
3496
3510
  getConfig: () => this.config,
3497
3511
  getCompositeRotations: () => this.compositeRotations,
@@ -3537,15 +3551,11 @@ var _Loom3 = class _Loom3 {
3537
3551
  this.resolvedFaceMeshes = this.resolveFaceMeshes(this.meshes);
3538
3552
  this.faceMesh = this.resolvedFaceMeshes.length > 0 ? this.meshByName.get(this.resolvedFaceMeshes[0]) || null : null;
3539
3553
  if (!this.config.morphToMesh?.face || this.config.morphToMesh.face.length === 0) {
3540
- const morphMeshNames = this.meshes.filter((m) => {
3541
- const infl = m.morphTargetInfluences;
3542
- const dict = m.morphTargetDictionary;
3543
- return Array.isArray(infl) && infl.length > 0 || dict && Object.keys(dict).length > 0;
3544
- }).map((m) => m.name).filter(Boolean);
3545
- if (morphMeshNames.length > 0) {
3554
+ const faceMeshNames = this.resolvedFaceMeshes.filter((name) => this.meshByName.has(name));
3555
+ if (faceMeshNames.length > 0) {
3546
3556
  this.config.morphToMesh = {
3547
3557
  ...this.config.morphToMesh,
3548
- face: Array.from(new Set(morphMeshNames))
3558
+ face: Array.from(new Set(faceMeshNames))
3549
3559
  };
3550
3560
  }
3551
3561
  }