loomlarge 1.0.0 → 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 +160 -34
- package/dist/index.cjs +436 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -1
- package/dist/index.d.ts +150 -1
- package/dist/index.js +422 -50
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Clock } from 'three';
|
|
2
|
+
|
|
1
3
|
var __defProp = Object.defineProperty;
|
|
2
4
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
5
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -239,121 +241,224 @@ var VISEME_KEYS = [
|
|
|
239
241
|
];
|
|
240
242
|
var BONE_AU_TO_BINDINGS = {
|
|
241
243
|
// Head turn and tilt (M51-M56) - use HEAD bone only (NECK should not rotate with head)
|
|
242
|
-
// Three.js Y rotation: positive = counter-clockwise from above = head turns LEFT (character POV)
|
|
243
244
|
51: [
|
|
244
|
-
{ node: "HEAD", channel: "ry", scale: 1, maxDegrees: 30 }
|
|
245
|
+
{ node: "HEAD", channel: "ry", scale: 1, maxDegrees: 30, axis: "yaw" }
|
|
245
246
|
// Head turn left
|
|
246
247
|
],
|
|
247
248
|
52: [
|
|
248
|
-
{ node: "HEAD", channel: "ry", scale: -1, maxDegrees: 30 }
|
|
249
|
+
{ node: "HEAD", channel: "ry", scale: -1, maxDegrees: 30, axis: "yaw" }
|
|
249
250
|
// Head turn right
|
|
250
251
|
],
|
|
251
252
|
53: [
|
|
252
|
-
{ node: "HEAD", channel: "rx", scale: -1, maxDegrees: 20 }
|
|
253
|
+
{ node: "HEAD", channel: "rx", scale: -1, maxDegrees: 20, axis: "pitch" }
|
|
253
254
|
// Head up
|
|
254
255
|
],
|
|
255
256
|
54: [
|
|
256
|
-
{ node: "HEAD", channel: "rx", scale: 1, maxDegrees: 20 }
|
|
257
|
+
{ node: "HEAD", channel: "rx", scale: 1, maxDegrees: 20, axis: "pitch" }
|
|
257
258
|
// Head down
|
|
258
259
|
],
|
|
259
260
|
55: [
|
|
260
|
-
{ node: "HEAD", channel: "rz", scale: -1, maxDegrees: 15 }
|
|
261
|
+
{ node: "HEAD", channel: "rz", scale: -1, maxDegrees: 15, axis: "roll" }
|
|
261
262
|
// Head tilt left
|
|
262
263
|
],
|
|
263
264
|
56: [
|
|
264
|
-
{ node: "HEAD", channel: "rz", scale: 1, maxDegrees: 15 }
|
|
265
|
+
{ node: "HEAD", channel: "rz", scale: 1, maxDegrees: 15, axis: "roll" }
|
|
265
266
|
// Head tilt right
|
|
266
267
|
],
|
|
267
268
|
// Eyes horizontal (yaw) - CC4 rigs use rz for horizontal eye rotation
|
|
268
269
|
61: [
|
|
269
|
-
{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 32 },
|
|
270
|
+
{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 32, axis: "yaw" },
|
|
270
271
|
// Eyes look left
|
|
271
|
-
{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 32 }
|
|
272
|
+
{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 32, axis: "yaw" }
|
|
272
273
|
],
|
|
273
274
|
62: [
|
|
274
|
-
{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 32 },
|
|
275
|
+
{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 32, axis: "yaw" },
|
|
275
276
|
// Eyes look right
|
|
276
|
-
{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 32 }
|
|
277
|
+
{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 32, axis: "yaw" }
|
|
277
278
|
],
|
|
278
279
|
63: [
|
|
279
|
-
{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 32 },
|
|
280
|
-
|
|
280
|
+
{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 32, axis: "pitch" },
|
|
281
|
+
// Eyes Up
|
|
282
|
+
{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 32, axis: "pitch" }
|
|
281
283
|
],
|
|
282
284
|
64: [
|
|
283
|
-
{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 32 },
|
|
284
|
-
|
|
285
|
+
{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 32, axis: "pitch" },
|
|
286
|
+
// Eyes Down
|
|
287
|
+
{ node: "EYE_R", channel: "rx", scale: 1, maxDegrees: 32, axis: "pitch" }
|
|
285
288
|
],
|
|
286
289
|
// Single-eye (Left) — horizontal (rz for CC4) and vertical (rx)
|
|
287
|
-
65: [{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 15 }],
|
|
288
|
-
66: [{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 15 }],
|
|
289
|
-
67: [{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 12 }],
|
|
290
|
-
|
|
290
|
+
65: [{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 15, axis: "yaw" }],
|
|
291
|
+
66: [{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 15, axis: "yaw" }],
|
|
292
|
+
67: [{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 12, axis: "pitch" }],
|
|
293
|
+
// Left Eye Up
|
|
294
|
+
68: [{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 12, axis: "pitch" }],
|
|
295
|
+
// Left Eye Down
|
|
291
296
|
// Single-eye (Right) — horizontal (rz for CC4) and vertical (rx)
|
|
292
|
-
69: [{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 15 }],
|
|
293
|
-
70: [{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 15 }],
|
|
294
|
-
71: [{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 12 }],
|
|
295
|
-
|
|
297
|
+
69: [{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 15, axis: "yaw" }],
|
|
298
|
+
70: [{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 15, axis: "yaw" }],
|
|
299
|
+
71: [{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 12, axis: "pitch" }],
|
|
300
|
+
// Right Eye Up
|
|
301
|
+
72: [{ node: "EYE_R", channel: "rx", scale: 1, maxDegrees: 12, axis: "pitch" }],
|
|
302
|
+
// Right Eye Down
|
|
296
303
|
// Jaw / Mouth
|
|
297
304
|
8: [
|
|
298
305
|
// Lips Toward Each Other - slight jaw open helps sell the lip press
|
|
299
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 8 }
|
|
300
|
-
// Small downward rotation (jaw opening slightly)
|
|
306
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 8, axis: "roll" }
|
|
301
307
|
],
|
|
302
308
|
25: [
|
|
303
309
|
// Lips Part — small jaw open
|
|
304
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 5.84 }
|
|
305
|
-
// 73% of 8
|
|
310
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 5.84, axis: "roll" }
|
|
306
311
|
],
|
|
307
312
|
26: [
|
|
308
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 28 }
|
|
309
|
-
// 73% of 20
|
|
313
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 28, axis: "roll" }
|
|
310
314
|
],
|
|
311
315
|
27: [
|
|
312
316
|
// Mouth Stretch — larger jaw open
|
|
313
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 32 }
|
|
314
|
-
// 73% of 25
|
|
317
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 32, axis: "roll" }
|
|
315
318
|
],
|
|
316
319
|
29: [
|
|
317
320
|
{ node: "JAW", channel: "tz", scale: -1, maxUnits: 0.02 }
|
|
318
|
-
//
|
|
321
|
+
// Translation - no axis needed
|
|
319
322
|
],
|
|
320
323
|
30: [
|
|
321
324
|
// Jaw Left
|
|
322
|
-
{ node: "JAW", channel: "ry", scale: -1, maxDegrees: 5 }
|
|
325
|
+
{ node: "JAW", channel: "ry", scale: -1, maxDegrees: 5, axis: "yaw" }
|
|
323
326
|
],
|
|
324
327
|
35: [
|
|
325
328
|
// Jaw Right
|
|
326
|
-
{ node: "JAW", channel: "ry", scale: 1, maxDegrees: 5 }
|
|
329
|
+
{ node: "JAW", channel: "ry", scale: 1, maxDegrees: 5, axis: "yaw" }
|
|
327
330
|
],
|
|
328
331
|
// Tongue
|
|
329
332
|
19: [
|
|
330
333
|
{ node: "TONGUE", channel: "tz", scale: -1, maxUnits: 8e-3 }
|
|
334
|
+
// Translation - no axis needed
|
|
331
335
|
],
|
|
332
336
|
37: [
|
|
333
337
|
// Tongue Up
|
|
334
|
-
{ node: "TONGUE", channel: "rz", scale: -1, maxDegrees: 45 }
|
|
338
|
+
{ node: "TONGUE", channel: "rz", scale: -1, maxDegrees: 45, axis: "pitch" }
|
|
335
339
|
],
|
|
336
340
|
38: [
|
|
337
341
|
// Tongue Down
|
|
338
|
-
{ node: "TONGUE", channel: "rz", scale: 1, maxDegrees: 45 }
|
|
342
|
+
{ node: "TONGUE", channel: "rz", scale: 1, maxDegrees: 45, axis: "pitch" }
|
|
339
343
|
],
|
|
340
344
|
39: [
|
|
341
345
|
// Tongue Left
|
|
342
|
-
{ node: "TONGUE", channel: "ry", scale: -1, maxDegrees: 10 }
|
|
346
|
+
{ node: "TONGUE", channel: "ry", scale: -1, maxDegrees: 10, axis: "yaw" }
|
|
343
347
|
],
|
|
344
348
|
40: [
|
|
345
349
|
// Tongue Right
|
|
346
|
-
{ node: "TONGUE", channel: "ry", scale: 1, maxDegrees: 10 }
|
|
350
|
+
{ node: "TONGUE", channel: "ry", scale: 1, maxDegrees: 10, axis: "yaw" }
|
|
347
351
|
],
|
|
348
352
|
41: [
|
|
349
353
|
// Tongue Tilt Left
|
|
350
|
-
{ node: "TONGUE", channel: "rx", scale: -1, maxDegrees: 20 }
|
|
354
|
+
{ node: "TONGUE", channel: "rx", scale: -1, maxDegrees: 20, axis: "roll" }
|
|
351
355
|
],
|
|
352
356
|
42: [
|
|
353
357
|
// Tongue Tilt Right
|
|
354
|
-
{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20 }
|
|
358
|
+
{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20, axis: "roll" }
|
|
355
359
|
]
|
|
356
360
|
};
|
|
361
|
+
var isMixedAU = (id) => !!(AU_TO_MORPHS[id]?.length && BONE_AU_TO_BINDINGS[id]?.length);
|
|
362
|
+
var hasLeftRightMorphs = (auId) => {
|
|
363
|
+
const keys = AU_TO_MORPHS[auId] || [];
|
|
364
|
+
return keys.some((k) => /_L$|_R$| L$| R$|Left$|Right$/i.test(k));
|
|
365
|
+
};
|
|
366
|
+
var COMPOSITE_ROTATIONS = [
|
|
367
|
+
{
|
|
368
|
+
node: "JAW",
|
|
369
|
+
pitch: { aus: [25, 26, 27], axis: "rz" },
|
|
370
|
+
// Jaw drop (opens mouth downward)
|
|
371
|
+
yaw: { aus: [30, 35], axis: "ry", negative: 30, positive: 35 },
|
|
372
|
+
// Jaw lateral (left/right)
|
|
373
|
+
roll: null
|
|
374
|
+
// Jaw doesn't have roll
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
node: "HEAD",
|
|
378
|
+
pitch: { aus: [54, 53], axis: "rx", negative: 54, positive: 53 },
|
|
379
|
+
// Head down/up
|
|
380
|
+
yaw: { aus: [51, 52], axis: "ry", negative: 51, positive: 52 },
|
|
381
|
+
// Head turn left/right
|
|
382
|
+
roll: { aus: [55, 56], axis: "rz", negative: 55, positive: 56 }
|
|
383
|
+
// Head tilt left/right
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
node: "EYE_L",
|
|
387
|
+
pitch: { aus: [64, 63], axis: "rx", negative: 64, positive: 63 },
|
|
388
|
+
// Eyes down/up
|
|
389
|
+
yaw: { aus: [61, 62], axis: "rz", negative: 61, positive: 62 },
|
|
390
|
+
// Eyes left/right (rz for CC4)
|
|
391
|
+
roll: null
|
|
392
|
+
// Eyes don't have roll
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
node: "EYE_R",
|
|
396
|
+
pitch: { aus: [64, 63], axis: "rx", negative: 64, positive: 63 },
|
|
397
|
+
// Eyes down/up
|
|
398
|
+
yaw: { aus: [61, 62], axis: "rz", negative: 61, positive: 62 },
|
|
399
|
+
// Eyes left/right (rz for CC4)
|
|
400
|
+
roll: null
|
|
401
|
+
// Eyes don't have roll
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
node: "TONGUE",
|
|
405
|
+
pitch: { aus: [38, 37], axis: "rz", negative: 38, positive: 37 },
|
|
406
|
+
// Tongue down/up
|
|
407
|
+
yaw: { aus: [39, 40], axis: "ry", negative: 39, positive: 40 },
|
|
408
|
+
// Tongue left/right
|
|
409
|
+
roll: { aus: [41, 42], axis: "rx", negative: 41, positive: 42 }
|
|
410
|
+
// Tongue tilt left/right
|
|
411
|
+
}
|
|
412
|
+
];
|
|
413
|
+
var CONTINUUM_PAIRS_MAP = {
|
|
414
|
+
// Eyes horizontal (yaw) - both eyes share same AUs
|
|
415
|
+
61: { pairId: 62, isNegative: true, axis: "yaw", node: "EYE_L" },
|
|
416
|
+
62: { pairId: 61, isNegative: false, axis: "yaw", node: "EYE_L" },
|
|
417
|
+
// Eyes vertical (pitch)
|
|
418
|
+
64: { pairId: 63, isNegative: true, axis: "pitch", node: "EYE_L" },
|
|
419
|
+
63: { pairId: 64, isNegative: false, axis: "pitch", node: "EYE_L" },
|
|
420
|
+
// Head yaw (turn left/right)
|
|
421
|
+
51: { pairId: 52, isNegative: true, axis: "yaw", node: "HEAD" },
|
|
422
|
+
52: { pairId: 51, isNegative: false, axis: "yaw", node: "HEAD" },
|
|
423
|
+
// Head pitch (up/down)
|
|
424
|
+
54: { pairId: 53, isNegative: true, axis: "pitch", node: "HEAD" },
|
|
425
|
+
53: { pairId: 54, isNegative: false, axis: "pitch", node: "HEAD" },
|
|
426
|
+
// Head roll (tilt left/right)
|
|
427
|
+
55: { pairId: 56, isNegative: true, axis: "roll", node: "HEAD" },
|
|
428
|
+
56: { pairId: 55, isNegative: false, axis: "roll", node: "HEAD" },
|
|
429
|
+
// Jaw yaw (left/right)
|
|
430
|
+
30: { pairId: 35, isNegative: true, axis: "yaw", node: "JAW" },
|
|
431
|
+
35: { pairId: 30, isNegative: false, axis: "yaw", node: "JAW" },
|
|
432
|
+
// Tongue yaw (left/right)
|
|
433
|
+
39: { pairId: 40, isNegative: true, axis: "yaw", node: "TONGUE" },
|
|
434
|
+
40: { pairId: 39, isNegative: false, axis: "yaw", node: "TONGUE" },
|
|
435
|
+
// Tongue pitch (up/down)
|
|
436
|
+
38: { pairId: 37, isNegative: true, axis: "pitch", node: "TONGUE" },
|
|
437
|
+
37: { pairId: 38, isNegative: false, axis: "pitch", node: "TONGUE" },
|
|
438
|
+
// Tongue roll (tilt left/right)
|
|
439
|
+
41: { pairId: 42, isNegative: true, axis: "roll", node: "TONGUE" },
|
|
440
|
+
42: { pairId: 41, isNegative: false, axis: "roll", node: "TONGUE" },
|
|
441
|
+
// Extended tongue morphs (continuum pairs)
|
|
442
|
+
73: { pairId: 74, isNegative: true, axis: "yaw", node: "TONGUE" },
|
|
443
|
+
// Tongue Narrow/Wide
|
|
444
|
+
74: { pairId: 73, isNegative: false, axis: "yaw", node: "TONGUE" },
|
|
445
|
+
76: { pairId: 77, isNegative: false, axis: "pitch", node: "TONGUE" },
|
|
446
|
+
// Tongue Tip Up/Down
|
|
447
|
+
77: { pairId: 76, isNegative: true, axis: "pitch", node: "TONGUE" }
|
|
448
|
+
};
|
|
449
|
+
var CONTINUUM_LABELS = {
|
|
450
|
+
"61-62": "Eyes \u2014 Horizontal",
|
|
451
|
+
"64-63": "Eyes \u2014 Vertical",
|
|
452
|
+
"51-52": "Head \u2014 Horizontal",
|
|
453
|
+
"54-53": "Head \u2014 Vertical",
|
|
454
|
+
"55-56": "Head \u2014 Tilt",
|
|
455
|
+
"30-35": "Jaw \u2014 Horizontal",
|
|
456
|
+
"38-37": "Tongue \u2014 Vertical",
|
|
457
|
+
"39-40": "Tongue \u2014 Horizontal",
|
|
458
|
+
"41-42": "Tongue \u2014 Tilt",
|
|
459
|
+
"73-74": "Tongue \u2014 Width",
|
|
460
|
+
"76-77": "Tongue Tip \u2014 Vertical"
|
|
461
|
+
};
|
|
357
462
|
var CC4_BONE_NODES = {
|
|
358
463
|
EYE_L: "CC_Base_L_Eye",
|
|
359
464
|
EYE_R: "CC_Base_R_Eye",
|
|
@@ -486,6 +591,41 @@ var AU_MIX_DEFAULTS = {
|
|
|
486
591
|
35: 0.5
|
|
487
592
|
// jaw left/right
|
|
488
593
|
};
|
|
594
|
+
var CC4_MESHES = {
|
|
595
|
+
// Body (6 meshes, 80 morphs each) - default render order 0
|
|
596
|
+
"CC_Base_Body_1": { category: "body", morphCount: 80 },
|
|
597
|
+
"CC_Base_Body_2": { category: "body", morphCount: 80 },
|
|
598
|
+
"CC_Base_Body_3": { category: "body", morphCount: 80 },
|
|
599
|
+
"CC_Base_Body_4": { category: "body", morphCount: 80 },
|
|
600
|
+
"CC_Base_Body_5": { category: "body", morphCount: 80 },
|
|
601
|
+
"CC_Base_Body_6": { category: "body", morphCount: 80 },
|
|
602
|
+
// Eyes (bone-driven, no morphs) - render first (behind everything)
|
|
603
|
+
"CC_Base_Eye": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
604
|
+
"CC_Base_Eye_1": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
605
|
+
"CC_Base_Eye_2": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
606
|
+
"CC_Base_Eye_3": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
607
|
+
"CC_Base_Eye_4": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
608
|
+
// Eye occlusion (94 morphs each) - render on top of eyes with transparency support
|
|
609
|
+
"CC_Base_EyeOcclusion_1": { category: "eyeOcclusion", morphCount: 94, material: { renderOrder: 2, transparent: true, opacity: 1, depthWrite: true, depthTest: true, blending: "Normal" } },
|
|
610
|
+
"CC_Base_EyeOcclusion_2": { category: "eyeOcclusion", morphCount: 94, material: { renderOrder: 2, transparent: true, opacity: 1, depthWrite: true, depthTest: true, blending: "Normal" } },
|
|
611
|
+
// Tear lines (90 morphs each) - on top of eyes/face
|
|
612
|
+
"CC_Base_TearLine_1": { category: "tearLine", morphCount: 90, material: { renderOrder: 2 } },
|
|
613
|
+
"CC_Base_TearLine_2": { category: "tearLine", morphCount: 90, material: { renderOrder: 2 } },
|
|
614
|
+
// Cornea (no morphs) - render first with eyes
|
|
615
|
+
"CC_Base_Cornea": { category: "cornea", morphCount: 0, material: { renderOrder: -10 } },
|
|
616
|
+
"CC_Base_Cornea_1": { category: "cornea", morphCount: 0, material: { renderOrder: -10 } },
|
|
617
|
+
// Teeth (no morphs, follow jaw bone) - default render order
|
|
618
|
+
"CC_Base_Teeth_1": { category: "teeth", morphCount: 0 },
|
|
619
|
+
"CC_Base_Teeth_2": { category: "teeth", morphCount: 0 },
|
|
620
|
+
// Tongue (23 morphs) - default render order
|
|
621
|
+
"CC_Base_Tongue": { category: "tongue", morphCount: 23 },
|
|
622
|
+
// Eyebrows (91 morphs each) - above face
|
|
623
|
+
"Male_Bushy_1": { category: "eyebrow", morphCount: 91, material: { renderOrder: 5 } },
|
|
624
|
+
"Male_Bushy_2": { category: "eyebrow", morphCount: 91, material: { renderOrder: 5 } },
|
|
625
|
+
// Hair (14 styling morphs each) - render last (on top of everything)
|
|
626
|
+
"Side_part_wavy_1": { category: "hair", morphCount: 14, material: { renderOrder: 10 } },
|
|
627
|
+
"Side_part_wavy_2": { category: "hair", morphCount: 14, material: { renderOrder: 10 } }
|
|
628
|
+
};
|
|
489
629
|
var MORPH_TO_MESH = {
|
|
490
630
|
// Face/AU morphs affect the main face mesh and both eyebrow meshes.
|
|
491
631
|
face: ["CC_Base_Body_1", "Male_Bushy_1", "Male_Bushy_2"],
|
|
@@ -537,6 +677,10 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
537
677
|
__publicField(this, "mixWeights", {});
|
|
538
678
|
// Viseme state
|
|
539
679
|
__publicField(this, "visemeValues", new Array(15).fill(0));
|
|
680
|
+
// Internal RAF loop
|
|
681
|
+
__publicField(this, "clock", new Clock());
|
|
682
|
+
__publicField(this, "rafId", null);
|
|
683
|
+
__publicField(this, "running", false);
|
|
540
684
|
this.config = config.auMappings || CC4_PRESET;
|
|
541
685
|
this.mixWeights = { ...this.config.auMixDefaults };
|
|
542
686
|
this.animation = animation || new AnimationThree();
|
|
@@ -573,6 +717,7 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
573
717
|
this.rigReady = true;
|
|
574
718
|
this.missingBoneWarnings.clear();
|
|
575
719
|
this.initBoneRotations();
|
|
720
|
+
this.applyMeshMaterialSettings(model);
|
|
576
721
|
}
|
|
577
722
|
update(deltaSeconds) {
|
|
578
723
|
const dtSeconds = Math.max(0, deltaSeconds || 0);
|
|
@@ -580,7 +725,30 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
580
725
|
this.animation.tick(dtSeconds);
|
|
581
726
|
this.flushPendingComposites();
|
|
582
727
|
}
|
|
728
|
+
/** Start the internal RAF loop */
|
|
729
|
+
start() {
|
|
730
|
+
if (this.running) return;
|
|
731
|
+
this.running = true;
|
|
732
|
+
this.clock.start();
|
|
733
|
+
const tick = () => {
|
|
734
|
+
if (!this.running) return;
|
|
735
|
+
const dt = this.clock.getDelta();
|
|
736
|
+
this.update(dt);
|
|
737
|
+
this.rafId = requestAnimationFrame(tick);
|
|
738
|
+
};
|
|
739
|
+
this.rafId = requestAnimationFrame(tick);
|
|
740
|
+
}
|
|
741
|
+
/** Stop the internal RAF loop */
|
|
742
|
+
stop() {
|
|
743
|
+
this.running = false;
|
|
744
|
+
if (this.rafId !== null) {
|
|
745
|
+
cancelAnimationFrame(this.rafId);
|
|
746
|
+
this.rafId = null;
|
|
747
|
+
}
|
|
748
|
+
this.clock.stop();
|
|
749
|
+
}
|
|
583
750
|
dispose() {
|
|
751
|
+
this.stop();
|
|
584
752
|
this.clearTransitions();
|
|
585
753
|
this.meshes = [];
|
|
586
754
|
this.model = null;
|
|
@@ -629,8 +797,9 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
629
797
|
if (bindings) {
|
|
630
798
|
for (const binding of bindings) {
|
|
631
799
|
if (binding.channel === "rx" || binding.channel === "ry" || binding.channel === "rz") {
|
|
632
|
-
|
|
633
|
-
|
|
800
|
+
if (binding.axis) {
|
|
801
|
+
this.updateBoneRotation(binding.node, binding.axis, v * binding.scale, binding.maxDegrees ?? 0);
|
|
802
|
+
}
|
|
634
803
|
} else if (binding.channel === "tx" || binding.channel === "ty" || binding.channel === "tz") {
|
|
635
804
|
if (binding.maxUnits !== void 0) {
|
|
636
805
|
this.updateBoneTranslation(binding.node, binding.channel, v * binding.scale, binding.maxUnits);
|
|
@@ -668,8 +837,9 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
668
837
|
}
|
|
669
838
|
for (const binding of bindings) {
|
|
670
839
|
if (binding.channel === "rx" || binding.channel === "ry" || binding.channel === "rz") {
|
|
671
|
-
|
|
672
|
-
|
|
840
|
+
if (binding.axis) {
|
|
841
|
+
handles.push(this.transitionBoneRotation(binding.node, binding.axis, target * binding.scale, binding.maxDegrees ?? 0, durationMs));
|
|
842
|
+
}
|
|
673
843
|
} else if (binding.channel === "tx" || binding.channel === "ty" || binding.channel === "tz") {
|
|
674
844
|
if (binding.maxUnits !== void 0) {
|
|
675
845
|
handles.push(this.transitionBoneTranslation(binding.node, binding.channel, target * binding.scale, binding.maxUnits, durationMs));
|
|
@@ -681,11 +851,16 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
681
851
|
getAU(id) {
|
|
682
852
|
return this.auValues[id] ?? 0;
|
|
683
853
|
}
|
|
684
|
-
|
|
685
|
-
// MORPH CONTROL
|
|
686
|
-
// ============================================================================
|
|
687
|
-
setMorph(key, v, meshNames) {
|
|
854
|
+
setMorph(key, v, meshNamesOrTargets) {
|
|
688
855
|
const val = clamp01(v);
|
|
856
|
+
if (Array.isArray(meshNamesOrTargets) && meshNamesOrTargets.length > 0 && typeof meshNamesOrTargets[0] === "object" && "infl" in meshNamesOrTargets[0]) {
|
|
857
|
+
const targets2 = meshNamesOrTargets;
|
|
858
|
+
for (const target of targets2) {
|
|
859
|
+
target.infl[target.idx] = val;
|
|
860
|
+
}
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const meshNames = meshNamesOrTargets;
|
|
689
864
|
const targetMeshes = meshNames || this.config.morphToMesh?.face || [];
|
|
690
865
|
const cached = this.morphCache.get(key);
|
|
691
866
|
if (cached) {
|
|
@@ -724,11 +899,54 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
724
899
|
this.morphCache.set(key, targets);
|
|
725
900
|
}
|
|
726
901
|
}
|
|
902
|
+
/**
|
|
903
|
+
* Resolve morph key to direct targets for ultra-fast repeated access.
|
|
904
|
+
* Use this when you need to set the same morph many times (e.g., in animation loops).
|
|
905
|
+
*/
|
|
906
|
+
resolveMorphTargets(key, meshNames) {
|
|
907
|
+
const cached = this.morphCache.get(key);
|
|
908
|
+
if (cached) return cached;
|
|
909
|
+
const targetMeshes = meshNames || this.config.morphToMesh?.face || [];
|
|
910
|
+
const targets = [];
|
|
911
|
+
if (targetMeshes.length) {
|
|
912
|
+
for (const name of targetMeshes) {
|
|
913
|
+
const mesh = this.meshByName.get(name);
|
|
914
|
+
if (!mesh) continue;
|
|
915
|
+
const dict = mesh.morphTargetDictionary;
|
|
916
|
+
const infl = mesh.morphTargetInfluences;
|
|
917
|
+
if (!dict || !infl) continue;
|
|
918
|
+
const idx = dict[key];
|
|
919
|
+
if (idx !== void 0) {
|
|
920
|
+
targets.push({ infl, idx });
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
} else {
|
|
924
|
+
for (const mesh of this.meshes) {
|
|
925
|
+
const dict = mesh.morphTargetDictionary;
|
|
926
|
+
const infl = mesh.morphTargetInfluences;
|
|
927
|
+
if (!dict || !infl) continue;
|
|
928
|
+
const idx = dict[key];
|
|
929
|
+
if (idx !== void 0) {
|
|
930
|
+
targets.push({ infl, idx });
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (targets.length > 0) {
|
|
935
|
+
this.morphCache.set(key, targets);
|
|
936
|
+
}
|
|
937
|
+
return targets;
|
|
938
|
+
}
|
|
727
939
|
transitionMorph(key, to, durationMs = 120, meshNames) {
|
|
728
940
|
const transitionKey = `morph_${key}`;
|
|
729
941
|
const from = this.getMorphValue(key);
|
|
730
942
|
const target = clamp01(to);
|
|
731
|
-
|
|
943
|
+
const targets = this.resolveMorphTargets(key, meshNames);
|
|
944
|
+
return this.animation.addTransition(transitionKey, from, target, durationMs, (value) => {
|
|
945
|
+
const val = clamp01(value);
|
|
946
|
+
for (const t of targets) {
|
|
947
|
+
t.infl[t.idx] = val;
|
|
948
|
+
}
|
|
949
|
+
});
|
|
732
950
|
}
|
|
733
951
|
// ============================================================================
|
|
734
952
|
// VISEME CONTROL
|
|
@@ -831,6 +1049,33 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
831
1049
|
});
|
|
832
1050
|
return result;
|
|
833
1051
|
}
|
|
1052
|
+
/** Get all morph targets grouped by mesh name */
|
|
1053
|
+
getMorphTargets() {
|
|
1054
|
+
const result = {};
|
|
1055
|
+
for (const mesh of this.meshes) {
|
|
1056
|
+
const dict = mesh.morphTargetDictionary;
|
|
1057
|
+
if (dict) {
|
|
1058
|
+
result[mesh.name] = Object.keys(dict).sort();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return result;
|
|
1062
|
+
}
|
|
1063
|
+
/** Get all resolved bone names and their current transforms */
|
|
1064
|
+
getBones() {
|
|
1065
|
+
const result = {};
|
|
1066
|
+
for (const name of Object.keys(this.bones)) {
|
|
1067
|
+
const entry = this.bones[name];
|
|
1068
|
+
if (entry) {
|
|
1069
|
+
const pos = entry.obj.position;
|
|
1070
|
+
const rot = entry.obj.rotation;
|
|
1071
|
+
result[name] = {
|
|
1072
|
+
position: [pos.x, pos.y, pos.z],
|
|
1073
|
+
rotation: [rot.x * 180 / Math.PI, rot.y * 180 / Math.PI, rot.z * 180 / Math.PI]
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return result;
|
|
1078
|
+
}
|
|
834
1079
|
setMeshVisible(meshName, visible) {
|
|
835
1080
|
if (!this.model) return;
|
|
836
1081
|
this.model.traverse((obj) => {
|
|
@@ -839,6 +1084,70 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
839
1084
|
}
|
|
840
1085
|
});
|
|
841
1086
|
}
|
|
1087
|
+
/** Get material config for a mesh */
|
|
1088
|
+
getMeshMaterialConfig(meshName) {
|
|
1089
|
+
if (!this.model) return null;
|
|
1090
|
+
let result = null;
|
|
1091
|
+
this.model.traverse((obj) => {
|
|
1092
|
+
if (obj.isMesh && obj.name === meshName) {
|
|
1093
|
+
const mat = obj.material;
|
|
1094
|
+
if (mat) {
|
|
1095
|
+
let blendingName = "Normal";
|
|
1096
|
+
for (const [name, value] of Object.entries(_LoomLargeThree.BLENDING_MODES)) {
|
|
1097
|
+
if (mat.blending === value) {
|
|
1098
|
+
blendingName = name;
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
result = {
|
|
1103
|
+
renderOrder: obj.renderOrder,
|
|
1104
|
+
transparent: mat.transparent,
|
|
1105
|
+
opacity: mat.opacity,
|
|
1106
|
+
depthWrite: mat.depthWrite,
|
|
1107
|
+
depthTest: mat.depthTest,
|
|
1108
|
+
blending: blendingName
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
return result;
|
|
1114
|
+
}
|
|
1115
|
+
/** Set material config for a mesh */
|
|
1116
|
+
setMeshMaterialConfig(meshName, config) {
|
|
1117
|
+
if (!this.model) return;
|
|
1118
|
+
this.model.traverse((obj) => {
|
|
1119
|
+
if (obj.isMesh && obj.name === meshName) {
|
|
1120
|
+
const mat = obj.material;
|
|
1121
|
+
if (config.renderOrder !== void 0) {
|
|
1122
|
+
obj.renderOrder = config.renderOrder;
|
|
1123
|
+
}
|
|
1124
|
+
if (mat) {
|
|
1125
|
+
if (config.opacity !== void 0) {
|
|
1126
|
+
mat.opacity = config.opacity;
|
|
1127
|
+
if (config.opacity < 1 && config.transparent === void 0) {
|
|
1128
|
+
mat.transparent = true;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (config.transparent !== void 0) {
|
|
1132
|
+
mat.transparent = config.transparent;
|
|
1133
|
+
}
|
|
1134
|
+
if (config.depthWrite !== void 0) {
|
|
1135
|
+
mat.depthWrite = config.depthWrite;
|
|
1136
|
+
}
|
|
1137
|
+
if (config.depthTest !== void 0) {
|
|
1138
|
+
mat.depthTest = config.depthTest;
|
|
1139
|
+
}
|
|
1140
|
+
if (config.blending !== void 0) {
|
|
1141
|
+
const blendValue = _LoomLargeThree.BLENDING_MODES[config.blending];
|
|
1142
|
+
if (blendValue !== void 0) {
|
|
1143
|
+
mat.blending = blendValue;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
mat.needsUpdate = true;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
842
1151
|
// ============================================================================
|
|
843
1152
|
// CONFIGURATION
|
|
844
1153
|
// ============================================================================
|
|
@@ -1004,6 +1313,42 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
1004
1313
|
cancel: () => handles.forEach((h) => h.cancel())
|
|
1005
1314
|
};
|
|
1006
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Apply render order and material settings from CC4_MESHES to all meshes.
|
|
1318
|
+
* This ensures proper layering (e.g., hair renders on top of eyebrows).
|
|
1319
|
+
*/
|
|
1320
|
+
applyMeshMaterialSettings(root) {
|
|
1321
|
+
root.traverse((obj) => {
|
|
1322
|
+
if (!obj.isMesh || !obj.name) return;
|
|
1323
|
+
const meshInfo = CC4_MESHES[obj.name];
|
|
1324
|
+
if (!meshInfo?.material) return;
|
|
1325
|
+
const settings = meshInfo.material;
|
|
1326
|
+
if (typeof settings.renderOrder === "number") {
|
|
1327
|
+
obj.renderOrder = settings.renderOrder;
|
|
1328
|
+
}
|
|
1329
|
+
if (obj.material) {
|
|
1330
|
+
if (typeof settings.transparent === "boolean") {
|
|
1331
|
+
obj.material.transparent = settings.transparent;
|
|
1332
|
+
}
|
|
1333
|
+
if (typeof settings.opacity === "number") {
|
|
1334
|
+
obj.material.opacity = settings.opacity;
|
|
1335
|
+
}
|
|
1336
|
+
if (typeof settings.depthWrite === "boolean") {
|
|
1337
|
+
obj.material.depthWrite = settings.depthWrite;
|
|
1338
|
+
}
|
|
1339
|
+
if (typeof settings.depthTest === "boolean") {
|
|
1340
|
+
obj.material.depthTest = settings.depthTest;
|
|
1341
|
+
}
|
|
1342
|
+
if (typeof settings.blending === "string") {
|
|
1343
|
+
const blendValue = _LoomLargeThree.BLENDING_MODES[settings.blending];
|
|
1344
|
+
if (blendValue !== void 0) {
|
|
1345
|
+
obj.material.blending = blendValue;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
obj.material.needsUpdate = true;
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1007
1352
|
};
|
|
1008
1353
|
// Viseme jaw amounts
|
|
1009
1354
|
__publicField(_LoomLargeThree, "VISEME_JAW_AMOUNTS", [
|
|
@@ -1024,6 +1369,19 @@ __publicField(_LoomLargeThree, "VISEME_JAW_AMOUNTS", [
|
|
|
1024
1369
|
0.4
|
|
1025
1370
|
]);
|
|
1026
1371
|
__publicField(_LoomLargeThree, "JAW_MAX_DEGREES", 28);
|
|
1372
|
+
/** Blending mode options for Three.js materials */
|
|
1373
|
+
__publicField(_LoomLargeThree, "BLENDING_MODES", {
|
|
1374
|
+
"Normal": 1,
|
|
1375
|
+
// THREE.NormalBlending
|
|
1376
|
+
"Additive": 2,
|
|
1377
|
+
// THREE.AdditiveBlending
|
|
1378
|
+
"Subtractive": 3,
|
|
1379
|
+
// THREE.SubtractiveBlending
|
|
1380
|
+
"Multiply": 4,
|
|
1381
|
+
// THREE.MultiplyBlending
|
|
1382
|
+
"None": 0
|
|
1383
|
+
// THREE.NoBlending
|
|
1384
|
+
});
|
|
1027
1385
|
var LoomLargeThree = _LoomLargeThree;
|
|
1028
1386
|
function collectMorphMeshes(root) {
|
|
1029
1387
|
const meshes = [];
|
|
@@ -1037,6 +1395,20 @@ function collectMorphMeshes(root) {
|
|
|
1037
1395
|
return meshes;
|
|
1038
1396
|
}
|
|
1039
1397
|
|
|
1398
|
+
// src/mappings/types.ts
|
|
1399
|
+
var BLENDING_MODES = {
|
|
1400
|
+
"Normal": 1,
|
|
1401
|
+
// THREE.NormalBlending
|
|
1402
|
+
"Additive": 2,
|
|
1403
|
+
// THREE.AdditiveBlending
|
|
1404
|
+
"Subtractive": 3,
|
|
1405
|
+
// THREE.SubtractiveBlending
|
|
1406
|
+
"Multiply": 4,
|
|
1407
|
+
// THREE.MultiplyBlending
|
|
1408
|
+
"None": 0
|
|
1409
|
+
// THREE.NoBlending
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1040
1412
|
// src/physics/HairPhysics.ts
|
|
1041
1413
|
var DEFAULT_HAIR_PHYSICS_CONFIG = {
|
|
1042
1414
|
mass: 1,
|
|
@@ -1161,6 +1533,6 @@ var HairPhysics = class {
|
|
|
1161
1533
|
}
|
|
1162
1534
|
};
|
|
1163
1535
|
|
|
1164
|
-
export { AnimationThree, CC4_PRESET, DEFAULT_HAIR_PHYSICS_CONFIG, HairPhysics, LoomLargeThree, collectMorphMeshes, LoomLargeThree as default };
|
|
1536
|
+
export { AU_INFO, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, HairPhysics, LoomLargeThree, MORPH_TO_MESH, VISEME_KEYS, collectMorphMeshes, LoomLargeThree as default, hasLeftRightMorphs, isMixedAU };
|
|
1165
1537
|
//# sourceMappingURL=index.js.map
|
|
1166
1538
|
//# sourceMappingURL=index.js.map
|