64 lines
1.4 KiB
TypeScript
64 lines
1.4 KiB
TypeScript
const epsilon = 0.01;
|
|
|
|
export const clamp = (a: number, b: number) => (x: number) =>
|
|
Math.max(a, Math.min(b, x));
|
|
|
|
/**
|
|
* step the spring, mutate the state to reflect the state at t+dt
|
|
*
|
|
*/
|
|
const stepSpringOne = (
|
|
s: { x: number; v: number },
|
|
{
|
|
tension,
|
|
friction,
|
|
maxVelocity = Infinity,
|
|
}: { tension: number; friction: number; maxVelocity?: number },
|
|
target: number,
|
|
dt = 1 / 60
|
|
) => {
|
|
const a = -tension * (s.x - target) - friction * s.v;
|
|
|
|
s.v += a * dt;
|
|
s.v = clamp(-maxVelocity / dt, maxVelocity / dt)(s.v);
|
|
s.x += s.v * dt;
|
|
};
|
|
|
|
/**
|
|
* return true if the spring is to be considered in a stable state
|
|
* ( close enough to the target and with a small enough velocity )
|
|
*/
|
|
export const isStable = (
|
|
s: { x: number; v: number },
|
|
target: number,
|
|
dt = 1 / 60
|
|
) => Math.abs(s.x - target) < epsilon && Math.abs(s.v * dt) < epsilon;
|
|
|
|
export const isStableAndBound = (
|
|
s: { x: number; v: number },
|
|
target: number,
|
|
dt?: number
|
|
) => {
|
|
const stable = isStable(s, target, dt);
|
|
if (stable) {
|
|
s.x = target;
|
|
s.v = 0;
|
|
}
|
|
return stable;
|
|
};
|
|
|
|
export const stepSpring = (
|
|
s: { x: number; v: number },
|
|
params: { tension: number; friction: number; maxVelocity?: number },
|
|
target: number,
|
|
dt = 1 / 60
|
|
) => {
|
|
const interval = 1 / 60;
|
|
|
|
while (dt > 0) {
|
|
stepSpringOne(s, params, target, Math.min(interval, dt));
|
|
// eslint-disable-next-line no-param-reassign
|
|
dt -= interval;
|
|
}
|
|
};
|