Wanderbrot

Last one in this series.

Log in to post a comment.

// Forked from "Wandering again" by llemarie
// https://turtletoy.net/turtle/c80c6ac104

// LL 2021

Canvas.setpenopacity(1);

const density = 0.08; // min=0.01 max=2 step=0.01
const steps = 1000; // min=100 max=10000 step=1
const epicenters = 2; // min=1 max=10 step=1
const round = 0.; // min=-1 max=1 step=0.01
const turn = 2.0; // min=0 max=10 step=0.1
const wobble = 0; // min=0 max=1 step=1 (No,Yes)
const draw_collision = 0; // min=0 max=1 step=1 (No,Yes)

const canvas_size = 110;

let grid;
const grid_size = 50;

const turtle = new Turtle();

let current_points;

function walk(i) {
    if (i==0) {
        initGrid();
        current_points = [];
        for (var j=0; j<epicenters; j++) {
            const cx = (Math.random() - 0.5) * canvas_size * 2;
            const cy = (Math.random() - 0.5) * canvas_size * 2;
            const ca = (Math.random() - 0.5) * Math.PI * 2;
            current_points.push(new Point(cx, cy, density, ca));
        }
        current_points.forEach(p => addToGrid(p));
    } else if ((((i % 1000) == 0) && (current_points.length < 100)) || current_points.length < 10) {
        var gi = Math.floor(Math.random() * (grid_size**2));
        var retries = grid_size ** 2;
        while (grid[gi].length < 1 && retries-- > 0) gi = (gi + 1) % (grid_size**2);
        if (grid[gi].length < 1) {
            const cx = (Math.random() - 0.5) * canvas_size * 2;
            const cy = (Math.random() - 0.5) * canvas_size * 2;
            const ca = (Math.random() - 0.5) * Math.PI * 2;
            current_points.push(new Point(cx, cy, density, ca));
        } else {
            const ri = Math.floor(Math.random() * grid[gi].length);
            const point = grid[gi][ri];
            r = point.r;
            a = point.a + Math.PI * ((Math.random()<0.5) ? -1 : 1) / (turn ? turn : (Math.random()));
            x = point.x;
            y = point.y;
            
            new_point = new Point(x, y, r, a);
    
            current_points.push(new_point);
        }
    }
    
    if (i >= steps * 100) return false;
    
    if (current_points.length < 1) return true;

    const ci = Math.floor(Math.random() * current_points.length);

    var r = current_points[ci].r;
    var a = current_points[ci].a + round / 10 * ((Math.random()<0.5) && wobble ? -1 : 1); //(Math.random() - 0.5) * 1;
    var dx = (current_points[ci].r + r) * 2 * Math.cos(a)
    var dy = (current_points[ci].r + r) * 2 * Math.sin(a)
    var x = current_points[ci].x + dx;
    var y = current_points[ci].y + dy;
    
    var new_point = new Point(x, y, r, a);
    var need_to_draw = true;
    
    if (!new_point.checkBounds() || new_point.checkCollision()) 
    {
        if (draw_collision) draw(current_points[ci], new_point);

        current_points.splice(ci, 1);
        return true;
    }
    
    addToGrid(new_point);
    
    if (need_to_draw) {
        draw(current_points[ci], new_point);
        // sleep(1);
    }
    
    current_points[ci] = new_point;

    return true;    
}

function draw(p1, p2) {
    turtle.jump(p1.x, p1.y);
    turtle.goto(p2.x, p2.y);
}

function initGrid() {
    grid = Array.from({length:grid_size**2}, (_) => []);
}

function getGridCell(point) {
    const gx = Math.min(grid_size-1, Math.max(0, Math.floor((point.x + canvas_size) / canvas_size / 2 * grid_size)));
    const gy = Math.min(grid_size-1, Math.max(0, Math.floor((point.y + canvas_size) / canvas_size / 2 * grid_size)));
    return gx + gy * grid_size;
}

function addToGrid(point) {
    if (point.checkCollision()) return;
    const gi = getGridCell(point);
    grid[gi].push(point);
}

class Point {
    constructor(x, y, r, a) { this.x = x; this.y = y; this.r = r; this.a = a; }
    
    checkBounds() { return (Math.abs(this.x - this.r) < canvas_size) && (Math.abs(this.y - this.r) < canvas_size); }
    
    checkCollision() {
        const gi = getGridCell(this);
        for (var dy=-1; dy<=1; dy++) {
            for (var dx=-1; dx<=1; dx++) {
                const gdi = gi + dx + dy * grid_size;
                if (gdi >= 0 && gdi < grid_size * grid_size) {
                    for (var i=0, l=grid[gdi].length; i<l; i++) {
                        const point = grid[gdi][i];
                        const dist2 = ((point.x - this.x) * (point.x - this.x) + (point.y - this.y) * (point.y - this.y)) / 8;
                        if (dist2 < this.r * this.r) return true;
                    }
                }
            }
        }
        
        const m_x = this.x / m_scale - offset_x;
        const m_y = this.y / m_scale - offset_y;
        const m = mandelbrot(m_x, m_y) / max_iterations;
        if (m < 0.6) return true;
        
        // const d = Math.hypot(this.x, this.y);
        // if (d > 90 || d < 40)  return true;

        return false;
    }
}

function sleep(ms) { const start = Date.now(); while (Date.now() - start < ms); }

const max_iterations = 25   /// min = 1, max = 100, step = 1
const m_scale =  87         /// min = 10, max = 1000, step = 0.01
const offset_x = 0.6       /// min = -10, max = 10, step = 0.001
const offset_y = 0          /// min = -10, max = 10, step = 0.001

function mandelbrot(x, y)
{
    var cr = x, ci = y, zr = 0, zi = 0, iterations = 0;
    while (zr * zr + zi * zi < 4)
    {
        const new_zr = zr * zr - zi * zi + cr;
        const new_zi = 2 * zr * zi + ci;
        zr = new_zr;
        zi = new_zi;
        iterations += 1;
        if (iterations >= max_iterations) break;
    }
    
    return iterations;
}