Log in to post a comment.

// Smooth Julia set. Created by Reinder Nijhoff 2022
// @reindernijhoff
//
// https://turtletoy.net/turtle/fcdf3cf8aa
//

Canvas.setpenopacity(.6);

const turtle = new Turtle();
turtle.traveled = 0;
turtle.direction = 1;

let cr = -0.54; // min=-1, max=1, step=0.01
let ci = -0.54; // min=-1, max=1, step=0.01

const radius = 1.5; // min=0.1, max=5, step=0.01
const minRadius = 0.0152; // min=0.01, max=0.1, step=0.01
const maxPathLength = 25;  // min=1, max=300, step=0.1
const maxIterations = 30;  // min = 1, max = 100, step = 1

const maxTries = 90;
const m_scale = 55;         /// min = 10, max = 10000, step = 0.01


const grid  = new PoissonDiscGrid(radius);

function juliaset(x, y) {
    let zr = x;
    let zi = y;
    
    let ld2 = 1.0;
    let lz2 = zr*zr + zi*zi;
    
    for (let i=0; i<maxIterations; i++) {
        [zr, zi] = [zr * zr - zi * zi + cr, 2 * zr * zi + ci];
        
        ld2 *= 4.0*lz2;
        lz2 = zr*zr + zi*zi;
        if (lz2 >= maxIterations) break;
    }

    const d = Math.sqrt(lz2/ld2)*Math.log(lz2);
    
    return Math.min(Math.max(0, Math.sqrt(d)), 1e8) / 2;
}

function get_image_intensity(x,y) {
    var center_x = x;
    var center_y = y;

    const m_x = (center_x) / m_scale;
    const m_y = (center_y) / m_scale;

    return juliaset(m_x, m_y);
}

function curlNoiseM(x, y) {
    const eps = 0.01;
    
    const dx = (get_image_intensity(x, y + eps) - get_image_intensity(x, y - eps))/(2 * eps);
    const dy = (get_image_intensity(x + eps, y) - get_image_intensity(x - eps, y))/(2 * eps);
    
    const l = Math.hypot(dx, dy) / radius * 10;
    const c = [dx / l, -dy / l];	
    
    return c;
}

function getRadius(p2) {
    const l = 1 - get_image_intensity(p2[0], p2[1]);
    return (minRadius * l + radius * (1-l)) / 2;
}

function walk(i) {
    const p = turtle.pos();

    const curl = curlNoiseM(p[0], p[1]);
    const dest = [p[0]+curl[0]*turtle.direction, p[1]+curl[1]*turtle.direction];
    dest[2] = getRadius(dest);
    
    if (turtle.traveled < maxPathLength && Math.abs(dest[0]) < 110 && Math.abs(dest[1]) < 110 && grid.insert(dest)) {
        turtle.goto(dest);
        turtle.traveled += Math.hypot(curl[0], curl[1]);
    } else {
        turtle.traveled = 0;
        turtle.direction = Math.random() > .5 ? 1 : -1;
        let r, i = 0;
        do { 
            r =[Math.random()*200-100, Math.random()*200-100];
            r[2] = getRadius(r);
            i ++;
        } while(!grid.insert(r) && i < maxTries);
        if (i >= maxTries) {
            return false;
        }
        turtle.jump(r);
    }
    return true;
}

////////////////////////////////////////////////////////////////
// Poisson-Disc utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/b5510898dc
////////////////////////////////////////////////////////////////
function PoissonDiscGrid(radius) {
    class PoissonDiscGrid {
        constructor(radius) {
            this.cellSize = 1/Math.sqrt(2)/radius;
            this.cells = [];
            this.queue = [];
        }
        insert(p) {
            const x = p[0]*this.cellSize|0, y=p[1]*this.cellSize|0;
            for (let xi = x-1; xi<=x+1; xi++) {
                for (let yi = y-1; yi<=y+1; yi++) {
                    const ps = this.cell(xi,yi);
                    for (let i=0; i<ps.length; i++) {
                        if ((ps[i][0]-p[0])**2 + (ps[i][1]-p[1])**2 < (ps[i][2]+p[2])**2) {
                            return false;
                        }
                    }
                }       
            }
            this.queue.push([p, x, y]);
            if (this.queue.length > 10) {
                const d = this.queue.shift();
                this.cell(d[1], d[2]).push(d[0]);
            }
            return true;
        }
        cell(x,y) {
            const c = this.cells;
            return (c[x]?c[x]:c[x]=[])[y]?c[x][y]:c[x][y]=[];
        }
    }
    return new PoissonDiscGrid(radius);
}