feat(debug): implement collision resolution for node overlap in visualizer
This commit is contained in:
@@ -124,6 +124,62 @@ module.exports = function (register, lib) {
|
||||
return positions;
|
||||
}
|
||||
|
||||
// ---- hard rectangle-overlap resolution ----
|
||||
// Repulsion/gravity alone only balance to *some* equilibrium distance;
|
||||
// nothing about that equilibrium accounts for box size, and node boxes
|
||||
// vary a lot in height (one row vs. MAX_VISIBLE_ROWS). So two boxes can
|
||||
// settle closer than their combined half-heights and visibly overlap.
|
||||
// This runs every simulation step and directly separates any pair of
|
||||
// visible boxes whose padded bounding rects intersect, along whichever
|
||||
// axis needs the smaller push -- a standard AABB de-overlap pass on top
|
||||
// of the physics, not a replacement for it. Returns the total remaining
|
||||
// overlap so callers can tell the simulation hasn't truly settled even
|
||||
// if velocities alone look calm.
|
||||
function resolveCollisions(nodeList, isVisible) {
|
||||
var totalOverlap = 0;
|
||||
for (var i = 0; i < nodeList.length; i++) {
|
||||
for (var j = i + 1; j < nodeList.length; j++) {
|
||||
var a = nodeList[i];
|
||||
var b = nodeList[j];
|
||||
if (!isVisible(a.id) || !isVisible(b.id)) continue;
|
||||
if (a.fixed && b.fixed) continue;
|
||||
|
||||
var minDX = NODE_WIDTH + H_GAP;
|
||||
var minDY = (a.height + b.height) / 2 + V_GAP;
|
||||
var dx = b.x - a.x;
|
||||
var dy = b.y - a.y;
|
||||
var overlapX = minDX - Math.abs(dx);
|
||||
var overlapY = minDY - Math.abs(dy);
|
||||
if (overlapX <= 0 || overlapY <= 0) continue;
|
||||
|
||||
totalOverlap += Math.min(overlapX, overlapY);
|
||||
|
||||
var pushX = 0, pushY = 0;
|
||||
if (dx === 0 && dy === 0) {
|
||||
pushX = minDX; // exactly coincident: nudge apart deterministically
|
||||
} else if (overlapX < overlapY) {
|
||||
pushX = (dx < 0 ? -1 : 1) * overlapX;
|
||||
} else {
|
||||
pushY = (dy < 0 ? -1 : 1) * overlapY;
|
||||
}
|
||||
|
||||
if (a.fixed) {
|
||||
b.x += pushX;
|
||||
b.y += pushY;
|
||||
} else if (b.fixed) {
|
||||
a.x -= pushX;
|
||||
a.y -= pushY;
|
||||
} else {
|
||||
a.x -= pushX / 2;
|
||||
a.y -= pushY / 2;
|
||||
b.x += pushX / 2;
|
||||
b.y += pushY / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalOverlap;
|
||||
}
|
||||
|
||||
// ---- simple O(n^2) force-directed layout, vis-network-style ----
|
||||
// isVisible(id) lets collapsed-subtree nodes sit out of the simulation
|
||||
// entirely (frozen, not just hidden) so they don't invisibly shove
|
||||
@@ -200,7 +256,12 @@ module.exports = function (register, lib) {
|
||||
n.y += n.vy;
|
||||
kinetic += n.vx * n.vx + n.vy * n.vy;
|
||||
});
|
||||
return kinetic;
|
||||
|
||||
// Folded into the same "settled" signal as velocity: as long as
|
||||
// boxes still overlap, the simulation isn't done, even if forces
|
||||
// alone have gone quiet.
|
||||
var overlap = resolveCollisions(nodeList, isVisible);
|
||||
return kinetic + overlap;
|
||||
}
|
||||
|
||||
return { nodes: nodeList, byId: byId, step: step };
|
||||
|
||||
Reference in New Issue
Block a user