@rxflow/manhattan 0.0.2-alpha.8 → 0.0.3

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 (116) hide show
  1. package/README.md +214 -10
  2. package/cjs/getManHattanPath.d.ts.map +1 -1
  3. package/cjs/getManHattanPath.js +77 -46
  4. package/cjs/obstacle/ObstacleMap.d.ts +41 -9
  5. package/cjs/obstacle/ObstacleMap.d.ts.map +1 -1
  6. package/cjs/obstacle/ObstacleMap.js +201 -96
  7. package/cjs/obstacle/QuadTree.d.ts +119 -0
  8. package/cjs/obstacle/QuadTree.d.ts.map +1 -0
  9. package/cjs/obstacle/QuadTree.js +334 -0
  10. package/cjs/options/defaults.d.ts +1 -1
  11. package/cjs/options/defaults.d.ts.map +1 -1
  12. package/cjs/options/resolver.d.ts.map +1 -1
  13. package/cjs/options/resolver.js +145 -17
  14. package/cjs/options/types.d.ts +41 -0
  15. package/cjs/options/types.d.ts.map +1 -1
  16. package/cjs/pathfinder/PathCache.d.ts +92 -0
  17. package/cjs/pathfinder/PathCache.d.ts.map +1 -0
  18. package/cjs/pathfinder/PathCache.js +249 -0
  19. package/cjs/pathfinder/findRoute.d.ts.map +1 -1
  20. package/cjs/pathfinder/findRoute.js +96 -31
  21. package/cjs/pathfinder/index.d.ts +1 -0
  22. package/cjs/pathfinder/index.d.ts.map +1 -1
  23. package/cjs/pathfinder/index.js +26 -1
  24. package/cjs/svg/pathConverter.d.ts +13 -0
  25. package/cjs/svg/pathConverter.d.ts.map +1 -1
  26. package/cjs/svg/pathConverter.js +170 -1
  27. package/cjs/utils/AdaptiveStepCalculator.d.ts +90 -0
  28. package/cjs/utils/AdaptiveStepCalculator.d.ts.map +1 -0
  29. package/cjs/utils/AdaptiveStepCalculator.js +224 -0
  30. package/cjs/utils/ErrorRecovery.d.ts +182 -0
  31. package/cjs/utils/ErrorRecovery.d.ts.map +1 -0
  32. package/cjs/utils/ErrorRecovery.js +413 -0
  33. package/cjs/utils/GlobalGrid.d.ts +99 -0
  34. package/cjs/utils/GlobalGrid.d.ts.map +1 -0
  35. package/cjs/utils/GlobalGrid.js +224 -0
  36. package/cjs/utils/PerformanceMonitor.d.ts +139 -0
  37. package/cjs/utils/PerformanceMonitor.d.ts.map +1 -0
  38. package/cjs/utils/PerformanceMonitor.js +305 -0
  39. package/cjs/utils/getAnchorPoints.d.ts.map +1 -1
  40. package/cjs/utils/getAnchorPoints.js +0 -4
  41. package/cjs/utils/grid.d.ts +15 -0
  42. package/cjs/utils/grid.d.ts.map +1 -1
  43. package/cjs/utils/grid.js +19 -12
  44. package/cjs/utils/heuristics.d.ts +61 -0
  45. package/cjs/utils/heuristics.d.ts.map +1 -0
  46. package/cjs/utils/heuristics.js +141 -0
  47. package/cjs/utils/index.d.ts +6 -0
  48. package/cjs/utils/index.d.ts.map +1 -1
  49. package/cjs/utils/index.js +66 -0
  50. package/cjs/utils/pathProcessing.d.ts +45 -0
  51. package/cjs/utils/pathProcessing.d.ts.map +1 -0
  52. package/cjs/utils/pathProcessing.js +270 -0
  53. package/cjs/utils/pathValidation.d.ts.map +1 -1
  54. package/cjs/utils/pathValidation.js +0 -1
  55. package/cjs/utils/rect.d.ts.map +1 -1
  56. package/cjs/utils/rect.js +7 -0
  57. package/cjs/utils/route.d.ts.map +1 -1
  58. package/cjs/utils/route.js +18 -2
  59. package/esm/getManHattanPath.d.ts.map +1 -1
  60. package/esm/getManHattanPath.js +92 -69
  61. package/esm/obstacle/ObstacleMap.d.ts +41 -9
  62. package/esm/obstacle/ObstacleMap.d.ts.map +1 -1
  63. package/esm/obstacle/ObstacleMap.js +218 -99
  64. package/esm/obstacle/QuadTree.d.ts +119 -0
  65. package/esm/obstacle/QuadTree.d.ts.map +1 -0
  66. package/esm/obstacle/QuadTree.js +488 -0
  67. package/esm/options/defaults.d.ts +1 -1
  68. package/esm/options/defaults.d.ts.map +1 -1
  69. package/esm/options/resolver.d.ts.map +1 -1
  70. package/esm/options/resolver.js +147 -18
  71. package/esm/options/types.d.ts +41 -0
  72. package/esm/options/types.d.ts.map +1 -1
  73. package/esm/pathfinder/PathCache.d.ts +92 -0
  74. package/esm/pathfinder/PathCache.d.ts.map +1 -0
  75. package/esm/pathfinder/PathCache.js +278 -0
  76. package/esm/pathfinder/findRoute.d.ts.map +1 -1
  77. package/esm/pathfinder/findRoute.js +98 -44
  78. package/esm/pathfinder/index.d.ts +1 -0
  79. package/esm/pathfinder/index.d.ts.map +1 -1
  80. package/esm/pathfinder/index.js +2 -1
  81. package/esm/svg/pathConverter.d.ts +13 -0
  82. package/esm/svg/pathConverter.d.ts.map +1 -1
  83. package/esm/svg/pathConverter.js +170 -1
  84. package/esm/utils/AdaptiveStepCalculator.d.ts +90 -0
  85. package/esm/utils/AdaptiveStepCalculator.d.ts.map +1 -0
  86. package/esm/utils/AdaptiveStepCalculator.js +252 -0
  87. package/esm/utils/ErrorRecovery.d.ts +182 -0
  88. package/esm/utils/ErrorRecovery.d.ts.map +1 -0
  89. package/esm/utils/ErrorRecovery.js +499 -0
  90. package/esm/utils/GlobalGrid.d.ts +99 -0
  91. package/esm/utils/GlobalGrid.d.ts.map +1 -0
  92. package/esm/utils/GlobalGrid.js +259 -0
  93. package/esm/utils/PerformanceMonitor.d.ts +139 -0
  94. package/esm/utils/PerformanceMonitor.d.ts.map +1 -0
  95. package/esm/utils/PerformanceMonitor.js +360 -0
  96. package/esm/utils/getAnchorPoints.d.ts.map +1 -1
  97. package/esm/utils/getAnchorPoints.js +0 -4
  98. package/esm/utils/grid.d.ts +15 -0
  99. package/esm/utils/grid.d.ts.map +1 -1
  100. package/esm/utils/grid.js +18 -13
  101. package/esm/utils/heuristics.d.ts +61 -0
  102. package/esm/utils/heuristics.d.ts.map +1 -0
  103. package/esm/utils/heuristics.js +144 -0
  104. package/esm/utils/index.d.ts +6 -0
  105. package/esm/utils/index.d.ts.map +1 -1
  106. package/esm/utils/index.js +7 -1
  107. package/esm/utils/pathProcessing.d.ts +45 -0
  108. package/esm/utils/pathProcessing.d.ts.map +1 -0
  109. package/esm/utils/pathProcessing.js +270 -0
  110. package/esm/utils/pathValidation.d.ts.map +1 -1
  111. package/esm/utils/pathValidation.js +0 -1
  112. package/esm/utils/rect.d.ts.map +1 -1
  113. package/esm/utils/rect.js +11 -4
  114. package/esm/utils/route.d.ts.map +1 -1
  115. package/esm/utils/route.js +18 -2
  116. package/package.json +10 -2
