@lovelace_lol/loom3 1.0.39 → 1.0.41
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 +23 -3
- package/dist/index.cjs +234 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -6
- package/dist/index.d.ts +61 -6
- package/dist/index.js +235 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https:
|
|
|
15
15
|
|
|
16
16
|
Loom3 is broader than a face-controller wrapper. The library spans four practical areas:
|
|
17
17
|
- Runtime control: Action Units, visemes, direct morphs, continuum pairs, composite rotations, transitions, and mixer playback.
|
|
18
|
-
- Rig configuration: built-in presets, profile overrides, preset
|
|
18
|
+
- Rig configuration: built-in presets, profile overrides, preset lookup and extension, name resolution, viseme routing, mix weights, and skeletal-only preset support.
|
|
19
19
|
- Inspection and validation: mesh, morph, and bone discovery; preset-fit checks; correction suggestions; and full model analysis.
|
|
20
20
|
- Runtime tooling: mesh/material debugging, baked animation clip helpers, hair physics, and region/geometry helpers for annotation or camera tooling.
|
|
21
21
|
|
|
@@ -366,7 +366,7 @@ For the current runtime-oriented documentation, including:
|
|
|
366
366
|
- `cameraOffset`
|
|
367
367
|
- `style.lineDirection`
|
|
368
368
|
- the difference between `cameraAngle: 0` and omitting `cameraAngle`
|
|
369
|
-
-
|
|
369
|
+
- runtime compatibility and legacy `config.regions` fallback behavior
|
|
370
370
|
|
|
371
371
|
see [ANNOTATION_CONFIGURATION.md](./ANNOTATION_CONFIGURATION.md).
|
|
372
372
|
|
|
@@ -380,7 +380,7 @@ Open in LoomLarge: [Properties tab](https://www.characterloom.com/?drawer=open&t
|
|
|
380
380
|
|
|
381
381
|
Before you tune AUs or hand-edit a profile, confirm that you picked the right preset and that the model actually matches it. Loom3 exposes a full preset-selection and validation workflow, not just low-level control APIs.
|
|
382
382
|
|
|
383
|
-
###
|
|
383
|
+
### Looking Up and Extending Presets by Type
|
|
384
384
|
|
|
385
385
|
Use preset helpers when you want a stable entry point by model class instead of importing a preset constant directly:
|
|
386
386
|
|
|
@@ -1356,6 +1356,25 @@ const targets = loom.resolveMorphTargets('Mouth_Smile_L', ['CC_Base_Body']);
|
|
|
1356
1356
|
const value = targets.length > 0 ? (targets[0].infl[targets[0].idx] ?? 0) : 0;
|
|
1357
1357
|
```
|
|
1358
1358
|
|
|
1359
|
+
### Adding runtime morph targets
|
|
1360
|
+
|
|
1361
|
+
Generated or sidecar morph targets can be registered after a model loads. Deltas use the same relative `POSITION` format as glTF morph targets: one XYZ delta per base mesh vertex. Optional `normal` and `tangent` deltas can be supplied when available.
|
|
1362
|
+
|
|
1363
|
+
```typescript
|
|
1364
|
+
const index = loom.addMorphTarget({
|
|
1365
|
+
meshName: 'CC_Base_Body',
|
|
1366
|
+
name: 'BodyType_Muscular',
|
|
1367
|
+
position: bodyTypeMuscularDeltas,
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
loom.setMorphInfluence(index, 0.6, ['CC_Base_Body']);
|
|
1371
|
+
loom.setMorph('BodyType_Muscular', 0.6, ['CC_Base_Body']);
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
By default, Loom3 replaces and disposes the mesh `BufferGeometry` before appending morph attributes. This is intentional: Three.js does not support mutating `geometry.morphAttributes` in place after a geometry has rendered. For pre-render authoring paths, pass `{ forceGeometryReplacement: false }`.
|
|
1375
|
+
|
|
1376
|
+
If you need a named slot before real deltas are available, use `ensureMorphInfluence(meshName, morphName)`. It creates a zero-delta target and returns the assigned `morphTargetInfluences` index. After external code changes morph dictionaries or geometry, call `refreshMorphTargets()` so AU, viseme, hair, and clip-building caches see the updated targets.
|
|
1377
|
+
|
|
1359
1378
|
### Morph caching
|
|
1360
1379
|
|
|
1361
1380
|
Loom3 caches morph target lookups for performance. The first time you access a morph, it searches all meshes and caches the index. Subsequent accesses are O(1).
|
|
@@ -2128,6 +2147,7 @@ This is a compact reference for the public surface exported by `@lovelace_lol/lo
|
|
|
2128
2147
|
- Lifecycle: `onReady()`, `update()`, `start()`, `stop()`, `dispose()`.
|
|
2129
2148
|
- Preset state: `setProfile()`, `getProfile()`.
|
|
2130
2149
|
- Control APIs: `setAU()`, `transitionAU()`, `setContinuum()`, `transitionContinuum()`, `setMorph()`, `transitionMorph()`, `setViseme()`, `transitionViseme()`.
|
|
2150
|
+
- Runtime morph authoring: `addMorphTarget()`, `addMorphTargets()`, `ensureMorphInfluence()`, `refreshMorphTargets()`.
|
|
2131
2151
|
- Transition state: `pause()`, `resume()`, `getPaused()`, `clearTransitions()`, `getActiveTransitionCount()`, `resetToNeutral()`.
|
|
2132
2152
|
|
|
2133
2153
|
### Presets and profiles
|
package/dist/index.cjs
CHANGED
|
@@ -3573,7 +3573,7 @@ var mergeAnnotationRegion = (base, override) => {
|
|
|
3573
3573
|
merged.meshes = override.meshes ? [...override.meshes] : base.meshes ? [...base.meshes] : void 0;
|
|
3574
3574
|
merged.objects = override.objects ? [...override.objects] : base.objects ? [...base.objects] : void 0;
|
|
3575
3575
|
merged.children = override.children ? [...override.children] : base.children ? [...base.children] : void 0;
|
|
3576
|
-
merged.cameraOffset = override.cameraOffset ? { ...override.cameraOffset } : base.cameraOffset ? { ...base.cameraOffset } : void 0;
|
|
3576
|
+
merged.cameraOffset = override.cameraOffset ? { ...base.cameraOffset, ...override.cameraOffset } : base.cameraOffset ? { ...base.cameraOffset } : void 0;
|
|
3577
3577
|
merged.style = override.style ? {
|
|
3578
3578
|
...base.style,
|
|
3579
3579
|
...override.style,
|
|
@@ -4638,11 +4638,7 @@ var _Loom3 = class _Loom3 {
|
|
|
4638
4638
|
this.morphIndexCache.clear();
|
|
4639
4639
|
model.traverse((obj) => {
|
|
4640
4640
|
if (obj.isMesh && obj.name) {
|
|
4641
|
-
|
|
4642
|
-
const dict = obj.morphTargetDictionary;
|
|
4643
|
-
if (Array.isArray(infl) && infl.length > 0 || dict && Object.keys(dict).length > 0) {
|
|
4644
|
-
this.meshByName.set(obj.name, obj);
|
|
4645
|
-
}
|
|
4641
|
+
this.meshByName.set(obj.name, obj);
|
|
4646
4642
|
}
|
|
4647
4643
|
});
|
|
4648
4644
|
this.bones = this.resolveBones(model);
|
|
@@ -5010,6 +5006,58 @@ var _Loom3 = class _Loom3 {
|
|
|
5010
5006
|
const currentContinuum = currentPos - currentNeg;
|
|
5011
5007
|
return this.animation.addTransition(driverKey, currentContinuum, target, durationMs, (value) => this.setContinuum(negAU, posAU, value, balance));
|
|
5012
5008
|
}
|
|
5009
|
+
// ============================================================================
|
|
5010
|
+
// MORPH CONTROL
|
|
5011
|
+
// ============================================================================
|
|
5012
|
+
addMorphTarget(target, options = {}) {
|
|
5013
|
+
const staleMorphTargets = this.collectResolvedExpressionMorphTargets();
|
|
5014
|
+
const index = this.applyMorphTargetDelta(target, options);
|
|
5015
|
+
this.refreshMorphTargets([target.meshName]);
|
|
5016
|
+
this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
|
|
5017
|
+
return index;
|
|
5018
|
+
}
|
|
5019
|
+
addMorphTargets(targets, options = {}) {
|
|
5020
|
+
const staleMorphTargets = this.collectResolvedExpressionMorphTargets();
|
|
5021
|
+
const result = {};
|
|
5022
|
+
for (const target of targets) {
|
|
5023
|
+
const index = this.applyMorphTargetDelta(target, options);
|
|
5024
|
+
result[`${target.meshName}:${target.name}`] = index;
|
|
5025
|
+
}
|
|
5026
|
+
this.refreshMorphTargets(Array.from(new Set(targets.map((target) => target.meshName))));
|
|
5027
|
+
this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
|
|
5028
|
+
return result;
|
|
5029
|
+
}
|
|
5030
|
+
ensureMorphInfluence(meshName, morphName) {
|
|
5031
|
+
const mesh = this.requireNamedMesh(meshName);
|
|
5032
|
+
const dict = this.getMeshMorphDictionary(mesh);
|
|
5033
|
+
const existing = dict[morphName];
|
|
5034
|
+
if (existing !== void 0) return existing;
|
|
5035
|
+
const position = mesh.geometry.getAttribute("position");
|
|
5036
|
+
if (!position) {
|
|
5037
|
+
throw new Error(`Cannot create morph target "${morphName}" on mesh "${meshName}": geometry has no position attribute.`);
|
|
5038
|
+
}
|
|
5039
|
+
return this.addMorphTarget({
|
|
5040
|
+
meshName,
|
|
5041
|
+
name: morphName,
|
|
5042
|
+
position: new Float32Array(position.count * position.itemSize),
|
|
5043
|
+
relative: true
|
|
5044
|
+
});
|
|
5045
|
+
}
|
|
5046
|
+
refreshMorphTargets(_meshNames) {
|
|
5047
|
+
this.morphKeyCache.clear();
|
|
5048
|
+
this.morphIndexCache.clear();
|
|
5049
|
+
if (this.model) {
|
|
5050
|
+
this.meshByName.clear();
|
|
5051
|
+
this.model.traverse((obj) => {
|
|
5052
|
+
if (obj.isMesh && obj.name) {
|
|
5053
|
+
this.meshByName.set(obj.name, obj);
|
|
5054
|
+
}
|
|
5055
|
+
});
|
|
5056
|
+
this.meshes = collectMorphMeshes(this.model);
|
|
5057
|
+
}
|
|
5058
|
+
this.rebuildMorphTargetsCache();
|
|
5059
|
+
this.hairPhysics.refreshMeshSelection();
|
|
5060
|
+
}
|
|
5013
5061
|
setMorph(key, v, meshNamesOrTargets) {
|
|
5014
5062
|
const val = clamp012(v);
|
|
5015
5063
|
if (Array.isArray(meshNamesOrTargets) && meshNamesOrTargets.length > 0 && typeof meshNamesOrTargets[0] === "object" && "infl" in meshNamesOrTargets[0]) {
|
|
@@ -5628,6 +5676,148 @@ var _Loom3 = class _Loom3 {
|
|
|
5628
5676
|
}
|
|
5629
5677
|
return 0;
|
|
5630
5678
|
}
|
|
5679
|
+
applyMorphTargetDelta(target, options) {
|
|
5680
|
+
const mesh = this.requireNamedMesh(target.meshName);
|
|
5681
|
+
const sourceGeometry = mesh.geometry;
|
|
5682
|
+
const position = sourceGeometry.getAttribute("position");
|
|
5683
|
+
if (!position) {
|
|
5684
|
+
throw new Error(`Cannot add morph target "${target.name}" to mesh "${target.meshName}": geometry has no position attribute.`);
|
|
5685
|
+
}
|
|
5686
|
+
if (!target.name || !target.name.trim()) {
|
|
5687
|
+
throw new Error(`Cannot add morph target to mesh "${target.meshName}": target name is required.`);
|
|
5688
|
+
}
|
|
5689
|
+
const replace = options.replace === true;
|
|
5690
|
+
const resetInfluence = options.resetInfluence !== false;
|
|
5691
|
+
const forceGeometryReplacement = options.forceGeometryReplacement !== false;
|
|
5692
|
+
const previousInfluences = mesh.morphTargetInfluences ? [...mesh.morphTargetInfluences] : [];
|
|
5693
|
+
const previousDictionary = this.getMeshMorphDictionary(mesh);
|
|
5694
|
+
const existingIndex = previousDictionary[target.name];
|
|
5695
|
+
if (existingIndex !== void 0 && !replace) {
|
|
5696
|
+
throw new Error(`Morph target "${target.name}" already exists on mesh "${target.meshName}". Pass replace: true to overwrite it.`);
|
|
5697
|
+
}
|
|
5698
|
+
const geometry = forceGeometryReplacement ? sourceGeometry.clone() : sourceGeometry;
|
|
5699
|
+
const dictionary = { ...previousDictionary };
|
|
5700
|
+
const usedIndices = Object.values(dictionary).filter(Number.isInteger);
|
|
5701
|
+
const existingAttributeTargetCount = Math.max(
|
|
5702
|
+
0,
|
|
5703
|
+
...Object.values(geometry.morphAttributes).map((attributes) => attributes?.length ?? 0)
|
|
5704
|
+
);
|
|
5705
|
+
const nextIndex = Math.max(existingAttributeTargetCount, usedIndices.length ? Math.max(...usedIndices) + 1 : 0);
|
|
5706
|
+
const index = existingIndex ?? nextIndex;
|
|
5707
|
+
dictionary[target.name] = index;
|
|
5708
|
+
this.setMorphAttributeAtIndex(geometry, "position", target.position, position.itemSize, position.count, index, target.name);
|
|
5709
|
+
const normal = geometry.getAttribute("normal");
|
|
5710
|
+
if (target.normal) {
|
|
5711
|
+
this.setMorphAttributeAtIndex(geometry, "normal", target.normal, normal?.itemSize ?? 3, position.count, index, target.name);
|
|
5712
|
+
} else {
|
|
5713
|
+
this.setZeroMorphAttributeAtIndex(geometry, "normal", normal?.itemSize ?? 3, position.count, index, target.name);
|
|
5714
|
+
}
|
|
5715
|
+
const tangent = geometry.getAttribute("tangent");
|
|
5716
|
+
if (target.tangent) {
|
|
5717
|
+
this.setMorphAttributeAtIndex(geometry, "tangent", target.tangent, tangent?.itemSize ?? 4, position.count, index, target.name);
|
|
5718
|
+
} else {
|
|
5719
|
+
this.setZeroMorphAttributeAtIndex(geometry, "tangent", tangent?.itemSize ?? 4, position.count, index, target.name);
|
|
5720
|
+
}
|
|
5721
|
+
const color = geometry.getAttribute("color");
|
|
5722
|
+
const existingColorMorph = geometry.morphAttributes.color?.find(Boolean);
|
|
5723
|
+
this.setZeroMorphAttributeAtIndex(
|
|
5724
|
+
geometry,
|
|
5725
|
+
"color",
|
|
5726
|
+
color?.itemSize ?? existingColorMorph?.itemSize ?? 3,
|
|
5727
|
+
position.count,
|
|
5728
|
+
index,
|
|
5729
|
+
target.name
|
|
5730
|
+
);
|
|
5731
|
+
geometry.morphTargetsRelative = target.relative !== false;
|
|
5732
|
+
geometry.morphTargetDictionary = dictionary;
|
|
5733
|
+
if (forceGeometryReplacement) {
|
|
5734
|
+
mesh.geometry = geometry;
|
|
5735
|
+
sourceGeometry.dispose();
|
|
5736
|
+
}
|
|
5737
|
+
const influenceLength = Math.max(previousInfluences.length, index + 1);
|
|
5738
|
+
const influences = previousInfluences.slice(0, influenceLength);
|
|
5739
|
+
while (influences.length < influenceLength) {
|
|
5740
|
+
influences.push(0);
|
|
5741
|
+
}
|
|
5742
|
+
if (resetInfluence) {
|
|
5743
|
+
influences[index] = 0;
|
|
5744
|
+
}
|
|
5745
|
+
mesh.morphTargetDictionary = dictionary;
|
|
5746
|
+
mesh.morphTargetInfluences = influences;
|
|
5747
|
+
this.addRuntimeMorphMesh(mesh);
|
|
5748
|
+
if (!this.config.morphToMesh?.face?.length) {
|
|
5749
|
+
this.config.morphToMesh = {
|
|
5750
|
+
...this.config.morphToMesh,
|
|
5751
|
+
face: [mesh.name]
|
|
5752
|
+
};
|
|
5753
|
+
}
|
|
5754
|
+
return index;
|
|
5755
|
+
}
|
|
5756
|
+
requireNamedMesh(meshName) {
|
|
5757
|
+
const mesh = this.meshByName.get(meshName);
|
|
5758
|
+
if (mesh) return mesh;
|
|
5759
|
+
if (this.model) {
|
|
5760
|
+
let found = null;
|
|
5761
|
+
this.model.traverse((obj) => {
|
|
5762
|
+
if (!found && obj.isMesh && obj.name === meshName) {
|
|
5763
|
+
found = obj;
|
|
5764
|
+
}
|
|
5765
|
+
});
|
|
5766
|
+
if (found) {
|
|
5767
|
+
this.meshByName.set(meshName, found);
|
|
5768
|
+
return found;
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5771
|
+
throw new Error(`Mesh "${meshName}" was not found in the current model.`);
|
|
5772
|
+
}
|
|
5773
|
+
getMeshMorphDictionary(mesh) {
|
|
5774
|
+
const meshDictionary = mesh.morphTargetDictionary;
|
|
5775
|
+
const geometryDictionary = mesh.geometry.morphTargetDictionary;
|
|
5776
|
+
const dictionary = meshDictionary || geometryDictionary || {};
|
|
5777
|
+
mesh.morphTargetDictionary = dictionary;
|
|
5778
|
+
mesh.geometry.morphTargetDictionary = dictionary;
|
|
5779
|
+
return dictionary;
|
|
5780
|
+
}
|
|
5781
|
+
setMorphAttributeAtIndex(geometry, semantic, data, itemSize, vertexCount, index, name) {
|
|
5782
|
+
const expectedLength = vertexCount * itemSize;
|
|
5783
|
+
if (data.length !== expectedLength) {
|
|
5784
|
+
throw new Error(
|
|
5785
|
+
`Morph target "${name}" ${semantic} data has ${data.length} values; expected ${expectedLength} (${vertexCount} vertices * itemSize ${itemSize}).`
|
|
5786
|
+
);
|
|
5787
|
+
}
|
|
5788
|
+
const attributes = geometry.morphAttributes[semantic] ? [...geometry.morphAttributes[semantic]] : [];
|
|
5789
|
+
while (attributes.length < index) {
|
|
5790
|
+
const empty = new THREE2.BufferAttribute(new Float32Array(expectedLength), itemSize);
|
|
5791
|
+
empty.name = `morph_${attributes.length}`;
|
|
5792
|
+
attributes.push(empty);
|
|
5793
|
+
}
|
|
5794
|
+
const values = data instanceof Float32Array ? new Float32Array(data) : Float32Array.from(data);
|
|
5795
|
+
const attribute = new THREE2.BufferAttribute(values, itemSize);
|
|
5796
|
+
attribute.name = name;
|
|
5797
|
+
attributes[index] = attribute;
|
|
5798
|
+
geometry.morphAttributes[semantic] = attributes;
|
|
5799
|
+
}
|
|
5800
|
+
setZeroMorphAttributeAtIndex(geometry, semantic, itemSize, vertexCount, index, name) {
|
|
5801
|
+
if (!geometry.morphAttributes[semantic]?.length) return;
|
|
5802
|
+
const expectedLength = vertexCount * itemSize;
|
|
5803
|
+
const attributes = [...geometry.morphAttributes[semantic]];
|
|
5804
|
+
while (attributes.length < index) {
|
|
5805
|
+
const empty2 = new THREE2.BufferAttribute(new Float32Array(expectedLength), itemSize);
|
|
5806
|
+
empty2.name = `morph_${attributes.length}`;
|
|
5807
|
+
attributes.push(empty2);
|
|
5808
|
+
}
|
|
5809
|
+
const empty = new THREE2.BufferAttribute(new Float32Array(expectedLength), itemSize);
|
|
5810
|
+
empty.name = name;
|
|
5811
|
+
attributes[index] = empty;
|
|
5812
|
+
geometry.morphAttributes[semantic] = attributes;
|
|
5813
|
+
}
|
|
5814
|
+
addRuntimeMorphMesh(mesh) {
|
|
5815
|
+
const key = mesh.name || mesh.uuid;
|
|
5816
|
+
const exists = this.meshes.some((candidate) => (candidate.name || candidate.uuid) === key);
|
|
5817
|
+
if (!exists) {
|
|
5818
|
+
this.meshes.push(mesh);
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5631
5821
|
getMorphKeyCacheKey(key, meshNames) {
|
|
5632
5822
|
return meshNames?.length ? `key:${key}@${meshNames.join(",")}` : `key:${key}`;
|
|
5633
5823
|
}
|
|
@@ -6222,22 +6412,32 @@ function mergeRegionsByName(base, override) {
|
|
|
6222
6412
|
}
|
|
6223
6413
|
return Array.from(merged.values());
|
|
6224
6414
|
}
|
|
6415
|
+
function getAnnotationRegions(value) {
|
|
6416
|
+
return Array.isArray(value) ? value : void 0;
|
|
6417
|
+
}
|
|
6418
|
+
function getLegacyNestedOverrides(config) {
|
|
6419
|
+
return isPlainObject2(config.profile) ? config.profile : {};
|
|
6420
|
+
}
|
|
6421
|
+
function getLegacyRuntimeRegions(config) {
|
|
6422
|
+
return Array.isArray(config.regions) && config.regions.length > 0 ? config.regions : void 0;
|
|
6423
|
+
}
|
|
6424
|
+
function getCanonicalAnnotationOverrides(config) {
|
|
6425
|
+
return mergeRegionsByName(
|
|
6426
|
+
getAnnotationRegions(getLegacyNestedOverrides(config).annotationRegions),
|
|
6427
|
+
getAnnotationRegions(config.annotationRegions)
|
|
6428
|
+
);
|
|
6429
|
+
}
|
|
6225
6430
|
function extractProfileOverrides(config) {
|
|
6226
6431
|
const topLevelConfig = config;
|
|
6227
|
-
const legacyNestedOverrides =
|
|
6432
|
+
const legacyNestedOverrides = getLegacyNestedOverrides(config);
|
|
6433
|
+
const canonicalAnnotationOverrides = getCanonicalAnnotationOverrides(config);
|
|
6434
|
+
const legacyRuntimeRegions = getLegacyRuntimeRegions(config);
|
|
6435
|
+
const annotationOverrides = canonicalAnnotationOverrides ?? (legacyRuntimeRegions ? legacyRuntimeRegions.map((region) => cloneRegion(region)) : void 0);
|
|
6228
6436
|
const overrides = {};
|
|
6229
6437
|
for (const key of PROFILE_OVERRIDE_KEYS) {
|
|
6230
6438
|
if (key === "annotationRegions") {
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
const legacyRegionFallback = Array.isArray(config.regions) && config.regions.length > 0 ? config.regions : void 0;
|
|
6234
|
-
const legacyProfileRegions = mergeRegionsByName(legacyRegionFallback, legacyAnnotationRegions);
|
|
6235
|
-
const regions = mergeRegionsByName(
|
|
6236
|
-
legacyProfileRegions,
|
|
6237
|
-
topLevelAnnotationRegions
|
|
6238
|
-
);
|
|
6239
|
-
if (regions) {
|
|
6240
|
-
overrides.annotationRegions = regions.map((region) => cloneRegion(region));
|
|
6439
|
+
if (annotationOverrides) {
|
|
6440
|
+
overrides.annotationRegions = annotationOverrides.map((region) => cloneRegion(region));
|
|
6241
6441
|
}
|
|
6242
6442
|
continue;
|
|
6243
6443
|
}
|
|
@@ -6250,13 +6450,6 @@ function extractProfileOverrides(config) {
|
|
|
6250
6450
|
}
|
|
6251
6451
|
return overrides;
|
|
6252
6452
|
}
|
|
6253
|
-
function hasCanonicalAnnotationRegionOverrides(config) {
|
|
6254
|
-
const topLevelConfig = config;
|
|
6255
|
-
if (Array.isArray(topLevelConfig.annotationRegions)) {
|
|
6256
|
-
return true;
|
|
6257
|
-
}
|
|
6258
|
-
return isPlainObject2(config.profile) && Array.isArray(config.profile.annotationRegions);
|
|
6259
|
-
}
|
|
6260
6453
|
function applyCharacterProfileToPreset(config) {
|
|
6261
6454
|
const presetType = config.auPresetType;
|
|
6262
6455
|
if (!presetType) {
|
|
@@ -6288,24 +6481,34 @@ function extendCharacterConfigWithPreset(config) {
|
|
|
6288
6481
|
if (!presetType || presetType === "custom") {
|
|
6289
6482
|
return config;
|
|
6290
6483
|
}
|
|
6484
|
+
const canonicalAnnotationOverrides = getCanonicalAnnotationOverrides(config);
|
|
6485
|
+
const legacyRuntimeRegions = getLegacyRuntimeRegions(config);
|
|
6291
6486
|
const profileOverrides = extractProfileOverrides(config);
|
|
6292
6487
|
const extendedPresetProfile = applyCharacterProfileToPreset(config);
|
|
6293
6488
|
if (!extendedPresetProfile) {
|
|
6294
6489
|
return config;
|
|
6295
6490
|
}
|
|
6296
|
-
const
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6491
|
+
const presetRegionNames = new Set(
|
|
6492
|
+
(getPreset(presetType).annotationRegions ?? []).map((region) => region.name)
|
|
6493
|
+
);
|
|
6494
|
+
const extendedAnnotationRegions = normalizeRegionTree(
|
|
6495
|
+
extendedPresetProfile.annotationRegions,
|
|
6496
|
+
profileOverrides.disabledRegions
|
|
6497
|
+
);
|
|
6498
|
+
const extendedRegionNames = new Set((extendedAnnotationRegions ?? []).map((region) => region.name));
|
|
6499
|
+
const legacyExtraRegions = canonicalAnnotationOverrides && legacyRuntimeRegions ? legacyRuntimeRegions.filter((region) => !presetRegionNames.has(region.name) && !extendedRegionNames.has(region.name)).map((region) => cloneRegion(region)) : void 0;
|
|
6500
|
+
const mergedRegions = normalizeRegionTree(
|
|
6501
|
+
mergeRegionsByName(extendedAnnotationRegions, legacyExtraRegions),
|
|
6300
6502
|
profileOverrides.disabledRegions
|
|
6301
6503
|
);
|
|
6302
6504
|
const extendedRegions = orderExtendedRegions(
|
|
6303
|
-
|
|
6304
|
-
[
|
|
6505
|
+
mergedRegions,
|
|
6506
|
+
canonicalAnnotationOverrides ? [extendedAnnotationRegions, legacyExtraRegions] : [legacyRuntimeRegions, extendedAnnotationRegions]
|
|
6305
6507
|
);
|
|
6306
6508
|
return {
|
|
6307
6509
|
...config,
|
|
6308
6510
|
...extendedPresetProfile,
|
|
6511
|
+
annotationRegions: extendedRegions ?? extendedAnnotationRegions,
|
|
6309
6512
|
regions: extendedRegions ?? config.regions
|
|
6310
6513
|
};
|
|
6311
6514
|
}
|
|
@@ -7465,7 +7668,7 @@ function extractMorphs(meshes) {
|
|
|
7465
7668
|
for (const mesh of meshes) {
|
|
7466
7669
|
const geo = mesh.geometry;
|
|
7467
7670
|
if (!geo.morphAttributes) continue;
|
|
7468
|
-
const dict = geo.morphTargetDictionary;
|
|
7671
|
+
const dict = mesh.morphTargetDictionary || geo.morphTargetDictionary;
|
|
7469
7672
|
if (dict) {
|
|
7470
7673
|
for (const [name, index] of Object.entries(dict)) {
|
|
7471
7674
|
morphs.push({
|