@@ -9,17 +9,52 @@
|
|
9
9
|
|
10
10
|
const debug = getParam("debuganimation");
|
11
11
|
|
12
|
-
export declare
|
12
|
+
export declare type PlayOptions = {
|
13
|
+
/**
|
14
|
+
* The fade duration in seconds for the action to fade in and other actions to fade out (if exclusive is enabled)
|
15
|
+
*/
|
13
16
|
fadeDuration?: number;
|
17
|
+
/**
|
18
|
+
* If true, the animation will loop
|
19
|
+
*/
|
14
20
|
loop?: boolean;
|
21
|
+
/**
|
22
|
+
* If true, will stop all other animations before playing this one
|
23
|
+
* @default true
|
24
|
+
*/
|
15
25
|
exclusive?: boolean;
|
26
|
+
/**
|
27
|
+
* The animation start time in seconds
|
28
|
+
*/
|
16
29
|
startTime?: number;
|
30
|
+
/**
|
31
|
+
* The animation end time in seconds
|
32
|
+
*/
|
17
33
|
endTime?: number;
|
34
|
+
/**
|
35
|
+
* If true, the animation will clamp when finished
|
36
|
+
*/
|
18
37
|
clampWhenFinished?: boolean;
|
38
|
+
/**
|
39
|
+
* Animation playback speed. This is a multiplier to the animation speed
|
40
|
+
* @default 1
|
41
|
+
*/
|
42
|
+
speed?: number;
|
43
|
+
/**
|
44
|
+
* Animation playback speed range. This will override speed
|
45
|
+
* @default undefined
|
46
|
+
*/
|
19
47
|
minMaxSpeed?: Vec2;
|
48
|
+
/**
|
49
|
+
* The normalized offset to start the animation at. This will override startTime
|
50
|
+
* @default undefined
|
51
|
+
*/
|
20
52
|
minMaxOffsetNormalized?: Vec2;
|
21
53
|
}
|
22
54
|
|
55
|
+
|
56
|
+
declare type AnimationIdentifier = AnimationClip | number | string | undefined;
|
57
|
+
|
23
58
|
class Vec2 { x!: number; y!: number }
|
24
59
|
|
25
60
|
/**
|
@@ -35,6 +70,7 @@
|
|
35
70
|
|
36
71
|
@serializable()
|
37
72
|
playAutomatically: boolean = true;
|
73
|
+
|
38
74
|
@serializable()
|
39
75
|
randomStartTime: boolean = true;
|
40
76
|
|
@@ -46,13 +82,22 @@
|
|
46
82
|
@serializable()
|
47
83
|
loop: boolean = true;
|
48
84
|
|
85
|
+
/**
|
86
|
+
* If true, the animation will clamp when finished
|
87
|
+
*/
|
49
88
|
@serializable()
|
50
89
|
clampWhenFinished: boolean = false;
|
51
90
|
|
52
91
|
private _tempAnimationClipBeforeGameObjectExisted: AnimationClip | null = null;
|
92
|
+
/**
|
93
|
+
* Get the first animation clip in the animations array
|
94
|
+
*/
|
53
95
|
get clip(): AnimationClip | null {
|
54
96
|
return this.animations?.length ? this.animations[0] : null;
|
55
97
|
}
|
98
|
+
/**
|
99
|
+
* Set the first animation clip in the animations array
|
100
|
+
*/
|
56
101
|
set clip(val: AnimationClip | null) {
|
57
102
|
if (!this.__didAwake) {
|
58
103
|
if (debug) console.warn("Assign clip during serialization", val);
|
@@ -88,20 +133,7 @@
|
|
88
133
|
get animations(): AnimationClip[] {
|
89
134
|
return this.gameObject.animations || this._tempAnimationsArray || [];
|
90
135
|
}
|
91
|
-
/**
|
92
|
-
* @deprecated Currently unsupported
|
93
|
-
*/
|
94
|
-
get currentAction(): AnimationAction | null {
|
95
|
-
return this._currentActions[0];
|
96
|
-
}
|
97
136
|
|
98
|
-
/**
|
99
|
-
* @deprecated Currently unsupported
|
100
|
-
*/
|
101
|
-
get currentActions(): AnimationAction[] {
|
102
|
-
return this._currentActions;
|
103
|
-
}
|
104
|
-
|
105
137
|
private mixer: AnimationMixer | undefined = undefined;
|
106
138
|
get actions(): Array<AnimationAction> {
|
107
139
|
return this._actions;
|
@@ -111,11 +143,9 @@
|
|
111
143
|
}
|
112
144
|
private _actions: Array<AnimationAction> = [];
|
113
145
|
|
114
|
-
// private _currentAction: AnimationAction | null = null;
|
115
|
-
|
116
|
-
private _currentActions: AnimationAction[] = [];
|
117
146
|
private _handles: AnimationHandle[] = [];
|
118
147
|
|
148
|
+
/** @internal */
|
119
149
|
awake() {
|
120
150
|
if (debug) console.log("Animation Awake", this.name, this);
|
121
151
|
if (this._tempAnimationsArray) {
|
@@ -130,45 +160,43 @@
|
|
130
160
|
if (this.playAutomatically)
|
131
161
|
this.init();
|
132
162
|
}
|
133
|
-
|
163
|
+
/** @internal */
|
134
164
|
onEnable(): void {
|
135
|
-
if (this.playAutomatically && this.animations?.length > 0
|
136
|
-
const index = Math.floor(Math.random() * this.
|
137
|
-
this.
|
165
|
+
if (this.playAutomatically && this.animations?.length > 0) {
|
166
|
+
const index = Math.floor(Math.random() * this.animations.length);
|
167
|
+
const animation = this.animations[index];
|
168
|
+
this.play(index, {
|
169
|
+
exclusive: true,
|
170
|
+
fadeDuration: 0,
|
171
|
+
startTime: this.randomStartTime ? Math.random() * animation.duration : 0,
|
172
|
+
loop: this.loop,
|
173
|
+
clampWhenFinished: this.clampWhenFinished
|
174
|
+
});
|
138
175
|
}
|
139
176
|
}
|
140
|
-
|
177
|
+
/** @internal */
|
178
|
+
update() {
|
179
|
+
if (!this.mixer) return;
|
180
|
+
this.mixer.update(this.context.time.deltaTime);
|
181
|
+
}
|
182
|
+
/** @internal */
|
141
183
|
onDisable(): void {
|
142
184
|
if (this.mixer) {
|
143
185
|
this.mixer.stopAllAction();
|
144
186
|
this.mixer = undefined;
|
145
187
|
}
|
146
188
|
}
|
147
|
-
|
189
|
+
/** @internal */
|
148
190
|
onDestroy(): void {
|
149
191
|
this.context.animations.unregisterAnimationMixer(this.mixer);
|
150
192
|
}
|
151
193
|
|
152
|
-
|
153
|
-
|
154
|
-
|
194
|
+
/** Get an animation action by the animation clip name */
|
195
|
+
getAction(name: string): AnimationAction | null {
|
196
|
+
return this.actions?.find(a => a.getClip().name === name) || null;
|
155
197
|
}
|
156
198
|
|
157
|
-
|
158
|
-
if (!this.mixer) return;
|
159
|
-
this.mixer.update(this.context.time.deltaTime);
|
160
|
-
// this is now handled via matrix auto update
|
161
|
-
// for (const handle of this._handles) {
|
162
|
-
// handle._update();
|
163
|
-
// }
|
164
|
-
// if (this._handles?.length > 0)
|
165
|
-
// InstancingUtil.markDirty(this.gameObject);
|
166
|
-
}
|
167
|
-
|
168
|
-
getAction(name: string): AnimationAction | undefined | null {
|
169
|
-
return this.actions?.find(a => a.getClip().name === name);
|
170
|
-
}
|
171
|
-
|
199
|
+
/** Is any animation playing? */
|
172
200
|
get isPlaying() {
|
173
201
|
for (let i = 0; i < this.actions.length; i++) {
|
174
202
|
if (this.actions[i].isRunning())
|
@@ -177,7 +205,54 @@
|
|
177
205
|
return false;
|
178
206
|
}
|
179
207
|
|
180
|
-
|
208
|
+
/** Stops all currently playing animations */
|
209
|
+
stopAll(opts?: Pick<PlayOptions, "fadeDuration">): void {
|
210
|
+
for (const act of this.actions) {
|
211
|
+
if (opts?.fadeDuration) {
|
212
|
+
act.fadeOut(opts.fadeDuration);
|
213
|
+
}
|
214
|
+
else {
|
215
|
+
act.stop();
|
216
|
+
}
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
/**
|
221
|
+
* Stops a specific animation clip or index. If clip is undefined then all animations will be stopped
|
222
|
+
*/
|
223
|
+
stop(clip?: AnimationIdentifier, opts?: Pick<PlayOptions, "fadeDuration">): void {
|
224
|
+
if (clip === undefined) {
|
225
|
+
this.stopAll();
|
226
|
+
return;
|
227
|
+
}
|
228
|
+
else if (typeof clip === "number") {
|
229
|
+
if (clip >= this.animations.length) {
|
230
|
+
if (debug) console.log("No animation at index", clip)
|
231
|
+
return;
|
232
|
+
}
|
233
|
+
clip = this.animations[clip];
|
234
|
+
}
|
235
|
+
else if (typeof clip === "string") {
|
236
|
+
clip = this.animations.find(a => a.name === clip);
|
237
|
+
}
|
238
|
+
if (!clip) {
|
239
|
+
console.error("Could not find clip", clip)
|
240
|
+
return;
|
241
|
+
}
|
242
|
+
const act = this.actions.find(a => a.getClip() === clip);
|
243
|
+
if (!act) {
|
244
|
+
console.error("Could not find action", clip)
|
245
|
+
return;
|
246
|
+
}
|
247
|
+
if (opts?.fadeDuration) {
|
248
|
+
act.fadeOut(opts.fadeDuration);
|
249
|
+
}
|
250
|
+
else {
|
251
|
+
act.stop();
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
play(clipOrNumber: AnimationIdentifier = 0, options?: PlayOptions): Promise<AnimationAction> | void {
|
181
256
|
if (debug) console.log("PLAY", clipOrNumber)
|
182
257
|
this.init();
|
183
258
|
if (!this.mixer) {
|
@@ -200,11 +275,9 @@
|
|
200
275
|
console.error("Could not find clip", clipOrNumber)
|
201
276
|
return;
|
202
277
|
}
|
278
|
+
|
203
279
|
if (!options) options = {};
|
204
|
-
|
205
|
-
if (!options.minMaxSpeed) options.minMaxSpeed = this.minMaxSpeed;
|
206
|
-
if (options.loop === undefined) options.loop = this.loop;
|
207
|
-
if (options.clampWhenFinished === undefined) options.clampWhenFinished = this.clampWhenFinished;
|
280
|
+
|
208
281
|
for (const act of this.actions) {
|
209
282
|
if (act.getClip() === clip) {
|
210
283
|
return this.internalOnPlay(act, options);
|
@@ -219,45 +292,63 @@
|
|
219
292
|
return this.internalOnPlay(act, options);
|
220
293
|
}
|
221
294
|
|
222
|
-
internalOnPlay(action: AnimationAction, options
|
223
|
-
var prev = this.
|
295
|
+
private internalOnPlay(action: AnimationAction, options: PlayOptions): Promise<AnimationAction> {
|
296
|
+
var prev = this.actions.find(a => a === action);
|
224
297
|
if (prev === action && prev.isRunning() && prev.time < prev.getClip().duration) {
|
225
298
|
const handle = this.tryFindHandle(action);
|
226
|
-
if (handle) return handle.
|
299
|
+
if (handle) return handle.waitForFinish();
|
227
300
|
}
|
301
|
+
|
302
|
+
// Assign defaults
|
303
|
+
if (!options.minMaxOffsetNormalized) options.minMaxOffsetNormalized = this.minMaxOffsetNormalized;
|
304
|
+
if (!options.minMaxSpeed) options.minMaxSpeed = this.minMaxSpeed;
|
305
|
+
if (options.loop === undefined) options.loop = this.loop;
|
306
|
+
if (options.clampWhenFinished === undefined) options.clampWhenFinished = this.clampWhenFinished;
|
307
|
+
|
308
|
+
// Reset currently running animations
|
228
309
|
const stopOther = options?.exclusive ?? true;
|
310
|
+
if (stopOther) {
|
311
|
+
this.stopAll(options);
|
312
|
+
}
|
229
313
|
if (options?.fadeDuration) {
|
230
|
-
if (stopOther)
|
231
|
-
prev?.fadeOut(options.fadeDuration);
|
232
314
|
action.fadeIn(options.fadeDuration);
|
233
315
|
}
|
316
|
+
action.enabled = true;
|
317
|
+
|
318
|
+
// Apply start time
|
319
|
+
if (options?.minMaxOffsetNormalized) {
|
320
|
+
const clip = action.getClip();
|
321
|
+
action.time = Mathf.lerp(options.minMaxOffsetNormalized.x, options.minMaxOffsetNormalized.y, Math.random()) * clip.duration;
|
322
|
+
}
|
323
|
+
else if (options?.startTime != undefined) {
|
324
|
+
action.time = options.startTime;
|
325
|
+
}
|
326
|
+
|
327
|
+
// Apply speed
|
328
|
+
if (options?.minMaxSpeed) {
|
329
|
+
action.timeScale = Mathf.lerp(options.minMaxSpeed.x, options.minMaxSpeed.y, Math.random());
|
330
|
+
}
|
234
331
|
else {
|
235
|
-
|
236
|
-
prev?.stop();
|
332
|
+
action.timeScale = options?.speed ?? 1;
|
237
333
|
}
|
238
|
-
action.reset();
|
239
|
-
action.enabled = true;
|
240
|
-
action.time = 0;
|
241
|
-
action.timeScale = 1;
|
242
|
-
const clip = action.getClip();
|
243
|
-
if (options?.minMaxOffsetNormalized) action.time = Mathf.lerp(options.minMaxOffsetNormalized.x, options.minMaxOffsetNormalized.y, Math.random()) * clip.duration;
|
244
|
-
if (options?.minMaxSpeed) action.timeScale = Mathf.lerp(options.minMaxSpeed.x, options.minMaxSpeed.y, Math.random());
|
245
|
-
if (options?.clampWhenFinished) action.clampWhenFinished = true;
|
246
|
-
if (options?.startTime !== undefined) action.time = options.startTime;
|
247
334
|
|
248
|
-
|
335
|
+
// Apply looping
|
336
|
+
if (options?.loop != undefined) {
|
249
337
|
action.loop = options.loop ? LoopRepeat : LoopOnce;
|
338
|
+
}
|
250
339
|
else action.loop = LoopOnce;
|
340
|
+
if (options?.clampWhenFinished) {
|
341
|
+
action.clampWhenFinished = true;
|
342
|
+
}
|
343
|
+
|
251
344
|
action.play();
|
252
|
-
if (debug)
|
253
|
-
console.log("PLAY", action.getClip().name, action)
|
254
345
|
|
346
|
+
if (debug) console.log("PLAY", action.getClip().name, action)
|
255
347
|
const handle = new AnimationHandle(action, this.mixer!, options, _ => {
|
256
348
|
this._handles.splice(this._handles.indexOf(handle), 1);
|
257
|
-
// console.log(this._handles);
|
258
349
|
});
|
259
350
|
this._handles.push(handle);
|
260
|
-
return handle.
|
351
|
+
return handle.waitForFinish();
|
261
352
|
}
|
262
353
|
|
263
354
|
private tryFindHandle(action: AnimationAction): AnimationHandle | undefined {
|
@@ -295,44 +386,37 @@
|
|
295
386
|
private _finishedCallback?: any;
|
296
387
|
private _resolvedOrRejectedCallback?: (AnimationHandle) => void;
|
297
388
|
|
298
|
-
constructor(action: AnimationAction, mixer: AnimationMixer, opts?: PlayOptions,
|
389
|
+
constructor(action: AnimationAction, mixer: AnimationMixer, opts?: PlayOptions, onDone?: (handle: AnimationHandle) => void) {
|
299
390
|
this.action = action;
|
300
391
|
this.mixer = mixer;
|
301
|
-
this._resolvedOrRejectedCallback =
|
392
|
+
this._resolvedOrRejectedCallback = onDone;
|
302
393
|
this._options = opts;
|
303
394
|
}
|
304
395
|
|
305
|
-
|
396
|
+
waitForFinish(): Promise<AnimationAction> {
|
306
397
|
if (this.promise) return this.promise;
|
307
|
-
|
308
398
|
this.promise = new Promise((res, rej) => {
|
309
399
|
this._resolveCallback = res;
|
310
400
|
this._rejectCallback = rej;
|
311
401
|
this.resolve = this.onResolve.bind(this);
|
312
402
|
this.reject = this.onReject.bind(this);
|
313
403
|
});
|
314
|
-
|
315
404
|
this._loopCallback = this.onLoop.bind(this);
|
316
405
|
this._finishedCallback = this.onFinished.bind(this);
|
317
406
|
this.mixer.addEventListener('loop', this._loopCallback);
|
318
407
|
this.mixer.addEventListener('finished', this._finishedCallback);
|
319
|
-
|
320
408
|
return this.promise;
|
321
409
|
}
|
322
410
|
|
323
411
|
_update() {
|
324
|
-
|
325
412
|
if (!this._options) return;
|
326
413
|
if (this._options.endTime !== undefined && this.action.time > this._options.endTime) {
|
327
414
|
if (this._options.loop === true) {
|
328
415
|
this.action.time = this._options.startTime ?? 0;
|
329
416
|
}
|
330
417
|
else {
|
331
|
-
// this.action.stop();
|
332
418
|
this.action.time = this._options.endTime;
|
333
419
|
this.action.timeScale = 0;
|
334
|
-
// if (!this._options.clampWhenFinished)
|
335
|
-
// this.action.stop();
|
336
420
|
this.onResolve();
|
337
421
|
}
|
338
422
|
}
|
@@ -388,6 +388,12 @@
|
|
388
388
|
get static() {
|
389
389
|
return this.gameObject?.userData.static;
|
390
390
|
}
|
391
|
+
set static(value: boolean) {
|
392
|
+
if (this.gameObject) {
|
393
|
+
if (!this.gameObject.userData) this.gameObject.userData = {}
|
394
|
+
this.gameObject.userData.static = value;
|
395
|
+
}
|
396
|
+
}
|
391
397
|
get hideFlags(): HideFlags {
|
392
398
|
return this.gameObject?.userData.hideFlags;
|
393
399
|
}
|
@@ -137,7 +137,7 @@
|
|
137
137
|
*/
|
138
138
|
export class Context implements IContext {
|
139
139
|
|
140
|
-
private static _defaultTargetFramerate: { value?: number, toString?() } = { value:
|
140
|
+
private static _defaultTargetFramerate: { value?: number, toString?() } = { value: 90, toString() { return this.value; } }
|
141
141
|
/** When a new context is created this is the framerate that will be used by default */
|
142
142
|
static get DefaultTargetFrameRate(): number | undefined {
|
143
143
|
return Context._defaultTargetFramerate.value;
|
@@ -854,6 +854,9 @@
|
|
854
854
|
private onPointerDown = (evt: PointerEvent) => {
|
855
855
|
if (this.context.isInAR) return;
|
856
856
|
if (this.canReceiveInput(evt) === false) return;
|
857
|
+
if (evt.target instanceof HTMLElement) {
|
858
|
+
evt.target.setPointerCapture(evt.pointerId);
|
859
|
+
}
|
857
860
|
const id = this.getPointerId(evt);
|
858
861
|
if (debug) showBalloonMessage(`pointer down #${id}, identifier:${evt.pointerId}`);
|
859
862
|
const space = this.getAndUpdateSpatialObjectForScreenPosition(id, evt.clientX, evt.clientY);
|
@@ -862,7 +865,8 @@
|
|
862
865
|
}
|
863
866
|
private onPointerMove = (evt: PointerEvent) => {
|
864
867
|
if (this.context.isInAR) return;
|
865
|
-
|
868
|
+
// We want to keep receiving move events until pointerUp and not stop handling events just because we're hovering over *some* HTML element
|
869
|
+
// if (this.canReceiveInput(evt) === false) return;
|
866
870
|
let button = evt.button;
|
867
871
|
if (evt.pointerType === "mouse") {
|
868
872
|
const pressedButton = this.getFirstPressedButtonForPointer(0);
|
@@ -876,8 +880,17 @@
|
|
876
880
|
const ne = new NEPointerEvent(InputEvents.PointerMove, evt, { origin: this, mode: "screen", deviceIndex: 0, pointerId: id, button: button, clientX: evt.clientX, clientY: evt.clientY, pointerType: evt.pointerType as PointerTypeNames, buttonName: this.getButtonName(evt), device: space, pressure: evt.pressure });
|
877
881
|
this.onMove(ne);
|
878
882
|
}
|
883
|
+
private onPointerCancel = (evt: PointerEvent) => {
|
884
|
+
if (this.context.isInAR) return;
|
885
|
+
if (debug) console.log("Pointer cancel", evt);
|
886
|
+
// we treat this as an up event for now to make sure we don't have any pointers stuck in a pressed state etc. Technically we dont want to invoke a up event for cancels...
|
887
|
+
this.onPointerUp(evt);
|
888
|
+
}
|
879
889
|
private onPointerUp = (evt: PointerEvent) => {
|
880
890
|
if (this.context.isInAR) return;
|
891
|
+
if (evt.target instanceof HTMLElement) {
|
892
|
+
evt.target.releasePointerCapture(evt.pointerId);
|
893
|
+
}
|
881
894
|
// the pointer up event should always be handled
|
882
895
|
// if (this.canReceiveInput(evt) === false) return;
|
883
896
|
const id = this.getPointerId(evt);
|
@@ -887,12 +900,6 @@
|
|
887
900
|
this._pointerIds[id] = -1;
|
888
901
|
if (debug) console.log("ID=" + id, "PointerId=" + evt.pointerId, "ALL:", [...this._pointerIds]);
|
889
902
|
}
|
890
|
-
private onPointerCancel = (evt: PointerEvent) => {
|
891
|
-
if (this.context.isInAR) return;
|
892
|
-
if (debug) console.log("Pointer cancel", evt);
|
893
|
-
// we treat this as an up event for now to make sure we don't have any pointers stuck in a pressed state etc. Technically we dont want to invoke a up event for cancels...
|
894
|
-
this.onPointerUp(evt);
|
895
|
-
}
|
896
903
|
|
897
904
|
private getPointerId(evt: PointerEvent, button?: number): number {
|
898
905
|
if (evt.pointerType === "mouse") return 0 + (button ?? evt.button);
|
@@ -842,7 +842,7 @@
|
|
842
842
|
// handle controller and input source changes changes
|
843
843
|
this.session.addEventListener('end', this.onEnd);
|
844
844
|
// handle input sources change
|
845
|
-
this.session.addEventListener("inputsourceschange", (evt:
|
845
|
+
this.session.addEventListener("inputsourceschange", (evt: XRInputSourcesChangeEvent) => {
|
846
846
|
// handle removed controllers
|
847
847
|
for (const removedInputSource of evt.removed) {
|
848
848
|
this.disconnectInputSource(removedInputSource);
|
@@ -718,6 +718,10 @@
|
|
718
718
|
if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
|
719
719
|
this._reflectionProbe.onUnset(this);
|
720
720
|
}
|
721
|
+
|
722
|
+
if (this.static && this.gameObject.matrixAutoUpdate) {
|
723
|
+
this.gameObject.matrixAutoUpdate = false;
|
724
|
+
}
|
721
725
|
}
|
722
726
|
|
723
727
|
/** Applies stencil settings for this renderer's objects (if stencil settings are available) */
|