@@ -14,13 +14,13 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
14
14
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
15
15
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
16
16
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
17
- import { getSmoothStepPath } from '@xyflow/react';
17
+ import { getSmoothStepPath, Position } from '@xyflow/react';
18
18
  import { Point, Rectangle } from "./geometry";
19
19
  import { ObstacleMap } from "./obstacle";
20
20
  import { resolveOptions } from "./options";
21
21
  import { findRoute } from "./pathfinder";
22
22
  import { pointsToPath, parseSVGPath } from "./svg";
23
- import { getNodeDimensions, getNodePosition, pathIntersectsObstacles } from "./utils";
23
+ import { getNodeDimensions, getNodePosition, pathIntersectsObstacles, ErrorRecovery } from "./utils";
24
24
 
25
25
  /**
26
26
  * Parameters for getManHattanPath function
@@ -78,10 +78,12 @@ export function getManHattanPath(params) {
78
78
  var targetNode = nodeLookup.get(targetNodeId);
79
79
  if (!sourceNode || !targetNode) {
80
80
  // Fallback to simple straight line if nodes not found
81
- console.warn('Source or target node not found in nodeLookup');
81
+ console.warn('[getManHattanPath] Source or target node not found in nodeLookup');
82
82
  var start = new Point(sourceX, sourceY);
83
83
  var end = new Point(targetX, targetY);
84
- return pointsToPath([start, end], options.precision);
84
+ // Use ErrorRecovery to generate a proper fallback path
85
+ var fallbackPath = ErrorRecovery.generateFallbackPath(start, end);
86
+ return pointsToPath(fallbackPath, options.precision, options.borderRadius);
85
87
  }
86
88
 
87
89
  // Get node dimensions using ReactFlow's priority logic
@@ -118,10 +120,8 @@ export function getManHattanPath(params) {
118
120
 
119
121
  // Check if smooth step path intersects with any obstacles
120
122
  if (smoothStepPoints.length > 0 && !pathIntersectsObstacles(smoothStepPoints, nodeLookup)) {
121
- console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
122
123
  return smoothStepPath;
123
124
  }
124
- console.log('[getManHattanPath] SmoothStepPath intersects obstacles, using Manhattan routing');
125
125
 
126
126
  // Build obstacle map with anchor information
127
127
  var obstacleMap = new ObstacleMap(options).build(nodeLookup, sourceNodeId, targetNodeId, sourceAnchor, targetAnchor);
@@ -129,27 +129,80 @@ export function getManHattanPath(params) {
129
129
  // Find route
130
130
  var route = findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, obstacleMap, options);
131
131
 
132
- // Fallback to straight line if no route found
132
+ // Fallback to Z-shaped path if no route found
133
133
  if (!route) {
134
- console.warn('Unable to find Manhattan route, using straight line fallback');
135
- route = [sourceAnchor, targetAnchor];
134
+ console.warn('[getManHattanPath] Unable to find Manhattan route, using fallback path');
135
+ route = ErrorRecovery.generateFallbackPath(sourceAnchor, targetAnchor);
136
136
  }
137
- console.log('[getManHattanPath] Route from findRoute:', route.map(function (p) {
138
- return "(".concat(p.x, ", ").concat(p.y, ")");
139
- }));
140
- console.log('[getManHattanPath] Source anchor:', "(".concat(sourceAnchor.x, ", ").concat(sourceAnchor.y, ")"));
141
- console.log('[getManHattanPath] Target anchor:', "(".concat(targetAnchor.x, ", ").concat(targetAnchor.y, ")"));
142
137
 
143
138
  // If using smart point generation (sourcePosition/targetPosition specified),
144
139
  // the route already contains the correct extension points, so skip manual processing
145
140
  var useSmartPoints = sourcePosition || targetPosition;
146
141
  if (useSmartPoints) {
147
- console.log('[getManHattanPath] Using smart points, skipping manual extension point processing');
142
+ // Post-process route to fix X coordinates for horizontal anchors
143
+ // This ensures the extension distance is fixed and not affected by grid alignment
144
+ var isSourceHorizontal = sourcePosition === Position.Left || sourcePosition === Position.Right;
145
+ var isTargetHorizontal = targetPosition === Position.Left || targetPosition === Position.Right;
146
+ if (isSourceHorizontal && isTargetHorizontal && route.length > 0) {
147
+ // Calculate expected extension X coordinates
148
+ var sourceExtensionX = sourcePosition === Position.Right ? sourceAnchor.x + options.extensionDistance : sourceAnchor.x - options.extensionDistance;
149
+ var targetExtensionX = targetPosition === Position.Left ? targetAnchor.x - options.extensionDistance : targetAnchor.x + options.extensionDistance;
150
+
151
+ // Find the horizontal segment (where Y stays constant but X changes significantly)
152
+ // This divides the path into source-side and target-side
153
+ var horizontalSegmentStart = -1;
154
+ var horizontalSegmentEnd = -1;
155
+ for (var _i = 0; _i < route.length - 1; _i++) {
156
+ var curr = route[_i];
157
+ var next = route[_i + 1];
158
+ var isHorizontalMove = Math.abs(curr.y - next.y) < 1 && Math.abs(curr.x - next.x) > options.step;
159
+ if (isHorizontalMove) {
160
+ horizontalSegmentStart = _i;
161
+ horizontalSegmentEnd = _i + 1;
162
+ break;
163
+ }
164
+ }
165
+
166
+ // Fix route points
167
+ for (var _i2 = 0; _i2 < route.length; _i2++) {
168
+ var point = route[_i2];
169
+ if (horizontalSegmentStart >= 0) {
170
+ if (_i2 <= horizontalSegmentStart) {
171
+ // Source side - all vertical segment points should use sourceExtensionX
172
+ route[_i2] = new Point(sourceExtensionX, point.y);
173
+ } else if (_i2 >= horizontalSegmentEnd) {
174
+ // Target side - all vertical segment points should use targetExtensionX
175
+ route[_i2] = new Point(targetExtensionX, point.y);
176
+ }
177
+ }
178
+ }
179
+
180
+ // Remove redundant points (consecutive points on same line)
181
+ var optimized = [];
182
+ for (var _i3 = 0; _i3 < route.length; _i3++) {
183
+ var _point = route[_i3];
184
+ if (optimized.length < 2) {
185
+ optimized.push(_point);
186
+ } else {
187
+ var prev = optimized[optimized.length - 1];
188
+ var prevPrev = optimized[optimized.length - 2];
189
+
190
+ // Check if prev is on the same line as prevPrev and point
191
+ var sameX = Math.abs(prevPrev.x - prev.x) < 1 && Math.abs(prev.x - _point.x) < 1;
192
+ var sameY = Math.abs(prevPrev.y - prev.y) < 1 && Math.abs(prev.y - _point.y) < 1;
193
+ if (sameX || sameY) {
194
+ // prev is redundant, replace it with current point
195
+ optimized[optimized.length - 1] = _point;
196
+ } else {
197
+ optimized.push(_point);
198
+ }
199
+ }
200
+ }
201
+ route = optimized;
202
+ }
203
+
148
204
  // Add source and target anchors to route
149
205
  var _finalRoute = [sourceAnchor].concat(_toConsumableArray(route), [targetAnchor]);
150
- console.log('[getManHattanPath] Final route:', _finalRoute.map(function (p) {
151
- return "(".concat(p.x, ", ").concat(p.y, ")");
152
- }));
153
206
  return pointsToPath(_finalRoute, options.precision, options.borderRadius);
154
207
  }
155
208
 
@@ -172,7 +225,6 @@ export function getManHattanPath(params) {
172
225
  // This is likely an extension point, remove it
173
226
  if (onRight && firstPoint.x > sourceAnchor.x || onLeft && firstPoint.x < sourceAnchor.x || onBottom && firstPoint.y > sourceAnchor.y || onTop && firstPoint.y < sourceAnchor.y) {
174
227
  route.shift();
175
- console.log('[getManHattanPath] Removed extension point from route start');
176
228
  }
177
229
  }
178
230
  }
@@ -191,7 +243,6 @@ export function getManHattanPath(params) {
191
243
  // This is likely an extension point, remove it
192
244
  if (_onLeft && lastPoint.x < targetAnchor.x || _onRight && lastPoint.x > targetAnchor.x || _onTop && lastPoint.y < targetAnchor.y || _onBottom && lastPoint.y > targetAnchor.y) {
193
245
  route.pop();
194
- console.log('[getManHattanPath] Removed extension point from route end');
195
246
  }
196
247
  }
197
248
  }
@@ -217,7 +268,6 @@ export function getManHattanPath(params) {
217
268
  route.unshift(new Point(extendX, _firstPoint.y)); // Corner point
218
269
  }
219
270
  route.unshift(extensionPoint); // Extension point (fixed distance)
220
- console.log('[getManHattanPath] Inserted source extension (right):', "(".concat(extendX, ", ").concat(sourceAnchor.y, ")"));
221
271
  } else if (_onLeft2) {
222
272
  // Anchor on left edge - extend left by step + borderRadius
223
273
  var _extendX = sourceAnchor.x - extensionDistance;
@@ -226,7 +276,6 @@ export function getManHattanPath(params) {
226
276
  route.unshift(new Point(_extendX, _firstPoint.y));
227
277
  }
228
278
  route.unshift(_extensionPoint);
229
- console.log('[getManHattanPath] Inserted source extension (left):', "(".concat(_extendX, ", ").concat(sourceAnchor.y, ")"));
230
279
  } else if (_onBottom2) {
231
280
  // Anchor on bottom edge - extend down by step + borderRadius
232
281
  var extendY = sourceAnchor.y + extensionDistance;
@@ -235,7 +284,6 @@ export function getManHattanPath(params) {
235
284
  route.unshift(new Point(_firstPoint.x, extendY));
236
285
  }
237
286
  route.unshift(_extensionPoint2);
238
- console.log('[getManHattanPath] Inserted source extension (down):', "(".concat(sourceAnchor.x, ", ").concat(extendY, ")"));
239
287
  } else if (_onTop2) {
240
288
  // Anchor on top edge - extend up by step + borderRadius
241
289
  var _extendY = sourceAnchor.y - extensionDistance;
@@ -244,33 +292,25 @@ export function getManHattanPath(params) {
244
292
  route.unshift(new Point(_firstPoint.x, _extendY));
245
293
  }
246
294
  route.unshift(_extensionPoint3);
247
- console.log('[getManHattanPath] Inserted source extension (up):', "(".concat(sourceAnchor.x, ", ").concat(_extendY, ")"));
248
295
  }
249
296
  }
250
297
 
251
298
  // Remove redundant points after source extension
252
299
  // If the first route point has the same x or y coordinate as the source anchor, it's redundant
253
- if (route.length > 2) {
254
- var firstRoutePoint = route[0]; // Extension point
255
- var secondRoutePoint = route[1]; // Corner point (if exists)
300
+ if (route.length > 3) {
256
301
  var thirdRoutePoint = route[2]; // Original A* point
257
-
258
302
  // Check if the third point (original A* point) is redundant
259
303
  // It's redundant if it's on the same line as the corner point and can be skipped
260
- var sameX = Math.abs(thirdRoutePoint.x - sourceAnchor.x) < tolerance;
261
- var sameY = Math.abs(thirdRoutePoint.y - sourceAnchor.y) < tolerance;
262
- if (sameX || sameY) {
263
- // The third point is aligned with the source anchor, likely redundant
264
- // Check if we can skip it by connecting corner point directly to the next point
265
- if (route.length > 3) {
266
- var fourthPoint = route[3];
267
- // If corner point and fourth point form a straight line, remove the third point
268
- var cornerToThird = Math.abs(secondRoutePoint.x - thirdRoutePoint.x) < tolerance || Math.abs(secondRoutePoint.y - thirdRoutePoint.y) < tolerance;
269
- var thirdToFourth = Math.abs(thirdRoutePoint.x - fourthPoint.x) < tolerance || Math.abs(thirdRoutePoint.y - fourthPoint.y) < tolerance;
270
- if (cornerToThird && thirdToFourth) {
271
- console.log('[getManHattanPath] Removing redundant point:', "(".concat(thirdRoutePoint.x, ", ").concat(thirdRoutePoint.y, ")"));
272
- route.splice(2, 1); // Remove the third point
273
- }
304
+ var _sameX = Math.abs(thirdRoutePoint.x - sourceAnchor.x) < tolerance;
305
+ var _sameY = Math.abs(thirdRoutePoint.y - sourceAnchor.y) < tolerance;
306
+ if (_sameX || _sameY) {
307
+ var secondRoutePoint = route[1]; // Corner point (if exists)
308
+ var fourthPoint = route[3];
309
+ // If corner point and fourth point form a straight line, remove the third point
310
+ var cornerToThird = Math.abs(secondRoutePoint.x - thirdRoutePoint.x) < tolerance || Math.abs(secondRoutePoint.y - thirdRoutePoint.y) < tolerance;
311
+ var thirdToFourth = Math.abs(thirdRoutePoint.x - fourthPoint.x) < tolerance || Math.abs(thirdRoutePoint.y - fourthPoint.y) < tolerance;
312
+ if (cornerToThird && thirdToFourth) {
313
+ route.splice(2, 1); // Remove the third point
274
314
  }
275
315
  }
276
316
  }
@@ -280,16 +320,12 @@ export function getManHattanPath(params) {
280
320
  // where the middle segment goes to target edge and then moves along it
281
321
  // Use target bbox with padding (same as ObstacleMap)
282
322
  var targetBBoxWithPadding = targetBBox.moveAndExpand(options.paddingBox);
283
- console.log('[getManHattanPath] Route before zigzag check:', route.map(function (p) {
284
- return "(".concat(p.x, ", ").concat(p.y, ")");
285
- }));
286
- console.log('[getManHattanPath] Target BBox with padding:', "x=".concat(targetBBoxWithPadding.x, ", y=").concat(targetBBoxWithPadding.y));
287
323
  if (route.length >= 3) {
288
- var _i = 0;
289
- while (_i < route.length - 2) {
290
- var p1 = route[_i];
291
- var p2 = route[_i + 1];
292
- var p3 = route[_i + 2];
324
+ var _i4 = 0;
325
+ while (_i4 < route.length - 2) {
326
+ var p1 = route[_i4];
327
+ var p2 = route[_i4 + 1];
328
+ var p3 = route[_i4 + 2];
293
329
 
294
330
  // Check if p2 is on the target bbox edge (with padding)
295
331
  var p2OnTargetLeftEdge = Math.abs(p2.x - targetBBoxWithPadding.x) < tolerance;
@@ -297,22 +333,19 @@ export function getManHattanPath(params) {
297
333
  var p2OnTargetTopEdge = Math.abs(p2.y - targetBBoxWithPadding.y) < tolerance;
298
334
  var p2OnTargetBottomEdge = Math.abs(p2.y - (targetBBoxWithPadding.y + targetBBoxWithPadding.height)) < tolerance;
299
335
  var p2OnTargetEdge = p2OnTargetLeftEdge || p2OnTargetRightEdge || p2OnTargetTopEdge || p2OnTargetBottomEdge;
300
- console.log("[getManHattanPath] Checking i=".concat(_i, ": p2=(").concat(p2.x, ", ").concat(p2.y, "), onEdge=").concat(p2OnTargetEdge));
301
336
  if (p2OnTargetEdge) {
302
337
  // Check if p1 -> p2 -> p3 forms a zigzag
303
338
  var p1ToP2Horizontal = Math.abs(p1.y - p2.y) < tolerance;
304
339
  var p2ToP3Vertical = Math.abs(p2.x - p3.x) < tolerance;
305
340
  var p1ToP2Vertical = Math.abs(p1.x - p2.x) < tolerance;
306
341
  var p2ToP3Horizontal = Math.abs(p2.y - p3.y) < tolerance;
307
- console.log("[getManHattanPath] Zigzag pattern: H->V=".concat(p1ToP2Horizontal && p2ToP3Vertical, ", V->H=").concat(p1ToP2Vertical && p2ToP3Horizontal));
308
342
  if (p1ToP2Horizontal && p2ToP3Vertical || p1ToP2Vertical && p2ToP3Horizontal) {
309
343
  // We have a zigzag at target edge, remove p2 and p3
310
- console.log('[getManHattanPath] Removing zigzag at target edge:', "(".concat(p2.x, ", ").concat(p2.y, ")"), "and (".concat(p3.x, ", ").concat(p3.y, ")"));
311
- route.splice(_i + 1, 2); // Remove p2 and p3
344
+ route.splice(_i4 + 1, 2); // Remove p2 and p3
312
345
  continue;
313
346
  }
314
347
  }
315
- _i++;
348
+ _i4++;
316
349
  }
317
350
  }
318
351
 
@@ -336,7 +369,6 @@ export function getManHattanPath(params) {
336
369
  route.push(new Point(_extendX2, _lastPoint.y)); // Corner point
337
370
  }
338
371
  route.push(_extensionPoint4); // Extension point (fixed distance)
339
- console.log('[getManHattanPath] Inserted target extension (left):', "(".concat(_extendX2, ", ").concat(targetAnchor.y, ")"));
340
372
  } else if (_onRight3) {
341
373
  // Anchor on right edge - extend right by step + borderRadius
342
374
  var _extendX3 = targetAnchor.x + _extensionDistance;
@@ -345,7 +377,6 @@ export function getManHattanPath(params) {
345
377
  route.push(new Point(_extendX3, _lastPoint.y));
346
378
  }
347
379
  route.push(_extensionPoint5);
348
- console.log('[getManHattanPath] Inserted target extension (right):', "(".concat(_extendX3, ", ").concat(targetAnchor.y, ")"));
349
380
  } else if (_onTop3) {
350
381
  // Anchor on top edge - extend up by step + borderRadius
351
382
  var _extendY2 = targetAnchor.y - _extensionDistance;
@@ -354,7 +385,6 @@ export function getManHattanPath(params) {
354
385
  route.push(new Point(_lastPoint.x, _extendY2));
355
386
  }
356
387
  route.push(_extensionPoint6);
357
- console.log('[getManHattanPath] Inserted target extension (up):', "(".concat(targetAnchor.x, ", ").concat(_extendY2, ")"));
358
388
  } else if (_onBottom3) {
359
389
  // Anchor on bottom edge - extend down by step + borderRadius
360
390
  var _extendY3 = targetAnchor.y + _extensionDistance;
@@ -363,7 +393,6 @@ export function getManHattanPath(params) {
363
393
  route.push(new Point(_lastPoint.x, _extendY3));
364
394
  }
365
395
  route.push(_extensionPoint7);
366
- console.log('[getManHattanPath] Inserted target extension (down):', "(".concat(targetAnchor.x, ", ").concat(_extendY3, ")"));
367
396
  }
368
397
  }
369
398
 
@@ -371,20 +400,18 @@ export function getManHattanPath(params) {
371
400
  // Similar logic for target side
372
401
  if (route.length > 2) {
373
402
  var lastIdx = route.length - 1;
374
- var lastRoutePoint = route[lastIdx]; // Extension point
375
403
  var secondLastPoint = route[lastIdx - 1]; // Corner point (if exists)
376
404
  var thirdLastPoint = route[lastIdx - 2]; // Original A* point
377
405
 
378
406
  // Check if the third-to-last point is redundant
379
- var _sameX = Math.abs(thirdLastPoint.x - targetAnchor.x) < tolerance;
380
- var _sameY = Math.abs(thirdLastPoint.y - targetAnchor.y) < tolerance;
381
- if (_sameX || _sameY) {
407
+ var _sameX2 = Math.abs(thirdLastPoint.x - targetAnchor.x) < tolerance;
408
+ var _sameY2 = Math.abs(thirdLastPoint.y - targetAnchor.y) < tolerance;
409
+ if (_sameX2 || _sameY2) {
382
410
  if (route.length > 3) {
383
411
  var fourthLastPoint = route[lastIdx - 3];
384
412
  var fourthToThird = Math.abs(fourthLastPoint.x - thirdLastPoint.x) < tolerance || Math.abs(fourthLastPoint.y - thirdLastPoint.y) < tolerance;
385
413
  var thirdToSecond = Math.abs(thirdLastPoint.x - secondLastPoint.x) < tolerance || Math.abs(thirdLastPoint.y - secondLastPoint.y) < tolerance;
386
414
  if (fourthToThird && thirdToSecond) {
387
- console.log('[getManHattanPath] Removing redundant point:', "(".concat(thirdLastPoint.x, ", ").concat(thirdLastPoint.y, ")"));
388
415
  route.splice(lastIdx - 2, 1); // Remove the third-to-last point
389
416
  }
390
417
  }
@@ -417,7 +444,6 @@ export function getManHattanPath(params) {
417
444
  var p3ToP4Horizontal = Math.abs(_p3.y - p4.y) < tolerance;
418
445
  if (p3ToP4Horizontal) {
419
446
  // Pattern: horizontal -> vertical -> horizontal (zigzag)
420
- console.log('[getManHattanPath] Removing zigzag at target edge:', "(".concat(_p2.x, ", ").concat(_p2.y, ")"), "and (".concat(_p3.x, ", ").concat(_p3.y, ")"));
421
447
  route.splice(i + 1, 2); // Remove p2 and p3
422
448
  continue;
423
449
  }
@@ -428,9 +454,6 @@ export function getManHattanPath(params) {
428
454
 
429
455
  // Add source and target anchors to route
430
456
  var finalRoute = [sourceAnchor].concat(_toConsumableArray(route), [targetAnchor]);
431
- console.log('[getManHattanPath] Final route:', finalRoute.map(function (p) {
432
- return "(".concat(p.x, ", ").concat(p.y, ")");
433
- }));
434
457
 
435
458
  // Convert to SVG path string
436
459
  return pointsToPath(finalRoute, options.precision, options.borderRadius);
@@ -2,29 +2,61 @@ import { Point } from '../geometry';
2
2
  import type { ResolvedOptions, NodeLookup } from '../options';
3
3
  /**
4
4
  * ObstacleMap class for managing obstacles in pathfinding
5
- * Uses a grid-based spatial partitioning for efficient queries
5
+ * Feature: manhattan-optimization
6
+ *
7
+ * Uses QuadTree for efficient spatial queries (O(log n) instead of O(n))
8
+ * Includes query caching for repeated accessibility checks
6
9
  */
