@lovelace_lol/loom3 1.0.42 → 1.0.43
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/dist/index.cjs +359 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +360 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -510,14 +510,19 @@ var Y_AXIS = new THREE2.Vector3(0, 1, 0);
|
|
|
510
510
|
var Z_AXIS = new THREE2.Vector3(0, 0, 1);
|
|
511
511
|
var CLIP_EVENT_METADATA_KEY = "__loom3ClipEvents";
|
|
512
512
|
var CLIP_EVENT_EPSILON = 1e-4;
|
|
513
|
+
var ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX = "__loom3_additive_delta";
|
|
513
514
|
var BakedAnimationController = class {
|
|
514
515
|
constructor(host) {
|
|
515
516
|
__publicField(this, "host");
|
|
517
|
+
// Clip-backed snippets need a later mixer pass so they can override baked additive tracks.
|
|
516
518
|
__publicField(this, "animationMixer", null);
|
|
519
|
+
__publicField(this, "clipAnimationMixer", null);
|
|
517
520
|
__publicField(this, "mixerFinishedListenerAttached", false);
|
|
521
|
+
__publicField(this, "clipMixerFinishedListenerAttached", false);
|
|
518
522
|
__publicField(this, "animationClips", []);
|
|
519
523
|
__publicField(this, "bakedSourceClips", /* @__PURE__ */ new Map());
|
|
520
524
|
__publicField(this, "bakedRuntimeActions", /* @__PURE__ */ new Map());
|
|
525
|
+
__publicField(this, "bakedAdditiveRuntimeClips", /* @__PURE__ */ new Map());
|
|
521
526
|
__publicField(this, "bakedActionGroups", /* @__PURE__ */ new Map());
|
|
522
527
|
__publicField(this, "bakedRuntimeClipToSource", /* @__PURE__ */ new Map());
|
|
523
528
|
__publicField(this, "animationActions", /* @__PURE__ */ new Map());
|
|
@@ -542,6 +547,173 @@ var BakedAnimationController = class {
|
|
|
542
547
|
action.__actionId = actionId;
|
|
543
548
|
return actionId;
|
|
544
549
|
}
|
|
550
|
+
clearActionId(action) {
|
|
551
|
+
if (!action) return;
|
|
552
|
+
const actionId = this.getActionId(action);
|
|
553
|
+
if (actionId) {
|
|
554
|
+
this.actionIdToClip.delete(actionId);
|
|
555
|
+
}
|
|
556
|
+
this.actionIds.delete(action);
|
|
557
|
+
delete action.__actionId;
|
|
558
|
+
}
|
|
559
|
+
uncacheClip(clip, mixer = this.animationMixer) {
|
|
560
|
+
if (!clip || !mixer) return;
|
|
561
|
+
try {
|
|
562
|
+
mixer.uncacheClip(clip);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
uncacheAction(action, mixer = this.animationMixer) {
|
|
567
|
+
if (!action || !mixer) return;
|
|
568
|
+
try {
|
|
569
|
+
const clip = action.getClip();
|
|
570
|
+
if (clip) {
|
|
571
|
+
mixer.uncacheAction(clip);
|
|
572
|
+
mixer.uncacheClip(clip);
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
releaseBakedRuntimeAction(runtimeClipName) {
|
|
578
|
+
const action = this.bakedRuntimeActions.get(runtimeClipName);
|
|
579
|
+
if (!action) return;
|
|
580
|
+
try {
|
|
581
|
+
action.stop();
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
this.uncacheAction(action);
|
|
585
|
+
this.clearActionId(action);
|
|
586
|
+
this.bakedRuntimeActions.delete(runtimeClipName);
|
|
587
|
+
}
|
|
588
|
+
clearBakedAdditiveRuntimeClip(runtimeClipName) {
|
|
589
|
+
const clip = this.bakedAdditiveRuntimeClips.get(runtimeClipName);
|
|
590
|
+
if (!clip) return;
|
|
591
|
+
this.uncacheClip(clip);
|
|
592
|
+
this.bakedAdditiveRuntimeClips.delete(runtimeClipName);
|
|
593
|
+
}
|
|
594
|
+
clearAllBakedAdditiveRuntimeClips() {
|
|
595
|
+
for (const runtimeClipName of Array.from(this.bakedAdditiveRuntimeClips.keys())) {
|
|
596
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClipName);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
resolveTrackTarget(model, parsed) {
|
|
600
|
+
const targetKey = parsed.objectName === "bones" && parsed.objectIndex ? parsed.objectIndex : parsed.nodeName;
|
|
601
|
+
if (!targetKey) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
return model.getObjectByProperty("uuid", targetKey) ?? THREE2.PropertyBinding.findNode(model, targetKey) ?? null;
|
|
605
|
+
}
|
|
606
|
+
getMorphTrackBaseValue(target, propertyIndex) {
|
|
607
|
+
if (!target) {
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
const meshTarget = target;
|
|
611
|
+
const influences = meshTarget.morphTargetInfluences;
|
|
612
|
+
if (!influences) {
|
|
613
|
+
return 0;
|
|
614
|
+
}
|
|
615
|
+
let morphIndex;
|
|
616
|
+
if (typeof propertyIndex === "number" && Number.isInteger(propertyIndex)) {
|
|
617
|
+
morphIndex = propertyIndex;
|
|
618
|
+
} else if (typeof propertyIndex === "string") {
|
|
619
|
+
if (/^\d+$/.test(propertyIndex)) {
|
|
620
|
+
morphIndex = Number(propertyIndex);
|
|
621
|
+
} else {
|
|
622
|
+
morphIndex = meshTarget.morphTargetDictionary?.[propertyIndex];
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (morphIndex === void 0) {
|
|
626
|
+
return 0;
|
|
627
|
+
}
|
|
628
|
+
return influences[morphIndex] ?? 0;
|
|
629
|
+
}
|
|
630
|
+
canCreateFirstFrameReferenceTrack(track) {
|
|
631
|
+
const valueSize = track.getValueSize();
|
|
632
|
+
if (!Number.isFinite(valueSize) || valueSize <= 0 || track.values.length < valueSize) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
return track.ValueTypeName === "number" || track.ValueTypeName === "quaternion" || track.ValueTypeName === "vector";
|
|
636
|
+
}
|
|
637
|
+
createFirstFrameReferenceTrack(track) {
|
|
638
|
+
const valueSize = track.getValueSize();
|
|
639
|
+
if (!this.canCreateFirstFrameReferenceTrack(track)) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const values = Array.from(track.values.slice(0, valueSize));
|
|
643
|
+
if (track.ValueTypeName === "number") {
|
|
644
|
+
return new THREE2.NumberKeyframeTrack(track.name, [0], values);
|
|
645
|
+
}
|
|
646
|
+
if (track.ValueTypeName === "quaternion") {
|
|
647
|
+
return new THREE2.QuaternionKeyframeTrack(track.name, [0], values);
|
|
648
|
+
}
|
|
649
|
+
if (track.ValueTypeName === "vector") {
|
|
650
|
+
return new THREE2.VectorKeyframeTrack(track.name, [0], values);
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
createAdditiveReferenceTrack(track, model) {
|
|
655
|
+
const trackName = typeof track?.name === "string" ? track.name : "";
|
|
656
|
+
if (!trackName) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
let parsed;
|
|
660
|
+
try {
|
|
661
|
+
parsed = THREE2.PropertyBinding.parseTrackName(trackName);
|
|
662
|
+
} catch {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
const target = this.resolveTrackTarget(model, parsed);
|
|
666
|
+
if (parsed.propertyName === "morphTargetInfluences") {
|
|
667
|
+
return new THREE2.NumberKeyframeTrack(
|
|
668
|
+
track.name,
|
|
669
|
+
[0],
|
|
670
|
+
[this.getMorphTrackBaseValue(target, parsed.propertyIndex)]
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return this.createFirstFrameReferenceTrack(track);
|
|
674
|
+
}
|
|
675
|
+
createAdditiveRuntimeClip(runtimeClip) {
|
|
676
|
+
const model = this.host.getModel();
|
|
677
|
+
if (!model) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
const additiveTracks = [];
|
|
681
|
+
const referenceTracks = [];
|
|
682
|
+
for (const track of runtimeClip.tracks) {
|
|
683
|
+
const referenceTrack = this.createAdditiveReferenceTrack(track, model);
|
|
684
|
+
if (!referenceTrack) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
additiveTracks.push(track.clone());
|
|
688
|
+
referenceTracks.push(referenceTrack);
|
|
689
|
+
}
|
|
690
|
+
const additiveClip = new THREE2.AnimationClip(
|
|
691
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}`,
|
|
692
|
+
runtimeClip.duration,
|
|
693
|
+
additiveTracks
|
|
694
|
+
);
|
|
695
|
+
if (additiveTracks.length > 0) {
|
|
696
|
+
const referenceClip = new THREE2.AnimationClip(
|
|
697
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}_reference`,
|
|
698
|
+
0,
|
|
699
|
+
referenceTracks
|
|
700
|
+
);
|
|
701
|
+
THREE2.AnimationUtils.makeClipAdditive(additiveClip, 0, referenceClip);
|
|
702
|
+
}
|
|
703
|
+
return additiveClip;
|
|
704
|
+
}
|
|
705
|
+
getOrCreateBakedAdditiveRuntimeClip(runtimeClip) {
|
|
706
|
+
const cached = this.bakedAdditiveRuntimeClips.get(runtimeClip.name);
|
|
707
|
+
if (cached) {
|
|
708
|
+
return cached;
|
|
709
|
+
}
|
|
710
|
+
const additiveClip = this.createAdditiveRuntimeClip(runtimeClip);
|
|
711
|
+
if (!additiveClip) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
this.bakedAdditiveRuntimeClips.set(runtimeClip.name, additiveClip);
|
|
715
|
+
return additiveClip;
|
|
716
|
+
}
|
|
545
717
|
setClipEventMetadata(clip, metadata) {
|
|
546
718
|
const userData = clip.userData ?? (clip.userData = {});
|
|
547
719
|
userData[CLIP_EVENT_METADATA_KEY] = metadata;
|
|
@@ -827,21 +999,28 @@ var BakedAnimationController = class {
|
|
|
827
999
|
}
|
|
828
1000
|
return 0;
|
|
829
1001
|
}
|
|
830
|
-
getOrCreateBakedRuntimeAction(sourceClipName, channel) {
|
|
1002
|
+
getOrCreateBakedRuntimeAction(sourceClipName, channel, blendMode = "replace") {
|
|
831
1003
|
const bakedClip = this.getBakedSourceClip(sourceClipName);
|
|
832
1004
|
const runtimeClip = bakedClip?.runtimeClips.find((entry) => entry.channel === channel)?.clip;
|
|
833
1005
|
if (!runtimeClip) {
|
|
834
1006
|
return null;
|
|
835
1007
|
}
|
|
1008
|
+
const desiredClip = blendMode === "additive" ? this.getOrCreateBakedAdditiveRuntimeClip(runtimeClip) : runtimeClip;
|
|
1009
|
+
if (!desiredClip) {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
836
1012
|
const existing = this.bakedRuntimeActions.get(runtimeClip.name);
|
|
837
|
-
if (existing) {
|
|
1013
|
+
if (existing?.getClip() === desiredClip) {
|
|
838
1014
|
return existing;
|
|
839
1015
|
}
|
|
840
1016
|
this.ensureMixer();
|
|
841
1017
|
if (!this.animationMixer) {
|
|
842
1018
|
return null;
|
|
843
1019
|
}
|
|
844
|
-
|
|
1020
|
+
if (existing) {
|
|
1021
|
+
this.releaseBakedRuntimeAction(runtimeClip.name);
|
|
1022
|
+
}
|
|
1023
|
+
const action = this.animationMixer.clipAction(desiredClip);
|
|
845
1024
|
this.bakedRuntimeActions.set(runtimeClip.name, action);
|
|
846
1025
|
return action;
|
|
847
1026
|
}
|
|
@@ -859,7 +1038,15 @@ var BakedAnimationController = class {
|
|
|
859
1038
|
}
|
|
860
1039
|
const channelActions = /* @__PURE__ */ new Map();
|
|
861
1040
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
862
|
-
const
|
|
1041
|
+
const channelBlendMode = resolveBakedChannelBlendMode(
|
|
1042
|
+
runtimeClip.channel,
|
|
1043
|
+
playbackState.requestedBlendMode
|
|
1044
|
+
) ?? "replace";
|
|
1045
|
+
const action = this.getOrCreateBakedRuntimeAction(
|
|
1046
|
+
clipName,
|
|
1047
|
+
runtimeClip.channel,
|
|
1048
|
+
channelBlendMode
|
|
1049
|
+
);
|
|
863
1050
|
if (action) {
|
|
864
1051
|
channelActions.set(runtimeClip.channel, action);
|
|
865
1052
|
}
|
|
@@ -898,13 +1085,39 @@ var BakedAnimationController = class {
|
|
|
898
1085
|
}
|
|
899
1086
|
return getMeshNamesForVisemeProfile(config);
|
|
900
1087
|
}
|
|
1088
|
+
hasActiveAdditivePlayback() {
|
|
1089
|
+
for (const [clipName, group] of this.bakedActionGroups) {
|
|
1090
|
+
const state = this.playbackState.get(clipName);
|
|
1091
|
+
if (state?.blendMode !== "additive") {
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
for (const action of group.channelActions.values()) {
|
|
1095
|
+
if (action.isRunning() && !action.paused) {
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
for (const [clipName, action] of this.animationActions) {
|
|
1101
|
+
const state = this.playbackState.get(clipName);
|
|
1102
|
+
if (state?.blendMode !== "additive") {
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (action.isRunning() && !action.paused) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
901
1111
|
update(dtSeconds) {
|
|
902
1112
|
if (this.animationMixer) {
|
|
1113
|
+
this.animationMixer.update(dtSeconds);
|
|
1114
|
+
}
|
|
1115
|
+
if (this.clipAnimationMixer) {
|
|
903
1116
|
const snapshots = Array.from(this.clipMonitors.values()).map((monitor) => ({
|
|
904
1117
|
actionId: monitor.actionId,
|
|
905
1118
|
previousTime: monitor.action.time
|
|
906
1119
|
}));
|
|
907
|
-
this.
|
|
1120
|
+
this.clipAnimationMixer.update(dtSeconds);
|
|
908
1121
|
for (const { actionId, previousTime } of snapshots) {
|
|
909
1122
|
const monitor = this.clipMonitors.get(actionId);
|
|
910
1123
|
if (!monitor) continue;
|
|
@@ -923,16 +1136,27 @@ var BakedAnimationController = class {
|
|
|
923
1136
|
}
|
|
924
1137
|
}
|
|
925
1138
|
}
|
|
1139
|
+
if (this.hasActiveAdditivePlayback()) {
|
|
1140
|
+
this.host.reapplyProceduralState?.();
|
|
1141
|
+
}
|
|
926
1142
|
}
|
|
927
1143
|
dispose() {
|
|
928
1144
|
this.stopAllAnimations();
|
|
1145
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
929
1146
|
if (this.animationMixer) {
|
|
930
1147
|
this.animationMixer.stopAllAction();
|
|
931
1148
|
this.animationMixer = null;
|
|
932
1149
|
}
|
|
1150
|
+
if (this.clipAnimationMixer) {
|
|
1151
|
+
this.clipAnimationMixer.stopAllAction();
|
|
1152
|
+
this.clipAnimationMixer = null;
|
|
1153
|
+
}
|
|
1154
|
+
this.mixerFinishedListenerAttached = false;
|
|
1155
|
+
this.clipMixerFinishedListenerAttached = false;
|
|
933
1156
|
this.animationClips = [];
|
|
934
1157
|
this.bakedSourceClips.clear();
|
|
935
1158
|
this.bakedRuntimeActions.clear();
|
|
1159
|
+
this.bakedAdditiveRuntimeClips.clear();
|
|
936
1160
|
this.bakedActionGroups.clear();
|
|
937
1161
|
this.bakedRuntimeClipToSource.clear();
|
|
938
1162
|
this.animationActions.clear();
|
|
@@ -955,6 +1179,8 @@ var BakedAnimationController = class {
|
|
|
955
1179
|
if (this.animationMixer) {
|
|
956
1180
|
for (const bakedClip of this.bakedSourceClips.values()) {
|
|
957
1181
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1182
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1183
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
958
1184
|
try {
|
|
959
1185
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
960
1186
|
} catch {
|
|
@@ -966,6 +1192,7 @@ var BakedAnimationController = class {
|
|
|
966
1192
|
}
|
|
967
1193
|
}
|
|
968
1194
|
}
|
|
1195
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
969
1196
|
for (const clipName of this.bakedSourceClips.keys()) {
|
|
970
1197
|
this.playbackState.delete(clipName);
|
|
971
1198
|
this.clipSources.delete(clipName);
|
|
@@ -1006,6 +1233,8 @@ var BakedAnimationController = class {
|
|
|
1006
1233
|
if (this.animationMixer) {
|
|
1007
1234
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1008
1235
|
const action = this.bakedRuntimeActions.get(runtimeClip.clip.name);
|
|
1236
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1237
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
1009
1238
|
try {
|
|
1010
1239
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
1011
1240
|
} catch {
|
|
@@ -1014,7 +1243,6 @@ var BakedAnimationController = class {
|
|
|
1014
1243
|
this.animationMixer.uncacheClip(runtimeClip.clip);
|
|
1015
1244
|
} catch {
|
|
1016
1245
|
}
|
|
1017
|
-
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
1018
1246
|
this.bakedRuntimeClipToSource.delete(runtimeClip.clip.name);
|
|
1019
1247
|
const actionId = this.getActionId(action);
|
|
1020
1248
|
if (actionId && action) {
|
|
@@ -1023,6 +1251,10 @@ var BakedAnimationController = class {
|
|
|
1023
1251
|
}
|
|
1024
1252
|
}
|
|
1025
1253
|
}
|
|
1254
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1255
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
1256
|
+
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
1257
|
+
}
|
|
1026
1258
|
this.animationClips = this.animationClips.filter((entry) => entry.name !== clipName);
|
|
1027
1259
|
this.bakedSourceClips.delete(clipName);
|
|
1028
1260
|
this.bakedActionGroups.delete(clipName);
|
|
@@ -1083,12 +1315,12 @@ var BakedAnimationController = class {
|
|
|
1083
1315
|
const actionId = this.getActionId(action);
|
|
1084
1316
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
1085
1317
|
action.stop();
|
|
1086
|
-
if (!isBaked && this.
|
|
1318
|
+
if (!isBaked && this.clipAnimationMixer) {
|
|
1087
1319
|
try {
|
|
1088
1320
|
const clip = action.getClip();
|
|
1089
1321
|
if (clip) {
|
|
1090
|
-
this.
|
|
1091
|
-
this.
|
|
1322
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1323
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1092
1324
|
}
|
|
1093
1325
|
} catch {
|
|
1094
1326
|
}
|
|
@@ -1110,11 +1342,11 @@ var BakedAnimationController = class {
|
|
|
1110
1342
|
const actionId = this.getActionId(clipAction);
|
|
1111
1343
|
try {
|
|
1112
1344
|
clipAction.stop();
|
|
1113
|
-
if (this.
|
|
1345
|
+
if (this.clipAnimationMixer) {
|
|
1114
1346
|
const clip = clipAction.getClip();
|
|
1115
1347
|
if (clip) {
|
|
1116
|
-
this.
|
|
1117
|
-
this.
|
|
1348
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1349
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1118
1350
|
}
|
|
1119
1351
|
}
|
|
1120
1352
|
} catch {
|
|
@@ -1318,8 +1550,23 @@ var BakedAnimationController = class {
|
|
|
1318
1550
|
next.blendMode = this.getBakedAggregateBlendMode(clipName, next);
|
|
1319
1551
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1320
1552
|
if (bakedGroup) {
|
|
1321
|
-
for (const [channel,
|
|
1553
|
+
for (const [channel, currentAction] of Array.from(bakedGroup.channelActions)) {
|
|
1554
|
+
const channelBlendMode = resolveBakedChannelBlendMode(channel, next.requestedBlendMode) ?? "replace";
|
|
1555
|
+
const previousTime = currentAction.time;
|
|
1556
|
+
const wasActive = currentAction.isRunning() || currentAction.paused;
|
|
1557
|
+
const wasPaused = currentAction.paused;
|
|
1558
|
+
const action2 = this.getOrCreateBakedRuntimeAction(clipName, channel, channelBlendMode);
|
|
1559
|
+
if (!action2) {
|
|
1560
|
+
bakedGroup.channelActions.delete(channel);
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1322
1563
|
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
1564
|
+
action2.time = Math.max(0, Math.min(action2.getClip().duration, previousTime));
|
|
1565
|
+
if (action2 !== currentAction && wasActive) {
|
|
1566
|
+
action2.play();
|
|
1567
|
+
}
|
|
1568
|
+
action2.paused = wasPaused;
|
|
1569
|
+
bakedGroup.channelActions.set(channel, action2);
|
|
1323
1570
|
}
|
|
1324
1571
|
}
|
|
1325
1572
|
this.setPlaybackState(clipName, next);
|
|
@@ -1334,6 +1581,10 @@ var BakedAnimationController = class {
|
|
|
1334
1581
|
seekAnimation(clipName, time) {
|
|
1335
1582
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1336
1583
|
if (bakedGroup) {
|
|
1584
|
+
const state = this.getPlaybackStateSnapshot(clipName, {
|
|
1585
|
+
loop: true,
|
|
1586
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
1587
|
+
});
|
|
1337
1588
|
const duration2 = this.getBakedSourceClip(clipName)?.sourceClip.duration ?? 0;
|
|
1338
1589
|
const clamped = Math.max(0, Math.min(duration2, Number.isFinite(time) ? time : 0));
|
|
1339
1590
|
for (const action2 of bakedGroup.channelActions.values()) {
|
|
@@ -1341,6 +1592,10 @@ var BakedAnimationController = class {
|
|
|
1341
1592
|
}
|
|
1342
1593
|
try {
|
|
1343
1594
|
this.animationMixer?.update(0);
|
|
1595
|
+
this.clipAnimationMixer?.update(0);
|
|
1596
|
+
if (state.blendMode === "additive") {
|
|
1597
|
+
this.host.reapplyProceduralState?.();
|
|
1598
|
+
}
|
|
1344
1599
|
} catch {
|
|
1345
1600
|
}
|
|
1346
1601
|
return;
|
|
@@ -1350,7 +1605,11 @@ var BakedAnimationController = class {
|
|
|
1350
1605
|
const duration = action.getClip().duration;
|
|
1351
1606
|
action.time = Math.max(0, Math.min(duration, Number.isFinite(time) ? time : 0));
|
|
1352
1607
|
try {
|
|
1353
|
-
this.
|
|
1608
|
+
this.clipAnimationMixer?.update(0);
|
|
1609
|
+
const state = this.playbackState.get(clipName);
|
|
1610
|
+
if (state?.blendMode === "additive") {
|
|
1611
|
+
this.host.reapplyProceduralState?.();
|
|
1612
|
+
}
|
|
1354
1613
|
} catch {
|
|
1355
1614
|
}
|
|
1356
1615
|
}
|
|
@@ -1358,6 +1617,9 @@ var BakedAnimationController = class {
|
|
|
1358
1617
|
if (this.animationMixer) {
|
|
1359
1618
|
this.animationMixer.timeScale = timeScale;
|
|
1360
1619
|
}
|
|
1620
|
+
if (this.clipAnimationMixer) {
|
|
1621
|
+
this.clipAnimationMixer.timeScale = timeScale;
|
|
1622
|
+
}
|
|
1361
1623
|
}
|
|
1362
1624
|
getAnimationState(clipName) {
|
|
1363
1625
|
const bakedClip = this.getBakedSourceClip(clipName);
|
|
@@ -1687,8 +1949,8 @@ var BakedAnimationController = class {
|
|
|
1687
1949
|
return clip;
|
|
1688
1950
|
}
|
|
1689
1951
|
playClip(clip, options) {
|
|
1690
|
-
this.
|
|
1691
|
-
if (!
|
|
1952
|
+
const mixer = this.ensureClipMixer();
|
|
1953
|
+
if (!mixer) {
|
|
1692
1954
|
console.warn("[Loom3] playClip: No model loaded, cannot create mixer");
|
|
1693
1955
|
return null;
|
|
1694
1956
|
}
|
|
@@ -1706,7 +1968,7 @@ var BakedAnimationController = class {
|
|
|
1706
1968
|
actionId = this.setActionId(action, clip.name);
|
|
1707
1969
|
}
|
|
1708
1970
|
if (!action) {
|
|
1709
|
-
action =
|
|
1971
|
+
action = mixer.clipAction(clip);
|
|
1710
1972
|
actionId = this.setActionId(action, clip.name);
|
|
1711
1973
|
}
|
|
1712
1974
|
const existingClip = this.animationClips.find((c) => c.name === clip.name);
|
|
@@ -1767,15 +2029,13 @@ var BakedAnimationController = class {
|
|
|
1767
2029
|
},
|
|
1768
2030
|
stop: () => {
|
|
1769
2031
|
action.stop();
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
} catch {
|
|
1778
|
-
}
|
|
2032
|
+
try {
|
|
2033
|
+
mixer.uncacheAction(clip);
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
mixer.uncacheClip(clip);
|
|
2038
|
+
} catch {
|
|
1779
2039
|
}
|
|
1780
2040
|
this.clipActions.delete(clip.name);
|
|
1781
2041
|
this.animationActions.delete(clip.name);
|
|
@@ -1816,7 +2076,7 @@ var BakedAnimationController = class {
|
|
|
1816
2076
|
const clamped = Math.max(0, Math.min(clip.duration, t));
|
|
1817
2077
|
action.time = clamped;
|
|
1818
2078
|
try {
|
|
1819
|
-
|
|
2079
|
+
mixer.update(0);
|
|
1820
2080
|
} catch {
|
|
1821
2081
|
}
|
|
1822
2082
|
this.syncClipMonitorTime(monitor, clamped, true);
|
|
@@ -1849,16 +2109,16 @@ var BakedAnimationController = class {
|
|
|
1849
2109
|
return this.playClip(clip, { ...options, source: options?.source ?? "clip" });
|
|
1850
2110
|
}
|
|
1851
2111
|
cleanupSnippet(name) {
|
|
1852
|
-
if (!this.
|
|
2112
|
+
if (!this.host.getModel()) return;
|
|
1853
2113
|
for (const [clipName, action] of Array.from(this.clipActions.entries())) {
|
|
1854
2114
|
if (clipName === name || clipName.startsWith(`${name}_`)) {
|
|
1855
2115
|
const actionId = this.getActionId(action);
|
|
1856
2116
|
try {
|
|
1857
2117
|
action.stop();
|
|
1858
2118
|
const clip = action.getClip();
|
|
1859
|
-
if (clip) {
|
|
1860
|
-
this.
|
|
1861
|
-
this.
|
|
2119
|
+
if (clip && this.clipAnimationMixer) {
|
|
2120
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
2121
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1862
2122
|
}
|
|
1863
2123
|
} catch {
|
|
1864
2124
|
}
|
|
@@ -1886,7 +2146,10 @@ var BakedAnimationController = class {
|
|
|
1886
2146
|
clipActions: Array.from(this.clipActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1887
2147
|
animationActions: Array.from(this.animationActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1888
2148
|
clipHandles: Array.from(this.clipHandles.entries()).map(([k, h]) => ({ name: k, actionId: h.actionId })),
|
|
1889
|
-
mixerActions:
|
|
2149
|
+
mixerActions: [
|
|
2150
|
+
...this.animationMixer?._actions || [],
|
|
2151
|
+
...this.clipAnimationMixer?._actions || []
|
|
2152
|
+
].map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.getActionId(a) }))
|
|
1890
2153
|
});
|
|
1891
2154
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
1892
2155
|
const apply = (action) => {
|
|
@@ -2000,35 +2263,48 @@ var BakedAnimationController = class {
|
|
|
2000
2263
|
this.animationMixer = new THREE2.AnimationMixer(model);
|
|
2001
2264
|
}
|
|
2002
2265
|
if (this.animationMixer && !this.mixerFinishedListenerAttached) {
|
|
2003
|
-
this.animationMixer.addEventListener("finished", (event) =>
|
|
2004
|
-
const action = event.action;
|
|
2005
|
-
const actionId = this.getActionId(action);
|
|
2006
|
-
if (actionId) {
|
|
2007
|
-
const monitor = this.clipMonitors.get(actionId);
|
|
2008
|
-
if (monitor) {
|
|
2009
|
-
monitor.finishedPending = true;
|
|
2010
|
-
return;
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
const clip = action.getClip();
|
|
2014
|
-
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
2015
|
-
if (bakedRuntime) {
|
|
2016
|
-
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
2017
|
-
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
2018
|
-
group.resolveFinished();
|
|
2019
|
-
}
|
|
2020
|
-
return;
|
|
2021
|
-
}
|
|
2022
|
-
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
2023
|
-
if (callback) {
|
|
2024
|
-
callback();
|
|
2025
|
-
this.animationFinishedCallbacks.delete(clip.name);
|
|
2026
|
-
}
|
|
2027
|
-
});
|
|
2266
|
+
this.animationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
2028
2267
|
this.mixerFinishedListenerAttached = true;
|
|
2029
2268
|
}
|
|
2030
2269
|
return this.animationMixer;
|
|
2031
2270
|
}
|
|
2271
|
+
ensureClipMixer() {
|
|
2272
|
+
const model = this.host.getModel();
|
|
2273
|
+
if (!model) return null;
|
|
2274
|
+
if (!this.clipAnimationMixer) {
|
|
2275
|
+
this.clipAnimationMixer = new THREE2.AnimationMixer(model);
|
|
2276
|
+
}
|
|
2277
|
+
if (this.clipAnimationMixer && !this.clipMixerFinishedListenerAttached) {
|
|
2278
|
+
this.clipAnimationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
2279
|
+
this.clipMixerFinishedListenerAttached = true;
|
|
2280
|
+
}
|
|
2281
|
+
return this.clipAnimationMixer;
|
|
2282
|
+
}
|
|
2283
|
+
handleMixerFinished(event) {
|
|
2284
|
+
const action = event.action;
|
|
2285
|
+
const actionId = this.getActionId(action);
|
|
2286
|
+
if (actionId) {
|
|
2287
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
2288
|
+
if (monitor) {
|
|
2289
|
+
monitor.finishedPending = true;
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
const clip = action.getClip();
|
|
2294
|
+
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
2295
|
+
if (bakedRuntime) {
|
|
2296
|
+
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
2297
|
+
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
2298
|
+
group.resolveFinished();
|
|
2299
|
+
}
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
2303
|
+
if (callback) {
|
|
2304
|
+
callback();
|
|
2305
|
+
this.animationFinishedCallbacks.delete(clip.name);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2032
2308
|
createAnimationHandle(clipName, action, finishedPromise) {
|
|
2033
2309
|
return {
|
|
2034
2310
|
actionId: this.getActionId(action),
|
|
@@ -4992,7 +5268,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4992
5268
|
getCompositeRotations: () => this.compositeRotations,
|
|
4993
5269
|
computeSideValues: (base, balance) => this.computeSideValues(base, balance),
|
|
4994
5270
|
getAUMixWeight: (auId) => this.getAUMixWeight(auId),
|
|
4995
|
-
isMixedAU: (auId) => this.isMixedAU(auId)
|
|
5271
|
+
isMixedAU: (auId) => this.isMixedAU(auId),
|
|
5272
|
+
reapplyProceduralState: () => this.reapplyProceduralStateAfterBakedUpdate()
|
|
4996
5273
|
});
|
|
4997
5274
|
this.hairPhysics = new HairPhysicsController({
|
|
4998
5275
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
@@ -5703,6 +5980,30 @@ var _Loom3 = class _Loom3 {
|
|
|
5703
5980
|
this.model.updateMatrixWorld(true);
|
|
5704
5981
|
}
|
|
5705
5982
|
}
|
|
5983
|
+
reapplyProceduralStateAfterBakedUpdate() {
|
|
5984
|
+
if (!this.model) {
|
|
5985
|
+
return;
|
|
5986
|
+
}
|
|
5987
|
+
let hasActiveOverrides = false;
|
|
5988
|
+
for (const [auIdStr, value] of Object.entries(this.auValues)) {
|
|
5989
|
+
if (value <= 0) continue;
|
|
5990
|
+
const auId = Number(auIdStr);
|
|
5991
|
+
if (Number.isNaN(auId)) continue;
|
|
5992
|
+
hasActiveOverrides = true;
|
|
5993
|
+
this.setAU(auId, value, this.auBalances[auId]);
|
|
5994
|
+
}
|
|
5995
|
+
for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
|
|
5996
|
+
const value = this.visemeValues[visemeIndex] ?? 0;
|
|
5997
|
+
if (value <= 0) continue;
|
|
5998
|
+
hasActiveOverrides = true;
|
|
5999
|
+
this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
|
|
6000
|
+
}
|
|
6001
|
+
if (!hasActiveOverrides) {
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
this.flushPendingComposites();
|
|
6005
|
+
this.model.updateMatrixWorld(true);
|
|
6006
|
+
}
|
|
5706
6007
|
// ============================================================================
|
|
5707
6008
|
// MESH CONTROL
|
|
5708
6009
|
// ============================================================================
|