@rxflow/manhattan 0.0.1-alpha.0 → 0.0.1-alpha.2

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.
Files changed (43) hide show
  1. package/cjs/geometry/collision.d.ts +15 -0
  2. package/cjs/geometry/collision.d.ts.map +1 -0
  3. package/cjs/geometry/collision.js +81 -0
  4. package/cjs/geometry/index.d.ts +1 -0
  5. package/cjs/geometry/index.d.ts.map +1 -1
  6. package/cjs/geometry/index.js +20 -1
  7. package/cjs/getManHattanPath.d.ts +7 -14
  8. package/cjs/getManHattanPath.d.ts.map +1 -1
  9. package/cjs/getManHattanPath.js +34 -36
  10. package/cjs/svg/index.d.ts +1 -0
  11. package/cjs/svg/index.d.ts.map +1 -1
  12. package/cjs/svg/index.js +14 -1
  13. package/cjs/svg/pathParser.d.ts +11 -0
  14. package/cjs/svg/pathParser.d.ts.map +1 -0
  15. package/cjs/svg/pathParser.js +75 -0
  16. package/cjs/utils/index.d.ts +1 -0
  17. package/cjs/utils/index.d.ts.map +1 -1
  18. package/cjs/utils/index.js +11 -0
  19. package/cjs/utils/pathValidation.d.ts +7 -0
  20. package/cjs/utils/pathValidation.d.ts.map +1 -0
  21. package/cjs/utils/pathValidation.js +95 -0
  22. package/esm/geometry/collision.d.ts +15 -0
  23. package/esm/geometry/collision.d.ts.map +1 -0
  24. package/esm/geometry/collision.js +73 -0
  25. package/esm/geometry/index.d.ts +1 -0
  26. package/esm/geometry/index.d.ts.map +1 -1
  27. package/esm/geometry/index.js +2 -1
  28. package/esm/getManHattanPath.d.ts +7 -14
  29. package/esm/getManHattanPath.d.ts.map +1 -1
  30. package/esm/getManHattanPath.js +42 -38
  31. package/esm/svg/index.d.ts +1 -0
  32. package/esm/svg/index.d.ts.map +1 -1
  33. package/esm/svg/index.js +2 -1
  34. package/esm/svg/pathParser.d.ts +11 -0
  35. package/esm/svg/pathParser.d.ts.map +1 -0
  36. package/esm/svg/pathParser.js +81 -0
  37. package/esm/utils/index.d.ts +1 -0
  38. package/esm/utils/index.d.ts.map +1 -1
  39. package/esm/utils/index.js +2 -1
  40. package/esm/utils/pathValidation.d.ts +7 -0
  41. package/esm/utils/pathValidation.d.ts.map +1 -0
  42. package/esm/utils/pathValidation.js +109 -0
  43. package/package.json +1 -1
