@lovelace_lol/loom3 1.0.12 → 1.0.13

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.
Files changed (2) hide show
  1. package/README.md +113 -42
  2. 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 `LoomLarge` interface exported from `@lovelace_lol/loom3`.
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 → actual node name in skeleton
166
- 'HEAD': 'CC_Base_Head',
167
- 'JAW': 'CC_Base_JawRoot',
168
- 'EYE_L': 'CC_Base_L_Eye',
169
- 'EYE_R': 'CC_Base_R_Eye',
170
- 'TONGUE': 'CC_Base_Tongue01',
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
- 'V_EE', 'V_Er', 'V_IH', 'V_Ah', 'V_Oh',
176
- 'V_W_OO', 'V_S_Z', 'V_Ch_J', 'V_F_V', 'V_TH',
177
- 'V_T_L_D_N', 'V_B_M_P', 'V_K_G_H_NG', 'V_AE', 'V_R'
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': ['CC_Base_EyeOcclusion_L', 'CC_Base_EyeOcclusion_R'],
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({ model, meshes, animations });
331
- const analysis = analyzeModel(modelData, { preset });
332
- const validation = validateMappings(modelData, preset);
333
- const corrections = generateMappingCorrections(modelData, preset);
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
- - Find missing morphs/bones and mesh mismatches
338
- - Score preset compatibility
339
- - Suggest corrections before you ship a profile
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
- ![Diagram showing preset inheritance and mergePreset overrides](./assets/readme/preset-inheritance.svg)
441
+ ![Diagram showing preset inheritance and profile override merging](./assets/readme/preset-inheritance.svg)
391
442
 
392
443
  ### Extending an existing preset
393
444
 
394
- Use `mergePreset` to override specific mappings while keeping the rest:
445
+ Use `resolveProfile` to override specific mappings while keeping the rest:
395
446
 
396
447
  ```typescript
397
- import { CC4_PRESET, mergePreset } from '@lovelace_lol/loom3';
448
+ import { CC4_PRESET, resolveProfile } from '@lovelace_lol/loom3';
398
449
 
399
- const MY_PRESET = mergePreset(CC4_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 | Er | "h**er**" |
1082
- | 2 | IH | "s**i**t" |
1083
- | 3 | Ah | "f**a**ther" |
1084
- | 4 | Oh | "g**o**" |
1085
- | 5 | W_OO | "t**oo**" |
1086
- | 6 | S_Z | "**s**un, **z**oo" |
1087
- | 7 | Ch_J | "**ch**ip, **j**ump" |
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 | TH | "**th**ink" |
1090
- | 10 | T_L_D_N | "**t**op, **l**ip, **d**og, **n**o" |
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
  ![Reference cards for FACS, Paul Ekman Group, Character Creator 4, and Three.js](./assets/readme/resources-cards.svg)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovelace_lol/loom3",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Lightweight 3D character animation engine for facial AUs, visemes, and bone-driven motion",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",