feat(debug): implement collision resolution for node overlap in visualizer

This commit is contained in:
mynameisdeleted
2026-07-09 13:52:53 -04:00
parent cfd638f19b
commit 9b75caba10

View File

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