@@ -0,0 +1,15 @@
1
+ import { Point } from './Point';
2
+ import { Rectangle } from './Rectangle';
3
+ /**
4
+ * Check if a line segment intersects with a rectangle
5
+ */
6
+ export declare function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rectangle): boolean;
7
+ /**
8
+ * Check if a line segment crosses through a rectangle (not just touching edges)
9
+ */
10
+ export declare function lineSegmentCrossesRect(p1: Point, p2: Point, rect: Rectangle): boolean;
11
+ /**
12
+ * Check if two line segments intersect
13
+ */
14
+ export declare function lineSegmentsIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean;
15
+ //# sourceMappingURL=collision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collision.d.ts","sourceRoot":"","sources":["../../src/geometry/collision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAwBxF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAsBrF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAgBzF"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.lineSegmentCrossesRect = lineSegmentCrossesRect;
7
+ exports.lineSegmentIntersectsRect = lineSegmentIntersectsRect;
8
+ exports.lineSegmentsIntersect = lineSegmentsIntersect;
9
+ var _Point = require("./Point");
10
+ /**
11
+ * Check if a line segment intersects with a rectangle
12
+ */
13
+ function lineSegmentIntersectsRect(p1, p2, rect) {
14
+ // Check if either endpoint is inside the rectangle
15
+ if (rect.containsPoint(p1) || rect.containsPoint(p2)) {
16
+ return true;
17
+ }
18
+
19
+ // Check if line segment intersects any of the rectangle's edges
20
+ const rectPoints = [new _Point.Point(rect.x, rect.y), new _Point.Point(rect.x + rect.width, rect.y), new _Point.Point(rect.x + rect.width, rect.y + rect.height), new _Point.Point(rect.x, rect.y + rect.height)];
21
+ for (let i = 0; i < 4; i++) {
22
+ const r1 = rectPoints[i];
23
+ const r2 = rectPoints[(i + 1) % 4];
24
+ if (lineSegmentsIntersect(p1, p2, r1, r2)) {
25
+ return true;
26
+ }
27
+ }
28
+ return false;
29
+ }
30
+
31
+ /**
32
+ * Check if a line segment crosses through a rectangle (not just touching edges)
33
+ */
34
+ function lineSegmentCrossesRect(p1, p2, rect) {
35
+ const tolerance = 1;
36
+
37
+ // Check if segment passes through the interior of the rectangle
38
+ // We sample points along the segment and check if any are strictly inside
39
+ const steps = 10;
40
+ for (let i = 1; i < steps; i++) {
41
+ const t = i / steps;
42
+ const x = p1.x + (p2.x - p1.x) * t;
43
+ const y = p1.y + (p2.y - p1.y) * t;
44
+ if (x > rect.x + tolerance && x < rect.x + rect.width - tolerance && y > rect.y + tolerance && y < rect.y + rect.height - tolerance) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+
51
+ /**
52
+ * Check if two line segments intersect
53
+ */
54
+ function lineSegmentsIntersect(p1, p2, p3, p4) {
55
+ const d1 = direction(p3, p4, p1);
56
+ const d2 = direction(p3, p4, p2);
57
+ const d3 = direction(p1, p2, p3);
58
+ const d4 = direction(p1, p2, p4);
59
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
60
+ return true;
61
+ }
62
+ if (d1 === 0 && onSegment(p3, p1, p4)) return true;
63
+ if (d2 === 0 && onSegment(p3, p2, p4)) return true;
64
+ if (d3 === 0 && onSegment(p1, p3, p2)) return true;
65
+ if (d4 === 0 && onSegment(p1, p4, p2)) return true;
66
+ return false;
67
+ }
68
+
69
+ /**
70
+ * Calculate direction of point p3 relative to line p1-p2
71
+ */
72
+ function direction(p1, p2, p3) {
73
+ return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
74
+ }
75
+
76
+ /**
77
+ * Check if point p2 is on line segment p1-p3
78
+ */
79
+ function onSegment(p1, p2, p3) {
80
+ 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);
81
+ }
@@ -1,4 +1,5 @@
1
1
  export { Point } from './Point';
2
2
  export { Rectangle } from './Rectangle';
3
3
  export { Line } from './Line';
4
+ export { lineSegmentIntersectsRect, lineSegmentCrossesRect, lineSegmentsIntersect } from './collision';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/geometry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/geometry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA"}
@@ -21,6 +21,25 @@ Object.defineProperty(exports, "Rectangle", {
21
21
  return _Rectangle.Rectangle;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "lineSegmentCrossesRect", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _collision.lineSegmentCrossesRect;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "lineSegmentIntersectsRect", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _collision.lineSegmentIntersectsRect;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "lineSegmentsIntersect", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _collision.lineSegmentsIntersect;
40
+ }
41
+ });
24
42
  var _Point = require("./Point");
25
43
  var _Rectangle = require("./Rectangle");
26
- var _Line = require("./Line");
44
+ var _Line = require("./Line");
45
+ var _collision = require("./collision");
@@ -1,3 +1,4 @@
1
+ import { Position } from '@xyflow/react';
1
2
  import type { ManhattanRouterOptions, NodeLookup } from './options';
2
3
  /**
3
4
  * Parameters for getManHattanPath function
@@ -11,20 +12,12 @@ export interface GetManHattanPathParams {
11
12
  * Target node ID
12
13
  */
13
14
  targetNodeId: string;
