Melting winds

Just me learning turtletoy. Using chat to help me explore appyling golden ratio to various designs.

Log in to post a comment.

// Phi Winds v2
// Inspired by "Triotone winds" by zoso95
// https://turtletoy.net/turtle/ac8e4f5a78
//
// Golden-ratio structured generative wind field.
// Designed for pen plotting with visible separation between lines.

Canvas.setpenopacity(0.28);

const PHI = (1 + Math.sqrt(5)) / 2;
const INV_PHI = 1 / PHI;

// ----------------------------------------------------
// Adjustable controls
// ----------------------------------------------------

const pointCount      = 34;    // min=13 max=89 step=1
const iterations      = 377;   // min=89 max=987 step=1
const lineSpacing     = 3;     // min=1 max=12 step=1
const turbulence      = 0.022; // min=0.004 max=0.05 step=0.001
const amplitude       = 20;    // min=4 max=45 step=1
const tension         = 0.48;  // min=0.15 max=0.85 step=0.01
const phiInfluence    = 0.90;  // min=0 max=1.5 step=0.05
const splineSegments  = 16;    // min=4 max=32 step=1
const seedValue       = 789;   // min=1 max=9999 step=1

// ----------------------------------------------------
// Turtle
// ----------------------------------------------------

const turtle = new Turtle();
turtle.penup();

// ----------------------------------------------------
// Seeded random number generator
// ----------------------------------------------------

let seed = seedValue;

function random() {
    const x = Math.sin(seed++) * 10000;
    return x - Math.floor(x);
}

// ----------------------------------------------------
// Cardinal spline
// ----------------------------------------------------

function drawSpline(pts, splineTension, segments) {

    const res = [];
    const p = pts.slice();

    // Duplicate edge points for open spline
    p.unshift(pts[1]);
    p.unshift(pts[0]);

    p.push(pts[pts.length - 2]);
    p.push(pts[pts.length - 1]);

    for (let i = 2; i < p.length - 4; i += 2) {

        for (let s = 0; s <= segments; s++) {

            const st = s / segments;

            const t1x =
                (p[i + 2] - p[i - 2]) * splineTension;

            const t2x =
                (p[i + 4] - p[i]) * splineTension;

            const t1y =
                (p[i + 3] - p[i - 1]) * splineTension;

            const t2y =
                (p[i + 5] - p[i + 1]) * splineTension;

            const c1 =
                2 * st * st * st -
                3 * st * st +
                1;

            const c2 =
                -2 * st * st * st +
                3 * st * st;

            const c3 =
                st * st * st -
                2 * st * st +
                st;

            const c4 =
                st * st * st -
                st * st;

            const x =
                c1 * p[i] +
                c2 * p[i + 2] +
                c3 * t1x +
                c4 * t2x;

            const y =
                c1 * p[i + 1] +
                c2 * p[i + 3] +
                c3 * t1y +
                c4 * t2y;

            res.push(x, y);
        }
    }

    if (res.length < 2) return;

    turtle.penup();
    turtle.goto(res[0], res[1]);
    turtle.pendown();

    for (let i = 2; i < res.length; i += 2) {
        turtle.goto(res[i], res[i + 1]);
    }

    turtle.penup();
}

// ----------------------------------------------------
// Control points
// ----------------------------------------------------

const points = [];
const velocities = [];

for (let i = 0; i < pointCount; i++) {

    const normalized =
        i / (pointCount - 1);

    const x =
        -100 +
        normalized * 200;

    // Random starting position
    const baseNoise =
        random() - random();

    // Golden-ratio spatial modulation
    const phiWave =
        Math.sin(
            normalized *
            Math.PI *
            PHI
        );

    const y =
        amplitude *
        baseNoise *
        (
            1 +
            phiInfluence *
            0.30 *
            phiWave
        );

    points.push(x, y);

    velocities.push(0, 0);
}

// ----------------------------------------------------
// Simulation
// ----------------------------------------------------

function updateWind(iteration) {

    for (let i = 0; i < pointCount; i++) {

        const yIndex =
            i * 2 + 1;

        const normalized =
            i / (pointCount - 1);

        // Slowly moving phi-based field
        const phiWave =
            Math.sin(
                normalized *
                Math.PI *
                PHI +
                iteration *
                INV_PHI *
                0.018
            );

        const noise =
            random() - random();

        const localTurbulence =
            turbulence *
            (
                1 +
                phiInfluence *
                0.28 *
                phiWave
            );

        velocities[yIndex] +=
            noise *
            localTurbulence;

        points[yIndex] +=
            velocities[yIndex];
    }
}

// ----------------------------------------------------
// Main walk loop
// ----------------------------------------------------

function walk(i) {

    updateWind(i);

    // IMPORTANT:
    // Simulation evolves every step,
    // but we only DRAW selected states.
    //
    // This preserves movement while keeping
    // visible separation between plotted lines.

    if (i % lineSpacing === 0) {

        drawSpline(
            points,
            tension,
            splineSegments
        );
    }

    return i < iterations;
}