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

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 (49) 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 +54 -54
  10. package/cjs/options/defaults.js +1 -1
  11. package/cjs/svg/index.d.ts +1 -0
  12. package/cjs/svg/index.d.ts.map +1 -1
  13. package/cjs/svg/index.js +14 -1
  14. package/cjs/svg/pathParser.d.ts +11 -0
  15. package/cjs/svg/pathParser.d.ts.map +1 -0
  16. package/cjs/svg/pathParser.js +76 -0
  17. package/cjs/utils/grid.d.ts.map +1 -1
  18. package/cjs/utils/grid.js +3 -17
  19. package/cjs/utils/index.d.ts +1 -0
  20. package/cjs/utils/index.d.ts.map +1 -1
  21. package/cjs/utils/index.js +11 -0
  22. package/cjs/utils/pathValidation.d.ts +11 -0
  23. package/cjs/utils/pathValidation.d.ts.map +1 -0
  24. package/cjs/utils/pathValidation.js +130 -0
  25. package/esm/geometry/collision.d.ts +15 -0
  26. package/esm/geometry/collision.d.ts.map +1 -0
  27. package/esm/geometry/collision.js +73 -0
  28. package/esm/geometry/index.d.ts +1 -0
  29. package/esm/geometry/index.d.ts.map +1 -1
  30. package/esm/geometry/index.js +2 -1
  31. package/esm/getManHattanPath.d.ts +7 -14
  32. package/esm/getManHattanPath.d.ts.map +1 -1
  33. package/esm/getManHattanPath.js +70 -64
  34. package/esm/options/defaults.js +1 -1
  35. package/esm/svg/index.d.ts +1 -0
  36. package/esm/svg/index.d.ts.map +1 -1
  37. package/esm/svg/index.js +2 -1
  38. package/esm/svg/pathParser.d.ts +11 -0
  39. package/esm/svg/pathParser.d.ts.map +1 -0
  40. package/esm/svg/pathParser.js +82 -0
  41. package/esm/utils/grid.d.ts.map +1 -1
  42. package/esm/utils/grid.js +3 -17
  43. package/esm/utils/index.d.ts +1 -0
  44. package/esm/utils/index.d.ts.map +1 -1
  45. package/esm/utils/index.js +2 -1
  46. package/esm/utils/pathValidation.d.ts +11 -0
  47. package/esm/utils/pathValidation.d.ts.map +1 -0
  48. package/esm/utils/pathValidation.js +162 -0
  49. 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,CAwRvE"}
@@ -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)) {
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);
@@ -135,7 +161,9 @@ function getManHattanPath(params) {
135
161
  }
136
162
 
137
163
  // Insert extension point at source - always extend away from node edge by fixed step distance
