UNPKG

@rxflow/manhattan

Version:

Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance

130 lines (119 loc) 4.71 kB
"use strict"; 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; }