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; }