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); }