14
- /**
15
- * Source anchor position (where the edge starts)
16
- */
17
- sourcePosition: {
18
- x: number;
19
- y: number;
20
- };
21
- /**
22
- * Target anchor position (where the edge ends)
23
- */
24
- targetPosition: {
25
- x: number;
26
- y: number;
27
- };
15
+ sourceX: number;
16
+ sourceY: number;
17
+ targetX: number;
18
+ targetY: number;
19
+ sourcePosition: Position;
20
+ targetPosition: Position;
28
21
  /**
29
22
  * ReactFlow node lookup map
30
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAOnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAExC;;OAEG;IACH,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAExC;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CA4PvE"}
1
+ {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAE,MAAM,eAAe,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAMnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAEhB,cAAc,EAAE,QAAQ,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC;IACzB;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAsRvE"}
@@ -4,9 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getManHattanPath = getManHattanPath;
7
+ var _react = require("@xyflow/react");
7
8
  var _geometry = require("./geometry");
8
- var _options = require("./options");
9
9
  var _obstacle = require("./obstacle");
10
+ var _options = require("./options");
10
11
  var _pathfinder = require("./pathfinder");
11
12
  var _svg = require("./svg");
12
13
  var _utils = require("./utils");
@@ -16,10 +17,10 @@ var _utils = require("./utils");
16
17
 
17
18
  /**
18
19
  * Generate Manhattan-routed path for ReactFlow edges
19
- *
20
+ *
20
21
  * @param params - Path generation parameters
21
22
  * @returns SVG path string that can be used with ReactFlow's BaseEdge
22
- *
23
+ *
23
24
  * @example
24
25
  * ```typescript
25
26
  * const path = getManHattanPath({
@@ -43,6 +44,10 @@ function getManHattanPath(params) {
43
44
  sourcePosition,
44
45
  targetPosition,
45
46
  nodeLookup,
47
+ sourceX,
48
+ sourceY,
49
+ targetX,
50
+ targetY,
46
51
  options: userOptions = {}
47
52
  } = params;
48
53
 
@@ -55,8 +60,8 @@ function getManHattanPath(params) {
55
60
  if (!sourceNode || !targetNode) {
56
61
  // Fallback to simple straight line if nodes not found
57
62
  console.warn('Source or target node not found in nodeLookup');
58
- const start = new _geometry.Point(sourcePosition.x, sourcePosition.y);
59
- const end = new _geometry.Point(targetPosition.x, targetPosition.y);
63
+ const start = new _geometry.Point(sourceX, sourceY);
64
+ const end = new _geometry.Point(targetX, targetY);
60
65
  return (0, _svg.pointsToPath)([start, end], options.precision);
61
66
  }
62
67
 
@@ -73,8 +78,29 @@ function getManHattanPath(params) {
73
78
  const targetBBox = new _geometry.Rectangle(targetPos.x, targetPos.y, targetDimensions.width, targetDimensions.height);
74
79
 
75
80
  // Create anchor points
76
- const sourceAnchor = new _geometry.Point(sourcePosition.x, sourcePosition.y);
77
- const targetAnchor = new _geometry.Point(targetPosition.x, targetPosition.y);
81
+ const sourceAnchor = new _geometry.Point(sourceX, sourceY);
82
+ const targetAnchor = new _geometry.Point(targetX, targetY);
83
+
84
+ // Try ReactFlow's getSmoothStepPath first
85
+ const [smoothStepPath] = (0, _react.getSmoothStepPath)({
86
+ sourceX,
87
+ sourceY,
88
+ targetX,
89
+ targetY,
90
+ sourcePosition,
91
+ targetPosition,
92
+ borderRadius: options.borderRadius
93
+ });
94
+
95
+ // Parse the smooth step path to extract points
96
+ const smoothStepPoints = (0, _svg.parseSVGPath)(smoothStepPath);
97
+
98
+ // Check if smooth step path intersects with any obstacles
99
+ if (smoothStepPoints.length > 0 && !(0, _utils.pathIntersectsObstacles)(smoothStepPoints, nodeLookup, sourceNodeId, targetNodeId)) {
100
+ console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
101
+ return smoothStepPath;
102
+ }
103
+ console.log('[getManHattanPath] SmoothStepPath intersects obstacles, using Manhattan routing');
78
104
 
79
105
  // Build obstacle map with anchor information
80
106
  const obstacleMap = new _obstacle.ObstacleMap(options).build(nodeLookup, sourceNodeId, targetNodeId, sourceAnchor, targetAnchor);
@@ -249,37 +275,9 @@ function getManHattanPath(params) {
249
275
  }
250
276
 
251
277
  // Simplify path by removing collinear points
252
- finalRoute = simplifyPath(finalRoute);
278
+ finalRoute = (0, _svg.simplifyPath)(finalRoute);
253
279
  console.log('[getManHattanPath] Simplified route:', finalRoute.map(p => `(${p.x}, ${p.y})`));
254
280
 
255
281
  // Convert to SVG path string
256
282
  return (0, _svg.pointsToPath)(finalRoute, options.precision, options.borderRadius);
257
- }
258
-
259
- /**
260
- * Simplify path by removing collinear intermediate points
261
- */
262
- function simplifyPath(points) {
263
- if (points.length <= 2) {
264
- return points;
265
- }
266
- const simplified = [points[0]];
267
- for (let i = 1; i < points.length - 1; i++) {
268
- const prev = simplified[simplified.length - 1];
269
- const current = points[i];
270
- const next = points[i + 1];
271
-
272
- // Check if current point is collinear with prev and next
273
- const isHorizontalLine = prev.y === current.y && current.y === next.y;
274
- const isVerticalLine = prev.x === current.x && current.x === next.x;
275
-
276
- // Only keep the point if it's not collinear (i.e., it's a corner)
277
- if (!isHorizontalLine && !isVerticalLine) {
278
- simplified.push(current);
279
- }
280
- }
281
-
282
- // Always add the last point
283
- simplified.push(points[points.length - 1]);
284
- return simplified;
285
283
  }
@@ -1,2 +1,3 @@
1
1
  export { pointsToPath, snapPathToGrid } from './pathConverter';
