@lovelace_lol/loom3 1.0.35 → 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 +490 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +491 -76
- 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
|