7
10
  export declare class ObstacleMap {
8
11
  private options;
9
- private mapGridSize;
10
- private map;
12
+ private quadTree;
13
+ private obstacles;
11
14
  private sourceAnchor?;
12
15
  private targetAnchor?;
16
+ private accessibilityCache;
17
+ private cacheHits;
18
+ private cacheMisses;
13
19
  constructor(options: ResolvedOptions);
14
20
  /**
15
- * Build obstacle map from node lookup
21
+ * Get cache statistics for performance monitoring
16
22
  */
17
- build(nodeLookup: NodeLookup, sourceNodeId: string, targetNodeId: string, sourceAnchor?: Point, targetAnchor?: Point): ObstacleMap;
23
+ getCacheStats(): {
24
+ hits: number;
25
+ misses: number;
26
+ hitRate: number;
27
+ };
28
+ /**
29
+ * Clear the accessibility cache
30
+ */
31
+ clearCache(): void;
18
32
  /**
19
- * Shrink bbox to exclude the area around the anchor point
20
- * This allows paths to start/end at the anchor but prevents crossing the node
33
+ * Build obstacle map from node lookup using QuadTree
21
34
  */
22
- private shrinkBBoxAroundAnchor;
35
+ build(nodeLookup: NodeLookup, sourceNodeId: string, targetNodeId: string, sourceAnchor?: Point, targetAnchor?: Point): ObstacleMap;
23
36
  /**
24
37
  * Check if a point is accessible (not inside any obstacle)
25
- * Uses binary search optimization: step -> step/2 -> step/4 -> ... -> 1px
38
+ * Uses QuadTree for efficient spatial queries with caching
26
39
  */
27
40
  isAccessible(point: Point, checkRadius?: number): boolean;
41
+ /**
42
+ * Check if an anchor point has sufficient clearance around it
43
+ * This ensures the path can start/end at the anchor without being blocked
44
+ */
45
+ hasAnchorClearance(anchor: Point, direction: 'top' | 'right' | 'bottom' | 'left'): boolean;
46
+ /**
47
+ * Check accessibility without using cache (for internal use)
48
+ */
49
+ private isAccessibleWithoutCache;
50
+ /**
51
+ * Find the nearest accessible point from an anchor in a given direction
52
+ * Uses binary search for efficiency
53
+ */
54
+ findNearestAccessiblePoint(anchor: Point, direction: 'top' | 'right' | 'bottom' | 'left', maxDistance: number): Point | null;
55
+ /**
56
+ * Batch check accessibility for multiple points (optimized)
57
+ * Returns array of booleans in same order as input points
58
+ */
59
+ areAccessible(points: Point[]): boolean[];
28
60
  /**
29
61
  * Check accessibility using binary search optimization
30
62
  * Tries step -> step/2 -> step/4 -> ... -> 1px
@@ -1 +1 @@
1
- {"version":3,"file":"ObstacleMap.d.ts","sourceRoot":"","sources":["../../src/obstacle/ObstacleMap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG7D;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,YAAY,CAAC,CAAO;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAO;gBAEhB,OAAO,EAAE,eAAe;IAMpC;;OAEG;IACH,KAAK,CACH,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,KAAK,EACpB,YAAY,CAAC,EAAE,KAAK,GACnB,WAAW;IAiEd;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA6D9B;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,GAAE,MAAU,GAAG,OAAO;IAiD5D;;;OAGG;IACH,OAAO,CAAC,4BAA4B;CAqCrC"}
1
+ {"version":3,"file":"ObstacleMap.d.ts","sourceRoot":"","sources":["../../src/obstacle/ObstacleMap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAI7D;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,SAAS,CAAmD;IACpE,OAAO,CAAC,YAAY,CAAC,CAAO;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAO;IAG5B,OAAO,CAAC,kBAAkB,CAAkC;IAC5D,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAY;gBAEnB,OAAO,EAAE,eAAe;IAIpC;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IASlE;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;OAEG;IACH,KAAK,CACH,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,KAAK,EACpB,YAAY,CAAC,EAAE,KAAK,GACnB,WAAW;IA8Cd;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,GAAE,MAAU,GAAG,OAAO;IAoD5D;;;OAGG;IACH,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO;IA0C1F;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAShC;;;OAGG;IACH,0BAA0B,CACxB,MAAM,EAAE,KAAK,EACb,SAAS,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,EAC9C,WAAW,EAAE,MAAM,GAClB,KAAK,GAAG,IAAI;IAyDf;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE;IAIzC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;CAqCrC"}