physics-animator 0.1.0 → 0.2.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.
Files changed (43) hide show
  1. package/dist/cjs/AnimationSequencer.js +156 -0
  2. package/dist/cjs/Animator.js +310 -0
  3. package/{src/Spring.ts → dist/cjs/Spring.js} +38 -80
  4. package/dist/cjs/index.js +19 -0
  5. package/dist/cjs/package.json +1 -0
  6. package/dist/cjs/react/index.js +19 -0
  7. package/dist/cjs/react/useAnimator.js +29 -0
  8. package/dist/cjs/react/useSpringState.js +16 -0
  9. package/dist/cjs/react/useSpringValue.js +59 -0
  10. package/dist/cjs/three/ThreeAnimator.js +453 -0
  11. package/dist/cjs/three/index.js +17 -0
  12. package/dist/esm/package.json +1 -0
  13. package/package.json +17 -10
  14. package/src/AnimationSequencer.ts +0 -193
  15. package/src/Animator.ts +0 -377
  16. package/src/index.ts +0 -3
  17. package/src/react/index.ts +0 -3
  18. package/src/react/useAnimator.ts +0 -45
  19. package/src/react/useSpringState.ts +0 -22
  20. package/src/react/useSpringValue.ts +0 -81
  21. package/src/three/ThreeAnimator.ts +0 -605
  22. package/src/three/index.ts +0 -1
  23. package/tsconfig.json +0 -14
  24. /package/dist/{AnimationSequencer.js → esm/AnimationSequencer.js} +0 -0
  25. /package/dist/{Animator.js → esm/Animator.js} +0 -0
  26. /package/dist/{Spring.js → esm/Spring.js} +0 -0
  27. /package/dist/{index.js → esm/index.js} +0 -0
  28. /package/dist/{react → esm/react}/index.js +0 -0
  29. /package/dist/{react → esm/react}/useAnimator.js +0 -0
  30. /package/dist/{react → esm/react}/useSpringState.js +0 -0
  31. /package/dist/{react → esm/react}/useSpringValue.js +0 -0
  32. /package/dist/{three → esm/three}/ThreeAnimator.js +0 -0
  33. /package/dist/{three → esm/three}/index.js +0 -0
  34. /package/dist/{AnimationSequencer.d.ts → types/AnimationSequencer.d.ts} +0 -0
  35. /package/dist/{Animator.d.ts → types/Animator.d.ts} +0 -0
  36. /package/dist/{Spring.d.ts → types/Spring.d.ts} +0 -0
  37. /package/dist/{index.d.ts → types/index.d.ts} +0 -0
  38. /package/dist/{react → types/react}/index.d.ts +0 -0
  39. /package/dist/{react → types/react}/useAnimator.d.ts +0 -0
  40. /package/dist/{react → types/react}/useSpringState.d.ts +0 -0
  41. /package/dist/{react → types/react}/useSpringValue.d.ts +0 -0
  42. /package/dist/{three → types/three}/ThreeAnimator.d.ts +0 -0
  43. /package/dist/{three → types/three}/index.d.ts +0 -0
