@rxflow/manhattan 0.0.1-alpha.3 → 0.0.1-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -96,7 +96,7 @@ function getManHattanPath(params) {
96
96
  const smoothStepPoints = (0, _svg.parseSVGPath)(smoothStepPath);
97
97
 
98
98
  // Check if smooth step path intersects with any obstacles
99
- if (smoothStepPoints.length > 0 && !(0, _utils.pathIntersectsObstacles)(smoothStepPoints, nodeLookup, sourceNodeId, targetNodeId)) {
99
+ if (smoothStepPoints.length > 0 && !(0, _utils.pathIntersectsObstacles)(smoothStepPoints, nodeLookup)) {
100
100
  console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
101
101
  return smoothStepPath;
102
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pathParser.d.ts","sourceRoot":"","sources":["../../src/svg/pathParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE,CA0CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
1
+ {"version":3,"file":"pathParser.d.ts","sourceRoot":"","sources":["../../src/svg/pathParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE,CA2CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
@@ -33,8 +33,9 @@ function parseSVGPath(pathString) {
33
33
  const x = coords[2];
34
34
  const y = coords[3];
35
35
 
36
- // Sample 5 points along the bezier curve
37
- for (let t = 0.2; t <= 1; t += 0.2) {
36
+ // Sample 10 points along the bezier curve for better accuracy
37
+ // This ensures we don't miss intersections with obstacles
38
+ for (let t = 0.1; t <= 1; t += 0.1) {
38
39
  const bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
39
40
  const by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
40
41
  points.push(new _geometry.Point(bx, by));
@@ -2,6 +2,10 @@ import { Point } from '../geometry';
2
2
  import type { NodeLookup } from '../options';
3
3
  /**
4
4
  * Check if a path intersects with any obstacles (nodes)
5
+ * A path is considered to intersect if it has >= 2 unique intersection points with a node
6
+ *
7
+ * Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
8
+ * into line segments, so this function works correctly with curved paths
5
9
  */
6
- export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup, sourceNodeId: string, targetNodeId: string): boolean;
10
+ export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup): boolean;
7
11
  //# sourceMappingURL=pathValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pathValidation.d.ts","sourceRoot":"","sources":["../../src/utils/pathValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AA8E5C;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,KAAK,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GACnB,OAAO,CA2BT"}
1
+ {"version":3,"file":"pathValidation.d.ts","sourceRoot":"","sources":["../../src/utils/pathValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAyF5C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAoC5F"}
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.pathIntersectsObstacles = pathIntersectsObstacles;
7
7
  var _geometry = require("../geometry");
8
- var _collision = require("../geometry/collision");
9
8
  var _node = require("./node");
10
9
  /**
11
10
  * Get node bounding box
@@ -19,77 +18,113 @@ function getNodeBBox(nodeId, nodeLookup) {
19
18
  }
20
19
 
21
20
  /**
22
- * Check if a point is strictly inside a rectangle (not on edge)
21
+ * Find unique intersection points between a line segment and a rectangle
22
+ * Returns array of intersection points (deduplicated)
23
23
  */
24
- function isPointStrictlyInside(point, rect, tolerance = 1) {
25
- return point.x > rect.x + tolerance && point.x < rect.x + rect.width - tolerance && point.y > rect.y + tolerance && point.y < rect.y + rect.height - tolerance;
26
- }
24
+ function findSegmentRectIntersections(p1, p2, rect) {
25
+ const intersections = [];
26
+ const tolerance = 0.01;
27
27
 
28
- /**
29
- * Check if a point intersects with a node
30
- */
31
- function checkPointIntersection(point, nodeId, nodeBBox, isTerminalNode) {
32
- if (isTerminalNode) {
33
- // For source/target nodes, only check if point is strictly inside (not on edge)
34
- if (isPointStrictlyInside(point, nodeBBox)) {
35
- console.log(`[pathIntersectsObstacles] Point (${point.x}, ${point.y}) is inside terminal node ${nodeId}`);
36
- return true;
37
- }
38
- } else {
39
- // For other nodes, check if point is inside or on edge
40
- if (nodeBBox.containsPoint(point)) {
41
- console.log(`[pathIntersectsObstacles] Point (${point.x}, ${point.y}) intersects node ${nodeId}`);
42
- return true;
28
+ // Define rectangle edges
29
+ const edges = [{
30
+ start: new _geometry.Point(rect.x, rect.y),
31
+ end: new _geometry.Point(rect.x + rect.width, rect.y)
32
+ },
33
+ // Top
34
+ {
35
+ start: new _geometry.Point(rect.x + rect.width, rect.y),
36
+ end: new _geometry.Point(rect.x + rect.width, rect.y + rect.height)
37
+ },
38
+ // Right
39
+ {
40
+ start: new _geometry.Point(rect.x + rect.width, rect.y + rect.height),
41
+ end: new _geometry.Point(rect.x, rect.y + rect.height)
42
+ },
43
+ // Bottom
44
+ {
45
+ start: new _geometry.Point(rect.x, rect.y + rect.height),
46
+ end: new _geometry.Point(rect.x, rect.y)
47
+ } // Left
48
+ ];
49
+
50
+ // Find intersection point with each edge
51
+ for (const edge of edges) {
52
+ const intersection = getLineSegmentIntersection(p1, p2, edge.start, edge.end);
53
+ if (intersection) {
54
+ // Check if this point is already in the list (avoid duplicates at corners)
55
+ const isDuplicate = intersections.some(existing => Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance);
56
+ if (!isDuplicate) {
57
+ intersections.push(intersection);
58
+ }
43
59
  }
44
60
  }
45
- return false;
61
+ return intersections;
46
62
  }
47
63
 
48
64
  /**
49
- * Check if a line segment intersects with a node
65
+ * Get intersection point between two line segments (if exists)
50
66
  */
51
- function checkSegmentIntersection(p1, p2, nodeId, nodeBBox, isTerminalNode) {
52
- if (isTerminalNode) {
53
- // For source/target nodes, check if segment crosses through (not just touching edge)
54
- if ((0, _collision.lineSegmentCrossesRect)(p1, p2, nodeBBox)) {
55
- console.log(`[pathIntersectsObstacles] Segment crosses terminal node ${nodeId}`);
56
- return true;
57
- }
58
- } else {
59
- // For other nodes, check normal intersection
60
- if ((0, _collision.lineSegmentIntersectsRect)(p1, p2, nodeBBox)) {
61
- console.log(`[pathIntersectsObstacles] Segment intersects node ${nodeId}`);
62
- return true;
63
- }
67
+ function getLineSegmentIntersection(p1, p2, p3, p4) {
68
+ const d1 = direction(p3, p4, p1);
69
+ const d2 = direction(p3, p4, p2);
70
+ const d3 = direction(p1, p2, p3);
71
+ const d4 = direction(p1, p2, p4);
72
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
73
+ // Lines intersect, calculate intersection point
74
+ 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));
75
+ return new _geometry.Point(p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
64
76
  }
65
- return false;
77
+
78
+ // Check collinear cases
79
+ if (d1 === 0 && onSegment(p3, p1, p4)) return p1.clone();
80
+ if (d2 === 0 && onSegment(p3, p2, p4)) return p2.clone();
81
+ if (d3 === 0 && onSegment(p1, p3, p2)) return p3.clone();
82
+ if (d4 === 0 && onSegment(p1, p4, p2)) return p4.clone();
83
+ return null;
84
+ }
85
+ function direction(p1, p2, p3) {
86
+ return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
87
+ }
88
+ function onSegment(p1, p2, p3) {
89
+ 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);
66
90
  }
67
91
 
68
92
  /**
69
93
  * Check if a path intersects with any obstacles (nodes)
94
+ * A path is considered to intersect if it has >= 2 unique intersection points with a node
95
+ *
96
+ * Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
97
+ * into line segments, so this function works correctly with curved paths
70
98
  */
71
- function pathIntersectsObstacles(pathPoints, nodeLookup, sourceNodeId, targetNodeId) {
72
- // Iterate through all nodes
73
- for (const [nodeId, node] of nodeLookup) {
99
+ function pathIntersectsObstacles(pathPoints, nodeLookup) {
100
+ const tolerance = 0.01;
101
+
102
+ // Iterate through all nodes (including source and target)
103
+ for (const [nodeId] of nodeLookup) {
74
104
  const nodeBBox = getNodeBBox(nodeId, nodeLookup);
75
105
  if (!nodeBBox) continue;
76
- const isTerminalNode = nodeId === sourceNodeId || nodeId === targetNodeId;
106
+ const allIntersections = [];
77
107
 
78
- // Check each point
79
- for (let i = 0; i < pathPoints.length; i++) {
80
- const point = pathPoints[i];
81
- if (checkPointIntersection(point, nodeId, nodeBBox, isTerminalNode)) {
82
- return true;
83
- }
108
+ // Collect all unique intersection points for this node
109
+ for (let i = 0; i < pathPoints.length - 1; i++) {
110
+ const p1 = pathPoints[i];
111
+ const p2 = pathPoints[i + 1];
112
+ const segmentIntersections = findSegmentRectIntersections(p1, p2, nodeBBox);
84
113
 
85
- // Check line segment to next point
86
- if (i < pathPoints.length - 1) {
87
- const nextPoint = pathPoints[i + 1];
88
- if (checkSegmentIntersection(point, nextPoint, nodeId, nodeBBox, isTerminalNode)) {
89
- return true;
114
+ // Add to global list, avoiding duplicates
115
+ for (const intersection of segmentIntersections) {
116
+ const isDuplicate = allIntersections.some(existing => Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance);
117
+ if (!isDuplicate) {
118
+ allIntersections.push(intersection);
90
119
  }
91
120
  }
92
121
  }
122
+
123
+ // If path has 2 or more unique intersections with this node, it crosses through it
124
+ if (allIntersections.length >= 2) {
125
+ console.log(`[pathIntersectsObstacles] Path crosses node ${nodeId} with ${allIntersections.length} intersections`);
126
+ return true;
127
+ }
93
128
  }
94
129
  return false;
95
130
  }
@@ -102,7 +102,7 @@ export function getManHattanPath(params) {
102
102
  var smoothStepPoints = parseSVGPath(smoothStepPath);
103
103
 
104
104
  // Check if smooth step path intersects with any obstacles
105
- if (smoothStepPoints.length > 0 && !pathIntersectsObstacles(smoothStepPoints, nodeLookup, sourceNodeId, targetNodeId)) {
105
+ if (smoothStepPoints.length > 0 && !pathIntersectsObstacles(smoothStepPoints, nodeLookup)) {
106
106
  console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
107
107
  return smoothStepPath;
108
108
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pathParser.d.ts","sourceRoot":"","sources":["../../src/svg/pathParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE,CA0CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
1
+ {"version":3,"file":"pathParser.d.ts","sourceRoot":"","sources":["../../src/svg/pathParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE,CA2CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
@@ -34,8 +34,9 @@ export function parseSVGPath(pathString) {
34
34
  var x = coords[2];
35
35
  var y = coords[3];
36
36
 
37
- // Sample 5 points along the bezier curve
38
- for (var t = 0.2; t <= 1; t += 0.2) {
37
+ // Sample 10 points along the bezier curve for better accuracy
38
+ // This ensures we don't miss intersections with obstacles
39
+ for (var t = 0.1; t <= 1; t += 0.1) {
39
40
  var bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
40
41
  var by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
41
42
  points.push(new Point(bx, by));
@@ -2,6 +2,10 @@ import { Point } from '../geometry';
2
2
  import type { NodeLookup } from '../options';
3
3
  /**
4
4
  * Check if a path intersects with any obstacles (nodes)
5
+ * A path is considered to intersect if it has >= 2 unique intersection points with a node
6
+ *
7
+ * Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
8
+ * into line segments, so this function works correctly with curved paths
5
9
  */
6
- export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup, sourceNodeId: string, targetNodeId: string): boolean;
10
+ export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup): boolean;
7
11
  //# sourceMappingURL=pathValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pathValidation.d.ts","sourceRoot":"","sources":["../../src/utils/pathValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AA8E5C;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,KAAK,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GACnB,OAAO,CA2BT"}
1
+ {"version":3,"file":"pathValidation.d.ts","sourceRoot":"","sources":["../../src/utils/pathValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAyF5C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAoC5F"}
@@ -5,8 +5,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
5
5
  function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
6
6
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
7
7
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
8
- import { Rectangle } from "../geometry";
9
- import { lineSegmentIntersectsRect, lineSegmentCrossesRect } from "../geometry/collision";
8
+ import { Point, Rectangle } from "../geometry";
10
9
  import { getNodeDimensions, getNodePosition } from "./node";
11
10
 
12
11
  /**
@@ -21,84 +20,138 @@ function getNodeBBox(nodeId, nodeLookup) {
21
20
  }
22
21
 
23
22
  /**
24
- * Check if a point is strictly inside a rectangle (not on edge)
23
+ * Find unique intersection points between a line segment and a rectangle
24
+ * Returns array of intersection points (deduplicated)
25
25
  */
26
- function isPointStrictlyInside(point, rect) {
27
- var tolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
28
- return point.x > rect.x + tolerance && point.x < rect.x + rect.width - tolerance && point.y > rect.y + tolerance && point.y < rect.y + rect.height - tolerance;
29
- }
26
+ function findSegmentRectIntersections(p1, p2, rect) {
27
+ var intersections = [];
28
+ var tolerance = 0.01;
30
29
 
31
- /**
32
- * Check if a point intersects with a node
33
- */
34
- function checkPointIntersection(point, nodeId, nodeBBox, isTerminalNode) {
35
- if (isTerminalNode) {
36
- // For source/target nodes, only check if point is strictly inside (not on edge)
37
- if (isPointStrictlyInside(point, nodeBBox)) {
38
- console.log("[pathIntersectsObstacles] Point (".concat(point.x, ", ").concat(point.y, ") is inside terminal node ").concat(nodeId));
39
- return true;
40
- }
41
- } else {
42
- // For other nodes, check if point is inside or on edge
43
- if (nodeBBox.containsPoint(point)) {
44
- console.log("[pathIntersectsObstacles] Point (".concat(point.x, ", ").concat(point.y, ") intersects node ").concat(nodeId));
45
- return true;
30
+ // Define rectangle edges
31
+ var edges = [{
32
+ start: new Point(rect.x, rect.y),
33
+ end: new Point(rect.x + rect.width, rect.y)
34
+ },
35
+ // Top
36
+ {
37
+ start: new Point(rect.x + rect.width, rect.y),
38
+ end: new Point(rect.x + rect.width, rect.y + rect.height)
39
+ },
40
+ // Right
41
+ {
42
+ start: new Point(rect.x + rect.width, rect.y + rect.height),
43
+ end: new Point(rect.x, rect.y + rect.height)
44
+ },
45
+ // Bottom
46
+ {
47
+ start: new Point(rect.x, rect.y + rect.height),
48
+ end: new Point(rect.x, rect.y)
49
+ } // Left
50
+ ];
51
+
52
+ // Find intersection point with each edge
53
+ var _loop = function _loop() {
54
+ var edge = _edges[_i];
55
+ var intersection = getLineSegmentIntersection(p1, p2, edge.start, edge.end);
56
+ if (intersection) {
57
+ // Check if this point is already in the list (avoid duplicates at corners)
58
+ var isDuplicate = intersections.some(function (existing) {
59
+ return Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance;
60
+ });
61
+ if (!isDuplicate) {
62
+ intersections.push(intersection);
63
+ }
46
64
  }
65
+ };
66
+ for (var _i = 0, _edges = edges; _i < _edges.length; _i++) {
67
+ _loop();
47
68
  }
48
- return false;
69
+ return intersections;
49
70
  }
50
71
 
51
72
  /**
52
- * Check if a line segment intersects with a node
73
+ * Get intersection point between two line segments (if exists)
53
74
  */
54
- function checkSegmentIntersection(p1, p2, nodeId, nodeBBox, isTerminalNode) {
55
- if (isTerminalNode) {
56
- // For source/target nodes, check if segment crosses through (not just touching edge)
57
- if (lineSegmentCrossesRect(p1, p2, nodeBBox)) {
58
- console.log("[pathIntersectsObstacles] Segment crosses terminal node ".concat(nodeId));
59
- return true;
60
- }
61
- } else {
62
- // For other nodes, check normal intersection
63
- if (lineSegmentIntersectsRect(p1, p2, nodeBBox)) {
64
- console.log("[pathIntersectsObstacles] Segment intersects node ".concat(nodeId));
65
- return true;
66
- }
75
+ function getLineSegmentIntersection(p1, p2, p3, p4) {
76
+ var d1 = direction(p3, p4, p1);
77
+ var d2 = direction(p3, p4, p2);
78
+ var d3 = direction(p1, p2, p3);
79
+ var d4 = direction(p1, p2, p4);
80
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
81
+ // Lines intersect, calculate intersection point
82
+ var 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));
83
+ return new Point(p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
67
84
  }
68
- return false;
85
+
86
+ // Check collinear cases
87
+ if (d1 === 0 && onSegment(p3, p1, p4)) return p1.clone();
88
+ if (d2 === 0 && onSegment(p3, p2, p4)) return p2.clone();
89
+ if (d3 === 0 && onSegment(p1, p3, p2)) return p3.clone();
90
+ if (d4 === 0 && onSegment(p1, p4, p2)) return p4.clone();
91
+ return null;
92
+ }
93
+ function direction(p1, p2, p3) {
94
+ return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
95
+ }
96
+ function onSegment(p1, p2, p3) {
97
+ 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);
69
98
  }
70
99
 
71
100
  /**
72
101
  * Check if a path intersects with any obstacles (nodes)
102
+ * A path is considered to intersect if it has >= 2 unique intersection points with a node
103
+ *
104
+ * Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
105
+ * into line segments, so this function works correctly with curved paths
73
106
  */
74
- export function pathIntersectsObstacles(pathPoints, nodeLookup, sourceNodeId, targetNodeId) {
75
- // Iterate through all nodes
107
+ export function pathIntersectsObstacles(pathPoints, nodeLookup) {
108
+ var tolerance = 0.01;
109
+
110
+ // Iterate through all nodes (including source and target)
76
111
  var _iterator = _createForOfIteratorHelper(nodeLookup),
77
112
  _step;
78
113
  try {
79
114
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
80
- var _step$value = _slicedToArray(_step.value, 2),
81
- nodeId = _step$value[0],
82
- node = _step$value[1];
115
+ var _step$value = _slicedToArray(_step.value, 1),
116
+ nodeId = _step$value[0];
83
117
  var nodeBBox = getNodeBBox(nodeId, nodeLookup);
84
118
  if (!nodeBBox) continue;
85
- var isTerminalNode = nodeId === sourceNodeId || nodeId === targetNodeId;
119
+ var allIntersections = [];
86
120
 
87
- // Check each point
88
- for (var i = 0; i < pathPoints.length; i++) {
89
- var point = pathPoints[i];
90
- if (checkPointIntersection(point, nodeId, nodeBBox, isTerminalNode)) {
91
- return true;
92
- }
121
+ // Collect all unique intersection points for this node
122
+ for (var i = 0; i < pathPoints.length - 1; i++) {
123
+ var p1 = pathPoints[i];
124
+ var p2 = pathPoints[i + 1];
125
+ var segmentIntersections = findSegmentRectIntersections(p1, p2, nodeBBox);
93
126
 
94
- // Check line segment to next point
95
- if (i < pathPoints.length - 1) {
96
- var nextPoint = pathPoints[i + 1];
97
- if (checkSegmentIntersection(point, nextPoint, nodeId, nodeBBox, isTerminalNode)) {
98
- return true;
127
+ // Add to global list, avoiding duplicates
128
+ var _iterator2 = _createForOfIteratorHelper(segmentIntersections),
129
+ _step2;
130
+ try {
131
+ var _loop2 = function _loop2() {
132
+ var intersection = _step2.value;
133
+ var isDuplicate = allIntersections.some(function (existing) {
134
+ return Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance;
135
+ });
136
+ if (!isDuplicate) {
137
+ allIntersections.push(intersection);
138
+ }
139
+ };
140
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
141
+ _loop2();
99
142
  }
143
+ } catch (err) {
144
+ _iterator2.e(err);
145
+ } finally {
146
+ _iterator2.f();
100
147
  }
101
148
  }
149
+
150
+ // If path has 2 or more unique intersections with this node, it crosses through it
151
+ if (allIntersections.length >= 2) {
152
+ console.log("[pathIntersectsObstacles] Path crosses node ".concat(nodeId, " with ").concat(allIntersections.length, " intersections"));
153
+ return true;
154
+ }
102
155
  }
103
156
  } catch (err) {
104
157
  _iterator.e(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxflow/manhattan",
3
- "version": "0.0.1-alpha.3",
3
+ "version": "0.0.1-alpha.5",
4
4
  "description": "Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance",
5
5
  "keywords": [
6
6
  "reactflow",