164
+ // Add borderRadius to extension distance to account for rounded corners
138
165
  if (route.length > 0) {
166
+ const extensionDistance = step + options.borderRadius;
139
167
  const firstPoint = route[0];
140
168
 
141
169
  // Determine which edge the source anchor is on
@@ -146,8 +174,8 @@ function getManHattanPath(params) {
146
174
 
147
175
  // Insert extension point and corner point to ensure orthogonal path
148
176
  if (onRight) {
149
- // Anchor on right edge - extend right by fixed step
150
- const extendX = sourceAnchor.x + step;
177
+ // Anchor on right edge - extend right by step + borderRadius
178
+ const extendX = sourceAnchor.x + extensionDistance;
151
179
  const extensionPoint = new _geometry.Point(extendX, sourceAnchor.y);
152
180
  // Check if we need a corner point
153
181
  if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
@@ -156,8 +184,8 @@ function getManHattanPath(params) {
156
184
  route.unshift(extensionPoint); // Extension point (fixed distance)
157
185
  console.log('[getManHattanPath] Inserted source extension (right):', `(${extendX}, ${sourceAnchor.y})`);
158
186
  } else if (onLeft) {
159
- // Anchor on left edge - extend left by fixed step
160
- const extendX = sourceAnchor.x - step;
187
+ // Anchor on left edge - extend left by step + borderRadius
188
+ const extendX = sourceAnchor.x - extensionDistance;
161
189
  const extensionPoint = new _geometry.Point(extendX, sourceAnchor.y);
162
190
  if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
163
191
  route.unshift(new _geometry.Point(extendX, firstPoint.y));
@@ -165,8 +193,8 @@ function getManHattanPath(params) {
165
193
  route.unshift(extensionPoint);
166
194
  console.log('[getManHattanPath] Inserted source extension (left):', `(${extendX}, ${sourceAnchor.y})`);
167
195
  } else if (onBottom) {
168
- // Anchor on bottom edge - extend down by fixed step
169
- const extendY = sourceAnchor.y + step;
196
+ // Anchor on bottom edge - extend down by step + borderRadius
197
+ const extendY = sourceAnchor.y + extensionDistance;
170
198
  const extensionPoint = new _geometry.Point(sourceAnchor.x, extendY);
171
199
  if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
172
200
  route.unshift(new _geometry.Point(firstPoint.x, extendY));
@@ -174,8 +202,8 @@ function getManHattanPath(params) {
174
202
  route.unshift(extensionPoint);
175
203
  console.log('[getManHattanPath] Inserted source extension (down):', `(${sourceAnchor.x}, ${extendY})`);
176
204
  } else if (onTop) {
177
- // Anchor on top edge - extend up by fixed step
178
- const extendY = sourceAnchor.y - step;
205
+ // Anchor on top edge - extend up by step + borderRadius
206
+ const extendY = sourceAnchor.y - extensionDistance;
179
207
  const extensionPoint = new _geometry.Point(sourceAnchor.x, extendY);
180
208
  if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
181
209
  route.unshift(new _geometry.Point(firstPoint.x, extendY));
@@ -186,9 +214,9 @@ function getManHattanPath(params) {
186
214
  }
187
215
 
188
216
  // Insert extension point at target - always extend away from node edge by fixed step distance
217
+ // Add borderRadius to extension distance to account for rounded corners
189
218
  if (route.length > 0) {
190
- const step = options.step;
191
- const tolerance = 1;
219
+ const extensionDistance = step + options.borderRadius;
192
220
  const lastPoint = route[route.length - 1];
193
221
 
194
222
  // Determine which edge the target anchor is on
@@ -199,8 +227,8 @@ function getManHattanPath(params) {
199
227
 
200
228
  // Insert extension point and corner point to ensure orthogonal path
201
229
  if (onLeft) {
202
- // Anchor on left edge - extend left by fixed step
203
- const extendX = targetAnchor.x - step;
230
+ // Anchor on left edge - extend left by step + borderRadius
231
+ const extendX = targetAnchor.x - extensionDistance;
204
232
  const extensionPoint = new _geometry.Point(extendX, targetAnchor.y);
205
233
  if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
206
234
  route.push(new _geometry.Point(extendX, lastPoint.y)); // Corner point
@@ -208,8 +236,8 @@ function getManHattanPath(params) {
208
236
  route.push(extensionPoint); // Extension point (fixed distance)
209
237
  console.log('[getManHattanPath] Inserted target extension (left):', `(${extendX}, ${targetAnchor.y})`);
210
238
  } else if (onRight) {
211
- // Anchor on right edge - extend right by fixed step
212
- const extendX = targetAnchor.x + step;
239
+ // Anchor on right edge - extend right by step + borderRadius
240
+ const extendX = targetAnchor.x + extensionDistance;
213
241
  const extensionPoint = new _geometry.Point(extendX, targetAnchor.y);
214
242
  if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
215
243
  route.push(new _geometry.Point(extendX, lastPoint.y));
@@ -217,8 +245,8 @@ function getManHattanPath(params) {
217
245
  route.push(extensionPoint);
218
246
  console.log('[getManHattanPath] Inserted target extension (right):', `(${extendX}, ${targetAnchor.y})`);
219
247
  } else if (onTop) {
220
- // Anchor on top edge - extend up by fixed step
221
- const extendY = targetAnchor.y - step;
248
+ // Anchor on top edge - extend up by step + borderRadius
249
+ const extendY = targetAnchor.y - extensionDistance;
222
250
  const extensionPoint = new _geometry.Point(targetAnchor.x, extendY);
223
251
  if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
224
252
  route.push(new _geometry.Point(lastPoint.x, extendY));
@@ -226,8 +254,8 @@ function getManHattanPath(params) {
226
254
  route.push(extensionPoint);
227
255
  console.log('[getManHattanPath] Inserted target extension (up):', `(${targetAnchor.x}, ${extendY})`);
228
256
  } else if (onBottom) {
229
- // Anchor on bottom edge - extend down by fixed step
230
- const extendY = targetAnchor.y + step;
257
+ // Anchor on bottom edge - extend down by step + borderRadius
258
+ const extendY = targetAnchor.y + extensionDistance;
231
259
  const extensionPoint = new _geometry.Point(targetAnchor.x, extendY);
232
260
  if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
233
261
  route.push(new _geometry.Point(lastPoint.x, extendY));
@@ -249,37 +277,9 @@ function getManHattanPath(params) {
249
277
  }
250
278
 
251
279
  // Simplify path by removing collinear points
252
- finalRoute = simplifyPath(finalRoute);
280
+ finalRoute = (0, _svg.simplifyPath)(finalRoute);
253
281
  console.log('[getManHattanPath] Simplified route:', finalRoute.map(p => `(${p.x}, ${p.y})`));
254
282
 
255
283
  // Convert to SVG path string
256
284
  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
285
  }
@@ -18,7 +18,7 @@ const defaults = exports.defaults = {
18
18
  excludeNodes: [],
19
19
  excludeShapes: [],
20
20
  excludeTerminals: [],
21
- padding: 0,
21
+ padding: 15,
22
22
  snapToGrid: true,
23
23
  borderRadius: 5,
24
24
  penalties: {
@@ -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,CA2CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
@@ -0,0 +1,76 @@
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 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) {
39
+ const bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
40
+ const by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
41
+ points.push(new _geometry.Point(bx, by));
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ return points;
48
+ }
49
+
50
+ /**
51
+ * Simplify path by removing collinear intermediate points
52
+ */
53
+ function simplifyPath(points) {
54
+ if (points.length <= 2) {
55
+ return points;
56
+ }
57
+ const simplified = [points[0]];
58
+ for (let i = 1; i < points.length - 1; i++) {
59
+ const prev = simplified[simplified.length - 1];
60
+ const current = points[i];
61
+ const next = points[i + 1];
62
+
63
+ // Check if current point is collinear with prev and next
64
+ const isHorizontalLine = prev.y === current.y && current.y === next.y;
65
+ const isVerticalLine = prev.x === current.x && current.x === next.x;
66
+
67
+ // Only keep the point if it's not collinear (i.e., it's a corner)
68
+ if (!isHorizontalLine && !isVerticalLine) {
69
+ simplified.push(current);
70
+ }
71
+ }
72
+
73
+ // Always add the last point
74
+ simplified.push(points[points.length - 1]);
75
+ return simplified;
76
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../src/utils/grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,KAAK,CAAA;IACb,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AA2BD;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAG,IAAI,CAMxE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE;AAYD;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAExE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAE5D"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../src/utils/grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,KAAK,CAAA;IACb,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAWD;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAG,IAAI,CAMxE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE;AAYD;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAExE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAE5D"}
package/cjs/utils/grid.js CHANGED
@@ -16,23 +16,9 @@ var _geometry = require("../geometry");
16
16
  * Get grid dimension for a single axis
17
17
  */
18
18
  function getGridDimension(diff, step) {
19
- // Return step if diff = 0
20
- if (!diff) {
21
- return step;
22
- }
23
- const abs = Math.abs(diff);
24
- const count = Math.round(abs / step);
25
-
26
- // Return abs if less than one step apart
27
- if (!count) {
28
- return abs;
29
- }
30
-
31
- // Otherwise, return corrected step
32
- const roundedDiff = count * step;
33
- const remainder = abs - roundedDiff;
34
- const correction = remainder / count;
35
- return step + correction;
19
+ // Always return fixed step size to maintain consistent padding
20
+ // This ensures paths stay at least 'padding' distance from obstacles
21
+ return step;
36
22
  }
37
23
 
38
24
  /**
@@ -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,11 @@
1
+ import { Point } from '../geometry';
2
+ import type { NodeLookup } from '../options';
3
+ /**
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
9
+ */
10
+ export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup): boolean;
11
+ //# 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;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"}