@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,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.pathIntersectsObstacles = pathIntersectsObstacles;
|
|
7
|
+
var _geometry = require("../geometry");
|
|
8
|
+
var _node = require("./node");
|
|
9
|
+
/**
|
|
10
|
+
* Get node bounding box
|
|
11
|
+
*/
|
|
12
|
+
function getNodeBBox(nodeId, nodeLookup) {
|
|
13
|
+
const node = nodeLookup.get(nodeId);
|
|
14
|
+
if (!node) return null;
|
|
15
|
+
const dimensions = (0, _node.getNodeDimensions)(node);
|
|
16
|
+
const position = (0, _node.getNodePosition)(node);
|
|
17
|
+
return new _geometry.Rectangle(position.x, position.y, dimensions.width, dimensions.height);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Find unique intersection points between a line segment and a rectangle
|
|
22
|
+
* Returns array of intersection points (deduplicated)
|
|
23
|
+
*/
|
|
24
|
+
function findSegmentRectIntersections(p1, p2, rect) {
|
|
25
|
+
const intersections = [];
|
|
26
|
+
const tolerance = 0.01;
|
|
27
|
+
|
|
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
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return intersections;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get intersection point between two line segments (if exists)
|
|
66
|
+
*/
|
|
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));
|
|
76
|
+
}
|
|
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);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
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
|
|
98
|
+
*/
|
|
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) {
|
|
104
|
+
const nodeBBox = getNodeBBox(nodeId, nodeLookup);
|
|
105
|
+
if (!nodeBBox) continue;
|
|
106
|
+
const allIntersections = [];
|
|
107
|
+
|
|
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);
|
|
113
|
+
|
|
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);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
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
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
@@ -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,CAwRvE"}
|
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)) {
|
|
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);
|
|
@@ -137,7 +169,9 @@ export function getManHattanPath(params) {
|
|
|
137
169
|
}
|
|
138
170
|
|
|
139
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
|
|
140
173
|
if (route.length > 0) {
|
|
174
|
+
var extensionDistance = step + options.borderRadius;
|
|
141
175
|
var _firstPoint = route[0];
|
|
142
176
|
|
|
143
177
|
// Determine which edge the source anchor is on
|
|
@@ -148,8 +182,8 @@ export function getManHattanPath(params) {
|
|
|
148
182
|
|
|
149
183
|
// Insert extension point and corner point to ensure orthogonal path
|
|
150
184
|
if (_onRight2) {
|
|
151
|
-
// Anchor on right edge - extend right by
|
|
152
|
-
var extendX = sourceAnchor.x +
|
|
185
|
+
// Anchor on right edge - extend right by step + borderRadius
|
|
186
|
+
var extendX = sourceAnchor.x + extensionDistance;
|
|
153
187
|
var extensionPoint = new Point(extendX, sourceAnchor.y);
|
|
154
188
|
// Check if we need a corner point
|
|
155
189
|
if (Math.abs(extensionPoint.y - _firstPoint.y) > tolerance) {
|
|
@@ -158,8 +192,8 @@ export function getManHattanPath(params) {
|
|
|
158
192
|
route.unshift(extensionPoint); // Extension point (fixed distance)
|
|
159
193
|
console.log('[getManHattanPath] Inserted source extension (right):', "(".concat(extendX, ", ").concat(sourceAnchor.y, ")"));
|
|
160
194
|
} else if (_onLeft2) {
|
|
161
|
-
// Anchor on left edge - extend left by
|
|
162
|
-
var _extendX = sourceAnchor.x -
|
|
195
|
+
// Anchor on left edge - extend left by step + borderRadius
|
|
196
|
+
var _extendX = sourceAnchor.x - extensionDistance;
|
|
163
197
|
var _extensionPoint = new Point(_extendX, sourceAnchor.y);
|
|
164
198
|
if (Math.abs(_extensionPoint.y - _firstPoint.y) > tolerance) {
|
|
165
199
|
route.unshift(new Point(_extendX, _firstPoint.y));
|
|
@@ -167,8 +201,8 @@ export function getManHattanPath(params) {
|
|
|
167
201
|
route.unshift(_extensionPoint);
|
|
168
202
|
console.log('[getManHattanPath] Inserted source extension (left):', "(".concat(_extendX, ", ").concat(sourceAnchor.y, ")"));
|
|
169
203
|
} else if (_onBottom2) {
|
|
170
|
-
// Anchor on bottom edge - extend down by
|
|
171
|
-
var extendY = sourceAnchor.y +
|
|
204
|
+
// Anchor on bottom edge - extend down by step + borderRadius
|
|
205
|
+
var extendY = sourceAnchor.y + extensionDistance;
|
|
172
206
|
var _extensionPoint2 = new Point(sourceAnchor.x, extendY);
|
|
173
207
|
if (Math.abs(_extensionPoint2.x - _firstPoint.x) > tolerance) {
|
|
174
208
|
route.unshift(new Point(_firstPoint.x, extendY));
|
|
@@ -176,8 +210,8 @@ export function getManHattanPath(params) {
|
|
|
176
210
|
route.unshift(_extensionPoint2);
|
|
177
211
|
console.log('[getManHattanPath] Inserted source extension (down):', "(".concat(sourceAnchor.x, ", ").concat(extendY, ")"));
|
|
178
212
|
} else if (_onTop2) {
|
|
179
|
-
// Anchor on top edge - extend up by
|
|
180
|
-
var _extendY = sourceAnchor.y -
|
|
213
|
+
// Anchor on top edge - extend up by step + borderRadius
|
|
214
|
+
var _extendY = sourceAnchor.y - extensionDistance;
|
|
181
215
|
var _extensionPoint3 = new Point(sourceAnchor.x, _extendY);
|
|
182
216
|
if (Math.abs(_extensionPoint3.x - _firstPoint.x) > tolerance) {
|
|
183
217
|
route.unshift(new Point(_firstPoint.x, _extendY));
|
|
@@ -188,50 +222,50 @@ export function getManHattanPath(params) {
|
|
|
188
222
|
}
|
|
189
223
|
|
|
190
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
|
|
191
226
|
if (route.length > 0) {
|
|
192
|
-
var
|
|
193
|
-
var _tolerance = 1;
|
|
227
|
+
var _extensionDistance = step + options.borderRadius;
|
|
194
228
|
var _lastPoint = route[route.length - 1];
|
|
195
229
|
|
|
196
230
|
// Determine which edge the target anchor is on
|
|
197
|
-
var _onLeft3 = Math.abs(targetAnchor.x - targetBBox.x) <
|
|
198
|
-
var _onRight3 = Math.abs(targetAnchor.x - (targetBBox.x + targetBBox.width)) <
|
|
199
|
-
var _onTop3 = Math.abs(targetAnchor.y - targetBBox.y) <
|
|
200
|
-
var _onBottom3 = Math.abs(targetAnchor.y - (targetBBox.y + targetBBox.height)) <
|
|
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;
|
|
201
235
|
|
|
202
236
|
// Insert extension point and corner point to ensure orthogonal path
|
|
203
237
|
if (_onLeft3) {
|
|
204
|
-
// Anchor on left edge - extend left by
|
|
205
|
-
var _extendX2 = targetAnchor.x -
|
|
238
|
+
// Anchor on left edge - extend left by step + borderRadius
|
|
239
|
+
var _extendX2 = targetAnchor.x - _extensionDistance;
|
|
206
240
|
var _extensionPoint4 = new Point(_extendX2, targetAnchor.y);
|
|
207
|
-
if (Math.abs(_extensionPoint4.y - _lastPoint.y) >
|
|
241
|
+
if (Math.abs(_extensionPoint4.y - _lastPoint.y) > tolerance) {
|
|
208
242
|
route.push(new Point(_extendX2, _lastPoint.y)); // Corner point
|
|
209
243
|
}
|
|
210
244
|
route.push(_extensionPoint4); // Extension point (fixed distance)
|
|
211
245
|
console.log('[getManHattanPath] Inserted target extension (left):', "(".concat(_extendX2, ", ").concat(targetAnchor.y, ")"));
|
|
212
246
|
} else if (_onRight3) {
|
|
213
|
-
// Anchor on right edge - extend right by
|
|
214
|
-
var _extendX3 = targetAnchor.x +
|
|
247
|
+
// Anchor on right edge - extend right by step + borderRadius
|
|
248
|
+
var _extendX3 = targetAnchor.x + _extensionDistance;
|
|
215
249
|
var _extensionPoint5 = new Point(_extendX3, targetAnchor.y);
|
|
216
|
-
if (Math.abs(_extensionPoint5.y - _lastPoint.y) >
|
|
250
|
+
if (Math.abs(_extensionPoint5.y - _lastPoint.y) > tolerance) {
|
|
217
251
|
route.push(new Point(_extendX3, _lastPoint.y));
|
|
218
252
|
}
|
|
219
253
|
route.push(_extensionPoint5);
|
|
220
254
|
console.log('[getManHattanPath] Inserted target extension (right):', "(".concat(_extendX3, ", ").concat(targetAnchor.y, ")"));
|
|
221
255
|
} else if (_onTop3) {
|
|
222
|
-
// Anchor on top edge - extend up by
|
|
223
|
-
var _extendY2 = targetAnchor.y -
|
|
256
|
+
// Anchor on top edge - extend up by step + borderRadius
|
|
257
|
+
var _extendY2 = targetAnchor.y - _extensionDistance;
|
|
224
258
|
var _extensionPoint6 = new Point(targetAnchor.x, _extendY2);
|
|
225
|
-
if (Math.abs(_extensionPoint6.x - _lastPoint.x) >
|
|
259
|
+
if (Math.abs(_extensionPoint6.x - _lastPoint.x) > tolerance) {
|
|
226
260
|
route.push(new Point(_lastPoint.x, _extendY2));
|
|
227
261
|
}
|
|
228
262
|
route.push(_extensionPoint6);
|
|
229
263
|
console.log('[getManHattanPath] Inserted target extension (up):', "(".concat(targetAnchor.x, ", ").concat(_extendY2, ")"));
|
|
230
264
|
} else if (_onBottom3) {
|
|
231
|
-
// Anchor on bottom edge - extend down by
|
|
232
|
-
var _extendY3 = targetAnchor.y +
|
|
265
|
+
// Anchor on bottom edge - extend down by step + borderRadius
|
|
266
|
+
var _extendY3 = targetAnchor.y + _extensionDistance;
|
|
233
267
|
var _extensionPoint7 = new Point(targetAnchor.x, _extendY3);
|
|
234
|
-
if (Math.abs(_extensionPoint7.x - _lastPoint.x) >
|
|
268
|
+
if (Math.abs(_extensionPoint7.x - _lastPoint.x) > tolerance) {
|
|
235
269
|
route.push(new Point(_lastPoint.x, _extendY3));
|
|
236
270
|
}
|
|
237
271
|
route.push(_extensionPoint7);
|
|
@@ -260,32 +294,4 @@ export function getManHattanPath(params) {
|
|
|
260
294
|
|
|
261
295
|
// Convert to SVG path string
|
|
262
296
|
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
297
|
}
|
package/esm/options/defaults.js
CHANGED
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,CA2CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
|
|
@@ -0,0 +1,82 @@
|
|
|
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 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) {
|
|
40
|
+
var bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
|
|
41
|
+
var by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
|
|
42
|
+
points.push(new Point(bx, by));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
_iterator.e(err);
|
|
50
|
+
} finally {
|
|
51
|
+
_iterator.f();
|
|
52
|
+
}
|
|
53
|
+
return points;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Simplify path by removing collinear intermediate points
|
|
58
|
+
*/
|
|
59
|
+
export function simplifyPath(points) {
|
|
60
|
+
if (points.length <= 2) {
|
|
61
|
+
return points;
|
|
62
|
+
}
|
|
63
|
+
var simplified = [points[0]];
|
|
64
|
+
for (var i = 1; i < points.length - 1; i++) {
|
|
65
|
+
var prev = simplified[simplified.length - 1];
|
|
66
|
+
var current = points[i];
|
|
67
|
+
var next = points[i + 1];
|
|
68
|
+
|
|
69
|
+
// Check if current point is collinear with prev and next
|
|
70
|
+
var isHorizontalLine = prev.y === current.y && current.y === next.y;
|
|
71
|
+
var isVerticalLine = prev.x === current.x && current.x === next.x;
|
|
72
|
+
|
|
73
|
+
// Only keep the point if it's not collinear (i.e., it's a corner)
|
|
74
|
+
if (!isHorizontalLine && !isVerticalLine) {
|
|
75
|
+
simplified.push(current);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Always add the last point
|
|
80
|
+
simplified.push(points[points.length - 1]);
|
|
81
|
+
return simplified;
|
|
82
|
+
}
|