@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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Loom3
2
2
 
3
- The missing character controller for Three.js, allowing you to bring humanoid and animal characters to life. Loom3 is based on the Facial Action Coding System (FACS) as the basis of its mappings, providing a morph and bone mapping library for controlling high-definition 3D characters in Three.js.
3
+ The missing character controller for Three.js! Loom3 allows you to bring humanoid and animal characters to life. Loom3 is based on the Facial Action Coding System (FACS) as the basis of its mappings, providing a morph and bone mapping library for controlling high-definition 3D characters in Three.js.
4
4
 
5
5
  Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https://en.wikipedia.org/wiki/Facial_Action_Coding_System) Action Units to the morph targets and bone transforms found in Character Creator 4 (CC4) characters. Instead of manually figuring out which blend shapes correspond to which facial movements, you can simply say `setAU(12, 0.8)` and the library handles the rest.
6
6
 
package/dist/index.cjs CHANGED
@@ -25,6 +25,20 @@ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
25
25
  var __defProp = Object.defineProperty;
26
26
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
27
27
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
28
+
29
+ // src/engines/three/balanceUtils.ts
30
+ function clampBalance(value) {
31
+ if (!Number.isFinite(value)) return 0;
32
+ return Math.max(-1, Math.min(1, value));
33
+ }
34
+ function resolveCurveBalance(curveId, globalBalance, balanceMap) {
35
+ if (balanceMap && Object.prototype.hasOwnProperty.call(balanceMap, curveId)) {
36
+ return clampBalance(Number(balanceMap[curveId]));
37
+ }
38
+ return clampBalance(globalBalance);
39
+ }
40
+
41
+ // src/engines/three/AnimationThree.ts
28
42
  var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
