@lovelace_lol/loom3 1.0.42 → 1.0.44
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 +448 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +449 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as THREE2 from 'three';
|
|
2
|
-
import { Vector3, Clock, Box3, BufferAttribute, Quaternion,
|
|
2
|
+
import { Vector3, Clock, Box3, BufferAttribute, Quaternion, PropertyBinding, NumberKeyframeTrack, QuaternionKeyframeTrack, VectorKeyframeTrack, AnimationClip, AnimationUtils, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopPingPong, LoopOnce, LoopRepeat, AnimationMixer, Mesh } from 'three';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -53,6 +53,9 @@ function bindingTargets(binding) {
|
|
|
53
53
|
if (targets && targets.length > 0) return targets;
|
|
54
54
|
return binding.morph !== void 0 && binding.morph !== "" ? [binding.morph] : [];
|
|
55
55
|
}
|
|
56
|
+
function normalizeBindingWeight(weight) {
|
|
57
|
+
return Number.isFinite(weight) ? Math.max(0, weight ?? 1) : 1;
|
|
58
|
+
}
|
|
56
59
|
function getProfileVisemeSlots(profile) {
|
|
57
60
|
if (profile.visemeSlots && profile.visemeSlots.length > 0) {
|
|
58
61
|
return [...profile.visemeSlots].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
@@ -78,6 +81,23 @@ function compileVisemeKeys(profile) {
|
|
|
78
81
|
return target ?? profile.visemeKeys?.[index] ?? "";
|
|
79
82
|
});
|
|
80
83
|
}
|
|
84
|
+
function getVisemeBindingTargets(profile, visemeIndex) {
|
|
85
|
+
const slots = getProfileVisemeSlots(profile);
|
|
86
|
+
const slot = slots[visemeIndex];
|
|
87
|
+
const binding = slot ? profile.visemeBindings?.[slot.id] : void 0;
|
|
88
|
+
const boundTargets = binding?.targets?.filter((target) => target.morph !== void 0 && target.morph !== "").map((target) => ({
|
|
89
|
+
morph: target.morph,
|
|
90
|
+
weight: normalizeBindingWeight(target.weight)
|
|
91
|
+
}));
|
|
92
|
+
if (boundTargets && boundTargets.length > 0) {
|
|
93
|
+
return boundTargets;
|
|
94
|
+
}
|
|
95
|
+
if (binding?.morph !== void 0 && binding.morph !== "") {
|
|
96
|
+
return [{ morph: binding.morph, weight: 1 }];
|
|
97
|
+
}
|
|
98
|
+
const legacyTarget = profile.visemeKeys?.[visemeIndex];
|
|
99
|
+
return legacyTarget !== void 0 && legacyTarget !== "" ? [{ morph: legacyTarget, weight: 1 }] : [];
|
|
100
|
+
}
|
|
81
101
|
function getVisemeJawAmounts(profile) {
|
|
82
102
|
const slots = getProfileVisemeSlots(profile);
|
|
83
103
|
if (slots.length === 0) return profile.visemeJawAmounts ? [...profile.visemeJawAmounts] : void 0;
|
|
@@ -489,14 +509,19 @@ var Y_AXIS = new Vector3(0, 1, 0);
|
|
|
489
509
|
var Z_AXIS = new Vector3(0, 0, 1);
|
|
490
510
|
var CLIP_EVENT_METADATA_KEY = "__loom3ClipEvents";
|
|
491
511
|
var CLIP_EVENT_EPSILON = 1e-4;
|
|
512
|
+
var ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX = "__loom3_additive_delta";
|
|
492
513
|
var BakedAnimationController = class {
|
|
493
514
|
constructor(host) {
|
|
494
515
|
__publicField(this, "host");
|
|
516
|
+
// Clip-backed snippets need a later mixer pass so they can override baked additive tracks.
|
|
495
517
|
__publicField(this, "animationMixer", null);
|
|
518
|
+
__publicField(this, "clipAnimationMixer", null);
|
|
496
519
|
__publicField(this, "mixerFinishedListenerAttached", false);
|
|
520
|
+
__publicField(this, "clipMixerFinishedListenerAttached", false);
|
|
497
521
|
__publicField(this, "animationClips", []);
|
|
498
522
|
__publicField(this, "bakedSourceClips", /* @__PURE__ */ new Map());
|
|
499
523
|
__publicField(this, "bakedRuntimeActions", /* @__PURE__ */ new Map());
|
|
524
|
+
__publicField(this, "bakedAdditiveRuntimeClips", /* @__PURE__ */ new Map());
|
|
500
525
|
__publicField(this, "bakedActionGroups", /* @__PURE__ */ new Map());
|
|
501
526
|
__publicField(this, "bakedRuntimeClipToSource", /* @__PURE__ */ new Map());
|
|
502
527
|
__publicField(this, "animationActions", /* @__PURE__ */ new Map());
|
|
@@ -521,6 +546,173 @@ var BakedAnimationController = class {
|
|
|
521
546
|
action.__actionId = actionId;
|
|
522
547
|
return actionId;
|
|
523
548
|
}
|
|
549
|
+
clearActionId(action) {
|
|
550
|
+
if (!action) return;
|
|
551
|
+
const actionId = this.getActionId(action);
|
|
552
|
+
if (actionId) {
|
|
553
|
+
this.actionIdToClip.delete(actionId);
|
|
554
|
+
}
|
|
555
|
+
this.actionIds.delete(action);
|
|
556
|
+
delete action.__actionId;
|
|
557
|
+
}
|
|
558
|
+
uncacheClip(clip, mixer = this.animationMixer) {
|
|
559
|
+
if (!clip || !mixer) return;
|
|
560
|
+
try {
|
|
561
|
+
mixer.uncacheClip(clip);
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
uncacheAction(action, mixer = this.animationMixer) {
|
|
566
|
+
if (!action || !mixer) return;
|
|
567
|
+
try {
|
|
568
|
+
const clip = action.getClip();
|
|
569
|
+
if (clip) {
|
|
570
|
+
mixer.uncacheAction(clip);
|
|
571
|
+
mixer.uncacheClip(clip);
|
|
572
|
+
}
|
|
573
|
+
} catch {
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
releaseBakedRuntimeAction(runtimeClipName) {
|
|
577
|
+
const action = this.bakedRuntimeActions.get(runtimeClipName);
|
|
578
|
+
if (!action) return;
|
|
579
|
+
try {
|
|
580
|
+
action.stop();
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
this.uncacheAction(action);
|
|
584
|
+
this.clearActionId(action);
|
|
585
|
+
this.bakedRuntimeActions.delete(runtimeClipName);
|
|
586
|
+
}
|
|
587
|
+
clearBakedAdditiveRuntimeClip(runtimeClipName) {
|
|
588
|
+
const clip = this.bakedAdditiveRuntimeClips.get(runtimeClipName);
|
|
589
|
+
if (!clip) return;
|
|
590
|
+
this.uncacheClip(clip);
|
|
591
|
+
this.bakedAdditiveRuntimeClips.delete(runtimeClipName);
|
|
592
|
+
}
|
|
593
|
+
clearAllBakedAdditiveRuntimeClips() {
|
|
594
|
+
for (const runtimeClipName of Array.from(this.bakedAdditiveRuntimeClips.keys())) {
|
|
595
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClipName);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
resolveTrackTarget(model, parsed) {
|
|
599
|
+
const targetKey = parsed.objectName === "bones" && parsed.objectIndex ? parsed.objectIndex : parsed.nodeName;
|
|
600
|
+
if (!targetKey) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
return model.getObjectByProperty("uuid", targetKey) ?? PropertyBinding.findNode(model, targetKey) ?? null;
|
|
604
|
+
}
|
|
605
|
+
getMorphTrackBaseValue(target, propertyIndex) {
|
|
606
|
+
if (!target) {
|
|
607
|
+
return 0;
|
|
608
|
+
}
|
|
609
|
+
const meshTarget = target;
|
|
610
|
+
const influences = meshTarget.morphTargetInfluences;
|
|
611
|
+
if (!influences) {
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
let morphIndex;
|
|
615
|
+
if (typeof propertyIndex === "number" && Number.isInteger(propertyIndex)) {
|
|
616
|
+
morphIndex = propertyIndex;
|
|
617
|
+
} else if (typeof propertyIndex === "string") {
|
|
618
|
+
if (/^\d+$/.test(propertyIndex)) {
|
|
619
|
+
morphIndex = Number(propertyIndex);
|
|
620
|
+
} else {
|
|
621
|
+
morphIndex = meshTarget.morphTargetDictionary?.[propertyIndex];
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (morphIndex === void 0) {
|
|
625
|
+
return 0;
|
|
626
|
+
}
|
|
627
|
+
return influences[morphIndex] ?? 0;
|
|
628
|
+
}
|
|
629
|
+
canCreateFirstFrameReferenceTrack(track) {
|
|
630
|
+
const valueSize = track.getValueSize();
|
|
631
|
+
if (!Number.isFinite(valueSize) || valueSize <= 0 || track.values.length < valueSize) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
return track.ValueTypeName === "number" || track.ValueTypeName === "quaternion" || track.ValueTypeName === "vector";
|
|
635
|
+
}
|
|
636
|
+
createFirstFrameReferenceTrack(track) {
|
|
637
|
+
const valueSize = track.getValueSize();
|
|
638
|
+
if (!this.canCreateFirstFrameReferenceTrack(track)) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
const values = Array.from(track.values.slice(0, valueSize));
|
|
642
|
+
if (track.ValueTypeName === "number") {
|
|
643
|
+
return new NumberKeyframeTrack(track.name, [0], values);
|
|
644
|
+
}
|
|
645
|
+
if (track.ValueTypeName === "quaternion") {
|
|
646
|
+
return new QuaternionKeyframeTrack(track.name, [0], values);
|
|
647
|
+
}
|
|
648
|
+
if (track.ValueTypeName === "vector") {
|
|
649
|
+
return new VectorKeyframeTrack(track.name, [0], values);
|
|
650
|
+
}
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
createAdditiveReferenceTrack(track, model) {
|
|
654
|
+
const trackName = typeof track?.name === "string" ? track.name : "";
|
|
655
|
+
if (!trackName) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
let parsed;
|
|
659
|
+
try {
|
|
660
|
+
parsed = PropertyBinding.parseTrackName(trackName);
|
|
661
|
+
} catch {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
const target = this.resolveTrackTarget(model, parsed);
|
|
665
|
+
if (parsed.propertyName === "morphTargetInfluences") {
|
|
666
|
+
return new NumberKeyframeTrack(
|
|
667
|
+
track.name,
|
|
668
|
+
[0],
|
|
669
|
+
[this.getMorphTrackBaseValue(target, parsed.propertyIndex)]
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
return this.createFirstFrameReferenceTrack(track);
|
|
673
|
+
}
|
|
674
|
+
createAdditiveRuntimeClip(runtimeClip) {
|
|
675
|
+
const model = this.host.getModel();
|
|
676
|
+
if (!model) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
const additiveTracks = [];
|
|
680
|
+
const referenceTracks = [];
|
|
681
|
+
for (const track of runtimeClip.tracks) {
|
|
682
|
+
const referenceTrack = this.createAdditiveReferenceTrack(track, model);
|
|
683
|
+
if (!referenceTrack) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
additiveTracks.push(track.clone());
|
|
687
|
+
referenceTracks.push(referenceTrack);
|
|
688
|
+
}
|
|
689
|
+
const additiveClip = new AnimationClip(
|
|
690
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}`,
|
|
691
|
+
runtimeClip.duration,
|
|
692
|
+
additiveTracks
|
|
693
|
+
);
|
|
694
|
+
if (additiveTracks.length > 0) {
|
|
695
|
+
const referenceClip = new AnimationClip(
|
|
696
|
+
`${runtimeClip.name}${ADDITIVE_BAKED_RUNTIME_CLIP_SUFFIX}_reference`,
|
|
697
|
+
0,
|
|
698
|
+
referenceTracks
|
|
699
|
+
);
|
|
700
|
+
AnimationUtils.makeClipAdditive(additiveClip, 0, referenceClip);
|
|
701
|
+
}
|
|
702
|
+
return additiveClip;
|
|
703
|
+
}
|
|
704
|
+
getOrCreateBakedAdditiveRuntimeClip(runtimeClip) {
|
|
705
|
+
const cached = this.bakedAdditiveRuntimeClips.get(runtimeClip.name);
|
|
706
|
+
if (cached) {
|
|
707
|
+
return cached;
|
|
708
|
+
}
|
|
709
|
+
const additiveClip = this.createAdditiveRuntimeClip(runtimeClip);
|
|
710
|
+
if (!additiveClip) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
this.bakedAdditiveRuntimeClips.set(runtimeClip.name, additiveClip);
|
|
714
|
+
return additiveClip;
|
|
715
|
+
}
|
|
524
716
|
setClipEventMetadata(clip, metadata) {
|
|
525
717
|
const userData = clip.userData ?? (clip.userData = {});
|
|
526
718
|
userData[CLIP_EVENT_METADATA_KEY] = metadata;
|
|
@@ -806,21 +998,28 @@ var BakedAnimationController = class {
|
|
|
806
998
|
}
|
|
807
999
|
return 0;
|
|
808
1000
|
}
|
|
809
|
-
getOrCreateBakedRuntimeAction(sourceClipName, channel) {
|
|
1001
|
+
getOrCreateBakedRuntimeAction(sourceClipName, channel, blendMode = "replace") {
|
|
810
1002
|
const bakedClip = this.getBakedSourceClip(sourceClipName);
|
|
811
1003
|
const runtimeClip = bakedClip?.runtimeClips.find((entry) => entry.channel === channel)?.clip;
|
|
812
1004
|
if (!runtimeClip) {
|
|
813
1005
|
return null;
|
|
814
1006
|
}
|
|
1007
|
+
const desiredClip = blendMode === "additive" ? this.getOrCreateBakedAdditiveRuntimeClip(runtimeClip) : runtimeClip;
|
|
1008
|
+
if (!desiredClip) {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
815
1011
|
const existing = this.bakedRuntimeActions.get(runtimeClip.name);
|
|
816
|
-
if (existing) {
|
|
1012
|
+
if (existing?.getClip() === desiredClip) {
|
|
817
1013
|
return existing;
|
|
818
1014
|
}
|
|
819
1015
|
this.ensureMixer();
|
|
820
1016
|
if (!this.animationMixer) {
|
|
821
1017
|
return null;
|
|
822
1018
|
}
|
|
823
|
-
|
|
1019
|
+
if (existing) {
|
|
1020
|
+
this.releaseBakedRuntimeAction(runtimeClip.name);
|
|
1021
|
+
}
|
|
1022
|
+
const action = this.animationMixer.clipAction(desiredClip);
|
|
824
1023
|
this.bakedRuntimeActions.set(runtimeClip.name, action);
|
|
825
1024
|
return action;
|
|
826
1025
|
}
|
|
@@ -838,7 +1037,15 @@ var BakedAnimationController = class {
|
|
|
838
1037
|
}
|
|
839
1038
|
const channelActions = /* @__PURE__ */ new Map();
|
|
840
1039
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
841
|
-
const
|
|
1040
|
+
const channelBlendMode = resolveBakedChannelBlendMode(
|
|
1041
|
+
runtimeClip.channel,
|
|
1042
|
+
playbackState.requestedBlendMode
|
|
1043
|
+
) ?? "replace";
|
|
1044
|
+
const action = this.getOrCreateBakedRuntimeAction(
|
|
1045
|
+
clipName,
|
|
1046
|
+
runtimeClip.channel,
|
|
1047
|
+
channelBlendMode
|
|
1048
|
+
);
|
|
842
1049
|
if (action) {
|
|
843
1050
|
channelActions.set(runtimeClip.channel, action);
|
|
844
1051
|
}
|
|
@@ -877,13 +1084,39 @@ var BakedAnimationController = class {
|
|
|
877
1084
|
}
|
|
878
1085
|
return getMeshNamesForVisemeProfile(config);
|
|
879
1086
|
}
|
|
1087
|
+
hasActiveAdditivePlayback() {
|
|
1088
|
+
for (const [clipName, group] of this.bakedActionGroups) {
|
|
1089
|
+
const state = this.playbackState.get(clipName);
|
|
1090
|
+
if (state?.blendMode !== "additive") {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
for (const action of group.channelActions.values()) {
|
|
1094
|
+
if (action.isRunning() && !action.paused) {
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
for (const [clipName, action] of this.animationActions) {
|
|
1100
|
+
const state = this.playbackState.get(clipName);
|
|
1101
|
+
if (state?.blendMode !== "additive") {
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
if (action.isRunning() && !action.paused) {
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
880
1110
|
update(dtSeconds) {
|
|
881
1111
|
if (this.animationMixer) {
|
|
1112
|
+
this.animationMixer.update(dtSeconds);
|
|
1113
|
+
}
|
|
1114
|
+
if (this.clipAnimationMixer) {
|
|
882
1115
|
const snapshots = Array.from(this.clipMonitors.values()).map((monitor) => ({
|
|
883
1116
|
actionId: monitor.actionId,
|
|
884
1117
|
previousTime: monitor.action.time
|
|
885
1118
|
}));
|
|
886
|
-
this.
|
|
1119
|
+
this.clipAnimationMixer.update(dtSeconds);
|
|
887
1120
|
for (const { actionId, previousTime } of snapshots) {
|
|
888
1121
|
const monitor = this.clipMonitors.get(actionId);
|
|
889
1122
|
if (!monitor) continue;
|
|
@@ -902,16 +1135,27 @@ var BakedAnimationController = class {
|
|
|
902
1135
|
}
|
|
903
1136
|
}
|
|
904
1137
|
}
|
|
1138
|
+
if (this.hasActiveAdditivePlayback()) {
|
|
1139
|
+
this.host.reapplyProceduralState?.();
|
|
1140
|
+
}
|
|
905
1141
|
}
|
|
906
1142
|
dispose() {
|
|
907
1143
|
this.stopAllAnimations();
|
|
1144
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
908
1145
|
if (this.animationMixer) {
|
|
909
1146
|
this.animationMixer.stopAllAction();
|
|
910
1147
|
this.animationMixer = null;
|
|
911
1148
|
}
|
|
1149
|
+
if (this.clipAnimationMixer) {
|
|
1150
|
+
this.clipAnimationMixer.stopAllAction();
|
|
1151
|
+
this.clipAnimationMixer = null;
|
|
1152
|
+
}
|
|
1153
|
+
this.mixerFinishedListenerAttached = false;
|
|
1154
|
+
this.clipMixerFinishedListenerAttached = false;
|
|
912
1155
|
this.animationClips = [];
|
|
913
1156
|
this.bakedSourceClips.clear();
|
|
914
1157
|
this.bakedRuntimeActions.clear();
|
|
1158
|
+
this.bakedAdditiveRuntimeClips.clear();
|
|
915
1159
|
this.bakedActionGroups.clear();
|
|
916
1160
|
this.bakedRuntimeClipToSource.clear();
|
|
917
1161
|
this.animationActions.clear();
|
|
@@ -934,6 +1178,8 @@ var BakedAnimationController = class {
|
|
|
934
1178
|
if (this.animationMixer) {
|
|
935
1179
|
for (const bakedClip of this.bakedSourceClips.values()) {
|
|
936
1180
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1181
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1182
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
937
1183
|
try {
|
|
938
1184
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
939
1185
|
} catch {
|
|
@@ -945,6 +1191,7 @@ var BakedAnimationController = class {
|
|
|
945
1191
|
}
|
|
946
1192
|
}
|
|
947
1193
|
}
|
|
1194
|
+
this.clearAllBakedAdditiveRuntimeClips();
|
|
948
1195
|
for (const clipName of this.bakedSourceClips.keys()) {
|
|
949
1196
|
this.playbackState.delete(clipName);
|
|
950
1197
|
this.clipSources.delete(clipName);
|
|
@@ -985,6 +1232,8 @@ var BakedAnimationController = class {
|
|
|
985
1232
|
if (this.animationMixer) {
|
|
986
1233
|
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
987
1234
|
const action = this.bakedRuntimeActions.get(runtimeClip.clip.name);
|
|
1235
|
+
this.releaseBakedRuntimeAction(runtimeClip.clip.name);
|
|
1236
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
988
1237
|
try {
|
|
989
1238
|
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
990
1239
|
} catch {
|
|
@@ -993,7 +1242,6 @@ var BakedAnimationController = class {
|
|
|
993
1242
|
this.animationMixer.uncacheClip(runtimeClip.clip);
|
|
994
1243
|
} catch {
|
|
995
1244
|
}
|
|
996
|
-
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
997
1245
|
this.bakedRuntimeClipToSource.delete(runtimeClip.clip.name);
|
|
998
1246
|
const actionId = this.getActionId(action);
|
|
999
1247
|
if (actionId && action) {
|
|
@@ -1002,6 +1250,10 @@ var BakedAnimationController = class {
|
|
|
1002
1250
|
}
|
|
1003
1251
|
}
|
|
1004
1252
|
}
|
|
1253
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
1254
|
+
this.clearBakedAdditiveRuntimeClip(runtimeClip.clip.name);
|
|
1255
|
+
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
1256
|
+
}
|
|
1005
1257
|
this.animationClips = this.animationClips.filter((entry) => entry.name !== clipName);
|
|
1006
1258
|
this.bakedSourceClips.delete(clipName);
|
|
1007
1259
|
this.bakedActionGroups.delete(clipName);
|
|
@@ -1062,12 +1314,12 @@ var BakedAnimationController = class {
|
|
|
1062
1314
|
const actionId = this.getActionId(action);
|
|
1063
1315
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
1064
1316
|
action.stop();
|
|
1065
|
-
if (!isBaked && this.
|
|
1317
|
+
if (!isBaked && this.clipAnimationMixer) {
|
|
1066
1318
|
try {
|
|
1067
1319
|
const clip = action.getClip();
|
|
1068
1320
|
if (clip) {
|
|
1069
|
-
this.
|
|
1070
|
-
this.
|
|
1321
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1322
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1071
1323
|
}
|
|
1072
1324
|
} catch {
|
|
1073
1325
|
}
|
|
@@ -1089,11 +1341,11 @@ var BakedAnimationController = class {
|
|
|
1089
1341
|
const actionId = this.getActionId(clipAction);
|
|
1090
1342
|
try {
|
|
1091
1343
|
clipAction.stop();
|
|
1092
|
-
if (this.
|
|
1344
|
+
if (this.clipAnimationMixer) {
|
|
1093
1345
|
const clip = clipAction.getClip();
|
|
1094
1346
|
if (clip) {
|
|
1095
|
-
this.
|
|
1096
|
-
this.
|
|
1347
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
1348
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1097
1349
|
}
|
|
1098
1350
|
}
|
|
1099
1351
|
} catch {
|
|
@@ -1297,8 +1549,23 @@ var BakedAnimationController = class {
|
|
|
1297
1549
|
next.blendMode = this.getBakedAggregateBlendMode(clipName, next);
|
|
1298
1550
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1299
1551
|
if (bakedGroup) {
|
|
1300
|
-
for (const [channel,
|
|
1552
|
+
for (const [channel, currentAction] of Array.from(bakedGroup.channelActions)) {
|
|
1553
|
+
const channelBlendMode = resolveBakedChannelBlendMode(channel, next.requestedBlendMode) ?? "replace";
|
|
1554
|
+
const previousTime = currentAction.time;
|
|
1555
|
+
const wasActive = currentAction.isRunning() || currentAction.paused;
|
|
1556
|
+
const wasPaused = currentAction.paused;
|
|
1557
|
+
const action2 = this.getOrCreateBakedRuntimeAction(clipName, channel, channelBlendMode);
|
|
1558
|
+
if (!action2) {
|
|
1559
|
+
bakedGroup.channelActions.delete(channel);
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1301
1562
|
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
1563
|
+
action2.time = Math.max(0, Math.min(action2.getClip().duration, previousTime));
|
|
1564
|
+
if (action2 !== currentAction && wasActive) {
|
|
1565
|
+
action2.play();
|
|
1566
|
+
}
|
|
1567
|
+
action2.paused = wasPaused;
|
|
1568
|
+
bakedGroup.channelActions.set(channel, action2);
|
|
1302
1569
|
}
|
|
1303
1570
|
}
|
|
1304
1571
|
this.setPlaybackState(clipName, next);
|
|
@@ -1313,6 +1580,10 @@ var BakedAnimationController = class {
|
|
|
1313
1580
|
seekAnimation(clipName, time) {
|
|
1314
1581
|
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
1315
1582
|
if (bakedGroup) {
|
|
1583
|
+
const state = this.getPlaybackStateSnapshot(clipName, {
|
|
1584
|
+
loop: true,
|
|
1585
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
1586
|
+
});
|
|
1316
1587
|
const duration2 = this.getBakedSourceClip(clipName)?.sourceClip.duration ?? 0;
|
|
1317
1588
|
const clamped = Math.max(0, Math.min(duration2, Number.isFinite(time) ? time : 0));
|
|
1318
1589
|
for (const action2 of bakedGroup.channelActions.values()) {
|
|
@@ -1320,6 +1591,10 @@ var BakedAnimationController = class {
|
|
|
1320
1591
|
}
|
|
1321
1592
|
try {
|
|
1322
1593
|
this.animationMixer?.update(0);
|
|
1594
|
+
this.clipAnimationMixer?.update(0);
|
|
1595
|
+
if (state.blendMode === "additive") {
|
|
1596
|
+
this.host.reapplyProceduralState?.();
|
|
1597
|
+
}
|
|
1323
1598
|
} catch {
|
|
1324
1599
|
}
|
|
1325
1600
|
return;
|
|
@@ -1329,7 +1604,11 @@ var BakedAnimationController = class {
|
|
|
1329
1604
|
const duration = action.getClip().duration;
|
|
1330
1605
|
action.time = Math.max(0, Math.min(duration, Number.isFinite(time) ? time : 0));
|
|
1331
1606
|
try {
|
|
1332
|
-
this.
|
|
1607
|
+
this.clipAnimationMixer?.update(0);
|
|
1608
|
+
const state = this.playbackState.get(clipName);
|
|
1609
|
+
if (state?.blendMode === "additive") {
|
|
1610
|
+
this.host.reapplyProceduralState?.();
|
|
1611
|
+
}
|
|
1333
1612
|
} catch {
|
|
1334
1613
|
}
|
|
1335
1614
|
}
|
|
@@ -1337,6 +1616,9 @@ var BakedAnimationController = class {
|
|
|
1337
1616
|
if (this.animationMixer) {
|
|
1338
1617
|
this.animationMixer.timeScale = timeScale;
|
|
1339
1618
|
}
|
|
1619
|
+
if (this.clipAnimationMixer) {
|
|
1620
|
+
this.clipAnimationMixer.timeScale = timeScale;
|
|
1621
|
+
}
|
|
1340
1622
|
}
|
|
1341
1623
|
getAnimationState(clipName) {
|
|
1342
1624
|
const bakedClip = this.getBakedSourceClip(clipName);
|
|
@@ -1452,12 +1734,13 @@ var BakedAnimationController = class {
|
|
|
1452
1734
|
const globalBalance = options?.balance ?? 0;
|
|
1453
1735
|
const balanceMap = options?.balanceMap;
|
|
1454
1736
|
const meshNames = options?.meshNames;
|
|
1737
|
+
const visemeSlotCount = getProfileVisemeSlots(config).length;
|
|
1455
1738
|
let maxTime = 0;
|
|
1456
1739
|
const isNumericAU = (id) => /^\d+$/.test(id);
|
|
1457
1740
|
const isVisemeIndex = (id) => {
|
|
1458
1741
|
if (options?.snippetCategory !== "visemeSnippet") return false;
|
|
1459
1742
|
const num = Number(id);
|
|
1460
|
-
return !Number.isNaN(num) && num >= 0 && num <
|
|
1743
|
+
return !Number.isNaN(num) && num >= 0 && num < visemeSlotCount;
|
|
1461
1744
|
};
|
|
1462
1745
|
const sampleAt = (arr, t) => {
|
|
1463
1746
|
if (!arr.length) return 0;
|
|
@@ -1495,11 +1778,13 @@ var BakedAnimationController = class {
|
|
|
1495
1778
|
const auId = Number(curveId);
|
|
1496
1779
|
if (isVisemeIndex(curveId)) {
|
|
1497
1780
|
const visemeMeshNames = this.getMeshNamesForViseme(config, meshNames);
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1781
|
+
for (const target of getVisemeBindingTargets(config, auId)) {
|
|
1782
|
+
const effectiveScale = intensityScale * target.weight;
|
|
1783
|
+
if (typeof target.morph === "number") {
|
|
1784
|
+
this.addMorphIndexTracks(tracks, target.morph, keyframes, effectiveScale, visemeMeshNames);
|
|
1785
|
+
} else if (target.morph) {
|
|
1786
|
+
this.addMorphTracks(tracks, target.morph, keyframes, effectiveScale, visemeMeshNames);
|
|
1787
|
+
}
|
|
1503
1788
|
}
|
|
1504
1789
|
} else {
|
|
1505
1790
|
const auMeshNames = this.getMeshNamesForAU(auId, config, meshNames);
|
|
@@ -1542,7 +1827,7 @@ var BakedAnimationController = class {
|
|
|
1542
1827
|
}
|
|
1543
1828
|
const autoVisemeJaw = options?.autoVisemeJaw !== false;
|
|
1544
1829
|
const jawScale = options?.jawScale ?? 1;
|
|
1545
|
-
const visemeJawAmounts = config
|
|
1830
|
+
const visemeJawAmounts = getVisemeJawAmounts(config);
|
|
1546
1831
|
if (autoVisemeJaw && jawScale > 0 && visemeJawAmounts && options?.snippetCategory === "visemeSnippet" && keyframeTimes.length > 0) {
|
|
1547
1832
|
const bones = this.host.getBones();
|
|
1548
1833
|
const jawEntry = bones["JAW"];
|
|
@@ -1550,7 +1835,7 @@ var BakedAnimationController = class {
|
|
|
1550
1835
|
const jawValues = [];
|
|
1551
1836
|
for (const t of keyframeTimes) {
|
|
1552
1837
|
let jawAmount = 0;
|
|
1553
|
-
for (let visemeIdx = 0; visemeIdx <
|
|
1838
|
+
for (let visemeIdx = 0; visemeIdx < visemeSlotCount; visemeIdx++) {
|
|
1554
1839
|
const visemeCurve = curves[String(visemeIdx)];
|
|
1555
1840
|
if (!visemeCurve) continue;
|
|
1556
1841
|
const visemeValue = clampIntensity(sampleAt(visemeCurve, t) * intensityScale);
|
|
@@ -1666,8 +1951,8 @@ var BakedAnimationController = class {
|
|
|
1666
1951
|
return clip;
|
|
1667
1952
|
}
|
|
1668
1953
|
playClip(clip, options) {
|
|
1669
|
-
this.
|
|
1670
|
-
if (!
|
|
1954
|
+
const mixer = this.ensureClipMixer();
|
|
1955
|
+
if (!mixer) {
|
|
1671
1956
|
console.warn("[Loom3] playClip: No model loaded, cannot create mixer");
|
|
1672
1957
|
return null;
|
|
1673
1958
|
}
|
|
@@ -1685,7 +1970,7 @@ var BakedAnimationController = class {
|
|
|
1685
1970
|
actionId = this.setActionId(action, clip.name);
|
|
1686
1971
|
}
|
|
1687
1972
|
if (!action) {
|
|
1688
|
-
action =
|
|
1973
|
+
action = mixer.clipAction(clip);
|
|
1689
1974
|
actionId = this.setActionId(action, clip.name);
|
|
1690
1975
|
}
|
|
1691
1976
|
const existingClip = this.animationClips.find((c) => c.name === clip.name);
|
|
@@ -1746,15 +2031,13 @@ var BakedAnimationController = class {
|
|
|
1746
2031
|
},
|
|
1747
2032
|
stop: () => {
|
|
1748
2033
|
action.stop();
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
} catch {
|
|
1757
|
-
}
|
|
2034
|
+
try {
|
|
2035
|
+
mixer.uncacheAction(clip);
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
try {
|
|
2039
|
+
mixer.uncacheClip(clip);
|
|
2040
|
+
} catch {
|
|
1758
2041
|
}
|
|
1759
2042
|
this.clipActions.delete(clip.name);
|
|
1760
2043
|
this.animationActions.delete(clip.name);
|
|
@@ -1795,7 +2078,7 @@ var BakedAnimationController = class {
|
|
|
1795
2078
|
const clamped = Math.max(0, Math.min(clip.duration, t));
|
|
1796
2079
|
action.time = clamped;
|
|
1797
2080
|
try {
|
|
1798
|
-
|
|
2081
|
+
mixer.update(0);
|
|
1799
2082
|
} catch {
|
|
1800
2083
|
}
|
|
1801
2084
|
this.syncClipMonitorTime(monitor, clamped, true);
|
|
@@ -1828,16 +2111,16 @@ var BakedAnimationController = class {
|
|
|
1828
2111
|
return this.playClip(clip, { ...options, source: options?.source ?? "clip" });
|
|
1829
2112
|
}
|
|
1830
2113
|
cleanupSnippet(name) {
|
|
1831
|
-
if (!this.
|
|
2114
|
+
if (!this.host.getModel()) return;
|
|
1832
2115
|
for (const [clipName, action] of Array.from(this.clipActions.entries())) {
|
|
1833
2116
|
if (clipName === name || clipName.startsWith(`${name}_`)) {
|
|
1834
2117
|
const actionId = this.getActionId(action);
|
|
1835
2118
|
try {
|
|
1836
2119
|
action.stop();
|
|
1837
2120
|
const clip = action.getClip();
|
|
1838
|
-
if (clip) {
|
|
1839
|
-
this.
|
|
1840
|
-
this.
|
|
2121
|
+
if (clip && this.clipAnimationMixer) {
|
|
2122
|
+
this.clipAnimationMixer.uncacheAction(clip);
|
|
2123
|
+
this.clipAnimationMixer.uncacheClip(clip);
|
|
1841
2124
|
}
|
|
1842
2125
|
} catch {
|
|
1843
2126
|
}
|
|
@@ -1865,7 +2148,10 @@ var BakedAnimationController = class {
|
|
|
1865
2148
|
clipActions: Array.from(this.clipActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1866
2149
|
animationActions: Array.from(this.animationActions.entries()).map(([k, a]) => ({ name: k, actionId: this.getActionId(a) })),
|
|
1867
2150
|
clipHandles: Array.from(this.clipHandles.entries()).map(([k, h]) => ({ name: k, actionId: h.actionId })),
|
|
1868
|
-
mixerActions:
|
|
2151
|
+
mixerActions: [
|
|
2152
|
+
...this.animationMixer?._actions || [],
|
|
2153
|
+
...this.clipAnimationMixer?._actions || []
|
|
2154
|
+
].map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.getActionId(a) }))
|
|
1869
2155
|
});
|
|
1870
2156
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
1871
2157
|
const apply = (action) => {
|
|
@@ -1979,35 +2265,48 @@ var BakedAnimationController = class {
|
|
|
1979
2265
|
this.animationMixer = new AnimationMixer(model);
|
|
1980
2266
|
}
|
|
1981
2267
|
if (this.animationMixer && !this.mixerFinishedListenerAttached) {
|
|
1982
|
-
this.animationMixer.addEventListener("finished", (event) =>
|
|
1983
|
-
const action = event.action;
|
|
1984
|
-
const actionId = this.getActionId(action);
|
|
1985
|
-
if (actionId) {
|
|
1986
|
-
const monitor = this.clipMonitors.get(actionId);
|
|
1987
|
-
if (monitor) {
|
|
1988
|
-
monitor.finishedPending = true;
|
|
1989
|
-
return;
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
const clip = action.getClip();
|
|
1993
|
-
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
1994
|
-
if (bakedRuntime) {
|
|
1995
|
-
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
1996
|
-
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
1997
|
-
group.resolveFinished();
|
|
1998
|
-
}
|
|
1999
|
-
return;
|
|
2000
|
-
}
|
|
2001
|
-
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
2002
|
-
if (callback) {
|
|
2003
|
-
callback();
|
|
2004
|
-
this.animationFinishedCallbacks.delete(clip.name);
|
|
2005
|
-
}
|
|
2006
|
-
});
|
|
2268
|
+
this.animationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
2007
2269
|
this.mixerFinishedListenerAttached = true;
|
|
2008
2270
|
}
|
|
2009
2271
|
return this.animationMixer;
|
|
2010
2272
|
}
|
|
2273
|
+
ensureClipMixer() {
|
|
2274
|
+
const model = this.host.getModel();
|
|
2275
|
+
if (!model) return null;
|
|
2276
|
+
if (!this.clipAnimationMixer) {
|
|
2277
|
+
this.clipAnimationMixer = new AnimationMixer(model);
|
|
2278
|
+
}
|
|
2279
|
+
if (this.clipAnimationMixer && !this.clipMixerFinishedListenerAttached) {
|
|
2280
|
+
this.clipAnimationMixer.addEventListener("finished", (event) => this.handleMixerFinished(event));
|
|
2281
|
+
this.clipMixerFinishedListenerAttached = true;
|
|
2282
|
+
}
|
|
2283
|
+
return this.clipAnimationMixer;
|
|
2284
|
+
}
|
|
2285
|
+
handleMixerFinished(event) {
|
|
2286
|
+
const action = event.action;
|
|
2287
|
+
const actionId = this.getActionId(action);
|
|
2288
|
+
if (actionId) {
|
|
2289
|
+
const monitor = this.clipMonitors.get(actionId);
|
|
2290
|
+
if (monitor) {
|
|
2291
|
+
monitor.finishedPending = true;
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
const clip = action.getClip();
|
|
2296
|
+
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
2297
|
+
if (bakedRuntime) {
|
|
2298
|
+
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
2299
|
+
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
2300
|
+
group.resolveFinished();
|
|
2301
|
+
}
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
2305
|
+
if (callback) {
|
|
2306
|
+
callback();
|
|
2307
|
+
this.animationFinishedCallbacks.delete(clip.name);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2011
2310
|
createAnimationHandle(clipName, action, finishedPromise) {
|
|
2012
2311
|
return {
|
|
2013
2312
|
actionId: this.getActionId(action),
|
|
@@ -4971,7 +5270,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4971
5270
|
getCompositeRotations: () => this.compositeRotations,
|
|
4972
5271
|
computeSideValues: (base, balance) => this.computeSideValues(base, balance),
|
|
4973
5272
|
getAUMixWeight: (auId) => this.getAUMixWeight(auId),
|
|
4974
|
-
isMixedAU: (auId) => this.isMixedAU(auId)
|
|
5273
|
+
isMixedAU: (auId) => this.isMixedAU(auId),
|
|
5274
|
+
reapplyProceduralState: () => this.reapplyProceduralStateAfterBakedUpdate()
|
|
4975
5275
|
});
|
|
4976
5276
|
this.hairPhysics = new HairPhysicsController({
|
|
4977
5277
|
getMeshByName: (name) => this.meshByName.get(name),
|
|
@@ -5063,10 +5363,15 @@ var _Loom3 = class _Loom3 {
|
|
|
5063
5363
|
};
|
|
5064
5364
|
this.resolvedAUMorphTargets.set(auId, resolved);
|
|
5065
5365
|
}
|
|
5066
|
-
for (let i = 0; i < (this.config
|
|
5067
|
-
const key = this.config.visemeKeys[i];
|
|
5366
|
+
for (let i = 0; i < getProfileVisemeSlots(this.config).length; i += 1) {
|
|
5068
5367
|
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
5069
|
-
const targets =
|
|
5368
|
+
const targets = [];
|
|
5369
|
+
for (const bindingTarget of getVisemeBindingTargets(this.config, i)) {
|
|
5370
|
+
const resolved = typeof bindingTarget.morph === "number" ? this.resolveMorphTargetsByIndex(bindingTarget.morph, visemeMeshNames) : this.resolveMorphTargets(bindingTarget.morph, visemeMeshNames);
|
|
5371
|
+
for (const target of resolved) {
|
|
5372
|
+
targets.push({ ...target, weight: bindingTarget.weight });
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5070
5375
|
this.resolvedVisemeTargets[i] = targets;
|
|
5071
5376
|
}
|
|
5072
5377
|
}
|
|
@@ -5534,46 +5839,33 @@ var _Loom3 = class _Loom3 {
|
|
|
5534
5839
|
// VISEME CONTROL
|
|
5535
5840
|
// ============================================================================
|
|
5536
5841
|
setViseme(visemeIndex, value, jawScale = 1) {
|
|
5537
|
-
if (visemeIndex < 0 || visemeIndex >= this.
|
|
5842
|
+
if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) return;
|
|
5538
5843
|
const val = clamp012(value);
|
|
5539
5844
|
this.visemeValues[visemeIndex] = val;
|
|
5540
5845
|
this.visemeJawScales[visemeIndex] = jawScale;
|
|
5541
|
-
|
|
5542
|
-
if (targets && targets.length > 0) {
|
|
5543
|
-
this.applyMorphTargets(targets, val);
|
|
5544
|
-
} else {
|
|
5545
|
-
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
5546
|
-
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
5547
|
-
if (typeof morphKey === "number") {
|
|
5548
|
-
this.setMorphInfluence(morphKey, val, visemeMeshNames);
|
|
5549
|
-
} else if (typeof morphKey === "string") {
|
|
5550
|
-
this.setMorph(morphKey, val, visemeMeshNames);
|
|
5551
|
-
}
|
|
5552
|
-
}
|
|
5553
|
-
const jawAmount = this.getVisemeJawAmount(visemeIndex) * val * jawScale;
|
|
5554
|
-
if (Math.abs(jawScale) > 1e-6 && Math.abs(jawAmount) > 1e-6) {
|
|
5555
|
-
this.updateBoneRotation("JAW", "pitch", jawAmount);
|
|
5556
|
-
}
|
|
5846
|
+
this.applyVisemeRuntimeState();
|
|
5557
5847
|
}
|
|
5558
5848
|
transitionViseme(visemeIndex, to, durationMs = 80, jawScale = 1) {
|
|
5559
|
-
if (visemeIndex < 0 || visemeIndex >= this.
|
|
5849
|
+
if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) {
|
|
5560
5850
|
return { promise: Promise.resolve(), pause: () => {
|
|
5561
5851
|
}, resume: () => {
|
|
5562
5852
|
}, cancel: () => {
|
|
5563
5853
|
} };
|
|
5564
5854
|
}
|
|
5565
|
-
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
5566
5855
|
const target = clamp012(to);
|
|
5567
|
-
this.visemeValues[visemeIndex]
|
|
5856
|
+
const from = this.visemeValues[visemeIndex] ?? 0;
|
|
5568
5857
|
this.visemeJawScales[visemeIndex] = jawScale;
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5858
|
+
return this.animation.addTransition(
|
|
5859
|
+
`viseme_value_${visemeIndex}`,
|
|
5860
|
+
from,
|
|
5861
|
+
target,
|
|
5862
|
+
durationMs,
|
|
5863
|
+
(value) => {
|
|
5864
|
+
this.visemeValues[visemeIndex] = clamp012(value);
|
|
5865
|
+
this.visemeJawScales[visemeIndex] = jawScale;
|
|
5866
|
+
this.applyVisemeRuntimeState();
|
|
5867
|
+
}
|
|
5868
|
+
);
|
|
5577
5869
|
}
|
|
5578
5870
|
setVisemeById(slotId, value, jawScale = 1) {
|
|
5579
5871
|
const index = getVisemeSlotIndex(this.config, slotId);
|
|
@@ -5637,8 +5929,9 @@ var _Loom3 = class _Loom3 {
|
|
|
5637
5929
|
}
|
|
5638
5930
|
resetToNeutral() {
|
|
5639
5931
|
this.auValues = {};
|
|
5640
|
-
|
|
5641
|
-
this.
|
|
5932
|
+
const visemeCount = getProfileVisemeSlots(this.config).length;
|
|
5933
|
+
this.visemeValues = new Array(visemeCount).fill(0);
|
|
5934
|
+
this.visemeJawScales = new Array(visemeCount).fill(1);
|
|
5642
5935
|
this.translations = {};
|
|
5643
5936
|
this.initBoneRotations();
|
|
5644
5937
|
this.clearTransitions();
|
|
@@ -5672,15 +5965,35 @@ var _Loom3 = class _Loom3 {
|
|
|
5672
5965
|
if (Number.isNaN(auId)) continue;
|
|
5673
5966
|
this.setAU(auId, value, this.auBalances[auId]);
|
|
5674
5967
|
}
|
|
5968
|
+
this.applyVisemeRuntimeState();
|
|
5969
|
+
if (this.model) {
|
|
5970
|
+
this.flushPendingComposites();
|
|
5971
|
+
this.model.updateMatrixWorld(true);
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
reapplyProceduralStateAfterBakedUpdate() {
|
|
5975
|
+
if (!this.model) {
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
let hasActiveOverrides = false;
|
|
5979
|
+
for (const [auIdStr, value] of Object.entries(this.auValues)) {
|
|
5980
|
+
if (value <= 0) continue;
|
|
5981
|
+
const auId = Number(auIdStr);
|
|
5982
|
+
if (Number.isNaN(auId)) continue;
|
|
5983
|
+
hasActiveOverrides = true;
|
|
5984
|
+
this.setAU(auId, value, this.auBalances[auId]);
|
|
5985
|
+
}
|
|
5675
5986
|
for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
|
|
5676
5987
|
const value = this.visemeValues[visemeIndex] ?? 0;
|
|
5677
5988
|
if (value <= 0) continue;
|
|
5989
|
+
hasActiveOverrides = true;
|
|
5678
5990
|
this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
|
|
5679
5991
|
}
|
|
5680
|
-
if (
|
|
5681
|
-
|
|
5682
|
-
this.model.updateMatrixWorld(true);
|
|
5992
|
+
if (!hasActiveOverrides) {
|
|
5993
|
+
return;
|
|
5683
5994
|
}
|
|
5995
|
+
this.flushPendingComposites();
|
|
5996
|
+
this.model.updateMatrixWorld(true);
|
|
5684
5997
|
}
|
|
5685
5998
|
// ============================================================================
|
|
5686
5999
|
// MESH CONTROL
|
|
@@ -6010,6 +6323,37 @@ var _Loom3 = class _Loom3 {
|
|
|
6010
6323
|
target.infl[target.idx] = val;
|
|
6011
6324
|
}
|
|
6012
6325
|
}
|
|
6326
|
+
applyVisemeRuntimeState() {
|
|
6327
|
+
for (const targets of this.resolvedVisemeTargets) {
|
|
6328
|
+
for (const target of targets || []) {
|
|
6329
|
+
if (target.idx < target.infl.length) {
|
|
6330
|
+
target.infl[target.idx] = 0;
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
for (let index = 0; index < this.visemeValues.length; index += 1) {
|
|
6335
|
+
const value = clamp012(this.visemeValues[index] ?? 0);
|
|
6336
|
+
if (value <= 1e-6) continue;
|
|
6337
|
+
const targets = this.resolvedVisemeTargets[index] || [];
|
|
6338
|
+
for (const target of targets) {
|
|
6339
|
+
if (target.idx >= target.infl.length) continue;
|
|
6340
|
+
const weighted = clamp012(value * target.weight);
|
|
6341
|
+
target.infl[target.idx] = Math.max(target.infl[target.idx] ?? 0, weighted);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
this.updateBoneRotation("JAW", "pitch", this.getActiveVisemeJawAmount());
|
|
6345
|
+
}
|
|
6346
|
+
getActiveVisemeJawAmount() {
|
|
6347
|
+
let jawAmount = 0;
|
|
6348
|
+
for (let index = 0; index < this.visemeValues.length; index += 1) {
|
|
6349
|
+
const value = clamp012(this.visemeValues[index] ?? 0);
|
|
6350
|
+
if (value <= 1e-6) continue;
|
|
6351
|
+
const jawScale = this.visemeJawScales[index] ?? 1;
|
|
6352
|
+
if (Math.abs(jawScale) <= 1e-6) continue;
|
|
6353
|
+
jawAmount = Math.max(jawAmount, this.getVisemeJawAmount(index) * value * jawScale);
|
|
6354
|
+
}
|
|
6355
|
+
return jawAmount;
|
|
6356
|
+
}
|
|
6013
6357
|
getMorphValue(key) {
|
|
6014
6358
|
if (this.faceMesh) {
|
|
6015
6359
|
const dict = this.faceMesh.morphTargetDictionary;
|
|
@@ -6195,7 +6539,7 @@ var _Loom3 = class _Loom3 {
|
|
|
6195
6539
|
return meshNames?.length ? `idx:${index}@${meshNames.join(",")}` : `idx:${index}`;
|
|
6196
6540
|
}
|
|
6197
6541
|
syncVisemeRuntimeState() {
|
|
6198
|
-
const visemeCount = this.config.
|
|
6542
|
+
const visemeCount = getProfileVisemeSlots(this.config).length;
|
|
6199
6543
|
this.visemeValues = Array.from(
|
|
6200
6544
|
{ length: visemeCount },
|
|
6201
6545
|
(_, index) => this.visemeValues[index] ?? 0
|
|
@@ -8268,6 +8612,6 @@ async function analyzeModel(options) {
|
|
|
8268
8612
|
};
|
|
8269
8613
|
}
|
|
8270
8614
|
|
|
8271
|
-
export { AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MAPPING_SECTIONS, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, CC4_VISEME_SLOTS, CC4_VISEME_SYSTEM_ID, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, FISH_AU_MAPPING_CONFIG, HairPhysics, Loom3, Loom3 as Loom3Three, Loom3 as LoomLargeThree, MORPH_TO_MESH, VISEME_JAW_AMOUNTS, VISEME_KEYS, analyzeModel, applyCharacterProfileToPreset, buildMappingEditorModel, collectMorphMeshes, compileVisemeKeys, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getMeshNamesForAUProfile, getMeshNamesForVisemeProfile, getModelForwardDirection, getPreset, getPresetWithProfile, getProfileVisemeSlots, getVisemeJawAmounts, getVisemeSlotIndex, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mapProviderVisemeToSlot, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, resolveVisemeMeshCategory, suggestBestPreset, validateMappingConfig, validateMappings };
|
|
8615
|
+
export { AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MAPPING_SECTIONS, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, CC4_VISEME_SLOTS, CC4_VISEME_SYSTEM_ID, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, DEFAULT_HAIR_PHYSICS_CONFIG, FISH_AU_MAPPING_CONFIG, HairPhysics, Loom3, Loom3 as Loom3Three, Loom3 as LoomLargeThree, MORPH_TO_MESH, VISEME_JAW_AMOUNTS, VISEME_KEYS, analyzeModel, applyCharacterProfileToPreset, buildMappingEditorModel, collectMorphMeshes, compileVisemeKeys, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getMeshNamesForAUProfile, getMeshNamesForVisemeProfile, getModelForwardDirection, getPreset, getPresetWithProfile, getProfileVisemeSlots, getVisemeBindingTargets, getVisemeJawAmounts, getVisemeSlotIndex, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mapProviderVisemeToSlot, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, resolveVisemeMeshCategory, suggestBestPreset, validateMappingConfig, validateMappings };
|
|
8272
8616
|
//# sourceMappingURL=index.js.map
|
|
8273
8617
|
//# sourceMappingURL=index.js.map
|