@rxflow/manhattan 0.0.1-alpha.1 → 0.0.1-alpha.11

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.
@@ -1 +1 @@
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
+ {"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"}
@@ -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
  }
@@ -161,7 +161,9 @@ function getManHattanPath(params) {
161
161
  }
162
162
 
163
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
164
165
  if (route.length > 0) {
166
+ const extensionDistance = step + options.borderRadius;
165
167
  const firstPoint = route[0];
166
168
 
167
169
  // Determine which edge the source anchor is on
@@ -172,8 +174,8 @@ function getManHattanPath(params) {
172
174
 
173
175
  // Insert extension point and corner point to ensure orthogonal path
174
176
  if (onRight) {
175
- // Anchor on right edge - extend right by fixed step
176
- const extendX = sourceAnchor.x + step;
177
+ // Anchor on right edge - extend right by step + borderRadius
178
+ const extendX = sourceAnchor.x + extensionDistance;
177
179
  const extensionPoint = new _geometry.Point(extendX, sourceAnchor.y);
178
180
  // Check if we need a corner point
179
181
  if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
@@ -182,8 +184,8 @@ function getManHattanPath(params) {
182
184
  route.unshift(extensionPoint); // Extension point (fixed distance)
183
185
  console.log('[getManHattanPath] Inserted source extension (right):', `(${extendX}, ${sourceAnchor.y})`);
184
186
  } else if (onLeft) {
185
- // Anchor on left edge - extend left by fixed step
186
- const extendX = sourceAnchor.x - step;
187
+ // Anchor on left edge - extend left by step + borderRadius
188
+ const extendX = sourceAnchor.x - extensionDistance;
187
189
  const extensionPoint = new _geometry.Point(extendX, sourceAnchor.y);
188
190
  if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
189
191
  route.unshift(new _geometry.Point(extendX, firstPoint.y));
@@ -191,8 +193,8 @@ function getManHattanPath(params) {
191
193
  route.unshift(extensionPoint);
192
194
  console.log('[getManHattanPath] Inserted source extension (left):', `(${extendX}, ${sourceAnchor.y})`);
193
195
  } else if (onBottom) {
194
- // Anchor on bottom edge - extend down by fixed step
195
- const extendY = sourceAnchor.y + step;
196
+ // Anchor on bottom edge - extend down by step + borderRadius
197
+ const extendY = sourceAnchor.y + extensionDistance;
196
198
  const extensionPoint = new _geometry.Point(sourceAnchor.x, extendY);
197
199
  if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
198
200
  route.unshift(new _geometry.Point(firstPoint.x, extendY));
@@ -200,8 +202,8 @@ function getManHattanPath(params) {
200
202
  route.unshift(extensionPoint);
201
203
  console.log('[getManHattanPath] Inserted source extension (down):', `(${sourceAnchor.x}, ${extendY})`);
202
204
  } else if (onTop) {
203
- // Anchor on top edge - extend up by fixed step
204
- const extendY = sourceAnchor.y - step;
205
+ // Anchor on top edge - extend up by step + borderRadius
206
+ const extendY = sourceAnchor.y - extensionDistance;
205
207
  const extensionPoint = new _geometry.Point(sourceAnchor.x, extendY);
206
208
  if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
207
209
  route.unshift(new _geometry.Point(firstPoint.x, extendY));
@@ -212,9 +214,9 @@ function getManHattanPath(params) {
212
214
  }
213
215
 
214
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
215
218
  if (route.length > 0) {
216
- const step = options.step;
217
- const tolerance = 1;
219
+ const extensionDistance = step + options.borderRadius;
218
220
  const lastPoint = route[route.length - 1];
219
221
 
220
222
  // Determine which edge the target anchor is on
@@ -225,8 +227,8 @@ function getManHattanPath(params) {
225
227
 
226
228
  // Insert extension point and corner point to ensure orthogonal path
227
229
  if (onLeft) {
228
- // Anchor on left edge - extend left by fixed step
229
- const extendX = targetAnchor.x - step;
230
+ // Anchor on left edge - extend left by step + borderRadius
231
+ const extendX = targetAnchor.x - extensionDistance;
230
232
  const extensionPoint = new _geometry.Point(extendX, targetAnchor.y);
231
233
  if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
232
234
  route.push(new _geometry.Point(extendX, lastPoint.y)); // Corner point
@@ -234,8 +236,8 @@ function getManHattanPath(params) {
234
236
  route.push(extensionPoint); // Extension point (fixed distance)
235
237
  console.log('[getManHattanPath] Inserted target extension (left):', `(${extendX}, ${targetAnchor.y})`);
236
238
  } else if (onRight) {
237
- // Anchor on right edge - extend right by fixed step
238
- const extendX = targetAnchor.x + step;
239
+ // Anchor on right edge - extend right by step + borderRadius
240
+ const extendX = targetAnchor.x + extensionDistance;
239
241
  const extensionPoint = new _geometry.Point(extendX, targetAnchor.y);
240
242
  if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
241
243
  route.push(new _geometry.Point(extendX, lastPoint.y));
@@ -243,8 +245,8 @@ function getManHattanPath(params) {
243
245
  route.push(extensionPoint);
244
246
  console.log('[getManHattanPath] Inserted target extension (right):', `(${extendX}, ${targetAnchor.y})`);
245
247
  } else if (onTop) {
246
- // Anchor on top edge - extend up by fixed step
247
- const extendY = targetAnchor.y - step;
248
+ // Anchor on top edge - extend up by step + borderRadius
249
+ const extendY = targetAnchor.y - extensionDistance;
248
250
  const extensionPoint = new _geometry.Point(targetAnchor.x, extendY);
249
251
  if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
250
252
  route.push(new _geometry.Point(lastPoint.x, extendY));
@@ -252,8 +254,8 @@ function getManHattanPath(params) {
252
254
  route.push(extensionPoint);
253
255
  console.log('[getManHattanPath] Inserted target extension (up):', `(${targetAnchor.x}, ${extendY})`);
254
256
  } else if (onBottom) {
255
- // Anchor on bottom edge - extend down by fixed step
256
- const extendY = targetAnchor.y + step;
257
+ // Anchor on bottom edge - extend down by step + borderRadius
258
+ const extendY = targetAnchor.y + extensionDistance;
257
259
  const extensionPoint = new _geometry.Point(targetAnchor.x, extendY);
258
260
  if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
259
261
  route.push(new _geometry.Point(lastPoint.x, extendY));
@@ -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 +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));
@@ -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
  /**
@@ -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
  }
@@ -1 +1 @@
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
+ {"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"}
@@ -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
  }
@@ -169,7 +169,9 @@ export function getManHattanPath(params) {
169
169
  }
170
170
 
171
171
  // Insert extension point at source - always extend away from node edge by fixed step distance
172
+ // Add borderRadius to extension distance to account for rounded corners
172
173
  if (route.length > 0) {
174
+ var extensionDistance = step + options.borderRadius;
173
175
  var _firstPoint = route[0];
174
176
 
175
177
  // Determine which edge the source anchor is on
@@ -180,8 +182,8 @@ export function getManHattanPath(params) {
180
182
 
181
183
  // Insert extension point and corner point to ensure orthogonal path
182
184
  if (_onRight2) {
183
- // Anchor on right edge - extend right by fixed step
184
- var extendX = sourceAnchor.x + step;
185
+ // Anchor on right edge - extend right by step + borderRadius
186
+ var extendX = sourceAnchor.x + extensionDistance;
185
187
  var extensionPoint = new Point(extendX, sourceAnchor.y);
186
188
  // Check if we need a corner point
187
189
  if (Math.abs(extensionPoint.y - _firstPoint.y) > tolerance) {
@@ -190,8 +192,8 @@ export function getManHattanPath(params) {
190
192
  route.unshift(extensionPoint); // Extension point (fixed distance)
191
193
  console.log('[getManHattanPath] Inserted source extension (right):', "(".concat(extendX, ", ").concat(sourceAnchor.y, ")"));
192
194
  } else if (_onLeft2) {
193
- // Anchor on left edge - extend left by fixed step
194
- var _extendX = sourceAnchor.x - step;
195
+ // Anchor on left edge - extend left by step + borderRadius
196
+ var _extendX = sourceAnchor.x - extensionDistance;
195
197
  var _extensionPoint = new Point(_extendX, sourceAnchor.y);
196
198
  if (Math.abs(_extensionPoint.y - _firstPoint.y) > tolerance) {
197
199
  route.unshift(new Point(_extendX, _firstPoint.y));
@@ -199,8 +201,8 @@ export function getManHattanPath(params) {
199
201
  route.unshift(_extensionPoint);
200
202
  console.log('[getManHattanPath] Inserted source extension (left):', "(".concat(_extendX, ", ").concat(sourceAnchor.y, ")"));
201
203
  } else if (_onBottom2) {
202
- // Anchor on bottom edge - extend down by fixed step
203
- var extendY = sourceAnchor.y + step;
204
+ // Anchor on bottom edge - extend down by step + borderRadius
205
+ var extendY = sourceAnchor.y + extensionDistance;
204
206
  var _extensionPoint2 = new Point(sourceAnchor.x, extendY);
205
207
  if (Math.abs(_extensionPoint2.x - _firstPoint.x) > tolerance) {
206
208
  route.unshift(new Point(_firstPoint.x, extendY));
@@ -208,8 +210,8 @@ export function getManHattanPath(params) {
208
210
  route.unshift(_extensionPoint2);
209
211
  console.log('[getManHattanPath] Inserted source extension (down):', "(".concat(sourceAnchor.x, ", ").concat(extendY, ")"));
210
212
  } else if (_onTop2) {
211
- // Anchor on top edge - extend up by fixed step
212
- var _extendY = sourceAnchor.y - step;
213
+ // Anchor on top edge - extend up by step + borderRadius
214
+ var _extendY = sourceAnchor.y - extensionDistance;
213
215
  var _extensionPoint3 = new Point(sourceAnchor.x, _extendY);
214
216
  if (Math.abs(_extensionPoint3.x - _firstPoint.x) > tolerance) {
215
217
  route.unshift(new Point(_firstPoint.x, _extendY));
@@ -220,50 +222,50 @@ export function getManHattanPath(params) {
220
222
  }
221
223
 
222
224
  // Insert extension point at target - always extend away from node edge by fixed step distance
225
+ // Add borderRadius to extension distance to account for rounded corners
223
226
  if (route.length > 0) {
224
- var _step = options.step;
225
- var _tolerance = 1;
227
+ var _extensionDistance = step + options.borderRadius;
226
228
  var _lastPoint = route[route.length - 1];
227
229
 
228
230
  // Determine which edge the target anchor is on
229
- var _onLeft3 = Math.abs(targetAnchor.x - targetBBox.x) < _tolerance;
230
- var _onRight3 = Math.abs(targetAnchor.x - (targetBBox.x + targetBBox.width)) < _tolerance;
231
- var _onTop3 = Math.abs(targetAnchor.y - targetBBox.y) < _tolerance;
232
- var _onBottom3 = Math.abs(targetAnchor.y - (targetBBox.y + targetBBox.height)) < _tolerance;
231
+ var _onLeft3 = Math.abs(targetAnchor.x - targetBBox.x) < tolerance;
232
+ var _onRight3 = Math.abs(targetAnchor.x - (targetBBox.x + targetBBox.width)) < tolerance;
233
+ var _onTop3 = Math.abs(targetAnchor.y - targetBBox.y) < tolerance;
234
+ var _onBottom3 = Math.abs(targetAnchor.y - (targetBBox.y + targetBBox.height)) < tolerance;
233
235
 
234
236
  // Insert extension point and corner point to ensure orthogonal path
235
237
  if (_onLeft3) {
236
- // Anchor on left edge - extend left by fixed step
237
- var _extendX2 = targetAnchor.x - _step;
238
+ // Anchor on left edge - extend left by step + borderRadius
239
+ var _extendX2 = targetAnchor.x - _extensionDistance;
238
240
  var _extensionPoint4 = new Point(_extendX2, targetAnchor.y);
239
- if (Math.abs(_extensionPoint4.y - _lastPoint.y) > _tolerance) {
241
+ if (Math.abs(_extensionPoint4.y - _lastPoint.y) > tolerance) {
240
242
  route.push(new Point(_extendX2, _lastPoint.y)); // Corner point
241
243
  }
242
244
  route.push(_extensionPoint4); // Extension point (fixed distance)
243
245
  console.log('[getManHattanPath] Inserted target extension (left):', "(".concat(_extendX2, ", ").concat(targetAnchor.y, ")"));
244
246
  } else if (_onRight3) {
245
- // Anchor on right edge - extend right by fixed step
246
- var _extendX3 = targetAnchor.x + _step;
247
+ // Anchor on right edge - extend right by step + borderRadius
248
+ var _extendX3 = targetAnchor.x + _extensionDistance;
247
249
  var _extensionPoint5 = new Point(_extendX3, targetAnchor.y);
248
- if (Math.abs(_extensionPoint5.y - _lastPoint.y) > _tolerance) {
250
+ if (Math.abs(_extensionPoint5.y - _lastPoint.y) > tolerance) {
249
251
  route.push(new Point(_extendX3, _lastPoint.y));
250
252
  }
251
253
  route.push(_extensionPoint5);
252
254
  console.log('[getManHattanPath] Inserted target extension (right):', "(".concat(_extendX3, ", ").concat(targetAnchor.y, ")"));
253
255
  } else if (_onTop3) {
254
- // Anchor on top edge - extend up by fixed step
255
- var _extendY2 = targetAnchor.y - _step;
256
+ // Anchor on top edge - extend up by step + borderRadius
257
+ var _extendY2 = targetAnchor.y - _extensionDistance;
256
258
  var _extensionPoint6 = new Point(targetAnchor.x, _extendY2);
257
- if (Math.abs(_extensionPoint6.x - _lastPoint.x) > _tolerance) {
259
+ if (Math.abs(_extensionPoint6.x - _lastPoint.x) > tolerance) {
258
260
  route.push(new Point(_lastPoint.x, _extendY2));
259
261
  }
260
262
  route.push(_extensionPoint6);
261
263
  console.log('[getManHattanPath] Inserted target extension (up):', "(".concat(targetAnchor.x, ", ").concat(_extendY2, ")"));
262
264
  } else if (_onBottom3) {
263
- // Anchor on bottom edge - extend down by fixed step
264
- var _extendY3 = targetAnchor.y + _step;
265
+ // Anchor on bottom edge - extend down by step + borderRadius
266
+ var _extendY3 = targetAnchor.y + _extensionDistance;
265
267
  var _extensionPoint7 = new Point(targetAnchor.x, _extendY3);
266
- if (Math.abs(_extensionPoint7.x - _lastPoint.x) > _tolerance) {
268
+ if (Math.abs(_extensionPoint7.x - _lastPoint.x) > tolerance) {
267
269
  route.push(new Point(_lastPoint.x, _extendY3));
268
270
  }
269
271
  route.push(_extensionPoint7);
@@ -12,7 +12,7 @@ export var defaults = {
12
12
  excludeNodes: [],
13
13
  excludeShapes: [],
14
14
  excludeTerminals: [],
15
- padding: 0,
15
+ padding: 15,
16
16
  snapToGrid: true,
17
17
  borderRadius: 5,
18
18
  penalties: {
@@ -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));
@@ -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/esm/utils/grid.js CHANGED
@@ -8,23 +8,9 @@ import { Point } from "../geometry";
8
8
  * Get grid dimension for a single axis
9
9
  */
10
10
  function getGridDimension(diff, step) {
11
- // Return step if diff = 0
12
- if (!diff) {
13
- return step;
14
- }
15
- var abs = Math.abs(diff);
16
- var count = Math.round(abs / step);
17
-
18
- // Return abs if less than one step apart
19
- if (!count) {
20
- return abs;
21
- }
22
-
23
- // Otherwise, return corrected step
24
- var roundedDiff = count * step;
25
- var remainder = abs - roundedDiff;
26
- var correction = remainder / count;
27
- return step + correction;
11
+ // Always return fixed step size to maintain consistent padding
12
+ // This ensures paths stay at least 'padding' distance from obstacles
13
+ return step;
28
14
  }
29
15
 
30
16
  /**
@@ -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.1",
3
+ "version": "0.0.1-alpha.11",
4
4
  "description": "Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance",
5
5
  "keywords": [
6
6
  "reactflow",