Perlin Pathways

Perlin noise creating intricate, organic patterns

Log in to post a comment.

Canvas.setpenopacity(1);

const size = 90;                // min=10 max=100 step=5
const perlinSize = 3;           // min=1 max=50 step=1
const drawBoth = 2;             // min=0 max=2 step=1, (no, yes, middle)
const stepSize = 0.1;           // min=0.01 max=1 step=0.01 Step size for each movement
const directionChange = 360;    // min=90 max=360 step=10 Direction change factor
const noiseAmplitude = 1;       // min=0.1 max=2 step=0.1 Noise amplitude factor

// Global code will be evaluated once.
const turtle = new Turtle();

class Perlin {
    constructor(size, gridSize) {
        this.size = size;
        this.gridSize = gridSize;
        this.grid = [];

        for (let i = 0; i <= gridSize; i++) {
            let table = [];
            for (let j = 0; j <= gridSize; j++) {
                let angle = Math.random() * 2 * Math.PI;
                let x = Math.cos(angle);
                let y = Math.sin(angle);
                table.push([x, y]);
            }
            this.grid.push(table);
        }
    }

    get(x, y) {
        x = x / 2 + this.size / 2;
        y = y / 2 + this.size / 2;
        if (x < 0) x = 0;
        if (x >= this.size) x = this.size - 0.01;
        if (y < 0) y = 0;
        if (y >= this.size) y = this.size - 0.01;

        let posx = x * this.gridSize / this.size;
        let posy = y * this.gridSize / this.size;

        let x1 = Math.floor(posx);
        let x2 = x1 + 1;
        let y1 = Math.floor(posy);
        let y2 = y1 + 1;

        let scal = [];
        scal.push(this.scalar(posx, posy, x1, y1));
        scal.push(this.scalar(posx, posy, x2, y1));
        scal.push(this.scalar(posx, posy, x1, y2));
        scal.push(this.scalar(posx, posy, x2, y2));

        let int1 = this.interpolate(posx - x1, scal[0], scal[1]);
        let int2 = this.interpolate(posx - x1, scal[2], scal[3]);

        return this.interpolate(posy - y1, int1, int2);
    }

    scalar(x, y, vx, vy) {
        x -= vx;
        y -= vy;
        return x * this.grid[vx][vy][0] + y * this.grid[vx][vy][1];
    }

    smooth(v) {
        if (v < 0) v = 0;
        if (v > 1) v = 1;
        return v ** 2 * (3 - 2 * v);
    }

    interpolate(x, a, b) {
        return a + (b - a) * this.smooth(x);
    }
}

let perlin = new Perlin(size, perlinSize);
let posx = 2 * size;
let posy = 0;
let step = 0;
let start = -size;
let direction = 180;

function walk(i) {
    if (Math.abs(posx) > size || Math.abs(posy) > size) {
        step++;
        posx = drawBoth === 2 ? 0 : start;
        posy = step - size;
        turtle.jump(posx, posy);

        if (posy > size) {
            if (drawBoth === 0) return false;
            if (start > 0) return false;
            start = size;
            step = 0;
            direction = 0;
        }
    }

    let amp = perlin.get(posx, posy);
    amp = (amp + 1) / 2 * noiseAmplitude;
    turtle.seth(amp * directionChange + direction);
    turtle.forward(stepSize);

    posx = turtle.x();
    posy = turtle.y();

    return true;
}