@zephyr3d/editor 0.3.5 → 0.3.7
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/assets/{index-DlnbO59X.js → index-ySiLtSxz.js} +2 -2
- package/dist/assistant/zephyr-types-index.json +14215 -7892
- package/dist/index.html +1 -1
- package/dist/modules/zephyr3d_backend-webgl.js.map +1 -1
- package/dist/modules/zephyr3d_backend-webgpu.js.map +1 -1
- package/dist/modules/zephyr3d_base.js +152 -152
- package/dist/modules/zephyr3d_base.js.map +1 -1
- package/dist/modules/zephyr3d_imgui.js.map +1 -1
- package/dist/modules/zephyr3d_loaders.js +6 -6
- package/dist/modules/zephyr3d_loaders.js.map +1 -1
- package/dist/modules/zephyr3d_scene.js +2538 -348
- package/dist/modules/zephyr3d_scene.js.map +1 -1
- package/dist/vendor/zephyr3d/backend-webgl/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/backend-webgpu/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/base/dist/vfs/common.js +152 -152
- package/dist/vendor/zephyr3d/base/dist/vfs/common.js.map +1 -1
- package/dist/vendor/zephyr3d/imgui/dist/index.d.ts +7 -0
- package/dist/vendor/zephyr3d/imgui/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js +6 -6
- package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/animation/animation.js +87 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animation.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/animation/animationcontroller.js +503 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animationcontroller.js.map +1 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js +782 -156
- package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/animation/animationtimeline.js +974 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animationtimeline.js.map +1 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js +320 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
- package/dist/vendor/zephyr3d/scene/dist/app/scriptregistry.js +130 -125
- package/dist/vendor/zephyr3d/scene/dist/app/scriptregistry.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/asset/model.js +64 -63
- package/dist/vendor/zephyr3d/scene/dist/asset/model.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/index.d.ts +1270 -9
- package/dist/vendor/zephyr3d/scene/dist/index.js +3 -1
- package/dist/vendor/zephyr3d/scene/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/material/mixins/lit.js +7 -3
- package/dist/vendor/zephyr3d/scene/dist/material/mixins/lit.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js +1 -0
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js +4 -3
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/node.js +3 -2
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/node.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Disposable, weightedAverage, Quaternion, Interpolator, Vector3 } from '@zephyr3d/base';
|
|
1
|
+
import { makeObservable, Disposable, weightedAverage, Quaternion, Observable, Interpolator, Vector3 } from '@zephyr3d/base';
|
|
2
2
|
import { AnimationClip } from './animation.js';
|
|
3
3
|
import { NodeRotationTrack } from './rotationtrack.js';
|
|
4
4
|
import { NodeEulerRotationTrack } from './eulerrotationtrack.js';
|
|
@@ -7,6 +7,224 @@ import { NodeScaleTrack } from './scaletrack.js';
|
|
|
7
7
|
import { HumanoidBodyRig } from './skeleton.js';
|
|
8
8
|
import { createSkeletalMaskedAnimationClip } from './animationmask.js';
|
|
9
9
|
|
|
10
|
+
let nextAnimationPlaybackId = 1;
|
|
11
|
+
/**
|
|
12
|
+
* Runtime handle for one playback of an AnimationClip.
|
|
13
|
+
* @public
|
|
14
|
+
*/ class AnimationPlayback extends Observable {
|
|
15
|
+
/** @internal */ _animationSet;
|
|
16
|
+
/** @internal */ _clip;
|
|
17
|
+
/** @internal */ _options;
|
|
18
|
+
/** @internal */ _id;
|
|
19
|
+
/** @internal */ _frameWaiters;
|
|
20
|
+
/** @internal */ _state;
|
|
21
|
+
/** @internal */ _time;
|
|
22
|
+
/** @internal */ _weight;
|
|
23
|
+
/** @internal */ _speedRatio;
|
|
24
|
+
/** @internal */ _layer;
|
|
25
|
+
/** @internal */ _priority;
|
|
26
|
+
/** @internal */ _interruptible;
|
|
27
|
+
constructor(animationSet, clip, options){
|
|
28
|
+
super();
|
|
29
|
+
this._animationSet = animationSet;
|
|
30
|
+
this._clip = clip;
|
|
31
|
+
this._options = {
|
|
32
|
+
...options ?? {}
|
|
33
|
+
};
|
|
34
|
+
this._id = this._options.id ?? `animation-playback-${nextAnimationPlaybackId++}`;
|
|
35
|
+
this._state = 'scheduled';
|
|
36
|
+
this._speedRatio = this._options.speedRatio ?? 1;
|
|
37
|
+
this._weight = this._options.weight ?? clip.weight ?? 1;
|
|
38
|
+
this._time = this._speedRatio < 0 ? clip.timeDuration : 0;
|
|
39
|
+
this._layer = this._options.layer ?? 'default';
|
|
40
|
+
this._priority = this._options.priority ?? 0;
|
|
41
|
+
this._interruptible = this._options.interruptible ?? true;
|
|
42
|
+
this._frameWaiters = new Map();
|
|
43
|
+
}
|
|
44
|
+
get id() {
|
|
45
|
+
return this._id;
|
|
46
|
+
}
|
|
47
|
+
get animationSet() {
|
|
48
|
+
return this._animationSet;
|
|
49
|
+
}
|
|
50
|
+
get clip() {
|
|
51
|
+
return this._clip;
|
|
52
|
+
}
|
|
53
|
+
get state() {
|
|
54
|
+
return this._state;
|
|
55
|
+
}
|
|
56
|
+
get time() {
|
|
57
|
+
return this._time;
|
|
58
|
+
}
|
|
59
|
+
set time(value) {
|
|
60
|
+
this.seek(value);
|
|
61
|
+
}
|
|
62
|
+
get normalizedTime() {
|
|
63
|
+
return this._clip.timeDuration > 0 ? this._time / this._clip.timeDuration : 0;
|
|
64
|
+
}
|
|
65
|
+
set normalizedTime(value) {
|
|
66
|
+
this.seek(value * this._clip.timeDuration);
|
|
67
|
+
}
|
|
68
|
+
get weight() {
|
|
69
|
+
return this._weight;
|
|
70
|
+
}
|
|
71
|
+
set weight(value) {
|
|
72
|
+
this._animationSet.setPlaybackWeight(this, value);
|
|
73
|
+
}
|
|
74
|
+
get speedRatio() {
|
|
75
|
+
return this._speedRatio;
|
|
76
|
+
}
|
|
77
|
+
set speedRatio(value) {
|
|
78
|
+
this._animationSet.setPlaybackSpeedRatio(this, value);
|
|
79
|
+
}
|
|
80
|
+
get layer() {
|
|
81
|
+
return this._layer;
|
|
82
|
+
}
|
|
83
|
+
get priority() {
|
|
84
|
+
return this._priority;
|
|
85
|
+
}
|
|
86
|
+
get interruptible() {
|
|
87
|
+
return this._interruptible;
|
|
88
|
+
}
|
|
89
|
+
play() {
|
|
90
|
+
this._animationSet.startPlayback(this);
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
pause() {
|
|
94
|
+
this._animationSet.pausePlayback(this);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
resume() {
|
|
98
|
+
this._animationSet.resumePlayback(this);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
stop(options) {
|
|
102
|
+
this._animationSet.stopPlayback(this, options);
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
seek(time, options) {
|
|
106
|
+
this._animationSet.seekPlayback(this, time, options);
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
fadeTo(weight, duration) {
|
|
110
|
+
this._animationSet.fadePlaybackTo(this, weight, duration);
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
crossFadeTo(name, options) {
|
|
114
|
+
const duration = Math.max(options?.duration ?? 0, 0);
|
|
115
|
+
this.stop({
|
|
116
|
+
fadeOut: duration,
|
|
117
|
+
reason: 'interrupted'
|
|
118
|
+
});
|
|
119
|
+
return this._animationSet.play(name, {
|
|
120
|
+
...options,
|
|
121
|
+
fadeIn: duration
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
waitForComplete() {
|
|
125
|
+
return new Promise((resolve)=>{
|
|
126
|
+
this.once('complete', ()=>resolve(this));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
waitForMarker(idOrName) {
|
|
130
|
+
if (this._state === 'stopped' || this._state === 'completed') {
|
|
131
|
+
return Promise.resolve(undefined);
|
|
132
|
+
}
|
|
133
|
+
return new Promise((resolve)=>{
|
|
134
|
+
const handler = (event)=>{
|
|
135
|
+
if (event.marker.id === idOrName || event.marker.name === idOrName) {
|
|
136
|
+
this.off('marker', handler);
|
|
137
|
+
this.off('stop', stop);
|
|
138
|
+
resolve(event);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const stop = ()=>{
|
|
142
|
+
this.off('marker', handler);
|
|
143
|
+
this.off('stop', stop);
|
|
144
|
+
resolve(undefined);
|
|
145
|
+
};
|
|
146
|
+
this.on('marker', handler);
|
|
147
|
+
this.on('stop', stop);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
waitForFrame(frame) {
|
|
151
|
+
if (this._state === 'stopped' || this._state === 'completed') {
|
|
152
|
+
return Promise.resolve(undefined);
|
|
153
|
+
}
|
|
154
|
+
return new Promise((resolve)=>{
|
|
155
|
+
const waiters = this._frameWaiters.get(frame) ?? [];
|
|
156
|
+
let settled = false;
|
|
157
|
+
let stop;
|
|
158
|
+
const wrappedResolve = (event)=>{
|
|
159
|
+
if (settled) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
settled = true;
|
|
163
|
+
this.off('stop', stop);
|
|
164
|
+
resolve(event);
|
|
165
|
+
};
|
|
166
|
+
stop = ()=>{
|
|
167
|
+
const current = this._frameWaiters.get(frame);
|
|
168
|
+
if (current) {
|
|
169
|
+
this._frameWaiters.set(frame, current.filter((item)=>item !== wrappedResolve));
|
|
170
|
+
}
|
|
171
|
+
wrappedResolve(undefined);
|
|
172
|
+
};
|
|
173
|
+
waiters.push(wrappedResolve);
|
|
174
|
+
this._frameWaiters.set(frame, waiters);
|
|
175
|
+
this.on('stop', stop);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/** @internal */ _getOptions() {
|
|
179
|
+
return this._options;
|
|
180
|
+
}
|
|
181
|
+
/** @internal */ _setState(state) {
|
|
182
|
+
this._state = state;
|
|
183
|
+
}
|
|
184
|
+
/** @internal */ _setTime(time) {
|
|
185
|
+
this._time = time;
|
|
186
|
+
}
|
|
187
|
+
/** @internal */ _setWeight(weight) {
|
|
188
|
+
this._weight = weight;
|
|
189
|
+
}
|
|
190
|
+
/** @internal */ _setSpeedRatio(speedRatio) {
|
|
191
|
+
this._speedRatio = speedRatio;
|
|
192
|
+
}
|
|
193
|
+
/** @internal */ _getWatchedFrames() {
|
|
194
|
+
return [
|
|
195
|
+
...this._frameWaiters.keys()
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
/** @internal */ _emitStart(event) {
|
|
199
|
+
this.dispatchEvent('start', event);
|
|
200
|
+
}
|
|
201
|
+
/** @internal */ _emitLoop(event) {
|
|
202
|
+
this.dispatchEvent('loop', event);
|
|
203
|
+
}
|
|
204
|
+
/** @internal */ _emitMarker(event) {
|
|
205
|
+
this.dispatchEvent('marker', event);
|
|
206
|
+
}
|
|
207
|
+
/** @internal */ _emitFrame(event) {
|
|
208
|
+
this.dispatchEvent('frame', event);
|
|
209
|
+
const waiters = this._frameWaiters.get(event.frame);
|
|
210
|
+
if (waiters) {
|
|
211
|
+
this._frameWaiters.delete(event.frame);
|
|
212
|
+
waiters.forEach((resolve)=>resolve(event));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** @internal */ _emitComplete(event) {
|
|
216
|
+
this.dispatchEvent('complete', event);
|
|
217
|
+
}
|
|
218
|
+
/** @internal */ _emitStop(event) {
|
|
219
|
+
this.dispatchEvent('stop', event);
|
|
220
|
+
}
|
|
221
|
+
/** @internal */ _emitPause(event) {
|
|
222
|
+
this.dispatchEvent('pause', event);
|
|
223
|
+
}
|
|
224
|
+
/** @internal */ _emitResume(event) {
|
|
225
|
+
this.dispatchEvent('resume', event);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
10
228
|
function cloneInterpolator(src) {
|
|
11
229
|
return new Interpolator(src.mode, src.target, src.inputs instanceof Float32Array ? new Float32Array(src.inputs) : [
|
|
12
230
|
...src.inputs
|
|
@@ -388,26 +606,26 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
388
606
|
dstClip.addTrack(remap.dstNode, track);
|
|
389
607
|
}
|
|
390
608
|
}
|
|
391
|
-
/**
|
|
392
|
-
* Animation set
|
|
393
|
-
*
|
|
394
|
-
* Manages a collection of named animation clips for a model and orchestrates:
|
|
395
|
-
* - Playback state (time, loops, speed, weights, fade-in/out).
|
|
396
|
-
* - Blending across multiple tracks targeting the same property via weighted averages.
|
|
397
|
-
* - Skeleton usage and application for clips that drive skeletal animation.
|
|
398
|
-
* - Active track registration and cleanup as clips start/stop.
|
|
399
|
-
*
|
|
400
|
-
* Usage:
|
|
401
|
-
* - Create or retrieve `AnimationClip`s by name.
|
|
402
|
-
* - Start playback with `playAnimation(name, options)`.
|
|
403
|
-
* - Advance animation with `update(deltaSeconds)`.
|
|
404
|
-
* - Optionally adjust weight while playing with `setAnimationWeight(name, weight)`.
|
|
405
|
-
*
|
|
406
|
-
* Lifetime:
|
|
407
|
-
* - Disposing the set releases references to the model, clips, and clears active state.
|
|
408
|
-
*
|
|
409
|
-
* @public
|
|
410
|
-
*/ class AnimationSet extends Disposable {
|
|
609
|
+
/**
|
|
610
|
+
* Animation set
|
|
611
|
+
*
|
|
612
|
+
* Manages a collection of named animation clips for a model and orchestrates:
|
|
613
|
+
* - Playback state (time, loops, speed, weights, fade-in/out).
|
|
614
|
+
* - Blending across multiple tracks targeting the same property via weighted averages.
|
|
615
|
+
* - Skeleton usage and application for clips that drive skeletal animation.
|
|
616
|
+
* - Active track registration and cleanup as clips start/stop.
|
|
617
|
+
*
|
|
618
|
+
* Usage:
|
|
619
|
+
* - Create or retrieve `AnimationClip`s by name.
|
|
620
|
+
* - Start playback with `playAnimation(name, options)`.
|
|
621
|
+
* - Advance animation with `update(deltaSeconds)`.
|
|
622
|
+
* - Optionally adjust weight while playing with `setAnimationWeight(name, weight)`.
|
|
623
|
+
*
|
|
624
|
+
* Lifetime:
|
|
625
|
+
* - Disposing the set releases references to the model, clips, and clears active state.
|
|
626
|
+
*
|
|
627
|
+
* @public
|
|
628
|
+
*/ class AnimationSet extends makeObservable(Disposable)() {
|
|
411
629
|
/** @internal */ _model;
|
|
412
630
|
/** @internal */ _animations;
|
|
413
631
|
/** @internal */ _skeletons;
|
|
@@ -416,10 +634,18 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
416
634
|
/** @internal */ _activeSkinBindings;
|
|
417
635
|
/** @internal */ _activeRigs;
|
|
418
636
|
/** @internal */ _activeAnimations;
|
|
419
|
-
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
637
|
+
/**
|
|
638
|
+
* Callbacks driven by the same logical clock as the animations (see {@link update}).
|
|
639
|
+
*
|
|
640
|
+
* Used by higher-level constructs (e.g. timeline runners) that need to advance
|
|
641
|
+
* wall-clock-independent timers in sync with playback, so they pause/scale with the
|
|
642
|
+
* game clock instead of running on real time.
|
|
643
|
+
* @internal
|
|
644
|
+
*/ _timelineTickers;
|
|
645
|
+
/**
|
|
646
|
+
* Create an AnimationSet controlling the provided model.
|
|
647
|
+
*
|
|
648
|
+
* @param model - The SceneNode (model root) controlled by this animation set.
|
|
423
649
|
*/ constructor(model){
|
|
424
650
|
super();
|
|
425
651
|
this._model = model;
|
|
@@ -430,29 +656,42 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
430
656
|
this._activeAnimations = new Map();
|
|
431
657
|
this._skeletons = [];
|
|
432
658
|
this._rigs = [];
|
|
659
|
+
this._timelineTickers = new Set();
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Register a callback advanced by {@link update} using the same delta time as the animations.
|
|
663
|
+
* @internal
|
|
664
|
+
*/ _registerTimelineTicker(ticker) {
|
|
665
|
+
this._timelineTickers.add(ticker);
|
|
433
666
|
}
|
|
434
|
-
/**
|
|
435
|
-
*
|
|
667
|
+
/**
|
|
668
|
+
* Unregister a callback previously registered with {@link _registerTimelineTicker}.
|
|
669
|
+
* @internal
|
|
670
|
+
*/ _unregisterTimelineTicker(ticker) {
|
|
671
|
+
this._timelineTickers.delete(ticker);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* The model (SceneNode) controlled by this animation set.
|
|
436
675
|
*/ get model() {
|
|
437
676
|
return this._model;
|
|
438
677
|
}
|
|
439
|
-
/**
|
|
440
|
-
* Number of animation clips registered in this set.
|
|
678
|
+
/**
|
|
679
|
+
* Number of animation clips registered in this set.
|
|
441
680
|
*/ get numAnimations() {
|
|
442
681
|
return Object.getOwnPropertyNames(this._animations).length;
|
|
443
682
|
}
|
|
444
|
-
/**
|
|
445
|
-
* The skeletons used by animations in this set.
|
|
683
|
+
/**
|
|
684
|
+
* The skeletons used by animations in this set.
|
|
446
685
|
*/ get skeletons() {
|
|
447
686
|
return this._skeletons;
|
|
448
687
|
}
|
|
449
|
-
/**
|
|
450
|
-
* The shared rigs used by animations in this set.
|
|
688
|
+
/**
|
|
689
|
+
* The shared rigs used by animations in this set.
|
|
451
690
|
*/ get rigs() {
|
|
452
691
|
return this._rigs;
|
|
453
692
|
}
|
|
454
|
-
/**
|
|
455
|
-
* Per-skin bindings used by skinned meshes in this set.
|
|
693
|
+
/**
|
|
694
|
+
* Per-skin bindings used by skinned meshes in this set.
|
|
456
695
|
*/ get skinBindings() {
|
|
457
696
|
return this._skeletons;
|
|
458
697
|
}
|
|
@@ -474,20 +713,20 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
474
713
|
}
|
|
475
714
|
return rig;
|
|
476
715
|
}
|
|
477
|
-
/**
|
|
478
|
-
* Retrieve an animation clip by name.
|
|
479
|
-
*
|
|
480
|
-
* @param name - Name of the animation.
|
|
481
|
-
* @returns The clip if present; otherwise null.
|
|
716
|
+
/**
|
|
717
|
+
* Retrieve an animation clip by name.
|
|
718
|
+
*
|
|
719
|
+
* @param name - Name of the animation.
|
|
720
|
+
* @returns The clip if present; otherwise null.
|
|
482
721
|
*/ get(name) {
|
|
483
722
|
return this._animations[name] ?? null;
|
|
484
723
|
}
|
|
485
|
-
/**
|
|
486
|
-
* Create and register a new animation clip.
|
|
487
|
-
*
|
|
488
|
-
* @param name - Unique name for the animation clip.
|
|
489
|
-
* @param embedded - Whether the clip is embedded/owned (implementation-specific). Default false.
|
|
490
|
-
* @returns The created clip, or null if the name is empty or not unique.
|
|
724
|
+
/**
|
|
725
|
+
* Create and register a new animation clip.
|
|
726
|
+
*
|
|
727
|
+
* @param name - Unique name for the animation clip.
|
|
728
|
+
* @param embedded - Whether the clip is embedded/owned (implementation-specific). Default false.
|
|
729
|
+
* @returns The created clip, or null if the name is empty or not unique.
|
|
491
730
|
*/ createAnimation(name, embedded = false) {
|
|
492
731
|
if (!name || this._animations[name]) {
|
|
493
732
|
console.error('Animation must have unique name');
|
|
@@ -499,12 +738,12 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
499
738
|
return animation;
|
|
500
739
|
}
|
|
501
740
|
}
|
|
502
|
-
/**
|
|
503
|
-
* Delete and dispose an animation clip by name.
|
|
504
|
-
*
|
|
505
|
-
* - If the animation is currently playing, it is first stopped (immediately).
|
|
506
|
-
*
|
|
507
|
-
* @param name - Name of the animation to remove.
|
|
741
|
+
/**
|
|
742
|
+
* Delete and dispose an animation clip by name.
|
|
743
|
+
*
|
|
744
|
+
* - If the animation is currently playing, it is first stopped (immediately).
|
|
745
|
+
*
|
|
746
|
+
* @param name - Name of the animation to remove.
|
|
508
747
|
*/ deleteAnimation(name) {
|
|
509
748
|
const animation = this._animations[name];
|
|
510
749
|
if (animation) {
|
|
@@ -513,50 +752,119 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
513
752
|
animation.dispose();
|
|
514
753
|
}
|
|
515
754
|
}
|
|
516
|
-
/**
|
|
517
|
-
* Get the list of all registered animation names.
|
|
518
|
-
*
|
|
519
|
-
* @returns An array of clip names.
|
|
755
|
+
/**
|
|
756
|
+
* Get the list of all registered animation names.
|
|
757
|
+
*
|
|
758
|
+
* @returns An array of clip names.
|
|
520
759
|
*/ getAnimationNames() {
|
|
521
760
|
return Object.keys(this._animations);
|
|
522
761
|
}
|
|
523
|
-
/**
|
|
524
|
-
* Advance and apply active animations.
|
|
525
|
-
*
|
|
526
|
-
* Responsibilities per call:
|
|
527
|
-
* - Update time cursor for each active clip (respecting speedRatio and looping).
|
|
528
|
-
* - Enforce repeat limits and apply fade-out termination if configured.
|
|
529
|
-
* - For each animated target, blend active tracks (weighted by clip weight × fade-in × fade-out)
|
|
530
|
-
* and apply the resulting state to the target.
|
|
531
|
-
* - Apply shared rig modifiers once, then update skin binding palettes.
|
|
532
|
-
*
|
|
533
|
-
* @param deltaInSeconds - Time step in seconds since last update.
|
|
762
|
+
/**
|
|
763
|
+
* Advance and apply active animations.
|
|
764
|
+
*
|
|
765
|
+
* Responsibilities per call:
|
|
766
|
+
* - Update time cursor for each active clip (respecting speedRatio and looping).
|
|
767
|
+
* - Enforce repeat limits and apply fade-out termination if configured.
|
|
768
|
+
* - For each animated target, blend active tracks (weighted by clip weight × fade-in × fade-out)
|
|
769
|
+
* and apply the resulting state to the target.
|
|
770
|
+
* - Apply shared rig modifiers once, then update skin binding palettes.
|
|
771
|
+
*
|
|
772
|
+
* @param deltaInSeconds - Time step in seconds since last update.
|
|
534
773
|
*/ update(deltaInSeconds) {
|
|
535
774
|
this._activeAnimations.forEach((v, k)=>{
|
|
775
|
+
if (!v.started) {
|
|
776
|
+
v.started = true;
|
|
777
|
+
const event = this.createPlaybackEvent(v, k);
|
|
778
|
+
v.playback._emitStart(event);
|
|
779
|
+
this.dispatchEvent('playbackstart', event);
|
|
780
|
+
}
|
|
781
|
+
if (v.paused) {
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
536
784
|
if (v.fadeOut > 0 && v.fadeOutStart < 0) {
|
|
537
785
|
v.fadeOutStart = v.animateTime;
|
|
538
786
|
}
|
|
787
|
+
if (v.completed) {
|
|
788
|
+
v.animateTime += Math.abs(deltaInSeconds * v.speedRatio);
|
|
789
|
+
if (v.fadeOut > 0 && v.animateTime - v.fadeOutStart >= v.fadeOut) {
|
|
790
|
+
this.stopAnimation(k.name, {
|
|
791
|
+
reason: v.stopReason
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
539
796
|
// Update animation time
|
|
540
797
|
if (v.firstFrame) {
|
|
541
798
|
v.firstFrame = false;
|
|
542
799
|
} else {
|
|
800
|
+
const previousTime = v.currentTime;
|
|
543
801
|
const timeAdvance = deltaInSeconds * v.speedRatio;
|
|
544
802
|
v.currentTime += timeAdvance;
|
|
545
803
|
v.animateTime += timeAdvance;
|
|
804
|
+
const direction = timeAdvance >= 0 ? 1 : -1;
|
|
546
805
|
if (k.timeDuration > 0) {
|
|
547
|
-
if (v.currentTime
|
|
548
|
-
v.
|
|
806
|
+
if (v.rangeEnd !== null && v.speedRatio >= 0 && v.currentTime >= v.rangeEnd) {
|
|
807
|
+
v.currentTime = v.rangeEnd;
|
|
808
|
+
v.playback._setTime(v.currentTime);
|
|
809
|
+
this.completePlayback(k, v);
|
|
810
|
+
return;
|
|
811
|
+
} else if (v.rangeStart !== null && v.speedRatio < 0 && v.currentTime <= v.rangeStart) {
|
|
812
|
+
v.currentTime = v.rangeStart;
|
|
813
|
+
v.playback._setTime(v.currentTime);
|
|
814
|
+
this.completePlayback(k, v);
|
|
815
|
+
return;
|
|
816
|
+
} else if (v.currentTime > k.timeDuration) {
|
|
817
|
+
const loops = Math.max(1, Math.floor(v.currentTime / k.timeDuration));
|
|
818
|
+
if (v.repeat !== 0 && v.repeatCounter + loops >= v.repeat) {
|
|
819
|
+
v.repeatCounter += loops;
|
|
820
|
+
v.currentTime = k.timeDuration;
|
|
821
|
+
v.playback._setTime(v.currentTime);
|
|
822
|
+
this.emitCrossedMarkers(v, k, previousTime, v.currentTime, direction);
|
|
823
|
+
this.emitCrossedFrames(v, k, previousTime, v.currentTime, direction);
|
|
824
|
+
this.completePlayback(k, v);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
v.repeatCounter += loops;
|
|
549
828
|
v.currentTime %= k.timeDuration;
|
|
829
|
+
const event = this.createPlaybackEvent(v, k);
|
|
830
|
+
v.playback._emitLoop(event);
|
|
831
|
+
this.dispatchEvent('playbackloop', event);
|
|
550
832
|
} else if (v.currentTime < 0) {
|
|
551
|
-
|
|
833
|
+
const loops = Math.max(1, Math.ceil(-v.currentTime / k.timeDuration));
|
|
834
|
+
if (v.repeat !== 0 && v.repeatCounter + loops >= v.repeat) {
|
|
835
|
+
v.repeatCounter += loops;
|
|
836
|
+
v.currentTime = 0;
|
|
837
|
+
v.playback._setTime(v.currentTime);
|
|
838
|
+
this.emitCrossedMarkers(v, k, previousTime, v.currentTime, direction);
|
|
839
|
+
this.emitCrossedFrames(v, k, previousTime, v.currentTime, direction);
|
|
840
|
+
this.completePlayback(k, v);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
v.repeatCounter += loops;
|
|
552
844
|
v.currentTime = (v.currentTime % k.timeDuration + k.timeDuration) % k.timeDuration;
|
|
845
|
+
const event = this.createPlaybackEvent(v, k);
|
|
846
|
+
v.playback._emitLoop(event);
|
|
847
|
+
this.dispatchEvent('playbackloop', event);
|
|
553
848
|
}
|
|
554
849
|
}
|
|
850
|
+
v.playback._setTime(v.currentTime);
|
|
851
|
+
this.emitCrossedMarkers(v, k, previousTime, v.currentTime, direction);
|
|
852
|
+
this.emitCrossedFrames(v, k, previousTime, v.currentTime, direction);
|
|
555
853
|
if (v.repeat !== 0 && v.repeatCounter >= v.repeat) {
|
|
556
|
-
this.
|
|
854
|
+
this.completePlayback(k, v);
|
|
557
855
|
} else if (v.fadeOut > 0) {
|
|
558
856
|
if (v.animateTime - v.fadeOutStart >= v.fadeOut) {
|
|
559
|
-
this.stopAnimation(k.name
|
|
857
|
+
this.stopAnimation(k.name, {
|
|
858
|
+
reason: v.stopReason
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (v.fadeToDuration > 0) {
|
|
863
|
+
const t = Math.min(1, (v.animateTime - v.fadeToStart) / v.fadeToDuration);
|
|
864
|
+
v.weight = v.fadeFromWeight + (v.fadeTargetWeight - v.fadeFromWeight) * t;
|
|
865
|
+
v.playback._setWeight(v.weight);
|
|
866
|
+
if (t >= 1) {
|
|
867
|
+
v.fadeToDuration = 0;
|
|
560
868
|
}
|
|
561
869
|
}
|
|
562
870
|
}
|
|
@@ -596,12 +904,17 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
596
904
|
this._skeletons.forEach((v)=>{
|
|
597
905
|
v.get()?.apply();
|
|
598
906
|
});
|
|
907
|
+
// Advance logical-clock timers (e.g. timeline `wait` steps) with the same delta time,
|
|
908
|
+
// so they stay in sync with playback and pause/scale with the game clock.
|
|
909
|
+
if (this._timelineTickers.size > 0) {
|
|
910
|
+
this._timelineTickers.forEach((ticker)=>ticker(deltaInSeconds));
|
|
911
|
+
}
|
|
599
912
|
}
|
|
600
|
-
/**
|
|
601
|
-
* Check whether an animation is currently playing.
|
|
602
|
-
*
|
|
603
|
-
* @param name - Optional animation name. If omitted, returns true if any animation is playing.
|
|
604
|
-
* @returns True if playing; otherwise false.
|
|
913
|
+
/**
|
|
914
|
+
* Check whether an animation is currently playing.
|
|
915
|
+
*
|
|
916
|
+
* @param name - Optional animation name. If omitted, returns true if any animation is playing.
|
|
917
|
+
* @returns True if playing; otherwise false.
|
|
605
918
|
*/ isPlayingAnimation(name) {
|
|
606
919
|
if (name) {
|
|
607
920
|
const animation = this._animations[name];
|
|
@@ -609,23 +922,23 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
609
922
|
}
|
|
610
923
|
return this._activeAnimations.size > 0;
|
|
611
924
|
}
|
|
612
|
-
/**
|
|
613
|
-
* Get an animation clip by name.
|
|
614
|
-
*
|
|
615
|
-
* Alias of `get(name)` returning a nullable type.
|
|
616
|
-
*
|
|
617
|
-
* @param name - Name of the animation.
|
|
618
|
-
* @returns The clip if present; otherwise null.
|
|
925
|
+
/**
|
|
926
|
+
* Get an animation clip by name.
|
|
927
|
+
*
|
|
928
|
+
* Alias of `get(name)` returning a nullable type.
|
|
929
|
+
*
|
|
930
|
+
* @param name - Name of the animation.
|
|
931
|
+
* @returns The clip if present; otherwise null.
|
|
619
932
|
*/ getAnimationClip(name) {
|
|
620
933
|
return this._animations[name] ?? null;
|
|
621
934
|
}
|
|
622
|
-
/**
|
|
623
|
-
* Set the runtime blend weight for a currently playing animation.
|
|
624
|
-
*
|
|
625
|
-
* Has no effect if the clip is not active.
|
|
626
|
-
*
|
|
627
|
-
* @param name - Name of the playing animation.
|
|
628
|
-
* @param weight - New weight value used during blending.
|
|
935
|
+
/**
|
|
936
|
+
* Set the runtime blend weight for a currently playing animation.
|
|
937
|
+
*
|
|
938
|
+
* Has no effect if the clip is not active.
|
|
939
|
+
*
|
|
940
|
+
* @param name - Name of the playing animation.
|
|
941
|
+
* @param weight - New weight value used during blending.
|
|
629
942
|
*/ setAnimationWeight(name, weight) {
|
|
630
943
|
const ani = this._animations[name];
|
|
631
944
|
if (!ani) {
|
|
@@ -635,42 +948,104 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
635
948
|
const info = this._activeAnimations.get(ani);
|
|
636
949
|
if (info) {
|
|
637
950
|
info.weight = weight;
|
|
951
|
+
info.targetWeight = weight;
|
|
952
|
+
info.playback._setWeight(weight);
|
|
638
953
|
}
|
|
639
954
|
}
|
|
640
|
-
/**
|
|
641
|
-
*
|
|
642
|
-
|
|
643
|
-
* Behavior:
|
|
644
|
-
* - If the clip is already playing, stops it first.
|
|
645
|
-
* - Otherwise initializes playback state (repeat counter, speed, weight, initial time).
|
|
646
|
-
* - Registers clip tracks and skeletons into the active sets for blending and application.
|
|
647
|
-
*
|
|
648
|
-
* @param name - Name of the animation to play.
|
|
649
|
-
* @param options - Playback options (repeat, speedRatio, fadeIn).
|
|
650
|
-
*/ playAnimation(name, options) {
|
|
955
|
+
/**
|
|
956
|
+
* Create a playback handle without starting it.
|
|
957
|
+
*/ createPlayback(name, options) {
|
|
651
958
|
const ani = this._animations[name];
|
|
652
959
|
if (!ani) {
|
|
653
960
|
console.error(`Animation ${name} not exists`);
|
|
654
|
-
return;
|
|
961
|
+
return null;
|
|
655
962
|
}
|
|
656
|
-
|
|
657
|
-
|
|
963
|
+
return new AnimationPlayback(this, ani, options);
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Start an animation and return its playback handle.
|
|
967
|
+
*/ play(name, options) {
|
|
968
|
+
const playback = this.createPlayback(name, options);
|
|
969
|
+
playback?.play();
|
|
970
|
+
return playback;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Get currently active playbacks.
|
|
974
|
+
*/ getPlaybacks(name) {
|
|
975
|
+
const playbacks = [];
|
|
976
|
+
this._activeAnimations.forEach((info, clip)=>{
|
|
977
|
+
if (!name || clip.name === name) {
|
|
978
|
+
playbacks.push(info.playback);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
return playbacks;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Get the currently active playback for a clip.
|
|
985
|
+
*/ getPlayback(name) {
|
|
986
|
+
const clip = this._animations[name];
|
|
987
|
+
return clip ? this._activeAnimations.get(clip)?.playback ?? null : null;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Start (or update) playback of an animation clip.
|
|
991
|
+
*
|
|
992
|
+
* Behavior:
|
|
993
|
+
* - If the clip is already playing, stops it first.
|
|
994
|
+
* - Otherwise initializes playback state (repeat counter, speed, weight, initial time).
|
|
995
|
+
* - Registers clip tracks and skeletons into the active sets for blending and application.
|
|
996
|
+
*
|
|
997
|
+
* @param name - Name of the animation to play.
|
|
998
|
+
* @param options - Playback options (repeat, speedRatio, fadeIn).
|
|
999
|
+
*/ playAnimation(name, options) {
|
|
1000
|
+
this.play(name, options);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Start a previously created playback.
|
|
1004
|
+
* @internal
|
|
1005
|
+
*/ startPlayback(playback) {
|
|
1006
|
+
const ani = playback.clip;
|
|
1007
|
+
if (!ani) {
|
|
1008
|
+
return;
|
|
658
1009
|
}
|
|
1010
|
+
const options = playback._getOptions();
|
|
659
1011
|
const fadeIn = Math.max(options?.fadeIn ?? 0, 0);
|
|
660
1012
|
const repeat = options?.repeat ?? 0;
|
|
661
|
-
const speedRatio =
|
|
662
|
-
const weight =
|
|
1013
|
+
const speedRatio = playback.speedRatio;
|
|
1014
|
+
const weight = playback.weight;
|
|
1015
|
+
const rangeStart = ani.resolveTimeRef(options?.range?.start);
|
|
1016
|
+
const rangeEnd = ani.resolveTimeRef(options?.range?.end);
|
|
1017
|
+
const syncSource = this.resolvePlaybackSyncSource(options?.sync);
|
|
1018
|
+
const initialTime = this.computePlaybackInitialTime(ani, speedRatio, rangeStart, rangeEnd, options?.sync, syncSource);
|
|
1019
|
+
if (this.isPlayingAnimation(ani.name)) {
|
|
1020
|
+
this.stopAnimation(ani.name, {
|
|
1021
|
+
reason: 'replaced'
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
playback._setState('playing');
|
|
1025
|
+
playback._setTime(initialTime);
|
|
663
1026
|
this._activeAnimations.set(ani, {
|
|
1027
|
+
playback,
|
|
664
1028
|
repeat,
|
|
665
1029
|
weight,
|
|
1030
|
+
targetWeight: weight,
|
|
666
1031
|
speedRatio,
|
|
667
1032
|
fadeIn,
|
|
668
1033
|
fadeOut: 0,
|
|
669
1034
|
repeatCounter: 0,
|
|
670
|
-
currentTime:
|
|
1035
|
+
currentTime: initialTime,
|
|
671
1036
|
animateTime: 0,
|
|
672
1037
|
fadeOutStart: 0,
|
|
673
|
-
|
|
1038
|
+
fadeToStart: 0,
|
|
1039
|
+
fadeToDuration: 0,
|
|
1040
|
+
fadeFromWeight: weight,
|
|
1041
|
+
fadeTargetWeight: weight,
|
|
1042
|
+
firstFrame: true,
|
|
1043
|
+
started: false,
|
|
1044
|
+
paused: false,
|
|
1045
|
+
stopReason: 'manual',
|
|
1046
|
+
completed: false,
|
|
1047
|
+
rangeStart,
|
|
1048
|
+
rangeEnd
|
|
674
1049
|
});
|
|
675
1050
|
ani.tracks?.forEach((v, k)=>{
|
|
676
1051
|
let nodeTracks = this._activeTracks.get(k);
|
|
@@ -707,18 +1082,257 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
707
1082
|
}
|
|
708
1083
|
});
|
|
709
1084
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1085
|
+
resolvePlaybackSyncSource(sync) {
|
|
1086
|
+
if (!sync?.target) {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
const playback = this.resolveActivePlayback(sync.target);
|
|
1090
|
+
if (!playback) {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
const info = this._activeAnimations.get(playback.clip);
|
|
1094
|
+
return info?.playback === playback ? {
|
|
1095
|
+
playback,
|
|
1096
|
+
info,
|
|
1097
|
+
clip: playback.clip
|
|
1098
|
+
} : null;
|
|
1099
|
+
}
|
|
1100
|
+
resolveActivePlayback(target) {
|
|
1101
|
+
const named = this.getPlayback(target);
|
|
1102
|
+
if (named) {
|
|
1103
|
+
return named;
|
|
1104
|
+
}
|
|
1105
|
+
for (const info of this._activeAnimations.values()){
|
|
1106
|
+
if (info.playback.id === target) {
|
|
1107
|
+
return info.playback;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
computePlaybackInitialTime(clip, speedRatio, rangeStart, rangeEnd, sync, syncSource) {
|
|
1113
|
+
const fallback = speedRatio < 0 ? rangeEnd ?? rangeStart ?? clip.timeDuration : rangeStart ?? 0;
|
|
1114
|
+
if (!sync || !syncSource) {
|
|
1115
|
+
return fallback;
|
|
1116
|
+
}
|
|
1117
|
+
const destination = this.getPlaybackEffectiveRange(clip, rangeStart, rangeEnd);
|
|
1118
|
+
if (destination.duration <= 0) {
|
|
1119
|
+
return fallback;
|
|
1120
|
+
}
|
|
1121
|
+
const offset = Number.isFinite(sync.offset) ? sync.offset ?? 0 : 0;
|
|
1122
|
+
const wrap = sync.wrap ?? true;
|
|
1123
|
+
if ((sync.mode ?? 'normalized') === 'time') {
|
|
1124
|
+
return this.constrainPlaybackTime(syncSource.info.currentTime + offset, destination, wrap);
|
|
1125
|
+
}
|
|
1126
|
+
const source = this.getPlaybackEffectiveRange(syncSource.clip, syncSource.info.rangeStart, syncSource.info.rangeEnd);
|
|
1127
|
+
if (source.duration <= 0) {
|
|
1128
|
+
return fallback;
|
|
1129
|
+
}
|
|
1130
|
+
const phase = (syncSource.info.currentTime - source.start) / source.duration + offset;
|
|
1131
|
+
return this.constrainPlaybackTime(destination.start + phase * destination.duration, destination, wrap);
|
|
1132
|
+
}
|
|
1133
|
+
getPlaybackEffectiveRange(clip, rangeStart, rangeEnd) {
|
|
1134
|
+
const start = rangeStart ?? 0;
|
|
1135
|
+
const end = rangeEnd ?? clip.timeDuration;
|
|
1136
|
+
return start <= end ? {
|
|
1137
|
+
start,
|
|
1138
|
+
end,
|
|
1139
|
+
duration: end - start
|
|
1140
|
+
} : {
|
|
1141
|
+
start: end,
|
|
1142
|
+
end: start,
|
|
1143
|
+
duration: start - end
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
constrainPlaybackTime(time, range, wrap) {
|
|
1147
|
+
if (!Number.isFinite(time)) {
|
|
1148
|
+
return range.start;
|
|
1149
|
+
}
|
|
1150
|
+
if (!wrap) {
|
|
1151
|
+
return Math.max(range.start, Math.min(range.end, time));
|
|
1152
|
+
}
|
|
1153
|
+
if (time >= range.start && time < range.end) {
|
|
1154
|
+
return time;
|
|
1155
|
+
}
|
|
1156
|
+
const offset = ((time - range.start) % range.duration + range.duration) % range.duration;
|
|
1157
|
+
return range.start + offset;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Stop a playback handle.
|
|
1161
|
+
* @internal
|
|
1162
|
+
*/ stopPlayback(playback, options) {
|
|
1163
|
+
if (this._activeAnimations.get(playback.clip)?.playback === playback) {
|
|
1164
|
+
this.stopAnimation(playback.clip.name, options);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Pause a playback handle.
|
|
1169
|
+
* @internal
|
|
1170
|
+
*/ pausePlayback(playback) {
|
|
1171
|
+
const info = this._activeAnimations.get(playback.clip);
|
|
1172
|
+
if (info?.playback === playback && !info.paused) {
|
|
1173
|
+
info.paused = true;
|
|
1174
|
+
playback._setState('paused');
|
|
1175
|
+
const event = this.createPlaybackEvent(info, playback.clip);
|
|
1176
|
+
playback._emitPause(event);
|
|
1177
|
+
this.dispatchEvent('playbackpause', event);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Resume a playback handle.
|
|
1182
|
+
* @internal
|
|
1183
|
+
*/ resumePlayback(playback) {
|
|
1184
|
+
const info = this._activeAnimations.get(playback.clip);
|
|
1185
|
+
if (info?.playback === playback && info.paused) {
|
|
1186
|
+
info.paused = false;
|
|
1187
|
+
playback._setState('playing');
|
|
1188
|
+
const event = this.createPlaybackEvent(info, playback.clip);
|
|
1189
|
+
playback._emitResume(event);
|
|
1190
|
+
this.dispatchEvent('playbackresume', event);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Seek a playback handle.
|
|
1195
|
+
* @internal
|
|
1196
|
+
*/ seekPlayback(playback, time, options) {
|
|
1197
|
+
const clip = playback.clip;
|
|
1198
|
+
const info = this._activeAnimations.get(clip);
|
|
1199
|
+
const duration = clip.timeDuration;
|
|
1200
|
+
const clampedTime = duration > 0 ? Math.max(0, Math.min(duration, time)) : Math.max(0, time);
|
|
1201
|
+
const previousTime = info?.currentTime ?? playback.time;
|
|
1202
|
+
playback._setTime(clampedTime);
|
|
1203
|
+
if (info?.playback === playback) {
|
|
1204
|
+
info.currentTime = clampedTime;
|
|
1205
|
+
if (options?.emitEvents) {
|
|
1206
|
+
const direction = clampedTime >= previousTime ? 1 : -1;
|
|
1207
|
+
this.emitCrossedMarkers(info, clip, previousTime, clampedTime, direction);
|
|
1208
|
+
this.emitCrossedFrames(info, clip, previousTime, clampedTime, direction);
|
|
1209
|
+
}
|
|
1210
|
+
if (options?.apply) {
|
|
1211
|
+
this.update(0);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Set playback weight.
|
|
1217
|
+
* @internal
|
|
1218
|
+
*/ setPlaybackWeight(playback, weight) {
|
|
1219
|
+
playback._setWeight(weight);
|
|
1220
|
+
const info = this._activeAnimations.get(playback.clip);
|
|
1221
|
+
if (info?.playback === playback) {
|
|
1222
|
+
info.weight = weight;
|
|
1223
|
+
info.targetWeight = weight;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Set playback speed ratio.
|
|
1228
|
+
* @internal
|
|
1229
|
+
*/ setPlaybackSpeedRatio(playback, speedRatio) {
|
|
1230
|
+
playback._setSpeedRatio(speedRatio);
|
|
1231
|
+
const info = this._activeAnimations.get(playback.clip);
|
|
1232
|
+
if (info?.playback === playback) {
|
|
1233
|
+
info.speedRatio = speedRatio;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Fade playback weight to a target value.
|
|
1238
|
+
* @internal
|
|
1239
|
+
*/ fadePlaybackTo(playback, weight, duration) {
|
|
1240
|
+
const info = this._activeAnimations.get(playback.clip);
|
|
1241
|
+
if (info?.playback === playback) {
|
|
1242
|
+
info.fadeFromWeight = info.weight;
|
|
1243
|
+
info.fadeTargetWeight = weight;
|
|
1244
|
+
info.targetWeight = weight;
|
|
1245
|
+
info.fadeToStart = info.animateTime;
|
|
1246
|
+
info.fadeToDuration = Math.max(duration, 0);
|
|
1247
|
+
if (info.fadeToDuration === 0) {
|
|
1248
|
+
info.weight = weight;
|
|
1249
|
+
playback._setWeight(weight);
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
playback._setWeight(weight);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
createPlaybackEvent(info, clip) {
|
|
1256
|
+
return {
|
|
1257
|
+
playback: info.playback,
|
|
1258
|
+
animationSet: this,
|
|
1259
|
+
clip,
|
|
1260
|
+
time: info.currentTime,
|
|
1261
|
+
normalizedTime: clip.timeDuration > 0 ? info.currentTime / clip.timeDuration : 0
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
completePlayback(clip, info) {
|
|
1265
|
+
if (info.completed) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const event = this.createPlaybackEvent(info, clip);
|
|
1269
|
+
const completionFadeOut = Math.max(info.playback._getOptions().completionFadeOut ?? 0, 0);
|
|
1270
|
+
info.playback._setState('completed');
|
|
1271
|
+
info.playback._emitComplete(event);
|
|
1272
|
+
this.dispatchEvent('playbackcomplete', event);
|
|
1273
|
+
if (completionFadeOut > 0) {
|
|
1274
|
+
info.completed = true;
|
|
1275
|
+
info.fadeOut = completionFadeOut;
|
|
1276
|
+
info.fadeOutStart = info.animateTime;
|
|
1277
|
+
info.stopReason = 'completed';
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
this.stopAnimation(clip.name, {
|
|
1281
|
+
reason: 'completed'
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
emitCrossedMarkers(info, clip, fromTime, toTime, direction) {
|
|
1285
|
+
for (const marker of this.getCrossedMarkers(clip, fromTime, toTime, direction)){
|
|
1286
|
+
const event = {
|
|
1287
|
+
...this.createPlaybackEvent(info, clip),
|
|
1288
|
+
marker,
|
|
1289
|
+
direction
|
|
1290
|
+
};
|
|
1291
|
+
info.playback._emitMarker(event);
|
|
1292
|
+
this.dispatchEvent('marker', event);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
emitCrossedFrames(info, clip, fromTime, toTime, direction) {
|
|
1296
|
+
for (const frame of info.playback._getWatchedFrames()){
|
|
1297
|
+
const frameTime = frame / clip.frameRate;
|
|
1298
|
+
const crossed = direction >= 0 ? this.crossedForward(fromTime, toTime, frameTime, clip.timeDuration) : this.crossedBackward(fromTime, toTime, frameTime, clip.timeDuration);
|
|
1299
|
+
if (crossed) {
|
|
1300
|
+
const event = {
|
|
1301
|
+
...this.createPlaybackEvent(info, clip),
|
|
1302
|
+
frame
|
|
1303
|
+
};
|
|
1304
|
+
info.playback._emitFrame(event);
|
|
1305
|
+
this.dispatchEvent('frame', event);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
getCrossedMarkers(clip, fromTime, toTime, direction) {
|
|
1310
|
+
return clip.markers.filter((marker)=>{
|
|
1311
|
+
const markerTime = clip.resolveMarkerTime(marker);
|
|
1312
|
+
if (markerTime === null) {
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
return direction >= 0 ? this.crossedForward(fromTime, toTime, markerTime, clip.timeDuration) : this.crossedBackward(fromTime, toTime, markerTime, clip.timeDuration);
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
crossedForward(fromTime, toTime, targetTime, duration) {
|
|
1319
|
+
return toTime >= fromTime ? targetTime > fromTime && targetTime <= toTime : duration > 0 && targetTime > fromTime && targetTime <= duration || targetTime <= toTime;
|
|
1320
|
+
}
|
|
1321
|
+
crossedBackward(fromTime, toTime, targetTime, duration) {
|
|
1322
|
+
return toTime <= fromTime ? targetTime < fromTime && targetTime >= toTime : duration > 0 && targetTime < fromTime && targetTime >= 0 || targetTime >= toTime;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Stop playback of an animation clip.
|
|
1326
|
+
*
|
|
1327
|
+
* Behavior:
|
|
1328
|
+
* - If `options.fadeOut > 0`, marks the clip for fade-out; actual removal occurs after fade completes.
|
|
1329
|
+
* - If `fadeOut` is 0 or omitted, immediately:
|
|
1330
|
+
* - Removes the clip from active animations.
|
|
1331
|
+
* - Unregisters its tracks from active track maps.
|
|
1332
|
+
* - Decrements skeleton reference counts; resets and removes skeletons when refcount reaches 0.
|
|
1333
|
+
*
|
|
1334
|
+
* @param name - Name of the animation to stop.
|
|
1335
|
+
* @param options - Optional fade-out configuration.
|
|
722
1336
|
*/ stopAnimation(name, options) {
|
|
723
1337
|
const ani = this._animations[name];
|
|
724
1338
|
if (!ani) {
|
|
@@ -728,9 +1342,12 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
728
1342
|
const info = this._activeAnimations.get(ani);
|
|
729
1343
|
if (info) {
|
|
730
1344
|
const fadeOut = Math.max(options?.fadeOut ?? 0, 0);
|
|
1345
|
+
const reason = options?.reason ?? 'manual';
|
|
731
1346
|
if (fadeOut !== 0) {
|
|
732
1347
|
info.fadeOut = fadeOut;
|
|
733
1348
|
info.fadeOutStart = -1;
|
|
1349
|
+
info.stopReason = reason;
|
|
1350
|
+
info.playback._setState('stopping');
|
|
734
1351
|
} else {
|
|
735
1352
|
this._activeAnimations.delete(ani);
|
|
736
1353
|
this._activeTracks.forEach((v)=>{
|
|
@@ -768,20 +1385,29 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
768
1385
|
}
|
|
769
1386
|
}
|
|
770
1387
|
});
|
|
1388
|
+
const event = {
|
|
1389
|
+
...this.createPlaybackEvent(info, ani),
|
|
1390
|
+
reason
|
|
1391
|
+
};
|
|
1392
|
+
if (reason !== 'completed') {
|
|
1393
|
+
info.playback._setState('stopped');
|
|
1394
|
+
}
|
|
1395
|
+
info.playback._emitStop(event);
|
|
1396
|
+
this.dispatchEvent('playbackstop', event);
|
|
771
1397
|
}
|
|
772
1398
|
}
|
|
773
1399
|
}
|
|
774
|
-
/**
|
|
775
|
-
* Create a skeletal-only masked clip from an existing clip in this set.
|
|
776
|
-
*
|
|
777
|
-
* The generated clip is a regular `AnimationClip`: it contains cloned node transform tracks
|
|
778
|
-
* for the selected rig joints and can be played/blended through the normal animation system.
|
|
779
|
-
* Non-skeletal tracks are skipped by default.
|
|
780
|
-
*
|
|
781
|
-
* @param sourceName - Name of the source clip.
|
|
782
|
-
* @param targetName - Name of the generated clip.
|
|
783
|
-
* @param options - Humanoid semantic or joint-name based mask options.
|
|
784
|
-
* @returns The generated clip, or null on failure.
|
|
1400
|
+
/**
|
|
1401
|
+
* Create a skeletal-only masked clip from an existing clip in this set.
|
|
1402
|
+
*
|
|
1403
|
+
* The generated clip is a regular `AnimationClip`: it contains cloned node transform tracks
|
|
1404
|
+
* for the selected rig joints and can be played/blended through the normal animation system.
|
|
1405
|
+
* Non-skeletal tracks are skipped by default.
|
|
1406
|
+
*
|
|
1407
|
+
* @param sourceName - Name of the source clip.
|
|
1408
|
+
* @param targetName - Name of the generated clip.
|
|
1409
|
+
* @param options - Humanoid semantic or joint-name based mask options.
|
|
1410
|
+
* @returns The generated clip, or null on failure.
|
|
785
1411
|
*/ createSkeletalMaskedAnimation(sourceName, targetName, options) {
|
|
786
1412
|
const sourceClip = this.get(sourceName);
|
|
787
1413
|
if (!sourceClip) {
|
|
@@ -988,21 +1614,21 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
988
1614
|
}
|
|
989
1615
|
return dstClip;
|
|
990
1616
|
}
|
|
991
|
-
/**
|
|
992
|
-
* Copy an animation clip from another AnimationSet into this one.
|
|
993
|
-
*
|
|
994
|
-
* Prerequisites:
|
|
995
|
-
* - Both sets must reference skeletons with identical joint names and counts.
|
|
996
|
-
* - The source clip must exist in `sourceSet`.
|
|
997
|
-
*
|
|
998
|
-
* @param sourceSet - The AnimationSet to copy from.
|
|
999
|
-
* @param animationName - Name of the clip to copy.
|
|
1000
|
-
* @param targetName - Name for the new clip in this set. Defaults to `animationName`.
|
|
1001
|
-
* @param excludeJoint - Optional predicate; joints whose name returns true are excluded from
|
|
1002
|
-
* skeleton structure matching.
|
|
1003
|
-
* @returns The newly created AnimationClip, or null on failure.
|
|
1004
|
-
*
|
|
1005
|
-
* @deprecated Use AnimationSet.copyHumanoidAnimationFrom instead.
|
|
1617
|
+
/**
|
|
1618
|
+
* Copy an animation clip from another AnimationSet into this one.
|
|
1619
|
+
*
|
|
1620
|
+
* Prerequisites:
|
|
1621
|
+
* - Both sets must reference skeletons with identical joint names and counts.
|
|
1622
|
+
* - The source clip must exist in `sourceSet`.
|
|
1623
|
+
*
|
|
1624
|
+
* @param sourceSet - The AnimationSet to copy from.
|
|
1625
|
+
* @param animationName - Name of the clip to copy.
|
|
1626
|
+
* @param targetName - Name for the new clip in this set. Defaults to `animationName`.
|
|
1627
|
+
* @param excludeJoint - Optional predicate; joints whose name returns true are excluded from
|
|
1628
|
+
* skeleton structure matching.
|
|
1629
|
+
* @returns The newly created AnimationClip, or null on failure.
|
|
1630
|
+
*
|
|
1631
|
+
* @deprecated Use AnimationSet.copyHumanoidAnimationFrom instead.
|
|
1006
1632
|
*/ copyAnimationFrom(sourceSet, animationName, targetName, excludeJoint) {
|
|
1007
1633
|
const destName = targetName ?? animationName;
|
|
1008
1634
|
const sourceClip = sourceSet.get(animationName);
|
|
@@ -1148,8 +1774,8 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
1148
1774
|
return ordered;
|
|
1149
1775
|
}
|
|
1150
1776
|
}
|
|
1151
|
-
/**
|
|
1152
|
-
* Reset all skeleton modifiers
|
|
1777
|
+
/**
|
|
1778
|
+
* Reset all skeleton modifiers
|
|
1153
1779
|
*/ resetSkeletonModifiers() {
|
|
1154
1780
|
for (const sk of this._skeletons){
|
|
1155
1781
|
for (const modifier of sk.get().modifiers){
|
|
@@ -1157,12 +1783,12 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
1157
1783
|
}
|
|
1158
1784
|
}
|
|
1159
1785
|
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Dispose the animation set and release owned resources.
|
|
1162
|
-
*
|
|
1163
|
-
* - Disposes the weak reference to the model.
|
|
1164
|
-
* - Disposes all registered animation clips.
|
|
1165
|
-
* - Clears active animations, tracks, and skeleton references.
|
|
1786
|
+
/**
|
|
1787
|
+
* Dispose the animation set and release owned resources.
|
|
1788
|
+
*
|
|
1789
|
+
* - Disposes the weak reference to the model.
|
|
1790
|
+
* - Disposes all registered animation clips.
|
|
1791
|
+
* - Clears active animations, tracks, and skeleton references.
|
|
1166
1792
|
*/ onDispose() {
|
|
1167
1793
|
super.onDispose();
|
|
1168
1794
|
for(const k in this._animations){
|
|
@@ -1176,5 +1802,5 @@ function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRoo
|
|
|
1176
1802
|
}
|
|
1177
1803
|
}
|
|
1178
1804
|
|
|
1179
|
-
export { AnimationSet };
|
|
1805
|
+
export { AnimationPlayback, AnimationSet };
|
|
1180
1806
|
//# sourceMappingURL=animationset.js.map
|