physics-animator 0.1.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 +50 -0
- package/dist/AnimationSequencer.d.ts +36 -0
- package/dist/AnimationSequencer.js +151 -0
- package/dist/Animator.d.ts +125 -0
- package/dist/Animator.js +306 -0
- package/dist/Spring.d.ts +47 -0
- package/dist/Spring.js +117 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +3 -0
- package/dist/react/useAnimator.d.ts +12 -0
- package/dist/react/useAnimator.js +26 -0
- package/dist/react/useSpringState.d.ts +15 -0
- package/dist/react/useSpringState.js +13 -0
- package/dist/react/useSpringValue.d.ts +15 -0
- package/dist/react/useSpringValue.js +56 -0
- package/dist/three/ThreeAnimator.d.ts +56 -0
- package/dist/three/ThreeAnimator.js +450 -0
- package/dist/three/index.d.ts +1 -0
- package/dist/three/index.js +1 -0
- package/package.json +50 -0
- package/src/AnimationSequencer.ts +193 -0
- package/src/Animator.ts +377 -0
- package/src/Spring.ts +162 -0
- package/src/index.ts +3 -0
- package/src/react/index.ts +3 -0
- package/src/react/useAnimator.ts +45 -0
- package/src/react/useSpringState.ts +22 -0
- package/src/react/useSpringValue.ts +81 -0
- package/src/three/ThreeAnimator.ts +605 -0
- package/src/three/index.ts +1 -0
- package/tsconfig.json +14 -0
package/dist/Spring.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring
|
|
3
|
+
*
|
|
4
|
+
* @author George Corney (haxiomic)
|
|
5
|
+
*/
|
|
6
|
+
type ExponentialParameters = {
|
|
7
|
+
/** Defined as the point in time we'll reach within 0.01% of target from 0 velocity start */
|
|
8
|
+
duration_s: number;
|
|
9
|
+
};
|
|
10
|
+
type UnderdampedParameters = {
|
|
11
|
+
/** Defined as the point in time we'll reach within 0.01% of target from 0 velocity start */
|
|
12
|
+
duration_s: number;
|
|
13
|
+
/**
|
|
14
|
+
* How soft / bouncy the spring, at 0 there is no bounce and decay is exponential, from 0 to infinity the spring will overshoot its target while decaying
|
|
15
|
+
* It can be loosely through of roughly the number of oscillations it will take to reach the target
|
|
16
|
+
*/
|
|
17
|
+
bounce: number;
|
|
18
|
+
};
|
|
19
|
+
export type SpringParameters = ExponentialParameters | UnderdampedParameters | Spring.PhysicsParameters;
|
|
20
|
+
export declare namespace Spring {
|
|
21
|
+
type PhysicsParameters = {
|
|
22
|
+
strength: number;
|
|
23
|
+
damping: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Starting with 0 velocity, this parameter describes how long it would take to reach half-way to the target
|
|
27
|
+
*
|
|
28
|
+
* `damping = 3.356694 / approxHalfLife_s`
|
|
29
|
+
*
|
|
30
|
+
* `strength = damping * damping / 4`
|
|
31
|
+
*/
|
|
32
|
+
function Exponential(options: ExponentialParameters): PhysicsParameters;
|
|
33
|
+
function Underdamped(options: UnderdampedParameters): PhysicsParameters;
|
|
34
|
+
function getPhysicsParameters(parameters: SpringParameters): PhysicsParameters;
|
|
35
|
+
/**
|
|
36
|
+
* Analytic spring integration
|
|
37
|
+
* @param dt_s
|
|
38
|
+
* @param state
|
|
39
|
+
* @param parameters
|
|
40
|
+
*/
|
|
41
|
+
function stepSpring(dt_s: number, state: {
|
|
42
|
+
x: number;
|
|
43
|
+
targetX: number;
|
|
44
|
+
v: number;
|
|
45
|
+
}, parameters: PhysicsParameters): number | undefined;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
package/dist/Spring.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring
|
|
3
|
+
*
|
|
4
|
+
* @author George Corney (haxiomic)
|
|
5
|
+
*/
|
|
6
|
+
export var Spring;
|
|
7
|
+
(function (Spring) {
|
|
8
|
+
/**
|
|
9
|
+
* Starting with 0 velocity, this parameter describes how long it would take to reach half-way to the target
|
|
10
|
+
*
|
|
11
|
+
* `damping = 3.356694 / approxHalfLife_s`
|
|
12
|
+
*
|
|
13
|
+
* `strength = damping * damping / 4`
|
|
14
|
+
*/
|
|
15
|
+
function Exponential(options) {
|
|
16
|
+
// solved numerically
|
|
17
|
+
const halfLifeConstant = 3.356694; // from solve (1+u)*exp(-u)=0.5 for u, and constant = 2u
|
|
18
|
+
const pointOnePercentConstant = 18.46682; // from solve (1+u)*exp(-u)=0.001 for u, and constant = 2u
|
|
19
|
+
const damping = pointOnePercentConstant / options.duration_s;
|
|
20
|
+
let strength = damping * damping / 4;
|
|
21
|
+
return { damping, strength };
|
|
22
|
+
}
|
|
23
|
+
Spring.Exponential = Exponential;
|
|
24
|
+
function Underdamped(options) {
|
|
25
|
+
const { duration_s, bounce } = options;
|
|
26
|
+
// -2ln(0.001) = b t
|
|
27
|
+
const durationTarget = 0.001; // 0.1% of target
|
|
28
|
+
let damping = -2 * Math.log(durationTarget) / duration_s;
|
|
29
|
+
// 4k - b^2 > 0
|
|
30
|
+
let bSq = damping * damping;
|
|
31
|
+
const criticalStrength = bSq / 4;
|
|
32
|
+
let strength = criticalStrength + (bounce * bounce + 1);
|
|
33
|
+
return { damping, strength };
|
|
34
|
+
}
|
|
35
|
+
Spring.Underdamped = Underdamped;
|
|
36
|
+
function getPhysicsParameters(parameters) {
|
|
37
|
+
if ('duration_s' in parameters) {
|
|
38
|
+
if ('bounce' in parameters) {
|
|
39
|
+
return Underdamped(parameters);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return Exponential(parameters);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// assume physics parameters
|
|
47
|
+
return parameters;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
Spring.getPhysicsParameters = getPhysicsParameters;
|
|
51
|
+
/**
|
|
52
|
+
* Analytic spring integration
|
|
53
|
+
* @param dt_s
|
|
54
|
+
* @param state
|
|
55
|
+
* @param parameters
|
|
56
|
+
*/
|
|
57
|
+
function stepSpring(dt_s, state, parameters) {
|
|
58
|
+
// analytic integration (unconditionally stable)
|
|
59
|
+
// visualization: https://www.desmos.com/calculator/c2iug0kerh
|
|
60
|
+
// references:
|
|
61
|
+
// https://mathworld.wolfram.com/OverdampedSimpleHarmonicMotion.html
|
|
62
|
+
// https://mathworld.wolfram.com/CriticallyDampedSimpleHarmonicMotion.html
|
|
63
|
+
// https://mathworld.wolfram.com/UnderdampedSimpleHarmonicMotion.html
|
|
64
|
+
let k = parameters.strength;
|
|
65
|
+
let b = parameters.damping;
|
|
66
|
+
let t = dt_s;
|
|
67
|
+
let v0 = state.v;
|
|
68
|
+
let dx0 = state.x - state.targetX;
|
|
69
|
+
// nothing will change; exit early
|
|
70
|
+
if (dx0 === 0 && v0 === 0)
|
|
71
|
+
return;
|
|
72
|
+
if (dt_s === 0)
|
|
73
|
+
return;
|
|
74
|
+
let critical = k * 4 - b * b;
|
|
75
|
+
if (critical > 0) {
|
|
76
|
+
// under damped
|
|
77
|
+
let q = 0.5 * Math.sqrt(critical); // γ
|
|
78
|
+
let A = dx0;
|
|
79
|
+
let B = ((b * dx0) * 0.5 + v0) / q;
|
|
80
|
+
let m = Math.exp(-b * 0.5 * t);
|
|
81
|
+
let c = Math.cos(q * t);
|
|
82
|
+
let s = Math.sin(q * t);
|
|
83
|
+
let dx1 = m * (A * c + B * s);
|
|
84
|
+
let v1 = m * ((B * q - 0.5 * A * b) * c +
|
|
85
|
+
(-A * q - 0.5 * b * B) * s);
|
|
86
|
+
state.v = v1;
|
|
87
|
+
state.x = dx1 + state.targetX;
|
|
88
|
+
}
|
|
89
|
+
else if (critical < 0) {
|
|
90
|
+
// over damped
|
|
91
|
+
let u = 0.5 * Math.sqrt(-critical);
|
|
92
|
+
let p = -0.5 * b + u;
|
|
93
|
+
let n = -0.5 * b - u;
|
|
94
|
+
let B = -(n * dx0 - v0) / (2 * u);
|
|
95
|
+
let A = dx0 - B;
|
|
96
|
+
let ep = Math.exp(p * t);
|
|
97
|
+
let en = Math.exp(n * t);
|
|
98
|
+
let dx1 = A * en + B * ep;
|
|
99
|
+
let v1 = A * n * en + B * p * ep;
|
|
100
|
+
state.v = v1;
|
|
101
|
+
state.x = dx1 + state.targetX;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// critically damped
|
|
105
|
+
let w = Math.sqrt(k); // ω
|
|
106
|
+
let A = dx0;
|
|
107
|
+
let B = v0 + w * dx0;
|
|
108
|
+
let e = Math.exp(-w * t);
|
|
109
|
+
let dx1 = (A + B * t) * e;
|
|
110
|
+
let v1 = (B - w * (A + B * t)) * e;
|
|
111
|
+
state.v = v1;
|
|
112
|
+
state.x = dx1 + state.targetX;
|
|
113
|
+
}
|
|
114
|
+
return 0.5 * k * state.x * state.x;
|
|
115
|
+
}
|
|
116
|
+
Spring.stepSpring = stepSpring;
|
|
117
|
+
})(Spring || (Spring = {}));
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Animator } from "../Animator.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns an instance of Animator running an interval loop
|
|
4
|
+
* @param interval_ms interval between animation steps, pass explicit `null` to disable / stop. Defaults to `animationFrame`
|
|
5
|
+
* @returns { Animator } instance of Animator
|
|
6
|
+
*/
|
|
7
|
+
export declare function useAnimator(interval_ms?: number | null | 'animationFrame'): Animator;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the same instance of Animator that is passed in, or creates a new one if not provided.
|
|
10
|
+
* @param animator an instance of Animator to use, will not create a new one
|
|
11
|
+
*/
|
|
12
|
+
export declare function useAnimator(animator?: Animator): Animator;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Animator } from "../Animator.js";
|
|
3
|
+
import { useInitializer } from "use-initializer";
|
|
4
|
+
export function useAnimator(input = 'animationFrame') {
|
|
5
|
+
if (input instanceof Animator) {
|
|
6
|
+
return input; // return the animator instance directly
|
|
7
|
+
}
|
|
8
|
+
const interval_ms = input;
|
|
9
|
+
const animator = useInitializer(() => new Animator(), (animator) => {
|
|
10
|
+
animator.stop();
|
|
11
|
+
animator.removeAll();
|
|
12
|
+
});
|
|
13
|
+
// react to change of interval handling
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
animator.stop();
|
|
16
|
+
if (interval_ms !== null) {
|
|
17
|
+
if (interval_ms === 'animationFrame') {
|
|
18
|
+
animator.startAnimationFrameLoop();
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
animator.startIntervalLoop(interval_ms);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}, [animator, interval_ms]);
|
|
25
|
+
return animator;
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Animator } from "../Animator.js";
|
|
2
|
+
import { SpringParameters } from "../Spring.js";
|
|
3
|
+
/**
|
|
4
|
+
* A value that animates to a target value using a spring animation.
|
|
5
|
+
* This **will** cause a re-render when the value changes.
|
|
6
|
+
*
|
|
7
|
+
* See {@link useSpringValue} for a version that does not cause re-renders.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useSpringState<T extends number | number[] | {
|
|
10
|
+
[field: PropertyKey]: number;
|
|
11
|
+
}>(options: {
|
|
12
|
+
animator?: Animator;
|
|
13
|
+
initial: T;
|
|
14
|
+
target: T;
|
|
15
|
+
} & SpringParameters): T;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useSpringValue } from "./useSpringValue.js";
|
|
3
|
+
/**
|
|
4
|
+
* A value that animates to a target value using a spring animation.
|
|
5
|
+
* This **will** cause a re-render when the value changes.
|
|
6
|
+
*
|
|
7
|
+
* See {@link useSpringValue} for a version that does not cause re-renders.
|
|
8
|
+
*/
|
|
9
|
+
export function useSpringState(options) {
|
|
10
|
+
const [state, setState] = useState(options.initial);
|
|
11
|
+
useSpringValue(options, setState);
|
|
12
|
+
return state;
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Animator } from "../Animator.js";
|
|
2
|
+
import { SpringParameters } from "../Spring.js";
|
|
3
|
+
/**
|
|
4
|
+
* A value that animates to a target value using a spring animation.
|
|
5
|
+
* This will **not** cause a re-render when the value changes.
|
|
6
|
+
*
|
|
7
|
+
* See {@link useSpringState} for a version that does cause re-renders.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useSpringValue<T extends number | number[] | {
|
|
10
|
+
[field: PropertyKey]: number;
|
|
11
|
+
}>(options: {
|
|
12
|
+
animator?: Animator;
|
|
13
|
+
initial: T;
|
|
14
|
+
target: T;
|
|
15
|
+
} & SpringParameters, onChange: (value: T) => void): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { useAnimator } from "./useAnimator.js";
|
|
3
|
+
import { useInitializer } from "use-initializer";
|
|
4
|
+
/**
|
|
5
|
+
* A value that animates to a target value using a spring animation.
|
|
6
|
+
* This will **not** cause a re-render when the value changes.
|
|
7
|
+
*
|
|
8
|
+
* See {@link useSpringState} for a version that does cause re-renders.
|
|
9
|
+
*/
|
|
10
|
+
export function useSpringValue(options, onChange) {
|
|
11
|
+
const animator = useAnimator(options.animator);
|
|
12
|
+
const springValue = useInitializer(() => {
|
|
13
|
+
let value = structuredClone(options.initial);
|
|
14
|
+
return {
|
|
15
|
+
get value() {
|
|
16
|
+
return value;
|
|
17
|
+
},
|
|
18
|
+
set value(newValue) {
|
|
19
|
+
value = newValue;
|
|
20
|
+
onChange(value);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
const afterStepListener = useRef(null);
|
|
25
|
+
switch (typeof options.initial) {
|
|
26
|
+
case 'number':
|
|
27
|
+
{
|
|
28
|
+
animator.springTo(springValue, 'value', options.target, options);
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
{
|
|
33
|
+
if (Array.isArray(options.initial)) {
|
|
34
|
+
for (let i = 0; i < options.initial.length; i++) {
|
|
35
|
+
animator.springTo(springValue.value, i, options.target[i], options);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// assume object, iterate over keys
|
|
40
|
+
for (const key in options.initial) {
|
|
41
|
+
animator.springTo(springValue.value, key, options.target[key], options);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!afterStepListener.current) {
|
|
45
|
+
afterStepListener.current = animator.onAfterStep.addListener(() => {
|
|
46
|
+
onChange(springValue.value);
|
|
47
|
+
});
|
|
48
|
+
animator.onAllComplete(springValue.value, () => {
|
|
49
|
+
afterStepListener.current?.remove();
|
|
50
|
+
afterStepListener.current = null;
|
|
51
|
+
}, 'once');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Euler, Quaternion, Vector2, Vector3, Vector4 } from "three";
|
|
2
|
+
import { Animator, TweenStepFn } from "../Animator.js";
|
|
3
|
+
import { Spring, SpringParameters } from "../Spring.js";
|
|
4
|
+
export declare enum QuaternionSpringMode {
|
|
5
|
+
DirectionRollCartesian = 0,
|
|
6
|
+
YawPitchRoll = 1
|
|
7
|
+
}
|
|
8
|
+
type SupportedTypes = Vector4 | Vector3 | Vector2 | Quaternion | Euler | number;
|
|
9
|
+
type KeysOfType<T, U> = keyof T;
|
|
10
|
+
/**
|
|
11
|
+
* Extends Animator to add support for animating vectors and quaternions
|
|
12
|
+
*/
|
|
13
|
+
export declare class ThreeAnimator {
|
|
14
|
+
animator: Animator;
|
|
15
|
+
get onAfterStep(): import("@haxiomic/event-signal").EventSignal<{
|
|
16
|
+
dt_s: number;
|
|
17
|
+
}, {
|
|
18
|
+
dt_s: number;
|
|
19
|
+
}>;
|
|
20
|
+
get onBeforeStep(): import("@haxiomic/event-signal").EventSignal<{
|
|
21
|
+
dt_s: number;
|
|
22
|
+
}, {
|
|
23
|
+
dt_s: number;
|
|
24
|
+
}>;
|
|
25
|
+
quaternionSprings: Map<Quaternion, {
|
|
26
|
+
q: Quaternion;
|
|
27
|
+
target: Quaternion;
|
|
28
|
+
direction: Vector3;
|
|
29
|
+
directionVelocity: Vector3;
|
|
30
|
+
rollVelocity: number;
|
|
31
|
+
params: Spring.PhysicsParameters | null;
|
|
32
|
+
mode: QuaternionSpringMode;
|
|
33
|
+
}>;
|
|
34
|
+
constructor(animator?: Animator);
|
|
35
|
+
setTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T): void;
|
|
36
|
+
springTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T, params?: SpringParameters, mode?: QuaternionSpringMode): void;
|
|
37
|
+
customTweenTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T, duration_s: number, step: TweenStepFn): void;
|
|
38
|
+
linearTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T, duration_s: number): void;
|
|
39
|
+
easeInOutTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T, duration_s: number): void;
|
|
40
|
+
easeInTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T, duration_s: number): void;
|
|
41
|
+
easeOutTo<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & SupportedTypes>(object: Obj, field: Name, target: T, duration_s: number): void;
|
|
42
|
+
step(dt_s: number): void;
|
|
43
|
+
tick(): void;
|
|
44
|
+
remove<Obj, Name extends KeysOfType<Obj, SupportedTypes>>(object: Obj, field: Name): void;
|
|
45
|
+
removeAll(): void;
|
|
46
|
+
getVelocity<Obj, Name extends KeysOfType<Obj, SupportedTypes>, T extends Obj[Name] & (Vector4 | Vector3 | Vector2 | {
|
|
47
|
+
directionVelocity: Vector3;
|
|
48
|
+
rollVelocity: number;
|
|
49
|
+
} | Euler | number)>(object: Obj, field: Name, into?: T): T;
|
|
50
|
+
startAnimationFrameLoop(): void;
|
|
51
|
+
startIntervalLoop(interval_ms?: number): void;
|
|
52
|
+
stop(): void;
|
|
53
|
+
private stepQuaternionSprings;
|
|
54
|
+
private getQuaternionSpring;
|
|
55
|
+
}
|
|
56
|
+
export {};
|