29
43
  var AnimationThree = class {
30
44
  constructor() {
@@ -136,6 +150,18 @@ var BakedAnimationController = class {
136
150
  __publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
137
151
  this.host = host;
138
152
  }
153
+ getMeshNamesForAU(auId, config, explicitMeshNames) {
154
+ if (explicitMeshNames && explicitMeshNames.length > 0) {
155
+ return explicitMeshNames;
156
+ }
157
+ if (typeof this.host.getMeshNamesForAU === "function") {
158
+ return this.host.getMeshNamesForAU(auId) || [];
159
+ }
160
+ const facePart = config.auInfo?.[String(auId)]?.facePart;
161
+ if (facePart === "Tongue") return config.morphToMesh?.tongue || [];
162
+ if (facePart === "Eye") return config.morphToMesh?.eye || [];
163
+ return config.morphToMesh?.face || [];
164
+ }
139
165
  update(dtSeconds) {
140
166
  if (this.animationMixer) {
141
167
  this.animationMixer.update(dtSeconds);
@@ -384,7 +410,8 @@ var BakedAnimationController = class {
384
410
  }
385
411
  const tracks = [];
386
412
  const intensityScale = options?.intensityScale ?? 1;
387
- const balance = options?.balance ?? 0;
413
+ const globalBalance = options?.balance ?? 0;
414
+ const balanceMap = options?.balanceMap;
388
415
  const meshNames = options?.meshNames;
389
416
  let maxTime = 0;
390
417
  const isNumericAU = (id) => /^\d+$/.test(id);
@@ -435,35 +462,37 @@ var BakedAnimationController = class {
435
462
  this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, meshNames);
436
463
  }
437
464
  } else {
465
+ const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
438
466
  const morphsBySide = config.auToMorphs[auId];
439
467
  const mixWeight = this.host.isMixedAU(auId) ? this.host.getAUMixWeight(auId) : 1;
440
468
  const leftKeys = morphsBySide?.left ?? [];
441
469
  const rightKeys = morphsBySide?.right ?? [];
442
470
  const centerKeys = morphsBySide?.center ?? [];
471
+ const curveBalance = resolveCurveBalance(curveId, globalBalance, balanceMap);
443
472
  for (const morphKey of leftKeys) {
444
473
  let effectiveScale = intensityScale * mixWeight;
445
- if (balance > 0) effectiveScale *= 1 - balance;
474
+ if (curveBalance > 0) effectiveScale *= 1 - curveBalance;
446
475
  if (typeof morphKey === "number") {
447
- this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
476
+ this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
448
477
  } else {
449
- this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
478
+ this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
450
479
  }
451
480
  }
452
481
  for (const morphKey of rightKeys) {
453
482
  let effectiveScale = intensityScale * mixWeight;
454
- if (balance < 0) effectiveScale *= 1 + balance;
483
+ if (curveBalance < 0) effectiveScale *= 1 + curveBalance;
455
484
  if (typeof morphKey === "number") {
456
- this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
485
+ this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
457
486
  } else {
458
- this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
487
+ this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
459
488
  }
460
489
  }
461
490
  for (const morphKey of centerKeys) {
462
491
  const effectiveScale = intensityScale * mixWeight;
463
492
  if (typeof morphKey === "number") {
464
- this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
493
+ this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
465
494
  } else {
466
- this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
495
+ this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
467
496
  }
468
497
  }
469
498
  }
@@ -851,11 +880,9 @@ var BakedAnimationController = class {
851
880
  }
852
881
  addMorphTracks(tracks, morphKey, keyframes, intensityScale, meshNames) {
853
882
  const config = this.host.getConfig();
854
- const meshes = this.host.getMeshes();
855
883
  const hasExplicitMeshes = !!(meshNames && meshNames.length > 0);
856
884
  const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
857
- const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : meshes;
858
- let added = false;
885
+ const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
859
886
  const addTrackForMesh = (mesh) => {
860
887
  const dict = mesh.morphTargetDictionary;
861
888
  if (!dict || dict[morphKey] === void 0) return;
@@ -869,25 +896,17 @@ var BakedAnimationController = class {
869
896
  const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
870
897
  const track = new THREE.NumberKeyframeTrack(trackName, times, values);
871
898
  tracks.push(track);
872
- added = true;
873
899
  };
874
900
  for (const mesh of targetMeshes) {
875
901
  addTrackForMesh(mesh);
876
902
  }
877
- if (!added && !hasExplicitMeshes && targetMeshes !== meshes) {
878
- for (const mesh of meshes) {
879
- addTrackForMesh(mesh);
880
- }
881
- }
882
903
  }
883
904
  addMorphIndexTracks(tracks, morphIndex, keyframes, intensityScale, meshNames) {
884
905
  if (!Number.isInteger(morphIndex) || morphIndex < 0) return;
885
906
  const config = this.host.getConfig();
886
- const meshes = this.host.getMeshes();
887
907
  const hasExplicitMeshes = !!(meshNames && meshNames.length > 0);
888
908
  const targetMeshNames = hasExplicitMeshes ? meshNames : config.morphToMesh?.face || [];
889
- const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : meshes;
890
- let added = false;
909
+ const targetMeshes = targetMeshNames.length ? targetMeshNames.map((name) => this.host.getMeshByName(name)).filter(Boolean) : [];
891
910
  const addTrackForMesh = (mesh) => {
892
911
  const infl = mesh.morphTargetInfluences;
893
912
  if (!infl || morphIndex < 0 || morphIndex >= infl.length) return;
@@ -900,16 +919,10 @@ var BakedAnimationController = class {
900
919
  const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
901
920
  const track = new THREE.NumberKeyframeTrack(trackName, times, values);
902
921
  tracks.push(track);
903
- added = true;
904
922
  };
905
923
  for (const mesh of targetMeshes) {
906
924
  addTrackForMesh(mesh);
907
925
  }
908
- if (!added && !hasExplicitMeshes && targetMeshes !== meshes) {
909
- for (const mesh of meshes) {
910
- addTrackForMesh(mesh);
911
- }
912
- }
913
926
  }
914
927
  ensureMixer() {
915
928
  const model = this.host.getModel();
@@ -3513,6 +3526,7 @@ var _Loom3 = class _Loom3 {
3513
3526
  getModel: () => this.model,
3514
3527
  getMeshes: () => this.meshes,
3515
3528
  getMeshByName: (name) => this.meshByName.get(name),
3529
+ getMeshNamesForAU: (auId) => this.getMeshNamesForAU(auId),
3516
3530
  getBones: () => this.bones,
3517
3531
  getConfig: () => this.config,
3518
3532
  getCompositeRotations: () => this.compositeRotations,
@@ -3558,15 +3572,11 @@ var _Loom3 = class _Loom3 {
3558
3572
  this.resolvedFaceMeshes = this.resolveFaceMeshes(this.meshes);
3559
3573
  this.faceMesh = this.resolvedFaceMeshes.length > 0 ? this.meshByName.get(this.resolvedFaceMeshes[0]) || null : null;
3560
3574
  if (!this.config.morphToMesh?.face || this.config.morphToMesh.face.length === 0) {
3561
- const morphMeshNames = this.meshes.filter((m) => {
3562
- const infl = m.morphTargetInfluences;
3563
- const dict = m.morphTargetDictionary;
3564
- return Array.isArray(infl) && infl.length > 0 || dict && Object.keys(dict).length > 0;
3565
- }).map((m) => m.name).filter(Boolean);
3566
- if (morphMeshNames.length > 0) {
3575
+ const faceMeshNames = this.resolvedFaceMeshes.filter((name) => this.meshByName.has(name));
3576
+ if (faceMeshNames.length > 0) {
3567
3577
  this.config.morphToMesh = {
3568
3578
  ...this.config.morphToMesh,
3569
- face: Array.from(new Set(morphMeshNames))
3579
+ face: Array.from(new Set(faceMeshNames))
3570
3580
  };
3571
3581
  }
3572
3582
  }