Stiffy
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax
// ---------- SLIDERS ----------
const penOpacity = 1.0; // min=0.05, max=1, step=0.05
const worldSize = 90; // min=60, max=120, step=1
const gridCells = 6; // min=2, max=24, step=1
const stepLen = 0.9; // min=0.2, max=2.0, step=0.05
const stepsPerPath = 160; // min=40, max=400, step=10
const pathCount = 260; // min=40, max=1200, step=20
const angleMul = 1.0; // min=0.5, max=6, step=0.1 (multiplies field rotation)
const jitter = 0.00; // min=0.00, max=0.50, step=0.01 (adds randomness per step)
const seed = 1337; // min=1, max=9999, step=1
Canvas.setpenopacity(penOpacity);
// ---------- RNG (seeded) ----------
function RNG(s) { this.s = s>>>0; }
RNG.prototype.next = function () {
// LCG (Numerical Recipes)
this.s = (1664525 * this.s + 1013904223) >>> 0;
return this.s / 4294967296;
};
const rng = new RNG(seed);
// ---------- Turtle ----------
const turtle = new Turtle();
// ---------- Perlin Noise ----------
class Perlin {
constructor(worldHalfSize, gridSize, randFn) {
this.worldHalfSize = worldHalfSize;
this.gridSize = gridSize;
this.rand = randFn;
this.grid = [];
for (let i = 0; i <= gridSize; i++) {
const row = [];
for (let j = 0; j <= gridSize; j++) {
const a = this.rand() * Math.PI * 2;
row.push([Math.cos(a), Math.sin(a)]);
}
this.grid.push(row);
}
}
fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
lerp(a, b, t) { return a + (b - a) * t; }
dot(ix, iy, x, y) {
const g = this.grid[ix][iy];
return g[0] * (x - ix) + g[1] * (y - iy);
}
// Sample Perlin noise at canvas coords cx,cy in [-worldHalfSize, +worldHalfSize]
get(cx, cy) {
const u = ((cx + this.worldHalfSize) / (2 * this.worldHalfSize)) * this.gridSize;
const v = ((cy + this.worldHalfSize) / (2 * this.worldHalfSize)) * this.gridSize;
let x0 = Math.floor(u), y0 = Math.floor(v);
let x1 = Math.min(this.gridSize, x0 + 1);
let y1 = Math.min(this.gridSize, y0 + 1);
x0 = Math.max(0, x0);
y0 = Math.max(0, y0);
const sx = u - x0, sy = v - y0;
const n00 = this.dot(x0, y0, u, v);
const n10 = this.dot(x1, y0, u, v);
const n01 = this.dot(x0, y1, u, v);
const n11 = this.dot(x1, y1, u, v);
const fx = this.fade(sx), fy = this.fade(sy);
const ix0 = this.lerp(n00, n10, fx);
const ix1 = this.lerp(n01, n11, fx);
return this.lerp(ix0, ix1, fy); // ~[-1,1]
}
}
const perlin = new Perlin(worldSize, gridCells, () => rng.next());
// ---------- Flow Field State ----------
let path = 0, step = 0;
let x = 0, y = 0;
function beginPath() {
// start inside a central box
x = (rng.next() * 2 - 1) * (worldSize * 0.9);
y = (rng.next() * 2 - 1) * (worldSize * 0.9);
turtle.penup();
turtle.goto(x, y);
turtle.pendown();
step = 0;
}
beginPath();
// ---------- Walk Loop ----------
function walk(i) {
// Noise -> angle
const n = perlin.get(x, y); // ~ [-1, 1]
let angle = (n + 1) * Math.PI; // 0..2π
angle *= angleMul; // extra swirl
// Optional jitter
angle += (rng.next() - 0.5) * jitter;
// Integrate step
x += Math.cos(angle) * stepLen;
y += Math.sin(angle) * stepLen;
// Stop if we leave the canvas bounds
if (x < -100 || x > 100 || y < -100 || y > 100) {
path++;
if (path >= pathCount) return false;
beginPath();
return true;
}
turtle.goto(x, y);
step++;
if (step >= stepsPerPath) {
path++;
if (path >= pathCount) return false;
beginPath();
}
return true;
}