@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as THREE2 from 'three';
2
- import { Vector3, Clock, Box3, BufferAttribute, Quaternion, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopPingPong, LoopOnce, LoopRepeat, QuaternionKeyframeTrack, NumberKeyframeTrack, AnimationClip, AnimationMixer, Mesh, PropertyBinding } from 'three';
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
- const action = this.animationMixer.clipAction(runtimeClip);
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 action = this.getOrCreateBakedRuntimeAction(clipName, runtimeClip.channel);
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.animationMixer.update(dtSeconds);
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.animationMixer) {
1317
+ if (!isBaked && this.clipAnimationMixer) {
1066
1318
  try {
1067
1319
  const clip = action.getClip();
1068
1320
  if (clip) {
1069
- this.animationMixer.uncacheAction(clip);
1070
- this.animationMixer.uncacheClip(clip);
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.animationMixer) {
1344
+ if (this.clipAnimationMixer) {
1093
1345
  const clip = clipAction.getClip();
1094
1346
  if (clip) {
1095
- this.animationMixer.uncacheAction(clip);
1096
- this.animationMixer.uncacheClip(clip);
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, action2] of bakedGroup.channelActions) {
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.animationMixer?.update(0);
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 < config.visemeKeys.length;
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 visemeKey = config.visemeKeys[auId];
1499
- if (typeof visemeKey === "number") {
1500
- this.addMorphIndexTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
1501
- } else if (visemeKey) {
1502
- this.addMorphTracks(tracks, visemeKey, keyframes, intensityScale, visemeMeshNames);
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.visemeJawAmounts;
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 < config.visemeKeys.length; 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.ensureMixer();
1670
- if (!this.animationMixer) {
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 = this.animationMixer.clipAction(clip);
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
- if (this.animationMixer) {
1750
- try {
1751
- this.animationMixer.uncacheAction(clip);
1752
- } catch {
1753
- }
1754
- try {
1755
- this.animationMixer.uncacheClip(clip);
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
- this.animationMixer?.update(0);
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.animationMixer || !this.host.getModel()) return;
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.animationMixer.uncacheAction(clip);
1840
- this.animationMixer.uncacheClip(clip);
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: (this.animationMixer?._actions || []).map((a) => ({ name: a?.getClip?.()?.name || "", actionId: this.getActionId(a) }))
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.visemeKeys || []).length; i += 1) {
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 = typeof key === "number" ? this.resolveMorphTargetsByIndex(key, visemeMeshNames) : this.resolveMorphTargets(key, visemeMeshNames);
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.config.visemeKeys.length) return;
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
- const targets = this.resolvedVisemeTargets[visemeIndex];
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.config.visemeKeys.length) {
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] = target;
5856
+ const from = this.visemeValues[visemeIndex] ?? 0;
5568
5857
  this.visemeJawScales[visemeIndex] = jawScale;
5569
- const visemeMeshNames = this.getMeshNamesForViseme();
5570
- const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
5571
- const jawAmount = this.getVisemeJawAmount(visemeIndex) * target * jawScale;
5572
- if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
5573
- return morphHandle;
5574
- }
5575
- const jawHandle = this.transitionBoneRotation("JAW", "pitch", jawAmount, durationMs);
5576
- return this.combineHandles([morphHandle, jawHandle]);
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
- this.visemeValues = new Array(this.config.visemeKeys.length).fill(0);
5641
- this.visemeJawScales = new Array(this.config.visemeKeys.length).fill(1);
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 (this.model) {
5681
- this.flushPendingComposites();
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.visemeKeys.length;
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