@@ -1,193 +0,0 @@
1
- import { EventSignal } from "@haxiomic/event-signal";
2
-
3
- export type Sequence = {
4
- stop: () => void,
5
- events: {
6
- onStep: EventSignal<number>,
7
- onComplete: EventSignal<void>,
8
- onFinally: EventSignal<{ complete: boolean, stepIndex: number }>,
9
- },
10
- promise: Promise<void>,
11
- };
12
-
13
- export class AnimationSequencer {
14
-
15
- timeoutHandles: Array<any> = [];
16
- intervalHandles: Array<any> = [];
17
- sequences: Array<Sequence> = [];
18
-
19
- constructor() {}
20
-
21
- /**
22
- * Execute a serial sequence of steps, firing a callback at each step. We either wait for the onCompleteEvent to be fired, or we wait for maxWait milliseconds before moving on to the next step.
23
- *
24
- * @returns Sequence - a function that can be called to stop the sequence
25
- */
26
- runSequence = (steps: Array<{
27
- callback: () => (Promise<void> | void),
28
- maxWait_ms?: number,
29
- onCompleteEvent?: EventSignal<any>
30
- }>): Sequence => {
31
- let sequenceEvents = {
32
- onStep: new EventSignal<number>(),
33
- onComplete: new EventSignal<void>(),
34
- onFinally: new EventSignal<{ complete: boolean, stepIndex: number }>(),
35
- onError: new EventSignal<any>(),
36
- }
37
-
38
- let openListeners = new Set<{ remove: () => void }>();
39
- let timeoutHandles = new Array<any>();
40
-
41
- let stepIndex = 0;
42
-
43
- const executeStep = (index: number) => {
44
- try {
45
- // check if we've reached the end of the sequence
46
- if (index >= steps.length) {
47
- sequenceEvents.onComplete.dispatch();
48
- sequenceEvents.onFinally.dispatch({
49
- complete: true,
50
- stepIndex: steps.length - 1,
51
- });
52
- return;
53
- }
54
-
55
- let step = steps[index];
56
- if (!step) {
57
- throw new Error(`Step at index ${index} is undefined`);
58
- }
59
-
60
- sequenceEvents.onStep.dispatch(index);
61
-
62
- let timeoutHandle: any = null;
63
- let completeListener: { remove: () => void } | null = null;
64
-
65
- if (step.onCompleteEvent) {
66
- let listener = step.onCompleteEvent.once(() => {
67
- openListeners.delete(listener);
68
- });
69
- }
70
-
71
- if (step.maxWait_ms) {
72
- timeoutHandle = this.setTimeout(() => next(), step.maxWait_ms);
73
- timeoutHandles.push(timeoutHandle);
74
- }
75
-
76
- if (!step.onCompleteEvent && !step.maxWait_ms) {
77
- next();
78
- }
79
-
80
- let hasFinished = false;
81
- function next() {
82
- if (hasFinished) return;
83
-
84
- clearTimeout(timeoutHandle);
85
- completeListener?.remove();
86
-
87
- stepIndex++;
88
- hasFinished = true;
89
- executeStep(stepIndex);
90
- }
91
-
92
- let result = step.callback();
93
- if ((result as any)['then']) {
94
- (result as Promise<any>).then(() => {
95
- // if no onCompleteEvent, then we can move on to the next step
96
- if (!step.onCompleteEvent) {
97
- next();
98
- }
99
- }).catch((error) => {
100
- sequenceEvents.onError.dispatch(error);
101
- stop();
102
- });
103
- }
104
- } catch (error) {
105
- sequenceEvents.onError.dispatch(error);
106
- stop();
107
- }
108
- }
109
-
110
- let stopped = false;
111
- function stop() {
112
- if (stopped) return;
113
- for (let listener of openListeners) {
114
- listener.remove();
115
- }
116
- for (let handle of timeoutHandles) {
117
- clearTimeout(handle);
118
- }
119
- sequenceEvents.onFinally.dispatch({
120
- complete: false,
121
- stepIndex,
122
- });
123
- stopped = true;
124
- }
125
-
126
- // promise interface
127
- let promise = new Promise<void>((resolve, reject) => {
128
- sequenceEvents.onComplete.once(() => resolve());
129
- sequenceEvents.onFinally.once(() => resolve());
130
- sequenceEvents.onError.once((error) => reject(error));
131
- });
132
-
133
- let sequence = {
134
- stop,
135
- events: sequenceEvents,
136
- promise,
137
- }
138
-
139
- // track sequence
140
- this.sequences.push(sequence);
141
- sequenceEvents.onFinally.once(() => {
142
- let index = this.sequences.indexOf(sequence);
143
- this.sequences.splice(index, 1);
144
- });
145
-
146
- // start
147
- executeStep(stepIndex);
148
-
149
- return sequence;
150
- }
151
-
152
- registerSequence = (sequence: Sequence) => {
153
- this.sequences.push(sequence);
154
- sequence.events.onFinally.once(() => {
155
- let index = this.sequences.indexOf(sequence);
156
- this.sequences.splice(index, 1);
157
- });
158
- }
159
-
160
- setTimeout = (callback: Function, delay: number) => {
161
- let handle = window.setTimeout(callback, delay);
162
- this.timeoutHandles.push(handle);
163
- return handle;
164
- }
165
-
166
- setInterval = (callback: Function, delay: number) => {
167
- let handle = window.setInterval(callback, delay);
168
- this.intervalHandles.push(handle);
169
- return handle;
170
- }
171
-
172
- stopAllTimeouts = () => {
173
- this.timeoutHandles.forEach(handle => clearTimeout(handle));
174
- this.timeoutHandles = [];
175
- }
176
-
177
- stopAllIntervals = () => {
178
- this.intervalHandles.forEach(handle => clearInterval(handle));
179
- this.intervalHandles = [];
180
- }
181
-
182
- stopAllSequences = () => {
183
- this.sequences.forEach(sequence => sequence.stop());
184
- this.sequences = [];
185
- }
186
-
187
- stopAll = () => {
188
- this.stopAllTimeouts();
189
- this.stopAllIntervals();
190
- this.stopAllSequences();
191
- }
192
-
193
- }
package/src/Animator.ts DELETED
@@ -1,377 +0,0 @@
1
- import { EventSignal } from "@haxiomic/event-signal";
2
- import { Spring, SpringParameters } from "./Spring.js";
3
-
4
- enum AnimationType {
5
- Spring = 0,
6
- Tween,
7
- }
8
-
9
- /**
10
- * Physically based animation of numeric properties of objects
11
- *
12
- * Designed to avoid discontinuities for smooth animation in all conditions
13
- */
14
- export class Animator {
15
-
16
- onBeforeStep = new EventSignal<{dt_s: number}>();
17
- onAfterStep = new EventSignal<{dt_s: number}>();
18
- protected _onAnimationComplete = new EventSignal<{object: any, field: string | number | symbol}>();
19
- protected _onObjectAnimationsComplete = new EventSignal<{object: any}>();
20
-
21
- animations = new Map<any, Map<string | number | symbol, {
22
- target: number,
23
- type: AnimationType,
24
- springParams: Spring.PhysicsParameters | null,
25
- tweenParams: Tween.Parameters | null,
26
- step: TweenStepFn | null,
27
- velocity: number,
28
- }>>();
29
-
30
- constructor(onBeforeStep?: (dt_s: number) => void, onAfterStep?: (dt_s: number) => void) {
31
- if (onBeforeStep) {
32
- this.onBeforeStep.addListener(e => onBeforeStep(e.dt_s));
33
- }
34
- if (onAfterStep) {
35
- this.onAfterStep.addListener(e => onAfterStep(e.dt_s));
36
- }
37
- }
38
-
39
- springTo<Obj, Name extends keyof Obj>(object: Obj, field: Name, target: Obj[Name] & number, params: SpringParameters | null = { duration_s: 0.5 }) {
40
- if (params != null) {
41
- let spring = this.getAnimationOrCreate(object, field, AnimationType.Spring);
42
- // update the target and parameters
43
- spring.type = AnimationType.Spring;
44
- spring.target = target;
45
- spring.springParams = Spring.getPhysicsParameters(params);
46
- spring.step = null;
47
- } else {
48
- this.setTo(object, field, target);
49
- }
50
- }
51
-
52
- customTweenTo<Obj, Name extends keyof Obj>(object: Obj, field: Name, target: Obj[Name] & number, duration_s: number, step: TweenStepFn) {
53
- let animation = this.getAnimationOrCreate(object, field, AnimationType.Tween);
54
- animation.type = AnimationType.Tween;
55
- animation.target = target;
56
- animation.tweenParams = {
57
- x0: object[field] as number,
58
- t0_ms: performance.now(),
59
- duration_s: duration_s,
60
- }
61
- animation.step = step;
62
- }
63
-
64
- linearTo<Obj, Name extends keyof Obj>(object: Obj, field: Name, target: Obj[Name] & number, duration_s: number) {
65
- this.customTweenTo(object, field, target, duration_s, Tween.linearStep);
66
- }
67
-
68
- easeInOutTo<Obj, Name extends keyof Obj>(object: Obj, field: Name, target: Obj[Name] & number, duration_s: number) {
69
- this.customTweenTo(object, field, target, duration_s, Tween.easeInOutStep);
70
- }
71
-
72
- easeInTo<Obj, Name extends keyof Obj>(object: Obj, field: Name, target: Obj[Name] & number, duration_s: number) {
73
- this.customTweenTo(object, field, target, duration_s, Tween.easeInStep);
74
- }
75
-
76
- easeOutTo<Obj, Name extends keyof Obj>(object: Obj, field: Name, target: Obj[Name] & number, duration_s: number) {
77
- this.customTweenTo(object, field, target, duration_s, Tween.easeOutStep);
78
- }
79
-
80
- /**
81
- * Remove animation from the object and set the field to the target value
82
- */
83
- setTo<Obj, Name extends keyof Obj, T extends Obj[Name]>(object: Obj, field: Name, target: T) {
84
- this.remove(object, field);
85
- object[field] = target;
86
- }
87
-
88
- onComplete<Obj, Name extends keyof Obj>(object: Obj, field: Name, callback: (object: Obj, field: Name) => void) {
89
- return this._onAnimationComplete.addListener(e => {
90
- if (e.object === object && e.field === field) {
91
- callback(object, field);
92
- }
93
- });
94
- }
95
-
96
- onAllComplete<Obj>(object: Obj, callback: (object: Obj) => void, once?: 'once') {
97
- let listener = this._onObjectAnimationsComplete.addListener(e => {
98
- if (e.object === object) {
99
- callback(object);
100
- if (once) {
101
- listener.remove();
102
- }
103
- }
104
- });
105
- return listener;
106
- }
107
-
108
- private _springState = { x: 0, targetX: 0, v: 0 };
109
- step(dt_s: number) {
110
- if (this.onBeforeStep.hasListeners()) {
111
- this.onBeforeStep.dispatch({dt_s});
112
- }
113
- let springState = this._springState
114
-
115
- // step all animations
116
- this.animations.forEach((objectAnims, object) => {
117
- objectAnims.forEach((animation, field) => {
118
- switch (animation.type) {
119
- case AnimationType.Spring: {
120
- // step the spring
121
- springState.x = object[field];
122
- springState.targetX = animation.target;
123
- springState.v = animation.velocity;
124
- if (animation.springParams != null) {
125
- Spring.stepSpring(dt_s, springState, animation.springParams);
126
- } else {
127
- // instant transition: set to the target
128
- springState.x = springState.targetX;
129
- springState.v = 0;
130
- }
131
- // update the object
132
- object[field] = springState.x;
133
- animation.velocity = springState.v;
134
-
135
- // remove the spring if it's close enough to the target and velocity is close to 0
136
- if (Math.abs(springState.x - springState.targetX) < 0.0001 && Math.abs(springState.v) < 0.0001) {
137
- object[field] = animation.target;
138
- objectAnims.delete(field);
139
- this._onAnimationComplete.dispatch({object, field});
140
- }
141
- } break;
142
- case AnimationType.Tween: {
143
- // step the tween
144
- let x = object[field];
145
- animation.step!(object, field, animation.target, animation.tweenParams!, dt_s);
146
- let x_new = object[field];
147
- animation.velocity = (x_new - x) / dt_s;
148
-
149
- // remove the tween if it's complete
150
- let deltaTime_s = (performance.now() - animation.tweenParams!.t0_ms) / 1000;
151
- if (deltaTime_s >= animation.tweenParams!.duration_s) {
152
- object[field] = animation.target;
153
- objectAnims.delete(field);
154
- this._onAnimationComplete.dispatch({object, field});
155
- }
156
- break;
157
- }
158
- }
159
- });
160
-
161
- // remove the object if it has no more springs
162
- if (objectAnims.size == 0) {
163
- this.animations.delete(object);
164
- this._onObjectAnimationsComplete.dispatch({object});
165
- }
166
- });
167
-
168
- this.onAfterStep.dispatch({dt_s});
169
- }
170
-
171
- private t_last = -1;
172
- tick() {
173
- let t_s = performance.now() / 1000;
174
- let dt_s = this.t_last >= 0 ? t_s - this.t_last : 1/60;
175
- this.t_last = t_s;
176
- this.step(dt_s);
177
- return dt_s;
178
- }
179
-
180
- protected _currentLoopControl: { stop: () => void, start: () => void } | null = null;
181
-
182
- /**
183
- * Start the animation loop using requestAnimationFrame
184
- *
185
- * This will stop any existing animation loop
186
- */
187
- startAnimationFrameLoop() {
188
- this.stop();
189
-
190
- let frameLoopHandle = -1;
191
- let frameLoop = () => {
192
- this.tick();
193
- frameLoopHandle = window.requestAnimationFrame(frameLoop);
194
- };
195
- frameLoop();
196
-
197
- this._currentLoopControl = {
198
- stop: () => {
199
- window.cancelAnimationFrame(frameLoopHandle);
200
- },
201
- start: () => {
202
- frameLoop();
203
- }
204
- }
205
- }
206
-
207
- /**
208
- * Start the animation loop using setTimeout
209
- *
210
- * This will stop any existing animation loop
211
- */
212
- startIntervalLoop(interval_ms: number = 1000 / 240) {
213
- this.stop();
214
-
215
- let intervalHandle = -1;
216
- let intervalLoop = () => {
217
- this.tick();
218
- intervalHandle = window.setTimeout(intervalLoop, interval_ms);
219
- };
220
- intervalLoop();
221
-
222
- this._currentLoopControl = {
223
- stop: () => {
224
- window.clearTimeout(intervalHandle);
225
- },
226
- start: () => {
227
- intervalLoop();
228
- }
229
- }
230
- }
231
-
232
- stop() {
233
- if (this._currentLoopControl != null) {
234
- this._currentLoopControl.stop();
235
- this._currentLoopControl = null;
236
- }
237
- }
238
-
239
- /**
240
- * Remove animation for this object and field if it exists
241
- * Does not change the value of the field
242
- */
243
- remove<T>(object: T, field: keyof T) {
244
- let objectSprings = this.animations.get(object);
245
- if (objectSprings != null) {
246
- objectSprings.delete(field);
247
- }
248
- // if there are no more springs for this object, remove it from the map
249
- if (objectSprings != null && objectSprings.size == 0) {
250
- this.animations.delete(object);
251
- }
252
- }
253
-
254
- /**
255
- * Remove all animations for this object
256
- */
257
- removeObject(object: any) {
258
- this.animations.delete(object);
259
- }
260
-
261
- /**
262
- * Remove all animations
263
- */
264
- removeAll() {
265
- this.animations.clear();
266
- }
267
-
268
- getVelocity<Obj, Name extends keyof Obj>(object: Obj, field: Name) {
269
- let spring = this.getObjectAnimations(object).get(field);
270
- return spring?.velocity ?? 0;
271
- }
272
-
273
- /**
274
- * Creates a new map if one doesn't already exist for the given object
275
- */
276
- private getObjectAnimations(object: any) {
277
- let objectAnimations = this.animations.get(object);
278
- if (objectAnimations == null) {
279
- // create
280
- objectAnimations = new Map();
281
- this.animations.set(object, objectAnimations);
282
- }
283
- return objectAnimations;
284
- }
285
-
286
- /**
287
- * Creates a new spring if one doesn't already exist for the given object and field
288
- */
289
- private getAnimationOrCreate(object: any, field: string | number | symbol, type: AnimationType) {
290
- let objectAnimations = this.getObjectAnimations(object);
291
- let animation = objectAnimations.get(field);
292
- if (animation == null) {
293
- // create
294
- animation = {
295
- target: 0,
296
- type: type,
297
- springParams: null,
298
- tweenParams: null,
299
- velocity: 0,
300
- step: null
301
- };
302
- objectAnimations.set(field, animation);
303
- }
304
- animation.type = type;
305
- return animation;
306
- }
307
-
308
- }
309
-
310
- export type TweenStepFn = (object: any, field: string | number | symbol, target: number, params: Tween.Parameters, dt_s: number) => void;
311
-
312
- export namespace Tween {
313
-
314
- export type Parameters = {
315
- x0: number,
316
- t0_ms: number,
317
- duration_s: number,
318
- }
319
-
320
- export function linearStep(
321
- object: any,
322
- field: string | number | symbol,
323
- target: number,
324
- params: Tween.Parameters,
325
- dt_s: number
326
- ) {
327
- let dx = target - params.x0;
328
- let t = (performance.now() - params.t0_ms) / 1000;
329
- let u = t / params.duration_s;
330
- let x_new = params.x0 + dx * u;
331
- object[field] = x_new;
332
- }
333
-
334
- // cubic ease in out
335
- export function easeInOutStep(
336
- object: any,
337
- field: string | number | symbol,
338
- target: number,
339
- params: Tween.Parameters,
340
- dt_s: number
341
- ) {
342
- let dx = target - params.x0;
343
- let t = (performance.now() - params.t0_ms) / 1000;
344
- let u = t / params.duration_s;
345
- let x_new = params.x0 + dx * u * u * (3 - 2 * u);
346
- object[field] = x_new;
347
- }
348
-
349
- export function easeInStep(
350
- object: any,
351
- field: string | number | symbol,
352
- target: number,
353
- params: Tween.Parameters,
354
- dt_s: number
355
- ) {
356
- let dx = target - params.x0;
357
- let t = (performance.now() - params.t0_ms) / 1000;
358
- let u = t / params.duration_s;
359
- let x_new = params.x0 + dx * u * u * u;
360
- object[field] = x_new;
361
- }
362
-
363
- export function easeOutStep(
364
- object: any,
365
- field: string | number | symbol,
366
- target: number,
367
- params: Tween.Parameters,
368
- dt_s: number
369
- ) {
370
- let dx = target - params.x0;
371
- let t = (performance.now() - params.t0_ms) / 1000;
372
- let u = t / params.duration_s;
373
- let x_new = params.x0 + dx * (1 - Math.pow(1 - u, 3));
374
- object[field] = x_new;
375
- }
376
-
377
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './AnimationSequencer.js';
2
- export * from './Animator.js';
3
- export * from './Spring.js';
@@ -1,3 +0,0 @@
1
- export * from './useAnimator.js';
2
- export * from './useSpringValue.js';
3
- export * from './useSpringState.js';
@@ -1,45 +0,0 @@
1
- import { useEffect } from "react";
2
- import { Animator } from "../Animator.js";
3
- import { useInitializer } from "use-initializer";
4
-
5
- /**
6
- * Returns an instance of Animator running an interval loop
7
- * @param interval_ms interval between animation steps, pass explicit `null` to disable / stop. Defaults to `animationFrame`
8
- * @returns { Animator } instance of Animator
9
- */
10
- export function useAnimator(interval_ms?: number | null | 'animationFrame'): Animator;
11
- /**
12
- * Returns the same instance of Animator that is passed in, or creates a new one if not provided.
13
- * @param animator an instance of Animator to use, will not create a new one
14
- */
15
- export function useAnimator(animator?: Animator): Animator;
16
- export function useAnimator(input: number | null | 'animationFrame' | Animator = 'animationFrame'): Animator {
17
- if (input instanceof Animator) {
18
- return input; // return the animator instance directly
19
- }
20
-
21
- const interval_ms = input;
22
-
23
- const animator = useInitializer(
24
- () => new Animator(),
25
- (animator) => {
26
- animator.stop();
27
- animator.removeAll();
28
- }
29
- );
30
-
31
- // react to change of interval handling
32
- useEffect(() => {
33
- animator.stop();
34
-
35
- if (interval_ms !== null) {
36
- if (interval_ms === 'animationFrame') {
37
- animator.startAnimationFrameLoop();
38
- } else {
39
- animator.startIntervalLoop(interval_ms);
40
- }
41
- }
42
- }, [animator, interval_ms])
43
-
44
- return animator;
45
- }
@@ -1,22 +0,0 @@
1
- import { useState } from "react";
2
- import { useSpringValue } from "./useSpringValue.js";
3
- import { Animator } from "../Animator.js";
4
- import { SpringParameters } from "../Spring.js";
5
-
6
- /**
7
- * A value that animates to a target value using a spring animation.
8
- * This **will** cause a re-render when the value changes.
9
- *
10
- * See {@link useSpringValue} for a version that does not cause re-renders.
11
- */
12
- export function useSpringState<T extends number | number[] | { [field: PropertyKey]: number }>(
13
- options: {
14
- animator?: Animator,
15
- initial: T;
16
- target: T;
17
- } & SpringParameters,
18
- ) {
19
- const [state, setState] = useState(options.initial);
20
- useSpringValue(options, setState);
21
- return state;
22
- }