physics-animator 0.2.0 → 0.9.0
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/README.md +13 -4
- package/dist/cjs/Animator.js +255 -157
- package/dist/cjs/IFieldAnimator.js +8 -0
- package/dist/cjs/{Spring.js → animators/SpringAnimator.js} +57 -12
- package/dist/cjs/animators/TweenAnimator.js +75 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/react/useSpringValue.js +12 -42
- package/dist/esm/Animator.js +254 -156
- package/dist/esm/IFieldAnimator.js +5 -0
- package/dist/esm/{Spring.js → animators/SpringAnimator.js} +56 -11
- package/dist/esm/animators/TweenAnimator.js +68 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/react/useSpringValue.js +12 -42
- package/dist/types/Animator.d.ts +88 -63
- package/dist/types/IFieldAnimator.d.ts +9 -0
- package/dist/types/{Spring.d.ts → animators/SpringAnimator.d.ts} +11 -1
- package/dist/types/animators/TweenAnimator.d.ts +21 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/react/useSpringState.d.ts +1 -1
- package/dist/types/react/useSpringValue.d.ts +1 -1
- package/package.json +5 -13
- package/dist/cjs/three/ThreeAnimator.js +0 -453
- package/dist/cjs/three/index.js +0 -17
- package/dist/esm/three/ThreeAnimator.js +0 -450
- package/dist/esm/three/index.js +0 -1
- package/dist/types/three/ThreeAnimator.d.ts +0 -56
- package/dist/types/three/index.d.ts +0 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TweenAnimator = void 0;
|
|
4
|
+
exports.linearStep = linearStep;
|
|
5
|
+
exports.easeInOutStep = easeInOutStep;
|
|
6
|
+
exports.easeInStep = easeInStep;
|
|
7
|
+
exports.easeOutStep = easeOutStep;
|
|
8
|
+
const IFieldAnimator_js_1 = require("../IFieldAnimator.js");
|
|
9
|
+
const defaultParams = {
|
|
10
|
+
duration_s: 0.5, // default duration of the tween in seconds
|
|
11
|
+
easingFn: linearStep, // default easing function is linear
|
|
12
|
+
};
|
|
13
|
+
exports.TweenAnimator = {
|
|
14
|
+
createState(obj, field, target, params) {
|
|
15
|
+
return {
|
|
16
|
+
x0: obj[field], // initial value of the field
|
|
17
|
+
t0_ms: performance.now(), // time when the tween started in milliseconds
|
|
18
|
+
target: target,
|
|
19
|
+
velocity: 0,
|
|
20
|
+
...(params ?? defaultParams),
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
updateState(state, object, field, target, params) {
|
|
24
|
+
state.target = target;
|
|
25
|
+
state.x0 = object[field]; // update the initial value of the field
|
|
26
|
+
state.t0_ms = performance.now(); // reset the start time of the tween
|
|
27
|
+
state.duration_s = params?.duration_s ?? defaultParams.duration_s; // update the duration of the tween
|
|
28
|
+
state.easingFn = params?.easingFn ?? defaultParams.easingFn; //
|
|
29
|
+
},
|
|
30
|
+
step(state, object, field, params, dt_s) {
|
|
31
|
+
// step the tween
|
|
32
|
+
let x = object[field];
|
|
33
|
+
params.easingFn(object, field, state.target, state, dt_s);
|
|
34
|
+
let x_new = object[field];
|
|
35
|
+
state.velocity = (x_new - x) / dt_s;
|
|
36
|
+
// remove the tween if it's complete
|
|
37
|
+
let deltaTime_s = (performance.now() - state.t0_ms) / 1000;
|
|
38
|
+
if (deltaTime_s >= state.duration_s) {
|
|
39
|
+
object[field] = state.target;
|
|
40
|
+
return IFieldAnimator_js_1.StepResult.Complete;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return IFieldAnimator_js_1.StepResult.Continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function linearStep(object, field, target, params, dt_s) {
|
|
48
|
+
let dx = target - params.x0;
|
|
49
|
+
let t = (performance.now() - params.t0_ms) / 1000;
|
|
50
|
+
let u = t / params.duration_s;
|
|
51
|
+
let x_new = params.x0 + dx * u;
|
|
52
|
+
object[field] = x_new;
|
|
53
|
+
}
|
|
54
|
+
// cubic ease in out
|
|
55
|
+
function easeInOutStep(object, field, target, params, dt_s) {
|
|
56
|
+
let dx = target - params.x0;
|
|
57
|
+
let t = (performance.now() - params.t0_ms) / 1000;
|
|
58
|
+
let u = t / params.duration_s;
|
|
59
|
+
let x_new = params.x0 + dx * u * u * (3 - 2 * u);
|
|
60
|
+
object[field] = x_new;
|
|
61
|
+
}
|
|
62
|
+
function easeInStep(object, field, target, params, dt_s) {
|
|
63
|
+
let dx = target - params.x0;
|
|
64
|
+
let t = (performance.now() - params.t0_ms) / 1000;
|
|
65
|
+
let u = t / params.duration_s;
|
|
66
|
+
let x_new = params.x0 + dx * u * u * u;
|
|
67
|
+
object[field] = x_new;
|
|
68
|
+
}
|
|
69
|
+
function easeOutStep(object, field, target, params, dt_s) {
|
|
70
|
+
let dx = target - params.x0;
|
|
71
|
+
let t = (performance.now() - params.t0_ms) / 1000;
|
|
72
|
+
let u = t / params.duration_s;
|
|
73
|
+
let x_new = params.x0 + dx * (1 - Math.pow(1 - u, 3));
|
|
74
|
+
object[field] = x_new;
|
|
75
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -16,4 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./AnimationSequencer.js"), exports);
|
|
18
18
|
__exportStar(require("./Animator.js"), exports);
|
|
19
|
-
__exportStar(require("./
|
|
19
|
+
__exportStar(require("./IFieldAnimator.js"), exports);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useSpringValue = useSpringValue;
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
const useAnimator_js_1 = require("./useAnimator.js");
|
|
6
4
|
const use_initializer_1 = require("use-initializer");
|
|
5
|
+
const useAnimator_js_1 = require("./useAnimator.js");
|
|
6
|
+
const react_1 = require("react");
|
|
7
7
|
/**
|
|
8
8
|
* A value that animates to a target value using a spring animation.
|
|
9
9
|
* This will **not** cause a re-render when the value changes.
|
|
@@ -13,47 +13,17 @@ const use_initializer_1 = require("use-initializer");
|
|
|
13
13
|
function useSpringValue(options, onChange) {
|
|
14
14
|
const animator = (0, useAnimator_js_1.useAnimator)(options.animator);
|
|
15
15
|
const springValue = (0, use_initializer_1.useInitializer)(() => {
|
|
16
|
-
let value = structuredClone(options.initial);
|
|
17
16
|
return {
|
|
18
|
-
|
|
19
|
-
return value;
|
|
20
|
-
},
|
|
21
|
-
set value(newValue) {
|
|
22
|
-
value = newValue;
|
|
23
|
-
onChange(value);
|
|
24
|
-
},
|
|
17
|
+
value: structuredClone(options.initial),
|
|
25
18
|
};
|
|
26
19
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (Array.isArray(options.initial)) {
|
|
37
|
-
for (let i = 0; i < options.initial.length; i++) {
|
|
38
|
-
animator.springTo(springValue.value, i, options.target[i], options);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
// assume object, iterate over keys
|
|
43
|
-
for (const key in options.initial) {
|
|
44
|
-
animator.springTo(springValue.value, key, options.target[key], options);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (!afterStepListener.current) {
|
|
48
|
-
afterStepListener.current = animator.onAfterStep.addListener(() => {
|
|
49
|
-
onChange(springValue.value);
|
|
50
|
-
});
|
|
51
|
-
animator.onAllComplete(springValue.value, () => {
|
|
52
|
-
afterStepListener.current?.remove();
|
|
53
|
-
afterStepListener.current = null;
|
|
54
|
-
}, 'once');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
20
|
+
(0, react_1.useEffect)(() => {
|
|
21
|
+
let remove = animator.onChange(springValue, () => {
|
|
22
|
+
onChange(springValue.value);
|
|
23
|
+
}).remove;
|
|
24
|
+
return () => {
|
|
25
|
+
remove();
|
|
26
|
+
};
|
|
27
|
+
}, [springValue, onChange]);
|
|
28
|
+
animator.springTo(springValue, { value: options.target }, options);
|
|
59
29
|
}
|
package/dist/esm/Animator.js
CHANGED
|
@@ -1,81 +1,80 @@
|
|
|
1
1
|
import { EventSignal } from "@haxiomic/event-signal";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
AnimationType[AnimationType["Spring"] = 0] = "Spring";
|
|
6
|
-
AnimationType[AnimationType["Tween"] = 1] = "Tween";
|
|
7
|
-
})(AnimationType || (AnimationType = {}));
|
|
2
|
+
import { StepResult } from "./IFieldAnimator.js";
|
|
3
|
+
import { SpringAnimator } from "./animators/SpringAnimator.js";
|
|
4
|
+
import { easeInOutStep, easeInStep, easeOutStep, linearStep, TweenAnimator } from "./animators/TweenAnimator.js";
|
|
8
5
|
/**
|
|
9
6
|
* Physically based animation of numeric properties of objects
|
|
10
7
|
*
|
|
11
8
|
* Designed to avoid discontinuities for smooth animation in all conditions
|
|
12
9
|
*/
|
|
13
10
|
export class Animator {
|
|
14
|
-
onBeforeStep = new EventSignal();
|
|
15
|
-
onAfterStep = new EventSignal();
|
|
16
|
-
_onAnimationComplete = new EventSignal();
|
|
17
|
-
_onObjectAnimationsComplete = new EventSignal();
|
|
18
11
|
animations = new Map();
|
|
12
|
+
events = {
|
|
13
|
+
beforeStep: new EventSignal(),
|
|
14
|
+
afterStep: new EventSignal(),
|
|
15
|
+
completeField: new EventSignal(),
|
|
16
|
+
completeObject: new EventSignal(),
|
|
17
|
+
};
|
|
18
|
+
changeObjectEvents = new Map();
|
|
19
|
+
changeFieldEvents = new Map();
|
|
20
|
+
// we use these signals to coalesce object change events
|
|
21
|
+
beforeChange = new EventSignal();
|
|
22
|
+
afterChange = new EventSignal();
|
|
19
23
|
constructor(onBeforeStep, onAfterStep) {
|
|
20
24
|
if (onBeforeStep) {
|
|
21
|
-
this.
|
|
25
|
+
this.events.beforeStep.addListener(e => onBeforeStep(e.dt_s));
|
|
22
26
|
}
|
|
23
27
|
if (onAfterStep) {
|
|
24
|
-
this.
|
|
28
|
+
this.events.afterStep.addListener(e => onAfterStep(e.dt_s));
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
spring.step = null;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
this.setTo(object, field, target);
|
|
38
|
-
}
|
|
31
|
+
setTo(object, target) {
|
|
32
|
+
this.beforeChange.dispatch();
|
|
33
|
+
forObjectFieldsRecursive(object, target, (obj, field, targetValue) => {
|
|
34
|
+
this.setFieldTo(obj, field, targetValue);
|
|
35
|
+
this.dispatchChangeObjectEvent(obj);
|
|
36
|
+
});
|
|
37
|
+
this.afterChange.dispatch();
|
|
39
38
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
animation.tweenParams = {
|
|
45
|
-
x0: object[field],
|
|
46
|
-
t0_ms: performance.now(),
|
|
47
|
-
duration_s: duration_s,
|
|
48
|
-
};
|
|
49
|
-
animation.step = step;
|
|
39
|
+
animateTo(object, target, animator = SpringAnimator, params = null) {
|
|
40
|
+
forObjectFieldsRecursive(object, target, (subObj, field, targetValue) => {
|
|
41
|
+
this.syncAnimation(subObj, field, targetValue, animator, params);
|
|
42
|
+
});
|
|
50
43
|
}
|
|
51
|
-
|
|
52
|
-
this.
|
|
44
|
+
springTo(object, target, params) {
|
|
45
|
+
this.animateTo(object, target, SpringAnimator, params);
|
|
53
46
|
}
|
|
54
|
-
|
|
55
|
-
this.
|
|
47
|
+
customTweenTo(object, target, duration_s, easingFn) {
|
|
48
|
+
this.animateTo(object, target, TweenAnimator, {
|
|
49
|
+
duration_s,
|
|
50
|
+
easingFn,
|
|
51
|
+
});
|
|
56
52
|
}
|
|
57
|
-
|
|
58
|
-
this.customTweenTo(object,
|
|
53
|
+
linearTo(object, target, duration_s) {
|
|
54
|
+
this.customTweenTo(object, target, duration_s, linearStep);
|
|
59
55
|
}
|
|
60
|
-
|
|
61
|
-
this.customTweenTo(object,
|
|
56
|
+
easeInOutTo(object, target, duration_s) {
|
|
57
|
+
this.customTweenTo(object, target, duration_s, easeInOutStep);
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
object[field] = target;
|
|
59
|
+
easeInTo(object, target, duration_s) {
|
|
60
|
+
this.customTweenTo(object, target, duration_s, easeInStep);
|
|
61
|
+
}
|
|
62
|
+
easeOutTo(object, target, duration_s) {
|
|
63
|
+
this.customTweenTo(object, target, duration_s, easeOutStep);
|
|
69
64
|
}
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
onCompleteField(object, field, callback, once) {
|
|
66
|
+
let listener = this.events.completeField.addListener(e => {
|
|
72
67
|
if (e.object === object && e.field === field) {
|
|
73
68
|
callback(object, field);
|
|
69
|
+
if (once) {
|
|
70
|
+
listener.remove();
|
|
71
|
+
}
|
|
74
72
|
}
|
|
75
73
|
});
|
|
74
|
+
return listener;
|
|
76
75
|
}
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
onComplete(object, callback, once) {
|
|
77
|
+
const listener = this.events.completeObject.addListener(e => {
|
|
79
78
|
if (e.object === object) {
|
|
80
79
|
callback(object);
|
|
81
80
|
if (once) {
|
|
@@ -85,65 +84,92 @@ export class Animator {
|
|
|
85
84
|
});
|
|
86
85
|
return listener;
|
|
87
86
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
this.
|
|
87
|
+
onChangeField(object, field, callback) {
|
|
88
|
+
// check if field is an object
|
|
89
|
+
if (typeof object[field] === 'object' && object[field] !== null) {
|
|
90
|
+
return this.onChange(object[field], (subObject) => {
|
|
91
|
+
callback(object, field);
|
|
92
|
+
});
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
+
else {
|
|
95
|
+
// add a listener for this field
|
|
96
|
+
return this.addChangeFieldListener(object, field, callback);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
onChange(object, callback) {
|
|
100
|
+
// add a listener for this object and every sub-object
|
|
101
|
+
const removeCallbacks = new Array();
|
|
102
|
+
// coalesce events within a single step
|
|
103
|
+
let objectChanged = false;
|
|
104
|
+
removeCallbacks.push(this.beforeChange.addListener(() => {
|
|
105
|
+
objectChanged = false;
|
|
106
|
+
}).remove, this.afterChange.addListener(() => {
|
|
107
|
+
if (objectChanged) {
|
|
108
|
+
callback(object);
|
|
109
|
+
}
|
|
110
|
+
}).remove);
|
|
111
|
+
const subObjectChangedCallback = (subObject) => {
|
|
112
|
+
objectChanged = true;
|
|
113
|
+
};
|
|
114
|
+
enumerateObjects(object, (subObject) => {
|
|
115
|
+
let signal = this.changeObjectEvents.get(object);
|
|
116
|
+
if (signal == null) {
|
|
117
|
+
signal = new EventSignal();
|
|
118
|
+
this.changeObjectEvents.set(subObject, signal);
|
|
119
|
+
}
|
|
120
|
+
const subListener = signal.addListener(subObjectChangedCallback);
|
|
121
|
+
removeCallbacks.push(() => {
|
|
122
|
+
subListener.remove();
|
|
123
|
+
if (!signal.hasListeners()) {
|
|
124
|
+
this.changeObjectEvents.delete(object);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
remove: () => {
|
|
130
|
+
for (const remove of removeCallbacks) {
|
|
131
|
+
remove();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
onBeforeStep(callback) {
|
|
137
|
+
return this.events.beforeStep.addListener(e => callback(e.dt_s));
|
|
138
|
+
}
|
|
139
|
+
onAfterStep(callback) {
|
|
140
|
+
return this.events.afterStep.addListener(e => callback(e.dt_s));
|
|
141
|
+
}
|
|
142
|
+
step(dt_s) {
|
|
143
|
+
this.events.beforeStep.dispatch({ dt_s });
|
|
144
|
+
this.beforeChange.dispatch();
|
|
94
145
|
// step all animations
|
|
95
|
-
this.animations.
|
|
96
|
-
objectAnims.
|
|
97
|
-
|
|
98
|
-
|
|
146
|
+
for (let [object, objectAnims] of this.animations.entries()) {
|
|
147
|
+
for (let [field, animation] of objectAnims.entries()) {
|
|
148
|
+
let result = animation.animator.step(animation.state, object, field, animation.params, dt_s);
|
|
149
|
+
// dispatch the field change event
|
|
150
|
+
this.dispatchChangeFieldEvent(object, field);
|
|
151
|
+
// handle animation completion
|
|
152
|
+
switch (result) {
|
|
153
|
+
case StepResult.Complete:
|
|
99
154
|
{
|
|
100
|
-
// step the spring
|
|
101
|
-
springState.x = object[field];
|
|
102
|
-
springState.targetX = animation.target;
|
|
103
|
-
springState.v = animation.velocity;
|
|
104
|
-
if (animation.springParams != null) {
|
|
105
|
-
Spring.stepSpring(dt_s, springState, animation.springParams);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// instant transition: set to the target
|
|
109
|
-
springState.x = springState.targetX;
|
|
110
|
-
springState.v = 0;
|
|
111
|
-
}
|
|
112
|
-
// update the object
|
|
113
|
-
object[field] = springState.x;
|
|
114
|
-
animation.velocity = springState.v;
|
|
115
|
-
// remove the spring if it's close enough to the target and velocity is close to 0
|
|
116
|
-
if (Math.abs(springState.x - springState.targetX) < 0.0001 && Math.abs(springState.v) < 0.0001) {
|
|
117
|
-
object[field] = animation.target;
|
|
118
|
-
objectAnims.delete(field);
|
|
119
|
-
this._onAnimationComplete.dispatch({ object, field });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
case AnimationType.Tween: {
|
|
124
|
-
// step the tween
|
|
125
|
-
let x = object[field];
|
|
126
|
-
animation.step(object, field, animation.target, animation.tweenParams, dt_s);
|
|
127
|
-
let x_new = object[field];
|
|
128
|
-
animation.velocity = (x_new - x) / dt_s;
|
|
129
|
-
// remove the tween if it's complete
|
|
130
|
-
let deltaTime_s = (performance.now() - animation.tweenParams.t0_ms) / 1000;
|
|
131
|
-
if (deltaTime_s >= animation.tweenParams.duration_s) {
|
|
132
|
-
object[field] = animation.target;
|
|
133
155
|
objectAnims.delete(field);
|
|
134
|
-
this.
|
|
156
|
+
this.events.completeField.dispatch({ object, field });
|
|
135
157
|
}
|
|
136
158
|
break;
|
|
137
|
-
}
|
|
138
159
|
}
|
|
139
|
-
}
|
|
160
|
+
}
|
|
161
|
+
;
|
|
162
|
+
// dispatch the object change event
|
|
163
|
+
this.dispatchChangeObjectEvent(object);
|
|
140
164
|
// remove the object if it has no more springs
|
|
141
165
|
if (objectAnims.size == 0) {
|
|
142
166
|
this.animations.delete(object);
|
|
143
|
-
this.
|
|
167
|
+
this.events.completeObject.dispatch({ object });
|
|
144
168
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
169
|
+
}
|
|
170
|
+
;
|
|
171
|
+
this.afterChange.dispatch();
|
|
172
|
+
this.events.afterStep.dispatch({ dt_s });
|
|
147
173
|
}
|
|
148
174
|
t_last = -1;
|
|
149
175
|
tick() {
|
|
@@ -208,31 +234,106 @@ export class Animator {
|
|
|
208
234
|
* Remove animation for this object and field if it exists
|
|
209
235
|
* Does not change the value of the field
|
|
210
236
|
*/
|
|
211
|
-
remove(object, field) {
|
|
212
|
-
let
|
|
213
|
-
if (
|
|
214
|
-
|
|
237
|
+
remove(object, field, dispatchComplete = false) {
|
|
238
|
+
let objectAnimations = this.animations.get(object);
|
|
239
|
+
if (objectAnimations != null) {
|
|
240
|
+
objectAnimations.delete(field);
|
|
241
|
+
if (dispatchComplete) {
|
|
242
|
+
this.events.completeField.dispatch({ object, field });
|
|
243
|
+
}
|
|
215
244
|
}
|
|
216
245
|
// if there are no more springs for this object, remove it from the map
|
|
217
|
-
if (
|
|
218
|
-
this.
|
|
246
|
+
if (objectAnimations != null && objectAnimations.size == 0) {
|
|
247
|
+
this.removeObject(object, dispatchComplete);
|
|
219
248
|
}
|
|
220
249
|
}
|
|
221
250
|
/**
|
|
222
251
|
* Remove all animations for this object
|
|
223
252
|
*/
|
|
224
|
-
removeObject(object) {
|
|
253
|
+
removeObject(object, dispatchComplete = false) {
|
|
225
254
|
this.animations.delete(object);
|
|
255
|
+
if (dispatchComplete) {
|
|
256
|
+
this.events.completeObject.dispatch({ object });
|
|
257
|
+
}
|
|
226
258
|
}
|
|
227
259
|
/**
|
|
228
260
|
* Remove all animations
|
|
229
261
|
*/
|
|
230
|
-
removeAll() {
|
|
262
|
+
removeAll(dispatchComplete = false) {
|
|
263
|
+
for (let [object, objectAnimations] of this.animations.entries()) {
|
|
264
|
+
for (let field of objectAnimations.keys()) {
|
|
265
|
+
this.remove(object, field, dispatchComplete);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
231
268
|
this.animations.clear();
|
|
232
269
|
}
|
|
233
|
-
|
|
234
|
-
let
|
|
235
|
-
return
|
|
270
|
+
getState(object, field) {
|
|
271
|
+
let animation = this.getObjectAnimations(object).get(field);
|
|
272
|
+
return animation?.state;
|
|
273
|
+
}
|
|
274
|
+
dispatchChangeObjectEvent(object) {
|
|
275
|
+
let signal = this.changeObjectEvents.get(object);
|
|
276
|
+
if (signal == null) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
signal.dispatch(object);
|
|
280
|
+
}
|
|
281
|
+
dispatchChangeFieldEvent(object, field) {
|
|
282
|
+
const map = this.changeFieldEvents.get(object);
|
|
283
|
+
if (map == null) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
let signal = map.get(field);
|
|
287
|
+
if (signal == null) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (signal.hasListeners()) {
|
|
291
|
+
signal.dispatch({ object, field });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
addChangeFieldListener(object, field, callback) {
|
|
295
|
+
const getOrCreateChangeFieldSignal = (object, field) => {
|
|
296
|
+
let map = this.changeFieldEvents.get(object);
|
|
297
|
+
if (map == null) {
|
|
298
|
+
map = new Map();
|
|
299
|
+
this.changeFieldEvents.set(object, map);
|
|
300
|
+
}
|
|
301
|
+
let signal = map.get(field);
|
|
302
|
+
if (signal == null) {
|
|
303
|
+
signal = new EventSignal();
|
|
304
|
+
map.set(field, signal);
|
|
305
|
+
}
|
|
306
|
+
return signal;
|
|
307
|
+
};
|
|
308
|
+
const signal = getOrCreateChangeFieldSignal(object, field);
|
|
309
|
+
const listener = signal.addListener((e) => {
|
|
310
|
+
callback(e.object, e.field);
|
|
311
|
+
});
|
|
312
|
+
return {
|
|
313
|
+
remove: () => {
|
|
314
|
+
listener.remove();
|
|
315
|
+
// cleanup
|
|
316
|
+
if (!signal.hasListeners()) {
|
|
317
|
+
let map = this.changeFieldEvents.get(object);
|
|
318
|
+
map?.delete(field);
|
|
319
|
+
if (map != null && map.size === 0) {
|
|
320
|
+
this.changeFieldEvents.delete(object);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Remove animation from the object and set the field to the target value
|
|
328
|
+
*
|
|
329
|
+
* This completes the animation immediately and dispatches the onComplete event
|
|
330
|
+
*/
|
|
331
|
+
setFieldTo(object, field, targetValue) {
|
|
332
|
+
const dispatchComplete = true;
|
|
333
|
+
this.remove(object, field, dispatchComplete);
|
|
334
|
+
object[field] = targetValue;
|
|
335
|
+
// dispatch the field change event
|
|
336
|
+
this.dispatchChangeFieldEvent(object, field);
|
|
236
337
|
}
|
|
237
338
|
/**
|
|
238
339
|
* Creates a new map if one doesn't already exist for the given object
|
|
@@ -249,58 +350,55 @@ export class Animator {
|
|
|
249
350
|
/**
|
|
250
351
|
* Creates a new spring if one doesn't already exist for the given object and field
|
|
251
352
|
*/
|
|
252
|
-
|
|
353
|
+
syncAnimation(object, field, targetValue, fieldAnimator, params = null) {
|
|
253
354
|
let objectAnimations = this.getObjectAnimations(object);
|
|
254
355
|
let animation = objectAnimations.get(field);
|
|
255
|
-
|
|
356
|
+
let animatorChanged = animation?.animator !== fieldAnimator;
|
|
357
|
+
if (animation == null || animatorChanged) {
|
|
256
358
|
// create
|
|
257
359
|
animation = {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
tweenParams: null,
|
|
262
|
-
velocity: 0,
|
|
263
|
-
step: null
|
|
360
|
+
animator: fieldAnimator,
|
|
361
|
+
state: fieldAnimator.createState(object, field, targetValue, params),
|
|
362
|
+
params,
|
|
264
363
|
};
|
|
265
364
|
objectAnimations.set(field, animation);
|
|
266
365
|
}
|
|
267
|
-
|
|
366
|
+
else {
|
|
367
|
+
animation.params = params;
|
|
368
|
+
animation.animator.updateState(animation.state, object, field, targetValue, params);
|
|
369
|
+
}
|
|
268
370
|
return animation;
|
|
269
371
|
}
|
|
270
372
|
}
|
|
271
|
-
|
|
272
|
-
(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
object[field] = x_new;
|
|
304
|
-
}
|
|
305
|
-
Tween.easeOutStep = easeOutStep;
|
|
306
|
-
})(Tween || (Tween = {}));
|
|
373
|
+
function forObjectFieldsRecursive(sourceObj, targetObj, callback) {
|
|
374
|
+
for (let field in targetObj) {
|
|
375
|
+
if (Object.prototype.hasOwnProperty.call(targetObj, field)) {
|
|
376
|
+
let targetValue = targetObj[field];
|
|
377
|
+
if (typeof targetValue === 'object') {
|
|
378
|
+
forObjectFieldsRecursive(sourceObj[field], targetValue, callback);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
callback(sourceObj, field, targetValue);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function enumerateObjects(input, callback) {
|
|
387
|
+
// Call callback for the current object if it's an object or array
|
|
388
|
+
if (typeof input === 'object' && input !== null) {
|
|
389
|
+
callback(input);
|
|
390
|
+
// Recursively enumerate nested objects
|
|
391
|
+
if (Array.isArray(input)) {
|
|
392
|
+
for (const item of input) {
|
|
393
|
+
enumerateObjects(item, callback);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
for (const key in input) {
|
|
398
|
+
if (input.hasOwnProperty(key)) {
|
|
399
|
+
enumerateObjects(input[key], callback);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|