@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.
- package/cjs/geometry/collision.d.ts +15 -0
- package/cjs/geometry/collision.d.ts.map +1 -0
- package/cjs/geometry/collision.js +81 -0
- package/cjs/geometry/index.d.ts +1 -0
- package/cjs/geometry/index.d.ts.map +1 -1
- package/cjs/geometry/index.js +20 -1
- package/cjs/getManHattanPath.d.ts +7 -14
- package/cjs/getManHattanPath.d.ts.map +1 -1
- package/cjs/getManHattanPath.js +34 -36
- package/cjs/svg/index.d.ts +1 -0
- package/cjs/svg/index.d.ts.map +1 -1
- package/cjs/svg/index.js +14 -1
- package/cjs/svg/pathParser.d.ts +11 -0
- package/cjs/svg/pathParser.d.ts.map +1 -0
- package/cjs/svg/pathParser.js +75 -0
- package/cjs/utils/index.d.ts +1 -0
- package/cjs/utils/index.d.ts.map +1 -1
- package/cjs/utils/index.js +11 -0
- package/cjs/utils/pathValidation.d.ts +7 -0
- package/cjs/utils/pathValidation.d.ts.map +1 -0
- package/cjs/utils/pathValidation.js +95 -0
- package/esm/geometry/collision.d.ts +15 -0
- package/esm/geometry/collision.d.ts.map +1 -0
- package/esm/geometry/collision.js +73 -0
- package/esm/geometry/index.d.ts +1 -0
- package/esm/geometry/index.d.ts.map +1 -1
- package/esm/geometry/index.js +2 -1
- package/esm/getManHattanPath.d.ts +7 -14
- package/esm/getManHattanPath.d.ts.map +1 -1
- package/esm/getManHattanPath.js +42 -38
- package/esm/svg/index.d.ts +1 -0
- package/esm/svg/index.d.ts.map +1 -1
- package/esm/svg/index.js +2 -1
- package/esm/svg/pathParser.d.ts +11 -0
- package/esm/svg/pathParser.d.ts.map +1 -0
- package/esm/svg/pathParser.js +81 -0
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +2 -1
- package/esm/utils/pathValidation.d.ts +7 -0
- package/esm/utils/pathValidation.d.ts.map +1 -0
- package/esm/utils/pathValidation.js +109 -0
- 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
|
+
}
|
package/cjs/geometry/index.d.ts
CHANGED
|
@@ -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"}
|
package/cjs/geometry/index.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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":"
|
|
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"}
|
package/cjs/getManHattanPath.js
CHANGED
|
@@ -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(
|
|
59
|
-
const end = new _geometry.Point(
|
|
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(
|
|
77
|
-
const targetAnchor = new _geometry.Point(
|
|
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
|
}
|
package/cjs/svg/index.d.ts
CHANGED
package/cjs/svg/index.d.ts.map
CHANGED
|
@@ -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
|
+
}
|
package/cjs/utils/index.d.ts
CHANGED
package/cjs/utils/index.d.ts.map
CHANGED
|
@@ -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"}
|
package/cjs/utils/index.js
CHANGED
|
@@ -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
|
+
}
|
package/esm/geometry/index.d.ts
CHANGED
|
@@ -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"}
|
package/esm/geometry/index.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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":"
|
|
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"}
|
package/esm/getManHattanPath.js
CHANGED
|
@@ -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(
|
|
59
|
-
var end = new Point(
|
|
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(
|
|
77
|
-
var targetAnchor = new Point(
|
|
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
|
}
|
package/esm/svg/index.d.ts
CHANGED
package/esm/svg/index.d.ts.map
CHANGED
|
@@ -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
|
+
}
|
package/esm/utils/index.d.ts
CHANGED
package/esm/utils/index.d.ts.map
CHANGED
|
@@ -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"}
|
package/esm/utils/index.js
CHANGED
|
@@ -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
|
+
}
|