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.cjs
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var three = require('three');
|
|
6
|
+
|
|
5
7
|
var __defProp = Object.defineProperty;
|
|
6
8
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
9
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -243,121 +245,224 @@ var VISEME_KEYS = [
|
|
|
243
245
|
];
|
|
244
246
|
var BONE_AU_TO_BINDINGS = {
|
|
245
247
|
// Head turn and tilt (M51-M56) - use HEAD bone only (NECK should not rotate with head)
|
|
246
|
-
// Three.js Y rotation: positive = counter-clockwise from above = head turns LEFT (character POV)
|
|
247
248
|
51: [
|
|
248
|
-
{ node: "HEAD", channel: "ry", scale: 1, maxDegrees: 30 }
|
|
249
|
+
{ node: "HEAD", channel: "ry", scale: 1, maxDegrees: 30, axis: "yaw" }
|
|
249
250
|
// Head turn left
|
|
250
251
|
],
|
|
251
252
|
52: [
|
|
252
|
-
{ node: "HEAD", channel: "ry", scale: -1, maxDegrees: 30 }
|
|
253
|
+
{ node: "HEAD", channel: "ry", scale: -1, maxDegrees: 30, axis: "yaw" }
|
|
253
254
|
// Head turn right
|
|
254
255
|
],
|
|
255
256
|
53: [
|
|
256
|
-
{ node: "HEAD", channel: "rx", scale: -1, maxDegrees: 20 }
|
|
257
|
+
{ node: "HEAD", channel: "rx", scale: -1, maxDegrees: 20, axis: "pitch" }
|
|
257
258
|
// Head up
|
|
258
259
|
],
|
|
259
260
|
54: [
|
|
260
|
-
{ node: "HEAD", channel: "rx", scale: 1, maxDegrees: 20 }
|
|
261
|
+
{ node: "HEAD", channel: "rx", scale: 1, maxDegrees: 20, axis: "pitch" }
|
|
261
262
|
// Head down
|
|
262
263
|
],
|
|
263
264
|
55: [
|
|
264
|
-
{ node: "HEAD", channel: "rz", scale: -1, maxDegrees: 15 }
|
|
265
|
+
{ node: "HEAD", channel: "rz", scale: -1, maxDegrees: 15, axis: "roll" }
|
|
265
266
|
// Head tilt left
|
|
266
267
|
],
|
|
267
268
|
56: [
|
|
268
|
-
{ node: "HEAD", channel: "rz", scale: 1, maxDegrees: 15 }
|
|
269
|
+
{ node: "HEAD", channel: "rz", scale: 1, maxDegrees: 15, axis: "roll" }
|
|
269
270
|
// Head tilt right
|
|
270
271
|
],
|
|
271
272
|
// Eyes horizontal (yaw) - CC4 rigs use rz for horizontal eye rotation
|
|
272
273
|
61: [
|
|
273
|
-
{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 32 },
|
|
274
|
+
{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 32, axis: "yaw" },
|
|
274
275
|
// Eyes look left
|
|
275
|
-
{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 32 }
|
|
276
|
+
{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 32, axis: "yaw" }
|
|
276
277
|
],
|
|
277
278
|
62: [
|
|
278
|
-
{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 32 },
|
|
279
|
+
{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 32, axis: "yaw" },
|
|
279
280
|
// Eyes look right
|
|
280
|
-
{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 32 }
|
|
281
|
+
{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 32, axis: "yaw" }
|
|
281
282
|
],
|
|
282
283
|
63: [
|
|
283
|
-
{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 32 },
|
|
284
|
-
|
|
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" }
|
|
285
287
|
],
|
|
286
288
|
64: [
|
|
287
|
-
{ node: "EYE_L", channel: "rx", scale: 1, maxDegrees: 32 },
|
|
288
|
-
|
|
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" }
|
|
289
292
|
],
|
|
290
293
|
// Single-eye (Left) — horizontal (rz for CC4) and vertical (rx)
|
|
291
|
-
65: [{ node: "EYE_L", channel: "rz", scale: -1, maxDegrees: 15 }],
|
|
292
|
-
66: [{ node: "EYE_L", channel: "rz", scale: 1, maxDegrees: 15 }],
|
|
293
|
-
67: [{ node: "EYE_L", channel: "rx", scale: -1, maxDegrees: 12 }],
|
|
294
|
-
|
|
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
|
|
295
300
|
// Single-eye (Right) — horizontal (rz for CC4) and vertical (rx)
|
|
296
|
-
69: [{ node: "EYE_R", channel: "rz", scale: -1, maxDegrees: 15 }],
|
|
297
|
-
70: [{ node: "EYE_R", channel: "rz", scale: 1, maxDegrees: 15 }],
|
|
298
|
-
71: [{ node: "EYE_R", channel: "rx", scale: -1, maxDegrees: 12 }],
|
|
299
|
-
|
|
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
|
|
300
307
|
// Jaw / Mouth
|
|
301
308
|
8: [
|
|
302
309
|
// Lips Toward Each Other - slight jaw open helps sell the lip press
|
|
303
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 8 }
|
|
304
|
-
// Small downward rotation (jaw opening slightly)
|
|
310
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 8, axis: "roll" }
|
|
305
311
|
],
|
|
306
312
|
25: [
|
|
307
313
|
// Lips Part — small jaw open
|
|
308
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 5.84 }
|
|
309
|
-
// 73% of 8
|
|
314
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 5.84, axis: "roll" }
|
|
310
315
|
],
|
|
311
316
|
26: [
|
|
312
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 28 }
|
|
313
|
-
// 73% of 20
|
|
317
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 28, axis: "roll" }
|
|
314
318
|
],
|
|
315
319
|
27: [
|
|
316
320
|
// Mouth Stretch — larger jaw open
|
|
317
|
-
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 32 }
|
|
318
|
-
// 73% of 25
|
|
321
|
+
{ node: "JAW", channel: "rz", scale: 1, maxDegrees: 32, axis: "roll" }
|
|
319
322
|
],
|
|
320
323
|
29: [
|
|
321
324
|
{ node: "JAW", channel: "tz", scale: -1, maxUnits: 0.02 }
|
|
322
|
-
//
|
|
325
|
+
// Translation - no axis needed
|
|
323
326
|
],
|
|
324
327
|
30: [
|
|
325
328
|
// Jaw Left
|
|
326
|
-
{ node: "JAW", channel: "ry", scale: -1, maxDegrees: 5 }
|
|
329
|
+
{ node: "JAW", channel: "ry", scale: -1, maxDegrees: 5, axis: "yaw" }
|
|
327
330
|
],
|
|
328
331
|
35: [
|
|
329
332
|
// Jaw Right
|
|
330
|
-
{ node: "JAW", channel: "ry", scale: 1, maxDegrees: 5 }
|
|
333
|
+
{ node: "JAW", channel: "ry", scale: 1, maxDegrees: 5, axis: "yaw" }
|
|
331
334
|
],
|
|
332
335
|
// Tongue
|
|
333
336
|
19: [
|
|
334
337
|
{ node: "TONGUE", channel: "tz", scale: -1, maxUnits: 8e-3 }
|
|
338
|
+
// Translation - no axis needed
|
|
335
339
|
],
|
|
336
340
|
37: [
|
|
337
341
|
// Tongue Up
|
|
338
|
-
{ node: "TONGUE", channel: "rz", scale: -1, maxDegrees: 45 }
|
|
342
|
+
{ node: "TONGUE", channel: "rz", scale: -1, maxDegrees: 45, axis: "pitch" }
|
|
339
343
|
],
|
|
340
344
|
38: [
|
|
341
345
|
// Tongue Down
|
|
342
|
-
{ node: "TONGUE", channel: "rz", scale: 1, maxDegrees: 45 }
|
|
346
|
+
{ node: "TONGUE", channel: "rz", scale: 1, maxDegrees: 45, axis: "pitch" }
|
|
343
347
|
],
|
|
344
348
|
39: [
|
|
345
349
|
// Tongue Left
|
|
346
|
-
{ node: "TONGUE", channel: "ry", scale: -1, maxDegrees: 10 }
|
|
350
|
+
{ node: "TONGUE", channel: "ry", scale: -1, maxDegrees: 10, axis: "yaw" }
|
|
347
351
|
],
|
|
348
352
|
40: [
|
|
349
353
|
// Tongue Right
|
|
350
|
-
{ node: "TONGUE", channel: "ry", scale: 1, maxDegrees: 10 }
|
|
354
|
+
{ node: "TONGUE", channel: "ry", scale: 1, maxDegrees: 10, axis: "yaw" }
|
|
351
355
|
],
|
|
352
356
|
41: [
|
|
353
357
|
// Tongue Tilt Left
|
|
354
|
-
{ node: "TONGUE", channel: "rx", scale: -1, maxDegrees: 20 }
|
|
358
|
+
{ node: "TONGUE", channel: "rx", scale: -1, maxDegrees: 20, axis: "roll" }
|
|
355
359
|
],
|
|
356
360
|
42: [
|
|
357
361
|
// Tongue Tilt Right
|
|
358
|
-
{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20 }
|
|
362
|
+
{ node: "TONGUE", channel: "rx", scale: 1, maxDegrees: 20, axis: "roll" }
|
|
359
363
|
]
|
|
360
364
|
};
|
|
365
|
+
var isMixedAU = (id) => !!(AU_TO_MORPHS[id]?.length && BONE_AU_TO_BINDINGS[id]?.length);
|
|
366
|
+
var hasLeftRightMorphs = (auId) => {
|
|
367
|
+
const keys = AU_TO_MORPHS[auId] || [];
|
|
368
|
+
return keys.some((k) => /_L$|_R$| L$| R$|Left$|Right$/i.test(k));
|
|
369
|
+
};
|
|
370
|
+
var COMPOSITE_ROTATIONS = [
|
|
371
|
+
{
|
|
372
|
+
node: "JAW",
|
|
373
|
+
pitch: { aus: [25, 26, 27], axis: "rz" },
|
|
374
|
+
// Jaw drop (opens mouth downward)
|
|
375
|
+
yaw: { aus: [30, 35], axis: "ry", negative: 30, positive: 35 },
|
|
376
|
+
// Jaw lateral (left/right)
|
|
377
|
+
roll: null
|
|
378
|
+
// Jaw doesn't have roll
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
node: "HEAD",
|
|
382
|
+
pitch: { aus: [54, 53], axis: "rx", negative: 54, positive: 53 },
|
|
383
|
+
// Head down/up
|
|
384
|
+
yaw: { aus: [51, 52], axis: "ry", negative: 51, positive: 52 },
|
|
385
|
+
// Head turn left/right
|
|
386
|
+
roll: { aus: [55, 56], axis: "rz", negative: 55, positive: 56 }
|
|
387
|
+
// Head tilt left/right
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
node: "EYE_L",
|
|
391
|
+
pitch: { aus: [64, 63], axis: "rx", negative: 64, positive: 63 },
|
|
392
|
+
// Eyes down/up
|
|
393
|
+
yaw: { aus: [61, 62], axis: "rz", negative: 61, positive: 62 },
|
|
394
|
+
// Eyes left/right (rz for CC4)
|
|
395
|
+
roll: null
|
|
396
|
+
// Eyes don't have roll
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
node: "EYE_R",
|
|
400
|
+
pitch: { aus: [64, 63], axis: "rx", negative: 64, positive: 63 },
|
|
401
|
+
// Eyes down/up
|
|
402
|
+
yaw: { aus: [61, 62], axis: "rz", negative: 61, positive: 62 },
|
|
403
|
+
// Eyes left/right (rz for CC4)
|
|
404
|
+
roll: null
|
|
405
|
+
// Eyes don't have roll
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
node: "TONGUE",
|
|
409
|
+
pitch: { aus: [38, 37], axis: "rz", negative: 38, positive: 37 },
|
|
410
|
+
// Tongue down/up
|
|
411
|
+
yaw: { aus: [39, 40], axis: "ry", negative: 39, positive: 40 },
|
|
412
|
+
// Tongue left/right
|
|
413
|
+
roll: { aus: [41, 42], axis: "rx", negative: 41, positive: 42 }
|
|
414
|
+
// Tongue tilt left/right
|
|
415
|
+
}
|
|
416
|
+
];
|
|
417
|
+
var CONTINUUM_PAIRS_MAP = {
|
|
418
|
+
// Eyes horizontal (yaw) - both eyes share same AUs
|
|
419
|
+
61: { pairId: 62, isNegative: true, axis: "yaw", node: "EYE_L" },
|
|
420
|
+
62: { pairId: 61, isNegative: false, axis: "yaw", node: "EYE_L" },
|
|
421
|
+
// Eyes vertical (pitch)
|
|
422
|
+
64: { pairId: 63, isNegative: true, axis: "pitch", node: "EYE_L" },
|
|
423
|
+
63: { pairId: 64, isNegative: false, axis: "pitch", node: "EYE_L" },
|
|
424
|
+
// Head yaw (turn left/right)
|
|
425
|
+
51: { pairId: 52, isNegative: true, axis: "yaw", node: "HEAD" },
|
|
426
|
+
52: { pairId: 51, isNegative: false, axis: "yaw", node: "HEAD" },
|
|
427
|
+
// Head pitch (up/down)
|
|
428
|
+
54: { pairId: 53, isNegative: true, axis: "pitch", node: "HEAD" },
|
|
429
|
+
53: { pairId: 54, isNegative: false, axis: "pitch", node: "HEAD" },
|
|
430
|
+
// Head roll (tilt left/right)
|
|
431
|
+
55: { pairId: 56, isNegative: true, axis: "roll", node: "HEAD" },
|
|
432
|
+
56: { pairId: 55, isNegative: false, axis: "roll", node: "HEAD" },
|
|
433
|
+
// Jaw yaw (left/right)
|
|
434
|
+
30: { pairId: 35, isNegative: true, axis: "yaw", node: "JAW" },
|
|
435
|
+
35: { pairId: 30, isNegative: false, axis: "yaw", node: "JAW" },
|
|
436
|
+
// Tongue yaw (left/right)
|
|
437
|
+
39: { pairId: 40, isNegative: true, axis: "yaw", node: "TONGUE" },
|
|
438
|
+
40: { pairId: 39, isNegative: false, axis: "yaw", node: "TONGUE" },
|
|
439
|
+
// Tongue pitch (up/down)
|
|
440
|
+
38: { pairId: 37, isNegative: true, axis: "pitch", node: "TONGUE" },
|
|
441
|
+
37: { pairId: 38, isNegative: false, axis: "pitch", node: "TONGUE" },
|
|
442
|
+
// Tongue roll (tilt left/right)
|
|
443
|
+
41: { pairId: 42, isNegative: true, axis: "roll", node: "TONGUE" },
|
|
444
|
+
42: { pairId: 41, isNegative: false, axis: "roll", node: "TONGUE" },
|
|
445
|
+
// Extended tongue morphs (continuum pairs)
|
|
446
|
+
73: { pairId: 74, isNegative: true, axis: "yaw", node: "TONGUE" },
|
|
447
|
+
// Tongue Narrow/Wide
|
|
448
|
+
74: { pairId: 73, isNegative: false, axis: "yaw", node: "TONGUE" },
|
|
449
|
+
76: { pairId: 77, isNegative: false, axis: "pitch", node: "TONGUE" },
|
|
450
|
+
// Tongue Tip Up/Down
|
|
451
|
+
77: { pairId: 76, isNegative: true, axis: "pitch", node: "TONGUE" }
|
|
452
|
+
};
|
|
453
|
+
var CONTINUUM_LABELS = {
|
|
454
|
+
"61-62": "Eyes \u2014 Horizontal",
|
|
455
|
+
"64-63": "Eyes \u2014 Vertical",
|
|
456
|
+
"51-52": "Head \u2014 Horizontal",
|
|
457
|
+
"54-53": "Head \u2014 Vertical",
|
|
458
|
+
"55-56": "Head \u2014 Tilt",
|
|
459
|
+
"30-35": "Jaw \u2014 Horizontal",
|
|
460
|
+
"38-37": "Tongue \u2014 Vertical",
|
|
461
|
+
"39-40": "Tongue \u2014 Horizontal",
|
|
462
|
+
"41-42": "Tongue \u2014 Tilt",
|
|
463
|
+
"73-74": "Tongue \u2014 Width",
|
|
464
|
+
"76-77": "Tongue Tip \u2014 Vertical"
|
|
465
|
+
};
|
|
361
466
|
var CC4_BONE_NODES = {
|
|
362
467
|
EYE_L: "CC_Base_L_Eye",
|
|
363
468
|
EYE_R: "CC_Base_R_Eye",
|
|
@@ -490,6 +595,41 @@ var AU_MIX_DEFAULTS = {
|
|
|
490
595
|
35: 0.5
|
|
491
596
|
// jaw left/right
|
|
492
597
|
};
|
|
598
|
+
var CC4_MESHES = {
|
|
599
|
+
// Body (6 meshes, 80 morphs each) - default render order 0
|
|
600
|
+
"CC_Base_Body_1": { category: "body", morphCount: 80 },
|
|
601
|
+
"CC_Base_Body_2": { category: "body", morphCount: 80 },
|
|
602
|
+
"CC_Base_Body_3": { category: "body", morphCount: 80 },
|
|
603
|
+
"CC_Base_Body_4": { category: "body", morphCount: 80 },
|
|
604
|
+
"CC_Base_Body_5": { category: "body", morphCount: 80 },
|
|
605
|
+
"CC_Base_Body_6": { category: "body", morphCount: 80 },
|
|
606
|
+
// Eyes (bone-driven, no morphs) - render first (behind everything)
|
|
607
|
+
"CC_Base_Eye": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
608
|
+
"CC_Base_Eye_1": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
609
|
+
"CC_Base_Eye_2": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
610
|
+
"CC_Base_Eye_3": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
611
|
+
"CC_Base_Eye_4": { category: "eye", morphCount: 0, material: { renderOrder: -10 } },
|
|
612
|
+
// Eye occlusion (94 morphs each) - render on top of eyes with transparency support
|
|
613
|
+
"CC_Base_EyeOcclusion_1": { category: "eyeOcclusion", morphCount: 94, material: { renderOrder: 2, transparent: true, opacity: 1, depthWrite: true, depthTest: true, blending: "Normal" } },
|
|
614
|
+
"CC_Base_EyeOcclusion_2": { category: "eyeOcclusion", morphCount: 94, material: { renderOrder: 2, transparent: true, opacity: 1, depthWrite: true, depthTest: true, blending: "Normal" } },
|
|
615
|
+
// Tear lines (90 morphs each) - on top of eyes/face
|
|
616
|
+
"CC_Base_TearLine_1": { category: "tearLine", morphCount: 90, material: { renderOrder: 2 } },
|
|
617
|
+
"CC_Base_TearLine_2": { category: "tearLine", morphCount: 90, material: { renderOrder: 2 } },
|
|
618
|
+
// Cornea (no morphs) - render first with eyes
|
|
619
|
+
"CC_Base_Cornea": { category: "cornea", morphCount: 0, material: { renderOrder: -10 } },
|
|
620
|
+
"CC_Base_Cornea_1": { category: "cornea", morphCount: 0, material: { renderOrder: -10 } },
|
|
621
|
+
// Teeth (no morphs, follow jaw bone) - default render order
|
|
622
|
+
"CC_Base_Teeth_1": { category: "teeth", morphCount: 0 },
|
|
623
|
+
"CC_Base_Teeth_2": { category: "teeth", morphCount: 0 },
|
|
624
|
+
// Tongue (23 morphs) - default render order
|
|
625
|
+
"CC_Base_Tongue": { category: "tongue", morphCount: 23 },
|
|
626
|
+
// Eyebrows (91 morphs each) - above face
|
|
627
|
+
"Male_Bushy_1": { category: "eyebrow", morphCount: 91, material: { renderOrder: 5 } },
|
|
628
|
+
"Male_Bushy_2": { category: "eyebrow", morphCount: 91, material: { renderOrder: 5 } },
|
|
629
|
+
// Hair (14 styling morphs each) - render last (on top of everything)
|
|
630
|
+
"Side_part_wavy_1": { category: "hair", morphCount: 14, material: { renderOrder: 10 } },
|
|
631
|
+
"Side_part_wavy_2": { category: "hair", morphCount: 14, material: { renderOrder: 10 } }
|
|
632
|
+
};
|
|
493
633
|
var MORPH_TO_MESH = {
|
|
494
634
|
// Face/AU morphs affect the main face mesh and both eyebrow meshes.
|
|
495
635
|
face: ["CC_Base_Body_1", "Male_Bushy_1", "Male_Bushy_2"],
|
|
@@ -541,6 +681,10 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
541
681
|
__publicField(this, "mixWeights", {});
|
|
542
682
|
// Viseme state
|
|
543
683
|
__publicField(this, "visemeValues", new Array(15).fill(0));
|
|
684
|
+
// Internal RAF loop
|
|
685
|
+
__publicField(this, "clock", new three.Clock());
|
|
686
|
+
__publicField(this, "rafId", null);
|
|
687
|
+
__publicField(this, "running", false);
|
|
544
688
|
this.config = config.auMappings || CC4_PRESET;
|
|
545
689
|
this.mixWeights = { ...this.config.auMixDefaults };
|
|
546
690
|
this.animation = animation || new AnimationThree();
|
|
@@ -577,6 +721,7 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
577
721
|
this.rigReady = true;
|
|
578
722
|
this.missingBoneWarnings.clear();
|
|
579
723
|
this.initBoneRotations();
|
|
724
|
+
this.applyMeshMaterialSettings(model);
|
|
580
725
|
}
|
|
581
726
|
update(deltaSeconds) {
|
|
582
727
|
const dtSeconds = Math.max(0, deltaSeconds || 0);
|
|
@@ -584,7 +729,30 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
584
729
|
this.animation.tick(dtSeconds);
|
|
585
730
|
this.flushPendingComposites();
|
|
586
731
|
}
|
|
732
|
+
/** Start the internal RAF loop */
|
|
733
|
+
start() {
|
|
734
|
+
if (this.running) return;
|
|
735
|
+
this.running = true;
|
|
736
|
+
this.clock.start();
|
|
737
|
+
const tick = () => {
|
|
738
|
+
if (!this.running) return;
|
|
739
|
+
const dt = this.clock.getDelta();
|
|
740
|
+
this.update(dt);
|
|
741
|
+
this.rafId = requestAnimationFrame(tick);
|
|
742
|
+
};
|
|
743
|
+
this.rafId = requestAnimationFrame(tick);
|
|
744
|
+
}
|
|
745
|
+
/** Stop the internal RAF loop */
|
|
746
|
+
stop() {
|
|
747
|
+
this.running = false;
|
|
748
|
+
if (this.rafId !== null) {
|
|
749
|
+
cancelAnimationFrame(this.rafId);
|
|
750
|
+
this.rafId = null;
|
|
751
|
+
}
|
|
752
|
+
this.clock.stop();
|
|
753
|
+
}
|
|
587
754
|
dispose() {
|
|
755
|
+
this.stop();
|
|
588
756
|
this.clearTransitions();
|
|
589
757
|
this.meshes = [];
|
|
590
758
|
this.model = null;
|
|
@@ -633,8 +801,9 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
633
801
|
if (bindings) {
|
|
634
802
|
for (const binding of bindings) {
|
|
635
803
|
if (binding.channel === "rx" || binding.channel === "ry" || binding.channel === "rz") {
|
|
636
|
-
|
|
637
|
-
|
|
804
|
+
if (binding.axis) {
|
|
805
|
+
this.updateBoneRotation(binding.node, binding.axis, v * binding.scale, binding.maxDegrees ?? 0);
|
|
806
|
+
}
|
|
638
807
|
} else if (binding.channel === "tx" || binding.channel === "ty" || binding.channel === "tz") {
|
|
639
808
|
if (binding.maxUnits !== void 0) {
|
|
640
809
|
this.updateBoneTranslation(binding.node, binding.channel, v * binding.scale, binding.maxUnits);
|
|
@@ -672,8 +841,9 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
672
841
|
}
|
|
673
842
|
for (const binding of bindings) {
|
|
674
843
|
if (binding.channel === "rx" || binding.channel === "ry" || binding.channel === "rz") {
|
|
675
|
-
|
|
676
|
-
|
|
844
|
+
if (binding.axis) {
|
|
845
|
+
handles.push(this.transitionBoneRotation(binding.node, binding.axis, target * binding.scale, binding.maxDegrees ?? 0, durationMs));
|
|
846
|
+
}
|
|
677
847
|
} else if (binding.channel === "tx" || binding.channel === "ty" || binding.channel === "tz") {
|
|
678
848
|
if (binding.maxUnits !== void 0) {
|
|
679
849
|
handles.push(this.transitionBoneTranslation(binding.node, binding.channel, target * binding.scale, binding.maxUnits, durationMs));
|
|
@@ -685,11 +855,16 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
685
855
|
getAU(id) {
|
|
686
856
|
return this.auValues[id] ?? 0;
|
|
687
857
|
}
|
|
688
|
-
|
|
689
|
-
// MORPH CONTROL
|
|
690
|
-
// ============================================================================
|
|
691
|
-
setMorph(key, v, meshNames) {
|
|
858
|
+
setMorph(key, v, meshNamesOrTargets) {
|
|
692
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;
|
|
693
868
|
const targetMeshes = meshNames || this.config.morphToMesh?.face || [];
|
|
694
869
|
const cached = this.morphCache.get(key);
|
|
695
870
|
if (cached) {
|
|
@@ -728,11 +903,54 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
728
903
|
this.morphCache.set(key, targets);
|
|
729
904
|
}
|
|
730
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
|
+
}
|
|
731
943
|
transitionMorph(key, to, durationMs = 120, meshNames) {
|
|
732
944
|
const transitionKey = `morph_${key}`;
|
|
733
945
|
const from = this.getMorphValue(key);
|
|
734
946
|
const target = clamp01(to);
|
|
735
|
-
|
|
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
|
+
});
|
|
736
954
|
}
|
|
737
955
|
// ============================================================================
|
|
738
956
|
// VISEME CONTROL
|
|
@@ -835,6 +1053,33 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
835
1053
|
});
|
|
836
1054
|
return result;
|
|
837
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
|
+
}
|
|
838
1083
|
setMeshVisible(meshName, visible) {
|
|
839
1084
|
if (!this.model) return;
|
|
840
1085
|
this.model.traverse((obj) => {
|
|
@@ -843,6 +1088,70 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
843
1088
|
}
|
|
844
1089
|
});
|
|
845
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
|
+
}
|
|
846
1155
|
// ============================================================================
|
|
847
1156
|
// CONFIGURATION
|
|
848
1157
|
// ============================================================================
|
|
@@ -1008,6 +1317,42 @@ var _LoomLargeThree = class _LoomLargeThree {
|
|
|
1008
1317
|
cancel: () => handles.forEach((h) => h.cancel())
|
|
1009
1318
|
};
|
|
1010
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Apply render order and material settings from CC4_MESHES to all meshes.
|
|
1322
|
+
* This ensures proper layering (e.g., hair renders on top of eyebrows).
|
|
1323
|
+
*/
|
|
1324
|
+
applyMeshMaterialSettings(root) {
|
|
1325
|
+
root.traverse((obj) => {
|
|
1326
|
+
if (!obj.isMesh || !obj.name) return;
|
|
1327
|
+
const meshInfo = CC4_MESHES[obj.name];
|
|
1328
|
+
if (!meshInfo?.material) return;
|
|
1329
|
+
const settings = meshInfo.material;
|
|
1330
|
+
if (typeof settings.renderOrder === "number") {
|
|
1331
|
+
obj.renderOrder = settings.renderOrder;
|
|
1332
|
+
}
|
|
1333
|
+
if (obj.material) {
|
|
1334
|
+
if (typeof settings.transparent === "boolean") {
|
|
1335
|
+
obj.material.transparent = settings.transparent;
|
|
1336
|
+
}
|
|
1337
|
+
if (typeof settings.opacity === "number") {
|
|
1338
|
+
obj.material.opacity = settings.opacity;
|
|
1339
|
+
}
|
|
1340
|
+
if (typeof settings.depthWrite === "boolean") {
|
|
1341
|
+
obj.material.depthWrite = settings.depthWrite;
|
|
1342
|
+
}
|
|
1343
|
+
if (typeof settings.depthTest === "boolean") {
|
|
1344
|
+
obj.material.depthTest = settings.depthTest;
|
|
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;
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1011
1356
|
};
|
|
1012
1357
|
// Viseme jaw amounts
|
|
1013
1358
|
__publicField(_LoomLargeThree, "VISEME_JAW_AMOUNTS", [
|
|
@@ -1028,6 +1373,19 @@ __publicField(_LoomLargeThree, "VISEME_JAW_AMOUNTS", [
|
|
|
1028
1373
|
0.4
|
|
1029
1374
|
]);
|
|
1030
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
|
+
});
|
|
1031
1389
|
var LoomLargeThree = _LoomLargeThree;
|
|
1032
1390
|
function collectMorphMeshes(root) {
|
|
1033
1391
|
const meshes = [];
|
|
@@ -1041,6 +1399,20 @@ function collectMorphMeshes(root) {
|
|
|
1041
1399
|
return meshes;
|
|
1042
1400
|
}
|
|
1043
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
|
+
|
|
1044
1416
|
// src/physics/HairPhysics.ts
|
|
1045
1417
|
var DEFAULT_HAIR_PHYSICS_CONFIG = {
|
|
1046
1418
|
mass: 1,
|
|
@@ -1165,12 +1537,27 @@ var HairPhysics = class {
|
|
|
1165
1537
|
}
|
|
1166
1538
|
};
|
|
1167
1539
|
|
|
1540
|
+
exports.AU_INFO = AU_INFO;
|
|
1541
|
+
exports.AU_MIX_DEFAULTS = AU_MIX_DEFAULTS;
|
|
1542
|
+
exports.AU_TO_MORPHS = AU_TO_MORPHS;
|
|
1168
1543
|
exports.AnimationThree = AnimationThree;
|
|
1544
|
+
exports.BLENDING_MODES = BLENDING_MODES;
|
|
1545
|
+
exports.BONE_AU_TO_BINDINGS = BONE_AU_TO_BINDINGS;
|
|
1546
|
+
exports.CC4_BONE_NODES = CC4_BONE_NODES;
|
|
1547
|
+
exports.CC4_EYE_MESH_NODES = CC4_EYE_MESH_NODES;
|
|
1548
|
+
exports.CC4_MESHES = CC4_MESHES;
|
|
1169
1549
|
exports.CC4_PRESET = CC4_PRESET;
|
|
1550
|
+
exports.COMPOSITE_ROTATIONS = COMPOSITE_ROTATIONS;
|
|
1551
|
+
exports.CONTINUUM_LABELS = CONTINUUM_LABELS;
|
|
1552
|
+
exports.CONTINUUM_PAIRS_MAP = CONTINUUM_PAIRS_MAP;
|
|
1170
1553
|
exports.DEFAULT_HAIR_PHYSICS_CONFIG = DEFAULT_HAIR_PHYSICS_CONFIG;
|
|
1171
1554
|
exports.HairPhysics = HairPhysics;
|
|
1172
1555
|
exports.LoomLargeThree = LoomLargeThree;
|
|
1556
|
+
exports.MORPH_TO_MESH = MORPH_TO_MESH;
|
|
1557
|
+
exports.VISEME_KEYS = VISEME_KEYS;
|
|
1173
1558
|
exports.collectMorphMeshes = collectMorphMeshes;
|
|
1174
1559
|
exports.default = LoomLargeThree;
|
|
1560
|
+
exports.hasLeftRightMorphs = hasLeftRightMorphs;
|
|
1561
|
+
exports.isMixedAU = isMixedAU;
|
|
1175
1562
|
//# sourceMappingURL=index.cjs.map
|
|
1176
1563
|
//# sourceMappingURL=index.cjs.map
|