physics-animator 0.2.1 → 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 +2 -10
- 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
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Or via state
|
|
|
20
20
|
```tsx
|
|
21
21
|
const opacity = useSpringState({ initial: 0, target: 1, duration_s: 0.8 })
|
|
22
22
|
|
|
23
|
-
return <div style={opacity} />
|
|
23
|
+
return <div style={{ opacity }} />
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
It works with arrays and objects
|
|
@@ -36,13 +36,22 @@ Outside of react we use the animator object
|
|
|
36
36
|
const animator = new Animator();
|
|
37
37
|
animator.startAnimationFrameLoop();
|
|
38
38
|
|
|
39
|
-
animator.springTo(character,
|
|
39
|
+
animator.springTo(character, { opacity: 1 }, { duration_s: 0.8, bounce: 1 })
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
We can animate
|
|
42
|
+
We can animate nested fields
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
|
-
animator.springTo(character,
|
|
45
|
+
animator.springTo(character, { position: { x, y, z } }, { duration_s: 2 })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
And get notified of changes, even if they're nested within the object
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
animator.springTo(character.color, { rgb: [1, 0, 0] }, { duration_s: 2 })
|
|
52
|
+
|
|
53
|
+
// will fire if any property within character is changed by the animator
|
|
54
|
+
animator.onChange(character, () => render());
|
|
46
55
|
```
|
|
47
56
|
|
|
48
57
|
Velocity state is stored within the animator object
|
package/dist/cjs/Animator.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Animator = void 0;
|
|
4
4
|
const event_signal_1 = require("@haxiomic/event-signal");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
AnimationType[AnimationType["Spring"] = 0] = "Spring";
|
|
9
|
-
AnimationType[AnimationType["Tween"] = 1] = "Tween";
|
|
10
|
-
})(AnimationType || (AnimationType = {}));
|
|
5
|
+
const IFieldAnimator_js_1 = require("./IFieldAnimator.js");
|
|
6
|
+
const SpringAnimator_js_1 = require("./animators/SpringAnimator.js");
|
|
7
|
+
const TweenAnimator_js_1 = require("./animators/TweenAnimator.js");
|
|
11
8
|
/**
|
|
12
9
|
* Physically based animation of numeric properties of objects
|
|
13
10
|
*
|
|
@@ -15,73 +12,74 @@ var AnimationType;
|
|
|
15
12
|
*/
|
|
16
13
|
class Animator {
|
|
17
14
|
constructor(onBeforeStep, onAfterStep) {
|
|
18
|
-
this.onBeforeStep = new event_signal_1.EventSignal();
|
|
19
|
-
this.onAfterStep = new event_signal_1.EventSignal();
|
|
20
|
-
this._onAnimationComplete = new event_signal_1.EventSignal();
|
|
21
|
-
this._onObjectAnimationsComplete = new event_signal_1.EventSignal();
|
|
22
15
|
this.animations = new Map();
|
|
23
|
-
this.
|
|
16
|
+
this.events = {
|
|
17
|
+
beforeStep: new event_signal_1.EventSignal(),
|
|
18
|
+
afterStep: new event_signal_1.EventSignal(),
|
|
19
|
+
completeField: new event_signal_1.EventSignal(),
|
|
20
|
+
completeObject: new event_signal_1.EventSignal(),
|
|
21
|
+
};
|
|
22
|
+
this.changeObjectEvents = new Map();
|
|
23
|
+
this.changeFieldEvents = new Map();
|
|
24
|
+
// we use these signals to coalesce object change events
|
|
25
|
+
this.beforeChange = new event_signal_1.EventSignal();
|
|
26
|
+
this.afterChange = new event_signal_1.EventSignal();
|
|
24
27
|
this.t_last = -1;
|
|
25
28
|
this._currentLoopControl = null;
|
|
26
29
|
if (onBeforeStep) {
|
|
27
|
-
this.
|
|
30
|
+
this.events.beforeStep.addListener(e => onBeforeStep(e.dt_s));
|
|
28
31
|
}
|
|
29
32
|
if (onAfterStep) {
|
|
30
|
-
this.
|
|
33
|
+
this.events.afterStep.addListener(e => onAfterStep(e.dt_s));
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
spring.step = null;
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
this.setTo(object, field, target);
|
|
44
|
-
}
|
|
36
|
+
setTo(object, target) {
|
|
37
|
+
this.beforeChange.dispatch();
|
|
38
|
+
forObjectFieldsRecursive(object, target, (obj, field, targetValue) => {
|
|
39
|
+
this.setFieldTo(obj, field, targetValue);
|
|
40
|
+
this.dispatchChangeObjectEvent(obj);
|
|
41
|
+
});
|
|
42
|
+
this.afterChange.dispatch();
|
|
45
43
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
animation.tweenParams = {
|
|
51
|
-
x0: object[field],
|
|
52
|
-
t0_ms: performance.now(),
|
|
53
|
-
duration_s: duration_s,
|
|
54
|
-
};
|
|
55
|
-
animation.step = step;
|
|
44
|
+
animateTo(object, target, animator = SpringAnimator_js_1.SpringAnimator, params = null) {
|
|
45
|
+
forObjectFieldsRecursive(object, target, (subObj, field, targetValue) => {
|
|
46
|
+
this.syncAnimation(subObj, field, targetValue, animator, params);
|
|
47
|
+
});
|
|
56
48
|
}
|
|
57
|
-
|
|
58
|
-
this.
|
|
49
|
+
springTo(object, target, params) {
|
|
50
|
+
this.animateTo(object, target, SpringAnimator_js_1.SpringAnimator, params);
|
|
59
51
|
}
|
|
60
|
-
|
|
61
|
-
this.
|
|
52
|
+
customTweenTo(object, target, duration_s, easingFn) {
|
|
53
|
+
this.animateTo(object, target, TweenAnimator_js_1.TweenAnimator, {
|
|
54
|
+
duration_s,
|
|
55
|
+
easingFn,
|
|
56
|
+
});
|
|
62
57
|
}
|
|
63
|
-
|
|
64
|
-
this.customTweenTo(object,
|
|
58
|
+
linearTo(object, target, duration_s) {
|
|
59
|
+
this.customTweenTo(object, target, duration_s, TweenAnimator_js_1.linearStep);
|
|
65
60
|
}
|
|
66
|
-
|
|
67
|
-
this.customTweenTo(object,
|
|
61
|
+
easeInOutTo(object, target, duration_s) {
|
|
62
|
+
this.customTweenTo(object, target, duration_s, TweenAnimator_js_1.easeInOutStep);
|
|
68
63
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*/
|
|
72
|
-
setTo(object, field, target) {
|
|
73
|
-
this.remove(object, field);
|
|
74
|
-
object[field] = target;
|
|
64
|
+
easeInTo(object, target, duration_s) {
|
|
65
|
+
this.customTweenTo(object, target, duration_s, TweenAnimator_js_1.easeInStep);
|
|
75
66
|
}
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
easeOutTo(object, target, duration_s) {
|
|
68
|
+
this.customTweenTo(object, target, duration_s, TweenAnimator_js_1.easeOutStep);
|
|
69
|
+
}
|
|
70
|
+
onCompleteField(object, field, callback, once) {
|
|
71
|
+
let listener = this.events.completeField.addListener(e => {
|
|
78
72
|
if (e.object === object && e.field === field) {
|
|
79
73
|
callback(object, field);
|
|
74
|
+
if (once) {
|
|
75
|
+
listener.remove();
|
|
76
|
+
}
|
|
80
77
|
}
|
|
81
78
|
});
|
|
79
|
+
return listener;
|
|
82
80
|
}
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
onComplete(object, callback, once) {
|
|
82
|
+
const listener = this.events.completeObject.addListener(e => {
|
|
85
83
|
if (e.object === object) {
|
|
86
84
|
callback(object);
|
|
87
85
|
if (once) {
|
|
@@ -91,64 +89,92 @@ class Animator {
|
|
|
91
89
|
});
|
|
92
90
|
return listener;
|
|
93
91
|
}
|
|
94
|
-
|
|
95
|
-
if
|
|
96
|
-
|
|
92
|
+
onChangeField(object, field, callback) {
|
|
93
|
+
// check if field is an object
|
|
94
|
+
if (typeof object[field] === 'object' && object[field] !== null) {
|
|
95
|
+
return this.onChange(object[field], (subObject) => {
|
|
96
|
+
callback(object, field);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// add a listener for this field
|
|
101
|
+
return this.addChangeFieldListener(object, field, callback);
|
|
97
102
|
}
|
|
98
|
-
|
|
103
|
+
}
|
|
104
|
+
onChange(object, callback) {
|
|
105
|
+
// add a listener for this object and every sub-object
|
|
106
|
+
const removeCallbacks = new Array();
|
|
107
|
+
// coalesce events within a single step
|
|
108
|
+
let objectChanged = false;
|
|
109
|
+
removeCallbacks.push(this.beforeChange.addListener(() => {
|
|
110
|
+
objectChanged = false;
|
|
111
|
+
}).remove, this.afterChange.addListener(() => {
|
|
112
|
+
if (objectChanged) {
|
|
113
|
+
callback(object);
|
|
114
|
+
}
|
|
115
|
+
}).remove);
|
|
116
|
+
const subObjectChangedCallback = (subObject) => {
|
|
117
|
+
objectChanged = true;
|
|
118
|
+
};
|
|
119
|
+
enumerateObjects(object, (subObject) => {
|
|
120
|
+
let signal = this.changeObjectEvents.get(object);
|
|
121
|
+
if (signal == null) {
|
|
122
|
+
signal = new event_signal_1.EventSignal();
|
|
123
|
+
this.changeObjectEvents.set(subObject, signal);
|
|
124
|
+
}
|
|
125
|
+
const subListener = signal.addListener(subObjectChangedCallback);
|
|
126
|
+
removeCallbacks.push(() => {
|
|
127
|
+
subListener.remove();
|
|
128
|
+
if (!signal.hasListeners()) {
|
|
129
|
+
this.changeObjectEvents.delete(object);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
remove: () => {
|
|
135
|
+
for (const remove of removeCallbacks) {
|
|
136
|
+
remove();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
onBeforeStep(callback) {
|
|
142
|
+
return this.events.beforeStep.addListener(e => callback(e.dt_s));
|
|
143
|
+
}
|
|
144
|
+
onAfterStep(callback) {
|
|
145
|
+
return this.events.afterStep.addListener(e => callback(e.dt_s));
|
|
146
|
+
}
|
|
147
|
+
step(dt_s) {
|
|
148
|
+
this.events.beforeStep.dispatch({ dt_s });
|
|
149
|
+
this.beforeChange.dispatch();
|
|
99
150
|
// step all animations
|
|
100
|
-
this.animations.
|
|
101
|
-
objectAnims.
|
|
102
|
-
|
|
103
|
-
|
|
151
|
+
for (let [object, objectAnims] of this.animations.entries()) {
|
|
152
|
+
for (let [field, animation] of objectAnims.entries()) {
|
|
153
|
+
let result = animation.animator.step(animation.state, object, field, animation.params, dt_s);
|
|
154
|
+
// dispatch the field change event
|
|
155
|
+
this.dispatchChangeFieldEvent(object, field);
|
|
156
|
+
// handle animation completion
|
|
157
|
+
switch (result) {
|
|
158
|
+
case IFieldAnimator_js_1.StepResult.Complete:
|
|
104
159
|
{
|
|
105
|
-
// step the spring
|
|
106
|
-
springState.x = object[field];
|
|
107
|
-
springState.targetX = animation.target;
|
|
108
|
-
springState.v = animation.velocity;
|
|
109
|
-
if (animation.springParams != null) {
|
|
110
|
-
Spring_js_1.Spring.stepSpring(dt_s, springState, animation.springParams);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
// instant transition: set to the target
|
|
114
|
-
springState.x = springState.targetX;
|
|
115
|
-
springState.v = 0;
|
|
116
|
-
}
|
|
117
|
-
// update the object
|
|
118
|
-
object[field] = springState.x;
|
|
119
|
-
animation.velocity = springState.v;
|
|
120
|
-
// remove the spring if it's close enough to the target and velocity is close to 0
|
|
121
|
-
if (Math.abs(springState.x - springState.targetX) < 0.0001 && Math.abs(springState.v) < 0.0001) {
|
|
122
|
-
object[field] = animation.target;
|
|
123
|
-
objectAnims.delete(field);
|
|
124
|
-
this._onAnimationComplete.dispatch({ object, field });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
break;
|
|
128
|
-
case AnimationType.Tween: {
|
|
129
|
-
// step the tween
|
|
130
|
-
let x = object[field];
|
|
131
|
-
animation.step(object, field, animation.target, animation.tweenParams, dt_s);
|
|
132
|
-
let x_new = object[field];
|
|
133
|
-
animation.velocity = (x_new - x) / dt_s;
|
|
134
|
-
// remove the tween if it's complete
|
|
135
|
-
let deltaTime_s = (performance.now() - animation.tweenParams.t0_ms) / 1000;
|
|
136
|
-
if (deltaTime_s >= animation.tweenParams.duration_s) {
|
|
137
|
-
object[field] = animation.target;
|
|
138
160
|
objectAnims.delete(field);
|
|
139
|
-
this.
|
|
161
|
+
this.events.completeField.dispatch({ object, field });
|
|
140
162
|
}
|
|
141
163
|
break;
|
|
142
|
-
}
|
|
143
164
|
}
|
|
144
|
-
}
|
|
165
|
+
}
|
|
166
|
+
;
|
|
167
|
+
// dispatch the object change event
|
|
168
|
+
this.dispatchChangeObjectEvent(object);
|
|
145
169
|
// remove the object if it has no more springs
|
|
146
170
|
if (objectAnims.size == 0) {
|
|
147
171
|
this.animations.delete(object);
|
|
148
|
-
this.
|
|
172
|
+
this.events.completeObject.dispatch({ object });
|
|
149
173
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
174
|
+
}
|
|
175
|
+
;
|
|
176
|
+
this.afterChange.dispatch();
|
|
177
|
+
this.events.afterStep.dispatch({ dt_s });
|
|
152
178
|
}
|
|
153
179
|
tick() {
|
|
154
180
|
let t_s = performance.now() / 1000;
|
|
@@ -211,31 +237,106 @@ class Animator {
|
|
|
211
237
|
* Remove animation for this object and field if it exists
|
|
212
238
|
* Does not change the value of the field
|
|
213
239
|
*/
|
|
214
|
-
remove(object, field) {
|
|
215
|
-
let
|
|
216
|
-
if (
|
|
217
|
-
|
|
240
|
+
remove(object, field, dispatchComplete = false) {
|
|
241
|
+
let objectAnimations = this.animations.get(object);
|
|
242
|
+
if (objectAnimations != null) {
|
|
243
|
+
objectAnimations.delete(field);
|
|
244
|
+
if (dispatchComplete) {
|
|
245
|
+
this.events.completeField.dispatch({ object, field });
|
|
246
|
+
}
|
|
218
247
|
}
|
|
219
248
|
// if there are no more springs for this object, remove it from the map
|
|
220
|
-
if (
|
|
221
|
-
this.
|
|
249
|
+
if (objectAnimations != null && objectAnimations.size == 0) {
|
|
250
|
+
this.removeObject(object, dispatchComplete);
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
253
|
/**
|
|
225
254
|
* Remove all animations for this object
|
|
226
255
|
*/
|
|
227
|
-
removeObject(object) {
|
|
256
|
+
removeObject(object, dispatchComplete = false) {
|
|
228
257
|
this.animations.delete(object);
|
|
258
|
+
if (dispatchComplete) {
|
|
259
|
+
this.events.completeObject.dispatch({ object });
|
|
260
|
+
}
|
|
229
261
|
}
|
|
230
262
|
/**
|
|
231
263
|
* Remove all animations
|
|
232
264
|
*/
|
|
233
|
-
removeAll() {
|
|
265
|
+
removeAll(dispatchComplete = false) {
|
|
266
|
+
for (let [object, objectAnimations] of this.animations.entries()) {
|
|
267
|
+
for (let field of objectAnimations.keys()) {
|
|
268
|
+
this.remove(object, field, dispatchComplete);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
234
271
|
this.animations.clear();
|
|
235
272
|
}
|
|
236
|
-
|
|
237
|
-
let
|
|
238
|
-
return
|
|
273
|
+
getState(object, field) {
|
|
274
|
+
let animation = this.getObjectAnimations(object).get(field);
|
|
275
|
+
return animation?.state;
|
|
276
|
+
}
|
|
277
|
+
dispatchChangeObjectEvent(object) {
|
|
278
|
+
let signal = this.changeObjectEvents.get(object);
|
|
279
|
+
if (signal == null) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
signal.dispatch(object);
|
|
283
|
+
}
|
|
284
|
+
dispatchChangeFieldEvent(object, field) {
|
|
285
|
+
const map = this.changeFieldEvents.get(object);
|
|
286
|
+
if (map == null) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
let signal = map.get(field);
|
|
290
|
+
if (signal == null) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (signal.hasListeners()) {
|
|
294
|
+
signal.dispatch({ object, field });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
addChangeFieldListener(object, field, callback) {
|
|
298
|
+
const getOrCreateChangeFieldSignal = (object, field) => {
|
|
299
|
+
let map = this.changeFieldEvents.get(object);
|
|
300
|
+
if (map == null) {
|
|
301
|
+
map = new Map();
|
|
302
|
+
this.changeFieldEvents.set(object, map);
|
|
303
|
+
}
|
|
304
|
+
let signal = map.get(field);
|
|
305
|
+
if (signal == null) {
|
|
306
|
+
signal = new event_signal_1.EventSignal();
|
|
307
|
+
map.set(field, signal);
|
|
308
|
+
}
|
|
309
|
+
return signal;
|
|
310
|
+
};
|
|
311
|
+
const signal = getOrCreateChangeFieldSignal(object, field);
|
|
312
|
+
const listener = signal.addListener((e) => {
|
|
313
|
+
callback(e.object, e.field);
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
remove: () => {
|
|
317
|
+
listener.remove();
|
|
318
|
+
// cleanup
|
|
319
|
+
if (!signal.hasListeners()) {
|
|
320
|
+
let map = this.changeFieldEvents.get(object);
|
|
321
|
+
map?.delete(field);
|
|
322
|
+
if (map != null && map.size === 0) {
|
|
323
|
+
this.changeFieldEvents.delete(object);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Remove animation from the object and set the field to the target value
|
|
331
|
+
*
|
|
332
|
+
* This completes the animation immediately and dispatches the onComplete event
|
|
333
|
+
*/
|
|
334
|
+
setFieldTo(object, field, targetValue) {
|
|
335
|
+
const dispatchComplete = true;
|
|
336
|
+
this.remove(object, field, dispatchComplete);
|
|
337
|
+
object[field] = targetValue;
|
|
338
|
+
// dispatch the field change event
|
|
339
|
+
this.dispatchChangeFieldEvent(object, field);
|
|
239
340
|
}
|
|
240
341
|
/**
|
|
241
342
|
* Creates a new map if one doesn't already exist for the given object
|
|
@@ -252,59 +353,56 @@ class Animator {
|
|
|
252
353
|
/**
|
|
253
354
|
* Creates a new spring if one doesn't already exist for the given object and field
|
|
254
355
|
*/
|
|
255
|
-
|
|
356
|
+
syncAnimation(object, field, targetValue, fieldAnimator, params = null) {
|
|
256
357
|
let objectAnimations = this.getObjectAnimations(object);
|
|
257
358
|
let animation = objectAnimations.get(field);
|
|
258
|
-
|
|
359
|
+
let animatorChanged = animation?.animator !== fieldAnimator;
|
|
360
|
+
if (animation == null || animatorChanged) {
|
|
259
361
|
// create
|
|
260
362
|
animation = {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
tweenParams: null,
|
|
265
|
-
velocity: 0,
|
|
266
|
-
step: null
|
|
363
|
+
animator: fieldAnimator,
|
|
364
|
+
state: fieldAnimator.createState(object, field, targetValue, params),
|
|
365
|
+
params,
|
|
267
366
|
};
|
|
268
367
|
objectAnimations.set(field, animation);
|
|
269
368
|
}
|
|
270
|
-
|
|
369
|
+
else {
|
|
370
|
+
animation.params = params;
|
|
371
|
+
animation.animator.updateState(animation.state, object, field, targetValue, params);
|
|
372
|
+
}
|
|
271
373
|
return animation;
|
|
272
374
|
}
|
|
273
375
|
}
|
|
274
376
|
exports.Animator = Animator;
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
object[field] = x_new;
|
|
308
|
-
}
|
|
309
|
-
Tween.easeOutStep = easeOutStep;
|
|
310
|
-
})(Tween || (exports.Tween = Tween = {}));
|
|
377
|
+
function forObjectFieldsRecursive(sourceObj, targetObj, callback) {
|
|
378
|
+
for (let field in targetObj) {
|
|
379
|
+
if (Object.prototype.hasOwnProperty.call(targetObj, field)) {
|
|
380
|
+
let targetValue = targetObj[field];
|
|
381
|
+
if (typeof targetValue === 'object') {
|
|
382
|
+
forObjectFieldsRecursive(sourceObj[field], targetValue, callback);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
callback(sourceObj, field, targetValue);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function enumerateObjects(input, callback) {
|
|
391
|
+
// Call callback for the current object if it's an object or array
|
|
392
|
+
if (typeof input === 'object' && input !== null) {
|
|
393
|
+
callback(input);
|
|
394
|
+
// Recursively enumerate nested objects
|
|
395
|
+
if (Array.isArray(input)) {
|
|
396
|
+
for (const item of input) {
|
|
397
|
+
enumerateObjects(item, callback);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
for (const key in input) {
|
|
402
|
+
if (input.hasOwnProperty(key)) {
|
|
403
|
+
enumerateObjects(input[key], callback);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StepResult = void 0;
|
|
4
|
+
var StepResult;
|
|
5
|
+
(function (StepResult) {
|
|
6
|
+
StepResult[StepResult["Continue"] = 0] = "Continue";
|
|
7
|
+
StepResult[StepResult["Complete"] = 1] = "Complete";
|
|
8
|
+
})(StepResult || (exports.StepResult = StepResult = {}));
|
|
@@ -1,11 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Spring
|
|
4
|
-
*
|
|
5
|
-
* @author George Corney (haxiomic)
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.Spring = void 0;
|
|
3
|
+
exports.Spring = exports.SpringAnimator = void 0;
|
|
4
|
+
const IFieldAnimator_js_1 = require("../IFieldAnimator.js");
|
|
5
|
+
const defaultSpringParameters = {
|
|
6
|
+
duration_s: 0.5,
|
|
7
|
+
};
|
|
8
|
+
exports.SpringAnimator = {
|
|
9
|
+
createState(obj, field, target, params) {
|
|
10
|
+
return {
|
|
11
|
+
x: obj[field],
|
|
12
|
+
targetX: target,
|
|
13
|
+
v: 0,
|
|
14
|
+
physicsParameters: Spring.getPhysicsParameters(params ?? defaultSpringParameters),
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
updateState(state, object, field, target, params) {
|
|
18
|
+
state.x = object[field];
|
|
19
|
+
state.targetX = target;
|
|
20
|
+
state.physicsParameters = Spring.getPhysicsParameters(params ?? defaultSpringParameters);
|
|
21
|
+
},
|
|
22
|
+
step(state, object, field, params, dt_s) {
|
|
23
|
+
let physicsParameters = state.physicsParameters;
|
|
24
|
+
// step the spring
|
|
25
|
+
if (physicsParameters != null && isFinite(physicsParameters.strength) && isFinite(physicsParameters.damping)) {
|
|
26
|
+
Spring.stepSpring(dt_s, state, physicsParameters);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// instant transition: set to the target
|
|
30
|
+
state.x = state.targetX;
|
|
31
|
+
state.v = 0;
|
|
32
|
+
}
|
|
33
|
+
// update the object
|
|
34
|
+
object[field] = state.x;
|
|
35
|
+
// complete the animation if it's close enough to the target and velocity is close to 0
|
|
36
|
+
if (Math.abs(state.x - state.targetX) < 0.0001 && Math.abs(state.v) < 0.0001) {
|
|
37
|
+
object[field] = state.targetX;
|
|
38
|
+
return IFieldAnimator_js_1.StepResult.Complete;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return IFieldAnimator_js_1.StepResult.Continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
9
45
|
var Spring;
|
|
10
46
|
(function (Spring) {
|
|
11
47
|
/**
|
|
@@ -16,7 +52,7 @@ var Spring;
|
|
|
16
52
|
* `strength = damping * damping / 4`
|
|
17
53
|
*/
|
|
18
54
|
function Exponential(options) {
|
|
19
|
-
//
|
|
55
|
+
// found numerically
|
|
20
56
|
const halfLifeConstant = 3.356694; // from solve (1+u)*exp(-u)=0.5 for u, and constant = 2u
|
|
21
57
|
const pointOnePercentConstant = 18.46682; // from solve (1+u)*exp(-u)=0.001 for u, and constant = 2u
|
|
22
58
|
const damping = pointOnePercentConstant / options.duration_s;
|
|
@@ -29,11 +65,12 @@ var Spring;
|
|
|
29
65
|
// -2ln(0.001) = b t
|
|
30
66
|
const durationTarget = 0.001; // 0.1% of target
|
|
31
67
|
let damping = -2 * Math.log(durationTarget) / duration_s;
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
68
|
+
// see https://www.desmos.com/calculator/h43ylohte7
|
|
69
|
+
const strength = 0.25 * (((2 * bounce * Math.PI) / duration_s) ** 2 + damping ** 2);
|
|
70
|
+
return {
|
|
71
|
+
damping,
|
|
72
|
+
strength,
|
|
73
|
+
};
|
|
37
74
|
}
|
|
38
75
|
Spring.Underdamped = Underdamped;
|
|
39
76
|
function getPhysicsParameters(parameters) {
|
|
@@ -56,6 +93,8 @@ var Spring;
|
|
|
56
93
|
* @param dt_s
|
|
57
94
|
* @param state
|
|
58
95
|
* @param parameters
|
|
96
|
+
*
|
|
97
|
+
* If parameters are NaN or infinite, the spring will skip to the target
|
|
59
98
|
*/
|
|
60
99
|
function stepSpring(dt_s, state, parameters) {
|
|
61
100
|
// analytic integration (unconditionally stable)
|
|
@@ -74,6 +113,12 @@ var Spring;
|
|
|
74
113
|
return;
|
|
75
114
|
if (dt_s === 0)
|
|
76
115
|
return;
|
|
116
|
+
if (!isFinite(k) || !isFinite(b) || !isFinite(v0) || !isFinite(dx0)) {
|
|
117
|
+
// skip to target
|
|
118
|
+
state.x = state.targetX;
|
|
119
|
+
state.v = 0;
|
|
120
|
+
return 0; // no energy
|
|
121
|
+
}
|
|
77
122
|
let critical = k * 4 - b * b;
|
|
78
123
|
if (critical > 0) {
|
|
79
124
|
// under damped
|