@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 +1 -1
- package/dist/index.cjs +44 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +44 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Loom3
|
|
2
2
|
|
|
3
|
-
The missing character controller for 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
|
|
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 (
|
|
474
|
+
if (curveBalance > 0) effectiveScale *= 1 - curveBalance;
|
|
446
475
|
if (typeof morphKey === "number") {
|
|
447
|
-
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
476
|
+
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
448
477
|
} else {
|
|
449
|
-
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
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 (
|
|
483
|
+
if (curveBalance < 0) effectiveScale *= 1 + curveBalance;
|
|
455
484
|
if (typeof morphKey === "number") {
|
|
456
|
-
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
485
|
+
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
457
486
|
} else {
|
|
458
|
-
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
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,
|
|
493
|
+
this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, auMeshNames);
|
|
465
494
|
} else {
|
|
466
|
-
this.addMorphTracks(tracks, morphKey, keyframes, effectiveScale,
|
|
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) :
|
|
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) :
|
|
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
|
|
3562
|
-
|
|
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(
|
|
3579
|
+
face: Array.from(new Set(faceMeshNames))
|
|
3570
3580
|
};
|
|
3571
3581
|
}
|
|
3572
3582
|
}
|