🚀 demo + spring
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Color, Grid } from "@snk/compute/grid";
|
||||
import { drawWorld } from "@snk/draw/drawWorld";
|
||||
import { drawLerpWorld, drawWorld } from "@snk/draw/drawWorld";
|
||||
import { Snake } from "@snk/compute/snake";
|
||||
|
||||
export const drawOptions = {
|
||||
@@ -44,5 +44,16 @@ export const createCanvas = ({
|
||||
drawWorld(ctx, grid, snake, stack, drawOptions);
|
||||
};
|
||||
|
||||
return { draw, canvas, ctx };
|
||||
const drawLerp = (
|
||||
grid: Grid,
|
||||
snake0: Snake,
|
||||
snake1: Snake,
|
||||
stack: Color[],
|
||||
k: number
|
||||
) => {
|
||||
ctx.clearRect(0, 0, 9999, 9999);
|
||||
drawLerpWorld(ctx, grid, snake0, snake1, stack, k, drawOptions);
|
||||
};
|
||||
|
||||
return { draw, drawLerp, canvas, ctx };
|
||||
};
|
||||
|
||||
@@ -3,30 +3,43 @@ import { getBestRoute } from "../compute/getBestRoute";
|
||||
import { Color, copyGrid } from "../compute/grid";
|
||||
import { grid, snake } from "./sample";
|
||||
import { step } from "@snk/compute/step";
|
||||
import { isStableAndBound, stepSpring } from "./springUtils";
|
||||
|
||||
const chain = [snake, ...getBestRoute(grid, snake)!];
|
||||
|
||||
//
|
||||
// draw
|
||||
|
||||
let k = 0;
|
||||
const spring = { x: 0, v: 0, target: 0 };
|
||||
const springParams = { tension: 120, friction: 20, maxVelocity: 50 };
|
||||
let animationFrame: number;
|
||||
|
||||
const { canvas, draw } = createCanvas(grid);
|
||||
const { canvas, drawLerp } = createCanvas(grid);
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const onChange = () => {
|
||||
const clamp = (x: number, a: number, b: number) => Math.max(a, Math.min(b, x));
|
||||
|
||||
const loop = () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
|
||||
stepSpring(spring, springParams, spring.target);
|
||||
const stable = isStableAndBound(spring, spring.target);
|
||||
|
||||
const grid0 = copyGrid(grid);
|
||||
const stack0: Color[] = [];
|
||||
let snake0 = snake;
|
||||
chain.slice(0, k).forEach((s) => {
|
||||
snake0 = s;
|
||||
step(grid0, stack0, snake0);
|
||||
});
|
||||
for (let i = 0; i < Math.min(chain.length, spring.x); i++)
|
||||
step(grid0, stack0, chain[i]);
|
||||
|
||||
draw(grid0, snake0, stack0);
|
||||
const snake0 = chain[clamp(Math.floor(spring.x), 0, chain.length - 1)];
|
||||
const snake1 = chain[clamp(Math.ceil(spring.x), 0, chain.length - 1)];
|
||||
const k = spring.x % 1;
|
||||
|
||||
drawLerp(grid0, snake0, snake1, stack0, k);
|
||||
|
||||
if (!stable) animationFrame = requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
onChange();
|
||||
loop();
|
||||
|
||||
const input = document.createElement("input") as any;
|
||||
input.type = "range";
|
||||
@@ -36,8 +49,9 @@ input.min = 0;
|
||||
input.max = chain.length;
|
||||
input.style.width = "90%";
|
||||
input.addEventListener("input", () => {
|
||||
k = +input.value;
|
||||
onChange();
|
||||
spring.target = +input.value;
|
||||
cancelAnimationFrame(animationFrame);
|
||||
animationFrame = requestAnimationFrame(loop);
|
||||
});
|
||||
document.body.append(input);
|
||||
document.body.addEventListener("click", () => input.focus());
|
||||
|
||||
63
packages/demo/springUtils.ts
Normal file
63
packages/demo/springUtils.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user