|
import array from "../array.js"; |
|
import lcg from "../lcg.js"; |
|
import {packEncloseRandom} from "./enclose.js"; |
|
|
|
function place(b, a, c) { |
|
var dx = b.x - a.x, x, a2, |
|
dy = b.y - a.y, y, b2, |
|
d2 = dx * dx + dy * dy; |
|
if (d2) { |
|
a2 = a.r + c.r, a2 *= a2; |
|
b2 = b.r + c.r, b2 *= b2; |
|
if (a2 > b2) { |
|
x = (d2 + b2 - a2) / (2 * d2); |
|
y = Math.sqrt(Math.max(0, b2 / d2 - x * x)); |
|
c.x = b.x - x * dx - y * dy; |
|
c.y = b.y - x * dy + y * dx; |
|
} else { |
|
x = (d2 + a2 - b2) / (2 * d2); |
|
y = Math.sqrt(Math.max(0, a2 / d2 - x * x)); |
|
c.x = a.x + x * dx - y * dy; |
|
c.y = a.y + x * dy + y * dx; |
|
} |
|
} else { |
|
c.x = a.x + c.r; |
|
c.y = a.y; |
|
} |
|
} |
|
|
|
function intersects(a, b) { |
|
var dr = a.r + b.r - 1e-6, dx = b.x - a.x, dy = b.y - a.y; |
|
return dr > 0 && dr * dr > dx * dx + dy * dy; |
|
} |
|
|
|
function score(node) { |
|
var a = node._, |
|
b = node.next._, |
|
ab = a.r + b.r, |
|
dx = (a.x * b.r + b.x * a.r) / ab, |
|
dy = (a.y * b.r + b.y * a.r) / ab; |
|
return dx * dx + dy * dy; |
|
} |
|
|
|
function Node(circle) { |
|
this._ = circle; |
|
this.next = null; |
|
this.previous = null; |
|
} |
|
|
|
export function packSiblingsRandom(circles, random) { |
|
if (!(n = (circles = array(circles)).length)) return 0; |
|
|
|
var a, b, c, n, aa, ca, i, j, k, sj, sk; |
|
|
|
|
|
a = circles[0], a.x = 0, a.y = 0; |
|
if (!(n > 1)) return a.r; |
|
|
|
|
|
b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0; |
|
if (!(n > 2)) return a.r + b.r; |
|
|
|
|
|
place(b, a, c = circles[2]); |
|
|
|
|
|
a = new Node(a), b = new Node(b), c = new Node(c); |
|
a.next = c.previous = b; |
|
b.next = a.previous = c; |
|
c.next = b.previous = a; |
|
|
|
|
|
pack: for (i = 3; i < n; ++i) { |
|
place(a._, b._, c = circles[i]), c = new Node(c); |
|
|
|
|
|
|
|
|
|
j = b.next, k = a.previous, sj = b._.r, sk = a._.r; |
|
do { |
|
if (sj <= sk) { |
|
if (intersects(j._, c._)) { |
|
b = j, a.next = b, b.previous = a, --i; |
|
continue pack; |
|
} |
|
sj += j._.r, j = j.next; |
|
} else { |
|
if (intersects(k._, c._)) { |
|
a = k, a.next = b, b.previous = a, --i; |
|
continue pack; |
|
} |
|
sk += k._.r, k = k.previous; |
|
} |
|
} while (j !== k.next); |
|
|
|
|
|
c.previous = a, c.next = b, a.next = b.previous = b = c; |
|
|
|
|
|
aa = score(a); |
|
while ((c = c.next) !== b) { |
|
if ((ca = score(c)) < aa) { |
|
a = c, aa = ca; |
|
} |
|
} |
|
b = a.next; |
|
} |
|
|
|
|
|
a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = packEncloseRandom(a, random); |
|
|
|
|
|
for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y; |
|
|
|
return c.r; |
|
} |
|
|
|
export default function(circles) { |
|
packSiblingsRandom(circles, lcg()); |
|
return circles; |
|
} |
|
|