@rxflow/manhattan 0.0.2-alpha.8 → 0.0.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/README.md +214 -10
- package/esm/getManHattanPath.js +92 -69
- package/esm/obstacle/ObstacleMap.js +218 -99
- package/esm/obstacle/QuadTree.js +488 -0
- package/esm/options/resolver.js +147 -18
- package/esm/pathfinder/PathCache.js +278 -0
- package/esm/pathfinder/findRoute.js +98 -44
- package/esm/pathfinder/index.js +2 -1
- package/esm/svg/pathConverter.js +170 -1
- package/esm/utils/AdaptiveStepCalculator.js +252 -0
- package/esm/utils/ErrorRecovery.js +499 -0
- package/esm/utils/GlobalGrid.js +259 -0
- package/esm/utils/PerformanceMonitor.js +360 -0
- package/esm/utils/getAnchorPoints.js +0 -4
- package/esm/utils/grid.js +18 -13
- package/esm/utils/heuristics.js +144 -0
- package/esm/utils/index.js +7 -1
- package/esm/utils/pathProcessing.js +270 -0
- package/esm/utils/pathValidation.js +0 -1
- package/esm/utils/rect.js +11 -4
- package/esm/utils/route.js +18 -2
- package/package.json +10 -2
- package/cjs/geometry/Line.d.ts +0 -21
- package/cjs/geometry/Line.d.ts.map +0 -1
- package/cjs/geometry/Line.js +0 -88
- package/cjs/geometry/Point.d.ts +0 -49
- package/cjs/geometry/Point.d.ts.map +0 -1
- package/cjs/geometry/Point.js +0 -94
- package/cjs/geometry/Rectangle.d.ts +0 -41
- package/cjs/geometry/Rectangle.d.ts.map +0 -1
- package/cjs/geometry/Rectangle.js +0 -65
- package/cjs/geometry/collision.d.ts +0 -15
- package/cjs/geometry/collision.d.ts.map +0 -1
- package/cjs/geometry/collision.js +0 -81
- package/cjs/geometry/index.d.ts +0 -5
- package/cjs/geometry/index.d.ts.map +0 -1
- package/cjs/geometry/index.js +0 -45
- package/cjs/getManHattanPath.d.ts +0 -53
- package/cjs/getManHattanPath.d.ts.map +0 -1
- package/cjs/getManHattanPath.js +0 -418
- package/cjs/index.d.ts +0 -16
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -117
- package/cjs/obstacle/ObstacleMap.d.ts +0 -34
- package/cjs/obstacle/ObstacleMap.d.ts.map +0 -1
- package/cjs/obstacle/ObstacleMap.js +0 -223
- package/cjs/obstacle/index.d.ts +0 -2
- package/cjs/obstacle/index.d.ts.map +0 -1
- package/cjs/obstacle/index.js +0 -12
- package/cjs/options/defaults.d.ts +0 -16
- package/cjs/options/defaults.d.ts.map +0 -1
- package/cjs/options/defaults.js +0 -39
- package/cjs/options/index.d.ts +0 -4
- package/cjs/options/index.d.ts.map +0 -1
- package/cjs/options/index.js +0 -38
- package/cjs/options/resolver.d.ts +0 -10
- package/cjs/options/resolver.d.ts.map +0 -1
- package/cjs/options/resolver.js +0 -120
- package/cjs/options/types.d.ts +0 -169
- package/cjs/options/types.d.ts.map +0 -1
- package/cjs/options/types.js +0 -5
- package/cjs/pathfinder/SortedSet.d.ts +0 -35
- package/cjs/pathfinder/SortedSet.d.ts.map +0 -1
- package/cjs/pathfinder/SortedSet.js +0 -95
- package/cjs/pathfinder/findRoute.d.ts +0 -8
- package/cjs/pathfinder/findRoute.d.ts.map +0 -1
- package/cjs/pathfinder/findRoute.js +0 -330
- package/cjs/pathfinder/index.d.ts +0 -3
- package/cjs/pathfinder/index.d.ts.map +0 -1
- package/cjs/pathfinder/index.js +0 -19
- package/cjs/svg/index.d.ts +0 -3
- package/cjs/svg/index.d.ts.map +0 -1
- package/cjs/svg/index.js +0 -31
- package/cjs/svg/pathConverter.d.ts +0 -10
- package/cjs/svg/pathConverter.d.ts.map +0 -1
- package/cjs/svg/pathConverter.js +0 -116
- package/cjs/svg/pathParser.d.ts +0 -11
- package/cjs/svg/pathParser.d.ts.map +0 -1
- package/cjs/svg/pathParser.js +0 -76
- package/cjs/utils/direction.d.ts +0 -24
- package/cjs/utils/direction.d.ts.map +0 -1
- package/cjs/utils/direction.js +0 -54
- package/cjs/utils/getAnchorPoints.d.ts +0 -15
- package/cjs/utils/getAnchorPoints.d.ts.map +0 -1
- package/cjs/utils/getAnchorPoints.js +0 -75
- package/cjs/utils/grid.d.ts +0 -27
- package/cjs/utils/grid.d.ts.map +0 -1
- package/cjs/utils/grid.js +0 -66
- package/cjs/utils/index.d.ts +0 -8
- package/cjs/utils/index.d.ts.map +0 -1
- package/cjs/utils/index.js +0 -82
- package/cjs/utils/node.d.ts +0 -27
- package/cjs/utils/node.d.ts.map +0 -1
- package/cjs/utils/node.js +0 -36
- package/cjs/utils/pathValidation.d.ts +0 -11
- package/cjs/utils/pathValidation.d.ts.map +0 -1
- package/cjs/utils/pathValidation.js +0 -130
- package/cjs/utils/rect.d.ts +0 -9
- package/cjs/utils/rect.d.ts.map +0 -1
- package/cjs/utils/rect.js +0 -103
- package/cjs/utils/route.d.ts +0 -19
- package/cjs/utils/route.d.ts.map +0 -1
- package/cjs/utils/route.js +0 -76
- package/esm/geometry/Line.d.ts +0 -21
- package/esm/geometry/Line.d.ts.map +0 -1
- package/esm/geometry/Point.d.ts +0 -49
- package/esm/geometry/Point.d.ts.map +0 -1
- package/esm/geometry/Rectangle.d.ts +0 -41
- package/esm/geometry/Rectangle.d.ts.map +0 -1
- package/esm/geometry/collision.d.ts +0 -15
- package/esm/geometry/collision.d.ts.map +0 -1
- package/esm/geometry/index.d.ts +0 -5
- package/esm/geometry/index.d.ts.map +0 -1
- package/esm/getManHattanPath.d.ts +0 -53
- package/esm/getManHattanPath.d.ts.map +0 -1
- package/esm/index.d.ts +0 -16
- package/esm/index.d.ts.map +0 -1
- package/esm/obstacle/ObstacleMap.d.ts +0 -34
- package/esm/obstacle/ObstacleMap.d.ts.map +0 -1
- package/esm/obstacle/index.d.ts +0 -2
- package/esm/obstacle/index.d.ts.map +0 -1
- package/esm/options/defaults.d.ts +0 -16
- package/esm/options/defaults.d.ts.map +0 -1
- package/esm/options/index.d.ts +0 -4
- package/esm/options/index.d.ts.map +0 -1
- package/esm/options/resolver.d.ts +0 -10
- package/esm/options/resolver.d.ts.map +0 -1
- package/esm/options/types.d.ts +0 -169
- package/esm/options/types.d.ts.map +0 -1
- package/esm/pathfinder/SortedSet.d.ts +0 -35
- package/esm/pathfinder/SortedSet.d.ts.map +0 -1
- package/esm/pathfinder/findRoute.d.ts +0 -8
- package/esm/pathfinder/findRoute.d.ts.map +0 -1
- package/esm/pathfinder/index.d.ts +0 -3
- package/esm/pathfinder/index.d.ts.map +0 -1
- package/esm/svg/index.d.ts +0 -3
- package/esm/svg/index.d.ts.map +0 -1
- package/esm/svg/pathConverter.d.ts +0 -10
- package/esm/svg/pathConverter.d.ts.map +0 -1
- package/esm/svg/pathParser.d.ts +0 -11
- package/esm/svg/pathParser.d.ts.map +0 -1
- package/esm/utils/direction.d.ts +0 -24
- package/esm/utils/direction.d.ts.map +0 -1
- package/esm/utils/getAnchorPoints.d.ts +0 -15
- package/esm/utils/getAnchorPoints.d.ts.map +0 -1
- package/esm/utils/grid.d.ts +0 -27
- package/esm/utils/grid.d.ts.map +0 -1
- package/esm/utils/index.d.ts +0 -8
- package/esm/utils/index.d.ts.map +0 -1
- package/esm/utils/node.d.ts +0 -27
- package/esm/utils/node.d.ts.map +0 -1
- package/esm/utils/pathValidation.d.ts +0 -11
- package/esm/utils/pathValidation.d.ts.map +0 -1
- package/esm/utils/rect.d.ts +0 -9
- package/esm/utils/rect.d.ts.map +0 -1
- package/esm/utils/route.d.ts +0 -19
- package/esm/utils/route.d.ts.map +0 -1
|
@@ -3,7 +3,7 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
|
|
|
3
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
4
|
import { Point } from "../geometry";
|
|
5
5
|
import { SortedSet } from "./SortedSet";
|
|
6
|
-
import { getGrid, round, getDirectionAngle, getDirectionChange, getGridOffsets, getRectPoints, getCost, getKey, reconstructRoute } from "../utils";
|
|
6
|
+
import { getGrid, round, getDirectionAngle, getDirectionChange, getGridOffsets, getRectPoints, getCost, getKey, reconstructRoute, ErrorRecovery } from "../utils";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Generate smart points based on position using extensionDistance and binary search
|
|
@@ -47,27 +47,21 @@ function generateSmartPoints(anchor, bbox, position, grid, map, options) {
|
|
|
47
47
|
|
|
48
48
|
// 1. First try extensionDistance
|
|
49
49
|
var extensionPoint = new Point(anchor.x + actualDirection.x * options.extensionDistance, anchor.y + actualDirection.y * options.extensionDistance).round(options.precision);
|
|
50
|
-
console.log("[generateSmartPoints] ".concat(isTarget ? 'Target' : 'Source', " position=").concat(position, ", trying extension point: (").concat(extensionPoint.x, ", ").concat(extensionPoint.y, ")"));
|
|
51
50
|
if (map.isAccessible(extensionPoint)) {
|
|
52
51
|
points.push(extensionPoint);
|
|
53
|
-
console.log("[generateSmartPoints] Extension point is accessible");
|
|
54
52
|
return points;
|
|
55
53
|
}
|
|
56
|
-
console.log("[generateSmartPoints] Extension point blocked, using step-based search");
|
|
57
54
|
|
|
58
55
|
// 2. Step-based search with binary refinement
|
|
59
56
|
// First, extend outward by step increments until we find an accessible point
|
|
60
57
|
var stepMultiplier = 1;
|
|
61
58
|
var maxSteps = 20; // Prevent infinite loop
|
|
62
59
|
var foundAccessibleDistance = -1;
|
|
63
|
-
console.log("[generateSmartPoints] Starting outward search with step=".concat(options.step));
|
|
64
60
|
while (stepMultiplier <= maxSteps) {
|
|
65
61
|
var distance = stepMultiplier * options.step;
|
|
66
62
|
var testPoint = new Point(anchor.x + actualDirection.x * distance, anchor.y + actualDirection.y * distance).round(options.precision);
|
|
67
|
-
console.log("[generateSmartPoints] Testing ".concat(stepMultiplier, "*step (distance=").concat(distance, "): (").concat(testPoint.x, ", ").concat(testPoint.y, ")"));
|
|
68
63
|
if (map.isAccessible(testPoint)) {
|
|
69
64
|
foundAccessibleDistance = distance;
|
|
70
|
-
console.log("[generateSmartPoints] Found accessible point at ".concat(stepMultiplier, "*step (distance=").concat(distance, ")"));
|
|
71
65
|
break;
|
|
72
66
|
}
|
|
73
67
|
stepMultiplier++;
|
|
@@ -77,35 +71,32 @@ function generateSmartPoints(anchor, bbox, position, grid, map, options) {
|
|
|
77
71
|
if (foundAccessibleDistance > 0) {
|
|
78
72
|
var outerDistance = foundAccessibleDistance;
|
|
79
73
|
var innerDistance = foundAccessibleDistance - options.step;
|
|
80
|
-
console.log("[generateSmartPoints] Refining between ".concat(innerDistance, " and ").concat(outerDistance));
|
|
81
74
|
|
|
82
75
|
// Binary search within the last step interval to find the closest accessible point
|
|
83
76
|
var left = innerDistance;
|
|
84
77
|
var right = outerDistance;
|
|
85
78
|
var bestDistance = outerDistance;
|
|
79
|
+
var binarySearchIterations = 0;
|
|
80
|
+
var maxBinarySearchIterations = 50; // Prevent infinite loop
|
|
86
81
|
|
|
87
82
|
// Binary search with precision of 1px
|
|
88
|
-
while (right - left > 1) {
|
|
83
|
+
while (right - left > 1 && binarySearchIterations < maxBinarySearchIterations) {
|
|
84
|
+
binarySearchIterations++;
|
|
89
85
|
var mid = (left + right) / 2;
|
|
90
86
|
var _testPoint = new Point(anchor.x + actualDirection.x * mid, anchor.y + actualDirection.y * mid).round(options.precision);
|
|
91
|
-
console.log("[generateSmartPoints] Binary search testing distance ".concat(mid.toFixed(1), ": (").concat(_testPoint.x, ", ").concat(_testPoint.y, ")"));
|
|
92
87
|
if (map.isAccessible(_testPoint)) {
|
|
93
88
|
bestDistance = mid;
|
|
94
89
|
right = mid;
|
|
95
|
-
console.log("[generateSmartPoints] Point accessible, searching closer (right=".concat(right, ")"));
|
|
96
90
|
} else {
|
|
97
91
|
left = mid;
|
|
98
|
-
console.log("[generateSmartPoints] Point blocked, searching further (left=".concat(left, ")"));
|
|
99
92
|
}
|
|
100
93
|
}
|
|
101
94
|
|
|
102
95
|
// Use the best distance found
|
|
103
96
|
var finalPoint = new Point(anchor.x + actualDirection.x * bestDistance, anchor.y + actualDirection.y * bestDistance).round(options.precision);
|
|
104
97
|
points.push(finalPoint);
|
|
105
|
-
console.log("[generateSmartPoints] Final point at distance ".concat(bestDistance, ": (").concat(finalPoint.x, ", ").concat(finalPoint.y, ")"));
|
|
106
98
|
} else {
|
|
107
99
|
// 4. If no accessible point found after maxSteps, use anchor as fallback
|
|
108
|
-
console.log("[generateSmartPoints] No accessible point found after ".concat(maxSteps, " steps, using anchor: (").concat(anchor.x, ", ").concat(anchor.y, ")"));
|
|
109
100
|
points.push(anchor);
|
|
110
101
|
}
|
|
111
102
|
return points;
|
|
@@ -115,6 +106,7 @@ function generateSmartPoints(anchor, bbox, position, grid, map, options) {
|
|
|
115
106
|
* Find route between two points using A* algorithm
|
|
116
107
|
*/
|
|
117
108
|
export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, map, options) {
|
|
109
|
+
var _options$performance;
|
|
118
110
|
var precision = options.precision;
|
|
119
111
|
|
|
120
112
|
// Round anchor points
|
|
@@ -124,6 +116,12 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
124
116
|
// Get grid for this route
|
|
125
117
|
var grid = getGrid(options.step, sourceEndpoint, targetEndpoint);
|
|
126
118
|
|
|
119
|
+
// DEBUG: Check for invalid grid values
|
|
120
|
+
if (grid.x === 0 || grid.y === 0 || !isFinite(grid.x) || !isFinite(grid.y)) {
|
|
121
|
+
console.error('[findRoute] INVALID GRID:', grid, 'step:', options.step);
|
|
122
|
+
return ErrorRecovery.generateFallbackPath(sourceEndpoint, targetEndpoint);
|
|
123
|
+
}
|
|
124
|
+
|
|
127
125
|
// Get pathfinding points
|
|
128
126
|
var startPoint = sourceEndpoint;
|
|
129
127
|
var endPoint = targetEndpoint;
|
|
@@ -136,14 +134,8 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
136
134
|
// Generate smart start points based on sourcePosition
|
|
137
135
|
if (options.sourcePosition) {
|
|
138
136
|
startPoints = generateSmartPoints(startPoint, sourceBBox, options.sourcePosition, grid, map, options, false);
|
|
139
|
-
console.log('[findRoute] Start points from smart generation:', startPoints.map(function (p) {
|
|
140
|
-
return "(".concat(p.x, ", ").concat(p.y, ")");
|
|
141
|
-
}));
|
|
142
137
|
} else {
|
|
143
138
|
startPoints = getRectPoints(startPoint, sourceBBox, options.startDirections, grid, options);
|
|
144
|
-
console.log('[findRoute] Start points from getRectPoints:', startPoints.map(function (p) {
|
|
145
|
-
return "(".concat(p.x, ", ").concat(p.y, ")");
|
|
146
|
-
}));
|
|
147
139
|
// Take into account only accessible rect points
|
|
148
140
|
startPoints = startPoints.filter(function (p) {
|
|
149
141
|
return map.isAccessible(p);
|
|
@@ -153,33 +145,24 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
153
145
|
// Generate smart end points based on targetPosition
|
|
154
146
|
if (options.targetPosition) {
|
|
155
147
|
endPoints = generateSmartPoints(targetEndpoint, targetBBox, options.targetPosition, grid, map, options, true);
|
|
156
|
-
console.log('[findRoute] End points from smart generation:', endPoints.map(function (p) {
|
|
157
|
-
return "(".concat(p.x, ", ").concat(p.y, ")");
|
|
158
|
-
}));
|
|
159
148
|
} else {
|
|
160
149
|
endPoints = getRectPoints(targetEndpoint, targetBBox, options.endDirections, grid, options);
|
|
161
|
-
console.log('[findRoute] End points from getRectPoints:', endPoints.map(function (p) {
|
|
162
|
-
return "(".concat(p.x, ", ").concat(p.y, ")");
|
|
163
|
-
}));
|
|
164
150
|
// Take into account only accessible rect points
|
|
165
151
|
endPoints = endPoints.filter(function (p) {
|
|
166
152
|
return map.isAccessible(p);
|
|
167
153
|
});
|
|
168
154
|
}
|
|
169
|
-
console.log('[findRoute] Start points after filter:', startPoints.map(function (p) {
|
|
170
|
-
return "(".concat(p.x, ", ").concat(p.y, ")");
|
|
171
|
-
}));
|
|
172
|
-
console.log('[findRoute] End points after filter:', endPoints.map(function (p) {
|
|
173
|
-
return "(".concat(p.x, ", ").concat(p.y, ")");
|
|
174
|
-
}));
|
|
175
155
|
|
|
176
156
|
// Ensure we always have at least the anchor points
|
|
177
157
|
// This handles edge cases where anchor is on the node boundary
|
|
178
158
|
if (startPoints.length === 0) {
|
|
179
159
|
startPoints = [round(startPoint, precision)];
|
|
180
160
|
}
|
|
161
|
+
|
|
162
|
+
// CRITICAL: If no accessible end points found, use fallback immediately
|
|
163
|
+
// This prevents A* from running 5000 iterations searching for unreachable points
|
|
181
164
|
if (endPoints.length === 0) {
|
|
182
|
-
|
|
165
|
+
return ErrorRecovery.generateFallbackPath(startPoint, endPoint);
|
|
183
166
|
}
|
|
184
167
|
|
|
185
168
|
// Initialize A* data structures
|
|
@@ -221,9 +204,35 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
221
204
|
return sp.equals(endPoints[i]);
|
|
222
205
|
});
|
|
223
206
|
|
|
207
|
+
// Calculate optimal path cost estimate for early termination
|
|
208
|
+
var optimalCostEstimate = startPoint.manhattanDistance(endPoint);
|
|
209
|
+
var earlyTerminationThreshold = (_options$performance = options.performance) !== null && _options$performance !== void 0 && _options$performance.earlyTermination ? 1.5 : Infinity;
|
|
210
|
+
var bestPathFound = null;
|
|
211
|
+
var bestPathCost = Infinity;
|
|
212
|
+
|
|
224
213
|
// Main A* loop
|
|
225
|
-
var
|
|
226
|
-
|
|
214
|
+
var maxIterations = options.maxLoopCount;
|
|
215
|
+
var iterationCount = 0;
|
|
216
|
+
|
|
217
|
+
// DEBUG: Log loop start
|
|
218
|
+
var loopStartTime = performance.now();
|
|
219
|
+
while (!openSet.isEmpty() && iterationCount < maxIterations) {
|
|
220
|
+
iterationCount++;
|
|
221
|
+
|
|
222
|
+
// DEBUG: Log every 10 iterations
|
|
223
|
+
if (iterationCount % 10 === 0) {
|
|
224
|
+
var elapsed = performance.now() - loopStartTime;
|
|
225
|
+
if (elapsed > 1000) {
|
|
226
|
+
console.error("[findRoute] Loop running for ".concat(elapsed.toFixed(0), "ms, iteration ").concat(iterationCount));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Early termination: if we've searched too many points without finding a path,
|
|
231
|
+
// the path is likely blocked or very complex - use fallback
|
|
232
|
+
if (iterationCount > 2000 && bestPathFound === null) {
|
|
233
|
+
return ErrorRecovery.generateFallbackPath(startPoint, endPoint);
|
|
234
|
+
}
|
|
235
|
+
|
|
227
236
|
// Get the closest item and mark it CLOSED
|
|
228
237
|
var currentKey = openSet.pop();
|
|
229
238
|
if (!currentKey) break;
|
|
@@ -248,8 +257,21 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
248
257
|
// Check if we reached any endpoint
|
|
249
258
|
var skipEndCheck = isRouteBeginning && sameStartEndPoints;
|
|
250
259
|
if (!skipEndCheck && endPointsKeys.has(currentKey)) {
|
|
260
|
+
var route = reconstructRoute(parents, points, currentPoint, startPoint, endPoint);
|
|
261
|
+
|
|
262
|
+
// Early termination: if path cost is within threshold of optimal, return immediately
|
|
263
|
+
if (currentCost <= optimalCostEstimate * earlyTerminationThreshold) {
|
|
264
|
+
options.previousDirectionAngle = previousDirectionAngle;
|
|
265
|
+
return route;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Track best path found so far
|
|
269
|
+
if (currentCost < bestPathCost) {
|
|
270
|
+
bestPathCost = currentCost;
|
|
271
|
+
bestPathFound = route;
|
|
272
|
+
}
|
|
251
273
|
options.previousDirectionAngle = previousDirectionAngle;
|
|
252
|
-
return
|
|
274
|
+
return route;
|
|
253
275
|
}
|
|
254
276
|
|
|
255
277
|
// Explore neighbors in all directions
|
|
@@ -298,10 +320,13 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
298
320
|
|
|
299
321
|
// Allow direct connection if it's orthogonal or close to orthogonal
|
|
300
322
|
var isOrthogonal = Math.abs(dx) < 0.1 || Math.abs(dy) < 0.1;
|
|
301
|
-
if (isOrthogonal
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
323
|
+
if (isOrthogonal) {
|
|
324
|
+
var accessible = map.isAccessible(endPt);
|
|
325
|
+
if (accessible) {
|
|
326
|
+
canReachEndPoint = true;
|
|
327
|
+
reachableEndPoint = endPt;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
305
330
|
}
|
|
306
331
|
}
|
|
307
332
|
}
|
|
@@ -318,7 +343,15 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
318
343
|
var totalCost = currentCost + direction.cost + endCost;
|
|
319
344
|
if (!openSet.isOpen(endKey) || totalCost < (costs.get(endKey) || Infinity)) {
|
|
320
345
|
points.set(endKey, reachableEndPoint);
|
|
321
|
-
|
|
346
|
+
|
|
347
|
+
// CRITICAL FIX: Don't set parent if neighbor and endpoint are the same
|
|
348
|
+
// This prevents circular reference in reconstructRoute
|
|
349
|
+
if (!neighborPoint.equals(reachableEndPoint)) {
|
|
350
|
+
parents.set(endKey, neighborPoint);
|
|
351
|
+
} else {
|
|
352
|
+
// If neighbor IS the endpoint, use currentPoint as parent
|
|
353
|
+
parents.set(endKey, currentPoint);
|
|
354
|
+
}
|
|
322
355
|
costs.set(endKey, totalCost);
|
|
323
356
|
|
|
324
357
|
// Also add the neighbor point if not already added
|
|
@@ -330,8 +363,21 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
330
363
|
|
|
331
364
|
// Check if this is our target end point
|
|
332
365
|
if (endPointsKeys.has(endKey)) {
|
|
366
|
+
var _route = reconstructRoute(parents, points, reachableEndPoint, startPoint, endPoint);
|
|
367
|
+
|
|
368
|
+
// Early termination check
|
|
369
|
+
if (totalCost <= optimalCostEstimate * earlyTerminationThreshold) {
|
|
370
|
+
options.previousDirectionAngle = directionAngle;
|
|
371
|
+
return _route;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Track best path
|
|
375
|
+
if (totalCost < bestPathCost) {
|
|
376
|
+
bestPathCost = totalCost;
|
|
377
|
+
bestPathFound = _route;
|
|
378
|
+
}
|
|
333
379
|
options.previousDirectionAngle = directionAngle;
|
|
334
|
-
return
|
|
380
|
+
return _route;
|
|
335
381
|
}
|
|
336
382
|
}
|
|
337
383
|
}
|
|
@@ -361,17 +407,25 @@ export function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, ma
|
|
|
361
407
|
openSet.add(neighborKey, costFromStart + getCost(neighborPoint, endPoints));
|
|
362
408
|
}
|
|
363
409
|
}
|
|
410
|
+
|
|
411
|
+
// Loop completed - check iteration count
|
|
364
412
|
} catch (err) {
|
|
365
413
|
_iterator2.e(err);
|
|
366
414
|
} finally {
|
|
367
415
|
_iterator2.f();
|
|
368
416
|
}
|
|
369
|
-
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Return best path found if any
|
|
420
|
+
if (bestPathFound) {
|
|
421
|
+
return bestPathFound;
|
|
370
422
|
}
|
|
371
423
|
|
|
372
424
|
// No path found, try fallback
|
|
373
425
|
if (options.fallbackRoute) {
|
|
374
426
|
return options.fallbackRoute(startPoint, endPoint);
|
|
375
427
|
}
|
|
376
|
-
|
|
428
|
+
|
|
429
|
+
// Use ErrorRecovery to generate fallback path
|
|
430
|
+
return ErrorRecovery.generateFallbackPath(startPoint, endPoint);
|
|
377
431
|
}
|
package/esm/pathfinder/index.js
CHANGED
package/esm/svg/pathConverter.js
CHANGED
|
@@ -4,6 +4,42 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
|
|
|
4
4
|
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
5
5
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
6
6
|
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; }
|
|
7
|
+
/**
|
|
8
|
+
* Check if three points form a corner (direction change)
|
|
9
|
+
*/
|
|
10
|
+
function isCorner(prev, current, next) {
|
|
11
|
+
var dx1 = current.x - prev.x;
|
|
12
|
+
var dy1 = current.y - prev.y;
|
|
13
|
+
var dx2 = next.x - current.x;
|
|
14
|
+
var dy2 = next.y - current.y;
|
|
15
|
+
|
|
16
|
+
// Check if direction changes (not collinear)
|
|
17
|
+
// For Manhattan paths, this means one segment is horizontal and the other is vertical
|
|
18
|
+
var isHorizontal1 = Math.abs(dy1) < 0.001;
|
|
19
|
+
var isVertical1 = Math.abs(dx1) < 0.001;
|
|
20
|
+
var isHorizontal2 = Math.abs(dy2) < 0.001;
|
|
21
|
+
var isVertical2 = Math.abs(dx2) < 0.001;
|
|
22
|
+
|
|
23
|
+
// Corner exists if one segment is horizontal and the other is vertical
|
|
24
|
+
return isHorizontal1 && isVertical2 || isVertical1 && isHorizontal2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calculate optimal border radius for a corner
|
|
29
|
+
* Ensures the radius doesn't exceed half the length of adjacent segments
|
|
30
|
+
*/
|
|
31
|
+
function calculateOptimalRadius(prev, current, next, maxRadius) {
|
|
32
|
+
var dx1 = current.x - prev.x;
|
|
33
|
+
var dy1 = current.y - prev.y;
|
|
34
|
+
var dx2 = next.x - current.x;
|
|
35
|
+
var dy2 = next.y - current.y;
|
|
36
|
+
var dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
37
|
+
var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
38
|
+
|
|
39
|
+
// Use the smaller of maxRadius or half the segment length
|
|
40
|
+
return Math.min(maxRadius, dist1 / 2, dist2 / 2);
|
|
41
|
+
}
|
|
42
|
+
|
|
7
43
|
/**
|
|
8
44
|
* Convert array of points to SVG path string
|
|
9
45
|
*/
|
|
@@ -38,6 +74,13 @@ export function pointsToPath(points, precision) {
|
|
|
38
74
|
var current = points[_i];
|
|
39
75
|
var next = points[_i + 1];
|
|
40
76
|
|
|
77
|
+
// Check if this is actually a corner
|
|
78
|
+
if (!isCorner(prev, current, next)) {
|
|
79
|
+
// Not a corner, just draw a line to current point
|
|
80
|
+
path += " L ".concat(roundCoord(current.x), " ").concat(roundCoord(current.y));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
41
84
|
// Calculate direction vectors
|
|
42
85
|
var dx1 = current.x - prev.x;
|
|
43
86
|
var dy1 = current.y - prev.y;
|
|
@@ -49,7 +92,13 @@ export function pointsToPath(points, precision) {
|
|
|
49
92
|
var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
50
93
|
|
|
51
94
|
// Use the smaller of borderRadius or half the segment length
|
|
52
|
-
var radius =
|
|
95
|
+
var radius = calculateOptimalRadius(prev, current, next, borderRadius);
|
|
96
|
+
|
|
97
|
+
// Skip if radius is too small
|
|
98
|
+
if (radius < 0.5) {
|
|
99
|
+
path += " L ".concat(roundCoord(current.x), " ").concat(roundCoord(current.y));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
53
102
|
|
|
54
103
|
// Normalize direction vectors
|
|
55
104
|
var ndx1 = dx1 / dist1;
|
|
@@ -83,6 +132,106 @@ export function pointsToPath(points, precision) {
|
|
|
83
132
|
return path;
|
|
84
133
|
}
|
|
85
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Convert array of points to SVG path string with cubic bezier curves
|
|
137
|
+
* Provides smoother corners than quadratic bezier
|
|
138
|
+
*/
|
|
139
|
+
export function pointsToPathCubic(points, precision) {
|
|
140
|
+
var borderRadius = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
141
|
+
if (points.length === 0) {
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
var factor = Math.pow(10, precision);
|
|
145
|
+
var roundCoord = function roundCoord(value) {
|
|
146
|
+
return Math.round(value * factor) / factor;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// If no border radius or only 2 points, use simple line path
|
|
150
|
+
if (borderRadius === 0 || points.length <= 2) {
|
|
151
|
+
var _firstPoint2 = points[0];
|
|
152
|
+
var _path2 = "M ".concat(roundCoord(_firstPoint2.x), " ").concat(roundCoord(_firstPoint2.y));
|
|
153
|
+
for (var i = 1; i < points.length; i++) {
|
|
154
|
+
var point = points[i];
|
|
155
|
+
_path2 += " L ".concat(roundCoord(point.x), " ").concat(roundCoord(point.y));
|
|
156
|
+
}
|
|
157
|
+
return _path2;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Start with M (moveTo) command for first point
|
|
161
|
+
var firstPoint = points[0];
|
|
162
|
+
var path = "M ".concat(roundCoord(firstPoint.x), " ").concat(roundCoord(firstPoint.y));
|
|
163
|
+
|
|
164
|
+
// Process each segment with rounded corners using cubic bezier
|
|
165
|
+
for (var _i2 = 1; _i2 < points.length - 1; _i2++) {
|
|
166
|
+
var prev = points[_i2 - 1];
|
|
167
|
+
var current = points[_i2];
|
|
168
|
+
var next = points[_i2 + 1];
|
|
169
|
+
|
|
170
|
+
// Check if this is actually a corner
|
|
171
|
+
if (!isCorner(prev, current, next)) {
|
|
172
|
+
path += " L ".concat(roundCoord(current.x), " ").concat(roundCoord(current.y));
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Calculate direction vectors
|
|
177
|
+
var dx1 = current.x - prev.x;
|
|
178
|
+
var dy1 = current.y - prev.y;
|
|
179
|
+
var dx2 = next.x - current.x;
|
|
180
|
+
var dy2 = next.y - current.y;
|
|
181
|
+
|
|
182
|
+
// Calculate distances
|
|
183
|
+
var dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
184
|
+
var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
185
|
+
|
|
186
|
+
// Use the smaller of borderRadius or half the segment length
|
|
187
|
+
var radius = calculateOptimalRadius(prev, current, next, borderRadius);
|
|
188
|
+
|
|
189
|
+
// Skip if radius is too small
|
|
190
|
+
if (radius < 0.5) {
|
|
191
|
+
path += " L ".concat(roundCoord(current.x), " ").concat(roundCoord(current.y));
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Normalize direction vectors
|
|
196
|
+
var ndx1 = dx1 / dist1;
|
|
197
|
+
var ndy1 = dy1 / dist1;
|
|
198
|
+
var ndx2 = dx2 / dist2;
|
|
199
|
+
var ndy2 = dy2 / dist2;
|
|
200
|
+
|
|
201
|
+
// Calculate the point before the corner
|
|
202
|
+
var beforeCorner = {
|
|
203
|
+
x: current.x - ndx1 * radius,
|
|
204
|
+
y: current.y - ndy1 * radius
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Calculate the point after the corner
|
|
208
|
+
var afterCorner = {
|
|
209
|
+
x: current.x + ndx2 * radius,
|
|
210
|
+
y: current.y + ndy2 * radius
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Draw line to the point before corner
|
|
214
|
+
path += " L ".concat(roundCoord(beforeCorner.x), " ").concat(roundCoord(beforeCorner.y));
|
|
215
|
+
|
|
216
|
+
// Draw cubic bezier curve for smoother rounded corner
|
|
217
|
+
// Control points are positioned to create a smooth arc
|
|
218
|
+
var cp1 = {
|
|
219
|
+
x: beforeCorner.x + ndx1 * radius * 0.55,
|
|
220
|
+
y: beforeCorner.y + ndy1 * radius * 0.55
|
|
221
|
+
};
|
|
222
|
+
var cp2 = {
|
|
223
|
+
x: afterCorner.x - ndx2 * radius * 0.55,
|
|
224
|
+
y: afterCorner.y - ndy2 * radius * 0.55
|
|
225
|
+
};
|
|
226
|
+
path += " C ".concat(roundCoord(cp1.x), " ").concat(roundCoord(cp1.y), " ").concat(roundCoord(cp2.x), " ").concat(roundCoord(cp2.y), " ").concat(roundCoord(afterCorner.x), " ").concat(roundCoord(afterCorner.y));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Draw line to the last point
|
|
230
|
+
var lastPoint = points[points.length - 1];
|
|
231
|
+
path += " L ".concat(roundCoord(lastPoint.x), " ").concat(roundCoord(lastPoint.y));
|
|
232
|
+
return path;
|
|
233
|
+
}
|
|
234
|
+
|
|
86
235
|
/**
|
|
87
236
|
* Snap path to grid by aligning consecutive points
|
|
88
237
|
*/
|
|
@@ -115,4 +264,24 @@ export function snapPathToGrid(points, gridSize) {
|
|
|
115
264
|
}
|
|
116
265
|
}
|
|
117
266
|
return snappedPoints;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Check if an SVG path contains rounded corners (Q or C commands)
|
|
271
|
+
*/
|
|
272
|
+
export function hasRoundedCorners(path) {
|
|
273
|
+
return /[QC]\s/.test(path);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Count the number of corners in a path
|
|
278
|
+
*/
|
|
279
|
+
export function countCorners(points) {
|
|
280
|
+
var corners = 0;
|
|
281
|
+
for (var i = 1; i < points.length - 1; i++) {
|
|
282
|
+
if (isCorner(points[i - 1], points[i], points[i + 1])) {
|
|
283
|
+
corners++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return corners;
|
|
118
287
|
}
|