@lovelace_lol/loom3 1.0.12 → 1.0.14
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 +113 -42
- package/dist/index.cjs +73 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -39
- package/dist/index.d.ts +6 -39
- package/dist/index.js +74 -73
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https:
|
|
|
27
27
|
13. [Playback & State Control](#13-playback--state-control)
|
|
28
28
|
14. [Hair Physics](#14-hair-physics)
|
|
29
29
|
15. [Baked Animations](#15-baked-animations)
|
|
30
|
+
16. [API Reference](#16-api-reference)
|
|
30
31
|
|
|
31
32
|
---
|
|
32
33
|
|
|
@@ -81,7 +82,7 @@ loader.load('/character.glb', (gltf) => {
|
|
|
81
82
|
// This drives all transitions and animations
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
If you’re implementing a custom renderer, target the `
|
|
85
|
+
If you’re implementing a custom renderer, target the `Loom3` interface exported from `@lovelace_lol/loom3` (legacy alias: `LoomLarge`).
|
|
85
86
|
|
|
86
87
|
### Quick start examples
|
|
87
88
|
|
|
@@ -162,26 +163,31 @@ import { CC4_PRESET } from '@lovelace_lol/loom3';
|
|
|
162
163
|
},
|
|
163
164
|
|
|
164
165
|
boneNodes: {
|
|
165
|
-
// Logical bone name →
|
|
166
|
-
'HEAD': '
|
|
167
|
-
'JAW': '
|
|
168
|
-
'EYE_L': '
|
|
169
|
-
'EYE_R': '
|
|
170
|
-
'TONGUE': '
|
|
166
|
+
// Logical bone name → base node name used with bonePrefix
|
|
167
|
+
'HEAD': 'Head',
|
|
168
|
+
'JAW': 'JawRoot',
|
|
169
|
+
'EYE_L': 'L_Eye',
|
|
170
|
+
'EYE_R': 'R_Eye',
|
|
171
|
+
'TONGUE': 'Tongue01',
|
|
171
172
|
},
|
|
172
173
|
|
|
174
|
+
bonePrefix: 'CC_Base_',
|
|
175
|
+
suffixPattern: '_\\d+$|\\.\\d+$',
|
|
176
|
+
|
|
173
177
|
visemeKeys: [
|
|
174
178
|
// 15 viseme morph names for lip-sync
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
179
|
+
'EE', 'Ah', 'Oh', 'OO', 'I',
|
|
180
|
+
'U', 'W', 'L', 'F_V', 'Th',
|
|
181
|
+
'S_Z', 'B_M_P', 'K_G_H_NG', 'AE', 'R'
|
|
178
182
|
],
|
|
179
183
|
|
|
180
184
|
morphToMesh: {
|
|
181
185
|
// Routes morph categories to specific meshes
|
|
182
186
|
'face': ['CC_Base_Body'],
|
|
187
|
+
'viseme': ['CC_Base_Body', 'CC_Base_Body_1'],
|
|
183
188
|
'tongue': ['CC_Base_Tongue'],
|
|
184
|
-
'eye': ['
|
|
189
|
+
'eye': ['CC_Base_EyeOcclusion_1', 'CC_Base_EyeOcclusion_2'],
|
|
190
|
+
'hair': ['Side_part_wavy_1', 'Side_part_wavy_2'],
|
|
185
191
|
},
|
|
186
192
|
|
|
187
193
|
auMixDefaults: {
|
|
@@ -203,6 +209,25 @@ import { CC4_PRESET } from '@lovelace_lol/loom3';
|
|
|
203
209
|
}
|
|
204
210
|
```
|
|
205
211
|
|
|
212
|
+
### Name resolution and profile fields
|
|
213
|
+
|
|
214
|
+
The runtime resolves bone nodes by composing `bonePrefix + boneNodes[key] + boneSuffix`, then falling back to suffix-pattern matching when a model uses numbered exports such as `_01` or `.001`. The same prefix/suffix rules are used by validation and correction helpers, which is why `CC4_PRESET` can keep base bone names like `Head` and `JawRoot` instead of repeating the full node names everywhere.
|
|
215
|
+
|
|
216
|
+
For region and marker configs, `resolveBoneName()` treats any mapped bone name that already contains `_` or `.` as a fully qualified name and skips prefix/suffix composition.
|
|
217
|
+
|
|
218
|
+
Two caveats are worth calling out:
|
|
219
|
+
- `morphPrefix` and `morphSuffix` are part of `Profile`, but morph playback still resolves exact morph keys on the targeted meshes today. They are already used by validation and correction helpers.
|
|
220
|
+
- `leftMorphSuffixes` and `rightMorphSuffixes` are profile metadata for laterality detection in tooling, not core runtime behavior.
|
|
221
|
+
|
|
222
|
+
Other `Profile` fields that are easy to miss:
|
|
223
|
+
- `morphToMesh` routes categories such as `face`, `viseme`, `eye`, `tongue`, and `hair` to specific mesh names.
|
|
224
|
+
- `eyeMeshNodes` provides fallback eye nodes when a rig uses meshes instead of bones for eye control.
|
|
225
|
+
- `auMixDefaults` sets the default morph/bone blend weight per AU.
|
|
226
|
+
- `compositeRotations` defines the per-node pitch/yaw/roll axis layout used by the composite rotation system.
|
|
227
|
+
- `continuumPairs` and `continuumLabels` describe bidirectional AU pairs and their UI labels.
|
|
228
|
+
- `annotationRegions` defines the regions used by the marker and camera tooling.
|
|
229
|
+
- `hairPhysics` stores the mixer-driven hair defaults, including direction signs and morph target mappings.
|
|
230
|
+
|
|
206
231
|
### Passing a preset to Loom3
|
|
207
232
|
|
|
208
233
|
```typescript
|
|
@@ -319,6 +344,7 @@ Loom3 includes validation and analysis helpers so you can verify presets against
|
|
|
319
344
|
|
|
320
345
|
```typescript
|
|
321
346
|
import {
|
|
347
|
+
extractFromGLTF,
|
|
322
348
|
extractModelData,
|
|
323
349
|
analyzeModel,
|
|
324
350
|
validateMappings,
|
|
@@ -327,16 +353,41 @@ import {
|
|
|
327
353
|
} from '@lovelace_lol/loom3';
|
|
328
354
|
|
|
329
355
|
const preset = resolvePreset('cc4');
|
|
330
|
-
const modelData = extractModelData(
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
const
|
|
356
|
+
const modelData = extractModelData(model, meshes, animations);
|
|
357
|
+
const gltfData = extractFromGLTF(gltf); // Same ModelData shape, one-step GLTF wrapper
|
|
358
|
+
|
|
359
|
+
const analysis = await analyzeModel({
|
|
360
|
+
source: { type: 'gltf', gltf },
|
|
361
|
+
preset,
|
|
362
|
+
suggestCorrections: true,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Validate against lower-level mesh + skeleton inputs when you already have them
|
|
366
|
+
const validation = validateMappings(meshes, skeleton, preset, { suggestCorrections: true });
|
|
367
|
+
const corrections = generateMappingCorrections(meshes, skeleton, preset, { useResolvedNames: true });
|
|
334
368
|
```
|
|
335
369
|
|
|
370
|
+
If you already have a `ModelData` bundle, `analyzeModel()` is the higher-level path; `validateMappings()` and `generateMappingCorrections()` are intentionally lower-level mesh/skeleton helpers.
|
|
371
|
+
|
|
336
372
|
Use these helpers to:
|
|
337
|
-
-
|
|
338
|
-
-
|
|
339
|
-
-
|
|
373
|
+
- Extract raw model facts with `extractModelData(model, meshes?, animations?)` or `extractFromGLTF(gltf)`
|
|
374
|
+
- Validate a preset against mesh/skeleton data with `validateMappings(meshes, skeleton, preset, options)`
|
|
375
|
+
- Generate best-effort fixes with `generateMappingCorrections(meshes, skeleton, preset, options)`
|
|
376
|
+
- Run a single end-to-end pass with `analyzeModel({ source, preset, suggestCorrections })`
|
|
377
|
+
|
|
378
|
+
`validateMappings()` returns a `ValidationResult` with:
|
|
379
|
+
- `valid` and `score`
|
|
380
|
+
- `missingMorphs`, `missingBones`, `foundMorphs`, `foundBones`
|
|
381
|
+
- `missingMeshes`, `foundMeshes`, `unmappedMorphs`, `unmappedBones`, `unmappedMeshes`
|
|
382
|
+
- `warnings`
|
|
383
|
+
- optional `suggestedConfig`, `corrections`, and `unresolved` when suggestion mode is enabled
|
|
384
|
+
|
|
385
|
+
`generateMappingCorrections()` returns:
|
|
386
|
+
- `correctedConfig`
|
|
387
|
+
- `corrections`
|
|
388
|
+
- `unresolved`
|
|
389
|
+
|
|
390
|
+
`analyzeModel()` returns a `ModelAnalysisReport` containing the extracted model data, optional validation results, animation summary, `overallScore`, and a plain-language `summary`.
|
|
340
391
|
|
|
341
392
|
### Controlling mesh visibility
|
|
342
393
|
|
|
@@ -387,16 +438,16 @@ This is especially useful for:
|
|
|
387
438
|
|
|
388
439
|
## 4. Extending & Custom Presets
|
|
389
440
|
|
|
390
|
-

|
|
391
442
|
|
|
392
443
|
### Extending an existing preset
|
|
393
444
|
|
|
394
|
-
Use `
|
|
445
|
+
Use `resolveProfile` to override specific mappings while keeping the rest:
|
|
395
446
|
|
|
396
447
|
```typescript
|
|
397
|
-
import { CC4_PRESET,
|
|
448
|
+
import { CC4_PRESET, resolveProfile } from '@lovelace_lol/loom3';
|
|
398
449
|
|
|
399
|
-
const MY_PRESET =
|
|
450
|
+
const MY_PRESET = resolveProfile(CC4_PRESET, {
|
|
400
451
|
|
|
401
452
|
// Override AU12 (smile) with custom morph names
|
|
402
453
|
auToMorphs: {
|
|
@@ -420,7 +471,7 @@ const loom = new Loom3({ profile: MY_PRESET });
|
|
|
420
471
|
### Creating a preset from scratch
|
|
421
472
|
|
|
422
473
|
```typescript
|
|
423
|
-
import { Profile } from '@lovelace_lol/loom3';
|
|
474
|
+
import type { Profile } from '@lovelace_lol/loom3';
|
|
424
475
|
|
|
425
476
|
const CUSTOM_PRESET: Profile = {
|
|
426
477
|
auToMorphs: {
|
|
@@ -1075,19 +1126,21 @@ Visemes are mouth shapes used for lip-sync. Loom3 includes 15 visemes with autom
|
|
|
1075
1126
|
|
|
1076
1127
|
### The 15 visemes
|
|
1077
1128
|
|
|
1129
|
+
The `VISEME_KEYS` export uses unprefixed keys in this order.
|
|
1130
|
+
|
|
1078
1131
|
| Index | Key | Phoneme Example |
|
|
1079
1132
|
|-------|-----|-----------------|
|
|
1080
1133
|
| 0 | EE | "b**ee**" |
|
|
1081
|
-
| 1 |
|
|
1082
|
-
| 2 |
|
|
1083
|
-
| 3 |
|
|
1084
|
-
| 4 |
|
|
1085
|
-
| 5 |
|
|
1086
|
-
| 6 |
|
|
1087
|
-
| 7 |
|
|
1134
|
+
| 1 | Ah | "f**a**ther" |
|
|
1135
|
+
| 2 | Oh | "g**o**" |
|
|
1136
|
+
| 3 | OO | "t**oo**" |
|
|
1137
|
+
| 4 | I | "s**i**t" |
|
|
1138
|
+
| 5 | U | "fl**u**te" |
|
|
1139
|
+
| 6 | W | "**w**e" |
|
|
1140
|
+
| 7 | L | "**l**ip" |
|
|
1088
1141
|
| 8 | F_V | "**f**un, **v**an" |
|
|
1089
|
-
| 9 |
|
|
1090
|
-
| 10 |
|
|
1142
|
+
| 9 | Th | "**th**ink" |
|
|
1143
|
+
| 10 | S_Z | "**s**un, **z**oo" |
|
|
1091
1144
|
| 11 | B_M_P | "**b**at, **m**an, **p**op" |
|
|
1092
1145
|
| 12 | K_G_H_NG | "**k**ite, **g**o, **h**at, si**ng**" |
|
|
1093
1146
|
| 13 | AE | "c**a**t" |
|
|
@@ -1117,16 +1170,7 @@ loom.transitionViseme(3, 1.0, 80, 0);
|
|
|
1117
1170
|
|
|
1118
1171
|
### Automatic jaw coupling
|
|
1119
1172
|
|
|
1120
|
-
Each viseme has a predefined jaw opening amount. When you set a viseme, the jaw automatically opens proportionally:
|
|
1121
|
-
|
|
1122
|
-
| Viseme | Jaw Amount |
|
|
1123
|
-
|--------|------------|
|
|
1124
|
-
| EE | 0.15 |
|
|
1125
|
-
| Ah | 0.70 |
|
|
1126
|
-
| Oh | 0.50 |
|
|
1127
|
-
| B_M_P | 0.20 |
|
|
1128
|
-
|
|
1129
|
-
The `jawScale` parameter multiplies this amount:
|
|
1173
|
+
Each viseme has a predefined jaw opening amount in the preset. When you set a viseme, the jaw automatically opens proportionally, and the `jawScale` parameter multiplies that amount:
|
|
1130
1174
|
- `jawScale = 1.0`: Normal jaw opening
|
|
1131
1175
|
- `jawScale = 0.5`: Half jaw opening
|
|
1132
1176
|
- `jawScale = 0`: No jaw movement (viseme only)
|
|
@@ -1653,6 +1697,33 @@ loom.transitionAU(45, 1.0, 100); // Blink
|
|
|
1653
1697
|
|
|
1654
1698
|
---
|
|
1655
1699
|
|
|
1700
|
+
## 16. API Reference
|
|
1701
|
+
|
|
1702
|
+
This is a compact reference for the public surface exported by `@lovelace_lol/loom3`.
|
|
1703
|
+
|
|
1704
|
+
### Engine surface
|
|
1705
|
+
|
|
1706
|
+
`Loom3` (legacy alias: `LoomLarge`) covers:
|
|
1707
|
+
- Initialization and lifecycle: `onReady()`, `update()`, `start()`, `stop()`, `dispose()`
|
|
1708
|
+
- AU, morph, viseme, and continuum control: `setAU()`, `transitionAU()`, `setMorph()`, `transitionMorph()`, `setViseme()`, `transitionViseme()`, `setContinuum()`, `transitionContinuum()`
|
|
1709
|
+
- Preset state: `setProfile()`, `getProfile()`
|
|
1710
|
+
- Validation and analysis helpers: `validateMappings()`, `generateMappingCorrections()`, `extractModelData()`, `extractFromGLTF()`, `analyzeModel()`
|
|
1711
|
+
- Mixer and baked animation helpers: `loadAnimationClips()`, `getAnimationClips()`, `playAnimation()`, `playClip()`, `playSnippet()`, `snippetToClip()`
|
|
1712
|
+
- Hair physics helpers: `setHairPhysicsEnabled()`, `setHairPhysicsConfig()`, `validateHairMorphTargets()`
|
|
1713
|
+
|
|
1714
|
+
### Core types and helpers
|
|
1715
|
+
|
|
1716
|
+
- `Profile` groups the mapping tables, name-resolution fields, `morphToMesh`, `visemeKeys`, `auMixDefaults`, `compositeRotations`, `continuumPairs`, `continuumLabels`, `eyeMeshNodes`, `annotationRegions`, and `hairPhysics`.
|
|
1717
|
+
- `TransitionHandle` exposes `promise`, `pause()`, `resume()`, and `cancel()`.
|
|
1718
|
+
- `ClipHandle` exposes `clipName`, `play()`, `stop()`, `pause()`, `resume()`, optional live-update setters, `getTime()`, `getDuration()`, and `finished`.
|
|
1719
|
+
- `collectMorphMeshes()` gathers meshes that already have morph targets.
|
|
1720
|
+
- `resolvePreset()` picks a built-in preset by name.
|
|
1721
|
+
- `resolvePresetWithOverrides()` resolves a preset and merges partial overrides.
|
|
1722
|
+
- `resolveProfile()` merges a base preset with a partial profile override.
|
|
1723
|
+
- `isMixedAU()` checks whether an AU has both morph and bone support in the CC4 preset.
|
|
1724
|
+
|
|
1725
|
+
---
|
|
1726
|
+
|
|
1656
1727
|
## Resources
|
|
1657
1728
|
|
|
1658
1729
|

|
package/dist/index.cjs
CHANGED
|
@@ -192,6 +192,17 @@ var BakedAnimationController = class {
|
|
|
192
192
|
__publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
|
|
193
193
|
this.host = host;
|
|
194
194
|
}
|
|
195
|
+
getActionId(action) {
|
|
196
|
+
if (!action) return void 0;
|
|
197
|
+
return this.actionIds.get(action) ?? action.__actionId;
|
|
198
|
+
}
|
|
199
|
+
setActionId(action, clipName) {
|
|
200
|
+
const actionId = makeActionId();
|
|
201
|
+
this.actionIds.set(action, actionId);
|
|
202
|
+
this.actionIdToClip.set(actionId, clipName);
|
|
203
|
+
action.__actionId = actionId;
|
|
204
|
+
return actionId;
|
|
205
|
+
}
|
|
195
206
|
getMeshNamesForAU(auId, config, explicitMeshNames) {
|
|
196
207
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
197
208
|
return explicitMeshNames;
|
|
@@ -696,19 +707,13 @@ var BakedAnimationController = class {
|
|
|
696
707
|
mixerWeight
|
|
697
708
|
} = options || {};
|
|
698
709
|
let action = this.clipActions.get(clip.name);
|
|
699
|
-
let actionId =
|
|
710
|
+
let actionId = this.getActionId(action);
|
|
700
711
|
if (action && !actionId) {
|
|
701
|
-
actionId =
|
|
702
|
-
this.actionIds.set(action, actionId);
|
|
703
|
-
this.actionIdToClip.set(actionId, clip.name);
|
|
704
|
-
action.__actionId = actionId;
|
|
712
|
+
actionId = this.setActionId(action, clip.name);
|
|
705
713
|
}
|
|
706
714
|
if (!action) {
|
|
707
715
|
action = this.animationMixer.clipAction(clip);
|
|
708
|
-
actionId =
|
|
709
|
-
this.actionIds.set(action, actionId);
|
|
710
|
-
this.actionIdToClip.set(actionId, clip.name);
|
|
711
|
-
action.__actionId = actionId;
|
|
716
|
+
actionId = this.setActionId(action, clip.name);
|
|
712
717
|
}
|
|
713
718
|
const existingClip = this.animationClips.find((c) => c.name === clip.name);
|
|
714
719
|
if (!existingClip) {
|
|
@@ -861,10 +866,10 @@ var BakedAnimationController = class {
|
|
|
861
866
|
const debugSnapshot = () => ({
|
|
862
867
|
target: name,
|
|
863
868
|
params,
|
|
864
|
-
clipActions: Array.from(this.clipActions.entries()).map(([k, a]) => ({ name: k, actionId: this.
|
|
865
|
-
animationActions: Array.from(this.animationActions.entries()).map(([k, a]) => ({ name: k, actionId: this.
|
|
869
|
+
clipActions: Array.from(this.clipActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
870
|
+
animationActions: Array.from(this.animationActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
866
871
|
clipHandles: Array.from(this.clipHandles.entries()).map(([k, h]) => ({ name: k, actionId: h.actionId })),
|
|
867
|
-
mixerActions: (this.animationMixer?._actions || []).map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.
|
|
872
|
+
mixerActions: (this.animationMixer?._actions || []).map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.getActionId(a) }))
|
|
868
873
|
});
|
|
869
874
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
870
875
|
const apply = (action) => {
|
|
@@ -1498,53 +1503,53 @@ var BONE_AU_TO_BINDINGS = {
|
|
|
1498
1503
|
42: [{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20 }]
|
|
1499
1504
|
};
|
|
1500
1505
|
var VISEME_KEYS = [
|
|
1501
|
-
"
|
|
1506
|
+
"AE",
|
|
1502
1507
|
"Ah",
|
|
1503
|
-
"Oh",
|
|
1504
|
-
"OO",
|
|
1505
|
-
"I",
|
|
1506
|
-
"U",
|
|
1507
|
-
"W",
|
|
1508
|
-
"L",
|
|
1509
|
-
"F_V",
|
|
1510
|
-
"Th",
|
|
1511
|
-
"S_Z",
|
|
1512
1508
|
"B_M_P",
|
|
1509
|
+
"Ch_J",
|
|
1510
|
+
"EE",
|
|
1511
|
+
"Er",
|
|
1512
|
+
"F_V",
|
|
1513
|
+
"Ih",
|
|
1513
1514
|
"K_G_H_NG",
|
|
1514
|
-
"
|
|
1515
|
-
"R"
|
|
1515
|
+
"Oh",
|
|
1516
|
+
"R",
|
|
1517
|
+
"S_Z",
|
|
1518
|
+
"T_L_D_N",
|
|
1519
|
+
"Th",
|
|
1520
|
+
"W_OO"
|
|
1516
1521
|
];
|
|
1517
1522
|
var VISEME_JAW_AMOUNTS = [
|
|
1518
|
-
0.
|
|
1519
|
-
// 0:
|
|
1523
|
+
0.75,
|
|
1524
|
+
// 0: AE
|
|
1520
1525
|
0.8,
|
|
1521
1526
|
// 1: Ah
|
|
1522
|
-
0
|
|
1523
|
-
// 2:
|
|
1524
|
-
0.5,
|
|
1525
|
-
// 3: OO
|
|
1526
|
-
0.2,
|
|
1527
|
-
// 4: I
|
|
1528
|
-
0.5,
|
|
1529
|
-
// 5: U
|
|
1530
|
-
0.4,
|
|
1531
|
-
// 6: W
|
|
1527
|
+
0,
|
|
1528
|
+
// 2: B_M_P
|
|
1532
1529
|
0.3,
|
|
1533
|
-
//
|
|
1534
|
-
0.
|
|
1535
|
-
//
|
|
1536
|
-
0.
|
|
1537
|
-
//
|
|
1530
|
+
// 3: Ch_J
|
|
1531
|
+
0.2,
|
|
1532
|
+
// 4: EE
|
|
1533
|
+
0.35,
|
|
1534
|
+
// 5: Er
|
|
1538
1535
|
0.1,
|
|
1539
|
-
//
|
|
1540
|
-
0,
|
|
1541
|
-
//
|
|
1536
|
+
// 6: F_V
|
|
1537
|
+
0.2,
|
|
1538
|
+
// 7: Ih
|
|
1542
1539
|
0.35,
|
|
1543
|
-
//
|
|
1544
|
-
0.
|
|
1545
|
-
//
|
|
1546
|
-
0.35
|
|
1547
|
-
//
|
|
1540
|
+
// 8: K_G_H_NG
|
|
1541
|
+
0.6,
|
|
1542
|
+
// 9: Oh
|
|
1543
|
+
0.35,
|
|
1544
|
+
// 10: R
|
|
1545
|
+
0.1,
|
|
1546
|
+
// 11: S_Z
|
|
1547
|
+
0.3,
|
|
1548
|
+
// 12: T_L_D_N
|
|
1549
|
+
0.15,
|
|
1550
|
+
// 13: Th
|
|
1551
|
+
0.5
|
|
1552
|
+
// 14: W_OO
|
|
1548
1553
|
];
|
|
1549
1554
|
var isMixedAU = (id) => {
|
|
1550
1555
|
const morphs = AU_TO_MORPHS[id];
|
|
@@ -2064,7 +2069,7 @@ var HairPhysicsController = class {
|
|
|
2064
2069
|
this.clearRegisteredHairObjects();
|
|
2065
2070
|
const result = [];
|
|
2066
2071
|
for (const obj of objects) {
|
|
2067
|
-
if (obj.
|
|
2072
|
+
if (obj instanceof THREE.Mesh) {
|
|
2068
2073
|
const mesh = obj;
|
|
2069
2074
|
this.registeredHairObjects.set(mesh.name, mesh);
|
|
2070
2075
|
const meshInfo = CC4_MESHES[mesh.name];
|
|
@@ -2140,7 +2145,7 @@ var HairPhysicsController = class {
|
|
|
2140
2145
|
}
|
|
2141
2146
|
} : this.hairPhysicsConfig.morphTargets;
|
|
2142
2147
|
this.hairPhysicsConfig = { ...this.hairPhysicsConfig, ...config, direction, morphTargets };
|
|
2143
|
-
const
|
|
2148
|
+
const idleKeys = [
|
|
2144
2149
|
"idleSwayAmount",
|
|
2145
2150
|
"idleSwaySpeed",
|
|
2146
2151
|
"windStrength",
|
|
@@ -2149,14 +2154,16 @@ var HairPhysicsController = class {
|
|
|
2149
2154
|
"windTurbulence",
|
|
2150
2155
|
"windFrequency",
|
|
2151
2156
|
"idleClipDuration"
|
|
2152
|
-
]
|
|
2157
|
+
];
|
|
2158
|
+
const idleChanged = idleKeys.some((key) => config[key] !== void 0);
|
|
2153
2159
|
const morphTargetsChanged = config.morphTargets !== void 0;
|
|
2154
2160
|
const directionChanged = config.direction !== void 0;
|
|
2155
|
-
const
|
|
2161
|
+
const impulseKeys = [
|
|
2156
2162
|
"stiffness",
|
|
2157
2163
|
"damping",
|
|
2158
2164
|
"impulseClipDuration"
|
|
2159
|
-
]
|
|
2165
|
+
];
|
|
2166
|
+
const impulseChanged = impulseKeys.some((key) => config[key] !== void 0);
|
|
2160
2167
|
const gravityChanged = morphTargetsChanged;
|
|
2161
2168
|
if (idleChanged || morphTargetsChanged) {
|
|
2162
2169
|
this.idleClipDirty = true;
|
|
@@ -2297,7 +2304,7 @@ var HairPhysicsController = class {
|
|
|
2297
2304
|
if (state.position) {
|
|
2298
2305
|
obj.position.set(state.position.x, state.position.y, state.position.z);
|
|
2299
2306
|
}
|
|
2300
|
-
if (state.color && obj.material) {
|
|
2307
|
+
if (state.color && obj.material && !Array.isArray(obj.material)) {
|
|
2301
2308
|
const mat = obj.material;
|
|
2302
2309
|
if (mat.color) {
|
|
2303
2310
|
mat.color.set(state.color.baseColor);
|
|
@@ -2675,35 +2682,29 @@ var HairPhysicsController = class {
|
|
|
2675
2682
|
};
|
|
2676
2683
|
|
|
2677
2684
|
// src/mappings/resolveProfile.ts
|
|
2678
|
-
var
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
}
|
|
2682
|
-
return item;
|
|
2683
|
-
});
|
|
2685
|
+
var isPlainObject = (value) => {
|
|
2686
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2687
|
+
};
|
|
2684
2688
|
var cloneValue = (value) => {
|
|
2685
2689
|
if (Array.isArray(value)) {
|
|
2686
|
-
return
|
|
2690
|
+
return value.map((item) => cloneValue(item));
|
|
2687
2691
|
}
|
|
2688
|
-
if (value
|
|
2692
|
+
if (isPlainObject(value)) {
|
|
2689
2693
|
return { ...value };
|
|
2690
2694
|
}
|
|
2691
2695
|
return value;
|
|
2692
2696
|
};
|
|
2693
2697
|
var mergeRecord = (base, override) => {
|
|
2694
|
-
if (!override) {
|
|
2695
|
-
const next2 = {};
|
|
2696
|
-
for (const [key, value] of Object.entries(base)) {
|
|
2697
|
-
next2[key] = cloneValue(value);
|
|
2698
|
-
}
|
|
2699
|
-
return next2;
|
|
2700
|
-
}
|
|
2701
2698
|
const next = {};
|
|
2702
2699
|
for (const [key, value] of Object.entries(base)) {
|
|
2703
2700
|
next[key] = cloneValue(value);
|
|
2704
2701
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2702
|
+
if (override) {
|
|
2703
|
+
for (const [key, value] of Object.entries(override)) {
|
|
2704
|
+
if (value !== void 0) {
|
|
2705
|
+
next[key] = cloneValue(value);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2707
2708
|
}
|
|
2708
2709
|
return next;
|
|
2709
2710
|
};
|