@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.
- package/README.md +428 -71
- 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. [
|
|
18
|
-
4. [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
86
|
+
Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
|
|
37
87
|
|
|
38
88
|

|
|
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
|
-
|
|
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
|

|
|
144
206
|
|
|
@@ -287,9 +349,110 @@ const loom = new Loom3({
|
|
|
287
349
|
|
|
288
350
|
---
|
|
289
351
|
|
|
290
|
-
## 3.
|
|
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
|
-
|
|
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
|

|
|
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: '
|
|
309
|
-
// { name: '
|
|
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
|
-
###
|
|
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
|
-
|
|
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('
|
|
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
|
-
##
|
|
643
|
+
## 5. Extending & Custom Presets
|
|
453
644
|
|
|
454
|
-
|
|
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
|
-

|
|
457
647
|

|
|
458
648
|
|
|
459
649
|
### Extending an existing preset
|
|
@@ -529,9 +719,9 @@ const current = loom.getProfile();
|
|
|
529
719
|
|
|
530
720
|
---
|
|
531
721
|
|
|
532
|
-
##
|
|
722
|
+
## 6. Creating Skeletal Animation Presets
|
|
533
723
|
|
|
534
|
-
|
|
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
|

|
|
537
727
|
|
|
@@ -812,9 +1002,9 @@ async function swimCycle() {
|
|
|
812
1002
|
|
|
813
1003
|
---
|
|
814
1004
|
|
|
815
|
-
##
|
|
1005
|
+
## 7. Action Unit Control
|
|
816
1006
|
|
|
817
|
-
|
|
1007
|
+
Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units)
|
|
818
1008
|
|
|
819
1009
|

|
|
820
1010
|
|
|
@@ -890,9 +1080,9 @@ loom.setAU(12, 0.8, 1); // Right side only
|
|
|
890
1080
|
|
|
891
1081
|
---
|
|
892
1082
|
|
|
893
|
-
##
|
|
1083
|
+
## 8. Mix Weight System
|
|
894
1084
|
|
|
895
|
-
|
|
1085
|
+
Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units)
|
|
896
1086
|
|
|
897
1087
|

|
|
898
1088
|
|
|
@@ -939,9 +1129,9 @@ if (isMixedAU(26)) {
|
|
|
939
1129
|
|
|
940
1130
|
---
|
|
941
1131
|
|
|
942
|
-
##
|
|
1132
|
+
## 9. Composite Rotation System
|
|
943
1133
|
|
|
944
|
-
|
|
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
|

|
|
947
1137
|
|
|
@@ -1001,9 +1191,9 @@ loom.setAU(64, 0.4);
|
|
|
1001
1191
|
|
|
1002
1192
|
---
|
|
1003
1193
|
|
|
1004
|
-
##
|
|
1194
|
+
## 10. Continuum Pairs
|
|
1005
1195
|
|
|
1006
|
-
|
|
1196
|
+
Open in LoomLarge: [Action Units tab](https://loomlarge.web.app/?drawer=open&tab=action-units)
|
|
1007
1197
|
|
|
1008
1198
|

|
|
1009
1199
|
|
|
@@ -1103,9 +1293,9 @@ const pair = CONTINUUM_PAIRS_MAP[51];
|
|
|
1103
1293
|
|
|
1104
1294
|
---
|
|
1105
1295
|
|
|
1106
|
-
##
|
|
1296
|
+
## 11. Direct Morph Control
|
|
1107
1297
|
|
|
1108
|
-
|
|
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
|

|
|
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
|
-
###
|
|
1327
|
+
### Resolving current morph targets
|
|
1138
1328
|
|
|
1139
1329
|
```typescript
|
|
1140
|
-
const
|
|
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
|
-
##
|
|
1340
|
+
## 12. Viseme System
|
|
1150
1341
|
|
|
1151
|
-
|
|
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
|

|
|
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
|
-
##
|
|
1426
|
+
## 13. Transition System
|
|
1234
1427
|
|
|
1235
|
-
|
|
1428
|
+
Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
|
|
1236
1429
|
|
|
1237
1430
|

|
|
1238
1431
|
|
|
@@ -1314,9 +1507,9 @@ loom.clearTransitions();
|
|
|
1314
1507
|
|
|
1315
1508
|
---
|
|
1316
1509
|
|
|
1317
|
-
##
|
|
1510
|
+
## 14. Playback & State Control
|
|
1318
1511
|
|
|
1319
|
-
|
|
1512
|
+
Open in LoomLarge: [Animation tab](https://loomlarge.web.app/?drawer=open&tab=animation)
|
|
1320
1513
|
|
|
1321
1514
|

|
|
1322
1515
|
|
|
@@ -1373,9 +1566,9 @@ loom.dispose();
|
|
|
1373
1566
|
|
|
1374
1567
|
---
|
|
1375
1568
|
|
|
1376
|
-
##
|
|
1569
|
+
## 15. Hair Physics
|
|
1377
1570
|
|
|
1378
|
-
|
|
1571
|
+
Open in LoomLarge: [Hair tab](https://loomlarge.web.app/?drawer=open&tab=hair)
|
|
1379
1572
|
|
|
1380
1573
|

|
|
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
|
-
##
|
|
1759
|
+
## 16. Baked Animations
|
|
1534
1760
|
|
|
1535
|
-
|
|
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
|
-
##
|
|
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
|
-
###
|
|
1743
|
-
|
|
1744
|
-
`Loom3`
|
|
1745
|
-
-
|
|
1746
|
-
-
|
|
1747
|
-
- Preset state: `setProfile()`, `getProfile()
|
|
1748
|
-
-
|
|
1749
|
-
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
-
|
|
1755
|
-
- `
|
|
1756
|
-
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
- `
|
|
1761
|
-
- `
|
|
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