@lovelace_lol/loom3 1.0.31 → 1.0.33
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 +13 -14
- package/dist/index.cjs +80 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -24
- package/dist/index.d.ts +20 -24
- package/dist/index.js +78 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# Loom3
|
|
2
2
|
|
|
3
|
-
_Author: Jonathan Sutton Fields._
|
|
4
3
|
|
|
5
4
|
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.
|
|
6
5
|
|
|
7
|
-
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
|
|
6
|
+
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 3d character assets. 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.
|
|
8
7
|
|
|
9
8
|
> **Note:** If you previously used the `loomlarge` npm package, it has been renamed to `@lovelace_lol/loom3`.
|
|
10
9
|
|
|
@@ -309,7 +308,7 @@ import { Loom3, CC4_PRESET } from '@lovelace_lol/loom3';
|
|
|
309
308
|
const loom = new Loom3({ profile: CC4_PRESET });
|
|
310
309
|
```
|
|
311
310
|
|
|
312
|
-
You can also
|
|
311
|
+
You can also look up presets by name and extend them without cloning the full preset:
|
|
313
312
|
|
|
314
313
|
```typescript
|
|
315
314
|
import { Loom3 } from '@lovelace_lol/loom3';
|
|
@@ -387,13 +386,13 @@ Use preset helpers when you want a stable entry point by model class instead of
|
|
|
387
386
|
|
|
388
387
|
```typescript
|
|
389
388
|
import {
|
|
390
|
-
|
|
391
|
-
|
|
389
|
+
getPreset,
|
|
390
|
+
getPresetWithProfile,
|
|
392
391
|
} from '@lovelace_lol/loom3';
|
|
393
392
|
|
|
394
|
-
const preset =
|
|
393
|
+
const preset = getPreset('cc4');
|
|
395
394
|
|
|
396
|
-
const
|
|
395
|
+
const extended = getPresetWithProfile('cc4', {
|
|
397
396
|
morphToMesh: { face: ['Object_9'] },
|
|
398
397
|
});
|
|
399
398
|
```
|
|
@@ -567,10 +566,10 @@ import {
|
|
|
567
566
|
analyzeModel,
|
|
568
567
|
validateMappings,
|
|
569
568
|
generateMappingCorrections,
|
|
570
|
-
|
|
569
|
+
getPreset,
|
|
571
570
|
} from '@lovelace_lol/loom3';
|
|
572
571
|
|
|
573
|
-
const preset =
|
|
572
|
+
const preset = getPreset('cc4');
|
|
574
573
|
const modelData = extractModelData(model, meshes, animations);
|
|
575
574
|
const gltfData = extractFromGLTF(gltf); // Same ModelData shape, one-step GLTF wrapper
|
|
576
575
|
|
|
@@ -674,12 +673,12 @@ Open in LoomLarge: [Properties tab](https://loomlarge.web.app/?drawer=open&tab=p
|
|
|
674
673
|
|
|
675
674
|
### Extending an existing preset
|
|
676
675
|
|
|
677
|
-
Use `
|
|
676
|
+
Use `extendPresetWithProfile` to override specific mappings while keeping the rest:
|
|
678
677
|
|
|
679
678
|
```typescript
|
|
680
|
-
import { CC4_PRESET,
|
|
679
|
+
import { CC4_PRESET, extendPresetWithProfile } from '@lovelace_lol/loom3';
|
|
681
680
|
|
|
682
|
-
const MY_PRESET =
|
|
681
|
+
const MY_PRESET = extendPresetWithProfile(CC4_PRESET, {
|
|
683
682
|
|
|
684
683
|
// Override AU12 (smile) with custom morph names
|
|
685
684
|
auToMorphs: {
|
|
@@ -2118,8 +2117,8 @@ This is a compact reference for the public surface exported by `@lovelace_lol/lo
|
|
|
2118
2117
|
|
|
2119
2118
|
### Presets and profiles
|
|
2120
2119
|
|
|
2121
|
-
- Presets: `CC4_PRESET`, `BETTA_FISH_PRESET`, `
|
|
2122
|
-
- Profile composition: `
|
|
2120
|
+
- Presets: `CC4_PRESET`, `BETTA_FISH_PRESET`, `getPreset()`, `getPresetWithProfile()`.
|
|
2121
|
+
- Profile composition: `extendPresetWithProfile()`.
|
|
2123
2122
|
- CC4 exports: `VISEME_KEYS`, `VISEME_JAW_AMOUNTS`, `CONTINUUM_PAIRS_MAP`, `CONTINUUM_LABELS`, `AU_INFO`, `COMPOSITE_ROTATIONS`, `AU_MIX_DEFAULTS`.
|
|
2124
2123
|
- Compatibility helpers: `isMixedAU()`, `hasLeftRightMorphs()`.
|
|
2125
2124
|
|
package/dist/index.cjs
CHANGED
|
@@ -2900,7 +2900,7 @@ var HairPhysicsController = class {
|
|
|
2900
2900
|
}
|
|
2901
2901
|
};
|
|
2902
2902
|
|
|
2903
|
-
// src/mappings/
|
|
2903
|
+
// src/mappings/extendPresetWithProfile.ts
|
|
2904
2904
|
var isPlainObject = (value) => {
|
|
2905
2905
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2906
2906
|
};
|
|
@@ -2992,31 +2992,33 @@ var mergeHairPhysicsConfig = (base, override) => {
|
|
|
2992
2992
|
}
|
|
2993
2993
|
return merged;
|
|
2994
2994
|
};
|
|
2995
|
-
function
|
|
2995
|
+
function extendPresetWithProfile(base, extension) {
|
|
2996
|
+
if (!extension) {
|
|
2997
|
+
return base;
|
|
2998
|
+
}
|
|
2999
|
+
const disabledRegions = extension.disabledRegions ? [...extension.disabledRegions] : base.disabledRegions ? [...base.disabledRegions] : void 0;
|
|
2996
3000
|
return {
|
|
2997
3001
|
...base,
|
|
2998
|
-
...
|
|
2999
|
-
auToMorphs: mergeRecord(base.auToMorphs,
|
|
3000
|
-
auToBones: mergeRecord(base.auToBones,
|
|
3001
|
-
boneNodes: mergeRecord(base.boneNodes,
|
|
3002
|
-
morphToMesh: mergeRecord(base.morphToMesh,
|
|
3003
|
-
auFacePartToMeshCategory: base.auFacePartToMeshCategory ||
|
|
3004
|
-
visemeKeys:
|
|
3005
|
-
visemeMeshCategory:
|
|
3006
|
-
auMixDefaults: base.auMixDefaults ||
|
|
3007
|
-
auInfo: base.auInfo ||
|
|
3008
|
-
eyeMeshNodes:
|
|
3009
|
-
compositeRotations:
|
|
3010
|
-
meshes: base.meshes ||
|
|
3011
|
-
continuumPairs: base.continuumPairs ||
|
|
3012
|
-
continuumLabels: base.continuumLabels ||
|
|
3013
|
-
annotationRegions: mergeAnnotationRegions(base.annotationRegions,
|
|
3014
|
-
|
|
3002
|
+
...extension,
|
|
3003
|
+
auToMorphs: mergeRecord(base.auToMorphs, extension.auToMorphs),
|
|
3004
|
+
auToBones: mergeRecord(base.auToBones, extension.auToBones),
|
|
3005
|
+
boneNodes: mergeRecord(base.boneNodes, extension.boneNodes),
|
|
3006
|
+
morphToMesh: mergeRecord(base.morphToMesh, extension.morphToMesh),
|
|
3007
|
+
auFacePartToMeshCategory: base.auFacePartToMeshCategory || extension.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, extension.auFacePartToMeshCategory || {}) : void 0,
|
|
3008
|
+
visemeKeys: extension.visemeKeys ? [...extension.visemeKeys] : [...base.visemeKeys],
|
|
3009
|
+
visemeMeshCategory: extension.visemeMeshCategory ?? base.visemeMeshCategory,
|
|
3010
|
+
auMixDefaults: base.auMixDefaults || extension.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, extension.auMixDefaults || {}) : void 0,
|
|
3011
|
+
auInfo: base.auInfo || extension.auInfo ? mergeRecord(base.auInfo || {}, extension.auInfo || {}) : void 0,
|
|
3012
|
+
eyeMeshNodes: extension.eyeMeshNodes ?? base.eyeMeshNodes,
|
|
3013
|
+
compositeRotations: extension.compositeRotations ?? base.compositeRotations,
|
|
3014
|
+
meshes: base.meshes || extension.meshes ? mergeRecord(base.meshes || {}, extension.meshes || {}) : void 0,
|
|
3015
|
+
continuumPairs: base.continuumPairs || extension.continuumPairs ? mergeRecord(base.continuumPairs || {}, extension.continuumPairs || {}) : void 0,
|
|
3016
|
+
continuumLabels: base.continuumLabels || extension.continuumLabels ? mergeRecord(base.continuumLabels || {}, extension.continuumLabels || {}) : void 0,
|
|
3017
|
+
annotationRegions: mergeAnnotationRegions(base.annotationRegions, extension.annotationRegions),
|
|
3018
|
+
disabledRegions,
|
|
3019
|
+
hairPhysics: mergeHairPhysicsConfig(base.hairPhysics, extension.hairPhysics)
|
|
3015
3020
|
};
|
|
3016
3021
|
}
|
|
3017
|
-
function applyProfileToPreset(base, override) {
|
|
3018
|
-
return override ? resolveProfile(base, override) : base;
|
|
3019
|
-
}
|
|
3020
3022
|
|
|
3021
3023
|
// src/presets/bettaFish.ts
|
|
3022
3024
|
var BONES = [
|
|
@@ -3803,22 +3805,24 @@ var ANNOTATION_REGIONS = [
|
|
|
3803
3805
|
name: "gills",
|
|
3804
3806
|
bones: ["GILL_L", "GILL_R"],
|
|
3805
3807
|
paddingFactor: 1.6,
|
|
3806
|
-
children: ["
|
|
3808
|
+
children: ["gill_left", "gill_right"],
|
|
3807
3809
|
expandAnimation: "outward"
|
|
3808
3810
|
},
|
|
3809
3811
|
{
|
|
3810
|
-
name: "
|
|
3812
|
+
name: "gill_left",
|
|
3811
3813
|
bones: ["GILL_L", "GILL_L_MID", "GILL_L_TIP"],
|
|
3812
3814
|
parent: "gills",
|
|
3813
3815
|
paddingFactor: 1.4,
|
|
3814
|
-
cameraAngle: 270
|
|
3816
|
+
cameraAngle: 270,
|
|
3817
|
+
style: { lineDirection: "left" }
|
|
3815
3818
|
},
|
|
3816
3819
|
{
|
|
3817
|
-
name: "
|
|
3820
|
+
name: "gill_right",
|
|
3818
3821
|
bones: ["GILL_R", "GILL_R_MID", "GILL_R_TIP"],
|
|
3819
3822
|
parent: "gills",
|
|
3820
3823
|
paddingFactor: 1.4,
|
|
3821
|
-
cameraAngle: 90
|
|
3824
|
+
cameraAngle: 90,
|
|
3825
|
+
style: { lineDirection: "right" }
|
|
3822
3826
|
}
|
|
3823
3827
|
];
|
|
3824
3828
|
var BETTA_FISH_PROFILE = {
|
|
@@ -3855,7 +3859,7 @@ var AU_MAPPING_CONFIG = BETTA_FISH_PRESET;
|
|
|
3855
3859
|
var FISH_AU_MAPPING_CONFIG = BETTA_FISH_PRESET;
|
|
3856
3860
|
|
|
3857
3861
|
// src/presets/index.ts
|
|
3858
|
-
function
|
|
3862
|
+
function getPreset(presetType) {
|
|
3859
3863
|
switch (presetType) {
|
|
3860
3864
|
case "fish":
|
|
3861
3865
|
case "skeletal":
|
|
@@ -3866,9 +3870,8 @@ function resolvePreset(presetType) {
|
|
|
3866
3870
|
return CC4_PRESET;
|
|
3867
3871
|
}
|
|
3868
3872
|
}
|
|
3869
|
-
function
|
|
3870
|
-
|
|
3871
|
-
return applyProfileToPreset(base, overrides);
|
|
3873
|
+
function getPresetWithProfile(presetType, profile) {
|
|
3874
|
+
return extendPresetWithProfile(getPreset(presetType), profile);
|
|
3872
3875
|
}
|
|
3873
3876
|
|
|
3874
3877
|
// src/engines/three/Loom3.ts
|
|
@@ -3944,8 +3947,8 @@ var _Loom3 = class _Loom3 {
|
|
|
3944
3947
|
__publicField(this, "isRunning", false);
|
|
3945
3948
|
/** Store original emissive colors for highlight reset */
|
|
3946
3949
|
__publicField(this, "originalEmissive", /* @__PURE__ */ new Map());
|
|
3947
|
-
const basePreset = config.presetType ?
|
|
3948
|
-
this.config =
|
|
3950
|
+
const basePreset = config.presetType ? getPreset(config.presetType) : CC4_PRESET;
|
|
3951
|
+
this.config = extendPresetWithProfile(basePreset, config.profile);
|
|
3949
3952
|
this.mixWeights = { ...this.config.auMixDefaults };
|
|
3950
3953
|
this.animation = animation || new AnimationThree();
|
|
3951
3954
|
this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
|
|
@@ -5354,7 +5357,30 @@ var BLENDING_MODES = {
|
|
|
5354
5357
|
// THREE.NoBlending
|
|
5355
5358
|
};
|
|
5356
5359
|
|
|
5357
|
-
// src/
|
|
5360
|
+
// src/regions/normalizeRegionTree.ts
|
|
5361
|
+
function normalizeDisabledNames(disabledNames) {
|
|
5362
|
+
return new Set((disabledNames ?? []).filter((name) => Boolean(name)));
|
|
5363
|
+
}
|
|
5364
|
+
function normalizeRegionTree(regions, disabledNames) {
|
|
5365
|
+
if (!regions) return void 0;
|
|
5366
|
+
const disabled = normalizeDisabledNames(disabledNames);
|
|
5367
|
+
const nextRegions = regions.filter((region) => !disabled.has(region.name)).map((region) => ({
|
|
5368
|
+
...region,
|
|
5369
|
+
children: region.children ? [...region.children] : void 0
|
|
5370
|
+
}));
|
|
5371
|
+
const remainingNames = new Set(nextRegions.map((region) => region.name));
|
|
5372
|
+
return nextRegions.map((region) => {
|
|
5373
|
+
const nextChildren = region.children?.filter((child) => remainingNames.has(child));
|
|
5374
|
+
const nextParent = region.parent && remainingNames.has(region.parent) ? region.parent : void 0;
|
|
5375
|
+
return {
|
|
5376
|
+
...region,
|
|
5377
|
+
parent: nextParent,
|
|
5378
|
+
children: nextChildren && nextChildren.length > 0 ? nextChildren : void 0
|
|
5379
|
+
};
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
// src/characters/extendCharacterConfigWithPreset.ts
|
|
5358
5384
|
var PROFILE_OVERRIDE_KEYS = [
|
|
5359
5385
|
"name",
|
|
5360
5386
|
"animalType",
|
|
@@ -5382,6 +5408,7 @@ var PROFILE_OVERRIDE_KEYS = [
|
|
|
5382
5408
|
"continuumPairs",
|
|
5383
5409
|
"continuumLabels",
|
|
5384
5410
|
"annotationRegions",
|
|
5411
|
+
"disabledRegions",
|
|
5385
5412
|
"hairPhysics"
|
|
5386
5413
|
];
|
|
5387
5414
|
function isPlainObject2(value) {
|
|
@@ -5500,11 +5527,11 @@ function applyCharacterProfileToPreset(config) {
|
|
|
5500
5527
|
if (!presetType) {
|
|
5501
5528
|
return null;
|
|
5502
5529
|
}
|
|
5503
|
-
return
|
|
5530
|
+
return extendPresetWithProfile(getPreset(presetType), extractProfileOverrides(config));
|
|
5504
5531
|
}
|
|
5505
|
-
function
|
|
5506
|
-
if (!
|
|
5507
|
-
const
|
|
5532
|
+
function orderExtendedRegions(extendedRegions, prioritizedLists) {
|
|
5533
|
+
if (!extendedRegions) return void 0;
|
|
5534
|
+
const extendedByName = new Map(extendedRegions.map((region) => [region.name, region]));
|
|
5508
5535
|
const orderedNames = [];
|
|
5509
5536
|
const seen = /* @__PURE__ */ new Set();
|
|
5510
5537
|
for (const regions of prioritizedLists) {
|
|
@@ -5514,12 +5541,12 @@ function orderResolvedRegions(resolvedRegions, prioritizedLists) {
|
|
|
5514
5541
|
orderedNames.push(region.name);
|
|
5515
5542
|
}
|
|
5516
5543
|
}
|
|
5517
|
-
for (const region of
|
|
5544
|
+
for (const region of extendedRegions) {
|
|
5518
5545
|
if (seen.has(region.name)) continue;
|
|
5519
5546
|
seen.add(region.name);
|
|
5520
5547
|
orderedNames.push(region.name);
|
|
5521
5548
|
}
|
|
5522
|
-
return orderedNames.map((name) =>
|
|
5549
|
+
return orderedNames.map((name) => extendedByName.get(name)).filter((region) => Boolean(region));
|
|
5523
5550
|
}
|
|
5524
5551
|
function extendCharacterConfigWithPreset(config) {
|
|
5525
5552
|
const presetType = config.auPresetType;
|
|
@@ -5527,25 +5554,26 @@ function extendCharacterConfigWithPreset(config) {
|
|
|
5527
5554
|
return config;
|
|
5528
5555
|
}
|
|
5529
5556
|
const profileOverrides = extractProfileOverrides(config);
|
|
5530
|
-
const
|
|
5531
|
-
if (!
|
|
5557
|
+
const extendedPresetProfile = applyCharacterProfileToPreset(config);
|
|
5558
|
+
if (!extendedPresetProfile) {
|
|
5532
5559
|
return config;
|
|
5533
5560
|
}
|
|
5534
|
-
const presetRegions =
|
|
5561
|
+
const presetRegions = extendedPresetProfile.annotationRegions;
|
|
5535
5562
|
const mergedRegions = mergeRegionsByName(presetRegions, config.regions);
|
|
5536
|
-
const
|
|
5563
|
+
const normalizedRegions = normalizeRegionTree(
|
|
5537
5564
|
mergedRegions,
|
|
5565
|
+
profileOverrides.disabledRegions
|
|
5566
|
+
);
|
|
5567
|
+
const extendedRegions = orderExtendedRegions(
|
|
5568
|
+
normalizedRegions,
|
|
5538
5569
|
[config.regions, profileOverrides.annotationRegions, presetRegions]
|
|
5539
5570
|
);
|
|
5540
5571
|
return {
|
|
5541
5572
|
...config,
|
|
5542
|
-
...
|
|
5543
|
-
regions:
|
|
5573
|
+
...extendedPresetProfile,
|
|
5574
|
+
regions: extendedRegions ?? config.regions
|
|
5544
5575
|
};
|
|
5545
5576
|
}
|
|
5546
|
-
function resolveCharacterConfig(config) {
|
|
5547
|
-
return extendCharacterConfigWithPreset(config);
|
|
5548
|
-
}
|
|
5549
5577
|
var DEFAULT_HEAD_BONE_NAMES = ["CC_Base_Head", "Head", "head", "Bip01_Head"];
|
|
5550
5578
|
var DEFAULT_REFERENCE_HEIGHT = 1.8;
|
|
5551
5579
|
var DEFAULT_FORWARD_OFFSET = 0.08;
|
|
@@ -6892,10 +6920,10 @@ exports.VISEME_JAW_AMOUNTS = VISEME_JAW_AMOUNTS;
|
|
|
6892
6920
|
exports.VISEME_KEYS = VISEME_KEYS;
|
|
6893
6921
|
exports.analyzeModel = analyzeModel;
|
|
6894
6922
|
exports.applyCharacterProfileToPreset = applyCharacterProfileToPreset;
|
|
6895
|
-
exports.applyProfileToPreset = applyProfileToPreset;
|
|
6896
6923
|
exports.collectMorphMeshes = collectMorphMeshes;
|
|
6897
6924
|
exports.detectFacingDirection = detectFacingDirection;
|
|
6898
6925
|
exports.extendCharacterConfigWithPreset = extendCharacterConfigWithPreset;
|
|
6926
|
+
exports.extendPresetWithProfile = extendPresetWithProfile;
|
|
6899
6927
|
exports.extractFromGLTF = extractFromGLTF;
|
|
6900
6928
|
exports.extractModelData = extractModelData;
|
|
6901
6929
|
exports.extractProfileOverrides = extractProfileOverrides;
|
|
@@ -6903,17 +6931,15 @@ exports.findFaceCenter = findFaceCenter;
|
|
|
6903
6931
|
exports.fuzzyNameMatch = fuzzyNameMatch;
|
|
6904
6932
|
exports.generateMappingCorrections = generateMappingCorrections;
|
|
6905
6933
|
exports.getModelForwardDirection = getModelForwardDirection;
|
|
6934
|
+
exports.getPreset = getPreset;
|
|
6935
|
+
exports.getPresetWithProfile = getPresetWithProfile;
|
|
6906
6936
|
exports.hasLeftRightMorphs = hasLeftRightMorphs;
|
|
6907
6937
|
exports.isMixedAU = isMixedAU;
|
|
6908
6938
|
exports.isPresetCompatible = isPresetCompatible;
|
|
6909
6939
|
exports.mergeCharacterRegionsByName = mergeRegionsByName;
|
|
6910
6940
|
exports.resolveBoneName = resolveBoneName;
|
|
6911
6941
|
exports.resolveBoneNames = resolveBoneNames;
|
|
6912
|
-
exports.resolveCharacterConfig = resolveCharacterConfig;
|
|
6913
6942
|
exports.resolveFaceCenter = resolveFaceCenter;
|
|
6914
|
-
exports.resolvePreset = resolvePreset;
|
|
6915
|
-
exports.resolvePresetWithOverrides = resolvePresetWithOverrides;
|
|
6916
|
-
exports.resolveProfile = resolveProfile;
|
|
6917
6943
|
exports.suggestBestPreset = suggestBestPreset;
|
|
6918
6944
|
exports.validateMappingConfig = validateMappingConfig;
|
|
6919
6945
|
exports.validateMappings = validateMappings;
|