2
+ export { parseSVGPath, simplifyPath } from './pathParser';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
package/cjs/svg/index.js CHANGED
@@ -3,16 +3,29 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "parseSVGPath", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _pathParser.parseSVGPath;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "pointsToPath", {
7
13
  enumerable: true,
8
14
  get: function () {
9
15
  return _pathConverter.pointsToPath;
10
16
  }
11
17
  });
18
+ Object.defineProperty(exports, "simplifyPath", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _pathParser.simplifyPath;
22
+ }
23
+ });
12
24
  Object.defineProperty(exports, "snapPathToGrid", {
13
25
  enumerable: true,
14
26
  get: function () {
15
27
  return _pathConverter.snapPathToGrid;
16
28
  }
17
29
  });
18
- var _pathConverter = require("./pathConverter");
30
+ var _pathConverter = require("./pathConverter");
31
+ var _pathParser = require("./pathParser");
@@ -0,0 +1,11 @@
1
+ import { Point } from '../geometry';
2
+ /**
3
+ * Parse SVG path string to extract points
4
+ * Simplified parser that handles M, L, Q commands
5
+ */
6
+ export declare function parseSVGPath(pathString: string): Point[];
7
+ /**
8
+ * Simplify path by removing collinear intermediate points
9
+ */
10
+ export declare function simplifyPath(points: Point[]): Point[];
11
+ //# sourceMappingURL=pathParser.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.parseSVGPath = parseSVGPath;
7
+ exports.simplifyPath = simplifyPath;
8
+ var _geometry = require("../geometry");
9
+ /**
10
+ * Parse SVG path string to extract points
11
+ * Simplified parser that handles M, L, Q commands
12
+ */
13
+ function parseSVGPath(pathString) {
14
+ const points = [];
15
+ const commands = pathString.match(/[MLQ][^MLQ]*/g);
16
+ if (!commands) return points;
17
+ for (const command of commands) {
18
+ const type = command[0];
19
+ const coords = command.slice(1).trim().split(/[\s,]+/).map(Number);
20
+ if (type === 'M' || type === 'L') {
21
+ // MoveTo or LineTo: x, y
22
+ if (coords.length >= 2) {
23
+ points.push(new _geometry.Point(coords[0], coords[1]));
24
+ }
25
+ } else if (type === 'Q') {
26
+ // Quadratic Bezier: cx, cy, x, y
27
+ // We sample points along the curve for collision detection
28
+ if (coords.length >= 4) {
29
+ const prevPoint = points[points.length - 1];
30
+ if (prevPoint) {
31
+ const cx = coords[0];
32
+ const cy = coords[1];
33
+ const x = coords[2];
34
+ const y = coords[3];
35
+
36
+ // Sample 5 points along the bezier curve
37
+ for (let t = 0.2; t <= 1; t += 0.2) {
38
+ const bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
39
+ const by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
40
+ points.push(new _geometry.Point(bx, by));
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ return points;
47
+ }
48
+
49
+ /**
50
+ * Simplify path by removing collinear intermediate points
51
+ */
52
+ function simplifyPath(points) {
53
+ if (points.length <= 2) {
54
+ return points;
55
+ }
56
+ const simplified = [points[0]];
57
+ for (let i = 1; i < points.length - 1; i++) {
58
+ const prev = simplified[simplified.length - 1];
59
+ const current = points[i];
60
+ const next = points[i + 1];
61
+
62
+ // Check if current point is collinear with prev and next
63
+ const isHorizontalLine = prev.y === current.y && current.y === next.y;
64
+ const isVerticalLine = prev.x === current.x && current.x === next.x;
65
+
66
+ // Only keep the point if it's not collinear (i.e., it's a corner)
67
+ if (!isHorizontalLine && !isVerticalLine) {
68
+ simplified.push(current);
69
+ }
70
+ }
71
+
72
+ // Always add the last point
73
+ simplified.push(points[points.length - 1]);
74
+ return simplified;
75
+ }
@@ -3,4 +3,5 @@ export * from './direction';
3
3
  export * from './rect';
4
4
  export * from './route';
5
5
  export * from './node';
6
+ export * from './pathValidation';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA"}
@@ -57,4 +57,15 @@ Object.keys(_node).forEach(function (key) {
57
57
  return _node[key];
58
58
  }
59
59
  });
60
+ });
61
+ var _pathValidation = require("./pathValidation");
62
+ Object.keys(_pathValidation).forEach(function (key) {
63
+ if (key === "default" || key === "__esModule") return;
64
+ if (key in exports && exports[key] === _pathValidation[key]) return;
65
+ Object.defineProperty(exports, key, {
66
+ enumerable: true,
67
+ get: function () {
68
+ return _pathValidation[key];
69
+ }
70
+ });
60
71
  });
@@ -0,0 +1,7 @@
1
+ import { Point } from '../geometry';
2
+ import type { NodeLookup } from '../options';
3
+ /**
4
+ * Check if a path intersects with any obstacles (nodes)
5
+ */
6
+ export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup, sourceNodeId: string, targetNodeId: string): boolean;
7
+ //# sourceMappingURL=pathValidation.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.pathIntersectsObstacles = pathIntersectsObstacles;
7
+ var _geometry = require("../geometry");
8
+ var _collision = require("../geometry/collision");
9
+ var _node = require("./node");
10
+ /**
11
+ * Get node bounding box
12
+ */
13
+ function getNodeBBox(nodeId, nodeLookup) {
14
+ const node = nodeLookup.get(nodeId);
15
+ if (!node) return null;
16
+ const dimensions = (0, _node.getNodeDimensions)(node);
17
+ const position = (0, _node.getNodePosition)(node);
18
+ return new _geometry.Rectangle(position.x, position.y, dimensions.width, dimensions.height);
19
+ }
20
+
21
+ /**
22
+ * Check if a point is strictly inside a rectangle (not on edge)
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
+ }
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;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+
48
+ /**
49
+ * Check if a line segment intersects with a node
50
+ */
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
+ }
64
+ }
65
+ return false;
66
+ }
67
+
68
+ /**
69
+ * Check if a path intersects with any obstacles (nodes)
70
+ */
71
+ function pathIntersectsObstacles(pathPoints, nodeLookup, sourceNodeId, targetNodeId) {
72
+ // Iterate through all nodes
73
+ for (const [nodeId, node] of nodeLookup) {
74
+ const nodeBBox = getNodeBBox(nodeId, nodeLookup);
75
+ if (!nodeBBox) continue;
76
+ const isTerminalNode = nodeId === sourceNodeId || nodeId === targetNodeId;
77
+
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
+ }
84
+
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;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return false;
95
+ }
@@ -0,0 +1,15 @@
1
+ import { Point } from './Point';
2
+ import { Rectangle } from './Rectangle';
3
+ /**
4
+ * Check if a line segment intersects with a rectangle
5
+ */
6
+ export declare function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rectangle): boolean;
7
+ /**
8
+ * Check if a line segment crosses through a rectangle (not just touching edges)
9
+ */
10
+ export declare function lineSegmentCrossesRect(p1: Point, p2: Point, rect: Rectangle): boolean;
11
+ /**
12
+ * Check if two line segments intersect
13
+ */
14
+ export declare function lineSegmentsIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean;
15
+ //# sourceMappingURL=collision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collision.d.ts","sourceRoot":"","sources":["../../src/geometry/collision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAwBxF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAsBrF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAgBzF"}
@@ -0,0 +1,73 @@
1
+ import { Point } from "./Point";
2
+ /**
3
+ * Check if a line segment intersects with a rectangle
4
+ */
5
+ export function lineSegmentIntersectsRect(p1, p2, rect) {
6
+ // Check if either endpoint is inside the rectangle
7
+ if (rect.containsPoint(p1) || rect.containsPoint(p2)) {
8
+ return true;
9
+ }
10
+
11
+ // Check if line segment intersects any of the rectangle's edges
12
+ var rectPoints = [new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Point(rect.x, rect.y + rect.height)];
13
+ for (var i = 0; i < 4; i++) {
14
+ var r1 = rectPoints[i];
15
+ var r2 = rectPoints[(i + 1) % 4];
16
+ if (lineSegmentsIntersect(p1, p2, r1, r2)) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+
23
+ /**
24
+ * Check if a line segment crosses through a rectangle (not just touching edges)
25
+ */
26
+ export function lineSegmentCrossesRect(p1, p2, rect) {
27
+ var tolerance = 1;
28
+
29
+ // Check if segment passes through the interior of the rectangle
30
+ // We sample points along the segment and check if any are strictly inside
31
+ var steps = 10;
32
+ for (var i = 1; i < steps; i++) {
33
+ var t = i / steps;
34
+ var x = p1.x + (p2.x - p1.x) * t;
35
+ var y = p1.y + (p2.y - p1.y) * t;
36
+ if (x > rect.x + tolerance && x < rect.x + rect.width - tolerance && y > rect.y + tolerance && y < rect.y + rect.height - tolerance) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+
43
+ /**
44
+ * Check if two line segments intersect
45
+ */
46
+ export function lineSegmentsIntersect(p1, p2, p3, p4) {
47
+ var d1 = direction(p3, p4, p1);
48
+ var d2 = direction(p3, p4, p2);
49
+ var d3 = direction(p1, p2, p3);
50
+ var d4 = direction(p1, p2, p4);
51
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
52
+ return true;
53
+ }
54
+ if (d1 === 0 && onSegment(p3, p1, p4)) return true;
55
+ if (d2 === 0 && onSegment(p3, p2, p4)) return true;
56
+ if (d3 === 0 && onSegment(p1, p3, p2)) return true;
57
+ if (d4 === 0 && onSegment(p1, p4, p2)) return true;
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Calculate direction of point p3 relative to line p1-p2
63
+ */
64
+ function direction(p1, p2, p3) {
65
+ return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
66
+ }
67
+
68
+ /**
69
+ * Check if point p2 is on line segment p1-p3
70
+ */
71
+ function onSegment(p1, p2, p3) {
72
+ 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);
73
+ }
@@ -1,4 +1,5 @@
1
1
  export { Point } from './Point';
2
2
  export { Rectangle } from './Rectangle';
3
3
  export { Line } from './Line';
4
+ export { lineSegmentIntersectsRect, lineSegmentCrossesRect, lineSegmentsIntersect } from './collision';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/geometry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/geometry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA"}
@@ -1,3 +1,4 @@
1
1
  export { Point } from "./Point";
2
2
  export { Rectangle } from "./Rectangle";
3
- export { Line } from "./Line";
3
+ export { Line } from "./Line";
4
+ export { lineSegmentIntersectsRect, lineSegmentCrossesRect, lineSegmentsIntersect } from "./collision";
@@ -1,3 +1,4 @@
1
+ import { Position } from '@xyflow/react';
1
2
  import type { ManhattanRouterOptions, NodeLookup } from './options';
2
3
  /**
3
4
  * Parameters for getManHattanPath function
@@ -11,20 +12,12 @@ export interface GetManHattanPathParams {
11
12
  * Target node ID
12
13
  */
13
14
  targetNodeId: string;
14
- /**
15
- * Source anchor position (where the edge starts)
16
- */
17
- sourcePosition: {
18
- x: number;
19
- y: number;
20
- };
21
- /**
22
- * Target anchor position (where the edge ends)
23
- */
24
- targetPosition: {
25
- x: number;
26
- y: number;
27
- };
15
+ sourceX: number;
16
+ sourceY: number;
17
+ targetX: number;
18
+ targetY: number;
19
+ sourcePosition: Position;
20
+ targetPosition: Position;
28
21
  /**
29
22
  * ReactFlow node lookup map
30
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAOnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAExC;;OAEG;IACH,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAExC;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CA4PvE"}
1
+ {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAE,MAAM,eAAe,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAMnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAEhB,cAAc,EAAE,QAAQ,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC;IACzB;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAsRvE"}
@@ -1,15 +1,20 @@
1
1
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
2
2
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
- 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); }
4
3
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
5
4
  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
5
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
6
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
7
+ 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); }
6
8
  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; }
9
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
10
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
11
+ import { getSmoothStepPath } from '@xyflow/react';
7
12
  import { Point, Rectangle } from "./geometry";
8
- import { resolveOptions } from "./options";
9
13
  import { ObstacleMap } from "./obstacle";
14
+ import { resolveOptions } from "./options";
10
15
  import { findRoute } from "./pathfinder";
11
- import { pointsToPath, snapPathToGrid } from "./svg";
12
- import { getNodeDimensions, getNodePosition } from "./utils";
16
+ import { pointsToPath, snapPathToGrid, parseSVGPath, simplifyPath } from "./svg";
17
+ import { getNodeDimensions, getNodePosition, pathIntersectsObstacles } from "./utils";
13
18
 
14
19
  /**
15
20
  * Parameters for getManHattanPath function
@@ -17,10 +22,10 @@ import { getNodeDimensions, getNodePosition } from "./utils";
17
22
 
18
23
  /**
19
24
  * Generate Manhattan-routed path for ReactFlow edges
20
- *
25
+ *
21
26
  * @param params - Path generation parameters
22
27
  * @returns SVG path string that can be used with ReactFlow's BaseEdge
23
- *
28
+ *
24
29
  * @example
25
30
  * ```typescript
26
31
  * const path = getManHattanPath({
@@ -43,6 +48,10 @@ export function getManHattanPath(params) {
43
48
  sourcePosition = params.sourcePosition,
44
49
  targetPosition = params.targetPosition,
45
50
  nodeLookup = params.nodeLookup,
51
+ sourceX = params.sourceX,
52
+ sourceY = params.sourceY,
53
+ targetX = params.targetX,
54
+ targetY = params.targetY,
46
55
  _params$options = params.options,
47
56
  userOptions = _params$options === void 0 ? {} : _params$options;
48
57
 
@@ -55,8 +64,8 @@ export function getManHattanPath(params) {
55
64
  if (!sourceNode || !targetNode) {
56
65
  // Fallback to simple straight line if nodes not found
57
66
  console.warn('Source or target node not found in nodeLookup');
58
- var start = new Point(sourcePosition.x, sourcePosition.y);
59
- var end = new Point(targetPosition.x, targetPosition.y);
67
+ var start = new Point(sourceX, sourceY);
68
+ var end = new Point(targetX, targetY);
60
69
  return pointsToPath([start, end], options.precision);
61
70
  }
62
71
 
@@ -73,8 +82,31 @@ export function getManHattanPath(params) {
73
82
  var targetBBox = new Rectangle(targetPos.x, targetPos.y, targetDimensions.width, targetDimensions.height);
74
83
 
75
84
  // Create anchor points
76
- var sourceAnchor = new Point(sourcePosition.x, sourcePosition.y);
77
- var targetAnchor = new Point(targetPosition.x, targetPosition.y);
85
+ var sourceAnchor = new Point(sourceX, sourceY);
86
+ var targetAnchor = new Point(targetX, targetY);
87
+
88
+ // Try ReactFlow's getSmoothStepPath first
89
+ var _getSmoothStepPath = getSmoothStepPath({
90
+ sourceX: sourceX,
91
+ sourceY: sourceY,
92
+ targetX: targetX,
93
+ targetY: targetY,
94
+ sourcePosition: sourcePosition,
95
+ targetPosition: targetPosition,
96
+ borderRadius: options.borderRadius
97
+ }),
98
+ _getSmoothStepPath2 = _slicedToArray(_getSmoothStepPath, 1),
99
+ smoothStepPath = _getSmoothStepPath2[0];
100
+
101
+ // Parse the smooth step path to extract points
102
+ var smoothStepPoints = parseSVGPath(smoothStepPath);
103
+
104
+ // Check if smooth step path intersects with any obstacles
105
+ if (smoothStepPoints.length > 0 && !pathIntersectsObstacles(smoothStepPoints, nodeLookup, sourceNodeId, targetNodeId)) {
106
+ console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
107
+ return smoothStepPath;
108
+ }
109
+ console.log('[getManHattanPath] SmoothStepPath intersects obstacles, using Manhattan routing');
78
110
 
79
111
  // Build obstacle map with anchor information
80
112
  var obstacleMap = new ObstacleMap(options).build(nodeLookup, sourceNodeId, targetNodeId, sourceAnchor, targetAnchor);
@@ -260,32 +292,4 @@ export function getManHattanPath(params) {
260
292
 
261
293
  // Convert to SVG path string
262
294
  return pointsToPath(finalRoute, options.precision, options.borderRadius);
263
- }
264
-
265
- /**
266
- * Simplify path by removing collinear intermediate points
267
- */
268
- function simplifyPath(points) {
269
- if (points.length <= 2) {
270
- return points;
271
- }
272
- var simplified = [points[0]];
273
- for (var i = 1; i < points.length - 1; i++) {
274
- var prev = simplified[simplified.length - 1];
275
- var current = points[i];
276
- var next = points[i + 1];
277
-
278
- // Check if current point is collinear with prev and next
279
- var isHorizontalLine = prev.y === current.y && current.y === next.y;
280
- var isVerticalLine = prev.x === current.x && current.x === next.x;
281
-
282
- // Only keep the point if it's not collinear (i.e., it's a corner)
283
- if (!isHorizontalLine && !isVerticalLine) {
284
- simplified.push(current);
285
- }
286
- }
287
-
288
- // Always add the last point
289
- simplified.push(points[points.length - 1]);
290
- return simplified;
291
295
  }
@@ -1,2 +1,3 @@
1
1
  export { pointsToPath, snapPathToGrid } from './pathConverter';
2
+ export { parseSVGPath, simplifyPath } from './pathParser';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
package/esm/svg/index.js CHANGED
@@ -1 +1,2 @@
1
- export { pointsToPath, snapPathToGrid } from "./pathConverter";
1
+ export { pointsToPath, snapPathToGrid } from "./pathConverter";
2
+ export { parseSVGPath, simplifyPath } from "./pathParser";
@@ -0,0 +1,11 @@
1
+ import { Point } from '../geometry';
2
+ /**
3
+ * Parse SVG path string to extract points
4
+ * Simplified parser that handles M, L, Q commands
5
+ */
6
+ export declare function parseSVGPath(pathString: string): Point[];
7
+ /**
8
+ * Simplify path by removing collinear intermediate points
9
+ */
10
+ export declare function simplifyPath(points: Point[]): Point[];
11
+ //# sourceMappingURL=pathParser.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,81 @@
1
+ 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; } } }; }
2
+ 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); }
3
+ 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; }
4
+ import { Point } from "../geometry";
5
+
6
+ /**
7
+ * Parse SVG path string to extract points
8
+ * Simplified parser that handles M, L, Q commands
9
+ */
10
+ export function parseSVGPath(pathString) {
11
+ var points = [];
12
+ var commands = pathString.match(/[MLQ][^MLQ]*/g);
13
+ if (!commands) return points;
14
+ var _iterator = _createForOfIteratorHelper(commands),
15
+ _step;
16
+ try {
17
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
18
+ var command = _step.value;
19
+ var type = command[0];
20
+ var coords = command.slice(1).trim().split(/[\s,]+/).map(Number);
21
+ if (type === 'M' || type === 'L') {
22
+ // MoveTo or LineTo: x, y
23
+ if (coords.length >= 2) {
24
+ points.push(new Point(coords[0], coords[1]));
25
+ }
26
+ } else if (type === 'Q') {
27
+ // Quadratic Bezier: cx, cy, x, y
28
+ // We sample points along the curve for collision detection
29
+ if (coords.length >= 4) {
30
+ var prevPoint = points[points.length - 1];
31
+ if (prevPoint) {
32
+ var cx = coords[0];
33
+ var cy = coords[1];
34
+ var x = coords[2];
35
+ var y = coords[3];
36
+
37
+ // Sample 5 points along the bezier curve
38
+ for (var t = 0.2; t <= 1; t += 0.2) {
39
+ var bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
40
+ var by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
41
+ points.push(new Point(bx, by));
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ } catch (err) {
48
+ _iterator.e(err);
49
+ } finally {
50
+ _iterator.f();
51
+ }
52
+ return points;
53
+ }
54
+
55
+ /**
56
+ * Simplify path by removing collinear intermediate points
57
+ */
58
+ export function simplifyPath(points) {
59
+ if (points.length <= 2) {
60
+ return points;
61
+ }
62
+ var simplified = [points[0]];
63
+ for (var i = 1; i < points.length - 1; i++) {
64
+ var prev = simplified[simplified.length - 1];
65
+ var current = points[i];
66
+ var next = points[i + 1];
67
+
68
+ // Check if current point is collinear with prev and next
69
+ var isHorizontalLine = prev.y === current.y && current.y === next.y;
70
+ var isVerticalLine = prev.x === current.x && current.x === next.x;
71
+
72
+ // Only keep the point if it's not collinear (i.e., it's a corner)
73
+ if (!isHorizontalLine && !isVerticalLine) {
74
+ simplified.push(current);
75
+ }
76
+ }
77
+
78
+ // Always add the last point
79
+ simplified.push(points[points.length - 1]);
80
+ return simplified;
81
+ }
@@ -3,4 +3,5 @@ export * from './direction';
3
3
  export * from './rect';
4
4
  export * from './route';
5
5
  export * from './node';
6
+ export * from './pathValidation';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA"}
@@ -2,4 +2,5 @@ export * from "./grid";
2
2
  export * from "./direction";
3
3
  export * from "./rect";
4
4
  export * from "./route";
5
- export * from "./node";
5
+ export * from "./node";
6
+ export * from "./pathValidation";
@@ -0,0 +1,7 @@
1
+ import { Point } from '../geometry';
2
+ import type { NodeLookup } from '../options';
3
+ /**
4
+ * Check if a path intersects with any obstacles (nodes)
5
+ */
6
+ export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup, sourceNodeId: string, targetNodeId: string): boolean;
7
+ //# sourceMappingURL=pathValidation.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,109 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
4
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
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
+ 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
+ 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";
10
+ import { getNodeDimensions, getNodePosition } from "./node";
11
+
12
+ /**
13
+ * Get node bounding box
14
+ */
15
+ function getNodeBBox(nodeId, nodeLookup) {
16
+ var node = nodeLookup.get(nodeId);
17
+ if (!node) return null;
18
+ var dimensions = getNodeDimensions(node);
19
+ var position = getNodePosition(node);
20
+ return new Rectangle(position.x, position.y, dimensions.width, dimensions.height);
21
+ }
22
+
23
+ /**
24
+ * Check if a point is strictly inside a rectangle (not on edge)
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
+ }
30
+
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;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+
51
+ /**
52
+ * Check if a line segment intersects with a node
53
+ */
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
+ }
67
+ }
68
+ return false;
69
+ }
70
+
71
+ /**
72
+ * Check if a path intersects with any obstacles (nodes)
73
+ */
74
+ export function pathIntersectsObstacles(pathPoints, nodeLookup, sourceNodeId, targetNodeId) {
75
+ // Iterate through all nodes
76
+ var _iterator = _createForOfIteratorHelper(nodeLookup),
77
+ _step;
78
+ try {
79
+ 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];
83
+ var nodeBBox = getNodeBBox(nodeId, nodeLookup);
84
+ if (!nodeBBox) continue;
85
+ var isTerminalNode = nodeId === sourceNodeId || nodeId === targetNodeId;
86
+
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
+ }
93
+
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;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ } catch (err) {
104
+ _iterator.e(err);
105
+ } finally {
106
+ _iterator.f();
107
+ }
108
+ return false;
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxflow/manhattan",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.2",
4
4
  "description": "Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance",
5
5
  "keywords": [
6
6
  "reactflow",