@rxflow/manhattan 0.0.1-alpha.0 → 0.0.1-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +54 -54
- package/cjs/options/defaults.js +1 -1
- 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 +76 -0
- package/cjs/utils/grid.d.ts.map +1 -1
- package/cjs/utils/grid.js +3 -17
- 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 +11 -0
- package/cjs/utils/pathValidation.d.ts.map +1 -0
- package/cjs/utils/pathValidation.js +130 -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 +70 -64
- package/esm/options/defaults.js +1 -1
- 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 +82 -0
- package/esm/utils/grid.d.ts.map +1 -1
- package/esm/utils/grid.js +3 -17
- 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 +11 -0
- package/esm/utils/pathValidation.d.ts.map +1 -0
- package/esm/utils/pathValidation.js +162 -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,CAwRvE"}
|
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)) {
|
|
100
|
+
console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
|
|
101
|
+
return smoothStepPath;
|
|
102
|
+
}
|
|
103
|
+
console.log('[getManHattanPath] SmoothStepPath intersects obstacles, using Manhattan routing');
|
|
78
104
|
|
|
79
105
|
// Build obstacle map with anchor information
|
|
80
106
|
const obstacleMap = new _obstacle.ObstacleMap(options).build(nodeLookup, sourceNodeId, targetNodeId, sourceAnchor, targetAnchor);
|
|
@@ -135,7 +161,9 @@ function getManHattanPath(params) {
|
|
|
135
161
|
}
|
|
136
162
|
|
|
137
163
|
// Insert extension point at source - always extend away from node edge by fixed step distance
|
|
164
|
+
// Add borderRadius to extension distance to account for rounded corners
|
|
138
165
|
if (route.length > 0) {
|
|
166
|
+
const extensionDistance = step + options.borderRadius;
|
|
139
167
|
const firstPoint = route[0];
|
|
140
168
|
|
|
141
169
|
// Determine which edge the source anchor is on
|
|
@@ -146,8 +174,8 @@ function getManHattanPath(params) {
|
|
|
146
174
|
|
|
147
175
|
// Insert extension point and corner point to ensure orthogonal path
|
|
148
176
|
if (onRight) {
|
|
149
|
-
// Anchor on right edge - extend right by
|
|
150
|
-
const extendX = sourceAnchor.x +
|
|
177
|
+
// Anchor on right edge - extend right by step + borderRadius
|
|
178
|
+
const extendX = sourceAnchor.x + extensionDistance;
|
|
151
179
|
const extensionPoint = new _geometry.Point(extendX, sourceAnchor.y);
|
|
152
180
|
// Check if we need a corner point
|
|
153
181
|
if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
|
|
@@ -156,8 +184,8 @@ function getManHattanPath(params) {
|
|
|
156
184
|
route.unshift(extensionPoint); // Extension point (fixed distance)
|
|
157
185
|
console.log('[getManHattanPath] Inserted source extension (right):', `(${extendX}, ${sourceAnchor.y})`);
|
|
158
186
|
} else if (onLeft) {
|
|
159
|
-
// Anchor on left edge - extend left by
|
|
160
|
-
const extendX = sourceAnchor.x -
|
|
187
|
+
// Anchor on left edge - extend left by step + borderRadius
|
|
188
|
+
const extendX = sourceAnchor.x - extensionDistance;
|
|
161
189
|
const extensionPoint = new _geometry.Point(extendX, sourceAnchor.y);
|
|
162
190
|
if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
|
|
163
191
|
route.unshift(new _geometry.Point(extendX, firstPoint.y));
|
|
@@ -165,8 +193,8 @@ function getManHattanPath(params) {
|
|
|
165
193
|
route.unshift(extensionPoint);
|
|
166
194
|
console.log('[getManHattanPath] Inserted source extension (left):', `(${extendX}, ${sourceAnchor.y})`);
|
|
167
195
|
} else if (onBottom) {
|
|
168
|
-
// Anchor on bottom edge - extend down by
|
|
169
|
-
const extendY = sourceAnchor.y +
|
|
196
|
+
// Anchor on bottom edge - extend down by step + borderRadius
|
|
197
|
+
const extendY = sourceAnchor.y + extensionDistance;
|
|
170
198
|
const extensionPoint = new _geometry.Point(sourceAnchor.x, extendY);
|
|
171
199
|
if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
|
|
172
200
|
route.unshift(new _geometry.Point(firstPoint.x, extendY));
|
|
@@ -174,8 +202,8 @@ function getManHattanPath(params) {
|
|
|
174
202
|
route.unshift(extensionPoint);
|
|
175
203
|
console.log('[getManHattanPath] Inserted source extension (down):', `(${sourceAnchor.x}, ${extendY})`);
|
|
176
204
|
} else if (onTop) {
|
|
177
|
-
// Anchor on top edge - extend up by
|
|
178
|
-
const extendY = sourceAnchor.y -
|
|
205
|
+
// Anchor on top edge - extend up by step + borderRadius
|
|
206
|
+
const extendY = sourceAnchor.y - extensionDistance;
|
|
179
207
|
const extensionPoint = new _geometry.Point(sourceAnchor.x, extendY);
|
|
180
208
|
if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
|
|
181
209
|
route.unshift(new _geometry.Point(firstPoint.x, extendY));
|
|
@@ -186,9 +214,9 @@ function getManHattanPath(params) {
|
|
|
186
214
|
}
|
|
187
215
|
|
|
188
216
|
// Insert extension point at target - always extend away from node edge by fixed step distance
|
|
217
|
+
// Add borderRadius to extension distance to account for rounded corners
|
|
189
218
|
if (route.length > 0) {
|
|
190
|
-
const
|
|
191
|
-
const tolerance = 1;
|
|
219
|
+
const extensionDistance = step + options.borderRadius;
|
|
192
220
|
const lastPoint = route[route.length - 1];
|
|
193
221
|
|
|
194
222
|
// Determine which edge the target anchor is on
|
|
@@ -199,8 +227,8 @@ function getManHattanPath(params) {
|
|
|
199
227
|
|
|
200
228
|
// Insert extension point and corner point to ensure orthogonal path
|
|
201
229
|
if (onLeft) {
|
|
202
|
-
// Anchor on left edge - extend left by
|
|
203
|
-
const extendX = targetAnchor.x -
|
|
230
|
+
// Anchor on left edge - extend left by step + borderRadius
|
|
231
|
+
const extendX = targetAnchor.x - extensionDistance;
|
|
204
232
|
const extensionPoint = new _geometry.Point(extendX, targetAnchor.y);
|
|
205
233
|
if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
|
|
206
234
|
route.push(new _geometry.Point(extendX, lastPoint.y)); // Corner point
|
|
@@ -208,8 +236,8 @@ function getManHattanPath(params) {
|
|
|
208
236
|
route.push(extensionPoint); // Extension point (fixed distance)
|
|
209
237
|
console.log('[getManHattanPath] Inserted target extension (left):', `(${extendX}, ${targetAnchor.y})`);
|
|
210
238
|
} else if (onRight) {
|
|
211
|
-
// Anchor on right edge - extend right by
|
|
212
|
-
const extendX = targetAnchor.x +
|
|
239
|
+
// Anchor on right edge - extend right by step + borderRadius
|
|
240
|
+
const extendX = targetAnchor.x + extensionDistance;
|
|
213
241
|
const extensionPoint = new _geometry.Point(extendX, targetAnchor.y);
|
|
214
242
|
if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
|
|
215
243
|
route.push(new _geometry.Point(extendX, lastPoint.y));
|
|
@@ -217,8 +245,8 @@ function getManHattanPath(params) {
|
|
|
217
245
|
route.push(extensionPoint);
|
|
218
246
|
console.log('[getManHattanPath] Inserted target extension (right):', `(${extendX}, ${targetAnchor.y})`);
|
|
219
247
|
} else if (onTop) {
|
|
220
|
-
// Anchor on top edge - extend up by
|
|
221
|
-
const extendY = targetAnchor.y -
|
|
248
|
+
// Anchor on top edge - extend up by step + borderRadius
|
|
249
|
+
const extendY = targetAnchor.y - extensionDistance;
|
|
222
250
|
const extensionPoint = new _geometry.Point(targetAnchor.x, extendY);
|
|
223
251
|
if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
|
|
224
252
|
route.push(new _geometry.Point(lastPoint.x, extendY));
|
|
@@ -226,8 +254,8 @@ function getManHattanPath(params) {
|
|
|
226
254
|
route.push(extensionPoint);
|
|
227
255
|
console.log('[getManHattanPath] Inserted target extension (up):', `(${targetAnchor.x}, ${extendY})`);
|
|
228
256
|
} else if (onBottom) {
|
|
229
|
-
// Anchor on bottom edge - extend down by
|
|
230
|
-
const extendY = targetAnchor.y +
|
|
257
|
+
// Anchor on bottom edge - extend down by step + borderRadius
|
|
258
|
+
const extendY = targetAnchor.y + extensionDistance;
|
|
231
259
|
const extensionPoint = new _geometry.Point(targetAnchor.x, extendY);
|
|
232
260
|
if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
|
|
233
261
|
route.push(new _geometry.Point(lastPoint.x, extendY));
|
|
@@ -249,37 +277,9 @@ function getManHattanPath(params) {
|
|
|
249
277
|
}
|
|
250
278
|
|
|
251
279
|
// Simplify path by removing collinear points
|
|
252
|
-
finalRoute = simplifyPath(finalRoute);
|
|
280
|
+
finalRoute = (0, _svg.simplifyPath)(finalRoute);
|
|
253
281
|
console.log('[getManHattanPath] Simplified route:', finalRoute.map(p => `(${p.x}, ${p.y})`));
|
|
254
282
|
|
|
255
283
|
// Convert to SVG path string
|
|
256
284
|
return (0, _svg.pointsToPath)(finalRoute, options.precision, options.borderRadius);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Simplify path by removing collinear intermediate points
|
|
261
|
-
*/
|
|
262
|
-
function simplifyPath(points) {
|
|
263
|
-
if (points.length <= 2) {
|
|
264
|
-
return points;
|
|
265
|
-
}
|
|
266
|
-
const simplified = [points[0]];
|
|
267
|
-
for (let i = 1; i < points.length - 1; i++) {
|
|
268
|
-
const prev = simplified[simplified.length - 1];
|
|
269
|
-
const current = points[i];
|
|
270
|
-
const next = points[i + 1];
|
|
271
|
-
|
|
272
|
-
// Check if current point is collinear with prev and next
|
|
273
|
-
const isHorizontalLine = prev.y === current.y && current.y === next.y;
|
|
274
|
-
const isVerticalLine = prev.x === current.x && current.x === next.x;
|
|
275
|
-
|
|
276
|
-
// Only keep the point if it's not collinear (i.e., it's a corner)
|
|
277
|
-
if (!isHorizontalLine && !isVerticalLine) {
|
|
278
|
-
simplified.push(current);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Always add the last point
|
|
283
|
-
simplified.push(points[points.length - 1]);
|
|
284
|
-
return simplified;
|
|
285
285
|
}
|
package/cjs/options/defaults.js
CHANGED
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,CA2CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.parseSVGPath = parseSVGPath;
|
|
7
|
+
exports.simplifyPath = simplifyPath;
|
|
8
|
+
var _geometry = require("../geometry");
|
|
9
|
+
/**
|
|
10
|
+
* Parse SVG path string to extract points
|
|
11
|
+
* Simplified parser that handles M, L, Q commands
|
|
12
|
+
*/
|
|
13
|
+
function parseSVGPath(pathString) {
|
|
14
|
+
const points = [];
|
|
15
|
+
const commands = pathString.match(/[MLQ][^MLQ]*/g);
|
|
16
|
+
if (!commands) return points;
|
|
17
|
+
for (const command of commands) {
|
|
18
|
+
const type = command[0];
|
|
19
|
+
const coords = command.slice(1).trim().split(/[\s,]+/).map(Number);
|
|
20
|
+
if (type === 'M' || type === 'L') {
|
|
21
|
+
// MoveTo or LineTo: x, y
|
|
22
|
+
if (coords.length >= 2) {
|
|
23
|
+
points.push(new _geometry.Point(coords[0], coords[1]));
|
|
24
|
+
}
|
|
25
|
+
} else if (type === 'Q') {
|
|
26
|
+
// Quadratic Bezier: cx, cy, x, y
|
|
27
|
+
// We sample points along the curve for collision detection
|
|
28
|
+
if (coords.length >= 4) {
|
|
29
|
+
const prevPoint = points[points.length - 1];
|
|
30
|
+
if (prevPoint) {
|
|
31
|
+
const cx = coords[0];
|
|
32
|
+
const cy = coords[1];
|
|
33
|
+
const x = coords[2];
|
|
34
|
+
const y = coords[3];
|
|
35
|
+
|
|
36
|
+
// Sample 10 points along the bezier curve for better accuracy
|
|
37
|
+
// This ensures we don't miss intersections with obstacles
|
|
38
|
+
for (let t = 0.1; t <= 1; t += 0.1) {
|
|
39
|
+
const bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
|
|
40
|
+
const by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
|
|
41
|
+
points.push(new _geometry.Point(bx, by));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return points;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Simplify path by removing collinear intermediate points
|
|
52
|
+
*/
|
|
53
|
+
function simplifyPath(points) {
|
|
54
|
+
if (points.length <= 2) {
|
|
55
|
+
return points;
|
|
56
|
+
}
|
|
57
|
+
const simplified = [points[0]];
|
|
58
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
59
|
+
const prev = simplified[simplified.length - 1];
|
|
60
|
+
const current = points[i];
|
|
61
|
+
const next = points[i + 1];
|
|
62
|
+
|
|
63
|
+
// Check if current point is collinear with prev and next
|
|
64
|
+
const isHorizontalLine = prev.y === current.y && current.y === next.y;
|
|
65
|
+
const isVerticalLine = prev.x === current.x && current.x === next.x;
|
|
66
|
+
|
|
67
|
+
// Only keep the point if it's not collinear (i.e., it's a corner)
|
|
68
|
+
if (!isHorizontalLine && !isVerticalLine) {
|
|
69
|
+
simplified.push(current);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Always add the last point
|
|
74
|
+
simplified.push(points[points.length - 1]);
|
|
75
|
+
return simplified;
|
|
76
|
+
}
|
package/cjs/utils/grid.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
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
|
/**
|
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,11 @@
|
|
|
1
|
+
import { Point } from '../geometry';
|
|
2
|
+
import type { NodeLookup } from '../options';
|
|
3
|
+
/**
|
|
4
|
+
* Check if a path intersects with any obstacles (nodes)
|
|
5
|
+
* A path is considered to intersect if it has >= 2 unique intersection points with a node
|
|
6
|
+
*
|
|
7
|
+
* Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
|
|
8
|
+
* into line segments, so this function works correctly with curved paths
|
|
9
|
+
*/
|
|
10
|
+
export declare function pathIntersectsObstacles(pathPoints: Point[], nodeLookup: NodeLookup): boolean;
|
|
11
|
+
//# sourceMappingURL=pathValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathValidation.d.ts","sourceRoot":"","sources":["../../src/utils/pathValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAyF5C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAoC5F"}
|