Ulam Spiral

Ulam Spiral connected by Bezier curves

Log in to post a comment.

const COUNT       = 20000;   // min=1000,  max=120000, step=1000
const SCALE       = 0.15;    // min=0.03,  max=2.0,    step=0.01  
const DOT         = 2.0;     // min=0.0,   max=6.0,    step=0.1   
const SEG         = 16;      // min=1,     max=40,     step=1     
const N_NEIGHBORS = 1;       // min=1,     max=8,      step=1     


const CURVE       = 0.35;    // min=-1.0,  max=1.0,    step=0.01  
const BSEG        = 20;      // min=6,     max=60,     step=1     

Canvas.setpenopacity(1.0);
const t = new Turtle();
t.penup();

function isPrime(n) {
    if (n < 2) return false;
    if (n === 2) return true;
    if (n % 2 === 0) return false;
    const r = Math.floor(Math.sqrt(n));
    for (let d = 3; d <= r; d += 2) if (n % d === 0) return false;
    return true;
}

function circle(cx, cy, r, segs) {
    t.penup();
    for (let i = 0; i <= segs; i++) {
        const a = (i / segs) * Math.PI * 2;
        const px = cx + Math.cos(a) * r;
        const py = cy + Math.sin(a) * r;
        if (i === 0) t.goto(px, py);
        else { t.pendown(); t.goto(px, py); }
    }
    t.penup();
}


function bezierQuad(x0, y0, x1, y1, x2, y2, segs) {
    let prevx = x0, prevy = y0;
    t.penup(); t.goto(prevx, prevy);
    for (let i = 1; i <= segs; i++) {
        const tpar = i / segs;
        const u = 1 - tpar;
        const bx = u*u*x0 + 2*u*tpar*x1 + tpar*tpar*x2;
        const by = u*u*y0 + 2*u*tpar*y1 + tpar*tpar*y2;
        t.pendown(); t.goto(bx, by);
    }
    t.penup();
}

function drawCurvedConnection(ax, ay, bx, by) {
    const dx = bx - ax, dy = by - ay;
    const len = Math.hypot(dx, dy) || 1.0;
    const mx = (ax + bx) * 0.5, my = (ay + by) * 0.5;
    // unit perpendicular
    const px = -dy / len, py = dx / len;

    const k = CURVE * 0.5 * len;
    const cx = mx + px * k, cy = my + py * k;
    bezierQuad(ax, ay, cx, cy, bx, by, BSEG);
}

const stepPx = SCALE * 10;
let x = 0, y = 0, dir = 0, stepLen = 1, stepsInDir = 0, arms = 0;
const pts = [];

for (let n = 1; n <= COUNT; n++) {
    if (isPrime(n)) pts.push({ x: x * stepPx, y: y * stepPx });
    if (dir === 0) x++;
    else if (dir === 1) y++;
    else if (dir === 2) x--;
    else y--;
    stepsInDir++;
    if (stepsInDir === stepLen) {
        dir = (dir + 1) & 3;
        stepsInDir = 0;
        arms++;
        if ((arms & 1) === 0) stepLen++;
    }
}

const edges = new Set();
for (let i = 0; i < pts.length; i++) {
    const p = pts[i];
    const dists = [];
    for (let j = 0; j < pts.length; j++) {
        if (i === j) continue;
        const q = pts[j];
        const dx = q.x - p.x, dy = q.y - p.y;
        dists.push({ j, d2: dx*dx + dy*dy + 1e-6*j });
    }
    dists.sort((a,b) => a.d2 - b.d2);

    let taken = 0, idx = 0;
    while (taken < N_NEIGHBORS && idx < dists.length) {
        const j = dists[idx++].j;
        const a = i < j ? i : j, b = i < j ? j : i;
        const key = a + ":" + b;
        if (!edges.has(key)) { edges.add(key); taken++; }
    }
}

edges.forEach(k => {
    const [ai, bi] = k.split(":").map(s => parseInt(s, 10));
    const A = pts[ai], B = pts[bi];
    drawCurvedConnection(A.x, A.y, B.x, B.y);
});

for (let i = 0; i < pts.length; i++) {
    circle(pts[i].x, pts[i].y, DOT * SCALE, SEG);
}