const walkUniformScale = 45.0;
createCanvas(windowWidth, windowHeight);
const curveScale = min(width, height);
const curveTr = new Transform2D(
createVector(width * 0.5, height * 0.5),
createVector(curveScale, curveScale));
let points = randomPoints(count);
const curve = Curve2D.fromPoints(points, true);
const curves = [ curve ];
curveEntity = new CurveEntity2D('From Points',
walkerSize = createVector(walkUniformScale, walkUniformScale);
const walkTr = new Transform2D(
createVector(), 0.0, walkerSize);
const mesh = Mesh2D.arrow();
walker = new MeshEntity2D('Walker',
walkerWorld = createVector();
walkerTanWorld = createVector();
walkerNormWorld = createVector();
walkerLocal = createVector();
walkerTanLocal = createVector();
walkerNormLocal = createVector();
closedLoopToggle = createCheckbox('Closed Loop', true);
closedLoopToggle.style('font-family', 'sans-serif');
closedLoopToggle.style('color', 'black');
closedLoopToggle.position(10, 10);
closedLoopToggle.elt.onchange = () => {
.closedLoop = closedLoopToggle.checked();
smoothButton = createButton('Smooth');
smoothButton.position(10, 40);
smoothButton.mousePressed(() => {
straightButton = createButton('Straighten');
straightButton.position(10, 70);
straightButton.mousePressed(() => {
dampingSlider = createSlider(0.001, 0.01, damping, 0.001);
dampingSlider.position(10, 100);
frMonitor = createDiv('0');
frMonitor.position(10, 130);
frMonitor.style('font-family', 'sans-serif');
frMonitor.style('color', 'black');
const curve = curveEntity.getCurve(0);
damping = dampingSlider.value();
let a = frameCount * damping;
const b = curve.closedLoop ? a - int(a) : 0.5 + 0.5 * cos(a);
curveEntity.evaluate(0, b,
walkerWorld, walkerTanWorld, walkerNormWorld,
walkerLocal, walkerTanLocal, walkerNormLocal);
const transform = walker.transform;
Transform2D.fromAxes(walkerNormWorld,
transform.scaleBy(walkerSize);
curveEntity.drawLabels();
frMonitor.elt.textContent = frameRate().toFixed(1);
function cubicBezierPoint(pt0, pt1, pt2, pt3,
const usq3t = ucb * 3.0 * t;
const tsq3u = tcb * 3.0 * u;
out.x = pt0.x * ucb + pt1.x * usq3t + pt2.x * tsq3u + pt3.x * tcb;
out.y = pt0.y * ucb + pt1.y * usq3t + pt2.y * tsq3u + pt3.y * tcb;
out.z = pt0.z * ucb + pt1.z * usq3t + pt2.z * tsq3u + pt3.z * tcb;
function cubicBezierTangent(pt0, pt1, pt2, pt3,
return p5.Vector.sub(pt1, pt0, out);
return p5.Vector.sub(pt3, pt2, out);
const usq3 = u * u * 3.0;
const tsq3 = t * t * 3.0;
function floorMod(a, b) {
return a - b * floor(a / b);
function perpendicular(vec, out = createVector()) {
out.set(-vec.y, vec.x, 0.0);
function randomPoints(count = 3,
left = -0.5, right = 0.5,
bottom = -0.5, top = 0.5) {
for(let i = 0; i < count; ++i) {
const x = random(left, right);
const y = random(bottom, top);
const point = createVector(x, y);