Mushroom Gills 3

A circle of points connected by springs. They are constrained to a slowly growing circle. When they get too far from their neighbors, they split. Each point leaves a line behind as it moves.

Log in to post a comment.

// Forked from "Mushroom Gills 1" by trashrabbit
// https://turtletoy.net/turtle/540dbd4001

const RAD_INITIAL = 10;
const RAD_FINAL = 90;
const NUM_SEEDS = 21;  // min=3, max=100, step=1
const NUM_STEPS = 175;  // min=5, max=500, step=1
const SEED_JITTER = 0.9; // min=0, max=1, step=0.05
const LERP_FRACTION = 0.01;  // min=0, max=1.0, step=0.01
const SPLIT_DIST = 14; // min=2, max=40, step=1

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

let rand = (min, max) =>
    Math.random() * (max - min) + min;

let randInt = (min, max) =>
    Math.floor(rand(min, max));

let remap = (x, oldmin, oldmax, newmin, newmax) => {
    let t = (x - oldmin) / (oldmax - oldmin);
    return t * (newmax - newmin) + newmin;
}

let Point = (x, y) => {
    let self = {x: x, y: y};
    self.len = () =>
        Math.sqrt((self.x * self.x) + (self.y * self.y));
    self.mul = (f) => {
        self.x *= f;
        self.y *= f;
    }
    self.setlen = (newlen) => {
        self.mul(newlen/self.len());
    }
    self.saveold = () => {
        self.xold = self.x;
        self.yold = self.y;
    }
    self.lerpto = (p2, t) => {
        self.x = (self.x * (1-t)) + p2.x * t;
        self.y = (self.y * (1-t)) + p2.y * t;
    }
    self.clone = () => {
        let p2 = Point(self.x, self.y);
        p2.xold = self.xold;
        p2.yold = self.yold;
        return p2;
    }
    self.jitter = (r) => {
        self.x += rand(-r, r);
        self.y += rand(-r, r);
    }
    return self;
};

let randPoint = () =>
    Point(rand(-1, 1), rand(-1, 1));

let dist = (p1, p2) => {
    let dx = p1.x - p2.x;
    let dy = p1.y - p2.y;
    return Math.sqrt(dx*dx + dy*dy);
}
let distold = (p1, p2) => {
    let dx = p1.xold - p2.xold;
    let dy = p1.yold - p2.yold;
    return Math.sqrt(dx*dx + dy*dy);
}

let points = [];

// The walk function will be called until it returns false.
console.log('-------------------');
function walk(ii) {
    if (ii === NUM_STEPS) { return false; }
    let rad0 = remap(ii, 0, NUM_STEPS, RAD_INITIAL, RAD_FINAL);
    let rad1 = remap(ii+1, 0, NUM_STEPS, RAD_INITIAL, RAD_FINAL);
    // set out initial points
    if (ii === 0) {
        for (let pp = 0; pp < NUM_SEEDS; pp++) {
            let theta = remap(pp + Math.random() * SEED_JITTER, 0, NUM_SEEDS, 0, Math.PI * 2);
            let p = Point(Math.sin(theta), Math.cos(theta));
            p.setlen(rad0);
            points.push(p);
        }
    }
    // save existing positions
    for (let p of points) {
        p.saveold();
    }
    // smooth with neighbors and normalize to new radius
    for (let pp = 0; pp < points.length; pp++) {
        let p0 = points[(pp + 0) % points.length];
        let p1 = points[(pp + 1) % points.length];
        let p2 = points[(pp + 2) % points.length];
        let midpoint = Point((p0.xold + p2.xold)/2, (p0.yold + p2.yold)/2);
        p1.lerpto(midpoint, LERP_FRACTION);
        p1.setlen(rad1);
    }
    // split when dist between neighbors is large enough
    let points2 = [...points];
    for (let pp = points2.length-1; pp >= 0; pp--) {
        let p0 = points2[(pp + 0) % points2.length];
        let p1 = points2[(pp + 1) % points2.length];
        let p2 = points2[(pp + 2) % points2.length];
        if (dist(p0, p2) > SPLIT_DIST) {
            let p1b = p1.clone();
            p1b.lerpto(p0, rand(0.1, 0.7));
            p1b.setlen(rad1);
            //p1b.saveold();
            points.splice(pp + 1, 0, p1b);
        }
    }
    // draw
    for (let p of points) {
        turtle.jump(p.xold, p.yold);
        turtle.goto(p.x, p.y);
    }
    return true;
}