An error occurred while loading the file. Please try again.
siblings.js 3.34 KiB
import enclose from "./enclose";
function place(a, b, c) {
  var ax = a.x,
      ay = a.y,
      da = b.r + c.r,
      db = a.r + c.r,
      dx = b.x - ax,
      dy = b.y - ay,
      dc = dx * dx + dy * dy;
  if (dc) {
    var x = 0.5 + ((db *= db) - (da *= da)) / (2 * dc),
        y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
    c.x = ax + x * dx + y * dy;
    c.y = ay + x * dy - y * dx;
  } else {
    c.x = ax + db;
    c.y = ay;
function intersects(a, b) {
  var dx = b.x - a.x,
      dy = b.y - a.y,
      dr = a.r + b.r;
  return dr * dr - 1e-6 > dx * dx + dy * dy;
function distance2(node, x, y) {
  var a = node._,
      b = node.next._,
      ab = a.r + b.r,
      dx = (a.x * b.r + b.x * a.r) / ab - x,
      dy = (a.y * b.r + b.y * a.r) / ab - y;
  return dx * dx + dy * dy;
function Node(circle) {
  this._ = circle;
  this.next = null;
  this.previous = null;
export function packEnclose(circles) {
  if (!(n = circles.length)) return 0;
  var a, b, c, n;
  // Place the first circle.
  a = circles[0], a.x = 0, a.y = 0;
  if (!(n > 1)) return a.r;
  // Place the second circle.
  b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0;
  if (!(n > 2)) return a.r + b.r;
  // Place the third circle.
  place(b, a, c = circles[2]);
  // Initialize the weighted centroid.
  var aa = a.r * a.r,
      ba = b.r * b.r,
      ca = c.r * c.r,
      oa = aa + ba + ca,
      ox = aa * a.x + ba * b.x + ca * c.x,
      oy = aa * a.y + ba * b.y + ca * c.y,
      cx, cy, i, j, k, sj, sk;
  // Initialize the front-chain using the first three circles a, b and c.
  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; // Attempt to place each remaining circle… pack: for (i = 3; i < n; ++i) { place(a._, b._, c = circles[i]), c = new Node(c); // Find the closest intersecting circle on the front-chain, if any. // “Closeness” is determined by linear distance along the front-chain. // “Ahead” or “behind” is likewise determined by linear distance. 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); // Success! Insert the new circle c between a and b. c.previous = a, c.next = b, a.next = b.previous = b = c; // Update the weighted centroid. oa += ca = c._.r * c._.r; ox += ca * c._.x; oy += ca * c._.y; // Compute the new closest circle pair to the centroid. aa = distance2(a, cx = ox / oa, cy = oy / oa); while ((c = c.next) !== b) { if ((ca = distance2(c, cx, cy)) < aa) { a = c, aa = ca; } } b = a.next; } // Compute the enclosing circle of the front chain. a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = enclose(a); // Translate the circles to put the enclosing circle around the origin. for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y; return c.r; } export default function(circles) { packEnclose(circles); return circles; }