@lovelace_lol/loom3 1.0.15 → 1.0.16
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 +317 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -5
- package/dist/index.d.ts +100 -5
- package/dist/index.js +318 -129
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -188,6 +188,8 @@ var BakedAnimationController = class {
|
|
|
188
188
|
__publicField(this, "animationFinishedCallbacks", /* @__PURE__ */ new Map());
|
|
189
189
|
__publicField(this, "clipActions", /* @__PURE__ */ new Map());
|
|
190
190
|
__publicField(this, "clipHandles", /* @__PURE__ */ new Map());
|
|
191
|
+
__publicField(this, "clipSources", /* @__PURE__ */ new Map());
|
|
192
|
+
__publicField(this, "playbackState", /* @__PURE__ */ new Map());
|
|
191
193
|
__publicField(this, "actionIds", /* @__PURE__ */ new WeakMap());
|
|
192
194
|
__publicField(this, "actionIdToClip", /* @__PURE__ */ new Map());
|
|
193
195
|
this.host = host;
|
|
@@ -203,6 +205,117 @@ var BakedAnimationController = class {
|
|
|
203
205
|
action.__actionId = actionId;
|
|
204
206
|
return actionId;
|
|
205
207
|
}
|
|
208
|
+
normalizePlaybackOptions(options, defaults) {
|
|
209
|
+
const clipOptions = options;
|
|
210
|
+
const rawRate = options?.playbackRate ?? options?.speed ?? 1;
|
|
211
|
+
const playbackRate = Number.isFinite(rawRate) ? Math.max(0, Math.abs(rawRate)) : 1;
|
|
212
|
+
const rawWeight = options?.weight ?? options?.intensity ?? clipOptions?.mixerWeight ?? 1;
|
|
213
|
+
const weight = Number.isFinite(rawWeight) ? Math.max(0, rawWeight) : 1;
|
|
214
|
+
const loopMode = options?.loopMode ?? (typeof options?.loop === "boolean" ? options.loop ? "repeat" : "once" : defaults.loop ? "repeat" : "once");
|
|
215
|
+
return {
|
|
216
|
+
source: options?.source ?? defaults.source,
|
|
217
|
+
loop: loopMode !== "once",
|
|
218
|
+
loopMode,
|
|
219
|
+
repeatCount: options?.repeatCount,
|
|
220
|
+
reverse: !!options?.reverse,
|
|
221
|
+
playbackRate,
|
|
222
|
+
weight,
|
|
223
|
+
balance: Number.isFinite(options?.balance) ? options?.balance ?? 0 : 0,
|
|
224
|
+
blendMode: options?.blendMode ?? (clipOptions?.mixerAdditive ? "additive" : "replace"),
|
|
225
|
+
easing: options?.easing ?? "linear"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
applyPlaybackState(action, state) {
|
|
229
|
+
const signedRate = state.reverse ? -state.playbackRate : state.playbackRate;
|
|
230
|
+
action.setEffectiveTimeScale(signedRate);
|
|
231
|
+
action.setEffectiveWeight(state.weight);
|
|
232
|
+
action.blendMode = state.blendMode === "additive" ? THREE.AdditiveAnimationBlendMode : THREE.NormalAnimationBlendMode;
|
|
233
|
+
const reps = state.repeatCount ?? Infinity;
|
|
234
|
+
if (state.loopMode === "pingpong") {
|
|
235
|
+
action.setLoop(THREE.LoopPingPong, reps);
|
|
236
|
+
} else if (state.loopMode === "once") {
|
|
237
|
+
action.setLoop(THREE.LoopOnce, 1);
|
|
238
|
+
} else {
|
|
239
|
+
action.setLoop(THREE.LoopRepeat, reps);
|
|
240
|
+
}
|
|
241
|
+
action.clampWhenFinished = state.loopMode === "once";
|
|
242
|
+
}
|
|
243
|
+
setPlaybackState(clipName, state) {
|
|
244
|
+
this.playbackState.set(clipName, state);
|
|
245
|
+
this.clipSources.set(clipName, state.source);
|
|
246
|
+
}
|
|
247
|
+
getPlaybackStateSnapshot(clipName, defaults) {
|
|
248
|
+
const existing = this.playbackState.get(clipName);
|
|
249
|
+
if (existing) {
|
|
250
|
+
return { ...existing };
|
|
251
|
+
}
|
|
252
|
+
return this.normalizePlaybackOptions(void 0, defaults);
|
|
253
|
+
}
|
|
254
|
+
mergePlaybackOptions(current, options) {
|
|
255
|
+
if (!options) {
|
|
256
|
+
return current;
|
|
257
|
+
}
|
|
258
|
+
const next = { ...current };
|
|
259
|
+
const clipOptions = options;
|
|
260
|
+
const loopMode = options.loopMode ?? (typeof options.loop === "boolean" ? options.loop ? "repeat" : "once" : void 0);
|
|
261
|
+
if (options.source) next.source = options.source;
|
|
262
|
+
if (loopMode) {
|
|
263
|
+
next.loopMode = loopMode;
|
|
264
|
+
next.loop = loopMode !== "once";
|
|
265
|
+
}
|
|
266
|
+
if (options.repeatCount !== void 0) {
|
|
267
|
+
next.repeatCount = Number.isFinite(options.repeatCount) ? Math.max(0, options.repeatCount ?? 0) : void 0;
|
|
268
|
+
}
|
|
269
|
+
if (typeof options.reverse === "boolean") {
|
|
270
|
+
next.reverse = options.reverse;
|
|
271
|
+
}
|
|
272
|
+
const rate = options.playbackRate ?? options.speed;
|
|
273
|
+
if (rate !== void 0) {
|
|
274
|
+
next.playbackRate = Number.isFinite(rate) ? Math.max(0, Math.abs(rate)) : current.playbackRate;
|
|
275
|
+
}
|
|
276
|
+
const weight = options.weight ?? options.intensity ?? clipOptions?.mixerWeight;
|
|
277
|
+
if (weight !== void 0) {
|
|
278
|
+
next.weight = Number.isFinite(weight) ? Math.max(0, weight) : current.weight;
|
|
279
|
+
}
|
|
280
|
+
if (typeof options.balance === "number" && Number.isFinite(options.balance)) {
|
|
281
|
+
next.balance = Math.max(-1, Math.min(1, options.balance));
|
|
282
|
+
}
|
|
283
|
+
if (options.blendMode) {
|
|
284
|
+
next.blendMode = options.blendMode;
|
|
285
|
+
} else if (typeof clipOptions?.mixerAdditive === "boolean") {
|
|
286
|
+
next.blendMode = clipOptions.mixerAdditive ? "additive" : "replace";
|
|
287
|
+
}
|
|
288
|
+
if (options.easing) {
|
|
289
|
+
next.easing = options.easing;
|
|
290
|
+
}
|
|
291
|
+
return next;
|
|
292
|
+
}
|
|
293
|
+
resolveStartTime(duration, state, explicitStartTime) {
|
|
294
|
+
if (typeof explicitStartTime === "number" && Number.isFinite(explicitStartTime)) {
|
|
295
|
+
return Math.max(0, Math.min(duration, explicitStartTime));
|
|
296
|
+
}
|
|
297
|
+
if (state.reverse && state.loopMode === "once") {
|
|
298
|
+
return duration;
|
|
299
|
+
}
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
getOrCreateBakedAction(clipName) {
|
|
303
|
+
const existing = this.animationActions.get(clipName);
|
|
304
|
+
if (existing) {
|
|
305
|
+
return existing;
|
|
306
|
+
}
|
|
307
|
+
this.ensureMixer();
|
|
308
|
+
if (!this.animationMixer) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const clip = this.animationClips.find((entry) => entry.name === clipName);
|
|
312
|
+
if (!clip || (this.clipSources.get(clipName) ?? "baked") !== "baked") {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const action = this.animationMixer.clipAction(clip);
|
|
316
|
+
this.animationActions.set(clipName, action);
|
|
317
|
+
return action;
|
|
318
|
+
}
|
|
206
319
|
getMeshNamesForAU(auId, config, explicitMeshNames) {
|
|
207
320
|
if (explicitMeshNames && explicitMeshNames.length > 0) {
|
|
208
321
|
return explicitMeshNames;
|
|
@@ -245,6 +358,8 @@ var BakedAnimationController = class {
|
|
|
245
358
|
this.animationFinishedCallbacks.clear();
|
|
246
359
|
this.clipActions.clear();
|
|
247
360
|
this.clipHandles.clear();
|
|
361
|
+
this.clipSources.clear();
|
|
362
|
+
this.playbackState.clear();
|
|
248
363
|
}
|
|
249
364
|
loadAnimationClips(clips) {
|
|
250
365
|
if (!this.host.getModel()) {
|
|
@@ -254,6 +369,7 @@ var BakedAnimationController = class {
|
|
|
254
369
|
this.ensureMixer();
|
|
255
370
|
this.animationClips = clips;
|
|
256
371
|
for (const clip of this.animationClips) {
|
|
372
|
+
this.clipSources.set(clip.name, "baked");
|
|
257
373
|
if (!this.animationActions.has(clip.name) && this.animationMixer) {
|
|
258
374
|
const action = this.animationMixer.clipAction(clip);
|
|
259
375
|
this.animationActions.set(clip.name, action);
|
|
@@ -264,53 +380,43 @@ var BakedAnimationController = class {
|
|
|
264
380
|
return this.animationClips.map((clip) => ({
|
|
265
381
|
name: clip.name,
|
|
266
382
|
duration: clip.duration,
|
|
267
|
-
trackCount: clip.tracks.length
|
|
383
|
+
trackCount: clip.tracks.length,
|
|
384
|
+
source: this.clipSources.get(clip.name) ?? "baked"
|
|
268
385
|
}));
|
|
269
386
|
}
|
|
270
387
|
playAnimation(clipName, options = {}) {
|
|
271
|
-
const action = this.
|
|
388
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
272
389
|
if (!action) {
|
|
273
390
|
console.warn(`Loom3: Animation clip "${clipName}" not found`);
|
|
274
391
|
return null;
|
|
275
392
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
intensity = 1,
|
|
279
|
-
loop = true,
|
|
280
|
-
loopMode = "repeat",
|
|
281
|
-
repeatCount,
|
|
282
|
-
crossfadeDuration = 0,
|
|
283
|
-
clampWhenFinished = true,
|
|
284
|
-
startTime = 0
|
|
285
|
-
} = options;
|
|
286
|
-
action.setEffectiveTimeScale(speed);
|
|
287
|
-
action.setEffectiveWeight(intensity);
|
|
288
|
-
action.clampWhenFinished = clampWhenFinished;
|
|
289
|
-
const reps = repeatCount ?? Infinity;
|
|
290
|
-
if (!loop || loopMode === "once") {
|
|
291
|
-
action.setLoop(THREE.LoopOnce, 1);
|
|
292
|
-
} else if (loopMode === "pingpong") {
|
|
293
|
-
action.setLoop(THREE.LoopPingPong, reps);
|
|
294
|
-
} else {
|
|
295
|
-
action.setLoop(THREE.LoopRepeat, reps);
|
|
296
|
-
}
|
|
297
|
-
if (startTime > 0) {
|
|
298
|
-
action.time = startTime;
|
|
393
|
+
if (!this.getActionId(action)) {
|
|
394
|
+
this.setActionId(action, clipName);
|
|
299
395
|
}
|
|
396
|
+
const playbackState = this.mergePlaybackOptions(
|
|
397
|
+
this.getPlaybackStateSnapshot(clipName, { loop: true, source: "baked" }),
|
|
398
|
+
options
|
|
399
|
+
);
|
|
400
|
+
const crossfadeDuration = options.crossfadeDuration ?? 0;
|
|
401
|
+
const clampWhenFinished = options.clampWhenFinished ?? playbackState.loopMode === "once";
|
|
402
|
+
const startTime = this.resolveStartTime(action.getClip().duration, playbackState, options.startTime);
|
|
403
|
+
this.applyPlaybackState(action, playbackState);
|
|
404
|
+
action.clampWhenFinished = clampWhenFinished;
|
|
300
405
|
if (crossfadeDuration > 0) {
|
|
301
406
|
action.reset();
|
|
302
407
|
action.fadeIn(crossfadeDuration);
|
|
303
408
|
} else {
|
|
304
409
|
action.reset();
|
|
305
410
|
}
|
|
411
|
+
action.time = startTime;
|
|
306
412
|
action.play();
|
|
307
413
|
this.animationActions.set(clipName, action);
|
|
308
|
-
this.
|
|
414
|
+
this.setPlaybackState(clipName, playbackState);
|
|
309
415
|
let resolveFinished;
|
|
310
416
|
const finishedPromise = new Promise((resolve) => {
|
|
311
417
|
resolveFinished = resolve;
|
|
312
418
|
});
|
|
313
|
-
if (
|
|
419
|
+
if (playbackState.loopMode === "once") {
|
|
314
420
|
this.animationFinishedCallbacks.set(clipName, () => resolveFinished());
|
|
315
421
|
}
|
|
316
422
|
return this.createAnimationHandle(clipName, action, finishedPromise);
|
|
@@ -318,8 +424,9 @@ var BakedAnimationController = class {
|
|
|
318
424
|
stopAnimation(clipName) {
|
|
319
425
|
const action = this.animationActions.get(clipName);
|
|
320
426
|
if (action) {
|
|
427
|
+
const isBaked = (this.clipSources.get(clipName) ?? "baked") === "baked";
|
|
321
428
|
action.stop();
|
|
322
|
-
if (this.animationMixer) {
|
|
429
|
+
if (!isBaked && this.animationMixer) {
|
|
323
430
|
try {
|
|
324
431
|
const clip = action.getClip();
|
|
325
432
|
if (clip) {
|
|
@@ -329,7 +436,15 @@ var BakedAnimationController = class {
|
|
|
329
436
|
} catch {
|
|
330
437
|
}
|
|
331
438
|
}
|
|
332
|
-
|
|
439
|
+
if (!isBaked) {
|
|
440
|
+
this.animationActions.delete(clipName);
|
|
441
|
+
this.playbackState.delete(clipName);
|
|
442
|
+
} else {
|
|
443
|
+
try {
|
|
444
|
+
action.paused = false;
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
}
|
|
333
448
|
this.animationFinishedCallbacks.delete(clipName);
|
|
334
449
|
}
|
|
335
450
|
const clipAction = this.clipActions.get(clipName);
|
|
@@ -347,44 +462,18 @@ var BakedAnimationController = class {
|
|
|
347
462
|
}
|
|
348
463
|
this.clipActions.delete(clipName);
|
|
349
464
|
}
|
|
465
|
+
if (this.clipActions.get(clipName) === action) {
|
|
466
|
+
this.clipActions.delete(clipName);
|
|
467
|
+
}
|
|
350
468
|
this.clipHandles.delete(clipName);
|
|
351
469
|
}
|
|
352
470
|
stopAllAnimations() {
|
|
353
|
-
for (const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (clip) {
|
|
359
|
-
this.animationMixer.uncacheAction(clip);
|
|
360
|
-
this.animationMixer.uncacheClip(clip);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} catch {
|
|
364
|
-
}
|
|
365
|
-
try {
|
|
366
|
-
this.animationFinishedCallbacks.delete(name);
|
|
367
|
-
} catch {
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
for (const [name, action] of this.clipActions) {
|
|
371
|
-
if (!this.animationActions.has(name)) {
|
|
372
|
-
try {
|
|
373
|
-
action.stop();
|
|
374
|
-
if (this.animationMixer) {
|
|
375
|
-
const clip = action.getClip();
|
|
376
|
-
if (clip) {
|
|
377
|
-
this.animationMixer.uncacheAction(clip);
|
|
378
|
-
this.animationMixer.uncacheClip(clip);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
} catch {
|
|
382
|
-
}
|
|
383
|
-
}
|
|
471
|
+
for (const clipName of /* @__PURE__ */ new Set([
|
|
472
|
+
...this.animationActions.keys(),
|
|
473
|
+
...this.clipActions.keys()
|
|
474
|
+
])) {
|
|
475
|
+
this.stopAnimation(clipName);
|
|
384
476
|
}
|
|
385
|
-
this.animationActions.clear();
|
|
386
|
-
this.clipActions.clear();
|
|
387
|
-
this.clipHandles.clear();
|
|
388
477
|
}
|
|
389
478
|
pauseAnimation(clipName) {
|
|
390
479
|
const action = this.animationActions.get(clipName);
|
|
@@ -413,15 +502,82 @@ var BakedAnimationController = class {
|
|
|
413
502
|
}
|
|
414
503
|
}
|
|
415
504
|
setAnimationSpeed(clipName, speed) {
|
|
416
|
-
const action = this.
|
|
505
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
417
506
|
if (action) {
|
|
418
|
-
|
|
507
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
508
|
+
loop: true,
|
|
509
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
510
|
+
});
|
|
511
|
+
next.playbackRate = Number.isFinite(speed) ? Math.max(0, Math.abs(speed)) : 1;
|
|
512
|
+
this.applyPlaybackState(action, next);
|
|
513
|
+
this.setPlaybackState(clipName, next);
|
|
419
514
|
}
|
|
420
515
|
}
|
|
421
516
|
setAnimationIntensity(clipName, intensity) {
|
|
422
|
-
const action = this.
|
|
517
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
423
518
|
if (action) {
|
|
424
|
-
|
|
519
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
520
|
+
loop: true,
|
|
521
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
522
|
+
});
|
|
523
|
+
next.weight = Number.isFinite(intensity) ? Math.max(0, intensity) : 1;
|
|
524
|
+
action.setEffectiveWeight(next.weight);
|
|
525
|
+
this.setPlaybackState(clipName, next);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
setAnimationLoopMode(clipName, loopMode) {
|
|
529
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
530
|
+
if (!action) return;
|
|
531
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
532
|
+
loop: true,
|
|
533
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
534
|
+
});
|
|
535
|
+
next.loopMode = loopMode;
|
|
536
|
+
next.loop = loopMode !== "once";
|
|
537
|
+
this.applyPlaybackState(action, next);
|
|
538
|
+
this.setPlaybackState(clipName, next);
|
|
539
|
+
}
|
|
540
|
+
setAnimationRepeatCount(clipName, repeatCount) {
|
|
541
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
542
|
+
if (!action) return;
|
|
543
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
544
|
+
loop: true,
|
|
545
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
546
|
+
});
|
|
547
|
+
next.repeatCount = typeof repeatCount === "number" && Number.isFinite(repeatCount) ? Math.max(0, repeatCount) : void 0;
|
|
548
|
+
this.applyPlaybackState(action, next);
|
|
549
|
+
this.setPlaybackState(clipName, next);
|
|
550
|
+
}
|
|
551
|
+
setAnimationReverse(clipName, reverse) {
|
|
552
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
553
|
+
if (!action) return;
|
|
554
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
555
|
+
loop: true,
|
|
556
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
557
|
+
});
|
|
558
|
+
next.reverse = !!reverse;
|
|
559
|
+
this.applyPlaybackState(action, next);
|
|
560
|
+
this.setPlaybackState(clipName, next);
|
|
561
|
+
}
|
|
562
|
+
setAnimationBlendMode(clipName, blendMode) {
|
|
563
|
+
const action = this.getOrCreateBakedAction(clipName);
|
|
564
|
+
if (!action) return;
|
|
565
|
+
const next = this.getPlaybackStateSnapshot(clipName, {
|
|
566
|
+
loop: true,
|
|
567
|
+
source: this.clipSources.get(clipName) ?? "baked"
|
|
568
|
+
});
|
|
569
|
+
next.blendMode = blendMode;
|
|
570
|
+
this.applyPlaybackState(action, next);
|
|
571
|
+
this.setPlaybackState(clipName, next);
|
|
572
|
+
}
|
|
573
|
+
seekAnimation(clipName, time) {
|
|
574
|
+
const action = this.getOrCreateBakedAction(clipName) ?? this.animationActions.get(clipName);
|
|
575
|
+
if (!action) return;
|
|
576
|
+
const duration = action.getClip().duration;
|
|
577
|
+
action.time = Math.max(0, Math.min(duration, Number.isFinite(time) ? time : 0));
|
|
578
|
+
try {
|
|
579
|
+
this.animationMixer?.update(0);
|
|
580
|
+
} catch {
|
|
425
581
|
}
|
|
426
582
|
}
|
|
427
583
|
setAnimationTimeScale(timeScale) {
|
|
@@ -433,15 +589,29 @@ var BakedAnimationController = class {
|
|
|
433
589
|
const action = this.animationActions.get(clipName);
|
|
434
590
|
if (!action) return null;
|
|
435
591
|
const clip = action.getClip();
|
|
592
|
+
const state = this.playbackState.get(clipName);
|
|
593
|
+
const loopMode = state?.loopMode ?? (action.loop === THREE.LoopPingPong ? "pingpong" : action.loop === THREE.LoopOnce ? "once" : "repeat");
|
|
594
|
+
const playbackRate = state?.playbackRate ?? Math.abs(action.getEffectiveTimeScale());
|
|
595
|
+
const reverse = state?.reverse ?? action.getEffectiveTimeScale() < 0;
|
|
436
596
|
return {
|
|
437
597
|
name: clip.name,
|
|
598
|
+
actionId: this.getActionId(action),
|
|
599
|
+
source: state?.source ?? this.clipSources.get(clip.name) ?? "baked",
|
|
438
600
|
isPlaying: action.isRunning() && !action.paused,
|
|
439
601
|
isPaused: action.paused,
|
|
440
602
|
time: action.time,
|
|
441
603
|
duration: clip.duration,
|
|
442
|
-
speed:
|
|
443
|
-
|
|
444
|
-
|
|
604
|
+
speed: playbackRate,
|
|
605
|
+
playbackRate,
|
|
606
|
+
reverse,
|
|
607
|
+
weight: state?.weight ?? action.getEffectiveWeight(),
|
|
608
|
+
balance: state?.balance ?? 0,
|
|
609
|
+
blendMode: state?.blendMode ?? "replace",
|
|
610
|
+
easing: state?.easing ?? "linear",
|
|
611
|
+
loop: loopMode !== "once",
|
|
612
|
+
loopMode,
|
|
613
|
+
repeatCount: state?.repeatCount,
|
|
614
|
+
isLooping: loopMode !== "once"
|
|
445
615
|
};
|
|
446
616
|
}
|
|
447
617
|
getPlayingAnimations() {
|
|
@@ -698,14 +868,14 @@ var BakedAnimationController = class {
|
|
|
698
868
|
console.warn("[Loom3] playClip: No model loaded, cannot create mixer");
|
|
699
869
|
return null;
|
|
700
870
|
}
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
871
|
+
const playbackState = this.mergePlaybackOptions(
|
|
872
|
+
this.getPlaybackStateSnapshot(clip.name, {
|
|
873
|
+
loop: false,
|
|
874
|
+
source: options?.source ?? "clip"
|
|
875
|
+
}),
|
|
876
|
+
options
|
|
877
|
+
);
|
|
878
|
+
const startTime = this.resolveStartTime(clip.duration, playbackState, options?.startTime);
|
|
709
879
|
let action = this.clipActions.get(clip.name);
|
|
710
880
|
let actionId = this.getActionId(action);
|
|
711
881
|
if (action && !actionId) {
|
|
@@ -719,20 +889,7 @@ var BakedAnimationController = class {
|
|
|
719
889
|
if (!existingClip) {
|
|
720
890
|
this.animationClips.push(clip);
|
|
721
891
|
}
|
|
722
|
-
|
|
723
|
-
action.setEffectiveTimeScale(timeScale);
|
|
724
|
-
const weight = typeof mixerWeight === "number" ? mixerWeight : 1;
|
|
725
|
-
action.setEffectiveWeight(weight);
|
|
726
|
-
const mode = loopMode || (loop ? "repeat" : "once");
|
|
727
|
-
action.clampWhenFinished = mode === "once";
|
|
728
|
-
const reps = repeatCount ?? Infinity;
|
|
729
|
-
if (mode === "pingpong") {
|
|
730
|
-
action.setLoop(THREE.LoopPingPong, reps);
|
|
731
|
-
} else if (mode === "once") {
|
|
732
|
-
action.setLoop(THREE.LoopOnce, 1);
|
|
733
|
-
} else {
|
|
734
|
-
action.setLoop(THREE.LoopRepeat, reps);
|
|
735
|
-
}
|
|
892
|
+
this.applyPlaybackState(action, playbackState);
|
|
736
893
|
let resolveFinished;
|
|
737
894
|
const finishedPromise = new Promise((resolve) => {
|
|
738
895
|
resolveFinished = resolve;
|
|
@@ -753,15 +910,24 @@ var BakedAnimationController = class {
|
|
|
753
910
|
});
|
|
754
911
|
finishedPromise.catch(() => cleanup());
|
|
755
912
|
action.reset();
|
|
913
|
+
action.time = startTime;
|
|
756
914
|
action.play();
|
|
757
915
|
this.clipActions.set(clip.name, action);
|
|
758
916
|
this.animationActions.set(clip.name, action);
|
|
759
|
-
|
|
917
|
+
this.setPlaybackState(clip.name, playbackState);
|
|
918
|
+
console.log(`[Loom3] playClip: Playing "${clip.name}" (rate: ${playbackState.playbackRate}, loop: ${playbackState.loop}, actionId: ${actionId})`);
|
|
760
919
|
const handle = {
|
|
761
920
|
clipName: clip.name,
|
|
762
921
|
actionId,
|
|
763
922
|
play: () => {
|
|
764
923
|
action.reset();
|
|
924
|
+
action.time = this.resolveStartTime(
|
|
925
|
+
clip.duration,
|
|
926
|
+
this.getPlaybackStateSnapshot(clip.name, {
|
|
927
|
+
loop: false,
|
|
928
|
+
source: this.clipSources.get(clip.name) ?? playbackState.source
|
|
929
|
+
})
|
|
930
|
+
);
|
|
765
931
|
action.play();
|
|
766
932
|
},
|
|
767
933
|
stop: () => {
|
|
@@ -779,6 +945,7 @@ var BakedAnimationController = class {
|
|
|
779
945
|
this.clipActions.delete(clip.name);
|
|
780
946
|
this.animationActions.delete(clip.name);
|
|
781
947
|
this.animationFinishedCallbacks.delete(clip.name);
|
|
948
|
+
this.playbackState.delete(clip.name);
|
|
782
949
|
resolveFinished();
|
|
783
950
|
cleanup();
|
|
784
951
|
},
|
|
@@ -789,21 +956,24 @@ var BakedAnimationController = class {
|
|
|
789
956
|
action.paused = false;
|
|
790
957
|
},
|
|
791
958
|
setWeight: (w) => {
|
|
792
|
-
|
|
959
|
+
const next = this.playbackState.get(clip.name) ?? playbackState;
|
|
960
|
+
next.weight = typeof w === "number" && Number.isFinite(w) ? Math.max(0, w) : 1;
|
|
961
|
+
action.setEffectiveWeight(next.weight);
|
|
962
|
+
this.setPlaybackState(clip.name, next);
|
|
793
963
|
},
|
|
794
964
|
setPlaybackRate: (r) => {
|
|
795
|
-
const
|
|
796
|
-
|
|
965
|
+
const next = this.playbackState.get(clip.name) ?? playbackState;
|
|
966
|
+
next.playbackRate = Number.isFinite(r) ? Math.max(0, Math.abs(r)) : 1;
|
|
967
|
+
this.applyPlaybackState(action, next);
|
|
968
|
+
this.setPlaybackState(clip.name, next);
|
|
797
969
|
},
|
|
798
|
-
setLoop: (
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
action.setLoop(THREE.LoopRepeat, reps2);
|
|
806
|
-
}
|
|
970
|
+
setLoop: (mode, repeatCount) => {
|
|
971
|
+
const next = this.playbackState.get(clip.name) ?? playbackState;
|
|
972
|
+
next.loopMode = mode;
|
|
973
|
+
next.loop = mode !== "once";
|
|
974
|
+
next.repeatCount = repeatCount;
|
|
975
|
+
this.applyPlaybackState(action, next);
|
|
976
|
+
this.setPlaybackState(clip.name, next);
|
|
807
977
|
},
|
|
808
978
|
setTime: (t) => {
|
|
809
979
|
const clamped = Math.max(0, Math.min(clip.duration, t));
|
|
@@ -825,14 +995,14 @@ var BakedAnimationController = class {
|
|
|
825
995
|
if (!clip) {
|
|
826
996
|
return null;
|
|
827
997
|
}
|
|
828
|
-
return this.playClip(clip, options);
|
|
998
|
+
return this.playClip(clip, { ...options, source: options?.source ?? "snippet" });
|
|
829
999
|
}
|
|
830
1000
|
buildClip(clipName, curves, options) {
|
|
831
1001
|
const clip = this.snippetToClip(clipName, curves, options);
|
|
832
1002
|
if (!clip) {
|
|
833
1003
|
return null;
|
|
834
1004
|
}
|
|
835
|
-
return this.playClip(clip, options);
|
|
1005
|
+
return this.playClip(clip, { ...options, source: options?.source ?? "clip" });
|
|
836
1006
|
}
|
|
837
1007
|
cleanupSnippet(name) {
|
|
838
1008
|
if (!this.animationMixer || !this.host.getModel()) return;
|
|
@@ -851,6 +1021,7 @@ var BakedAnimationController = class {
|
|
|
851
1021
|
this.animationActions.delete(clipName);
|
|
852
1022
|
this.clipHandles.delete(clipName);
|
|
853
1023
|
this.animationFinishedCallbacks.delete(clipName);
|
|
1024
|
+
this.playbackState.delete(clipName);
|
|
854
1025
|
}
|
|
855
1026
|
}
|
|
856
1027
|
}
|
|
@@ -874,31 +1045,34 @@ var BakedAnimationController = class {
|
|
|
874
1045
|
console.log("[Loom3] updateClipParams start", debugSnapshot());
|
|
875
1046
|
const apply = (action) => {
|
|
876
1047
|
if (!action) return;
|
|
1048
|
+
const clipName = action.getClip().name;
|
|
1049
|
+
const next = this.playbackState.get(clipName) ?? this.normalizePlaybackOptions(void 0, { loop: false, source: this.clipSources.get(clipName) ?? "clip" });
|
|
877
1050
|
try {
|
|
878
1051
|
action.paused = false;
|
|
879
1052
|
} catch {
|
|
880
1053
|
}
|
|
881
1054
|
if (typeof params.weight === "number" && Number.isFinite(params.weight)) {
|
|
882
1055
|
action.setEffectiveWeight(params.weight);
|
|
1056
|
+
next.weight = Math.max(0, params.weight);
|
|
883
1057
|
updated = true;
|
|
884
1058
|
}
|
|
885
1059
|
if (typeof params.rate === "number" && Number.isFinite(params.rate)) {
|
|
886
|
-
|
|
1060
|
+
next.playbackRate = Math.max(0, Math.abs(params.rate));
|
|
1061
|
+
if (typeof params.reverse === "boolean") {
|
|
1062
|
+
next.reverse = params.reverse;
|
|
1063
|
+
}
|
|
1064
|
+
const signedRate = next.reverse ? -next.playbackRate : next.playbackRate;
|
|
887
1065
|
action.setEffectiveTimeScale(signedRate);
|
|
888
1066
|
updated = true;
|
|
889
1067
|
}
|
|
890
1068
|
if (typeof params.loop === "boolean" || params.loopMode || params.repeatCount !== void 0) {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
} else if (mode === "once") {
|
|
896
|
-
action.setLoop(THREE.LoopOnce, 1);
|
|
897
|
-
} else {
|
|
898
|
-
action.setLoop(THREE.LoopRepeat, reps);
|
|
899
|
-
}
|
|
1069
|
+
next.loopMode = params.loopMode || (params.loop ? "repeat" : "once");
|
|
1070
|
+
next.loop = next.loopMode !== "once";
|
|
1071
|
+
next.repeatCount = params.repeatCount;
|
|
1072
|
+
this.applyPlaybackState(action, next);
|
|
900
1073
|
updated = true;
|
|
901
1074
|
}
|
|
1075
|
+
this.setPlaybackState(clipName, next);
|
|
902
1076
|
};
|
|
903
1077
|
for (const [clipName, action] of this.clipActions.entries()) {
|
|
904
1078
|
if (matches(clipName, action)) {
|
|
@@ -988,14 +1162,13 @@ var BakedAnimationController = class {
|
|
|
988
1162
|
}
|
|
989
1163
|
createAnimationHandle(clipName, action, finishedPromise) {
|
|
990
1164
|
return {
|
|
1165
|
+
actionId: this.getActionId(action),
|
|
991
1166
|
stop: () => this.stopAnimation(clipName),
|
|
992
1167
|
pause: () => this.pauseAnimation(clipName),
|
|
993
1168
|
resume: () => this.resumeAnimation(clipName),
|
|
994
1169
|
setSpeed: (speed) => this.setAnimationSpeed(clipName, speed),
|
|
995
1170
|
setWeight: (weight) => this.setAnimationIntensity(clipName, weight),
|
|
996
|
-
seekTo: (time) =>
|
|
997
|
-
action.time = Math.max(0, Math.min(time, action.getClip().duration));
|
|
998
|
-
},
|
|
1171
|
+
seekTo: (time) => this.seekAnimation(clipName, time),
|
|
999
1172
|
getState: () => this.getAnimationState(clipName),
|
|
1000
1173
|
crossfadeTo: (targetClip, dur) => this.crossfadeTo(targetClip, dur),
|
|
1001
1174
|
finished: finishedPromise
|
|
@@ -3619,12 +3792,6 @@ var _Loom3 = class _Loom3 {
|
|
|
3619
3792
|
this.animation = animation || new AnimationThree();
|
|
3620
3793
|
this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
|
|
3621
3794
|
this.auToCompositeMap = buildAUToCompositeMap(this.compositeRotations);
|
|
3622
|
-
this.hairPhysics = new HairPhysicsController({
|
|
3623
|
-
getMeshByName: (name) => this.meshByName.get(name),
|
|
3624
|
-
getSelectedHairMeshNames: () => this.config.morphToMesh?.hair || [],
|
|
3625
|
-
buildClip: (clipName, curves, options) => this.buildClip(clipName, curves, options),
|
|
3626
|
-
cleanupSnippet: (name) => this.cleanupSnippet(name)
|
|
3627
|
-
});
|
|
3628
3795
|
this.bakedAnimations = new BakedAnimationController({
|
|
3629
3796
|
getModel: () => this.model,
|
|
3630
3797
|
getMeshes: () => this.meshes,
|
|
@@ -3638,6 +3805,13 @@ var _Loom3 = class _Loom3 {
|
|
|
3638
3805
|
getAUMixWeight: (auId) => this.getAUMixWeight(auId),
|
|
3639
3806
|
isMixedAU: (auId) => this.isMixedAU(auId)
|
|
3640
3807
|
});
|
|
3808
|
+
this.hairPhysics = new HairPhysicsController({
|
|
3809
|
+
getMeshByName: (name) => this.meshByName.get(name),
|
|
3810
|
+
getSelectedHairMeshNames: () => this.config.morphToMesh?.hair || [],
|
|
3811
|
+
// Hair physics needs clip construction, but mixer ownership still lives in BakedAnimationController.
|
|
3812
|
+
buildClip: (clipName, curves, options) => this.bakedAnimations.buildClip(clipName, curves, options),
|
|
3813
|
+
cleanupSnippet: (name) => this.bakedAnimations.cleanupSnippet(name)
|
|
3814
|
+
});
|
|
3641
3815
|
this.applyHairPhysicsProfileConfig();
|
|
3642
3816
|
}
|
|
3643
3817
|
// ============================================================================
|
|
@@ -4870,8 +5044,8 @@ var _Loom3 = class _Loom3 {
|
|
|
4870
5044
|
}
|
|
4871
5045
|
});
|
|
4872
5046
|
}
|
|
4873
|
-
//
|
|
4874
|
-
//
|
|
5047
|
+
// ============================================================================
|
|
5048
|
+
// MIXER / CLIP CONTROL
|
|
4875
5049
|
// ============================================================================
|
|
4876
5050
|
loadAnimationClips(clips) {
|
|
4877
5051
|
this.bakedAnimations.loadAnimationClips(clips);
|
|
@@ -4906,6 +5080,21 @@ var _Loom3 = class _Loom3 {
|
|
|
4906
5080
|
setAnimationIntensity(clipName, intensity) {
|
|
4907
5081
|
this.bakedAnimations.setAnimationIntensity(clipName, intensity);
|
|
4908
5082
|
}
|
|
5083
|
+
setAnimationLoopMode(clipName, loopMode) {
|
|
5084
|
+
this.bakedAnimations.setAnimationLoopMode(clipName, loopMode);
|
|
5085
|
+
}
|
|
5086
|
+
setAnimationRepeatCount(clipName, repeatCount) {
|
|
5087
|
+
this.bakedAnimations.setAnimationRepeatCount(clipName, repeatCount);
|
|
5088
|
+
}
|
|
5089
|
+
setAnimationReverse(clipName, reverse) {
|
|
5090
|
+
this.bakedAnimations.setAnimationReverse(clipName, reverse);
|
|
5091
|
+
}
|
|
5092
|
+
setAnimationBlendMode(clipName, blendMode) {
|
|
5093
|
+
this.bakedAnimations.setAnimationBlendMode(clipName, blendMode);
|
|
5094
|
+
}
|
|
5095
|
+
seekAnimation(clipName, time) {
|
|
5096
|
+
this.bakedAnimations.seekAnimation(clipName, time);
|
|
5097
|
+
}
|
|
4909
5098
|
setAnimationTimeScale(timeScale) {
|
|
4910
5099
|
this.bakedAnimations.setAnimationTimeScale(timeScale);
|
|
4911
5100
|
}
|