Evolving Spiral

A spiral evolving into infinity.

Log in to post a comment.

let numPoints = 150; // min=20, max=300, step=5, Total number of points in the spiral
let a = 0; // min=0, max=50, step=0.1, Starting radius
let b = 0.28; // min=0, max=3, step=0.001, Controls the tightness of the spiral
let s = 1.88; // min=0, max=3, step=0.001, Controls the spacing of the spiral

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(0.12);

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

function drawRandomCubicHermiteSpline(pStart, pEnd, numPoints, aScale) {
    // Generates a set of random control points between pStart and pEnd
    function generateRandomControlPoints(pStart, pEnd, numPoints) {
        let points = [pStart];
        let dx = pEnd.x - pStart.x;
        let dy = pEnd.y - pStart.y;
        let dTotal = Math.sqrt(dx * dx + dy * dy);
        let normalDirection = { x: -dy / dTotal, y: dx / dTotal };

        for (let i = 1; i < numPoints - 1; i++) {
            let t = i / (numPoints - 1);
            let p = { x: pStart.x + dx * t, y: pStart.y + dy * t };
            let f = Math.random() * 2 - 1;
            let offsetMagnitude = Math.sin(t * Math.PI * f) * (Math.random() * dTotal * aScale);
            p.x += offsetMagnitude * normalDirection.x;
            p.y += offsetMagnitude * normalDirection.y;
            points.push(p);
        }

        points.push(pEnd);
        return points;
    }

    // Calculates tangent using two points.
    function calculateTangent(p0, p1) {
        return { x: p1.x - p0.x, y: p1.y - p0.y };
    }

    // Calculates tangents for each control point
    function calculateTangents(points) {
        let tangents = [];
        for (let i = 0; i < points.length; i++) {
            if (i == 0) {
                tangents.push(calculateTangent(points[i], points[i + 1]));
            } else if (i == points.length - 1) {
                tangents.push(calculateTangent(points[i - 1], points[i]));
            } else {
                let tangent = calculateTangent(points[i - 1], points[i + 1]);
                tangents.push({ x: tangent.x / 2, y: tangent.y / 2 });
            }
        }
        return tangents;
    }

    // Computes a point on the Cubic Hermite spline
    function hermiteInterpolate(p0, p1, t0, t1, t) {
        let h00 = (2 * t ** 3) - (3 * t ** 2) + 1;
        let h10 = (t ** 3) - (2 * t ** 2) + t;
        let h01 = (-2 * t ** 3) + (3 * t ** 2);
        let h11 = (t ** 3) - (t ** 2);
        return {
            x: h00 * p0.x + h10 * t0.x + h01 * p1.x + h11 * t1.x,
            y: h00 * p0.y + h10 * t0.y + h01 * p1.y + h11 * t1.y
        };
    }

    // Generate control points
    let controlPoints = generateRandomControlPoints(pStart, pEnd, numPoints);

    // Calculate tangents for control points
    let tangents = calculateTangents(controlPoints);

    // Draw the spline by interpolating between each pair of points
    for (let i = 0; i < controlPoints.length - 1; i++) {
        let p0 = controlPoints[i];
        let p1 = controlPoints[i + 1];
        let t0 = tangents[i];
        let t1 = tangents[i + 1];

        turtle.penup();
        turtle.goto(p0.x, p0.y);
        turtle.pendown();

        for (let t = 0; t <= 1; t += 0.1) { // Smaller step for a smoother curve
            let point = hermiteInterpolate(p0, p1, t0, t1, t);
            turtle.goto(point.x, point.y);
        }
        turtle.penup();
    }
}

// generate a spiral path
function generateSpiralPoints(numPoints, a, b, s = 0.1, yOffset = 0) {
    let points = [];
    for (let i = 0; i < numPoints; i++) {
        // Angle increases linearly with each point
        let theta = i * s; // Adjust the s multiplier to change spacing between points

        // Radius increases with theta to create the spiral
        let r = a + b * theta; // 'a' is the starting radius, 'b' controls how tightly the spiral winds

        // Convert polar coordinates (r, theta) to Cartesian coordinates (x, y)
        let x = r * Math.cos(theta);
        let y = r * Math.sin(theta) + yOffset;

        points.push({ x, y });
    }
    return points;
}


let spiralPoints = generateSpiralPoints(numPoints, a, b, s, -12);

// draw the figure below
for (let j = 0; j < 16; j++) {
    for (let i = 0; i < j * 2 + 1; i++) {
        let x = -90 + j * 12;
        drawRandomCubicHermiteSpline({ x: x, y: 75 }, { x: x, y: 95 }, j * 2.5, 0.2);
    }
}

// The walk function will be called until it returns false.
function walk(i) {

    if (i < spiralPoints.length - 1) {
        // Use the current and next point in the spiral as start and end points for the spline
        let pStart = spiralPoints[i];
        let pEnd = spiralPoints[i + 1];
        let nCurves = i * 0.4;
        // Call the function to draw the spline segment between these points
        for (let k = 0; k < nCurves; k++) {
            drawRandomCubicHermiteSpline(pStart, pEnd, i * 0.05, 0.1 * i / spiralPoints.length);
        }

        return true; // Continue walking
    }
    return false; // Stop walking once we've processed all points
}