@lovelace_lol/loom3 1.0.22 → 1.0.24

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 +428 -71
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -10,30 +10,80 @@ Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https:
10
10
 
11
11
  ---
12
12
 
13
+ ## What Loom3 Covers
14
+
15
+ Loom3 is broader than a face-controller wrapper. The library spans four practical areas:
16
+ - Runtime control: Action Units, visemes, direct morphs, continuum pairs, composite rotations, transitions, and mixer playback.
17
+ - Rig configuration: built-in presets, profile overrides, preset resolution, name resolution, viseme routing, mix weights, and skeletal-only preset support.
18
+ - Inspection and validation: mesh, morph, and bone discovery; preset-fit checks; correction suggestions; and full model analysis.
19
+ - Runtime tooling: mesh/material debugging, baked animation clip helpers, hair physics, and region/geometry helpers for annotation or camera tooling.
20
+
21
+ ## Reading Paths
22
+
23
+ Use the README in one of these paths:
24
+ - First successful character: [Installation & Setup](#1-installation--setup) -> [Using Presets](#2-using-presets) -> [Preset Selection & Validation](#3-preset-selection--validation) -> [Getting to Know Your Character](#4-getting-to-know-your-character) -> [Action Unit Control](#7-action-unit-control) -> [Viseme System](#12-viseme-system) -> [Transition System](#13-transition-system) -> [Baked Animations](#16-baked-animations)
25
+ - Retargeting an existing rig: [Using Presets](#2-using-presets) -> [Preset Selection & Validation](#3-preset-selection--validation) -> [Getting to Know Your Character](#4-getting-to-know-your-character) -> [Extending & Custom Presets](#5-extending--custom-presets)
26
+ - Skeletal-only character: [Creating Skeletal Animation Presets](#6-creating-skeletal-animation-presets) -> [Baked Animations](#16-baked-animations) -> [Regions & Geometry Helpers](#17-regions--geometry-helpers)
27
+ - Annotation or camera tooling: [Preset Selection & Validation](#3-preset-selection--validation) -> [Getting to Know Your Character](#4-getting-to-know-your-character) -> [Regions & Geometry Helpers](#17-regions--geometry-helpers)
28
+
29
+ ## Production Playground
30
+
31
+ These links open the production LoomLarge drawer on the matching tab. Production currently supports stable `drawer` + `tab` deep links, so the README should lean on tab-specific links instead of pretending it can deep-link to a fully reconstructed authoring state.
32
+
33
+ | Goal | Open in LoomLarge |
34
+ |------|-------------------|
35
+ | Start with the main runtime surface | [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation) |
36
+ | Inspect preset and profile settings | [Properties tab](https://loomlarge.web.app/?drawer=open&tab=properties) |
37
+ | Inspect AU, morph, and bone routing | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings) |
38
+ | Inspect meshes and material state | [Meshes tab](https://loomlarge.web.app/?drawer=open&tab=meshes) |
39
+ | Inspect resolved bones | [Bones tab](https://loomlarge.web.app/?drawer=open&tab=bones) |
40
+ | Tune expressions and continuum pairs | [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units) |
41
+ | Inspect lip-sync views | [Visemes tab](https://loomlarge.web.app/?drawer=open&tab=visemes) and [Speech tab](https://loomlarge.web.app/?drawer=open&tab=speech) |
42
+ | Tune hair behavior | [Hair tab](https://loomlarge.web.app/?drawer=open&tab=hair) |
43
+
44
+ Most screenshots below were captured from LoomLarge with the matching tab open so the docs and the live product are easy to compare. The viseme grid image is the main exception: it still shows older labels from the captured UI, so the viseme table later in the README should be treated as the source of truth.
45
+
13
46
  ## Table of Contents
14
47
 
48
+ ### Foundations
49
+
15
50
  1. [Installation & Setup](#1-installation--setup)
16
51
  2. [Using Presets](#2-using-presets)
17
- 3. [Getting to Know Your Character](#3-getting-to-know-your-character)
18
- 4. [Extending & Custom Presets](#4-extending--custom-presets)
19
- 5. [Creating Skeletal Animation Presets](#5-creating-skeletal-animation-presets)
20
- 6. [Action Unit Control](#6-action-unit-control)
21
- 7. [Mix Weight System](#7-mix-weight-system)
22
- 8. [Composite Rotation System](#8-composite-rotation-system)
23
- 9. [Continuum Pairs](#9-continuum-pairs)
24
- 10. [Direct Morph Control](#10-direct-morph-control)
25
- 11. [Viseme System](#11-viseme-system)
26
- 12. [Transition System](#12-transition-system)
27
- 13. [Playback & State Control](#13-playback--state-control)
28
- 14. [Hair Physics](#14-hair-physics)
29
- 15. [Baked Animations](#15-baked-animations)
30
- 16. [API Reference](#16-api-reference)
52
+ 3. [Preset Selection & Validation](#3-preset-selection--validation)
53
+ 4. [Getting to Know Your Character](#4-getting-to-know-your-character)
54
+
55
+ ### Advanced Authoring
56
+
57
+ 5. [Extending & Custom Presets](#5-extending--custom-presets)
58
+ 6. [Creating Skeletal Animation Presets](#6-creating-skeletal-animation-presets)
59
+
60
+ ### Runtime Control
61
+
62
+ 7. [Action Unit Control](#7-action-unit-control)
63
+ 8. [Mix Weight System](#8-mix-weight-system)
64
+ 9. [Composite Rotation System](#9-composite-rotation-system)
65
+ 10. [Continuum Pairs](#10-continuum-pairs)
66
+ 11. [Direct Morph Control](#11-direct-morph-control)
67
+ 12. [Viseme System](#12-viseme-system)
68
+ 13. [Transition System](#13-transition-system)
69
+ 14. [Playback & State Control](#14-playback--state-control)
70
+ 15. [Hair Physics](#15-hair-physics)
71
+ 16. [Baked Animations](#16-baked-animations)
72
+
73
+ ### Tooling & Reference
74
+
75
+ 17. [Regions & Geometry Helpers](#17-regions--geometry-helpers)
76
+ 18. [API Reference](#18-api-reference)
77
+
78
+ Additional:
79
+ - [Resources](#resources)
80
+ - [License](#license)
31
81
 
32
82
  ---
33
83
 
34
84
  ## 1. Installation & Setup
35
85
 
36
- Production demo: [Open LoomLarge](https://loomlarge.web.app/?drawer=open&tab=animation)
86
+ Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
37
87
 
38
88
  ![Project structure with Loom3 installed](./assets/readme/project-structure.svg)
39
89
 
@@ -86,6 +136,18 @@ loader.load('/character.glb', (gltf) => {
86
136
 
87
137
  If you’re implementing a custom renderer, target the `Loom3` interface exported from `@lovelace_lol/loom3` (legacy alias: `LoomLarge`).
88
138
 
139
+ ### Lifecycle and update ownership
140
+
141
+ You have two valid integration patterns:
142
+ - External render loop: call `loom.update(deltaSeconds)` from your app’s main loop.
143
+ - Internal render loop: call `loom.start()` after `onReady()` and let Loom3 drive its own RAF-based updates.
144
+
145
+ The main lifecycle methods are:
146
+ - `onReady({ meshes, model })`: bind the loaded model to the engine
147
+ - `update(deltaSeconds)`: advance transitions, mixer playback, and runtime systems once
148
+ - `start()` / `stop()`: opt into or out of the internal loop
149
+ - `dispose()`: stop playback and release engine state when the character is torn down
150
+
89
151
  ### Quick start examples
90
152
 
91
153
  Once your character is loaded, you can control facial expressions immediately:
@@ -138,7 +200,7 @@ const meshes = collectMorphMeshes(gltf.scene);
138
200
 
139
201
  ## 2. Using Presets
140
202
 
141
- Production demos: [Properties](https://loomlarge.web.app/?drawer=open&tab=properties) | [Mappings](https://loomlarge.web.app/?drawer=open&tab=mappings)
203
+ Open in LoomLarge: [Properties tab](https://loomlarge.web.app/?drawer=open&tab=properties) | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings)
142
204
 
143
205
  ![Diagram showing how Loom3 presets connect AUs to morphs and bones](./assets/readme/preset-au-flow.svg)
144
206
 
@@ -287,9 +349,110 @@ const loom = new Loom3({
287
349
 
288
350
  ---
289
351
 
290
- ## 3. Getting to Know Your Character
352
+ ## 3. Preset Selection & Validation
353
+
354
+ Open in LoomLarge: [Properties tab](https://loomlarge.web.app/?drawer=open&tab=properties) | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings) | [Bones tab](https://loomlarge.web.app/?drawer=open&tab=bones)
355
+
356
+ 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.
357
+
358
+ ### Resolving presets by type
359
+
360
+ Use preset helpers when you want a stable entry point by model class instead of importing a preset constant directly:
361
+
362
+ ```typescript
363
+ import {
364
+ resolvePreset,
365
+ resolvePresetWithOverrides,
366
+ } from '@lovelace_lol/loom3';
367
+
368
+ const preset = resolvePreset('cc4');
369
+
370
+ const resolved = resolvePresetWithOverrides('cc4', {
371
+ morphToMesh: { face: ['Object_9'] },
372
+ });
373
+ ```
374
+
375
+ ### Validating the config itself
376
+
377
+ `validateMappingConfig()` checks the profile for internal consistency before you even compare it to a model:
378
+
379
+ ```typescript
380
+ import { validateMappingConfig } from '@lovelace_lol/loom3';
381
+
382
+ const consistency = validateMappingConfig(resolved);
383
+ console.log(consistency.errors, consistency.warnings);
384
+ ```
385
+
386
+ ### Checking a model against a preset
387
+
388
+ ```typescript
389
+ import * as THREE from 'three';
390
+ import {
391
+ validateMappings,
392
+ isPresetCompatible,
393
+ generateMappingCorrections,
394
+ } from '@lovelace_lol/loom3';
395
+
396
+ const skinnedMesh = gltf.scene.getObjectByProperty('type', 'SkinnedMesh') as THREE.SkinnedMesh | undefined;
397
+ const skeleton = skinnedMesh?.skeleton ?? null;
398
+
399
+ const validation = validateMappings(meshes, skeleton, resolved, {
400
+ suggestCorrections: true,
401
+ });
402
+
403
+ const compatible = isPresetCompatible(meshes, skeleton, resolved);
404
+ const corrections = generateMappingCorrections(meshes, skeleton, resolved, {
405
+ useResolvedNames: true,
406
+ });
407
+ ```
408
+
409
+ ### Suggesting the best preset from a candidate set
410
+
411
+ ```typescript
412
+ import {
413
+ CC4_PRESET,
414
+ BETTA_FISH_PRESET,
415
+ suggestBestPreset,
416
+ } from '@lovelace_lol/loom3';
417
+
418
+ const best = suggestBestPreset(meshes, skeleton, [
419
+ CC4_PRESET,
420
+ BETTA_FISH_PRESET,
421
+ ]);
422
+ ```
423
+
424
+ ### Running a full model analysis
425
+
426
+ ```typescript
427
+ import {
428
+ analyzeModel,
429
+ extractFromGLTF,
430
+ extractModelData,
431
+ } from '@lovelace_lol/loom3';
432
+
433
+ const extracted = extractFromGLTF(gltf);
434
+ const runtimeData = extractModelData(gltf.scene, meshes, gltf.animations);
435
+
436
+ const report = await analyzeModel({
437
+ source: { type: 'gltf', gltf },
438
+ preset: resolved,
439
+ suggestCorrections: true,
440
+ });
441
+
442
+ console.log(report.summary, report.overallScore);
443
+ ```
444
+
445
+ Use this section when you need to:
446
+ - choose between built-in presets before wiring the character into your app
447
+ - lint a preset/profile for broken internal references
448
+ - measure how well a preset matches an imported model
449
+ - generate correction suggestions before building a custom profile
450
+
451
+ ---
452
+
453
+ ## 4. Getting to Know Your Character
291
454
 
292
- Production demos: [Meshes](https://loomlarge.web.app/?drawer=open&tab=meshes) | [Bones](https://loomlarge.web.app/?drawer=open&tab=bones) | [Mappings](https://loomlarge.web.app/?drawer=open&tab=mappings)
455
+ Open in LoomLarge: [Meshes tab](https://loomlarge.web.app/?drawer=open&tab=meshes) | [Bones tab](https://loomlarge.web.app/?drawer=open&tab=bones) | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings)
293
456
 
294
457
  ![Console-style diagram showing Loom3 mesh and morph target inspection output](./assets/readme/console-mesh-output.svg)
295
458
 
@@ -305,8 +468,8 @@ console.log(meshes);
305
468
  // [
306
469
  // { name: 'CC_Base_Body', visible: true, morphCount: 142 },
307
470
  // { name: 'CC_Base_Tongue', visible: true, morphCount: 12 },
308
- // { name: 'CC_Base_EyeOcclusion_L', visible: true, morphCount: 8 },
309
- // { name: 'CC_Base_EyeOcclusion_R', visible: true, morphCount: 8 },
471
+ // { name: 'CC_Base_EyeOcclusion_1', visible: true, morphCount: 8 },
472
+ // { name: 'CC_Base_EyeOcclusion_2', visible: true, morphCount: 8 },
310
473
  // { name: 'Male_Bushy_1', visible: true, morphCount: 142 },
311
474
  // ...
312
475
  // ]
@@ -351,9 +514,25 @@ console.log(bones);
351
514
  // }
352
515
  ```
353
516
 
354
- ### Validation & analysis
517
+ ### Listing morph target indices
518
+
519
+ Use the index view when you need to work with `setMorphInfluence()` or build tools that operate on morph slots directly:
520
+
521
+ ```typescript
522
+ const indices = loom.getMorphTargetIndices();
523
+ console.log(indices);
524
+ // {
525
+ // 'CC_Base_Body': [
526
+ // { index: 0, name: 'A01_Brow_Inner_Up' },
527
+ // { index: 1, name: 'A02_Brow_Down_Left' },
528
+ // ...
529
+ // ],
530
+ // }
531
+ ```
532
+
533
+ ### Validating and analyzing the model you loaded
355
534
 
356
- Loom3 includes validation and analysis helpers so you can verify presets against a model and generate corrections:
535
+ Use the extraction and validation helpers when inspection needs to turn into preset-fit analysis:
357
536
 
358
537
  ```typescript
359
538
  import {
@@ -414,6 +593,18 @@ loom.setMeshVisible('Side_part_wavy_1', false);
414
593
  loom.setMeshVisible('Side_part_wavy_1', true);
415
594
  ```
416
595
 
596
+ ### Highlighting a mesh
597
+
598
+ Use highlighting when you need to confirm which mesh a profile field or morph category is actually targeting:
599
+
600
+ ```typescript
601
+ // Highlight one mesh
602
+ loom.highlightMesh('CC_Base_Body');
603
+
604
+ // Clear all highlights
605
+ loom.highlightMesh(null);
606
+ ```
607
+
417
608
  ### Adjusting material properties
418
609
 
419
610
  Fine-tune render order, transparency, and blending for each mesh:
@@ -432,7 +623,7 @@ console.log(config);
432
623
  // }
433
624
 
434
625
  // Set custom material config
435
- loom.setMeshMaterialConfig('CC_Base_EyeOcclusion_L', {
626
+ loom.setMeshMaterialConfig('CC_Base_EyeOcclusion_1', {
436
627
  renderOrder: 10,
437
628
  transparent: true,
438
629
  opacity: 0.8,
@@ -449,11 +640,10 @@ This is especially useful for:
449
640
 
450
641
  ---
451
642
 
452
- ## 4. Extending & Custom Presets
643
+ ## 5. Extending & Custom Presets
453
644
 
454
- Production demos: [Properties](https://loomlarge.web.app/?drawer=open&tab=properties) | [Mappings](https://loomlarge.web.app/?drawer=open&tab=mappings)
645
+ Open in LoomLarge: [Properties tab](https://loomlarge.web.app/?drawer=open&tab=properties) | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings)
455
646
 
456
- ![Diagram showing preset inheritance and mergePreset overrides](./assets/readme/preset-inheritance.svg)
457
647
  ![Diagram showing preset inheritance and profile override merging](./assets/readme/preset-inheritance.svg)
458
648
 
459
649
  ### Extending an existing preset
@@ -529,9 +719,9 @@ const current = loom.getProfile();
529
719
 
530
720
  ---
531
721
 
532
- ## 5. Creating Skeletal Animation Presets
722
+ ## 6. Creating Skeletal Animation Presets
533
723
 
534
- Production demos: [Bones](https://loomlarge.web.app/?drawer=open&tab=bones) | [Action Units](https://loomlarge.web.app/?drawer=open&tab=action-units) | [Animation](https://loomlarge.web.app/?drawer=open&tab=animation)
724
+ Open in LoomLarge: [Bones tab](https://loomlarge.web.app/?drawer=open&tab=bones) | [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units) | [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
535
725
 
536
726
  ![Betta fish model with bones panel visible](./assets/readme/fish-bones-ui.webp)
537
727
 
@@ -812,9 +1002,9 @@ async function swimCycle() {
812
1002
 
813
1003
  ---
814
1004
 
815
- ## 6. Action Unit Control
1005
+ ## 7. Action Unit Control
816
1006
 
817
- Production demo: [Action Units](https://loomlarge.web.app/?drawer=open&tab=action-units)
1007
+ Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units)
818
1008
 
819
1009
  ![Grid of Loom3 Action Unit examples on a character](./assets/readme/au-values-grid.webp)
820
1010
 
@@ -890,9 +1080,9 @@ loom.setAU(12, 0.8, 1); // Right side only
890
1080
 
891
1081
  ---
892
1082
 
893
- ## 7. Mix Weight System
1083
+ ## 8. Mix Weight System
894
1084
 
895
- Production demo: [Action Units](https://loomlarge.web.app/?drawer=open&tab=action-units)
1085
+ Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units)
896
1086
 
897
1087
  ![Comparison of morph-only, mixed, and bone-only AU results](./assets/readme/mix-weight-comparison.webp)
898
1088
 
@@ -939,9 +1129,9 @@ if (isMixedAU(26)) {
939
1129
 
940
1130
  ---
941
1131
 
942
- ## 8. Composite Rotation System
1132
+ ## 9. Composite Rotation System
943
1133
 
944
- Production demos: [Action Units](https://loomlarge.web.app/?drawer=open&tab=action-units) | [Bones](https://loomlarge.web.app/?drawer=open&tab=bones)
1134
+ Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units) | [Bones tab](https://loomlarge.web.app/?drawer=open&tab=bones)
945
1135
 
946
1136
  ![Diagram showing pitch, yaw, and roll axes on the head](./assets/readme/head-axes-diagram.svg)
947
1137
 
@@ -1001,9 +1191,9 @@ loom.setAU(64, 0.4);
1001
1191
 
1002
1192
  ---
1003
1193
 
1004
- ## 9. Continuum Pairs
1194
+ ## 10. Continuum Pairs
1005
1195
 
1006
- Production demo: [Action Units](https://loomlarge.web.app/?drawer=open&tab=action-units)
1196
+ Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units)
1007
1197
 
1008
1198
  ![Continuum slider UI for paired Loom3 AUs](./assets/readme/continuum-slider-ui.webp)
1009
1199
 
@@ -1103,9 +1293,9 @@ const pair = CONTINUUM_PAIRS_MAP[51];
1103
1293
 
1104
1294
  ---
1105
1295
 
1106
- ## 10. Direct Morph Control
1296
+ ## 11. Direct Morph Control
1107
1297
 
1108
- Production demos: [Meshes](https://loomlarge.web.app/?drawer=open&tab=meshes) | [Mappings](https://loomlarge.web.app/?drawer=open&tab=mappings)
1298
+ Open in LoomLarge: [Meshes tab](https://loomlarge.web.app/?drawer=open&tab=meshes) | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings)
1109
1299
 
1110
1300
  ![Direct morph control screenshot with a live morph preview](./assets/readme/direct-morph-control.webp)
1111
1301
 
@@ -1134,10 +1324,11 @@ loom.transitionMorph('Eye_Blink_L', 1.0, 100, ['CC_Base_Body']);
1134
1324
  await handle.promise;
1135
1325
  ```
1136
1326
 
1137
- ### Reading current morph value
1327
+ ### Resolving current morph targets
1138
1328
 
1139
1329
  ```typescript
1140
- const value = loom.getMorphValue('Mouth_Smile_L');
1330
+ const targets = loom.resolveMorphTargets('Mouth_Smile_L', ['CC_Base_Body']);
1331
+ const value = targets.length > 0 ? (targets[0].infl[targets[0].idx] ?? 0) : 0;
1141
1332
  ```
1142
1333
 
1143
1334
  ### Morph caching
@@ -1146,12 +1337,14 @@ Loom3 caches morph target lookups for performance. The first time you access a m
1146
1337
 
1147
1338
  ---
1148
1339
 
1149
- ## 11. Viseme System
1340
+ ## 12. Viseme System
1150
1341
 
1151
- Production demos: [Visemes](https://loomlarge.web.app/?drawer=open&tab=visemes) | [Speech](https://loomlarge.web.app/?drawer=open&tab=speech)
1342
+ Open in LoomLarge: [Visemes tab](https://loomlarge.web.app/?drawer=open&tab=visemes) | [Speech tab](https://loomlarge.web.app/?drawer=open&tab=speech)
1152
1343
 
1153
1344
  ![Grid of all 15 Loom3 viseme mouth shapes](./assets/readme/viseme-grid.webp)
1154
1345
 
1346
+ This screenshot was captured before the viseme label refresh, so some cards still show legacy names such as `Er`, `IH`, and `W_OO`. Treat the table below as the source of truth for the current exported `VISEME_KEYS` order.
1347
+
1155
1348
  Visemes are mouth shapes used for lip-sync. Loom3 includes 15 visemes with automatic jaw coupling.
1156
1349
 
1157
1350
  ### The 15 visemes
@@ -1230,9 +1423,9 @@ speak([5, 0, 10, 4]);
1230
1423
 
1231
1424
  ---
1232
1425
 
1233
- ## 12. Transition System
1426
+ ## 13. Transition System
1234
1427
 
1235
- Production demo: [Animation](https://loomlarge.web.app/?drawer=open&tab=animation)
1428
+ Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
1236
1429
 
1237
1430
  ![Diagram showing a Loom3 transition timeline with easing and handle methods](./assets/readme/transition-timeline-easing.svg)
1238
1431
 
@@ -1314,9 +1507,9 @@ loom.clearTransitions();
1314
1507
 
1315
1508
  ---
1316
1509
 
1317
- ## 13. Playback & State Control
1510
+ ## 14. Playback & State Control
1318
1511
 
1319
- Production demo: [Animation](https://loomlarge.web.app/?drawer=open&tab=animation)
1512
+ Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
1320
1513
 
1321
1514
  ![Playback controls UI showing Loom3 pause and resume controls](./assets/readme/pause-resume-controls.webp)
1322
1515
 
@@ -1373,9 +1566,9 @@ loom.dispose();
1373
1566
 
1374
1567
  ---
1375
1568
 
1376
- ## 14. Hair Physics (Mixer-Driven)
1569
+ ## 15. Hair Physics
1377
1570
 
1378
- Production demo: [Hair](https://loomlarge.web.app/?drawer=open&tab=hair)
1571
+ Open in LoomLarge: [Hair tab](https://loomlarge.web.app/?drawer=open&tab=hair)
1379
1572
 
1380
1573
  ![Animated GIF showing Loom3 hair physics reacting to head motion](./assets/readme/hair-physics.gif)
1381
1574
 
@@ -1412,6 +1605,13 @@ loader.load('/character.glb', (gltf) => {
1412
1605
  });
1413
1606
  ```
1414
1607
 
1608
+ ### Inspecting registered hair objects
1609
+
1610
+ ```typescript
1611
+ const hairObjects = loom.getRegisteredHairObjects();
1612
+ console.log(hairObjects.map((mesh) => mesh.name));
1613
+ ```
1614
+
1415
1615
  ### Configuration (profile defaults)
1416
1616
 
1417
1617
  Hair physics defaults live in the preset/profile and are applied automatically at init:
@@ -1514,6 +1714,32 @@ if (missing.length > 0) {
1514
1714
 
1515
1715
  Loom3 also logs a warning the first time it encounters a missing hair morph key.
1516
1716
 
1717
+ ### Applying styling state
1718
+
1719
+ Use the engine helpers when you want to toggle brows, outlines, or simple per-object visual state from a UI:
1720
+
1721
+ ```typescript
1722
+ loom.applyHairStateToObject('Sideburns', {
1723
+ visible: true,
1724
+ outline: { show: true, color: '#7dd3fc', opacity: 0.6 },
1725
+ color: {
1726
+ baseColor: '#8b5e3c',
1727
+ emissive: '#000000',
1728
+ emissiveIntensity: 0,
1729
+ },
1730
+ });
1731
+ ```
1732
+
1733
+ ### Applying morphs to named hair meshes
1734
+
1735
+ ```typescript
1736
+ loom.setMorphOnMeshes(
1737
+ ['Side_part_wavy_1', 'Side_part_wavy_2'],
1738
+ 'L_Hair_Front',
1739
+ 0.35
1740
+ );
1741
+ ```
1742
+
1517
1743
  ### Notes
1518
1744
 
1519
1745
  - **Head rotation input** comes from AUs (e.g. 51/52 yaw, 53/54 pitch).
@@ -1530,9 +1756,9 @@ Loom3 also logs a warning the first time it encounters a missing hair morph key.
1530
1756
 
1531
1757
  ---
1532
1758
 
1533
- ## 15. Baked Animations
1759
+ ## 16. Baked Animations
1534
1760
 
1535
- Production demo: [Animation](https://loomlarge.web.app/?drawer=open&tab=animation)
1761
+ Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
1536
1762
 
1537
1763
  Loom3 can play baked skeletal animations from your GLB/GLTF files using Three.js AnimationMixer. This allows you to combine pre-made animations (idle, walk, gestures) with real-time facial control.
1538
1764
 
@@ -1609,6 +1835,55 @@ if (clip) {
1609
1835
  }
1610
1836
  ```
1611
1837
 
1838
+ ### Playing a snippet directly
1839
+
1840
+ If you already have a named snippet object, you can skip manual clip creation:
1841
+
1842
+ ```typescript
1843
+ const handle = loom.playSnippet({
1844
+ name: 'look-left',
1845
+ curves: {
1846
+ '61': [{ time: 0, intensity: 0 }, { time: 0.25, intensity: 0.7 }],
1847
+ '62': [{ time: 0, intensity: 0 }, { time: 0.25, intensity: 0 }],
1848
+ },
1849
+ }, { loop: false });
1850
+ ```
1851
+
1852
+ ### Building and updating managed clips
1853
+
1854
+ `buildClip()` keeps a named clip/action around so you can adjust it later without rebuilding your entire animation flow:
1855
+
1856
+ ```typescript
1857
+ const clipHandle = loom.buildClip('gaze-loop', {
1858
+ '61': [{ time: 0, intensity: 0 }, { time: 0.3, intensity: 0.6 }],
1859
+ '62': [{ time: 0, intensity: 0.3 }, { time: 0.3, intensity: 0 }],
1860
+ }, {
1861
+ loop: true,
1862
+ loopMode: 'pingpong',
1863
+ });
1864
+
1865
+ loom.updateClipParams('gaze-loop', {
1866
+ weight: 0.7,
1867
+ rate: 1.2,
1868
+ loopMode: 'repeat',
1869
+ });
1870
+
1871
+ clipHandle?.pause();
1872
+ clipHandle?.resume();
1873
+ ```
1874
+
1875
+ ### Checking curve support
1876
+
1877
+ ```typescript
1878
+ const supported = loom.supportsClipCurves({
1879
+ '61': [{ time: 0, intensity: 0 }, { time: 0.2, intensity: 0.4 }],
1880
+ });
1881
+
1882
+ if (!supported) {
1883
+ console.warn('Curves need a fallback playback path');
1884
+ }
1885
+ ```
1886
+
1612
1887
  ### Controlling playback
1613
1888
 
1614
1889
  The handle returned from `playAnimation()` provides full control:
@@ -1735,30 +2010,112 @@ loom.transitionAU(45, 1.0, 100); // Blink
1735
2010
 
1736
2011
  ---
1737
2012
 
1738
- ## 16. API Reference
2013
+ ## 17. Regions & Geometry Helpers
2014
+
2015
+ Open in LoomLarge: [Bones tab](https://loomlarge.web.app/?drawer=open&tab=bones) | [Mappings tab](https://loomlarge.web.app/?drawer=open&tab=mappings)
2016
+
2017
+ These helpers are for applications that need semantic face regions, marker anchors, or camera targets in addition to direct animation control.
2018
+
2019
+ ### Finding a face center directly from the model
2020
+
2021
+ ```typescript
2022
+ import { findFaceCenter } from '@lovelace_lol/loom3';
2023
+
2024
+ const result = findFaceCenter(gltf.scene, {
2025
+ headBoneNames: ['CC_Base_Head'],
2026
+ faceMeshNames: ['CC_Base_Body'],
2027
+ });
2028
+
2029
+ console.log(result.center, result.method, result.debugInfo);
2030
+ ```
2031
+
2032
+ ### Resolving region-driven centers
2033
+
2034
+ ```typescript
2035
+ import type { CharacterConfig, Region } from '@lovelace_lol/loom3';
2036
+ import { resolveBoneName, resolveBoneNames, resolveFaceCenter } from '@lovelace_lol/loom3';
2037
+
2038
+ const region: Region = {
2039
+ name: 'face',
2040
+ bones: ['HEAD'],
2041
+ meshes: ['CC_Base_Body'],
2042
+ };
2043
+
2044
+ const config = {
2045
+ characterId: 'demo',
2046
+ characterName: 'Demo',
2047
+ modelPath: '/demo.glb',
2048
+ regions: [region],
2049
+ bonePrefix: 'CC_Base_',
2050
+ boneNodes: { HEAD: 'Head' },
2051
+ } satisfies CharacterConfig;
2052
+
2053
+ const headBone = resolveBoneName('HEAD', config);
2054
+ const resolvedBones = resolveBoneNames(['HEAD'], config);
2055
+ const faceCenter = resolveFaceCenter(gltf.scene, region, config);
2056
+ ```
2057
+
2058
+ ### Working with model orientation
2059
+
2060
+ ```typescript
2061
+ import {
2062
+ getModelForwardDirection,
2063
+ detectFacingDirection,
2064
+ } from '@lovelace_lol/loom3';
2065
+
2066
+ const forward = getModelForwardDirection(gltf.scene);
2067
+ const facing = detectFacingDirection(gltf.scene);
2068
+ ```
2069
+
2070
+ Use these helpers when you need to:
2071
+ - place annotation markers using semantic regions instead of hard-coded coordinates
2072
+ - resolve prefixed/suffixed bone names from a reusable character config
2073
+ - derive a face anchor for camera tooling or interaction layers
2074
+ - reason about model orientation before building your own camera or annotation system
2075
+
2076
+ ---
2077
+
2078
+ ## 18. API Reference
2079
+
2080
+ Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
1739
2081
 
1740
2082
  This is a compact reference for the public surface exported by `@lovelace_lol/loom3`.
1741
2083
 
1742
- ### Engine surface
1743
-
1744
- `Loom3` (legacy alias: `LoomLarge`) covers:
1745
- - Initialization and lifecycle: `onReady()`, `update()`, `start()`, `stop()`, `dispose()`
1746
- - AU, morph, viseme, and continuum control: `setAU()`, `transitionAU()`, `setMorph()`, `transitionMorph()`, `setViseme()`, `transitionViseme()`, `setContinuum()`, `transitionContinuum()`
1747
- - Preset state: `setProfile()`, `getProfile()`
1748
- - Validation and analysis helpers: `validateMappings()`, `generateMappingCorrections()`, `extractModelData()`, `extractFromGLTF()`, `analyzeModel()`
1749
- - Mixer and baked animation helpers: `loadAnimationClips()`, `getAnimationClips()`, `playAnimation()`, `playClip()`, `playSnippet()`, `snippetToClip()`
1750
- - Hair physics helpers: `setHairPhysicsEnabled()`, `setHairPhysicsConfig()`, `validateHairMorphTargets()`
1751
-
1752
- ### Core types and helpers
1753
-
1754
- - `Profile` groups the mapping tables, name-resolution fields, `morphToMesh`, `visemeKeys`, `auMixDefaults`, `compositeRotations`, `continuumPairs`, `continuumLabels`, `eyeMeshNodes`, `annotationRegions`, and `hairPhysics`.
1755
- - `TransitionHandle` exposes `promise`, `pause()`, `resume()`, and `cancel()`.
1756
- - `ClipHandle` exposes `clipName`, `play()`, `stop()`, `pause()`, `resume()`, optional live-update setters, `getTime()`, `getDuration()`, and `finished`.
1757
- - `collectMorphMeshes()` gathers meshes that already have morph targets.
1758
- - `resolvePreset()` picks a built-in preset by name.
1759
- - `resolvePresetWithOverrides()` resolves a preset and merges partial overrides.
1760
- - `resolveProfile()` merges a base preset with a partial profile override.
1761
- - `isMixedAU()` checks whether an AU has both morph and bone support in the CC4 preset.
2084
+ ### Core runtime
2085
+
2086
+ - `Loom3` is the main Three.js implementation.
2087
+ - `collectMorphMeshes()` gathers meshes that already expose morph targets.
2088
+ - Lifecycle: `onReady()`, `update()`, `start()`, `stop()`, `dispose()`.
2089
+ - Preset state: `setProfile()`, `getProfile()`.
2090
+ - Control APIs: `setAU()`, `transitionAU()`, `setContinuum()`, `transitionContinuum()`, `setMorph()`, `transitionMorph()`, `setViseme()`, `transitionViseme()`.
2091
+ - Transition state: `pause()`, `resume()`, `getPaused()`, `clearTransitions()`, `getActiveTransitionCount()`, `resetToNeutral()`.
2092
+
2093
+ ### Presets and profiles
2094
+
2095
+ - Presets: `CC4_PRESET`, `BETTA_FISH_PRESET`, `resolvePreset()`, `resolvePresetWithOverrides()`.
2096
+ - Profile composition: `resolveProfile()`.
2097
+ - CC4 exports: `VISEME_KEYS`, `VISEME_JAW_AMOUNTS`, `CONTINUUM_PAIRS_MAP`, `CONTINUUM_LABELS`, `AU_INFO`, `COMPOSITE_ROTATIONS`, `AU_MIX_DEFAULTS`.
2098
+ - Compatibility helpers: `isMixedAU()`, `hasLeftRightMorphs()`.
2099
+
2100
+ ### Validation and inspection
2101
+
2102
+ - Extraction: `extractModelData()`, `extractFromGLTF()`.
2103
+ - Config linting: `validateMappingConfig()`.
2104
+ - Model fit: `validateMappings()`, `isPresetCompatible()`, `suggestBestPreset()`, `generateMappingCorrections()`.
2105
+ - Unified report: `analyzeModel()`.
2106
+
2107
+ ### Runtime tooling
2108
+
2109
+ - Mesh inspection: `getMeshList()`, `getMorphTargets()`, `getMorphTargetIndices()`, `getBones()`.
2110
+ - Mesh debugging: `setMeshVisible()`, `highlightMesh()`, `getMeshMaterialConfig()`, `setMeshMaterialConfig()`.
2111
+ - Hair runtime: `registerHairObjects()`, `getRegisteredHairObjects()`, `setHairPhysicsEnabled()`, `setHairPhysicsConfig()`, `validateHairMorphTargets()`, `applyHairStateToObject()`.
2112
+ - Mixer helpers: `loadAnimationClips()`, `getAnimationClips()`, `playAnimation()`, `pauseAnimation()`, `resumeAnimation()`, `stopAnimation()`, `stopAllAnimations()`, `pauseAllAnimations()`, `resumeAllAnimations()`, `setAnimationSpeed()`, `setAnimationIntensity()`, `setAnimationTimeScale()`, `getAnimationState()`, `getPlayingAnimations()`, `crossfadeTo()`, `snippetToClip()`, `playClip()`, `playSnippet()`, `buildClip()`, `updateClipParams()`, `supportsClipCurves()`.
2113
+
2114
+ ### Types and lower-level exports
2115
+
2116
+ - Configuration/types: `Profile`, `MeshInfo`, `BlendingMode`, `TransitionHandle`, `ClipHandle`, `Snippet`, `AnimationState`, `AnimationClipInfo`.
2117
+ - Standalone implementations: `AnimationThree`, `HairPhysics`, `BLENDING_MODES`.
2118
+ - Region and geometry helpers: `resolveBoneName()`, `resolveBoneNames()`, `resolveFaceCenter()`, `findFaceCenter()`, `getModelForwardDirection()`, `detectFacingDirection()`.
1762
2119
 
1763
2120
  ---
1764
2121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovelace_lol/loom3",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
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",