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