@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.
Files changed (157) hide show
  1. package/README.md +214 -10
  2. package/esm/getManHattanPath.js +92 -69
  3. package/esm/obstacle/ObstacleMap.js +218 -99
  4. package/esm/obstacle/QuadTree.js +488 -0
  5. package/esm/options/resolver.js +147 -18
  6. package/esm/pathfinder/PathCache.js +278 -0
  7. package/esm/pathfinder/findRoute.js +98 -44
  8. package/esm/pathfinder/index.js +2 -1
  9. package/esm/svg/pathConverter.js +170 -1
  10. package/esm/utils/AdaptiveStepCalculator.js +252 -0
  11. package/esm/utils/ErrorRecovery.js +499 -0
  12. package/esm/utils/GlobalGrid.js +259 -0
  13. package/esm/utils/PerformanceMonitor.js +360 -0
  14. package/esm/utils/getAnchorPoints.js +0 -4
  15. package/esm/utils/grid.js +18 -13
  16. package/esm/utils/heuristics.js +144 -0
  17. package/esm/utils/index.js +7 -1
  18. package/esm/utils/pathProcessing.js +270 -0
  19. package/esm/utils/pathValidation.js +0 -1
  20. package/esm/utils/rect.js +11 -4
  21. package/esm/utils/route.js +18 -2
  22. package/package.json +10 -2
  23. package/cjs/geometry/Line.d.ts +0 -21
  24. package/cjs/geometry/Line.d.ts.map +0 -1
  25. package/cjs/geometry/Line.js +0 -88
  26. package/cjs/geometry/Point.d.ts +0 -49
  27. package/cjs/geometry/Point.d.ts.map +0 -1
  28. package/cjs/geometry/Point.js +0 -94
  29. package/cjs/geometry/Rectangle.d.ts +0 -41
  30. package/cjs/geometry/Rectangle.d.ts.map +0 -1
  31. package/cjs/geometry/Rectangle.js +0 -65
  32. package/cjs/geometry/collision.d.ts +0 -15
  33. package/cjs/geometry/collision.d.ts.map +0 -1
  34. package/cjs/geometry/collision.js +0 -81
  35. package/cjs/geometry/index.d.ts +0 -5
  36. package/cjs/geometry/index.d.ts.map +0 -1
  37. package/cjs/geometry/index.js +0 -45
  38. package/cjs/getManHattanPath.d.ts +0 -53
  39. package/cjs/getManHattanPath.d.ts.map +0 -1
  40. package/cjs/getManHattanPath.js +0 -418
  41. package/cjs/index.d.ts +0 -16
  42. package/cjs/index.d.ts.map +0 -1
  43. package/cjs/index.js +0 -117
  44. package/cjs/obstacle/ObstacleMap.d.ts +0 -34
  45. package/cjs/obstacle/ObstacleMap.d.ts.map +0 -1
  46. package/cjs/obstacle/ObstacleMap.js +0 -223
  47. package/cjs/obstacle/index.d.ts +0 -2
  48. package/cjs/obstacle/index.d.ts.map +0 -1
  49. package/cjs/obstacle/index.js +0 -12
  50. package/cjs/options/defaults.d.ts +0 -16
  51. package/cjs/options/defaults.d.ts.map +0 -1
  52. package/cjs/options/defaults.js +0 -39
  53. package/cjs/options/index.d.ts +0 -4
  54. package/cjs/options/index.d.ts.map +0 -1
  55. package/cjs/options/index.js +0 -38
  56. package/cjs/options/resolver.d.ts +0 -10
  57. package/cjs/options/resolver.d.ts.map +0 -1
  58. package/cjs/options/resolver.js +0 -120
  59. package/cjs/options/types.d.ts +0 -169
  60. package/cjs/options/types.d.ts.map +0 -1
  61. package/cjs/options/types.js +0 -5
  62. package/cjs/pathfinder/SortedSet.d.ts +0 -35
  63. package/cjs/pathfinder/SortedSet.d.ts.map +0 -1
  64. package/cjs/pathfinder/SortedSet.js +0 -95
  65. package/cjs/pathfinder/findRoute.d.ts +0 -8
  66. package/cjs/pathfinder/findRoute.d.ts.map +0 -1
  67. package/cjs/pathfinder/findRoute.js +0 -330
  68. package/cjs/pathfinder/index.d.ts +0 -3
  69. package/cjs/pathfinder/index.d.ts.map +0 -1
  70. package/cjs/pathfinder/index.js +0 -19
  71. package/cjs/svg/index.d.ts +0 -3
  72. package/cjs/svg/index.d.ts.map +0 -1
  73. package/cjs/svg/index.js +0 -31
  74. package/cjs/svg/pathConverter.d.ts +0 -10
  75. package/cjs/svg/pathConverter.d.ts.map +0 -1
  76. package/cjs/svg/pathConverter.js +0 -116
  77. package/cjs/svg/pathParser.d.ts +0 -11
  78. package/cjs/svg/pathParser.d.ts.map +0 -1
  79. package/cjs/svg/pathParser.js +0 -76
  80. package/cjs/utils/direction.d.ts +0 -24
  81. package/cjs/utils/direction.d.ts.map +0 -1
  82. package/cjs/utils/direction.js +0 -54
  83. package/cjs/utils/getAnchorPoints.d.ts +0 -15
  84. package/cjs/utils/getAnchorPoints.d.ts.map +0 -1
  85. package/cjs/utils/getAnchorPoints.js +0 -75
  86. package/cjs/utils/grid.d.ts +0 -27
  87. package/cjs/utils/grid.d.ts.map +0 -1
  88. package/cjs/utils/grid.js +0 -66
  89. package/cjs/utils/index.d.ts +0 -8
  90. package/cjs/utils/index.d.ts.map +0 -1
  91. package/cjs/utils/index.js +0 -82
  92. package/cjs/utils/node.d.ts +0 -27
  93. package/cjs/utils/node.d.ts.map +0 -1
  94. package/cjs/utils/node.js +0 -36
  95. package/cjs/utils/pathValidation.d.ts +0 -11
  96. package/cjs/utils/pathValidation.d.ts.map +0 -1
  97. package/cjs/utils/pathValidation.js +0 -130
  98. package/cjs/utils/rect.d.ts +0 -9
  99. package/cjs/utils/rect.d.ts.map +0 -1
  100. package/cjs/utils/rect.js +0 -103
  101. package/cjs/utils/route.d.ts +0 -19
  102. package/cjs/utils/route.d.ts.map +0 -1
  103. package/cjs/utils/route.js +0 -76
  104. package/esm/geometry/Line.d.ts +0 -21
  105. package/esm/geometry/Line.d.ts.map +0 -1
  106. package/esm/geometry/Point.d.ts +0 -49
  107. package/esm/geometry/Point.d.ts.map +0 -1
  108. package/esm/geometry/Rectangle.d.ts +0 -41
  109. package/esm/geometry/Rectangle.d.ts.map +0 -1
  110. package/esm/geometry/collision.d.ts +0 -15
  111. package/esm/geometry/collision.d.ts.map +0 -1
  112. package/esm/geometry/index.d.ts +0 -5
  113. package/esm/geometry/index.d.ts.map +0 -1
  114. package/esm/getManHattanPath.d.ts +0 -53
  115. package/esm/getManHattanPath.d.ts.map +0 -1
  116. package/esm/index.d.ts +0 -16
  117. package/esm/index.d.ts.map +0 -1
  118. package/esm/obstacle/ObstacleMap.d.ts +0 -34
  119. package/esm/obstacle/ObstacleMap.d.ts.map +0 -1
  120. package/esm/obstacle/index.d.ts +0 -2
  121. package/esm/obstacle/index.d.ts.map +0 -1
  122. package/esm/options/defaults.d.ts +0 -16
  123. package/esm/options/defaults.d.ts.map +0 -1
  124. package/esm/options/index.d.ts +0 -4
  125. package/esm/options/index.d.ts.map +0 -1
  126. package/esm/options/resolver.d.ts +0 -10
  127. package/esm/options/resolver.d.ts.map +0 -1
  128. package/esm/options/types.d.ts +0 -169
  129. package/esm/options/types.d.ts.map +0 -1
  130. package/esm/pathfinder/SortedSet.d.ts +0 -35
  131. package/esm/pathfinder/SortedSet.d.ts.map +0 -1
  132. package/esm/pathfinder/findRoute.d.ts +0 -8
  133. package/esm/pathfinder/findRoute.d.ts.map +0 -1
  134. package/esm/pathfinder/index.d.ts +0 -3
  135. package/esm/pathfinder/index.d.ts.map +0 -1
  136. package/esm/svg/index.d.ts +0 -3
  137. package/esm/svg/index.d.ts.map +0 -1
  138. package/esm/svg/pathConverter.d.ts +0 -10
  139. package/esm/svg/pathConverter.d.ts.map +0 -1
  140. package/esm/svg/pathParser.d.ts +0 -11
  141. package/esm/svg/pathParser.d.ts.map +0 -1
  142. package/esm/utils/direction.d.ts +0 -24
  143. package/esm/utils/direction.d.ts.map +0 -1
  144. package/esm/utils/getAnchorPoints.d.ts +0 -15
  145. package/esm/utils/getAnchorPoints.d.ts.map +0 -1
  146. package/esm/utils/grid.d.ts +0 -27
  147. package/esm/utils/grid.d.ts.map +0 -1
  148. package/esm/utils/index.d.ts +0 -8
  149. package/esm/utils/index.d.ts.map +0 -1
  150. package/esm/utils/node.d.ts +0 -27
  151. package/esm/utils/node.d.ts.map +0 -1
  152. package/esm/utils/pathValidation.d.ts +0 -11
  153. package/esm/utils/pathValidation.d.ts.map +0 -1
  154. package/esm/utils/rect.d.ts +0 -9
  155. package/esm/utils/rect.d.ts.map +0 -1
  156. package/esm/utils/route.d.ts +0 -19
  157. 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
- endPoints = [round(endPoint, precision)];
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 loopsRemaining = options.maxLoopCount;
226
- while (!openSet.isEmpty() && loopsRemaining > 0) {
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 reconstructRoute(parents, points, currentPoint, startPoint, endPoint);
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 && map.isAccessible(endPt)) {
302
- canReachEndPoint = true;
303
- reachableEndPoint = endPt;
304
- break;
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
- parents.set(endKey, neighborPoint);
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 reconstructRoute(parents, points, reachableEndPoint, startPoint, endPoint);
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
- loopsRemaining -= 1;
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
- return null;
428
+
429
+ // Use ErrorRecovery to generate fallback path
430
+ return ErrorRecovery.generateFallbackPath(startPoint, endPoint);
377
431
  }
@@ -1,2 +1,3 @@
1
1
  export { SortedSet } from "./SortedSet";
2
- export { findRoute } from "./findRoute";
2
+ export { findRoute } from "./findRoute";
3
+ export { PathCache, generateCacheKey, getGlobalPathCache, resetGlobalPathCache } from "./PathCache";
@@ -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 = Math.min(borderRadius, dist1 / 2, dist2 / 2);
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
  }