@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.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var THREE2 = require('three');
|
|
4
4
|
|
|
5
5
|
function _interopNamespace(e) {
|
|
6
6
|
if (e && e.__esModule) return e;
|
|
@@ -20,7 +20,7 @@ function _interopNamespace(e) {
|
|
|
20
20
|
return Object.freeze(n);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
var
|
|
23
|
+
var THREE2__namespace = /*#__PURE__*/_interopNamespace(THREE2);
|
|
24
24
|
|
|
25
25
|
var __defProp = Object.defineProperty;
|
|
26
26
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -90,12 +90,12 @@ function getRuntimeClipName(sourceClipName, channel) {
|
|
|
90
90
|
function parseTrackTarget(trackName, model) {
|
|
91
91
|
let parsed;
|
|
92
92
|
try {
|
|
93
|
-
parsed =
|
|
93
|
+
parsed = THREE2.PropertyBinding.parseTrackName(trackName);
|
|
94
94
|
} catch {
|
|
95
95
|
return null;
|
|
96
96
|
}
|
|
97
97
|
const targetKey = parsed.objectName === "bones" && parsed.objectIndex ? String(parsed.objectIndex) : parsed.nodeName;
|
|
98
|
-
const target = targetKey ? model.getObjectByProperty("uuid", targetKey) ??
|
|
98
|
+
const target = targetKey ? model.getObjectByProperty("uuid", targetKey) ?? THREE2.PropertyBinding.findNode(model, targetKey) : null;
|
|
99
99
|
return {
|
|
100
100
|
propertyName: parsed.propertyName,
|
|
101
101
|
target,
|
|
@@ -183,7 +183,7 @@ function partitionBakedClip(clip, model, bones) {
|
|
|
183
183
|
}
|
|
184
184
|
runtimeClips.push({
|
|
185
185
|
channel,
|
|
186
|
-
clip: new
|
|
186
|
+
clip: new THREE2.AnimationClip(getRuntimeClipName(clip.name, channel), clip.duration, tracks)
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
189
|
return {
|
|
@@ -288,9 +288,11 @@ var AnimationThree = class {
|
|
|
288
288
|
}
|
|
289
289
|
};
|
|
290
290
|
var makeActionId = () => `act_${Math.random().toString(36).slice(2, 8)}_${Date.now().toString(36)}`;
|
|
291
|
-
var X_AXIS = new
|
|
292
|
-
var Y_AXIS = new
|
|
293
|
-
var Z_AXIS = new
|
|
291
|
+
var X_AXIS = new THREE2.Vector3(1, 0, 0);
|
|
292
|
+
var Y_AXIS = new THREE2.Vector3(0, 1, 0);
|
|
293
|
+
var Z_AXIS = new THREE2.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;
|
|
@@ -348,14 +506,14 @@ var BakedAnimationController = class {
|
|
|
348
506
|
const signedRate = state.reverse ? -state.playbackRate : state.playbackRate;
|
|
349
507
|
action.setEffectiveTimeScale(signedRate);
|
|
350
508
|
action.setEffectiveWeight(state.weight);
|
|
351
|
-
action.blendMode = state.blendMode === "additive" ?
|
|
509
|
+
action.blendMode = state.blendMode === "additive" ? THREE2.AdditiveAnimationBlendMode : THREE2.NormalAnimationBlendMode;
|
|
352
510
|
const reps = state.repeatCount ?? Infinity;
|
|
353
511
|
if (state.loopMode === "pingpong") {
|
|
354
|
-
action.setLoop(
|
|
512
|
+
action.setLoop(THREE2.LoopPingPong, reps);
|
|
355
513
|
} else if (state.loopMode === "once") {
|
|
356
|
-
action.setLoop(
|
|
514
|
+
action.setLoop(THREE2.LoopOnce, 1);
|
|
357
515
|
} else {
|
|
358
|
-
action.setLoop(
|
|
516
|
+
action.setLoop(THREE2.LoopRepeat, reps);
|
|
359
517
|
}
|
|
360
518
|
action.clampWhenFinished = state.loopMode === "once";
|
|
361
519
|
}
|
|
@@ -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);
|
|
@@ -974,7 +1158,7 @@ var BakedAnimationController = class {
|
|
|
974
1158
|
if (!state2 && !action2) {
|
|
975
1159
|
return null;
|
|
976
1160
|
}
|
|
977
|
-
const loopMode2 = state2?.loopMode ?? (action2?.loop ===
|
|
1161
|
+
const loopMode2 = state2?.loopMode ?? (action2?.loop === THREE2.LoopPingPong ? "pingpong" : action2?.loop === THREE2.LoopOnce ? "once" : "repeat");
|
|
978
1162
|
const playbackRate2 = state2?.playbackRate ?? Math.abs(action2?.getEffectiveTimeScale?.() ?? 1);
|
|
979
1163
|
const reverse2 = state2?.reverse ?? (action2?.getEffectiveTimeScale?.() ?? 1) < 0;
|
|
980
1164
|
const pausedValues = this.bakedActionGroups.get(clipName) ? Array.from(this.bakedActionGroups.get(clipName).channelActions.values()).map((entry) => entry.paused) : [];
|
|
@@ -1005,7 +1189,7 @@ var BakedAnimationController = class {
|
|
|
1005
1189
|
if (!action) return null;
|
|
1006
1190
|
const clip = action.getClip();
|
|
1007
1191
|
const state = this.playbackState.get(clipName);
|
|
1008
|
-
const loopMode = state?.loopMode ?? (action.loop ===
|
|
1192
|
+
const loopMode = state?.loopMode ?? (action.loop === THREE2.LoopPingPong ? "pingpong" : action.loop === THREE2.LoopOnce ? "once" : "repeat");
|
|
1009
1193
|
const playbackRate = state?.playbackRate ?? Math.abs(action.getEffectiveTimeScale());
|
|
1010
1194
|
const reverse = state?.reverse ?? action.getEffectiveTimeScale() < 0;
|
|
1011
1195
|
return {
|
|
@@ -1192,12 +1376,12 @@ var BakedAnimationController = class {
|
|
|
1192
1376
|
const jawBinding = config.auToBones[26]?.[0];
|
|
1193
1377
|
const maxDegrees = jawBinding?.maxDegrees ?? 30;
|
|
1194
1378
|
const radians = maxDegrees * Math.PI / 180 * jawAmount;
|
|
1195
|
-
const jawQ = new
|
|
1196
|
-
jawQ.multiply(new
|
|
1379
|
+
const jawQ = new THREE2.Quaternion().copy(jawEntry.baseQuat);
|
|
1380
|
+
jawQ.multiply(new THREE2.Quaternion().setFromAxisAngle(Z_AXIS, radians));
|
|
1197
1381
|
jawValues.push(jawQ.x, jawQ.y, jawQ.z, jawQ.w);
|
|
1198
1382
|
}
|
|
1199
1383
|
const trackName = `${jawEntry.obj.uuid}.quaternion`;
|
|
1200
|
-
tracks.push(new
|
|
1384
|
+
tracks.push(new THREE2.QuaternionKeyframeTrack(trackName, keyframeTimes, jawValues));
|
|
1201
1385
|
}
|
|
1202
1386
|
}
|
|
1203
1387
|
if (keyframeTimes.length > 0) {
|
|
@@ -1241,7 +1425,7 @@ var BakedAnimationController = class {
|
|
|
1241
1425
|
}
|
|
1242
1426
|
const values = [];
|
|
1243
1427
|
for (const t of keyframeTimes) {
|
|
1244
|
-
const compositeQ = new
|
|
1428
|
+
const compositeQ = new THREE2.Quaternion().copy(entry.baseQuat);
|
|
1245
1429
|
const applyAxis = (axisConfig) => {
|
|
1246
1430
|
if (!axisConfig) return;
|
|
1247
1431
|
let axisValue = getAxisValue(nodeKey, axisConfig, t);
|
|
@@ -1250,7 +1434,7 @@ var BakedAnimationController = class {
|
|
|
1250
1434
|
if (!binding?.maxDegrees || !binding.channel) return;
|
|
1251
1435
|
const radians = binding.maxDegrees * Math.PI / 180 * Math.abs(axisValue) * binding.scale;
|
|
1252
1436
|
const axis = binding.channel === "rx" ? X_AXIS : binding.channel === "ry" ? Y_AXIS : Z_AXIS;
|
|
1253
|
-
const deltaQ = new
|
|
1437
|
+
const deltaQ = new THREE2.Quaternion().setFromAxisAngle(axis, radians);
|
|
1254
1438
|
compositeQ.multiply(deltaQ);
|
|
1255
1439
|
};
|
|
1256
1440
|
applyAxis(composite.yaw);
|
|
@@ -1259,7 +1443,7 @@ var BakedAnimationController = class {
|
|
|
1259
1443
|
values.push(compositeQ.x, compositeQ.y, compositeQ.z, compositeQ.w);
|
|
1260
1444
|
}
|
|
1261
1445
|
const trackName = `${entry.obj.uuid}.quaternion`;
|
|
1262
|
-
tracks.push(new
|
|
1446
|
+
tracks.push(new THREE2.QuaternionKeyframeTrack(trackName, keyframeTimes, values));
|
|
1263
1447
|
}
|
|
1264
1448
|
for (const curveId of Object.keys(curves)) {
|
|
1265
1449
|
if (!isNumericAU(curveId)) continue;
|
|
@@ -1280,7 +1464,7 @@ var BakedAnimationController = class {
|
|
|
1280
1464
|
values.push(basePos + delta);
|
|
1281
1465
|
}
|
|
1282
1466
|
const trackName = `${entry.obj.uuid}.position[${axisIndex}]`;
|
|
1283
|
-
tracks.push(new
|
|
1467
|
+
tracks.push(new THREE2.NumberKeyframeTrack(trackName, keyframeTimes, values));
|
|
1284
1468
|
}
|
|
1285
1469
|
}
|
|
1286
1470
|
}
|
|
@@ -1288,7 +1472,8 @@ var BakedAnimationController = class {
|
|
|
1288
1472
|
console.warn(`[Loom3] snippetToClip: No tracks created for "${clipName}"`);
|
|
1289
1473
|
return null;
|
|
1290
1474
|
}
|
|
1291
|
-
const clip = new
|
|
1475
|
+
const clip = new THREE2.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);
|
|
@@ -1540,7 +1754,7 @@ var BakedAnimationController = class {
|
|
|
1540
1754
|
values.push(Math.max(0, Math.min(2, kf.intensity * intensityScale)));
|
|
1541
1755
|
}
|
|
1542
1756
|
const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
|
|
1543
|
-
const track = new
|
|
1757
|
+
const track = new THREE2.NumberKeyframeTrack(trackName, times, values);
|
|
1544
1758
|
tracks.push(track);
|
|
1545
1759
|
};
|
|
1546
1760
|
for (const mesh of targetMeshes) {
|
|
@@ -1563,7 +1777,7 @@ var BakedAnimationController = class {
|
|
|
1563
1777
|
values.push(Math.max(0, Math.min(2, kf.intensity * intensityScale)));
|
|
1564
1778
|
}
|
|
1565
1779
|
const trackName = `${mesh.uuid}.morphTargetInfluences[${morphIndex}]`;
|
|
1566
|
-
const track = new
|
|
1780
|
+
const track = new THREE2.NumberKeyframeTrack(trackName, times, values);
|
|
1567
1781
|
tracks.push(track);
|
|
1568
1782
|
};
|
|
1569
1783
|
for (const mesh of targetMeshes) {
|
|
@@ -1574,11 +1788,19 @@ var BakedAnimationController = class {
|
|
|
1574
1788
|
const model = this.host.getModel();
|
|
1575
1789
|
if (!model) return null;
|
|
1576
1790
|
if (!this.animationMixer) {
|
|
1577
|
-
this.animationMixer = new
|
|
1791
|
+
this.animationMixer = new THREE2.AnimationMixer(model);
|
|
1578
1792
|
}
|
|
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) {
|
|
@@ -2703,7 +2925,7 @@ var HairPhysicsController = class {
|
|
|
2703
2925
|
this.clearRegisteredHairObjects();
|
|
2704
2926
|
const result = [];
|
|
2705
2927
|
for (const obj of objects) {
|
|
2706
|
-
if (obj instanceof
|
|
2928
|
+
if (obj instanceof THREE2.Mesh) {
|
|
2707
2929
|
const mesh = obj;
|
|
2708
2930
|
this.registeredHairObjects.set(mesh.name, mesh);
|
|
2709
2931
|
const meshInfo = CC4_MESHES[mesh.name];
|
|
@@ -4285,15 +4507,17 @@ 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;
|
|
4294
|
-
var X_AXIS2 = new
|
|
4295
|
-
var Y_AXIS2 = new
|
|
4296
|
-
var Z_AXIS2 = new
|
|
4518
|
+
var X_AXIS2 = new THREE2.Vector3(1, 0, 0);
|
|
4519
|
+
var Y_AXIS2 = new THREE2.Vector3(0, 1, 0);
|
|
4520
|
+
var Z_AXIS2 = new THREE2.Vector3(0, 0, 1);
|
|
4297
4521
|
function buildAUToCompositeMap(composites) {
|
|
4298
4522
|
const map = /* @__PURE__ */ new Map();
|
|
4299
4523
|
composites.forEach((comp) => {
|
|
@@ -4357,7 +4581,7 @@ var _Loom3 = class _Loom3 {
|
|
|
4357
4581
|
__publicField(this, "bakedAnimations");
|
|
4358
4582
|
__publicField(this, "hairPhysics");
|
|
4359
4583
|
// Internal animation loop
|
|
4360
|
-
__publicField(this, "clock", new
|
|
4584
|
+
__publicField(this, "clock", new THREE2.Clock(false));
|
|
4361
4585
|
// Don't auto-start
|
|
4362
4586
|
__publicField(this, "animationFrameId", null);
|
|
4363
4587
|
__publicField(this, "isRunning", false);
|
|
@@ -4504,11 +4728,11 @@ var _Loom3 = class _Loom3 {
|
|
|
4504
4728
|
}
|
|
4505
4729
|
const head = this.bones["HEAD"]?.obj;
|
|
4506
4730
|
if (head && availableMorphMeshes.length > 0) {
|
|
4507
|
-
const headPos = new
|
|
4731
|
+
const headPos = new THREE2.Vector3();
|
|
4508
4732
|
head.getWorldPosition?.(headPos);
|
|
4509
4733
|
const headCandidates = availableMorphMeshes.map((mesh) => {
|
|
4510
|
-
const box = new
|
|
4511
|
-
const center = new
|
|
4734
|
+
const box = new THREE2.Box3().setFromObject(mesh);
|
|
4735
|
+
const center = new THREE2.Vector3();
|
|
4512
4736
|
box.getCenter(center);
|
|
4513
4737
|
const distance = box.containsPoint(headPos) ? 0 : center.distanceTo(headPos);
|
|
4514
4738
|
const morphCount = mesh.morphTargetDictionary ? Object.keys(mesh.morphTargetDictionary).length : 0;
|
|
@@ -5543,13 +5767,13 @@ var _Loom3 = class _Loom3 {
|
|
|
5543
5767
|
return;
|
|
5544
5768
|
}
|
|
5545
5769
|
const getAxis = (channel) => channel === "rx" ? X_AXIS2 : channel === "ry" ? Y_AXIS2 : Z_AXIS2;
|
|
5546
|
-
const compositeQ = new
|
|
5770
|
+
const compositeQ = new THREE2.Quaternion().copy(baseQuat);
|
|
5547
5771
|
if (config.yaw && rotState.yaw !== 0) {
|
|
5548
5772
|
const binding = this.getCompositeAxisBindingForNode(nodeKey, config.yaw, rotState.yaw);
|
|
5549
5773
|
if (binding?.maxDegrees && binding.channel) {
|
|
5550
5774
|
const radians = deg2rad(binding.maxDegrees) * Math.abs(rotState.yaw) * binding.scale;
|
|
5551
5775
|
const axis = getAxis(binding.channel);
|
|
5552
|
-
const deltaQ = new
|
|
5776
|
+
const deltaQ = new THREE2.Quaternion().setFromAxisAngle(axis, radians);
|
|
5553
5777
|
compositeQ.multiply(deltaQ);
|
|
5554
5778
|
}
|
|
5555
5779
|
}
|
|
@@ -5558,7 +5782,7 @@ var _Loom3 = class _Loom3 {
|
|
|
5558
5782
|
if (binding?.maxDegrees && binding.channel) {
|
|
5559
5783
|
const radians = deg2rad(binding.maxDegrees) * Math.abs(rotState.pitch) * binding.scale;
|
|
5560
5784
|
const axis = getAxis(binding.channel);
|
|
5561
|
-
const deltaQ = new
|
|
5785
|
+
const deltaQ = new THREE2.Quaternion().setFromAxisAngle(axis, radians);
|
|
5562
5786
|
compositeQ.multiply(deltaQ);
|
|
5563
5787
|
}
|
|
5564
5788
|
}
|
|
@@ -5567,7 +5791,7 @@ var _Loom3 = class _Loom3 {
|
|
|
5567
5791
|
if (binding?.maxDegrees && binding.channel) {
|
|
5568
5792
|
const radians = deg2rad(binding.maxDegrees) * Math.abs(rotState.roll) * binding.scale;
|
|
5569
5793
|
const axis = getAxis(binding.channel);
|
|
5570
|
-
const deltaQ = new
|
|
5794
|
+
const deltaQ = new THREE2.Quaternion().setFromAxisAngle(axis, radians);
|
|
5571
5795
|
compositeQ.multiply(deltaQ);
|
|
5572
5796
|
}
|
|
5573
5797
|
}
|
|
@@ -6006,10 +6230,11 @@ function extractProfileOverrides(config) {
|
|
|
6006
6230
|
if (key === "annotationRegions") {
|
|
6007
6231
|
const topLevelAnnotationRegions = Array.isArray(topLevelConfig.annotationRegions) ? topLevelConfig.annotationRegions : void 0;
|
|
6008
6232
|
const legacyAnnotationRegions = Array.isArray(legacyNestedOverrides.annotationRegions) ? legacyNestedOverrides.annotationRegions : void 0;
|
|
6009
|
-
const
|
|
6233
|
+
const legacyRegionFallback = Array.isArray(config.regions) && config.regions.length > 0 ? config.regions : void 0;
|
|
6234
|
+
const legacyProfileRegions = mergeRegionsByName(legacyRegionFallback, legacyAnnotationRegions);
|
|
6010
6235
|
const regions = mergeRegionsByName(
|
|
6011
|
-
|
|
6012
|
-
|
|
6236
|
+
legacyProfileRegions,
|
|
6237
|
+
topLevelAnnotationRegions
|
|
6013
6238
|
);
|
|
6014
6239
|
if (regions) {
|
|
6015
6240
|
overrides.annotationRegions = regions.map((region) => cloneRegion(region));
|
|
@@ -6025,6 +6250,13 @@ function extractProfileOverrides(config) {
|
|
|
6025
6250
|
}
|
|
6026
6251
|
return overrides;
|
|
6027
6252
|
}
|
|
6253
|
+
function hasCanonicalAnnotationRegionOverrides(config) {
|
|
6254
|
+
const topLevelConfig = config;
|
|
6255
|
+
if (Array.isArray(topLevelConfig.annotationRegions)) {
|
|
6256
|
+
return true;
|
|
6257
|
+
}
|
|
6258
|
+
return isPlainObject2(config.profile) && Array.isArray(config.profile.annotationRegions);
|
|
6259
|
+
}
|
|
6028
6260
|
function applyCharacterProfileToPreset(config) {
|
|
6029
6261
|
const presetType = config.auPresetType;
|
|
6030
6262
|
if (!presetType) {
|
|
@@ -6062,7 +6294,7 @@ function extendCharacterConfigWithPreset(config) {
|
|
|
6062
6294
|
return config;
|
|
6063
6295
|
}
|
|
6064
6296
|
const presetRegions = extendedPresetProfile.annotationRegions;
|
|
6065
|
-
const mergedRegions = mergeRegionsByName(presetRegions, config.regions);
|
|
6297
|
+
const mergedRegions = hasCanonicalAnnotationRegionOverrides(config) ? mergeRegionsByName(config.regions, presetRegions) : mergeRegionsByName(presetRegions, config.regions);
|
|
6066
6298
|
const normalizedRegions = normalizeRegionTree(
|
|
6067
6299
|
mergedRegions,
|
|
6068
6300
|
profileOverrides.disabledRegions
|
|
@@ -6077,6 +6309,43 @@ function extendCharacterConfigWithPreset(config) {
|
|
|
6077
6309
|
regions: extendedRegions ?? config.regions
|
|
6078
6310
|
};
|
|
6079
6311
|
}
|
|
6312
|
+
var DEFAULT_EPSILON = 1e-4;
|
|
6313
|
+
var DEFAULT_YAW_WEIGHT = 0.35;
|
|
6314
|
+
var DEFAULT_PITCH_WEIGHT = 0.2;
|
|
6315
|
+
var ZERO_OFFSET = { x: 0, y: 0 };
|
|
6316
|
+
function clampOffset(value) {
|
|
6317
|
+
return THREE2__namespace.MathUtils.clamp(value, -1, 1);
|
|
6318
|
+
}
|
|
6319
|
+
function toModelLocalDirection(model, worldDirection) {
|
|
6320
|
+
const localDirection = worldDirection.clone();
|
|
6321
|
+
model.updateWorldMatrix(true, false);
|
|
6322
|
+
const worldQuaternion = new THREE2__namespace.Quaternion();
|
|
6323
|
+
model.getWorldQuaternion(worldQuaternion);
|
|
6324
|
+
localDirection.applyQuaternion(worldQuaternion.invert());
|
|
6325
|
+
return localDirection.normalize();
|
|
6326
|
+
}
|
|
6327
|
+
function computeCameraRelativeGazeOffset(model, cameraPosition, targetPosition, options = {}) {
|
|
6328
|
+
if (!model) {
|
|
6329
|
+
return ZERO_OFFSET;
|
|
6330
|
+
}
|
|
6331
|
+
const epsilon = options.epsilon ?? DEFAULT_EPSILON;
|
|
6332
|
+
const yawWeight = options.yawWeight ?? DEFAULT_YAW_WEIGHT;
|
|
6333
|
+
const pitchWeight = options.pitchWeight ?? DEFAULT_PITCH_WEIGHT;
|
|
6334
|
+
const worldOffset = new THREE2__namespace.Vector3().subVectors(cameraPosition, targetPosition);
|
|
6335
|
+
if (worldOffset.lengthSq() < epsilon) {
|
|
6336
|
+
return ZERO_OFFSET;
|
|
6337
|
+
}
|
|
6338
|
+
const localDirection = toModelLocalDirection(model, worldOffset.normalize());
|
|
6339
|
+
const yawAngle = Math.atan2(localDirection.x, localDirection.z);
|
|
6340
|
+
const pitchAngle = Math.atan2(
|
|
6341
|
+
localDirection.y,
|
|
6342
|
+
Math.max(Math.hypot(localDirection.x, localDirection.z), epsilon)
|
|
6343
|
+
);
|
|
6344
|
+
return {
|
|
6345
|
+
x: clampOffset(yawAngle / (Math.PI / 2)) * yawWeight,
|
|
6346
|
+
y: clampOffset(pitchAngle / (Math.PI / 3)) * pitchWeight
|
|
6347
|
+
};
|
|
6348
|
+
}
|
|
6080
6349
|
var DEFAULT_HEAD_BONE_NAMES = ["CC_Base_Head", "Head", "head", "Bip01_Head"];
|
|
6081
6350
|
var DEFAULT_REFERENCE_HEIGHT = 1.8;
|
|
6082
6351
|
var DEFAULT_FORWARD_OFFSET = 0.08;
|
|
@@ -6092,8 +6361,8 @@ function findFaceCenter(model, options = {}) {
|
|
|
6092
6361
|
referenceHeight = DEFAULT_REFERENCE_HEIGHT
|
|
6093
6362
|
} = options;
|
|
6094
6363
|
const debugInfo = [];
|
|
6095
|
-
const boundingBox = new
|
|
6096
|
-
const modelSize = new
|
|
6364
|
+
const boundingBox = new THREE2__namespace.Box3().setFromObject(model);
|
|
6365
|
+
const modelSize = new THREE2__namespace.Vector3();
|
|
6097
6366
|
boundingBox.getSize(modelSize);
|
|
6098
6367
|
const scale = modelSize.y / referenceHeight;
|
|
6099
6368
|
debugInfo.push(`Model height: ${modelSize.y.toFixed(2)}m, scale factor: ${scale.toFixed(2)}`);
|
|
@@ -6104,7 +6373,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6104
6373
|
const objName = obj.name.toLowerCase();
|
|
6105
6374
|
for (const boneName of headBoneNames) {
|
|
6106
6375
|
if (objName === boneName.toLowerCase() || objName.includes(boneName.toLowerCase())) {
|
|
6107
|
-
headPos2 = new
|
|
6376
|
+
headPos2 = new THREE2__namespace.Vector3();
|
|
6108
6377
|
obj.getWorldPosition(headPos2);
|
|
6109
6378
|
debugInfo.push(`Found head bone "${obj.name}" at (${headPos2.x.toFixed(3)}, ${headPos2.y.toFixed(3)}, ${headPos2.z.toFixed(3)})`);
|
|
6110
6379
|
return;
|
|
@@ -6123,7 +6392,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6123
6392
|
if (!eyes2.left) {
|
|
6124
6393
|
for (const name of EYE_BONE_NAMES.left) {
|
|
6125
6394
|
if (objName.includes(name)) {
|
|
6126
|
-
eyes2.left = new
|
|
6395
|
+
eyes2.left = new THREE2__namespace.Vector3();
|
|
6127
6396
|
obj.getWorldPosition(eyes2.left);
|
|
6128
6397
|
debugInfo.push(`Found left eye "${obj.name}" at (${eyes2.left.x.toFixed(3)}, ${eyes2.left.y.toFixed(3)}, ${eyes2.left.z.toFixed(3)})`);
|
|
6129
6398
|
break;
|
|
@@ -6133,7 +6402,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6133
6402
|
if (!eyes2.right) {
|
|
6134
6403
|
for (const name of EYE_BONE_NAMES.right) {
|
|
6135
6404
|
if (objName.includes(name)) {
|
|
6136
|
-
eyes2.right = new
|
|
6405
|
+
eyes2.right = new THREE2__namespace.Vector3();
|
|
6137
6406
|
obj.getWorldPosition(eyes2.right);
|
|
6138
6407
|
debugInfo.push(`Found right eye "${obj.name}" at (${eyes2.right.x.toFixed(3)}, ${eyes2.right.y.toFixed(3)}, ${eyes2.right.z.toFixed(3)})`);
|
|
6139
6408
|
break;
|
|
@@ -6145,7 +6414,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6145
6414
|
};
|
|
6146
6415
|
if (faceMeshNames && faceMeshNames.length > 0) {
|
|
6147
6416
|
debugInfo.push(`Looking for face meshes: ${faceMeshNames.join(", ")}`);
|
|
6148
|
-
const meshBox = new
|
|
6417
|
+
const meshBox = new THREE2__namespace.Box3();
|
|
6149
6418
|
let foundMesh = false;
|
|
6150
6419
|
model.traverse((obj) => {
|
|
6151
6420
|
const isMesh = obj.isMesh;
|
|
@@ -6153,7 +6422,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6153
6422
|
for (const meshName of faceMeshNames) {
|
|
6154
6423
|
if (obj.name === meshName || obj.name.includes(meshName)) {
|
|
6155
6424
|
const mesh = obj;
|
|
6156
|
-
const tempBox = new
|
|
6425
|
+
const tempBox = new THREE2__namespace.Box3().setFromObject(mesh);
|
|
6157
6426
|
meshBox.union(tempBox);
|
|
6158
6427
|
foundMesh = true;
|
|
6159
6428
|
debugInfo.push(`Found mesh: "${obj.name}"`);
|
|
@@ -6162,11 +6431,11 @@ function findFaceCenter(model, options = {}) {
|
|
|
6162
6431
|
}
|
|
6163
6432
|
});
|
|
6164
6433
|
if (foundMesh && !meshBox.isEmpty()) {
|
|
6165
|
-
const meshBoundsSize = new
|
|
6434
|
+
const meshBoundsSize = new THREE2__namespace.Vector3();
|
|
6166
6435
|
meshBox.getSize(meshBoundsSize);
|
|
6167
6436
|
const isFullBodyMesh = meshBoundsSize.y > modelSize.y * 0.7;
|
|
6168
6437
|
if (!isFullBodyMesh) {
|
|
6169
|
-
const meshCenter = new
|
|
6438
|
+
const meshCenter = new THREE2__namespace.Vector3();
|
|
6170
6439
|
meshBox.getCenter(meshCenter);
|
|
6171
6440
|
debugInfo.push(`Using face mesh center: (${meshCenter.x.toFixed(3)}, ${meshCenter.y.toFixed(3)}, ${meshCenter.z.toFixed(3)})`);
|
|
6172
6441
|
return {
|
|
@@ -6180,7 +6449,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6180
6449
|
}
|
|
6181
6450
|
const eyes = findEyes();
|
|
6182
6451
|
if (eyes.left && eyes.right) {
|
|
6183
|
-
const eyeCenter = new
|
|
6452
|
+
const eyeCenter = new THREE2__namespace.Vector3().addVectors(eyes.left, eyes.right).multiplyScalar(0.5);
|
|
6184
6453
|
debugInfo.push(`Eye center (face position): (${eyeCenter.x.toFixed(3)}, ${eyeCenter.y.toFixed(3)}, ${eyeCenter.z.toFixed(3)})`);
|
|
6185
6454
|
const headPos2 = findHeadBone();
|
|
6186
6455
|
return {
|
|
@@ -6205,10 +6474,10 @@ function findFaceCenter(model, options = {}) {
|
|
|
6205
6474
|
};
|
|
6206
6475
|
}
|
|
6207
6476
|
debugInfo.push("No head bone found, using model center fallback");
|
|
6208
|
-
const modelCenter = new
|
|
6477
|
+
const modelCenter = new THREE2__namespace.Vector3();
|
|
6209
6478
|
boundingBox.getCenter(modelCenter);
|
|
6210
6479
|
const headHeight = boundingBox.min.y + modelSize.y * 0.9;
|
|
6211
|
-
const fallbackCenter = new
|
|
6480
|
+
const fallbackCenter = new THREE2__namespace.Vector3(modelCenter.x, headHeight, modelCenter.z);
|
|
6212
6481
|
debugInfo.push(`Fallback center: (${fallbackCenter.x.toFixed(3)}, ${fallbackCenter.y.toFixed(3)}, ${fallbackCenter.z.toFixed(3)})`);
|
|
6213
6482
|
return {
|
|
6214
6483
|
center: fallbackCenter,
|
|
@@ -6217,7 +6486,7 @@ function findFaceCenter(model, options = {}) {
|
|
|
6217
6486
|
};
|
|
6218
6487
|
}
|
|
6219
6488
|
function getModelForwardDirection(model) {
|
|
6220
|
-
const forward = new
|
|
6489
|
+
const forward = new THREE2__namespace.Vector3(0, 0, 1);
|
|
6221
6490
|
forward.applyQuaternion(model.quaternion);
|
|
6222
6491
|
return forward.normalize();
|
|
6223
6492
|
}
|
|
@@ -6234,7 +6503,7 @@ function detectFacingDirection(model, eyeBoneNames = {
|
|
|
6234
6503
|
if (!eyes.left) {
|
|
6235
6504
|
for (const name of eyeBoneNames.left) {
|
|
6236
6505
|
if (objName.includes(name)) {
|
|
6237
|
-
eyes.left = new
|
|
6506
|
+
eyes.left = new THREE2__namespace.Vector3();
|
|
6238
6507
|
obj.getWorldPosition(eyes.left);
|
|
6239
6508
|
break;
|
|
6240
6509
|
}
|
|
@@ -6243,7 +6512,7 @@ function detectFacingDirection(model, eyeBoneNames = {
|
|
|
6243
6512
|
if (!eyes.right) {
|
|
6244
6513
|
for (const name of eyeBoneNames.right) {
|
|
6245
6514
|
if (objName.includes(name)) {
|
|
6246
|
-
eyes.right = new
|
|
6515
|
+
eyes.right = new THREE2__namespace.Vector3();
|
|
6247
6516
|
obj.getWorldPosition(eyes.right);
|
|
6248
6517
|
break;
|
|
6249
6518
|
}
|
|
@@ -7159,19 +7428,19 @@ function extractBones(root) {
|
|
|
7159
7428
|
const bones = [];
|
|
7160
7429
|
const boneDepths = /* @__PURE__ */ new Map();
|
|
7161
7430
|
root.traverse((obj) => {
|
|
7162
|
-
if (obj instanceof
|
|
7163
|
-
const worldPos = new
|
|
7431
|
+
if (obj instanceof THREE2__namespace.Bone || obj.type === "Bone") {
|
|
7432
|
+
const worldPos = new THREE2__namespace.Vector3();
|
|
7164
7433
|
obj.getWorldPosition(worldPos);
|
|
7165
7434
|
let depth = 0;
|
|
7166
7435
|
let parent = obj.parent;
|
|
7167
7436
|
while (parent) {
|
|
7168
|
-
if (parent instanceof
|
|
7437
|
+
if (parent instanceof THREE2__namespace.Bone || parent.type === "Bone") {
|
|
7169
7438
|
depth++;
|
|
7170
7439
|
}
|
|
7171
7440
|
parent = parent.parent;
|
|
7172
7441
|
}
|
|
7173
7442
|
boneDepths.set(obj.name, depth);
|
|
7174
|
-
const parentBone = obj.parent instanceof
|
|
7443
|
+
const parentBone = obj.parent instanceof THREE2__namespace.Bone || obj.parent?.type === "Bone" ? obj.parent.name : null;
|
|
7175
7444
|
bones.push({
|
|
7176
7445
|
name: obj.name,
|
|
7177
7446
|
parent: parentBone,
|
|
@@ -7424,6 +7693,7 @@ exports.VISEME_KEYS = VISEME_KEYS;
|
|
|
7424
7693
|
exports.analyzeModel = analyzeModel;
|
|
7425
7694
|
exports.applyCharacterProfileToPreset = applyCharacterProfileToPreset;
|
|
7426
7695
|
exports.collectMorphMeshes = collectMorphMeshes;
|
|
7696
|
+
exports.computeCameraRelativeGazeOffset = computeCameraRelativeGazeOffset;
|
|
7427
7697
|
exports.detectFacingDirection = detectFacingDirection;
|
|
7428
7698
|
exports.extendCharacterConfigWithPreset = extendCharacterConfigWithPreset;
|
|
7429
7699
|
exports.extendPresetWithProfile = extendPresetWithProfile;
|
|
@@ -7443,6 +7713,8 @@ exports.mergeCharacterRegionsByName = mergeRegionsByName;
|
|
|
7443
7713
|
exports.resolveBoneName = resolveBoneName;
|
|
7444
7714
|
exports.resolveBoneNames = resolveBoneNames;
|
|
7445
7715
|
exports.resolveFaceCenter = resolveFaceCenter;
|
|
7716
|
+
exports.resolvePreset = resolvePreset;
|
|
7717
|
+
exports.resolvePresetWithOverrides = resolvePresetWithOverrides;
|
|
7446
7718
|
exports.suggestBestPreset = suggestBestPreset;
|
|
7447
7719
|
exports.validateMappingConfig = validateMappingConfig;
|
|
7448
7720
|
exports.validateMappings = validateMappings;
|