@lovelace_lol/loom3 1.0.36 → 1.0.37
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 +242 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +241 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1846,6 +1846,7 @@ Loom3 can convert AU/morph curves into AnimationMixer clips for smooth, mixer-on
|
|
|
1846
1846
|
Key APIs:
|
|
1847
1847
|
- `snippetToClip(name, curves, options)` builds an AnimationClip from curves.
|
|
1848
1848
|
- `playClip(clip, options)` returns a ClipHandle you can pause/resume/stop.
|
|
1849
|
+
- `clipHandle.subscribe(listener)` streams lifecycle events from the runtime update loop.
|
|
1849
1850
|
- `clipHandle.stop()` now resolves cleanly (no rejected promise).
|
|
1850
1851
|
|
|
1851
1852
|
```typescript
|
|
@@ -1856,10 +1857,24 @@ const clip = loom.snippetToClip('gaze', {
|
|
|
1856
1857
|
|
|
1857
1858
|
if (clip) {
|
|
1858
1859
|
const handle = loom.playClip(clip, { loop: false, speed: 1 });
|
|
1860
|
+
const unsubscribe = handle?.subscribe?.((event) => {
|
|
1861
|
+
if (event.type === 'keyframe') {
|
|
1862
|
+
console.log(event.currentTime, event.keyframeIndex);
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1859
1866
|
await handle.finished;
|
|
1867
|
+
unsubscribe?.();
|
|
1860
1868
|
}
|
|
1861
1869
|
```
|
|
1862
1870
|
|
|
1871
|
+
Clip stream events are discrete runtime events, not a polling surface:
|
|
1872
|
+
|
|
1873
|
+
- `keyframe` fires when playback crosses an authored keyframe.
|
|
1874
|
+
- `loop` fires when looping playback starts another iteration.
|
|
1875
|
+
- `seek` fires when `setTime()` scrubs the clip.
|
|
1876
|
+
- `completed` fires when non-looping playback reaches its terminal state.
|
|
1877
|
+
|
|
1863
1878
|
### Playing a snippet directly
|
|
1864
1879
|
|
|
1865
1880
|
If you already have a named snippet object, you can skip manual clip creation:
|
|
@@ -2138,7 +2153,7 @@ This is a compact reference for the public surface exported by `@lovelace_lol/lo
|
|
|
2138
2153
|
|
|
2139
2154
|
### Types and lower-level exports
|
|
2140
2155
|
|
|
2141
|
-
- Configuration/types: `Profile`, `MeshInfo`, `BlendingMode`, `TransitionHandle`, `ClipHandle`, `Snippet`, `AnimationState`, `AnimationClipInfo`.
|
|
2156
|
+
- Configuration/types: `Profile`, `MeshInfo`, `BlendingMode`, `TransitionHandle`, `ClipEvent`, `ClipEventListener`, `ClipHandle`, `Snippet`, `AnimationState`, `AnimationClipInfo`.
|
|
2142
2157
|
- Standalone implementations: `AnimationThree`, `HairPhysics`, `BLENDING_MODES`.
|
|
2143
2158
|
- Region and geometry helpers: `resolveBoneName()`, `resolveBoneNames()`, `resolveFaceCenter()`, `findFaceCenter()`, `getModelForwardDirection()`, `detectFacingDirection()`.
|
|
2144
2159
|
|
package/dist/index.cjs
CHANGED
|
@@ -291,6 +291,8 @@ var makeActionId = () => `act_${Math.random().toString(36).slice(2, 8)}_${Date.n
|
|
|
291
291
|
var X_AXIS = new THREE.Vector3(1, 0, 0);
|
|
292
292
|
var Y_AXIS = new THREE.Vector3(0, 1, 0);
|
|
293
293
|
var Z_AXIS = new THREE.Vector3(0, 0, 1);
|
|
294
|
+
var CLIP_EVENT_METADATA_KEY = "__loom3ClipEvents";
|
|
295
|
+
var CLIP_EVENT_EPSILON = 1e-4;
|
|
294
296
|
var BakedAnimationController = class {
|
|
295
297
|
constructor(host) {
|
|
296
298
|
__publicField(this, "host");
|
|
@@ -309,6 +311,7 @@ var BakedAnimationController = class {
|
|
|
309
311
|
__publicField(this, "playbackState", /* @__PURE__ */ new Map());
|
|
310
312
|
__publicField(this, "actionIds", /* @__PURE__ */ new WeakMap());
|
|
311
313
|
__publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
|
|
314
|
+
__publicField(this, "clipMonitors", /* @__PURE__ */ new Map());
|
|
312
315
|
this.host = host;
|
|
313
316
|
}
|
|
314
317
|
getActionId(action) {
|
|
@@ -322,6 +325,161 @@ var BakedAnimationController = class {
|
|
|
322
325
|
action.__actionId = actionId;
|
|
323
326
|
return actionId;
|
|
324
327
|
}
|
|
328
|
+
setClipEventMetadata(clip, metadata) {
|
|
329
|
+
const userData = clip.userData ?? (clip.userData = {});
|
|
330
|
+
userData[CLIP_EVENT_METADATA_KEY] = metadata;
|
|
331
|
+
}
|
|
332
|
+
getClipEventMetadata(clip) {
|
|
333
|
+
const userData = clip.userData;
|
|
334
|
+
const keyframeTimes = Array.isArray(userData?.[CLIP_EVENT_METADATA_KEY]?.keyframeTimes) ? userData[CLIP_EVENT_METADATA_KEY].keyframeTimes.filter((time) => Number.isFinite(time)) : [];
|
|
335
|
+
return { keyframeTimes };
|
|
336
|
+
}
|
|
337
|
+
getKeyframeIndex(times, currentTime) {
|
|
338
|
+
if (!times.length) return -1;
|
|
339
|
+
const target = Math.max(0, currentTime) + 1e-3;
|
|
340
|
+
let lo = 0;
|
|
341
|
+
let hi = times.length - 1;
|
|
342
|
+
let idx = 0;
|
|
343
|
+
while (lo <= hi) {
|
|
344
|
+
const mid = lo + hi >>> 1;
|
|
345
|
+
if (times[mid] <= target) {
|
|
346
|
+
idx = mid;
|
|
347
|
+
lo = mid + 1;
|
|
348
|
+
} else {
|
|
349
|
+
hi = mid - 1;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return idx;
|
|
353
|
+
}
|
|
354
|
+
emitClipEvent(monitor, event) {
|
|
355
|
+
for (const listener of Array.from(monitor.listeners)) {
|
|
356
|
+
try {
|
|
357
|
+
listener(event);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error("[Loom3] clip event listener failed", error);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
emitKeyframesForRange(monitor, startTime, endTime, direction, includeStart) {
|
|
364
|
+
if (!monitor.keyframeTimes.length) return;
|
|
365
|
+
const times = direction === 1 ? monitor.keyframeTimes : [...monitor.keyframeTimes].reverse();
|
|
366
|
+
for (const time of times) {
|
|
367
|
+
const matchesForward = direction === 1 && (includeStart ? time >= startTime - CLIP_EVENT_EPSILON : time > startTime + CLIP_EVENT_EPSILON) && time <= endTime + CLIP_EVENT_EPSILON;
|
|
368
|
+
const matchesReverse = direction === -1 && (includeStart ? time <= startTime + CLIP_EVENT_EPSILON : time < startTime - CLIP_EVENT_EPSILON) && time >= endTime - CLIP_EVENT_EPSILON;
|
|
369
|
+
if (!matchesForward && !matchesReverse) continue;
|
|
370
|
+
const keyframeIndex = monitor.keyframeTimes.indexOf(time);
|
|
371
|
+
monitor.lastKeyframeIndex = keyframeIndex;
|
|
372
|
+
this.emitClipEvent(monitor, {
|
|
373
|
+
type: "keyframe",
|
|
374
|
+
clipName: monitor.clipName,
|
|
375
|
+
keyframeIndex,
|
|
376
|
+
totalKeyframes: monitor.keyframeTimes.length,
|
|
377
|
+
currentTime: time,
|
|
378
|
+
duration: monitor.duration,
|
|
379
|
+
iteration: monitor.iteration
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
resetClipMonitor(monitor, currentTime) {
|
|
384
|
+
monitor.iteration = 0;
|
|
385
|
+
monitor.direction = monitor.initialDirection;
|
|
386
|
+
monitor.lastTime = currentTime;
|
|
387
|
+
monitor.lastKeyframeIndex = this.getKeyframeIndex(monitor.keyframeTimes, currentTime);
|
|
388
|
+
monitor.finishedPending = false;
|
|
389
|
+
}
|
|
390
|
+
syncClipMonitorTime(monitor, currentTime, emitSeek = false) {
|
|
391
|
+
const clamped = Math.max(0, Math.min(monitor.duration, currentTime));
|
|
392
|
+
monitor.lastTime = clamped;
|
|
393
|
+
monitor.lastKeyframeIndex = this.getKeyframeIndex(monitor.keyframeTimes, clamped);
|
|
394
|
+
if (emitSeek) {
|
|
395
|
+
this.emitClipEvent(monitor, {
|
|
396
|
+
type: "seek",
|
|
397
|
+
clipName: monitor.clipName,
|
|
398
|
+
currentTime: clamped,
|
|
399
|
+
duration: monitor.duration,
|
|
400
|
+
iteration: monitor.iteration
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
cleanupClipMonitor(actionId) {
|
|
405
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
406
|
+
if (!monitor || monitor.cleanedUp) return;
|
|
407
|
+
monitor.cleanedUp = true;
|
|
408
|
+
try {
|
|
409
|
+
monitor.action.paused = true;
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
monitor.resolveFinished();
|
|
413
|
+
monitor.listeners.clear();
|
|
414
|
+
this.clipMonitors.delete(actionId);
|
|
415
|
+
this.actionIdToClip.delete(actionId);
|
|
416
|
+
}
|
|
417
|
+
advanceClipMonitor(monitor, previousTime) {
|
|
418
|
+
if (monitor.cleanedUp || monitor.action.paused && !monitor.finishedPending) return;
|
|
419
|
+
const currentTime = Math.max(0, Math.min(monitor.duration, monitor.action.time));
|
|
420
|
+
const delta = currentTime - previousTime;
|
|
421
|
+
if (monitor.loopMode === "pingpong") {
|
|
422
|
+
const movingForward = monitor.direction === 1;
|
|
423
|
+
const bouncedAtEnd = movingForward && delta < -CLIP_EVENT_EPSILON;
|
|
424
|
+
const bouncedAtStart = !movingForward && delta > CLIP_EVENT_EPSILON;
|
|
425
|
+
if (bouncedAtEnd) {
|
|
426
|
+
this.emitKeyframesForRange(monitor, previousTime, monitor.duration, 1, false);
|
|
427
|
+
monitor.direction = -1;
|
|
428
|
+
this.emitKeyframesForRange(monitor, monitor.duration, currentTime, -1, false);
|
|
429
|
+
} else if (bouncedAtStart) {
|
|
430
|
+
this.emitKeyframesForRange(monitor, previousTime, 0, -1, false);
|
|
431
|
+
monitor.direction = 1;
|
|
432
|
+
monitor.iteration += 1;
|
|
433
|
+
this.emitClipEvent(monitor, {
|
|
434
|
+
type: "loop",
|
|
435
|
+
clipName: monitor.clipName,
|
|
436
|
+
iteration: monitor.iteration,
|
|
437
|
+
currentTime: 0,
|
|
438
|
+
duration: monitor.duration
|
|
439
|
+
});
|
|
440
|
+
this.emitKeyframesForRange(monitor, 0, currentTime, 1, false);
|
|
441
|
+
} else if (delta > CLIP_EVENT_EPSILON) {
|
|
442
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, 1, false);
|
|
443
|
+
monitor.direction = 1;
|
|
444
|
+
} else if (delta < -CLIP_EVENT_EPSILON) {
|
|
445
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, -1, false);
|
|
446
|
+
monitor.direction = -1;
|
|
447
|
+
}
|
|
448
|
+
} else if (monitor.direction === 1) {
|
|
449
|
+
const wrapped = currentTime + CLIP_EVENT_EPSILON < previousTime;
|
|
450
|
+
if (wrapped) {
|
|
451
|
+
this.emitKeyframesForRange(monitor, previousTime, monitor.duration, 1, false);
|
|
452
|
+
monitor.iteration += 1;
|
|
453
|
+
this.emitClipEvent(monitor, {
|
|
454
|
+
type: "loop",
|
|
455
|
+
clipName: monitor.clipName,
|
|
456
|
+
iteration: monitor.iteration,
|
|
457
|
+
currentTime: 0,
|
|
458
|
+
duration: monitor.duration
|
|
459
|
+
});
|
|
460
|
+
this.emitKeyframesForRange(monitor, 0, currentTime, 1, true);
|
|
461
|
+
} else if (delta > CLIP_EVENT_EPSILON) {
|
|
462
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, 1, false);
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
const wrapped = currentTime > previousTime + CLIP_EVENT_EPSILON;
|
|
466
|
+
if (wrapped) {
|
|
467
|
+
this.emitKeyframesForRange(monitor, previousTime, 0, -1, false);
|
|
468
|
+
monitor.iteration += 1;
|
|
469
|
+
this.emitClipEvent(monitor, {
|
|
470
|
+
type: "loop",
|
|
471
|
+
clipName: monitor.clipName,
|
|
472
|
+
iteration: monitor.iteration,
|
|
473
|
+
currentTime: monitor.duration,
|
|
474
|
+
duration: monitor.duration
|
|
475
|
+
});
|
|
476
|
+
this.emitKeyframesForRange(monitor, monitor.duration, currentTime, -1, true);
|
|
477
|
+
} else if (delta < -CLIP_EVENT_EPSILON) {
|
|
478
|
+
this.emitKeyframesForRange(monitor, previousTime, currentTime, -1, false);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
this.syncClipMonitorTime(monitor, currentTime);
|
|
482
|
+
}
|
|
325
483
|
normalizePlaybackOptions(options, defaults) {
|
|
326
484
|
const clipOptions = options;
|
|
327
485
|
const rawRate = options?.playbackRate ?? options?.speed ?? 1;
|
|
@@ -533,7 +691,28 @@ var BakedAnimationController = class {
|
|
|
533
691
|
}
|
|
534
692
|
update(dtSeconds) {
|
|
535
693
|
if (this.animationMixer) {
|
|
694
|
+
const snapshots = Array.from(this.clipMonitors.values()).map((monitor) => ({
|
|
695
|
+
actionId: monitor.actionId,
|
|
696
|
+
previousTime: monitor.action.time
|
|
697
|
+
}));
|
|
536
698
|
this.animationMixer.update(dtSeconds);
|
|
699
|
+
for (const { actionId, previousTime } of snapshots) {
|
|
700
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
701
|
+
if (!monitor) continue;
|
|
702
|
+
this.advanceClipMonitor(monitor, previousTime);
|
|
703
|
+
if (monitor.finishedPending) {
|
|
704
|
+
const finalTime = Math.max(0, Math.min(monitor.duration, monitor.action.time));
|
|
705
|
+
this.syncClipMonitorTime(monitor, finalTime);
|
|
706
|
+
this.emitClipEvent(monitor, {
|
|
707
|
+
type: "completed",
|
|
708
|
+
clipName: monitor.clipName,
|
|
709
|
+
currentTime: finalTime,
|
|
710
|
+
duration: monitor.duration,
|
|
711
|
+
iteration: monitor.iteration
|
|
712
|
+
});
|
|
713
|
+
this.cleanupClipMonitor(actionId);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
537
716
|
}
|
|
538
717
|
}
|
|
539
718
|
dispose() {
|
|
@@ -553,6 +732,7 @@ var BakedAnimationController = class {
|
|
|
553
732
|
this.clipHandles.clear();
|
|
554
733
|
this.clipSources.clear();
|
|
555
734
|
this.playbackState.clear();
|
|
735
|
+
this.clipMonitors.clear();
|
|
556
736
|
}
|
|
557
737
|
loadAnimationClips(clips) {
|
|
558
738
|
const model = this.host.getModel();
|
|
@@ -691,6 +871,7 @@ var BakedAnimationController = class {
|
|
|
691
871
|
}
|
|
692
872
|
const action = this.animationActions.get(clipName);
|
|
693
873
|
if (action) {
|
|
874
|
+
const actionId = this.getActionId(action);
|
|
694
875
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
695
876
|
action.stop();
|
|
696
877
|
if (!isBaked && this.animationMixer) {
|
|
@@ -713,9 +894,11 @@ var BakedAnimationController = class {
|
|
|
713
894
|
}
|
|
714
895
|
}
|
|
715
896
|
this.animationFinishedCallbacks.delete(clipName);
|
|
897
|
+
if (actionId) this.cleanupClipMonitor(actionId);
|
|
716
898
|
}
|
|
717
899
|
const clipAction = this.clipActions.get(clipName);
|
|
718
900
|
if (clipAction && clipAction !== action) {
|
|
901
|
+
const actionId = this.getActionId(clipAction);
|
|
719
902
|
try {
|
|
720
903
|
clipAction.stop();
|
|
721
904
|
if (this.animationMixer) {
|
|
@@ -728,6 +911,7 @@ var BakedAnimationController = class {
|
|
|
728
911
|
} catch {
|
|
729
912
|
}
|
|
730
913
|
this.clipActions.delete(clipName);
|
|
914
|
+
if (actionId) this.cleanupClipMonitor(actionId);
|
|
731
915
|
}
|
|
732
916
|
if (this.clipActions.get(clipName) === action) {
|
|
733
917
|
this.clipActions.delete(clipName);
|
|
@@ -1289,6 +1473,7 @@ var BakedAnimationController = class {
|
|
|
1289
1473
|
return null;
|
|
1290
1474
|
}
|
|
1291
1475
|
const clip = new THREE.AnimationClip(clipName, maxTime, tracks);
|
|
1476
|
+
this.setClipEventMetadata(clip, { keyframeTimes });
|
|
1292
1477
|
console.log(`[Loom3] snippetToClip: Created clip "${clipName}" with ${tracks.length} tracks, duration ${maxTime.toFixed(2)}s`);
|
|
1293
1478
|
return clip;
|
|
1294
1479
|
}
|
|
@@ -1320,28 +1505,38 @@ var BakedAnimationController = class {
|
|
|
1320
1505
|
this.animationClips.push(clip);
|
|
1321
1506
|
}
|
|
1322
1507
|
this.applyPlaybackState(action, playbackState);
|
|
1508
|
+
if (actionId) {
|
|
1509
|
+
this.cleanupClipMonitor(actionId);
|
|
1510
|
+
}
|
|
1323
1511
|
let resolveFinished;
|
|
1324
1512
|
const finishedPromise = new Promise((resolve) => {
|
|
1325
1513
|
resolveFinished = resolve;
|
|
1326
1514
|
});
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1515
|
+
const keyframeTimes = this.getClipEventMetadata(clip).keyframeTimes;
|
|
1516
|
+
const initialDirection = playbackState.reverse ? -1 : 1;
|
|
1517
|
+
const monitor = {
|
|
1518
|
+
action,
|
|
1519
|
+
actionId,
|
|
1520
|
+
clip,
|
|
1521
|
+
clipName: clip.name,
|
|
1522
|
+
duration: clip.duration,
|
|
1523
|
+
keyframeTimes,
|
|
1524
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
1525
|
+
initialDirection,
|
|
1526
|
+
direction: initialDirection,
|
|
1527
|
+
iteration: 0,
|
|
1528
|
+
lastTime: Math.max(0, Math.min(clip.duration, action.time)),
|
|
1529
|
+
lastKeyframeIndex: this.getKeyframeIndex(keyframeTimes, action.time),
|
|
1530
|
+
loopMode: playbackState.loopMode,
|
|
1531
|
+
finishedPending: false,
|
|
1532
|
+
cleanedUp: false,
|
|
1533
|
+
resolveFinished
|
|
1336
1534
|
};
|
|
1337
|
-
this.
|
|
1338
|
-
resolveFinished();
|
|
1339
|
-
cleanup();
|
|
1340
|
-
});
|
|
1341
|
-
finishedPromise.catch(() => cleanup());
|
|
1535
|
+
this.clipMonitors.set(actionId, monitor);
|
|
1342
1536
|
action.reset();
|
|
1343
1537
|
action.time = startTime;
|
|
1344
1538
|
action.play();
|
|
1539
|
+
this.resetClipMonitor(monitor, action.time);
|
|
1345
1540
|
this.clipActions.set(clip.name, action);
|
|
1346
1541
|
this.animationActions.set(clip.name, action);
|
|
1347
1542
|
this.setPlaybackState(clip.name, playbackState);
|
|
@@ -1359,6 +1554,7 @@ var BakedAnimationController = class {
|
|
|
1359
1554
|
})
|
|
1360
1555
|
);
|
|
1361
1556
|
action.play();
|
|
1557
|
+
this.resetClipMonitor(monitor, action.time);
|
|
1362
1558
|
},
|
|
1363
1559
|
stop: () => {
|
|
1364
1560
|
action.stop();
|
|
@@ -1376,8 +1572,7 @@ var BakedAnimationController = class {
|
|
|
1376
1572
|
this.animationActions.delete(clip.name);
|
|
1377
1573
|
this.animationFinishedCallbacks.delete(clip.name);
|
|
1378
1574
|
this.playbackState.delete(clip.name);
|
|
1379
|
-
|
|
1380
|
-
cleanup();
|
|
1575
|
+
this.cleanupClipMonitor(actionId);
|
|
1381
1576
|
},
|
|
1382
1577
|
pause: () => {
|
|
1383
1578
|
action.paused = true;
|
|
@@ -1395,6 +1590,8 @@ var BakedAnimationController = class {
|
|
|
1395
1590
|
const next = this.playbackState.get(clip.name) ?? playbackState;
|
|
1396
1591
|
next.playbackRate = Number.isFinite(r) ? Math.max(0, Math.abs(r)) : 1;
|
|
1397
1592
|
this.applyPlaybackState(action, next);
|
|
1593
|
+
monitor.direction = next.reverse ? -1 : 1;
|
|
1594
|
+
monitor.initialDirection = monitor.direction;
|
|
1398
1595
|
this.setPlaybackState(clip.name, next);
|
|
1399
1596
|
},
|
|
1400
1597
|
setLoop: (mode, repeatCount) => {
|
|
@@ -1403,6 +1600,7 @@ var BakedAnimationController = class {
|
|
|
1403
1600
|
next.loop = mode !== "once";
|
|
1404
1601
|
next.repeatCount = repeatCount;
|
|
1405
1602
|
this.applyPlaybackState(action, next);
|
|
1603
|
+
monitor.loopMode = mode;
|
|
1406
1604
|
this.setPlaybackState(clip.name, next);
|
|
1407
1605
|
},
|
|
1408
1606
|
setTime: (t) => {
|
|
@@ -1412,9 +1610,16 @@ var BakedAnimationController = class {
|
|
|
1412
1610
|
this.animationMixer?.update(0);
|
|
1413
1611
|
} catch {
|
|
1414
1612
|
}
|
|
1613
|
+
this.syncClipMonitorTime(monitor, clamped, true);
|
|
1415
1614
|
},
|
|
1416
1615
|
getTime: () => action.time,
|
|
1417
1616
|
getDuration: () => clip.duration,
|
|
1617
|
+
subscribe: (listener) => {
|
|
1618
|
+
monitor.listeners.add(listener);
|
|
1619
|
+
return () => {
|
|
1620
|
+
monitor.listeners.delete(listener);
|
|
1621
|
+
};
|
|
1622
|
+
},
|
|
1418
1623
|
finished: finishedPromise
|
|
1419
1624
|
};
|
|
1420
1625
|
this.clipHandles.set(clip.name, handle);
|
|
@@ -1438,6 +1643,7 @@ var BakedAnimationController = class {
|
|
|
1438
1643
|
if (!this.animationMixer || !this.host.getModel()) return;
|
|
1439
1644
|
for (const [clipName, action] of Array.from(this.clipActions.entries())) {
|
|
1440
1645
|
if (clipName === name || clipName.startsWith(`${name}_`)) {
|
|
1646
|
+
const actionId = this.getActionId(action);
|
|
1441
1647
|
try {
|
|
1442
1648
|
action.stop();
|
|
1443
1649
|
const clip = action.getClip();
|
|
@@ -1452,6 +1658,7 @@ var BakedAnimationController = class {
|
|
|
1452
1658
|
this.clipHandles.delete(clipName);
|
|
1453
1659
|
this.animationFinishedCallbacks.delete(clipName);
|
|
1454
1660
|
this.playbackState.delete(clipName);
|
|
1661
|
+
if (actionId) this.cleanupClipMonitor(actionId);
|
|
1455
1662
|
}
|
|
1456
1663
|
}
|
|
1457
1664
|
}
|
|
@@ -1475,6 +1682,8 @@ var BakedAnimationController = class {
|
|
|
1475
1682
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
1476
1683
|
const apply = (action) => {
|
|
1477
1684
|
if (!action) return;
|
|
1685
|
+
const actionId = this.getActionId(action);
|
|
1686
|
+
const monitor = actionId ? this.clipMonitors.get(actionId) : void 0;
|
|
1478
1687
|
const clipName = action.getClip().name;
|
|
1479
1688
|
const next = this.playbackState.get(clipName) ?? this.normalizePlaybackOptions(void 0, { loop: false, source: this.clipSources.get(clipName) ?? "clip" });
|
|
1480
1689
|
try {
|
|
@@ -1493,6 +1702,10 @@ var BakedAnimationController = class {
|
|
|
1493
1702
|
}
|
|
1494
1703
|
const signedRate = next.reverse ? -next.playbackRate : next.playbackRate;
|
|
1495
1704
|
action.setEffectiveTimeScale(signedRate);
|
|
1705
|
+
if (monitor) {
|
|
1706
|
+
monitor.direction = next.reverse ? -1 : 1;
|
|
1707
|
+
monitor.initialDirection = monitor.direction;
|
|
1708
|
+
}
|
|
1496
1709
|
updated = true;
|
|
1497
1710
|
}
|
|
1498
1711
|
if (typeof params.loop === "boolean" || params.loopMode || params.repeatCount !== void 0) {
|
|
@@ -1500,6 +1713,7 @@ var BakedAnimationController = class {
|
|
|
1500
1713
|
next.loop = next.loopMode !== "once";
|
|
1501
1714
|
next.repeatCount = params.repeatCount;
|
|
1502
1715
|
this.applyPlaybackState(action, next);
|
|
1716
|
+
if (monitor) monitor.loopMode = next.loopMode;
|
|
1503
1717
|
updated = true;
|
|
1504
1718
|
}
|
|
1505
1719
|
this.setPlaybackState(clipName, next);
|
|
@@ -1579,6 +1793,14 @@ var BakedAnimationController = class {
|
|
|
1579
1793
|
if (this.animationMixer && !this.mixerFinishedListenerAttached) {
|
|
1580
1794
|
this.animationMixer.addEventListener("finished", (event) => {
|
|
1581
1795
|
const action = event.action;
|
|
1796
|
+
const actionId = this.getActionId(action);
|
|
1797
|
+
if (actionId) {
|
|
1798
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
1799
|
+
if (monitor) {
|
|
1800
|
+
monitor.finishedPending = true;
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1582
1804
|
const clip = action.getClip();
|
|
1583
1805
|
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
1584
1806
|
if (bakedRuntime) {
|
|
@@ -4285,9 +4507,11 @@ function getPreset(presetType) {
|
|
|
4285
4507
|
return CC4_PRESET;
|
|
4286
4508
|
}
|
|
4287
4509
|
}
|
|
4510
|
+
var resolvePreset = getPreset;
|
|
4288
4511
|
function getPresetWithProfile(presetType, profile) {
|
|
4289
4512
|
return extendPresetWithProfile(getPreset(presetType), profile);
|
|
4290
4513
|
}
|
|
4514
|
+
var resolvePresetWithOverrides = getPresetWithProfile;
|
|
4291
4515
|
|
|
4292
4516
|
// src/engines/three/Loom3.ts
|
|
4293
4517
|
var deg2rad = (d) => d * Math.PI / 180;
|
|
@@ -7443,6 +7667,8 @@ exports.mergeCharacterRegionsByName = mergeRegionsByName;
|
|
|
7443
7667
|
exports.resolveBoneName = resolveBoneName;
|
|
7444
7668
|
exports.resolveBoneNames = resolveBoneNames;
|
|
7445
7669
|
exports.resolveFaceCenter = resolveFaceCenter;
|
|
7670
|
+
exports.resolvePreset = resolvePreset;
|
|
7671
|
+
exports.resolvePresetWithOverrides = resolvePresetWithOverrides;
|
|
7446
7672
|
exports.suggestBestPreset = suggestBestPreset;
|
|
7447
7673
|
exports.validateMappingConfig = validateMappingConfig;
|
|
7448
7674
|
exports.validateMappings = validateMappings;
|