Heart Spiral

Nested hearts, each a little smaller and rotated a bit more than the last, spiraling inward.

Log in to post a comment.

// Turtletoy sketch: a heart, with a smaller heart nested inside the
// previous one, repeated `numHearts` times, rotating a bit more with
// each successive heart.
// Paste this directly into https://turtletoy.net
// The block below becomes sliders in the Turtletoy UI (Adjustable Variables).

Canvas.setpenopacity(1);
const turtle = new Turtle();

const numHearts = 100;    // min=1, max=1000, step=1, how many nested hearts to draw
const startScale = 5.5;   // min=1, max=15, step=.5, size of the outermost heart
const scaleStep = 0.985;  // min=.8, max=.999, step=.001, each heart's size as a fraction of the last
const steps = 120;        // min=10, max=100, step=10, points sampled per heart outline
const rotationStep = 4;   // min=0, max=45, step=.5, degrees each successive heart rotates by

// Classic parametric heart curve.
function heartPoint(t, scale) {
    const x = 16 * Math.pow(Math.sin(t), 3);
    const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
    return [x * scale, -y * scale]; // flip y so the heart points up on screen
}

function rotate(x, y, degrees) {
    const a = degrees * Math.PI / 180;
    const c = Math.cos(a), s = Math.sin(a);
    return [x * c - y * s, x * s + y * c];
}

function walk(i) {
    const scale = startScale * Math.pow(scaleStep, i);
    const angle = i * rotationStep;

    for (let j = 0; j <= steps; j++) {
        const t = (j / steps) * Math.PI * 2;
        let [x, y] = heartPoint(t, scale);
        [x, y] = rotate(x, y, angle);

        if (j === 0) {
            turtle.penup();
            turtle.goto(x, y);
            turtle.pendown();
        } else {
            turtle.goto(x, y);
        }
    }

    return i < numHearts - 1; // keep going until we've drawn numHearts hearts
}