loomlarge 0.2.1 → 1.0.1
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 +126 -20
- package/dist/index.cjs +227 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -22
- package/dist/index.d.ts +84 -22
- package/dist/index.js +227 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,16 +10,17 @@ LoomLarge provides pre-built mappings that connect [Facial Action Coding System
|
|
|
10
10
|
|
|
11
11
|
1. [Installation & Setup](#1-installation--setup)
|
|
12
12
|
2. [Using Presets](#2-using-presets)
|
|
13
|
-
3. [
|
|
14
|
-
4. [
|
|
15
|
-
5. [
|
|
16
|
-
6. [
|
|
17
|
-
7. [
|
|
18
|
-
8. [
|
|
19
|
-
9. [
|
|
20
|
-
10. [
|
|
21
|
-
11. [
|
|
22
|
-
12. [
|
|
13
|
+
3. [Getting to Know Your Character](#3-getting-to-know-your-character)
|
|
14
|
+
4. [Extending & Custom Presets](#4-extending--custom-presets)
|
|
15
|
+
5. [Action Unit Control](#5-action-unit-control)
|
|
16
|
+
6. [Mix Weight System](#6-mix-weight-system)
|
|
17
|
+
7. [Composite Rotation System](#7-composite-rotation-system)
|
|
18
|
+
8. [Continuum Pairs](#8-continuum-pairs)
|
|
19
|
+
9. [Direct Morph Control](#9-direct-morph-control)
|
|
20
|
+
10. [Viseme System](#10-viseme-system)
|
|
21
|
+
11. [Transition System](#11-transition-system)
|
|
22
|
+
12. [Playback & State Control](#12-playback--state-control)
|
|
23
|
+
13. [Hair Physics](#13-hair-physics)
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
@@ -198,7 +199,112 @@ const loom = new LoomLargeThree({ auMappings: CC4_PRESET });
|
|
|
198
199
|
|
|
199
200
|
---
|
|
200
201
|
|
|
201
|
-
## 3.
|
|
202
|
+
## 3. Getting to Know Your Character
|
|
203
|
+
|
|
204
|
+
Before customizing presets or extending mappings, it's helpful to understand what's actually in your character model. LoomLarge provides several methods to inspect meshes, morph targets, and bones.
|
|
205
|
+
|
|
206
|
+
### Listing meshes
|
|
207
|
+
|
|
208
|
+
Get all meshes in your character with their visibility and morph target counts:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const meshes = loom.getMeshList();
|
|
212
|
+
console.log(meshes);
|
|
213
|
+
// [
|
|
214
|
+
// { name: 'CC_Base_Body', visible: true, morphCount: 142 },
|
|
215
|
+
// { name: 'CC_Base_Tongue', visible: true, morphCount: 12 },
|
|
216
|
+
// { name: 'CC_Base_EyeOcclusion_L', visible: true, morphCount: 8 },
|
|
217
|
+
// { name: 'CC_Base_EyeOcclusion_R', visible: true, morphCount: 8 },
|
|
218
|
+
// { name: 'Male_Bushy_1', visible: true, morphCount: 142 },
|
|
219
|
+
// ...
|
|
220
|
+
// ]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Listing morph targets
|
|
224
|
+
|
|
225
|
+
Get all morph target names grouped by mesh:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const morphs = loom.getMorphTargets();
|
|
229
|
+
console.log(morphs);
|
|
230
|
+
// {
|
|
231
|
+
// 'CC_Base_Body': [
|
|
232
|
+
// 'A01_Brow_Inner_Up', 'A02_Brow_Down_Left', 'A02_Brow_Down_Right',
|
|
233
|
+
// 'A04_Brow_Outer_Up_Left', 'A04_Brow_Outer_Up_Right',
|
|
234
|
+
// 'Mouth_Smile_L', 'Mouth_Smile_R', 'Eye_Blink_L', 'Eye_Blink_R',
|
|
235
|
+
// ...
|
|
236
|
+
// ],
|
|
237
|
+
// 'CC_Base_Tongue': [
|
|
238
|
+
// 'V_Tongue_Out', 'V_Tongue_Up', 'V_Tongue_Down', ...
|
|
239
|
+
// ],
|
|
240
|
+
// ...
|
|
241
|
+
// }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
This is invaluable when creating custom presets—you need to know the exact morph target names your character uses.
|
|
245
|
+
|
|
246
|
+
### Listing bones
|
|
247
|
+
|
|
248
|
+
Get all resolved bones with their current positions and rotations (in degrees):
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const bones = loom.getBones();
|
|
252
|
+
console.log(bones);
|
|
253
|
+
// {
|
|
254
|
+
// 'HEAD': { position: [0, 156.2, 0], rotation: [0, 0, 0] },
|
|
255
|
+
// 'JAW': { position: [0, 154.1, 2.3], rotation: [0, 0, 0] },
|
|
256
|
+
// 'EYE_L': { position: [-3.2, 160.5, 8.1], rotation: [0, 0, 0] },
|
|
257
|
+
// 'EYE_R': { position: [3.2, 160.5, 8.1], rotation: [0, 0, 0] },
|
|
258
|
+
// 'TONGUE': { position: [0, 152.3, 1.8], rotation: [0, 0, 0] },
|
|
259
|
+
// }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Controlling mesh visibility
|
|
263
|
+
|
|
264
|
+
Hide or show individual meshes:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Hide hair mesh
|
|
268
|
+
loom.setMeshVisible('Side_part_wavy_1', false);
|
|
269
|
+
|
|
270
|
+
// Show it again
|
|
271
|
+
loom.setMeshVisible('Side_part_wavy_1', true);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Adjusting material properties
|
|
275
|
+
|
|
276
|
+
Fine-tune render order, transparency, and blending for each mesh:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Get current material config
|
|
280
|
+
const config = loom.getMeshMaterialConfig('CC_Base_Body');
|
|
281
|
+
console.log(config);
|
|
282
|
+
// {
|
|
283
|
+
// renderOrder: 0,
|
|
284
|
+
// transparent: false,
|
|
285
|
+
// opacity: 1,
|
|
286
|
+
// depthWrite: true,
|
|
287
|
+
// depthTest: true,
|
|
288
|
+
// blending: 'Normal'
|
|
289
|
+
// }
|
|
290
|
+
|
|
291
|
+
// Set custom material config
|
|
292
|
+
loom.setMeshMaterialConfig('CC_Base_EyeOcclusion_L', {
|
|
293
|
+
renderOrder: 10,
|
|
294
|
+
transparent: true,
|
|
295
|
+
opacity: 0.8,
|
|
296
|
+
blending: 'Normal' // 'Normal', 'Additive', 'Subtractive', 'Multiply', 'None'
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
This is especially useful for:
|
|
301
|
+
- Fixing render order issues (eyebrows behind hair, etc.)
|
|
302
|
+
- Making meshes semi-transparent for debugging
|
|
303
|
+
- Adjusting blending modes for special effects
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 4. Extending & Custom Presets
|
|
202
308
|
|
|
203
309
|
### Extending an existing preset
|
|
204
310
|
|
|
@@ -275,7 +381,7 @@ const current = loom.getAUMappings();
|
|
|
275
381
|
|
|
276
382
|
---
|
|
277
383
|
|
|
278
|
-
##
|
|
384
|
+
## 5. Action Unit Control
|
|
279
385
|
|
|
280
386
|
Action Units are the core of FACS. Each AU represents a specific muscular movement of the face.
|
|
281
387
|
|
|
@@ -349,7 +455,7 @@ loom.setAU(12, 0.8, 1); // Right side only
|
|
|
349
455
|
|
|
350
456
|
---
|
|
351
457
|
|
|
352
|
-
##
|
|
458
|
+
## 6. Mix Weight System
|
|
353
459
|
|
|
354
460
|
Some AUs can be driven by both morph targets (blend shapes) AND bone rotations. The mix weight controls the blend between them.
|
|
355
461
|
|
|
@@ -394,7 +500,7 @@ if (isMixedAU(26)) {
|
|
|
394
500
|
|
|
395
501
|
---
|
|
396
502
|
|
|
397
|
-
##
|
|
503
|
+
## 7. Composite Rotation System
|
|
398
504
|
|
|
399
505
|
Bones like the head and eyes need multi-axis rotation (pitch, yaw, roll). The composite rotation system handles this automatically.
|
|
400
506
|
|
|
@@ -452,7 +558,7 @@ loom.setAU(64, 0.4);
|
|
|
452
558
|
|
|
453
559
|
---
|
|
454
560
|
|
|
455
|
-
##
|
|
561
|
+
## 8. Continuum Pairs
|
|
456
562
|
|
|
457
563
|
Continuum pairs are bidirectional AU pairs that represent opposite directions on the same axis. They're linked so that activating one should deactivate the other.
|
|
458
564
|
|
|
@@ -498,7 +604,7 @@ const pair = CONTINUUM_PAIRS_MAP[51];
|
|
|
498
604
|
|
|
499
605
|
---
|
|
500
606
|
|
|
501
|
-
##
|
|
607
|
+
## 9. Direct Morph Control
|
|
502
608
|
|
|
503
609
|
Sometimes you need to control morph targets directly by name, bypassing the AU system.
|
|
504
610
|
|
|
@@ -537,7 +643,7 @@ LoomLarge caches morph target lookups for performance. The first time you access
|
|
|
537
643
|
|
|
538
644
|
---
|
|
539
645
|
|
|
540
|
-
##
|
|
646
|
+
## 10. Viseme System
|
|
541
647
|
|
|
542
648
|
Visemes are mouth shapes used for lip-sync. LoomLarge includes 15 visemes with automatic jaw coupling.
|
|
543
649
|
|
|
@@ -622,7 +728,7 @@ speak([5, 0, 10, 4]);
|
|
|
622
728
|
|
|
623
729
|
---
|
|
624
730
|
|
|
625
|
-
##
|
|
731
|
+
## 11. Transition System
|
|
626
732
|
|
|
627
733
|
All animated changes in LoomLarge go through the transition system, which provides smooth interpolation with easing.
|
|
628
734
|
|
|
@@ -702,7 +808,7 @@ loom.clearTransitions();
|
|
|
702
808
|
|
|
703
809
|
---
|
|
704
810
|
|
|
705
|
-
##
|
|
811
|
+
## 12. Playback & State Control
|
|
706
812
|
|
|
707
813
|
### Pausing and resuming
|
|
708
814
|
|
|
@@ -757,7 +863,7 @@ loom.dispose();
|
|
|
757
863
|
|
|
758
864
|
---
|
|
759
865
|
|
|
760
|
-
##
|
|
866
|
+
## 13. Hair Physics
|
|
761
867
|
|
|
762
868
|
LoomLarge includes an experimental hair physics system that simulates hair movement based on head motion.
|
|
763
869
|
|
package/dist/index.cjs
CHANGED
|
@@ -245,119 +245,121 @@ var VISEME_KEYS = [
|
|
|
245
245
|
];
|
|
246
246
|
var BONE_AU_TO_BINDINGS = {
|
|
247
247
|
// Head turn and tilt (M51-M56) - use HEAD bone only (NECK should not rotate with head)
|
|
248
|
-
// Three.js Y rotation: positive = counter-clockwise from above = head turns LEFT (character POV)
|
|
249
248
|
51: [
|
|
250
|
-
{ node: "HEAD", channel: "ry", scale: 1, maxDegrees: 30 }
|
|
249
|
+
{ node: "HEAD", channel: "ry", scale: 1, maxDegrees: 30, axis: "yaw" }
|
|
251
250
|
// Head turn left
|
|
252
251
|
],
|
|
253
252
|
52: [
|
|
254
|
-
{ node: "HEAD", channel: "ry", scale: -1, maxDegrees: 30 }
|
|
253
|
+
{ node: "HEAD", channel: "ry", scale: -1, maxDegrees: 30, axis: "yaw" }
|
|
255
254
|
// Head turn right
|
|
256
255
|
],
|
|
257
256
|
53: [
|
|
258
|
-
{ node: "HEAD", channel: "rx", scale: -1, maxDegrees: 20 }
|
|
257
|
+
{ node: "HEAD", channel: "rx", scale: -1, maxDegrees: 20, axis: "pitch" }
|
|
259
258
|
// Head up
|
|
260
259
|
],
|
|
261
260
|
54: [
|
|
262
|
-
{ node: "HEAD", channel: "rx", scale: 1, maxDegrees: 20 }
|
|
261
|
+
{ node: "HEAD", channel: "rx", scale: 1, maxDegrees: 20, axis: "pitch" }
|
|
263
262
|
// Head down
|
|
264
263
|
],
|
|
265
264
|
55: [
|
|
266
|
-
{ node: "HEAD", channel: "rz", scale: -1, maxDegrees: 15 }
|
|
265
|
+
{ node: "HEAD", channel: "rz", scale: -1, maxDegrees: 15, axis: "roll" }
|
|
267
266
|
// Head tilt left
|
|
268
267
|
],
|
|
269
268
|
56: [
|
|
270
|
-
{ node: "HEAD", channel: "rz", scale: 1, maxDegrees: 15 }
|
|
269
|
+
{ node: "HEAD", channel: "rz", scale: 1, maxDegrees: 15, axis: "roll" }
|
|
271
270
|
// Head tilt right
|
|
272
271
|
],
|
|
273
272
|
// Eyes horizontal (yaw) - CC4 rigs use rz for horizontal eye rotation
|
|
274
273
|
61: [
|
|
275
|
-
{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 32 },
|
|
274
|
+
{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 32, axis: "yaw" },
|
|
276
275
|
// Eyes look left
|
|
277
|
-
{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 32 }
|
|
276
|
+
{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 32, axis: "yaw" }
|
|
278
277
|
],
|
|
279
278
|
62: [
|
|
280
|
-
{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 32 },
|
|
279
|
+
{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 32, axis: "yaw" },
|
|
281
280
|
// Eyes look right
|
|
282
|
-
{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 32 }
|
|
281
|
+
{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 32, axis: "yaw" }
|
|
283
282
|
],
|
|
284
283
|
63: [
|
|
285
|
-
{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 32 },
|
|
286
|
-
|
|
284
|
+
{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 32, axis: "pitch" },
|
|
285
|
+
// Eyes Up
|
|
286
|
+
{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 32, axis: "pitch" }
|
|
287
287
|
],
|
|
288
288
|
64: [
|
|
289
|
-
{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 32 },
|
|
290
|
-
|
|
289
|
+
{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 32, axis: "pitch" },
|
|
290
|
+
// Eyes Down
|
|
291
|
+
{ node: "EYE_R", channel: "rx", scale: 1, maxDegrees: 32, axis: "pitch" }
|
|
291
292
|
],
|
|
292
293
|
// Single-eye (Left) — horizontal (rz for CC4) and vertical (rx)
|
|
293
|
-
65: [{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 15 }],
|
|
294
|
-
66: [{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 15 }],
|
|
295
|
-
67: [{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 12 }],
|
|
296
|
-
|
|
294
|
+
65: [{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 15, axis: "yaw" }],
|
|
295
|
+
66: [{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 15, axis: "yaw" }],
|
|
296
|
+
67: [{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 12, axis: "pitch" }],
|
|
297
|
+
// Left Eye Up
|
|
298
|
+
68: [{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 12, axis: "pitch" }],
|
|
299
|
+
// Left Eye Down
|
|
297
300
|
// Single-eye (Right) — horizontal (rz for CC4) and vertical (rx)
|
|
298
|
-
69: [{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 15 }],
|
|
299
|
-
70: [{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 15 }],
|
|
300
|
-
71: [{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 12 }],
|
|
301
|
-
|
|
301
|
+
69: [{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 15, axis: "yaw" }],
|
|
302
|
+
70: [{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 15, axis: "yaw" }],
|
|
303
|
+
71: [{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 12, axis: "pitch" }],
|
|
304
|
+
// Right Eye Up
|
|
305
|
+
72: [{ node: "EYE_R", channel: "rx", scale: 1, maxDegrees: 12, axis: "pitch" }],
|
|
306
|
+
// Right Eye Down
|
|
302
307
|
// Jaw / Mouth
|
|
303
308
|
8: [
|
|
304
309
|
// Lips Toward Each Other - slight jaw open helps sell the lip press
|
|
305
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 8 }
|
|
306
|
-
// Small downward rotation (jaw opening slightly)
|
|
310
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 8, axis: "roll" }
|
|
307
311
|
],
|
|
308
312
|
25: [
|
|
309
313
|
// Lips Part — small jaw open
|
|
310
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 5.84 }
|
|
311
|
-
// 73% of 8
|
|
314
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 5.84, axis: "roll" }
|
|
312
315
|
],
|
|
313
316
|
26: [
|
|
314
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 28 }
|
|
315
|
-
// 73% of 20
|
|
317
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 28, axis: "roll" }
|
|
316
318
|
],
|
|
317
319
|
27: [
|
|
318
320
|
// Mouth Stretch — larger jaw open
|
|
319
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 32 }
|
|
320
|
-
// 73% of 25
|
|
321
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 32, axis: "roll" }
|
|
321
322
|
],
|
|
322
323
|
29: [
|
|
323
324
|
{ node: "JAW", channel: "tz", scale: -1, maxUnits: 0.02 }
|
|
324
|
-
//
|
|
325
|
+
// Translation - no axis needed
|
|
325
326
|
],
|
|
326
327
|
30: [
|
|
327
328
|
// Jaw Left
|
|
328
|
-
{ node: "JAW", channel: "ry", scale: -1, maxDegrees: 5 }
|
|
329
|
+
{ node: "JAW", channel: "ry", scale: -1, maxDegrees: 5, axis: "yaw" }
|
|
329
330
|
],
|
|
330
331
|
35: [
|
|
331
332
|
// Jaw Right
|
|
332
|
-
{ node: "JAW", channel: "ry", scale: 1, maxDegrees: 5 }
|
|
333
|
+
{ node: "JAW", channel: "ry", scale: 1, maxDegrees: 5, axis: "yaw" }
|
|
333
334
|
],
|
|
334
335
|
// Tongue
|
|
335
336
|
19: [
|
|
336
337
|
{ node: "TONGUE", channel: "tz", scale: -1, maxUnits: 8e-3 }
|
|
338
|
+
// Translation - no axis needed
|
|
337
339
|
],
|
|
338
340
|
37: [
|
|
339
341
|
// Tongue Up
|
|
340
|
-
{ node: "TONGUE", channel: "rz", scale: -1, maxDegrees: 45 }
|
|
342
|
+
{ node: "TONGUE", channel: "rz", scale: -1, maxDegrees: 45, axis: "pitch" }
|
|
341
343
|
],
|
|
342
344
|
38: [
|
|
343
345
|
// Tongue Down
|
|
344
|
-
{ node: "TONGUE", channel: "rz", scale: 1, maxDegrees: 45 }
|
|
346
|
+
{ node: "TONGUE", channel: "rz", scale: 1, maxDegrees: 45, axis: "pitch" }
|
|
345
347
|
],
|
|
346
348
|
39: [
|
|
347
349
|
// Tongue Left
|
|
348
|
-
{ node: "TONGUE", channel: "ry", scale: -1, maxDegrees: 10 }
|
|
350
|
+
{ node: "TONGUE", channel: "ry", scale: -1, maxDegrees: 10, axis: "yaw" }
|
|
349
351
|
],
|
|
350
352
|
40: [
|
|
351
353
|
// Tongue Right
|
|
352
|
-
{ node: "TONGUE", channel: "ry", scale: 1, maxDegrees: 10 }
|
|
354
|
+
{ node: "TONGUE", channel: "ry", scale: 1, maxDegrees: 10, axis: "yaw" }
|
|
353
355
|
],
|
|
354
356
|
41: [
|
|
355
357
|
// Tongue Tilt Left
|
|
356
|
-
{ node: "TONGUE", channel: "rx", scale: -1, maxDegrees: 20 }
|
|
358
|
+
{ node: "TONGUE", channel: "rx", scale: -1, maxDegrees: 20, axis: "roll" }
|
|
357
359
|
],
|
|
358
360
|
42: [
|
|
359
361
|
// Tongue Tilt Right
|
|
360
|
-
{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20 }
|
|
362
|
+
{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20, axis: "roll" }
|
|
361
363
|
]
|
|
362
364
|
};
|
|
363
365
|
var isMixedAU = (id) => !!(AU_TO_MORPHS[id]?.length && BONE_AU_TO_BINDINGS[id]?.length);
|
|
@@ -799,8 +801,9 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
799
801
|
if (bindings) {
|
|
800
802
|
for (const binding of bindings) {
|
|
801
803
|
if (binding.channel === "rx" || binding.channel === "ry" || binding.channel === "rz") {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
+
if (binding.axis) {
|
|
805
|
+
this.updateBoneRotation(binding.node, binding.axis, v * binding.scale, binding.maxDegrees ?? 0);
|
|
806
|
+
}
|
|
804
807
|
} else if (binding.channel === "tx" || binding.channel === "ty" || binding.channel === "tz") {
|
|
805
808
|
if (binding.maxUnits !== void 0) {
|
|
806
809
|
this.updateBoneTranslation(binding.node, binding.channel, v * binding.scale, binding.maxUnits);
|
|
@@ -838,8 +841,9 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
838
841
|
}
|
|
839
842
|
for (const binding of bindings) {
|
|
840
843
|
if (binding.channel === "rx" || binding.channel === "ry" || binding.channel === "rz") {
|
|
841
|
-
|
|
842
|
-
|
|
844
|
+
if (binding.axis) {
|
|
845
|
+
handles.push(this.transitionBoneRotation(binding.node, binding.axis, target * binding.scale, binding.maxDegrees ?? 0, durationMs));
|
|
846
|
+
}
|
|
843
847
|
} else if (binding.channel === "tx" || binding.channel === "ty" || binding.channel === "tz") {
|
|
844
848
|
if (binding.maxUnits !== void 0) {
|
|
845
849
|
handles.push(this.transitionBoneTranslation(binding.node, binding.channel, target * binding.scale, binding.maxUnits, durationMs));
|
|
@@ -851,11 +855,16 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
851
855
|
getAU(id) {
|
|
852
856
|
return this.auValues[id] ?? 0;
|
|
853
857
|
}
|
|
854
|
-
|
|
855
|
-
// MORPH CONTROL
|
|
856
|
-
// ============================================================================
|
|
857
|
-
setMorph(key, v, meshNames) {
|
|
858
|
+
setMorph(key, v, meshNamesOrTargets) {
|
|
858
859
|
const val = clamp01(v);
|
|
860
|
+
if (Array.isArray(meshNamesOrTargets) && meshNamesOrTargets.length > 0 && typeof meshNamesOrTargets[0] === "object" && "infl" in meshNamesOrTargets[0]) {
|
|
861
|
+
const targets2 = meshNamesOrTargets;
|
|
862
|
+
for (const target of targets2) {
|
|
863
|
+
target.infl[target.idx] = val;
|
|
864
|
+
}
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const meshNames = meshNamesOrTargets;
|
|
859
868
|
const targetMeshes = meshNames || this.config.morphToMesh?.face || [];
|
|
860
869
|
const cached = this.morphCache.get(key);
|
|
861
870
|
if (cached) {
|
|
@@ -894,11 +903,54 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
894
903
|
this.morphCache.set(key, targets);
|
|
895
904
|
}
|
|
896
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* Resolve morph key to direct targets for ultra-fast repeated access.
|
|
908
|
+
* Use this when you need to set the same morph many times (e.g., in animation loops).
|
|
909
|
+
*/
|
|
910
|
+
resolveMorphTargets(key, meshNames) {
|
|
911
|
+
const cached = this.morphCache.get(key);
|
|
912
|
+
if (cached) return cached;
|
|
913
|
+
const targetMeshes = meshNames || this.config.morphToMesh?.face || [];
|
|
914
|
+
const targets = [];
|
|
915
|
+
if (targetMeshes.length) {
|
|
916
|
+
for (const name of targetMeshes) {
|
|
917
|
+
const mesh = this.meshByName.get(name);
|
|
918
|
+
if (!mesh) continue;
|
|
919
|
+
const dict = mesh.morphTargetDictionary;
|
|
920
|
+
const infl = mesh.morphTargetInfluences;
|
|
921
|
+
if (!dict || !infl) continue;
|
|
922
|
+
const idx = dict[key];
|
|
923
|
+
if (idx !== void 0) {
|
|
924
|
+
targets.push({ infl, idx });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
for (const mesh of this.meshes) {
|
|
929
|
+
const dict = mesh.morphTargetDictionary;
|
|
930
|
+
const infl = mesh.morphTargetInfluences;
|
|
931
|
+
if (!dict || !infl) continue;
|
|
932
|
+
const idx = dict[key];
|
|
933
|
+
if (idx !== void 0) {
|
|
934
|
+
targets.push({ infl, idx });
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (targets.length > 0) {
|
|
939
|
+
this.morphCache.set(key, targets);
|
|
940
|
+
}
|
|
941
|
+
return targets;
|
|
942
|
+
}
|
|
897
943
|
transitionMorph(key, to, durationMs = 120, meshNames) {
|
|
898
944
|
const transitionKey = `morph_${key}`;
|
|
899
945
|
const from = this.getMorphValue(key);
|
|
900
946
|
const target = clamp01(to);
|
|
901
|
-
|
|
947
|
+
const targets = this.resolveMorphTargets(key, meshNames);
|
|
948
|
+
return this.animation.addTransition(transitionKey, from, target, durationMs, (value) => {
|
|
949
|
+
const val = clamp01(value);
|
|
950
|
+
for (const t of targets) {
|
|
951
|
+
t.infl[t.idx] = val;
|
|
952
|
+
}
|
|
953
|
+
});
|
|
902
954
|
}
|
|
903
955
|
// ============================================================================
|
|
904
956
|
// VISEME CONTROL
|
|
@@ -1001,6 +1053,33 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
1001
1053
|
});
|
|
1002
1054
|
return result;
|
|
1003
1055
|
}
|
|
1056
|
+
/** Get all morph targets grouped by mesh name */
|
|
1057
|
+
getMorphTargets() {
|
|
1058
|
+
const result = {};
|
|
1059
|
+
for (const mesh of this.meshes) {
|
|
1060
|
+
const dict = mesh.morphTargetDictionary;
|
|
1061
|
+
if (dict) {
|
|
1062
|
+
result[mesh.name] = Object.keys(dict).sort();
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return result;
|
|
1066
|
+
}
|
|
1067
|
+
/** Get all resolved bone names and their current transforms */
|
|
1068
|
+
getBones() {
|
|
1069
|
+
const result = {};
|
|
1070
|
+
for (const name of Object.keys(this.bones)) {
|
|
1071
|
+
const entry = this.bones[name];
|
|
1072
|
+
if (entry) {
|
|
1073
|
+
const pos = entry.obj.position;
|
|
1074
|
+
const rot = entry.obj.rotation;
|
|
1075
|
+
result[name] = {
|
|
1076
|
+
position: [pos.x, pos.y, pos.z],
|
|
1077
|
+
rotation: [rot.x * 180 / Math.PI, rot.y * 180 / Math.PI, rot.z * 180 / Math.PI]
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1004
1083
|
setMeshVisible(meshName, visible) {
|
|
1005
1084
|
if (!this.model) return;
|
|
1006
1085
|
this.model.traverse((obj) => {
|
|
@@ -1009,6 +1088,70 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
1009
1088
|
}
|
|
1010
1089
|
});
|
|
1011
1090
|
}
|
|
1091
|
+
/** Get material config for a mesh */
|
|
1092
|
+
getMeshMaterialConfig(meshName) {
|
|
1093
|
+
if (!this.model) return null;
|
|
1094
|
+
let result = null;
|
|
1095
|
+
this.model.traverse((obj) => {
|
|
1096
|
+
if (obj.isMesh && obj.name === meshName) {
|
|
1097
|
+
const mat = obj.material;
|
|
1098
|
+
if (mat) {
|
|
1099
|
+
let blendingName = "Normal";
|
|
1100
|
+
for (const [name, value] of Object.entries(_LoomLargeThree.BLENDING_MODES)) {
|
|
1101
|
+
if (mat.blending === value) {
|
|
1102
|
+
blendingName = name;
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
result = {
|
|
1107
|
+
renderOrder: obj.renderOrder,
|
|
1108
|
+
transparent: mat.transparent,
|
|
1109
|
+
opacity: mat.opacity,
|
|
1110
|
+
depthWrite: mat.depthWrite,
|
|
1111
|
+
depthTest: mat.depthTest,
|
|
1112
|
+
blending: blendingName
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
return result;
|
|
1118
|
+
}
|
|
1119
|
+
/** Set material config for a mesh */
|
|
1120
|
+
setMeshMaterialConfig(meshName, config) {
|
|
1121
|
+
if (!this.model) return;
|
|
1122
|
+
this.model.traverse((obj) => {
|
|
1123
|
+
if (obj.isMesh && obj.name === meshName) {
|
|
1124
|
+
const mat = obj.material;
|
|
1125
|
+
if (config.renderOrder !== void 0) {
|
|
1126
|
+
obj.renderOrder = config.renderOrder;
|
|
1127
|
+
}
|
|
1128
|
+
if (mat) {
|
|
1129
|
+
if (config.opacity !== void 0) {
|
|
1130
|
+
mat.opacity = config.opacity;
|
|
1131
|
+
if (config.opacity < 1 && config.transparent === void 0) {
|
|
1132
|
+
mat.transparent = true;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (config.transparent !== void 0) {
|
|
1136
|
+
mat.transparent = config.transparent;
|
|
1137
|
+
}
|
|
1138
|
+
if (config.depthWrite !== void 0) {
|
|
1139
|
+
mat.depthWrite = config.depthWrite;
|
|
1140
|
+
}
|
|
1141
|
+
if (config.depthTest !== void 0) {
|
|
1142
|
+
mat.depthTest = config.depthTest;
|
|
1143
|
+
}
|
|
1144
|
+
if (config.blending !== void 0) {
|
|
1145
|
+
const blendValue = _LoomLargeThree.BLENDING_MODES[config.blending];
|
|
1146
|
+
if (blendValue !== void 0) {
|
|
1147
|
+
mat.blending = blendValue;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
mat.needsUpdate = true;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1012
1155
|
// ============================================================================
|
|
1013
1156
|
// CONFIGURATION
|
|
1014
1157
|
// ============================================================================
|
|
@@ -1200,6 +1343,13 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
1200
1343
|
if (typeof settings.depthTest === "boolean") {
|
|
1201
1344
|
obj.material.depthTest = settings.depthTest;
|
|
1202
1345
|
}
|
|
1346
|
+
if (typeof settings.blending === "string") {
|
|
1347
|
+
const blendValue = _LoomLargeThree.BLENDING_MODES[settings.blending];
|
|
1348
|
+
if (blendValue !== void 0) {
|
|
1349
|
+
obj.material.blending = blendValue;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
obj.material.needsUpdate = true;
|
|
1203
1353
|
}
|
|
1204
1354
|
});
|
|
1205
1355
|
}
|
|
@@ -1223,6 +1373,19 @@ __publicField(_LoomLargeThree, "VISEME_JAW_AMOUNTS", [
|
|
|
1223
1373
|
0.4
|
|
1224
1374
|
]);
|
|
1225
1375
|
__publicField(_LoomLargeThree, "JAW_MAX_DEGREES", 28);
|
|
1376
|
+
/** Blending mode options for Three.js materials */
|
|
1377
|
+
__publicField(_LoomLargeThree, "BLENDING_MODES", {
|
|
1378
|
+
"Normal": 1,
|
|
1379
|
+
// THREE.NormalBlending
|
|
1380
|
+
"Additive": 2,
|
|
1381
|
+
// THREE.AdditiveBlending
|
|
1382
|
+
"Subtractive": 3,
|
|
1383
|
+
// THREE.SubtractiveBlending
|
|
1384
|
+
"Multiply": 4,
|
|
1385
|
+
// THREE.MultiplyBlending
|
|
1386
|
+
"None": 0
|
|
1387
|
+
// THREE.NoBlending
|
|
1388
|
+
});
|
|
1226
1389
|
var LoomLargeThree = _LoomLargeThree;
|
|
1227
1390
|
function collectMorphMeshes(root) {
|
|
1228
1391
|
const meshes = [];
|
|
@@ -1236,6 +1399,20 @@ function collectMorphMeshes(root) {
|
|
|
1236
1399
|
return meshes;
|
|
1237
1400
|
}
|
|
1238
1401
|
|
|
1402
|
+
// src/mappings/types.ts
|
|
1403
|
+
var BLENDING_MODES = {
|
|
1404
|
+
"Normal": 1,
|
|
1405
|
+
// THREE.NormalBlending
|
|
1406
|
+
"Additive": 2,
|
|
1407
|
+
// THREE.AdditiveBlending
|
|
1408
|
+
"Subtractive": 3,
|
|
1409
|
+
// THREE.SubtractiveBlending
|
|
1410
|
+
"Multiply": 4,
|
|
1411
|
+
// THREE.MultiplyBlending
|
|
1412
|
+
"None": 0
|
|
1413
|
+
// THREE.NoBlending
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1239
1416
|
// src/physics/HairPhysics.ts
|
|
1240
1417
|
var DEFAULT_HAIR_PHYSICS_CONFIG = {
|
|
1241
1418
|
mass: 1,
|
|
@@ -1364,6 +1541,7 @@ exports.AU_INFO = AU_INFO;
|
|
|
1364
1541
|
exports.AU_MIX_DEFAULTS = AU_MIX_DEFAULTS;
|
|
1365
1542
|
exports.AU_TO_MORPHS = AU_TO_MORPHS;
|
|
1366
1543
|
exports.AnimationThree = AnimationThree;
|
|
1544
|
+
exports.BLENDING_MODES = BLENDING_MODES;
|
|
1367
1545
|
exports.BONE_AU_TO_BINDINGS = BONE_AU_TO_BINDINGS;
|
|
1368
1546
|
exports.CC4_BONE_NODES = CC4_BONE_NODES;
|
|
1369
1547
|
exports.CC4_EYE_MESH_NODES = CC4_EYE_MESH_NODES;
|