@rxflow/manhattan
Version:
Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance
130 lines (119 loc) • 4.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.pathIntersectsObstacles = pathIntersectsObstacles;
var _geometry = require("../geometry");
var _node = require("./node");
/**
* Get node bounding box
*/
function getNodeBBox(nodeId, nodeLookup) {
const node = nodeLookup.get(nodeId);
if (!node) return null;
const dimensions = (0, _node.getNodeDimensions)(node);
const position = (0, _node.getNodePosition)(node);
return new _geometry.Rectangle(position.x, position.y, dimensions.width, dimensions.height);
}
/**
* Find unique intersection points between a line segment and a rectangle
* Returns array of intersection points (deduplicated)
*/
function findSegmentRectIntersections(p1, p2, rect) {
const intersections = [];
const tolerance = 0.01;
// Define rectangle edges
const edges = [{
start: new _geometry.Point(rect.x, rect.y),
end: new _geometry.Point(rect.x + rect.width, rect.y)
},
// Top
{
start: new _geometry.Point(rect.x + rect.width, rect.y),
end: new _geometry.Point(rect.x + rect.width, rect.y + rect.height)
},
// Right
{
start: new _geometry.Point(rect.x + rect.width, rect.y + rect.height),
end: new _geometry.Point(rect.x, rect.y + rect.height)
},
// Bottom
{
start: new _geometry.Point(rect.x, rect.y + rect.height),
end: new _geometry.Point(rect.x, rect.y)
} // Left
];
// Find intersection point with each edge
for (const edge of edges) {
const intersection = getLineSegmentIntersection(p1, p2, edge.start, edge.end);
if (intersection) {
// Check if this point is already in the list (avoid duplicates at corners)
const isDuplicate = intersections.some(existing => Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance);
if (!isDuplicate) {
intersections.push(intersection);
}
}
}
return intersections;
}
/**
* Get intersection point between two line segments (if exists)
*/
function getLineSegmentIntersection(p1, p2, p3, p4) {
const d1 = direction(p3, p4, p1);
const d2 = direction(p3, p4, p2);
const d3 = direction(p1, p2, p3);
const d4 = direction(p1, p2, p4);
if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
// Lines intersect, calculate intersection point
const t = ((p3.x - p1.x) * (p3.y - p4.y) - (p3.y - p1.y) * (p3.x - p4.x)) / ((p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x));
return new _geometry.Point(p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
}
// Check collinear cases
if (d1 === 0 && onSegment(p3, p1, p4)) return p1.clone();
if (d2 === 0 && onSegment(p3, p2, p4)) return p2.clone();
if (d3 === 0 && onSegment(p1, p3, p2)) return p3.clone();
if (d4 === 0 && onSegment(p1, p4, p2)) return p4.clone();
return null;
}
function direction(p1, p2, p3) {
return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
}
function onSegment(p1, p2, p3) {
return p2.x >= Math.min(p1.x, p3.x) && p2.x <= Math.max(p1.x, p3.x) && p2.y >= Math.min(p1.y, p3.y) && p2.y <= Math.max(p1.y, p3.y);
}
/**
* Check if a path intersects with any obstacles (nodes)
* A path is considered to intersect if it has >= 2 unique intersection points with a node
*
* Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
* into line segments, so this function works correctly with curved paths
*/
function pathIntersectsObstacles(pathPoints, nodeLookup) {
const tolerance = 0.01;
// Iterate through all nodes (including source and target)
for (const [nodeId] of nodeLookup) {
const nodeBBox = getNodeBBox(nodeId, nodeLookup);
if (!nodeBBox) continue;
const allIntersections = [];
// Collect all unique intersection points for this node
for (let i = 0; i < pathPoints.length - 1; i++) {
const p1 = pathPoints[i];
const p2 = pathPoints[i + 1];
const segmentIntersections = findSegmentRectIntersections(p1, p2, nodeBBox);
// Add to global list, avoiding duplicates
for (const intersection of segmentIntersections) {
const isDuplicate = allIntersections.some(existing => Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance);
if (!isDuplicate) {
allIntersections.push(intersection);
}
}
}
// If path has 2 or more unique intersections with this node, it crosses through it
if (allIntersections.length >= 2) {
console.log(`[pathIntersectsObstacles] Path crosses node ${nodeId} with ${allIntersections.length} intersections`);
return true;
}
}
return false;
}