@lovelace_lol/loom3 1.0.34 → 1.0.36
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 +584 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +585 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -79,6 +79,119 @@ function resolveCurveBalance(curveId, globalBalance, balanceMap) {
|
|
|
79
79
|
}
|
|
80
80
|
return clampBalance(globalBalance);
|
|
81
81
|
}
|
|
82
|
+
var RUNTIME_CLIP_PREFIX = "__loom3_baked_partition__/";
|
|
83
|
+
var FACE_SAFE_TARGET_RE = /(head|neck|jaw|eye|brow|lid|mouth|lip|face|cheek|nose|tongue|teeth)/i;
|
|
84
|
+
var BODY_LIKE_TARGET_RE = /(root|armature|hips?|pelvis|spine|waist|chest|torso|shoulder|arm|forearm|hand|finger|leg|thigh|calf|knee|foot|toe|tail|wing|fin|body|abdomen|clavicle)/i;
|
|
85
|
+
var SCENE_LIKE_TARGET_RE = /(camera|cam|scene|world|global|origin|pivot|cube)/i;
|
|
86
|
+
var CHANNEL_ORDER = ["face", "body", "scene"];
|
|
87
|
+
function getRuntimeClipName(sourceClipName, channel) {
|
|
88
|
+
return `${RUNTIME_CLIP_PREFIX}${sourceClipName}/${channel}`;
|
|
89
|
+
}
|
|
90
|
+
function parseTrackTarget(trackName, model) {
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = THREE.PropertyBinding.parseTrackName(trackName);
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const targetKey = parsed.objectName === "bones" && parsed.objectIndex ? String(parsed.objectIndex) : parsed.nodeName;
|
|
98
|
+
const target = targetKey ? model.getObjectByProperty("uuid", targetKey) ?? THREE.PropertyBinding.findNode(model, targetKey) : null;
|
|
99
|
+
return {
|
|
100
|
+
propertyName: parsed.propertyName,
|
|
101
|
+
target,
|
|
102
|
+
targetName: target?.name ?? parsed.nodeName ?? ""
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function isSceneTrackTarget(target, targetName) {
|
|
106
|
+
if (!target) return true;
|
|
107
|
+
if (target.isCamera) return true;
|
|
108
|
+
return SCENE_LIKE_TARGET_RE.test(targetName);
|
|
109
|
+
}
|
|
110
|
+
function isFaceSafeTransformTarget(target, targetName, safeTransformTargets) {
|
|
111
|
+
if (target && safeTransformTargets.has(target)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (!targetName) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (BODY_LIKE_TARGET_RE.test(targetName) || SCENE_LIKE_TARGET_RE.test(targetName)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return FACE_SAFE_TARGET_RE.test(targetName);
|
|
121
|
+
}
|
|
122
|
+
function classifyBakedTrack(track, model, bones) {
|
|
123
|
+
const parsed = parseTrackTarget(track.name, model);
|
|
124
|
+
if (!parsed) {
|
|
125
|
+
return "scene";
|
|
126
|
+
}
|
|
127
|
+
if (parsed.propertyName === "morphTargetInfluences" || parsed.propertyName === "weights") {
|
|
128
|
+
return "face";
|
|
129
|
+
}
|
|
130
|
+
if (isSceneTrackTarget(parsed.target, parsed.targetName)) {
|
|
131
|
+
return "scene";
|
|
132
|
+
}
|
|
133
|
+
if (parsed.propertyName === "quaternion") {
|
|
134
|
+
const safeTransformTargets = new Set(
|
|
135
|
+
Object.values(bones).map((entry) => entry?.obj).filter((entry) => !!entry)
|
|
136
|
+
);
|
|
137
|
+
if (isFaceSafeTransformTarget(parsed.target, parsed.targetName, safeTransformTargets)) {
|
|
138
|
+
return "face";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return "body";
|
|
142
|
+
}
|
|
143
|
+
function resolveBakedChannelBlendMode(channel, requestedBlendMode) {
|
|
144
|
+
if (channel === "face") {
|
|
145
|
+
return requestedBlendMode === "additive" ? "additive" : "replace";
|
|
146
|
+
}
|
|
147
|
+
if (channel === "body") {
|
|
148
|
+
return "replace";
|
|
149
|
+
}
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
function resolveBakedAggregateBlendMode(channels, requestedBlendMode) {
|
|
153
|
+
if (requestedBlendMode !== "additive") {
|
|
154
|
+
return "replace";
|
|
155
|
+
}
|
|
156
|
+
return channels.some((channel) => channel.channel === "face" && channel.playable && channel.trackCount > 0) ? "additive" : "replace";
|
|
157
|
+
}
|
|
158
|
+
function partitionBakedClip(clip, model, bones) {
|
|
159
|
+
const tracksByChannel = new Map(
|
|
160
|
+
CHANNEL_ORDER.map((channel) => [channel, []])
|
|
161
|
+
);
|
|
162
|
+
for (const track of clip.tracks) {
|
|
163
|
+
const channel = classifyBakedTrack(track, model, bones);
|
|
164
|
+
tracksByChannel.get(channel)?.push(track.clone());
|
|
165
|
+
}
|
|
166
|
+
const runtimeClips = [];
|
|
167
|
+
const channels = [];
|
|
168
|
+
for (const channel of CHANNEL_ORDER) {
|
|
169
|
+
const tracks = tracksByChannel.get(channel) ?? [];
|
|
170
|
+
if (tracks.length === 0) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const playable = channel !== "scene";
|
|
174
|
+
const blendMode = resolveBakedChannelBlendMode(channel, "additive");
|
|
175
|
+
channels.push({
|
|
176
|
+
channel,
|
|
177
|
+
trackCount: tracks.length,
|
|
178
|
+
playable,
|
|
179
|
+
blendMode
|
|
180
|
+
});
|
|
181
|
+
if (!playable) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
runtimeClips.push({
|
|
185
|
+
channel,
|
|
186
|
+
clip: new THREE.AnimationClip(getRuntimeClipName(clip.name, channel), clip.duration, tracks)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
sourceClip: clip,
|
|
191
|
+
channels,
|
|
192
|
+
runtimeClips
|
|
193
|
+
};
|
|
194
|
+
}
|
|
82
195
|
|
|
83
196
|
// src/engines/three/AnimationThree.ts
|
|
84
197
|
var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
@@ -184,6 +297,10 @@ var BakedAnimationController = class {
|
|
|
184
297
|
__publicField(this, "animationMixer", null);
|
|
185
298
|
__publicField(this, "mixerFinishedListenerAttached", false);
|
|
186
299
|
__publicField(this, "animationClips", []);
|
|
300
|
+
__publicField(this, "bakedSourceClips", /* @__PURE__ */ new Map());
|
|
301
|
+
__publicField(this, "bakedRuntimeActions", /* @__PURE__ */ new Map());
|
|
302
|
+
__publicField(this, "bakedActionGroups", /* @__PURE__ */ new Map());
|
|
303
|
+
__publicField(this, "bakedRuntimeClipToSource", /* @__PURE__ */ new Map());
|
|
187
304
|
__publicField(this, "animationActions", /* @__PURE__ */ new Map());
|
|
188
305
|
__publicField(this, "animationFinishedCallbacks", /* @__PURE__ */ new Map());
|
|
189
306
|
__publicField(this, "clipActions", /* @__PURE__ */ new Map());
|
|
@@ -212,6 +329,7 @@ var BakedAnimationController = class {
|
|
|
212
329
|
const rawWeight = options?.weight ?? options?.intensity ?? clipOptions?.mixerWeight ?? 1;
|
|
213
330
|
const weight = Number.isFinite(rawWeight) ? Math.max(0, rawWeight) : 1;
|
|
214
331
|
const loopMode = options?.loopMode ?? (typeof options?.loop === "boolean" ? options.loop ? "repeat" : "once" : defaults.loop ? "repeat" : "once");
|
|
332
|
+
const requestedBlendMode = options?.blendMode ?? (clipOptions?.mixerAdditive ? "additive" : "replace");
|
|
215
333
|
return {
|
|
216
334
|
source: options?.source ?? defaults.source,
|
|
217
335
|
loop: loopMode !== "once",
|
|
@@ -221,7 +339,8 @@ var BakedAnimationController = class {
|
|
|
221
339
|
playbackRate,
|
|
222
340
|
weight,
|
|
223
341
|
balance: Number.isFinite(options?.balance) ? options?.balance ?? 0 : 0,
|
|
224
|
-
|
|
342
|
+
requestedBlendMode,
|
|
343
|
+
blendMode: requestedBlendMode,
|
|
225
344
|
easing: options?.easing ?? "linear"
|
|
226
345
|
};
|
|
227
346
|
}
|
|
@@ -281,15 +400,49 @@ var BakedAnimationController = class {
|
|
|
281
400
|
next.balance = Math.max(-1, Math.min(1, options.balance));
|
|
282
401
|
}
|
|
283
402
|
if (options.blendMode) {
|
|
284
|
-
next.
|
|
403
|
+
next.requestedBlendMode = options.blendMode;
|
|
285
404
|
} else if (typeof clipOptions?.mixerAdditive === "boolean") {
|
|
286
|
-
next.
|
|
405
|
+
next.requestedBlendMode = clipOptions.mixerAdditive ? "additive" : "replace";
|
|
287
406
|
}
|
|
407
|
+
next.blendMode = next.requestedBlendMode;
|
|
288
408
|
if (options.easing) {
|
|
289
409
|
next.easing = options.easing;
|
|
290
410
|
}
|
|
291
411
|
return next;
|
|
292
412
|
}
|
|
413
|
+
isBakedSourceClip(clipName) {
|
|
414
|
+
return this.bakedSourceClips.has(clipName);
|
|
415
|
+
}
|
|
416
|
+
getBakedSourceClip(clipName) {
|
|
417
|
+
return this.bakedSourceClips.get(clipName);
|
|
418
|
+
}
|
|
419
|
+
getBakedChannelInfo(clipName, playbackState) {
|
|
420
|
+
const bakedClip = this.getBakedSourceClip(clipName);
|
|
421
|
+
if (!bakedClip) {
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
const requestedBlendMode = playbackState?.requestedBlendMode ?? "replace";
|
|
425
|
+
return bakedClip.channels.map((channel) => ({
|
|
426
|
+
...channel,
|
|
427
|
+
blendMode: resolveBakedChannelBlendMode(channel.channel, requestedBlendMode)
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
getBakedAggregateBlendMode(clipName, playbackState) {
|
|
431
|
+
const channels = this.getBakedChannelInfo(clipName, playbackState);
|
|
432
|
+
if (!channels) {
|
|
433
|
+
return playbackState?.requestedBlendMode ?? playbackState?.blendMode ?? "replace";
|
|
434
|
+
}
|
|
435
|
+
return resolveBakedAggregateBlendMode(
|
|
436
|
+
channels,
|
|
437
|
+
playbackState?.requestedBlendMode ?? "replace"
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
applyPlaybackStateToBakedAction(action, state, channel) {
|
|
441
|
+
this.applyPlaybackState(action, {
|
|
442
|
+
...state,
|
|
443
|
+
blendMode: resolveBakedChannelBlendMode(channel, state.requestedBlendMode) ?? "replace"
|
|
444
|
+
});
|
|
445
|
+
}
|
|
293
446
|
resolveStartTime(duration, state, explicitStartTime) {
|
|
294
447
|
if (typeof explicitStartTime === "number" && Number.isFinite(explicitStartTime)) {
|
|
295
448
|
return Math.max(0, Math.min(duration, explicitStartTime));
|
|
@@ -299,8 +452,13 @@ var BakedAnimationController = class {
|
|
|
299
452
|
}
|
|
300
453
|
return 0;
|
|
301
454
|
}
|
|
302
|
-
|
|
303
|
-
const
|
|
455
|
+
getOrCreateBakedRuntimeAction(sourceClipName, channel) {
|
|
456
|
+
const bakedClip = this.getBakedSourceClip(sourceClipName);
|
|
457
|
+
const runtimeClip = bakedClip?.runtimeClips.find((entry) => entry.channel === channel)?.clip;
|
|
458
|
+
if (!runtimeClip) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
const existing = this.bakedRuntimeActions.get(runtimeClip.name);
|
|
304
462
|
if (existing) {
|
|
305
463
|
return existing;
|
|
306
464
|
}
|
|
@@ -308,13 +466,44 @@ var BakedAnimationController = class {
|
|
|
308
466
|
if (!this.animationMixer) {
|
|
309
467
|
return null;
|
|
310
468
|
}
|
|
311
|
-
const
|
|
312
|
-
|
|
469
|
+
const action = this.animationMixer.clipAction(runtimeClip);
|
|
470
|
+
this.bakedRuntimeActions.set(runtimeClip.name, action);
|
|
471
|
+
return action;
|
|
472
|
+
}
|
|
473
|
+
getRepresentativeBakedAction(clipName) {
|
|
474
|
+
const group = this.bakedActionGroups.get(clipName);
|
|
475
|
+
if (!group) {
|
|
313
476
|
return null;
|
|
314
477
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
478
|
+
return group.channelActions.values().next().value ?? null;
|
|
479
|
+
}
|
|
480
|
+
createBakedActionGroup(clipName, playbackState) {
|
|
481
|
+
const bakedClip = this.getBakedSourceClip(clipName);
|
|
482
|
+
if (!bakedClip) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
const channelActions = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
487
|
+
const action = this.getOrCreateBakedRuntimeAction(clipName, runtimeClip.channel);
|
|
488
|
+
if (action) {
|
|
489
|
+
channelActions.set(runtimeClip.channel, action);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (channelActions.size === 0) {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
let resolveFinished = () => {
|
|
496
|
+
};
|
|
497
|
+
const finishedPromise = new Promise((resolve) => {
|
|
498
|
+
resolveFinished = resolve;
|
|
499
|
+
});
|
|
500
|
+
return {
|
|
501
|
+
actionId: makeActionId(),
|
|
502
|
+
channelActions,
|
|
503
|
+
pendingFinishedChannels: playbackState.loopMode === "once" ? new Set(channelActions.keys()) : /* @__PURE__ */ new Set(),
|
|
504
|
+
finishedPromise,
|
|
505
|
+
resolveFinished
|
|
506
|
+
};
|
|
318
507
|
}
|
|
319
508
|
getMeshNamesForAU(auId, config, explicitMeshNames) {
|
|
320
509
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
@@ -354,6 +543,10 @@ var BakedAnimationController = class {
|
|
|
354
543
|
this.animationMixer = null;
|
|
355
544
|
}
|
|
356
545
|
this.animationClips = [];
|
|
546
|
+
this.bakedSourceClips.clear();
|
|
547
|
+
this.bakedRuntimeActions.clear();
|
|
548
|
+
this.bakedActionGroups.clear();
|
|
549
|
+
this.bakedRuntimeClipToSource.clear();
|
|
357
550
|
this.animationActions.clear();
|
|
358
551
|
this.animationFinishedCallbacks.clear();
|
|
359
552
|
this.clipActions.clear();
|
|
@@ -362,17 +555,47 @@ var BakedAnimationController = class {
|
|
|
362
555
|
this.playbackState.clear();
|
|
363
556
|
}
|
|
364
557
|
loadAnimationClips(clips) {
|
|
365
|
-
|
|
558
|
+
const model = this.host.getModel();
|
|
559
|
+
if (!model) {
|
|
366
560
|
console.warn("Loom3: Cannot load animation clips before calling onReady()");
|
|
367
561
|
return;
|
|
368
562
|
}
|
|
563
|
+
for (const clipName of this.bakedSourceClips.keys()) {
|
|
564
|
+
this.stopAnimation(clipName);
|
|
565
|
+
}
|
|
566
|
+
if (this.animationMixer) {
|
|
567
|
+
for (const bakedClip of this.bakedSourceClips.values()) {
|
|
568
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
569
|
+
try {
|
|
570
|
+
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
this.animationMixer.uncacheClip(runtimeClip.clip);
|
|
575
|
+
} catch {
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const clipName of this.bakedSourceClips.keys()) {
|
|
581
|
+
this.playbackState.delete(clipName);
|
|
582
|
+
this.clipSources.delete(clipName);
|
|
583
|
+
}
|
|
584
|
+
this.bakedSourceClips.clear();
|
|
585
|
+
this.bakedRuntimeActions.clear();
|
|
586
|
+
this.bakedActionGroups.clear();
|
|
587
|
+
this.bakedRuntimeClipToSource.clear();
|
|
369
588
|
this.ensureMixer();
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
589
|
+
const partitionedClips = clips.map((clip) => partitionBakedClip(clip, model, this.host.getBones()));
|
|
590
|
+
this.animationClips = partitionedClips.map((clip) => clip.sourceClip);
|
|
591
|
+
for (const bakedClip of partitionedClips) {
|
|
592
|
+
this.bakedSourceClips.set(bakedClip.sourceClip.name, bakedClip);
|
|
593
|
+
this.clipSources.set(bakedClip.sourceClip.name, "baked");
|
|
594
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
595
|
+
this.bakedRuntimeClipToSource.set(runtimeClip.clip.name, {
|
|
596
|
+
sourceClipName: bakedClip.sourceClip.name,
|
|
597
|
+
channel: runtimeClip.channel
|
|
598
|
+
});
|
|
376
599
|
}
|
|
377
600
|
}
|
|
378
601
|
}
|
|
@@ -381,84 +604,91 @@ var BakedAnimationController = class {
|
|
|
381
604
|
name: clip.name,
|
|
382
605
|
duration: clip.duration,
|
|
383
606
|
trackCount: clip.tracks.length,
|
|
384
|
-
source: this.clipSources.get(clip.name) ?? "baked"
|
|
607
|
+
source: this.clipSources.get(clip.name) ?? "baked",
|
|
608
|
+
channels: this.getBakedSourceClip(clip.name)?.channels
|
|
385
609
|
}));
|
|
386
610
|
}
|
|
387
611
|
removeAnimationClip(clipName) {
|
|
388
|
-
const
|
|
389
|
-
if (!
|
|
612
|
+
const bakedClip = this.getBakedSourceClip(clipName);
|
|
613
|
+
if (!bakedClip) {
|
|
390
614
|
return false;
|
|
391
615
|
}
|
|
392
|
-
const relatedActions = /* @__PURE__ */ new Set();
|
|
393
|
-
const bakedAction = this.animationActions.get(clipName);
|
|
394
|
-
const clipAction = this.clipActions.get(clipName);
|
|
395
|
-
if (bakedAction) relatedActions.add(bakedAction);
|
|
396
|
-
if (clipAction) relatedActions.add(clipAction);
|
|
397
616
|
this.stopAnimation(clipName);
|
|
398
617
|
if (this.animationMixer) {
|
|
399
|
-
for (const
|
|
618
|
+
for (const runtimeClip of bakedClip.runtimeClips) {
|
|
619
|
+
const action = this.bakedRuntimeActions.get(runtimeClip.clip.name);
|
|
400
620
|
try {
|
|
401
|
-
this.animationMixer.uncacheAction(clip);
|
|
621
|
+
this.animationMixer.uncacheAction(runtimeClip.clip);
|
|
402
622
|
} catch {
|
|
403
623
|
}
|
|
404
624
|
try {
|
|
405
|
-
this.animationMixer.uncacheClip(clip);
|
|
625
|
+
this.animationMixer.uncacheClip(runtimeClip.clip);
|
|
406
626
|
} catch {
|
|
407
627
|
}
|
|
628
|
+
this.bakedRuntimeActions.delete(runtimeClip.clip.name);
|
|
629
|
+
this.bakedRuntimeClipToSource.delete(runtimeClip.clip.name);
|
|
408
630
|
const actionId = this.getActionId(action);
|
|
409
|
-
if (actionId) {
|
|
631
|
+
if (actionId && action) {
|
|
410
632
|
this.actionIdToClip.delete(actionId);
|
|
633
|
+
this.actionIds.delete(action);
|
|
411
634
|
}
|
|
412
|
-
this.actionIds.delete(action);
|
|
413
635
|
}
|
|
414
636
|
}
|
|
415
637
|
this.animationClips = this.animationClips.filter((entry) => entry.name !== clipName);
|
|
416
|
-
this.
|
|
417
|
-
this.
|
|
418
|
-
this.clipHandles.delete(clipName);
|
|
419
|
-
this.animationFinishedCallbacks.delete(clipName);
|
|
638
|
+
this.bakedSourceClips.delete(clipName);
|
|
639
|
+
this.bakedActionGroups.delete(clipName);
|
|
420
640
|
this.playbackState.delete(clipName);
|
|
421
641
|
this.clipSources.delete(clipName);
|
|
422
642
|
return true;
|
|
423
643
|
}
|
|
424
644
|
playAnimation(clipName, options = {}) {
|
|
425
|
-
const
|
|
426
|
-
if (!
|
|
645
|
+
const bakedClip = this.getBakedSourceClip(clipName);
|
|
646
|
+
if (!bakedClip) {
|
|
427
647
|
console.warn(`Loom3: Animation clip "${clipName}" not found`);
|
|
428
648
|
return null;
|
|
429
649
|
}
|
|
430
|
-
if (!this.getActionId(action)) {
|
|
431
|
-
this.setActionId(action, clipName);
|
|
432
|
-
}
|
|
433
650
|
const playbackState = this.mergePlaybackOptions(
|
|
434
651
|
this.getPlaybackStateSnapshot(clipName, { loop: true, source: "baked" }),
|
|
435
652
|
options
|
|
436
653
|
);
|
|
654
|
+
playbackState.blendMode = this.getBakedAggregateBlendMode(clipName, playbackState);
|
|
655
|
+
const actionGroup = this.createBakedActionGroup(clipName, playbackState);
|
|
656
|
+
if (!actionGroup) {
|
|
657
|
+
console.warn(`Loom3: Animation clip "${clipName}" has no character-runtime channels to play`);
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
437
660
|
const crossfadeDuration = options.crossfadeDuration ?? 0;
|
|
438
661
|
const clampWhenFinished = options.clampWhenFinished ?? playbackState.loopMode === "once";
|
|
439
|
-
const startTime = this.resolveStartTime(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
662
|
+
const startTime = this.resolveStartTime(bakedClip.sourceClip.duration, playbackState, options.startTime);
|
|
663
|
+
for (const [channel, action] of actionGroup.channelActions) {
|
|
664
|
+
this.applyPlaybackStateToBakedAction(action, playbackState, channel);
|
|
665
|
+
action.clampWhenFinished = clampWhenFinished;
|
|
666
|
+
if (crossfadeDuration > 0) {
|
|
667
|
+
action.reset();
|
|
668
|
+
action.fadeIn(crossfadeDuration);
|
|
669
|
+
} else {
|
|
670
|
+
action.reset();
|
|
671
|
+
}
|
|
672
|
+
action.time = startTime;
|
|
673
|
+
action.play();
|
|
447
674
|
}
|
|
448
|
-
|
|
449
|
-
action.play();
|
|
450
|
-
this.animationActions.set(clipName, action);
|
|
675
|
+
this.bakedActionGroups.set(clipName, actionGroup);
|
|
451
676
|
this.setPlaybackState(clipName, playbackState);
|
|
452
|
-
|
|
453
|
-
const finishedPromise = new Promise((resolve) => {
|
|
454
|
-
resolveFinished = resolve;
|
|
455
|
-
});
|
|
456
|
-
if (playbackState.loopMode === "once") {
|
|
457
|
-
this.animationFinishedCallbacks.set(clipName, () => resolveFinished());
|
|
458
|
-
}
|
|
459
|
-
return this.createAnimationHandle(clipName, action, finishedPromise);
|
|
677
|
+
return this.createBakedAnimationHandle(clipName, actionGroup);
|
|
460
678
|
}
|
|
461
679
|
stopAnimation(clipName) {
|
|
680
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
681
|
+
if (bakedGroup) {
|
|
682
|
+
for (const action2 of bakedGroup.channelActions.values()) {
|
|
683
|
+
action2.stop();
|
|
684
|
+
try {
|
|
685
|
+
action2.paused = false;
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
this.bakedActionGroups.delete(clipName);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
462
692
|
const action = this.animationActions.get(clipName);
|
|
463
693
|
if (action) {
|
|
464
694
|
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
@@ -506,6 +736,7 @@ var BakedAnimationController = class {
|
|
|
506
736
|
}
|
|
507
737
|
stopAllAnimations() {
|
|
508
738
|
for (const clipName of /* @__PURE__ */ new Set([
|
|
739
|
+
...this.bakedActionGroups.keys(),
|
|
509
740
|
...this.animationActions.keys(),
|
|
510
741
|
...this.clipActions.keys()
|
|
511
742
|
])) {
|
|
@@ -513,18 +744,39 @@ var BakedAnimationController = class {
|
|
|
513
744
|
}
|
|
514
745
|
}
|
|
515
746
|
pauseAnimation(clipName) {
|
|
747
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
748
|
+
if (bakedGroup) {
|
|
749
|
+
for (const action2 of bakedGroup.channelActions.values()) {
|
|
750
|
+
action2.paused = true;
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
516
754
|
const action = this.animationActions.get(clipName);
|
|
517
755
|
if (action) {
|
|
518
756
|
action.paused = true;
|
|
519
757
|
}
|
|
520
758
|
}
|
|
521
759
|
resumeAnimation(clipName) {
|
|
760
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
761
|
+
if (bakedGroup) {
|
|
762
|
+
for (const action2 of bakedGroup.channelActions.values()) {
|
|
763
|
+
action2.paused = false;
|
|
764
|
+
}
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
522
767
|
const action = this.animationActions.get(clipName);
|
|
523
768
|
if (action) {
|
|
524
769
|
action.paused = false;
|
|
525
770
|
}
|
|
526
771
|
}
|
|
527
772
|
pauseAllAnimations() {
|
|
773
|
+
for (const group of this.bakedActionGroups.values()) {
|
|
774
|
+
for (const action of group.channelActions.values()) {
|
|
775
|
+
if (action.isRunning()) {
|
|
776
|
+
action.paused = true;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
528
780
|
for (const action of this.animationActions.values()) {
|
|
529
781
|
if (action.isRunning()) {
|
|
530
782
|
action.paused = true;
|
|
@@ -532,6 +784,13 @@ var BakedAnimationController = class {
|
|
|
532
784
|
}
|
|
533
785
|
}
|
|
534
786
|
resumeAllAnimations() {
|
|
787
|
+
for (const group of this.bakedActionGroups.values()) {
|
|
788
|
+
for (const action of group.channelActions.values()) {
|
|
789
|
+
if (action.paused) {
|
|
790
|
+
action.paused = false;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
535
794
|
for (const action of this.animationActions.values()) {
|
|
536
795
|
if (action.paused) {
|
|
537
796
|
action.paused = false;
|
|
@@ -539,76 +798,161 @@ var BakedAnimationController = class {
|
|
|
539
798
|
}
|
|
540
799
|
}
|
|
541
800
|
setAnimationSpeed(clipName, speed) {
|
|
542
|
-
|
|
543
|
-
if (action) {
|
|
801
|
+
if (this.isBakedSourceClip(clipName)) {
|
|
544
802
|
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
545
803
|
loop: true,
|
|
546
804
|
source: this.clipSources.get(clipName) ?? "baked"
|
|
547
805
|
});
|
|
548
806
|
next.playbackRate = Number.isFinite(speed) ? Math.max(0, Math.abs(speed)) : 1;
|
|
807
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
808
|
+
if (bakedGroup) {
|
|
809
|
+
for (const [channel, action2] of bakedGroup.channelActions) {
|
|
810
|
+
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
this.setPlaybackState(clipName, next);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const action = this.animationActions.get(clipName);
|
|
817
|
+
if (action) {
|
|
818
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
819
|
+
loop: true,
|
|
820
|
+
source: this.clipSources.get(clipName) ?? "clip"
|
|
821
|
+
});
|
|
822
|
+
next.playbackRate = Number.isFinite(speed) ? Math.max(0, Math.abs(speed)) : 1;
|
|
549
823
|
this.applyPlaybackState(action, next);
|
|
550
824
|
this.setPlaybackState(clipName, next);
|
|
551
825
|
}
|
|
552
826
|
}
|
|
553
827
|
setAnimationIntensity(clipName, intensity) {
|
|
554
|
-
|
|
555
|
-
if (action) {
|
|
828
|
+
if (this.isBakedSourceClip(clipName)) {
|
|
556
829
|
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
557
830
|
loop: true,
|
|
558
831
|
source: this.clipSources.get(clipName) ?? "baked"
|
|
559
832
|
});
|
|
560
833
|
next.weight = Number.isFinite(intensity) ? Math.max(0, intensity) : 1;
|
|
834
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
835
|
+
if (bakedGroup) {
|
|
836
|
+
for (const [channel, action2] of bakedGroup.channelActions) {
|
|
837
|
+
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
this.setPlaybackState(clipName, next);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const action = this.animationActions.get(clipName);
|
|
844
|
+
if (action) {
|
|
845
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
846
|
+
loop: true,
|
|
847
|
+
source: this.clipSources.get(clipName) ?? "clip"
|
|
848
|
+
});
|
|
849
|
+
next.weight = Number.isFinite(intensity) ? Math.max(0, intensity) : 1;
|
|
561
850
|
action.setEffectiveWeight(next.weight);
|
|
562
851
|
this.setPlaybackState(clipName, next);
|
|
563
852
|
}
|
|
564
853
|
}
|
|
565
854
|
setAnimationLoopMode(clipName, loopMode) {
|
|
566
|
-
const action = this.getOrCreateBakedAction(clipName);
|
|
567
|
-
if (!action) return;
|
|
568
855
|
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
569
856
|
loop: true,
|
|
570
|
-
source: this.clipSources.get(clipName) ?? "baked"
|
|
857
|
+
source: this.clipSources.get(clipName) ?? (this.isBakedSourceClip(clipName) ? "baked" : "clip")
|
|
571
858
|
});
|
|
572
859
|
next.loopMode = loopMode;
|
|
573
860
|
next.loop = loopMode !== "once";
|
|
861
|
+
if (this.isBakedSourceClip(clipName)) {
|
|
862
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
863
|
+
if (bakedGroup) {
|
|
864
|
+
for (const [channel, action2] of bakedGroup.channelActions) {
|
|
865
|
+
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
this.setPlaybackState(clipName, next);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const action = this.animationActions.get(clipName);
|
|
872
|
+
if (!action) return;
|
|
574
873
|
this.applyPlaybackState(action, next);
|
|
575
874
|
this.setPlaybackState(clipName, next);
|
|
576
875
|
}
|
|
577
876
|
setAnimationRepeatCount(clipName, repeatCount) {
|
|
578
|
-
const action = this.getOrCreateBakedAction(clipName);
|
|
579
|
-
if (!action) return;
|
|
580
877
|
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
581
878
|
loop: true,
|
|
582
|
-
source: this.clipSources.get(clipName) ?? "baked"
|
|
879
|
+
source: this.clipSources.get(clipName) ?? (this.isBakedSourceClip(clipName) ? "baked" : "clip")
|
|
583
880
|
});
|
|
584
881
|
next.repeatCount = typeof repeatCount === "number" && Number.isFinite(repeatCount) ? Math.max(0, repeatCount) : void 0;
|
|
882
|
+
if (this.isBakedSourceClip(clipName)) {
|
|
883
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
884
|
+
if (bakedGroup) {
|
|
885
|
+
for (const [channel, action2] of bakedGroup.channelActions) {
|
|
886
|
+
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
this.setPlaybackState(clipName, next);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const action = this.animationActions.get(clipName);
|
|
893
|
+
if (!action) return;
|
|
585
894
|
this.applyPlaybackState(action, next);
|
|
586
895
|
this.setPlaybackState(clipName, next);
|
|
587
896
|
}
|
|
588
897
|
setAnimationReverse(clipName, reverse) {
|
|
589
|
-
const action = this.getOrCreateBakedAction(clipName);
|
|
590
|
-
if (!action) return;
|
|
591
898
|
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
592
899
|
loop: true,
|
|
593
|
-
source: this.clipSources.get(clipName) ?? "baked"
|
|
900
|
+
source: this.clipSources.get(clipName) ?? (this.isBakedSourceClip(clipName) ? "baked" : "clip")
|
|
594
901
|
});
|
|
595
902
|
next.reverse = !!reverse;
|
|
903
|
+
if (this.isBakedSourceClip(clipName)) {
|
|
904
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
905
|
+
if (bakedGroup) {
|
|
906
|
+
for (const [channel, action2] of bakedGroup.channelActions) {
|
|
907
|
+
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
this.setPlaybackState(clipName, next);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const action = this.animationActions.get(clipName);
|
|
914
|
+
if (!action) return;
|
|
596
915
|
this.applyPlaybackState(action, next);
|
|
597
916
|
this.setPlaybackState(clipName, next);
|
|
598
917
|
}
|
|
599
918
|
setAnimationBlendMode(clipName, blendMode) {
|
|
600
|
-
const action = this.getOrCreateBakedAction(clipName);
|
|
601
|
-
if (!action) return;
|
|
602
919
|
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
603
920
|
loop: true,
|
|
604
|
-
source: this.clipSources.get(clipName) ?? "baked"
|
|
921
|
+
source: this.clipSources.get(clipName) ?? (this.isBakedSourceClip(clipName) ? "baked" : "clip")
|
|
605
922
|
});
|
|
923
|
+
next.requestedBlendMode = blendMode;
|
|
924
|
+
if (this.isBakedSourceClip(clipName)) {
|
|
925
|
+
next.blendMode = this.getBakedAggregateBlendMode(clipName, next);
|
|
926
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
927
|
+
if (bakedGroup) {
|
|
928
|
+
for (const [channel, action2] of bakedGroup.channelActions) {
|
|
929
|
+
this.applyPlaybackStateToBakedAction(action2, next, channel);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
this.setPlaybackState(clipName, next);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
606
935
|
next.blendMode = blendMode;
|
|
936
|
+
const action = this.animationActions.get(clipName);
|
|
937
|
+
if (!action) return;
|
|
607
938
|
this.applyPlaybackState(action, next);
|
|
608
939
|
this.setPlaybackState(clipName, next);
|
|
609
940
|
}
|
|
610
941
|
seekAnimation(clipName, time) {
|
|
611
|
-
const
|
|
942
|
+
const bakedGroup = this.bakedActionGroups.get(clipName);
|
|
943
|
+
if (bakedGroup) {
|
|
944
|
+
const duration2 = this.getBakedSourceClip(clipName)?.sourceClip.duration ?? 0;
|
|
945
|
+
const clamped = Math.max(0, Math.min(duration2, Number.isFinite(time) ? time : 0));
|
|
946
|
+
for (const action2 of bakedGroup.channelActions.values()) {
|
|
947
|
+
action2.time = clamped;
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
this.animationMixer?.update(0);
|
|
951
|
+
} catch {
|
|
952
|
+
}
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const action = this.animationActions.get(clipName);
|
|
612
956
|
if (!action) return;
|
|
613
957
|
const duration = action.getClip().duration;
|
|
614
958
|
action.time = Math.max(0, Math.min(duration, Number.isFinite(time) ? time : 0));
|
|
@@ -623,6 +967,40 @@ var BakedAnimationController = class {
|
|
|
623
967
|
}
|
|
624
968
|
}
|
|
625
969
|
getAnimationState(clipName) {
|
|
970
|
+
const bakedClip = this.getBakedSourceClip(clipName);
|
|
971
|
+
if (bakedClip) {
|
|
972
|
+
const state2 = this.playbackState.get(clipName);
|
|
973
|
+
const action2 = this.getRepresentativeBakedAction(clipName);
|
|
974
|
+
if (!state2 && !action2) {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
const loopMode2 = state2?.loopMode ?? (action2?.loop === THREE.LoopPingPong ? "pingpong" : action2?.loop === THREE.LoopOnce ? "once" : "repeat");
|
|
978
|
+
const playbackRate2 = state2?.playbackRate ?? Math.abs(action2?.getEffectiveTimeScale?.() ?? 1);
|
|
979
|
+
const reverse2 = state2?.reverse ?? (action2?.getEffectiveTimeScale?.() ?? 1) < 0;
|
|
980
|
+
const pausedValues = this.bakedActionGroups.get(clipName) ? Array.from(this.bakedActionGroups.get(clipName).channelActions.values()).map((entry) => entry.paused) : [];
|
|
981
|
+
return {
|
|
982
|
+
name: bakedClip.sourceClip.name,
|
|
983
|
+
actionId: this.bakedActionGroups.get(clipName)?.actionId,
|
|
984
|
+
source: state2?.source ?? this.clipSources.get(clipName) ?? "baked",
|
|
985
|
+
isPlaying: this.bakedActionGroups.get(clipName) ? Array.from(this.bakedActionGroups.get(clipName).channelActions.values()).some((entry) => entry.isRunning() && !entry.paused) : false,
|
|
986
|
+
isPaused: pausedValues.length > 0 ? pausedValues.every(Boolean) : false,
|
|
987
|
+
time: action2?.time ?? 0,
|
|
988
|
+
duration: bakedClip.sourceClip.duration,
|
|
989
|
+
speed: playbackRate2,
|
|
990
|
+
playbackRate: playbackRate2,
|
|
991
|
+
reverse: reverse2,
|
|
992
|
+
weight: state2?.weight ?? action2?.getEffectiveWeight?.() ?? 1,
|
|
993
|
+
balance: state2?.balance ?? 0,
|
|
994
|
+
requestedBlendMode: state2?.requestedBlendMode ?? "replace",
|
|
995
|
+
blendMode: this.getBakedAggregateBlendMode(clipName, state2),
|
|
996
|
+
channels: this.getBakedChannelInfo(clipName, state2),
|
|
997
|
+
easing: state2?.easing ?? "linear",
|
|
998
|
+
loop: loopMode2 !== "once",
|
|
999
|
+
loopMode: loopMode2,
|
|
1000
|
+
repeatCount: state2?.repeatCount,
|
|
1001
|
+
isLooping: loopMode2 !== "once"
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
626
1004
|
const action = this.animationActions.get(clipName);
|
|
627
1005
|
if (!action) return null;
|
|
628
1006
|
const clip = action.getClip();
|
|
@@ -643,7 +1021,9 @@ var BakedAnimationController = class {
|
|
|
643
1021
|
reverse,
|
|
644
1022
|
weight: state?.weight ?? action.getEffectiveWeight(),
|
|
645
1023
|
balance: state?.balance ?? 0,
|
|
1024
|
+
requestedBlendMode: state?.requestedBlendMode ?? state?.blendMode ?? "replace",
|
|
646
1025
|
blendMode: state?.blendMode ?? "replace",
|
|
1026
|
+
channels: state?.source === "baked" ? this.getBakedChannelInfo(clipName, state) : void 0,
|
|
647
1027
|
easing: state?.easing ?? "linear",
|
|
648
1028
|
loop: loopMode !== "once",
|
|
649
1029
|
loopMode,
|
|
@@ -653,6 +1033,12 @@ var BakedAnimationController = class {
|
|
|
653
1033
|
}
|
|
654
1034
|
getPlayingAnimations() {
|
|
655
1035
|
const playing = [];
|
|
1036
|
+
for (const name of this.bakedActionGroups.keys()) {
|
|
1037
|
+
const state = this.getAnimationState(name);
|
|
1038
|
+
if (state?.isPlaying) {
|
|
1039
|
+
playing.push(state);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
656
1042
|
for (const [name, action] of this.animationActions) {
|
|
657
1043
|
if (action.isRunning()) {
|
|
658
1044
|
const state = this.getAnimationState(name);
|
|
@@ -662,6 +1048,13 @@ var BakedAnimationController = class {
|
|
|
662
1048
|
return playing;
|
|
663
1049
|
}
|
|
664
1050
|
crossfadeTo(clipName, duration = 0.3, options = {}) {
|
|
1051
|
+
for (const group of this.bakedActionGroups.values()) {
|
|
1052
|
+
for (const action of group.channelActions.values()) {
|
|
1053
|
+
if (action.isRunning()) {
|
|
1054
|
+
action.fadeOut(duration);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
665
1058
|
for (const action of this.animationActions.values()) {
|
|
666
1059
|
if (action.isRunning()) {
|
|
667
1060
|
action.fadeOut(duration);
|
|
@@ -1187,6 +1580,14 @@ var BakedAnimationController = class {
|
|
|
1187
1580
|
this.animationMixer.addEventListener("finished", (event) => {
|
|
1188
1581
|
const action = event.action;
|
|
1189
1582
|
const clip = action.getClip();
|
|
1583
|
+
const bakedRuntime = this.bakedRuntimeClipToSource.get(clip.name);
|
|
1584
|
+
if (bakedRuntime) {
|
|
1585
|
+
const group = this.bakedActionGroups.get(bakedRuntime.sourceClipName);
|
|
1586
|
+
if (group && group.pendingFinishedChannels.delete(bakedRuntime.channel) && group.pendingFinishedChannels.size === 0) {
|
|
1587
|
+
group.resolveFinished();
|
|
1588
|
+
}
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1190
1591
|
const callback = this.animationFinishedCallbacks.get(clip.name);
|
|
1191
1592
|
if (callback) {
|
|
1192
1593
|
callback();
|
|
@@ -1211,6 +1612,20 @@ var BakedAnimationController = class {
|
|
|
1211
1612
|
finished: finishedPromise
|
|
1212
1613
|
};
|
|
1213
1614
|
}
|
|
1615
|
+
createBakedAnimationHandle(clipName, group) {
|
|
1616
|
+
return {
|
|
1617
|
+
actionId: group.actionId,
|
|
1618
|
+
stop: () => this.stopAnimation(clipName),
|
|
1619
|
+
pause: () => this.pauseAnimation(clipName),
|
|
1620
|
+
resume: () => this.resumeAnimation(clipName),
|
|
1621
|
+
setSpeed: (speed) => this.setAnimationSpeed(clipName, speed),
|
|
1622
|
+
setWeight: (weight) => this.setAnimationIntensity(clipName, weight),
|
|
1623
|
+
seekTo: (time) => this.seekAnimation(clipName, time),
|
|
1624
|
+
getState: () => this.getAnimationState(clipName),
|
|
1625
|
+
crossfadeTo: (targetClip, dur) => this.crossfadeTo(targetClip, dur),
|
|
1626
|
+
finished: group.finishedPromise
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1214
1629
|
};
|
|
1215
1630
|
|
|
1216
1631
|
// src/presets/cc4.ts
|
|
@@ -3937,7 +4352,8 @@ var _Loom3 = class _Loom3 {
|
|
|
3937
4352
|
__publicField(this, "bones", {});
|
|
3938
4353
|
__publicField(this, "mixWeights", {});
|
|
3939
4354
|
// Viseme state
|
|
3940
|
-
__publicField(this, "visemeValues",
|
|
4355
|
+
__publicField(this, "visemeValues", []);
|
|
4356
|
+
__publicField(this, "visemeJawScales", []);
|
|
3941
4357
|
__publicField(this, "bakedAnimations");
|
|
3942
4358
|
__publicField(this, "hairPhysics");
|
|
3943
4359
|
// Internal animation loop
|
|
@@ -3950,6 +4366,7 @@ var _Loom3 = class _Loom3 {
|
|
|
3950
4366
|
const basePreset = config.presetType ? getPreset(config.presetType) : CC4_PRESET;
|
|
3951
4367
|
this.config = extendPresetWithProfile(basePreset, config.profile);
|
|
3952
4368
|
this.mixWeights = { ...this.config.auMixDefaults };
|
|
4369
|
+
this.syncVisemeRuntimeState();
|
|
3953
4370
|
this.animation = animation || new AnimationThree();
|
|
3954
4371
|
this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
|
|
3955
4372
|
this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
|
|
@@ -4482,6 +4899,7 @@ var _Loom3 = class _Loom3 {
|
|
|
4482
4899
|
if (visemeIndex < 0 || visemeIndex >= this.config.visemeKeys.length) return;
|
|
4483
4900
|
const val = clamp012(value);
|
|
4484
4901
|
this.visemeValues[visemeIndex] = val;
|
|
4902
|
+
this.visemeJawScales[visemeIndex] = jawScale;
|
|
4485
4903
|
const targets = this.resolvedVisemeTargets[visemeIndex];
|
|
4486
4904
|
if (targets && targets.length > 0) {
|
|
4487
4905
|
this.applyMorphTargets(targets, val);
|
|
@@ -4494,7 +4912,7 @@ var _Loom3 = class _Loom3 {
|
|
|
4494
4912
|
this.setMorph(morphKey, val, visemeMeshNames);
|
|
4495
4913
|
}
|
|
4496
4914
|
}
|
|
4497
|
-
const jawAmount =
|
|
4915
|
+
const jawAmount = this.getVisemeJawAmount(visemeIndex) * val * jawScale;
|
|
4498
4916
|
if (Math.abs(jawScale) > 1e-6 && Math.abs(jawAmount) > 1e-6) {
|
|
4499
4917
|
this.updateBoneRotation("JAW", "pitch", jawAmount);
|
|
4500
4918
|
}
|
|
@@ -4509,9 +4927,10 @@ var _Loom3 = class _Loom3 {
|
|
|
4509
4927
|
const morphKey = this.config.visemeKeys[visemeIndex];
|
|
4510
4928
|
const target = clamp012(to);
|
|
4511
4929
|
this.visemeValues[visemeIndex] = target;
|
|
4930
|
+
this.visemeJawScales[visemeIndex] = jawScale;
|
|
4512
4931
|
const visemeMeshNames = this.getMeshNamesForViseme();
|
|
4513
4932
|
const morphHandle = typeof morphKey === "number" ? this.transitionMorphInfluence(morphKey, target, durationMs, visemeMeshNames) : this.transitionMorph(morphKey, target, durationMs, visemeMeshNames);
|
|
4514
|
-
const jawAmount =
|
|
4933
|
+
const jawAmount = this.getVisemeJawAmount(visemeIndex) * target * jawScale;
|
|
4515
4934
|
if (Math.abs(jawScale) <= 1e-6 || Math.abs(jawAmount) <= 1e-6) {
|
|
4516
4935
|
return morphHandle;
|
|
4517
4936
|
}
|
|
@@ -4565,6 +4984,9 @@ var _Loom3 = class _Loom3 {
|
|
|
4565
4984
|
}
|
|
4566
4985
|
resetToNeutral() {
|
|
4567
4986
|
this.auValues = {};
|
|
4987
|
+
this.visemeValues = new Array(this.config.visemeKeys.length).fill(0);
|
|
4988
|
+
this.visemeJawScales = new Array(this.config.visemeKeys.length).fill(1);
|
|
4989
|
+
this.translations = {};
|
|
4568
4990
|
this.initBoneRotations();
|
|
4569
4991
|
this.clearTransitions();
|
|
4570
4992
|
for (const m of this.meshes) {
|
|
@@ -4580,6 +5002,33 @@ var _Loom3 = class _Loom3 {
|
|
|
4580
5002
|
entry.obj.quaternion.copy(entry.baseQuat);
|
|
4581
5003
|
});
|
|
4582
5004
|
}
|
|
5005
|
+
reinitializeRuntimeStateFromCurrentControls(staleMorphTargets = []) {
|
|
5006
|
+
this.clearTransitions();
|
|
5007
|
+
this.resetMorphTargetHandles(staleMorphTargets);
|
|
5008
|
+
this.translations = {};
|
|
5009
|
+
this.initBoneRotations();
|
|
5010
|
+
Object.values(this.bones).forEach((entry) => {
|
|
5011
|
+
if (!entry) return;
|
|
5012
|
+
entry.obj.position.copy(entry.basePos);
|
|
5013
|
+
entry.obj.quaternion.copy(entry.baseQuat);
|
|
5014
|
+
entry.obj.updateMatrixWorld(false);
|
|
5015
|
+
});
|
|
5016
|
+
for (const [auIdStr, value] of Object.entries(this.auValues)) {
|
|
5017
|
+
if (value <= 0) continue;
|
|
5018
|
+
const auId = Number(auIdStr);
|
|
5019
|
+
if (Number.isNaN(auId)) continue;
|
|
5020
|
+
this.setAU(auId, value, this.auBalances[auId]);
|
|
5021
|
+
}
|
|
5022
|
+
for (let visemeIndex = 0; visemeIndex < this.visemeValues.length; visemeIndex += 1) {
|
|
5023
|
+
const value = this.visemeValues[visemeIndex] ?? 0;
|
|
5024
|
+
if (value <= 0) continue;
|
|
5025
|
+
this.setViseme(visemeIndex, value, this.visemeJawScales[visemeIndex] ?? 1);
|
|
5026
|
+
}
|
|
5027
|
+
if (this.model) {
|
|
5028
|
+
this.flushPendingComposites();
|
|
5029
|
+
this.model.updateMatrixWorld(true);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
4583
5032
|
// ============================================================================
|
|
4584
5033
|
// MESH CONTROL
|
|
4585
5034
|
// ============================================================================
|
|
@@ -4810,12 +5259,20 @@ var _Loom3 = class _Loom3 {
|
|
|
4810
5259
|
}
|
|
4811
5260
|
setProfile(profile) {
|
|
4812
5261
|
this.config = profile;
|
|
5262
|
+
this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
|
|
5263
|
+
this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
|
|
4813
5264
|
this.mixWeights = { ...profile.auMixDefaults };
|
|
5265
|
+
this.syncVisemeRuntimeState();
|
|
5266
|
+
let staleMorphTargets = [];
|
|
4814
5267
|
if (this.model) {
|
|
5268
|
+
staleMorphTargets = this.collectResolvedExpressionMorphTargets();
|
|
5269
|
+
this.bones = this.resolveBones(this.model);
|
|
5270
|
+
this.missingBoneWarnings.clear();
|
|
4815
5271
|
this.rebuildMorphTargetsCache();
|
|
4816
5272
|
}
|
|
4817
5273
|
this.hairPhysics.refreshMeshSelection();
|
|
4818
5274
|
this.applyHairPhysicsProfileConfig();
|
|
5275
|
+
this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
|
|
4819
5276
|
}
|
|
4820
5277
|
getProfile() {
|
|
4821
5278
|
return this.config;
|
|
@@ -4953,6 +5410,39 @@ var _Loom3 = class _Loom3 {
|
|
|
4953
5410
|
getMorphIndexCacheKey(index, meshNames) {
|
|
4954
5411
|
return meshNames?.length ? `idx:${index}@${meshNames.join(",")}` : `idx:${index}`;
|
|
4955
5412
|
}
|
|
5413
|
+
syncVisemeRuntimeState() {
|
|
5414
|
+
const visemeCount = this.config.visemeKeys.length;
|
|
5415
|
+
this.visemeValues = Array.from(
|
|
5416
|
+
{ length: visemeCount },
|
|
5417
|
+
(_, index) => this.visemeValues[index] ?? 0
|
|
5418
|
+
);
|
|
5419
|
+
this.visemeJawScales = Array.from(
|
|
5420
|
+
{ length: visemeCount },
|
|
5421
|
+
(_, index) => this.visemeJawScales[index] ?? 1
|
|
5422
|
+
);
|
|
5423
|
+
}
|
|
5424
|
+
getVisemeJawAmount(visemeIndex) {
|
|
5425
|
+
return this.config.visemeJawAmounts?.[visemeIndex] ?? _Loom3.VISEME_JAW_AMOUNTS[visemeIndex] ?? 0;
|
|
5426
|
+
}
|
|
5427
|
+
collectResolvedExpressionMorphTargets() {
|
|
5428
|
+
const targets = [];
|
|
5429
|
+
for (const resolved of this.resolvedAUMorphTargets.values()) {
|
|
5430
|
+
targets.push(...resolved.left, ...resolved.right, ...resolved.center);
|
|
5431
|
+
}
|
|
5432
|
+
for (const resolved of this.resolvedVisemeTargets) {
|
|
5433
|
+
if (resolved?.length) {
|
|
5434
|
+
targets.push(...resolved);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
return targets;
|
|
5438
|
+
}
|
|
5439
|
+
resetMorphTargetHandles(targets) {
|
|
5440
|
+
for (const { infl, idx } of targets) {
|
|
5441
|
+
if (idx < infl.length) {
|
|
5442
|
+
infl[idx] = 0;
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
4956
5446
|
isMixedAU(id) {
|
|
4957
5447
|
const morphs = this.config.auToMorphs[id];
|
|
4958
5448
|
const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
|
|
@@ -5094,12 +5584,25 @@ var _Loom3 = class _Loom3 {
|
|
|
5094
5584
|
}
|
|
5095
5585
|
resolveBones(root) {
|
|
5096
5586
|
const resolved = {};
|
|
5587
|
+
const previousBones = this.bones;
|
|
5097
5588
|
const snapshot = (obj) => ({
|
|
5098
5589
|
obj,
|
|
5099
5590
|
basePos: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
|
|
5100
5591
|
baseQuat: obj.quaternion.clone(),
|
|
5101
5592
|
baseEuler: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, order: obj.rotation.order }
|
|
5102
5593
|
});
|
|
5594
|
+
const snapshotPreservingBasePose = (obj) => {
|
|
5595
|
+
const existing = Object.values(previousBones).find((entry) => entry?.obj === obj);
|
|
5596
|
+
if (!existing) {
|
|
5597
|
+
return snapshot(obj);
|
|
5598
|
+
}
|
|
5599
|
+
return {
|
|
5600
|
+
obj,
|
|
5601
|
+
basePos: { ...existing.basePos },
|
|
5602
|
+
baseQuat: existing.baseQuat.clone(),
|
|
5603
|
+
baseEuler: { ...existing.baseEuler }
|
|
5604
|
+
};
|
|
5605
|
+
};
|
|
5103
5606
|
const prefix = this.config.bonePrefix || "";
|
|
5104
5607
|
const suffix = this.config.boneSuffix || "";
|
|
5105
5608
|
const suffixRegex = this.config.suffixPattern ? new RegExp(this.config.suffixPattern) : null;
|
|
@@ -5130,19 +5633,19 @@ var _Loom3 = class _Loom3 {
|
|
|
5130
5633
|
for (const [key, nodeName] of Object.entries(this.config.boneNodes)) {
|
|
5131
5634
|
const node = findNode(nodeName);
|
|
5132
5635
|
if (node) {
|
|
5133
|
-
resolved[key] =
|
|
5636
|
+
resolved[key] = snapshotPreservingBasePose(node);
|
|
5134
5637
|
}
|
|
5135
5638
|
}
|
|
5136
5639
|
if (!resolved.EYE_L && this.config.eyeMeshNodes) {
|
|
5137
5640
|
const node = findNode(this.config.eyeMeshNodes.LEFT);
|
|
5138
5641
|
if (node) {
|
|
5139
|
-
resolved.EYE_L =
|
|
5642
|
+
resolved.EYE_L = snapshotPreservingBasePose(node);
|
|
5140
5643
|
}
|
|
5141
5644
|
}
|
|
5142
5645
|
if (!resolved.EYE_R && this.config.eyeMeshNodes) {
|
|
5143
5646
|
const node = findNode(this.config.eyeMeshNodes.RIGHT);
|
|
5144
5647
|
if (node) {
|
|
5145
|
-
resolved.EYE_R =
|
|
5648
|
+
resolved.EYE_R = snapshotPreservingBasePose(node);
|
|
5146
5649
|
}
|
|
5147
5650
|
}
|
|
5148
5651
|
return resolved;
|