mirror of
https://kevinblog.sytes.net/Code/Jibo-Revival-Group/RoboCommander.git
synced 2026-06-15 22:56:02 +00:00
116 lines
3.0 KiB
JavaScript
116 lines
3.0 KiB
JavaScript
|
|
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 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 packEnclose(circles) {
|
||
|
|
if (!(n = circles.length)) return 0;
|
||
|
|
|
||
|
|
var a, b, c, n, aa, ca, i, j, k, sj, sk;
|
||
|
|
|
||
|
|
// 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 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;
|
||
|
|
|
||
|
|
// Compute the new closest circle pair to the centroid.
|
||
|
|
aa = score(a);
|
||
|
|
while ((c = c.next) !== b) {
|
||
|
|
if ((ca = score(c)) < 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;
|
||
|
|
}
|