@lovelace_lol/loom3 1.0.36 → 1.0.38
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 +16 -1
- package/dist/index.cjs +348 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -3
- package/dist/index.d.ts +51 -3
- package/dist/index.js +310 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as THREE2 from 'three';
|
|
2
2
|
import { Vector3, Clock, Box3, Quaternion, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopPingPong, LoopOnce, LoopRepeat, QuaternionKeyframeTrack, NumberKeyframeTrack, AnimationClip, AnimationMixer, Mesh, PropertyBinding } from 'three';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -270,6 +270,8 @@ var makeActionId = () => `act_${Math.random().toString(36).slice(2, 8)}_${Date.n
|
|
|
270
270
|
var X_AXIS = new Vector3(1, 0, 0);
|
|
271
271
|
var Y_AXIS = new Vector3(0, 1, 0);
|
|
272
272
|
var Z_AXIS = new Vector3(0, 0, 1);
|
|
273
|
+
var CLIP_EVENT_METADATA_KEY = "__loom3ClipEvents";
|
|
274
|
+
var CLIP_EVENT_EPSILON = 1e-4;
|
|
273
275
|
var BakedAnimationController = class {
|
|
274
276
|
constructor(host) {
|
|
275
277
|
__publicField(this, "host");
|
|
@@ -288,6 +290,7 @@ var BakedAnimationController = class {
|
|
|
288
290
|
__publicField(this, "playbackState", /* @__PURE__ */ new Map());
|
|
289
291
|
__publicField(this, "actionIds", /* @__PURE__ */ new WeakMap());
|
|
290
292
|
__publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
|
|
293
|
+
__publicField(this, "clipMonitors", /* @__PURE__ */ new Map());
|
|
291
294
|
this.host = host;
|
|
292
295
|
}
|
|
293
296
|
getActionId(action) {
|
|
@@ -301,6 +304,161 @@ var BakedAnimationController = class {
|
|
|
301
304
|
action.__actionId = actionId;
|
|
302
305
|
return actionId;
|
|
303
306
|
}
|
|
307
|
+
setClipEventMetadata(clip, metadata) {
|
|
308
|
+
const userData = clip.userData ?? (clip.userData = {});
|
|
309
|
+
userData[CLIP_EVENT_METADATA_KEY] = metadata;
|
|
310
|
+
}
|
|
311
|
+
getClipEventMetadata(clip) {
|
|
312
|
+
const userData = clip.userData;
|
|
313
|
+
const keyframeTimes = Array.isArray(userData?.[CLIP_EVENT_METADATA_KEY]?.keyframeTimes) ? userData[CLIP_EVENT_METADATA_KEY].keyframeTimes.filter((time) => Number.isFinite(time)) : [];
|
|
314
|
+
return { keyframeTimes };
|
|
315
|
+
}
|
|
316
|
+
getKeyframeIndex(times, currentTime) {
|
|
317
|
+
if (!times.length) return -1;
|
|
318
|
+
const target = Math.max(0, currentTime) + 1e-3;
|
|
319
|
+
let lo = 0;
|
|
320
|
+
let hi = times.length - 1;
|
|
321
|
+
let idx = 0;
|
|
322
|
+
while (lo <= hi) {
|
|
323
|
+
const mid = lo + hi >>> 1;
|
|
324
|
+
if (times[mid] <= target) {
|
|
325
|
+
idx = mid;
|
|
326
|
+
lo = mid + 1;
|
|
327
|
+
} else {
|
|
328
|
+
hi = mid - 1;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return idx;
|
|
332
|
+
}
|
|
333
|
+
emitClipEvent(monitor, event) {
|
|
334
|
+
for (const listener of Array.from(monitor.listeners)) {
|
|
335
|
+
try {
|
|
336
|
+
listener(event);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error("[Loom3] clip event listener failed", error);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
emitKeyframesForRange(monitor, startTime, endTime, direction, includeStart) {
|
|
343
|
+
if (!monitor.keyframeTimes.length) return;
|
|
344
|
+
const times = direction === 1 ? monitor.keyframeTimes : [...monitor.keyframeTimes].reverse();
|
|
345
|
+
for (const time of times) {
|
|
346
|
+
const matchesForward = direction === 1 && (includeStart ? time >= startTime - CLIP_EVENT_EPSILON : time > startTime + CLIP_EVENT_EPSILON) && time <= endTime + CLIP_EVENT_EPSILON;
|
|
347
|
+
const matchesReverse = direction === -1 && (includeStart ? time <= startTime + CLIP_EVENT_EPSILON : time < startTime - CLIP_EVENT_EPSILON) && time >= endTime - CLIP_EVENT_EPSILON;
|
|
348
|
+
if (!matchesForward && !matchesReverse) continue;
|
|
349
|
+
const keyframeIndex = monitor.keyframeTimes.indexOf(time);
|
|
350
|
+
monitor.lastKeyframeIndex = keyframeIndex;
|
|
351
|
+
this.emitClipEvent(monitor, {
|
|
352
|
+
type: "keyframe",
|
|
353
|
+
clipName: monitor.clipName,
|
|
354
|
+
keyframeIndex,
|
|
355
|
+
totalKeyframes: monitor.keyframeTimes.length,
|
|
356
|
+
currentTime: time,
|
|
357
|
+
duration: monitor.duration,
|
|
358
|
+
iteration: monitor.iteration
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
resetClipMonitor(monitor, currentTime) {
|
|
363
|
+
monitor.iteration = 0;
|
|
364
|
+
monitor.direction = monitor.initialDirection;
|
|
365
|
+
monitor.lastTime = currentTime;
|
|
366
|
+
monitor.lastKeyframeIndex = this.getKeyframeIndex(monitor.keyframeTimes, currentTime);
|
|
367
|
+
monitor.finishedPending = false;
|
|
368
|
+
}
|
|
369
|
+
syncClipMonitorTime(monitor, currentTime, emitSeek = false) {
|
|
370
|
+
const clamped = Math.max(0, Math.min(monitor.duration, currentTime));
|
|
371
|
+
monitor.lastTime = clamped;
|
|
372
|
+
monitor.lastKeyframeIndex = this.getKeyframeIndex(monitor.keyframeTimes, clamped);
|
|
373
|
+
if (emitSeek) {
|
|
374
|
+
this.emitClipEvent(monitor, {
|
|
375
|
+
type: "seek",
|
|
376
|
+
clipName: monitor.clipName,
|
|
377
|
+
currentTime: clamped,
|
|
378
|
+
duration: monitor.duration,
|
|
379
|
+
iteration: monitor.iteration
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
cleanupClipMonitor(actionId) {
|
|
384
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
385
|
+
if (!monitor || monitor.cleanedUp) return;
|
|
386
|
+
monitor.cleanedUp = true;
|
|
387
|
+
try {
|
|
388
|
+
monitor.action.paused = true;
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
monitor.resolveFinished();
|
|
392
|
+
monitor.listeners.clear();
|
|
393
|
+
this.clipMonitors.delete(actionId);
|
|
394
|
+
this.actionIdToClip.delete(actionId);
|
|
395
|
+
}
|
|
396
|
+
advanceClipMonitor(monitor, previousTime) {
|
|
397
|
+
if (monitor.cleanedUp || monitor.action.paused && !monitor.finishedPending) return;
|
|
398
|
+
const currentTime = Math.max(0, Math.min(monitor.duration, monitor.action.time));
|
|
399
|
+
const delta = currentTime - previousTime;
|
|
400
|
+
if (monitor.loopMode === "pingpong") {
|
|
401
|
+
const movingForward = monitor.direction === 1;
|
|
402
|
+
const bouncedAtEnd = movingForward && delta < -CLIP_EVENT_EPSILON;
|
|
403
|
+
const bouncedAtStart = !movingForward && delta > CLIP_EVENT_EPSILON;
|
|
404
|
+
if (bouncedAtEnd) {
|
|
405
|
+
this.emitKeyframesForRange(monitor, previousTime, monitor.duration, 1, false);
|
|
406
|
+
monitor.direction = -1;
|
|
407
|
+
this.emitKeyframesForRange(monitor, monitor.duration, currentTime, -1, false);
|
|
408
|
+
} else if (bouncedAtStart) {
|
|
409
|
+
this.emitKeyframesForRange(monitor, previousTime, 0, -1, false);
|
|
410
|
+
monitor.direction = 1;
|
|
411
|
+
monitor.iteration += 1;
|
|
412
|
+
this.emitClipEvent(monitor, {
|
|
413
|
+
type: "loop",
|
|
414
|
+
clipName: monitor.clipName,
|
|
415
|
+
iteration: monitor.iteration,
|
|
416
|
+
currentTime: 0,
|
|
417
|
+
duration: monitor.duration
|
|
418
|
+
});
|
|
419
|
+
this.emitKeyframesForRange(monitor, 0, currentTime, 1, false);
|
|
420
|
+
} else if (delta > CLIP_EVENT_EPSILON) {
|
|
421
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, 1, false);
|
|
422
|
+
monitor.direction = 1;
|
|
423
|
+
} else if (delta < -CLIP_EVENT_EPSILON) {
|
|
424
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, -1, false);
|
|
425
|
+
monitor.direction = -1;
|
|
426
|
+
}
|
|
427
|
+
} else if (monitor.direction === 1) {
|
|
428
|
+
const wrapped = currentTime + CLIP_EVENT_EPSILON < previousTime;
|
|
429
|
+
if (wrapped) {
|
|
430
|
+
this.emitKeyframesForRange(monitor, previousTime, monitor.duration, 1, false);
|
|
431
|
+
monitor.iteration += 1;
|
|
432
|
+
this.emitClipEvent(monitor, {
|
|
433
|
+
type: "loop",
|
|
434
|
+
clipName: monitor.clipName,
|
|
435
|
+
iteration: monitor.iteration,
|
|
436
|
+
currentTime: 0,
|
|
437
|
+
duration: monitor.duration
|
|
438
|
+
});
|
|
439
|
+
this.emitKeyframesForRange(monitor, 0, currentTime, 1, true);
|
|
440
|
+
} else if (delta > CLIP_EVENT_EPSILON) {
|
|
441
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, 1, false);
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
const wrapped = currentTime > previousTime + CLIP_EVENT_EPSILON;
|
|
445
|
+
if (wrapped) {
|
|
446
|
+
this.emitKeyframesForRange(monitor, previousTime, 0, -1, false);
|
|
447
|
+
monitor.iteration += 1;
|
|
448
|
+
this.emitClipEvent(monitor, {
|
|
449
|
+
type: "loop",
|
|
450
|
+
clipName: monitor.clipName,
|
|
451
|
+
iteration: monitor.iteration,
|
|
452
|
+
currentTime: monitor.duration,
|
|
453
|
+
duration: monitor.duration
|
|
454
|
+
});
|
|
455
|
+
this.emitKeyframesForRange(monitor, monitor.duration, currentTime, -1, true);
|
|
456
|
+
} else if (delta < -CLIP_EVENT_EPSILON) {
|
|
457
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, -1, false);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
this.syncClipMonitorTime(monitor, currentTime);
|
|
461
|
+
}
|
|
304
462
|
normalizePlaybackOptions(options, defaults) {
|
|
305
463
|
const clipOptions = options;
|
|
306
464
|
const rawRate = options?.playbackRate ?? options?.speed ?? 1;
|
|
@@ -512,7 +670,28 @@ var BakedAnimationController = class {
|
|
|
512
670
|
}
|
|
513
671
|
update(dtSeconds) {
|
|
514
672
|
if (this.animationMixer) {
|
|
673
|
+
const snapshots = Array.from(this.clipMonitors.values()).map((monitor) => ({
|
|
674
|
+
actionId: monitor.actionId,
|
|
675
|
+
previousTime: monitor.action.time
|
|
676
|
+
}));
|
|
515
677
|
this.animationMixer.update(dtSeconds);
|
|
678
|
+
for (const { actionId, previousTime } of snapshots) {
|
|
679
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
680
|
+
if (!monitor) continue;
|
|
681
|
+
this.advanceClipMonitor(monitor, previousTime);
|
|
682
|
+
if (monitor.finishedPending) {
|
|
683
|
+
const finalTime = Math.max(0, Math.min(monitor.duration, monitor.action.time));
|
|
684
|
+
this.syncClipMonitorTime(monitor, finalTime);
|
|
685
|
+
this.emitClipEvent(monitor, {
|
|
686
|
+
type: "completed",
|
|
687
|
+
clipName: monitor.clipName,
|
|
688
|
+
currentTime: finalTime,
|
|
689
|
+
duration: monitor.duration,
|
|
690
|
+
iteration: monitor.iteration
|
|
691
|
+
});
|
|
692
|
+
this.cleanupClipMonitor(actionId);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
516
695
|
}
|
|
517
696
|
}
|
|
518
697
|
dispose() {
|
|
@@ -532,6 +711,7 @@ var BakedAnimationController = class {
|
|
|
532
711
|
this.clipHandles.clear();
|
|
533
712
|
this.clipSources.clear();
|
|
534
713
|
this.playbackState.clear();
|
|
714
|
+
this.clipMonitors.clear();
|
|
535
715
|
}
|
|
536
716
|
loadAnimationClips(clips) {
|
|
537
717
|
const model = this.host.getModel();
|
|
@@ -670,6 +850,7 @@ var BakedAnimationController = class {
|
|
|
670
850
|
}
|
|
671
851
|
const action = this.animationActions.get(clipName);
|
|
672
852
|
if (action) {
|
|
853
|
+
const actionId = this.getActionId(action);
|
|
673
854
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
674
855
|
action.stop();
|
|
675
856
|
if (!isBaked && this.animationMixer) {
|
|
@@ -692,9 +873,11 @@ var BakedAnimationController = class {
|
|
|
692
873
|
}
|
|
693
874
|
}
|
|
694
875
|
this.animationFinishedCallbacks.delete(clipName);
|
|
876
|
+
if (actionId) this.cleanupClipMonitor(actionId);
|
|
695
877
|
}
|
|
696
878
|
const clipAction = this.clipActions.get(clipName);
|
|
697
879
|
if (clipAction && clipAction !== action) {
|
|
880
|
+
const actionId = this.getActionId(clipAction);
|
|
698
881
|
try {
|
|
699
882
|
clipAction.stop();
|
|
700
883
|
if (this.animationMixer) {
|
|
@@ -707,6 +890,7 @@ var BakedAnimationController = class {
|
|
|
707
890
|
} catch {
|
|
708
891
|
}
|
|
709
892
|
this.clipActions.delete(clipName);
|
|
893
|
+
if (actionId) this.cleanupClipMonitor(actionId);
|
|
710
894
|
}
|
|
711
895
|
if (this.clipActions.get(clipName) === action) {
|
|
712
896
|
this.clipActions.delete(clipName);
|
|
@@ -1268,6 +1452,7 @@ var BakedAnimationController = class {
|
|
|
1268
1452
|
return null;
|
|
1269
1453
|
}
|
|
1270
1454
|
const clip = new AnimationClip(clipName, maxTime, tracks);
|
|
1455
|
+
this.setClipEventMetadata(clip, { keyframeTimes });
|
|
1271
1456
|
console.log(`[Loom3] snippetToClip: Created clip "${clipName}" with ${tracks.length} tracks, duration ${maxTime.toFixed(2)}s`);
|
|
1272
1457
|
return clip;
|
|
1273
1458
|
}
|
|
@@ -1299,28 +1484,38 @@ var BakedAnimationController = class {
|
|
|
1299
1484
|
this.animationClips.push(clip);
|
|
1300
1485
|
}
|
|
1301
1486
|
this.applyPlaybackState(action, playbackState);
|
|
1487
|
+
if (actionId) {
|
|
1488
|
+
this.cleanupClipMonitor(actionId);
|
|
1489
|
+
}
|
|
1302
1490
|
let resolveFinished;
|
|
1303
1491
|
const finishedPromise = new Promise((resolve) => {
|
|
1304
1492
|
resolveFinished = resolve;
|
|
1305
1493
|
});
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1494
|
+
const keyframeTimes = this.getClipEventMetadata(clip).keyframeTimes;
|
|
1495
|
+
const initialDirection = playbackState.reverse ? -1 : 1;
|
|
1496
|
+
const monitor = {
|
|
1497
|
+
action,
|
|
1498
|
+
actionId,
|
|
1499
|
+
clip,
|
|
1500
|
+
clipName: clip.name,
|
|
1501
|
+
duration: clip.duration,
|
|
1502
|
+
keyframeTimes,
|
|
1503
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
1504
|
+
initialDirection,
|
|
1505
|
+
direction: initialDirection,
|
|
1506
|
+
iteration: 0,
|
|
1507
|
+
lastTime: Math.max(0, Math.min(clip.duration, action.time)),
|
|
1508
|
+
lastKeyframeIndex: this.getKeyframeIndex(keyframeTimes, action.time),
|
|
1509
|
+
loopMode: playbackState.loopMode,
|
|
1510
|
+
finishedPending: false,
|
|
1511
|
+
cleanedUp: false,
|
|
1512
|
+
resolveFinished
|
|
1315
1513
|
};
|
|
1316
|
-
this.
|
|
1317
|
-
resolveFinished();
|
|
1318
|
-
cleanup();
|
|
1319
|
-
});
|
|
1320
|
-
finishedPromise.catch(() => cleanup());
|
|
1514
|
+
this.clipMonitors.set(actionId, monitor);
|
|
1321
1515
|
action.reset();
|
|
1322
1516
|
action.time = startTime;
|
|
1323
1517
|
action.play();
|
|
1518
|
+
this.resetClipMonitor(monitor, action.time);
|
|
1324
1519
|
this.clipActions.set(clip.name, action);
|
|
1325
1520
|
this.animationActions.set(clip.name, action);
|
|
1326
1521
|
this.setPlaybackState(clip.name, playbackState);
|
|
@@ -1338,6 +1533,7 @@ var BakedAnimationController = class {
|
|
|
1338
1533
|
})
|
|
1339
1534
|
);
|
|
1340
1535
|
action.play();
|
|
1536
|
+
this.resetClipMonitor(monitor, action.time);
|
|
1341
1537
|
},
|
|
1342
1538
|
stop: () => {
|
|
1343
1539
|
action.stop();
|
|
@@ -1355,8 +1551,7 @@ var BakedAnimationController = class {
|
|
|
1355
1551
|
this.animationActions.delete(clip.name);
|
|
1356
1552
|
this.animationFinishedCallbacks.delete(clip.name);
|
|
1357
1553
|
this.playbackState.delete(clip.name);
|
|
1358
|
-
|
|
1359
|
-
cleanup();
|
|
1554
|
+
this.cleanupClipMonitor(actionId);
|
|
1360
1555
|
},
|
|
1361
1556
|
pause: () => {
|
|
1362
1557
|
action.paused = true;
|
|
@@ -1374,6 +1569,8 @@ var BakedAnimationController = class {
|
|
|
1374
1569
|
const next = this.playbackState.get(clip.name) ?? playbackState;
|
|
1375
1570
|
next.playbackRate = Number.isFinite(r) ? Math.max(0, Math.abs(r)) : 1;
|
|
1376
1571
|
this.applyPlaybackState(action, next);
|
|
1572
|
+
monitor.direction = next.reverse ? -1 : 1;
|
|
1573
|
+
monitor.initialDirection = monitor.direction;
|
|
1377
1574
|
this.setPlaybackState(clip.name, next);
|
|
1378
1575
|
},
|
|
1379
1576
|
setLoop: (mode, repeatCount) => {
|
|
@@ -1382,6 +1579,7 @@ var BakedAnimationController = class {
|
|
|
1382
1579
|
next.loop = mode !== "once";
|
|
1383
1580
|
next.repeatCount = repeatCount;
|
|
1384
1581
|
this.applyPlaybackState(action, next);
|
|
1582
|
+
monitor.loopMode = mode;
|
|
1385
1583
|
this.setPlaybackState(clip.name, next);
|
|
1386
1584
|
},
|
|
1387
1585
|
setTime: (t) => {
|
|
@@ -1391,9 +1589,16 @@ var BakedAnimationController = class {
|
|
|
1391
1589
|
this.animationMixer?.update(0);
|
|
1392
1590
|
} catch {
|
|
1393
1591
|
}
|
|
1592
|
+
this.syncClipMonitorTime(monitor, clamped, true);
|
|
1394
1593
|
},
|
|
1395
1594
|
getTime: () => action.time,
|
|
1396
1595
|
getDuration: () => clip.duration,
|
|
1596
|
+
subscribe: (listener) => {
|
|
1597
|
+
monitor.listeners.add(listener);
|
|
1598
|
+
return () => {
|
|
1599
|
+
monitor.listeners.delete(listener);
|
|
1600
|
+
};
|
|
1601
|
+
},
|
|
1397
1602
|
finished: finishedPromise
|
|
1398
1603
|
};
|
|
1399
1604
|
this.clipHandles.set(clip.name, handle);
|
|
@@ -1417,6 +1622,7 @@ var BakedAnimationController = class {
|
|
|
1417
1622
|
if (!this.animationMixer || !this.host.getModel()) return;
|
|
1418
1623
|
for (const [clipName, action] of Array.from(this.clipActions.entries())) {
|
|
1419
1624
|
if (clipName === name || clipName.startsWith(`${name}_`)) {
|
|
1625
|
+
const actionId = this.getActionId(action);
|
|
1420
1626
|
try {
|
|
1421
1627
|
action.stop();
|
|
1422
1628
|
const clip = action.getClip();
|
|
@@ -1431,6 +1637,7 @@ var BakedAnimationController = class {
|
|
|
1431
1637
|
this.clipHandles.delete(clipName);
|
|
1432
1638
|
this.animationFinishedCallbacks.delete(clipName);
|
|
1433
1639
|
this.playbackState.delete(clipName);
|
|
1640
|
+
if (actionId) this.cleanupClipMonitor(actionId);
|
|
1434
1641
|
}
|
|
1435
1642
|
}
|
|
1436
1643
|
}
|
|
@@ -1454,6 +1661,8 @@ var BakedAnimationController = class {
|
|
|
1454
1661
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
1455
1662
|
const apply = (action) => {
|
|
1456
1663
|
if (!action) return;
|
|
1664
|
+
const actionId = this.getActionId(action);
|
|
1665
|
+
const monitor = actionId ? this.clipMonitors.get(actionId) : void 0;
|
|
1457
1666
|
const clipName = action.getClip().name;
|
|
1458
1667
|
const next = this.playbackState.get(clipName) ?? this.normalizePlaybackOptions(void 0, { loop: false, source: this.clipSources.get(clipName) ?? "clip" });
|
|
1459
1668
|
try {
|
|
@@ -1472,6 +1681,10 @@ var BakedAnimationController = class {
|
|
|
1472
1681
|
}
|
|
1473
1682
|
const signedRate = next.reverse ? -next.playbackRate : next.playbackRate;
|
|
1474
1683
|
action.setEffectiveTimeScale(signedRate);
|
|
1684
|
+
if (monitor) {
|
|
1685
|
+
monitor.direction = next.reverse ? -1 : 1;
|
|
1686
|
+
monitor.initialDirection = monitor.direction;
|
|
1687
|
+
}
|
|
1475
1688
|
updated = true;
|
|
1476
1689
|
}
|
|
1477
1690
|
if (typeof params.loop === "boolean" || params.loopMode || params.repeatCount !== void 0) {
|
|
@@ -1479,6 +1692,7 @@ var BakedAnimationController = class {
|
|
|
1479
1692
|
next.loop = next.loopMode !== "once";
|
|
1480
1693
|
next.repeatCount = params.repeatCount;
|
|
1481
1694
|
this.applyPlaybackState(action, next);
|
|
1695
|
+
if (monitor) monitor.loopMode = next.loopMode;
|
|
1482
1696
|
updated = true;
|
|
1483
1697
|
}
|
|
1484
1698
|
this.setPlaybackState(clipName, next);
|
|
@@ -1558,6 +1772,14 @@ var BakedAnimationController = class {
|
|
|
1558
1772
|
if (this.animationMixer && !this.mixerFinishedListenerAttached) {
|
|
1559
1773
|
this.animationMixer.addEventListener("finished", (event) => {
|
|
1560
1774
|
const action = event.action;
|
|
1775
|
+
const actionId = this.getActionId(action);
|
|
1776
|
+
if (actionId) {
|
|
1777
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
1778
|
+
if (monitor) {
|
|
1779
|
+
monitor.finishedPending = true;
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1561
1783
|
const clip = action.getClip();
|
|
1562
1784
|
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
1563
1785
|
if (bakedRuntime) {
|
|
@@ -4264,9 +4486,11 @@ function getPreset(presetType) {
|
|
|
4264
4486
|
return CC4_PRESET;
|
|
4265
4487
|
}
|
|
4266
4488
|
}
|
|
4489
|
+
var resolvePreset = getPreset;
|
|
4267
4490
|
function getPresetWithProfile(presetType, profile) {
|
|
4268
4491
|
return extendPresetWithProfile(getPreset(presetType), profile);
|
|
4269
4492
|
}
|
|
4493
|
+
var resolvePresetWithOverrides = getPresetWithProfile;
|
|
4270
4494
|
|
|
4271
4495
|
// src/engines/three/Loom3.ts
|
|
4272
4496
|
var deg2rad = (d) => d * Math.PI / 180;
|
|
@@ -5985,10 +6209,11 @@ function extractProfileOverrides(config) {
|
|
|
5985
6209
|
if (key === "annotationRegions") {
|
|
5986
6210
|
const topLevelAnnotationRegions = Array.isArray(topLevelConfig.annotationRegions) ? topLevelConfig.annotationRegions : void 0;
|
|
5987
6211
|
const legacyAnnotationRegions = Array.isArray(legacyNestedOverrides.annotationRegions) ? legacyNestedOverrides.annotationRegions : void 0;
|
|
5988
|
-
const
|
|
6212
|
+
const legacyRegionFallback = Array.isArray(config.regions) && config.regions.length > 0 ? config.regions : void 0;
|
|
6213
|
+
const legacyProfileRegions = mergeRegionsByName(legacyRegionFallback, legacyAnnotationRegions);
|
|
5989
6214
|
const regions = mergeRegionsByName(
|
|
5990
|
-
|
|
5991
|
-
|
|
6215
|
+
legacyProfileRegions,
|
|
6216
|
+
topLevelAnnotationRegions
|
|
5992
6217
|
);
|
|
5993
6218
|
if (regions) {
|
|
5994
6219
|
overrides.annotationRegions = regions.map((region) => cloneRegion(region));
|
|
@@ -6004,6 +6229,13 @@ function extractProfileOverrides(config) {
|
|
|
6004
6229
|
}
|
|
6005
6230
|
return overrides;
|
|
6006
6231
|
}
|
|
6232
|
+
function hasCanonicalAnnotationRegionOverrides(config) {
|
|
6233
|
+
const topLevelConfig = config;
|
|
6234
|
+
if (Array.isArray(topLevelConfig.annotationRegions)) {
|
|
6235
|
+
return true;
|
|
6236
|
+
}
|
|
6237
|
+
return isPlainObject2(config.profile) && Array.isArray(config.profile.annotationRegions);
|
|
6238
|
+
}
|
|
6007
6239
|
function applyCharacterProfileToPreset(config) {
|
|
6008
6240
|
const presetType = config.auPresetType;
|
|
6009
6241
|
if (!presetType) {
|
|
@@ -6041,7 +6273,7 @@ function extendCharacterConfigWithPreset(config) {
|
|
|
6041
6273
|
return config;
|
|
6042
6274
|
}
|
|
6043
6275
|
const presetRegions = extendedPresetProfile.annotationRegions;
|
|
6044
|
-
const mergedRegions = mergeRegionsByName(presetRegions, config.regions);
|
|
6276
|
+
const mergedRegions = hasCanonicalAnnotationRegionOverrides(config) ? mergeRegionsByName(config.regions, presetRegions) : mergeRegionsByName(presetRegions, config.regions);
|
|
6045
6277
|
const normalizedRegions = normalizeRegionTree(
|
|
6046
6278
|
mergedRegions,
|
|
6047
6279
|
profileOverrides.disabledRegions
|
|
@@ -6056,6 +6288,43 @@ function extendCharacterConfigWithPreset(config) {
|
|
|
6056
6288
|
regions: extendedRegions ?? config.regions
|
|
6057
6289
|
};
|
|
6058
6290
|
}
|
|
6291
|
+
var DEFAULT_EPSILON = 1e-4;
|
|
6292
|
+
var DEFAULT_YAW_WEIGHT = 0.35;
|
|
6293
|
+
var DEFAULT_PITCH_WEIGHT = 0.2;
|
|
6294
|
+
var ZERO_OFFSET = { x: 0, y: 0 };
|
|
6295
|
+
function clampOffset(value) {
|
|
6296
|
+
return THREE2.MathUtils.clamp(value, -1, 1);
|
|
6297
|
+
}
|
|
6298
|
+
function toModelLocalDirection(model, worldDirection) {
|
|
6299
|
+
const localDirection = worldDirection.clone();
|
|
6300
|
+
model.updateWorldMatrix(true, false);
|
|
6301
|
+
const worldQuaternion = new THREE2.Quaternion();
|
|
6302
|
+
model.getWorldQuaternion(worldQuaternion);
|
|
6303
|
+
localDirection.applyQuaternion(worldQuaternion.invert());
|
|
6304
|
+
return localDirection.normalize();
|
|
6305
|
+
}
|
|
6306
|
+
function computeCameraRelativeGazeOffset(model, cameraPosition, targetPosition, options = {}) {
|
|
6307
|
+
if (!model) {
|
|
6308
|
+
return ZERO_OFFSET;
|
|
6309
|
+
}
|
|
6310
|
+
const epsilon = options.epsilon ?? DEFAULT_EPSILON;
|
|
6311
|
+
const yawWeight = options.yawWeight ?? DEFAULT_YAW_WEIGHT;
|
|
6312
|
+
const pitchWeight = options.pitchWeight ?? DEFAULT_PITCH_WEIGHT;
|
|
6313
|
+
const worldOffset = new THREE2.Vector3().subVectors(cameraPosition, targetPosition);
|
|
6314
|
+
if (worldOffset.lengthSq() < epsilon) {
|
|
6315
|
+
return ZERO_OFFSET;
|
|
6316
|
+
}
|
|
6317
|
+
const localDirection = toModelLocalDirection(model, worldOffset.normalize());
|
|
6318
|
+
const yawAngle = Math.atan2(localDirection.x, localDirection.z);
|
|
6319
|
+
const pitchAngle = Math.atan2(
|
|
6320
|
+
localDirection.y,
|
|
6321
|
+
Math.max(Math.hypot(localDirection.x, localDirection.z), epsilon)
|
|
6322
|
+
);
|
|
6323
|
+
return {
|
|
6324
|
+
x: clampOffset(yawAngle / (Math.PI / 2)) * yawWeight,
|
|
6325
|
+
y: clampOffset(pitchAngle / (Math.PI / 3)) * pitchWeight
|
|
6326
|
+
};
|
|
6327
|
+
}
|
|
6059
6328
|
var DEFAULT_HEAD_BONE_NAMES = ["CC_Base_Head", "Head", "head", "Bip01_Head"];
|
|
6060
6329
|
var DEFAULT_REFERENCE_HEIGHT = 1.8;
|
|
6061
6330
|
var DEFAULT_FORWARD_OFFSET = 0.08;
|
|
@@ -6071,8 +6340,8 @@ function findFaceCenter(model, options = {}) {
|
|
|
6071
6340
|
referenceHeight = DEFAULT_REFERENCE_HEIGHT
|
|
6072
6341
|
} = options;
|
|
6073
6342
|
const debugInfo = [];
|
|
6074
|
-
const boundingBox = new
|
|
6075
|
-
const modelSize = new
|
|
6343
|
+
const boundingBox = new THREE2.Box3().setFromObject(model);
|
|
6344
|
+
const modelSize = new THREE2.Vector3();
|
|
6076
6345
|
boundingBox.getSize(modelSize);
|
|
6077
6346
|
const scale = modelSize.y / referenceHeight;
|
|
6078
6347
|
debugInfo.push(`Model height: ${modelSize.y.toFixed(2)}m, scale factor: ${scale.toFixed(2)}`);
|
|
@@ -6083,7 +6352,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6083
6352
|
const objName = obj.name.toLowerCase();
|
|
6084
6353
|
for (const boneName of headBoneNames) {
|
|
6085
6354
|
if (objName === boneName.toLowerCase() || objName.includes(boneName.toLowerCase())) {
|
|
6086
|
-
headPos2 = new
|
|
6355
|
+
headPos2 = new THREE2.Vector3();
|
|
6087
6356
|
obj.getWorldPosition(headPos2);
|
|
6088
6357
|
debugInfo.push(`Found head bone "${obj.name}" at (${headPos2.x.toFixed(3)}, ${headPos2.y.toFixed(3)}, ${headPos2.z.toFixed(3)})`);
|
|
6089
6358
|
return;
|
|
@@ -6102,7 +6371,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6102
6371
|
if (!eyes2.left) {
|
|
6103
6372
|
for (const name of EYE_BONE_NAMES.left) {
|
|
6104
6373
|
if (objName.includes(name)) {
|
|
6105
|
-
eyes2.left = new
|
|
6374
|
+
eyes2.left = new THREE2.Vector3();
|
|
6106
6375
|
obj.getWorldPosition(eyes2.left);
|
|
6107
6376
|
debugInfo.push(`Found left eye "${obj.name}" at (${eyes2.left.x.toFixed(3)}, ${eyes2.left.y.toFixed(3)}, ${eyes2.left.z.toFixed(3)})`);
|
|
6108
6377
|
break;
|
|
@@ -6112,7 +6381,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6112
6381
|
if (!eyes2.right) {
|
|
6113
6382
|
for (const name of EYE_BONE_NAMES.right) {
|
|
6114
6383
|
if (objName.includes(name)) {
|
|
6115
|
-
eyes2.right = new
|
|
6384
|
+
eyes2.right = new THREE2.Vector3();
|
|
6116
6385
|
obj.getWorldPosition(eyes2.right);
|
|
6117
6386
|
debugInfo.push(`Found right eye "${obj.name}" at (${eyes2.right.x.toFixed(3)}, ${eyes2.right.y.toFixed(3)}, ${eyes2.right.z.toFixed(3)})`);
|
|
6118
6387
|
break;
|
|
@@ -6124,7 +6393,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6124
6393
|
};
|
|
6125
6394
|
if (faceMeshNames && faceMeshNames.length > 0) {
|
|
6126
6395
|
debugInfo.push(`Looking for face meshes: ${faceMeshNames.join(", ")}`);
|
|
6127
|
-
const meshBox = new
|
|
6396
|
+
const meshBox = new THREE2.Box3();
|
|
6128
6397
|
let foundMesh = false;
|
|
6129
6398
|
model.traverse((obj) => {
|
|
6130
6399
|
const isMesh = obj.isMesh;
|
|
@@ -6132,7 +6401,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6132
6401
|
for (const meshName of faceMeshNames) {
|
|
6133
6402
|
if (obj.name === meshName || obj.name.includes(meshName)) {
|
|
6134
6403
|
const mesh = obj;
|
|
6135
|
-
const tempBox = new
|
|
6404
|
+
const tempBox = new THREE2.Box3().setFromObject(mesh);
|
|
6136
6405
|
meshBox.union(tempBox);
|
|
6137
6406
|
foundMesh = true;
|
|
6138
6407
|
debugInfo.push(`Found mesh: "${obj.name}"`);
|
|
@@ -6141,11 +6410,11 @@ function findFaceCenter(model, options = {}) {
|
|
|
6141
6410
|
}
|
|
6142
6411
|
});
|
|
6143
6412
|
if (foundMesh && !meshBox.isEmpty()) {
|
|
6144
|
-
const meshBoundsSize = new
|
|
6413
|
+
const meshBoundsSize = new THREE2.Vector3();
|
|
6145
6414
|
meshBox.getSize(meshBoundsSize);
|
|
6146
6415
|
const isFullBodyMesh = meshBoundsSize.y > modelSize.y * 0.7;
|
|
6147
6416
|
if (!isFullBodyMesh) {
|
|
6148
|
-
const meshCenter = new
|
|
6417
|
+
const meshCenter = new THREE2.Vector3();
|
|
6149
6418
|
meshBox.getCenter(meshCenter);
|
|
6150
6419
|
debugInfo.push(`Using face mesh center: (${meshCenter.x.toFixed(3)}, ${meshCenter.y.toFixed(3)}, ${meshCenter.z.toFixed(3)})`);
|
|
6151
6420
|
return {
|
|
@@ -6159,7 +6428,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6159
6428
|
}
|
|
6160
6429
|
const eyes = findEyes();
|
|
6161
6430
|
if (eyes.left && eyes.right) {
|
|
6162
|
-
const eyeCenter = new
|
|
6431
|
+
const eyeCenter = new THREE2.Vector3().addVectors(eyes.left, eyes.right).multiplyScalar(0.5);
|
|
6163
6432
|
debugInfo.push(`Eye center (face position): (${eyeCenter.x.toFixed(3)}, ${eyeCenter.y.toFixed(3)}, ${eyeCenter.z.toFixed(3)})`);
|
|
6164
6433
|
const headPos2 = findHeadBone();
|
|
6165
6434
|
return {
|
|
@@ -6184,10 +6453,10 @@ function findFaceCenter(model, options = {}) {
|
|
|
6184
6453
|
};
|
|
6185
6454
|
}
|
|
6186
6455
|
debugInfo.push("No head bone found, using model center fallback");
|
|
6187
|
-
const modelCenter = new
|
|
6456
|
+
const modelCenter = new THREE2.Vector3();
|
|
6188
6457
|
boundingBox.getCenter(modelCenter);
|
|
6189
6458
|
const headHeight = boundingBox.min.y + modelSize.y * 0.9;
|
|
6190
|
-
const fallbackCenter = new
|
|
6459
|
+
const fallbackCenter = new THREE2.Vector3(modelCenter.x, headHeight, modelCenter.z);
|
|
6191
6460
|
debugInfo.push(`Fallback center: (${fallbackCenter.x.toFixed(3)}, ${fallbackCenter.y.toFixed(3)}, ${fallbackCenter.z.toFixed(3)})`);
|
|
6192
6461
|
return {
|
|
6193
6462
|
center: fallbackCenter,
|
|
@@ -6196,7 +6465,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6196
6465
|
};
|
|
6197
6466
|
}
|
|
6198
6467
|
function getModelForwardDirection(model) {
|
|
6199
|
-
const forward = new
|
|
6468
|
+
const forward = new THREE2.Vector3(0, 0, 1);
|
|
6200
6469
|
forward.applyQuaternion(model.quaternion);
|
|
6201
6470
|
return forward.normalize();
|
|
6202
6471
|
}
|
|
@@ -6213,7 +6482,7 @@ function detectFacingDirection(model, eyeBoneNames = {
|
|
|
6213
6482
|
if (!eyes.left) {
|
|
6214
6483
|
for (const name of eyeBoneNames.left) {
|
|
6215
6484
|
if (objName.includes(name)) {
|
|
6216
|
-
eyes.left = new
|
|
6485
|
+
eyes.left = new THREE2.Vector3();
|
|
6217
6486
|
obj.getWorldPosition(eyes.left);
|
|
6218
6487
|
break;
|
|
6219
6488
|
}
|
|
@@ -6222,7 +6491,7 @@ function detectFacingDirection(model, eyeBoneNames = {
|
|
|
6222
6491
|
if (!eyes.right) {
|
|
6223
6492
|
for (const name of eyeBoneNames.right) {
|
|
6224
6493
|
if (objName.includes(name)) {
|
|
6225
|
-
eyes.right = new
|
|
6494
|
+
eyes.right = new THREE2.Vector3();
|
|
6226
6495
|
obj.getWorldPosition(eyes.right);
|
|
6227
6496
|
break;
|
|
6228
6497
|
}
|
|
@@ -7138,19 +7407,19 @@ function extractBones(root) {
|
|
|
7138
7407
|
const bones = [];
|
|
7139
7408
|
const boneDepths = /* @__PURE__ */ new Map();
|
|
7140
7409
|
root.traverse((obj) => {
|
|
7141
|
-
if (obj instanceof
|
|
7142
|
-
const worldPos = new
|
|
7410
|
+
if (obj instanceof THREE2.Bone || obj.type === "Bone") {
|
|
7411
|
+
const worldPos = new THREE2.Vector3();
|
|
7143
7412
|
obj.getWorldPosition(worldPos);
|
|
7144
7413
|
let depth = 0;
|
|
7145
7414
|
let parent = obj.parent;
|
|
7146
7415
|
while (parent) {
|
|
7147
|
-
if (parent instanceof
|
|
7416
|
+
if (parent instanceof THREE2.Bone || parent.type === "Bone") {
|
|
7148
7417
|
depth++;
|
|
7149
7418
|
}
|
|
7150
7419
|
parent = parent.parent;
|
|
7151
7420
|
}
|
|
7152
7421
|
boneDepths.set(obj.name, depth);
|
|
7153
|
-
const parentBone = obj.parent instanceof
|
|
7422
|
+
const parentBone = obj.parent instanceof THREE2.Bone || obj.parent?.type === "Bone" ? obj.parent.name : null;
|
|
7154
7423
|
bones.push({
|
|
7155
7424
|
name: obj.name,
|
|
7156
7425
|
parent: parentBone,
|
|
@@ -7374,6 +7643,6 @@ async function analyzeModel(options) {
|
|
|
7374
7643
|
};
|
|
7375
7644
|
}
|
|
7376
7645
|
|
|
7377
|
-
export { AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, FISH_AU_MAPPING_CONFIG, HairPhysics, Loom3, Loom3 as Loom3Three, Loom3 as LoomLargeThree, MORPH_TO_MESH, VISEME_JAW_AMOUNTS, VISEME_KEYS, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, suggestBestPreset, validateMappingConfig, validateMappings };
|
|
7646
|
+
export { AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, FISH_AU_MAPPING_CONFIG, HairPhysics, Loom3, Loom3 as Loom3Three, Loom3 as LoomLargeThree, MORPH_TO_MESH, VISEME_JAW_AMOUNTS, VISEME_KEYS, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, suggestBestPreset, validateMappingConfig, validateMappings };
|
|
7378
7647
|
//# sourceMappingURL=index.js.map
|
|
7379
7648
|
//# sourceMappingURL=index.js.map
|