@rxflow/manhattan 0.0.1-alpha.11 → 0.0.1-alpha.13

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 (49) hide show
  1. package/cjs/geometry/Rectangle.d.ts +1 -1
  2. package/cjs/geometry/Rectangle.js +2 -2
  3. package/cjs/getManHattanPath.d.ts.map +1 -1
  4. package/cjs/getManHattanPath.js +153 -20
  5. package/cjs/obstacle/ObstacleMap.d.ts +7 -1
  6. package/cjs/obstacle/ObstacleMap.d.ts.map +1 -1
  7. package/cjs/obstacle/ObstacleMap.js +53 -1
  8. package/cjs/options/defaults.d.ts +1 -1
  9. package/cjs/options/defaults.d.ts.map +1 -1
  10. package/cjs/options/defaults.js +1 -1
  11. package/cjs/options/resolver.d.ts.map +1 -1
  12. package/cjs/options/resolver.js +4 -2
  13. package/cjs/options/types.d.ts +19 -6
  14. package/cjs/options/types.d.ts.map +1 -1
  15. package/cjs/pathfinder/findRoute.d.ts.map +1 -1
  16. package/cjs/pathfinder/findRoute.js +239 -15
  17. package/cjs/svg/pathConverter.d.ts.map +1 -1
  18. package/cjs/svg/pathConverter.js +23 -12
  19. package/cjs/utils/getAnchorPoints.d.ts +15 -0
  20. package/cjs/utils/getAnchorPoints.d.ts.map +1 -0
  21. package/cjs/utils/getAnchorPoints.js +75 -0
  22. package/cjs/utils/index.d.ts +1 -0
  23. package/cjs/utils/index.d.ts.map +1 -1
  24. package/cjs/utils/index.js +11 -0
  25. package/esm/geometry/Rectangle.d.ts +1 -1
  26. package/esm/geometry/Rectangle.js +2 -2
  27. package/esm/getManHattanPath.d.ts.map +1 -1
  28. package/esm/getManHattanPath.js +162 -22
  29. package/esm/obstacle/ObstacleMap.d.ts +7 -1
  30. package/esm/obstacle/ObstacleMap.d.ts.map +1 -1
  31. package/esm/obstacle/ObstacleMap.js +78 -0
  32. package/esm/options/defaults.d.ts +1 -1
  33. package/esm/options/defaults.d.ts.map +1 -1
  34. package/esm/options/defaults.js +1 -1
  35. package/esm/options/resolver.d.ts.map +1 -1
  36. package/esm/options/resolver.js +5 -3
  37. package/esm/options/types.d.ts +19 -6
  38. package/esm/options/types.d.ts.map +1 -1
  39. package/esm/pathfinder/findRoute.d.ts.map +1 -1
  40. package/esm/pathfinder/findRoute.js +262 -23
  41. package/esm/svg/pathConverter.d.ts.map +1 -1
  42. package/esm/svg/pathConverter.js +23 -12
  43. package/esm/utils/getAnchorPoints.d.ts +15 -0
  44. package/esm/utils/getAnchorPoints.d.ts.map +1 -0
  45. package/esm/utils/getAnchorPoints.js +69 -0
  46. package/esm/utils/index.d.ts +1 -0
  47. package/esm/utils/index.d.ts.map +1 -1
  48. package/esm/utils/index.js +2 -1
  49. package/package.json +1 -1
@@ -4,8 +4,162 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.findRoute = findRoute;
7
+ var _geometry = require("../geometry");
7
8
  var _SortedSet = require("./SortedSet");
8
9
  var _utils = require("../utils");
10
+ /**
11
+ * Find the closest accessible point in a given direction using step-based search with linear refinement
12
+ *
13
+ * Algorithm:
14
+ * 1. Try moving by step increments (step, 2*step, 3*step...) until finding an accessible point
15
+ * 2. Once found, linearly search backward from (n-1)*step+1 to n*step to find the closest accessible point
16
+ * 3. Returns null if no accessible point found within maxSteps
17
+ */
18
+ function findAccessibleNeighbor(from, directionX, directionY, step, map, precision, maxSteps = 20) {
19
+ // Normalize direction
20
+ const length = Math.sqrt(directionX * directionX + directionY * directionY);
21
+ const ndx = directionX / length;
22
+ const ndy = directionY / length;
23
+
24
+ // 1. Try the first step directly (most common case)
25
+ const firstStepPoint = new _geometry.Point(from.x + ndx * step, from.y + ndy * step).round(precision);
26
+ if (map.isAccessible(firstStepPoint)) {
27
+ // First step is accessible, no need for refinement
28
+ return firstStepPoint;
29
+ }
30
+
31
+ // 2. First step blocked, search outward by step increments
32
+ let stepMultiplier = 2;
33
+ let foundAccessibleDistance = -1;
34
+ while (stepMultiplier <= maxSteps) {
35
+ const distance = stepMultiplier * step;
36
+ const testPoint = new _geometry.Point(from.x + ndx * distance, from.y + ndy * distance).round(precision);
37
+ if (map.isAccessible(testPoint)) {
38
+ foundAccessibleDistance = distance;
39
+ break;
40
+ }
41
+ stepMultiplier++;
42
+ }
43
+ if (foundAccessibleDistance < 0) {
44
+ return null; // No accessible point found
45
+ }
46
+
47
+ // 3. Linear search within the last step interval to find closest accessible point
48
+ // Search from (n-1)*step+1 to n*step
49
+ const outerDistance = foundAccessibleDistance;
50
+ const innerDistance = foundAccessibleDistance - step;
51
+
52
+ // Search backward from outer to inner to find the closest accessible point
53
+ for (let dist = innerDistance + 1; dist < outerDistance; dist++) {
54
+ const testPoint = new _geometry.Point(from.x + ndx * dist, from.y + ndy * dist).round(precision);
55
+ if (map.isAccessible(testPoint)) {
56
+ return testPoint;
57
+ }
58
+ }
59
+
60
+ // If no closer point found, return the outer distance point
61
+ return new _geometry.Point(from.x + ndx * outerDistance, from.y + ndy * outerDistance).round(precision);
62
+ }
63
+
64
+ /**
65
+ * Generate smart points based on position using extensionDistance and binary search
66
+ *
67
+ * Algorithm:
68
+ * 1. First try extensionDistance in the specified direction
69
+ * 2. If blocked, use binary search starting from step, halving until finding accessible point
70
+ * 3. For target points, approach from opposite direction
71
+ */
72
+ function generateSmartPoints(anchor, bbox, position, grid, map, options, isTarget = false) {
73
+ const directionMap = {
74
+ 'right': {
75
+ x: 1,
76
+ y: 0
77
+ },
78
+ 'left': {
79
+ x: -1,
80
+ y: 0
81
+ },
82
+ 'top': {
83
+ x: 0,
84
+ y: -1
85
+ },
86
+ 'bottom': {
87
+ x: 0,
88
+ y: 1
89
+ }
90
+ };
91
+ const direction = directionMap[position];
92
+ if (!direction) {
93
+ console.warn(`[generateSmartPoints] Unknown position: ${position}, falling back to anchor`);
94
+ return [anchor];
95
+ }
96
+
97
+ // Both source and target extend in the specified direction
98
+ // - Source: extends away from node in sourcePosition direction
99
+ // - Target: extends away from node in targetPosition direction (path approaches from this direction)
100
+ const actualDirection = direction;
101
+ const points = [];
102
+
103
+ // 1. First try extensionDistance
104
+ const extensionPoint = new _geometry.Point(anchor.x + actualDirection.x * options.extensionDistance, anchor.y + actualDirection.y * options.extensionDistance).round(options.precision);
105
+ console.log(`[generateSmartPoints] ${isTarget ? 'Target' : 'Source'} position=${position}, trying extension point: (${extensionPoint.x}, ${extensionPoint.y})`);
106
+ if (map.isAccessible(extensionPoint)) {
107
+ points.push(extensionPoint);
108
+ console.log(`[generateSmartPoints] Extension point is accessible`);
109
+ return points;
110
+ }
111
+ console.log(`[generateSmartPoints] Extension point blocked, using step-based search`);
112
+
113
+ // 2. Step-based search with binary refinement
114
+ // First, extend outward by step increments until we find an accessible point
115
+ let stepMultiplier = 1;
116
+ let maxSteps = 20; // Prevent infinite loop
117
+ let foundAccessibleDistance = -1;
118
+ console.log(`[generateSmartPoints] Starting outward search with step=${options.step}`);
119
+ while (stepMultiplier <= maxSteps) {
120
+ const distance = stepMultiplier * options.step;
121
+ const testPoint = new _geometry.Point(anchor.x + actualDirection.x * distance, anchor.y + actualDirection.y * distance).round(options.precision);
122
+ console.log(`[generateSmartPoints] Testing ${stepMultiplier}*step (distance=${distance}): (${testPoint.x}, ${testPoint.y})`);
123
+ if (map.isAccessible(testPoint)) {
124
+ foundAccessibleDistance = distance;
125
+ console.log(`[generateSmartPoints] Found accessible point at ${stepMultiplier}*step (distance=${distance})`);
126
+ break;
127
+ }
128
+ stepMultiplier++;
129
+ }
130
+
131
+ // 3. If we found an accessible point, refine by linear search within the last step interval
132
+ if (foundAccessibleDistance > 0) {
133
+ const outerDistance = foundAccessibleDistance;
134
+ const innerDistance = foundAccessibleDistance - options.step;
135
+ console.log(`[generateSmartPoints] Refining between ${innerDistance} and ${outerDistance}`);
136
+
137
+ // Linear search from innerDistance+1 to outerDistance to find the closest accessible point
138
+ let bestDistance = outerDistance;
139
+
140
+ // Search forward from inner to outer to find the first accessible point
141
+ for (let dist = innerDistance + 1; dist < outerDistance; dist++) {
142
+ const testPoint = new _geometry.Point(anchor.x + actualDirection.x * dist, anchor.y + actualDirection.y * dist).round(options.precision);
143
+ console.log(`[generateSmartPoints] Linear search testing distance ${dist}: (${testPoint.x}, ${testPoint.y})`);
144
+ if (map.isAccessible(testPoint)) {
145
+ bestDistance = dist;
146
+ console.log(`[generateSmartPoints] Found closest accessible point at distance ${dist}`);
147
+ break;
148
+ }
149
+ }
150
+
151
+ // Use the best distance found
152
+ const finalPoint = new _geometry.Point(anchor.x + actualDirection.x * bestDistance, anchor.y + actualDirection.y * bestDistance).round(options.precision);
153
+ points.push(finalPoint);
154
+ console.log(`[generateSmartPoints] Final point at distance ${bestDistance}: (${finalPoint.x}, ${finalPoint.y})`);
155
+ } else {
156
+ // 4. If no accessible point found after maxSteps, use anchor as fallback
157
+ console.log(`[generateSmartPoints] No accessible point found after ${maxSteps} steps, using anchor: (${anchor.x}, ${anchor.y})`);
158
+ points.push(anchor);
159
+ }
160
+ return points;
161
+ }
162
+
9
163
  /**
10
164
  * Find route between two points using A* algorithm
11
165
  */
@@ -24,24 +178,41 @@ function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, map, opti
24
178
  const endPoint = targetEndpoint;
25
179
 
26
180
  // Get start and end points around rectangles
27
- let startPoints = (0, _utils.getRectPoints)(startPoint, sourceBBox, options.startDirections, grid, options);
28
- let endPoints = (0, _utils.getRectPoints)(targetEndpoint, targetBBox, options.endDirections, grid, options);
29
- console.log('[findRoute] Start points from getRectPoints:', startPoints.map(p => `(${p.x}, ${p.y})`));
30
- console.log('[findRoute] End points from getRectPoints:', endPoints.map(p => `(${p.x}, ${p.y})`));
31
-
32
- // Take into account only accessible rect points
33
- startPoints = startPoints.filter(p => map.isAccessible(p));
34
- endPoints = endPoints.filter(p => map.isAccessible(p));
181
+ // Use smart point generation based on position if available
182
+ let startPoints;
183
+ let endPoints;
184
+
185
+ // Generate smart start points based on sourcePosition
186
+ if (options.sourcePosition) {
187
+ startPoints = generateSmartPoints(startPoint, sourceBBox, options.sourcePosition, grid, map, options, false);
188
+ console.log('[findRoute] Start points from smart generation:', startPoints.map(p => `(${p.x}, ${p.y})`));
189
+ } else {
190
+ startPoints = (0, _utils.getRectPoints)(startPoint, sourceBBox, options.startDirections, grid, options);
191
+ console.log('[findRoute] Start points from getRectPoints:', startPoints.map(p => `(${p.x}, ${p.y})`));
192
+ // Take into account only accessible rect points
193
+ startPoints = startPoints.filter(p => map.isAccessible(p));
194
+ }
195
+
196
+ // Generate smart end points based on targetPosition
197
+ if (options.targetPosition) {
198
+ endPoints = generateSmartPoints(targetEndpoint, targetBBox, options.targetPosition, grid, map, options, true);
199
+ console.log('[findRoute] End points from smart generation:', endPoints.map(p => `(${p.x}, ${p.y})`));
200
+ } else {
201
+ endPoints = (0, _utils.getRectPoints)(targetEndpoint, targetBBox, options.endDirections, grid, options);
202
+ console.log('[findRoute] End points from getRectPoints:', endPoints.map(p => `(${p.x}, ${p.y})`));
203
+ // Take into account only accessible rect points
204
+ endPoints = endPoints.filter(p => map.isAccessible(p));
205
+ }
35
206
  console.log('[findRoute] Start points after filter:', startPoints.map(p => `(${p.x}, ${p.y})`));
36
207
  console.log('[findRoute] End points after filter:', endPoints.map(p => `(${p.x}, ${p.y})`));
37
208
 
38
209
  // Ensure we always have at least the anchor points
39
210
  // This handles edge cases where anchor is on the node boundary
40
211
  if (startPoints.length === 0) {
41
- startPoints = [(0, _utils.align)(startPoint, grid, precision)];
212
+ startPoints = [(0, _utils.round)(startPoint, precision)];
42
213
  }
43
214
  if (endPoints.length === 0) {
44
- endPoints = [(0, _utils.align)(endPoint, grid, precision)];
215
+ endPoints = [(0, _utils.round)(endPoint, precision)];
45
216
  }
46
217
 
47
218
  // Initialize A* data structures
@@ -111,16 +282,69 @@ function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, map, opti
111
282
  continue;
112
283
  }
113
284
 
114
- // Calculate neighbor point
115
- const neighborPoint = (0, _utils.align)(currentPoint.clone().translate(direction.gridOffsetX || 0, direction.gridOffsetY || 0), grid, precision);
285
+ // Find the closest accessible neighbor in this direction using binary search
286
+ const neighborPoint = findAccessibleNeighbor(currentPoint, direction.offsetX, direction.offsetY, options.step, map, precision);
287
+
288
+ // Skip if no accessible neighbor found in this direction
289
+ if (!neighborPoint) {
290
+ continue;
291
+ }
116
292
  const neighborKey = (0, _utils.getKey)(neighborPoint);
117
293
 
118
- // Skip if closed or not accessible
119
- if (openSet.isClose(neighborKey) || !map.isAccessible(neighborPoint)) {
294
+ // Skip if already closed
295
+ if (openSet.isClose(neighborKey)) {
120
296
  continue;
121
297
  }
122
298
 
123
- // Check if neighbor is an end point
299
+ // Check if we can reach any end point directly from this neighbor
300
+ // This allows connecting to end points that are not on the grid
301
+ let canReachEndPoint = false;
302
+ let reachableEndPoint = null;
303
+ for (const endPt of endPoints) {
304
+ const distanceToEnd = neighborPoint.manhattanDistance(endPt);
305
+
306
+ // If close enough to end point (within step distance), try direct connection
307
+ if (distanceToEnd < options.step * 1.5) {
308
+ // Check if we can move directly to the end point
309
+ const dx = endPt.x - neighborPoint.x;
310
+ const dy = endPt.y - neighborPoint.y;
311
+
312
+ // Allow direct connection if it's orthogonal or close to orthogonal
313
+ const isOrthogonal = Math.abs(dx) < 0.1 || Math.abs(dy) < 0.1;
314
+ if (isOrthogonal && map.isAccessible(endPt)) {
315
+ canReachEndPoint = true;
316
+ reachableEndPoint = endPt;
317
+ break;
318
+ }
319
+ }
320
+ }
321
+
322
+ // If we can reach an end point directly, add it as the final step
323
+ if (canReachEndPoint && reachableEndPoint) {
324
+ const endKey = (0, _utils.getKey)(reachableEndPoint);
325
+ const endCost = neighborPoint.manhattanDistance(reachableEndPoint);
326
+ const totalCost = currentCost + direction.cost + endCost;
327
+ if (!openSet.isOpen(endKey) || totalCost < (costs.get(endKey) || Infinity)) {
328
+ points.set(endKey, reachableEndPoint);
329
+ parents.set(endKey, neighborPoint);
330
+ costs.set(endKey, totalCost);
331
+
332
+ // Also add the neighbor point if not already added
333
+ if (!points.has(neighborKey)) {
334
+ points.set(neighborKey, neighborPoint);
335
+ parents.set(neighborKey, currentPoint);
336
+ costs.set(neighborKey, currentCost + direction.cost);
337
+ }
338
+
339
+ // Check if this is our target end point
340
+ if (endPointsKeys.has(endKey)) {
341
+ options.previousDirectionAngle = directionAngle;
342
+ return (0, _utils.reconstructRoute)(parents, points, reachableEndPoint, startPoint, endPoint);
343
+ }
344
+ }
345
+ }
346
+
347
+ // Check if neighbor is an end point (exact match)
124
348
  if (endPointsKeys.has(neighborKey)) {
125
349
  const isEndPoint = neighborPoint.equals(endPoint);
126
350
  if (!isEndPoint) {
@@ -1 +1 @@
1
- {"version":3,"file":"pathConverter.d.ts","sourceRoot":"","sources":["../../src/svg/pathConverter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAE,MAAU,GAAG,MAAM,CAgEjG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE,CAgCzE"}
1
+ {"version":3,"file":"pathConverter.d.ts","sourceRoot":"","sources":["../../src/svg/pathConverter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAE,MAAU,GAAG,MAAM,CA2EjG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE,CAgCzE"}
@@ -36,31 +36,42 @@ function pointsToPath(points, precision, borderRadius = 0) {
36
36
  const current = points[i];
37
37
  const next = points[i + 1];
38
38
 
39
- // Calculate the distance from prev to current and current to next
40
- const distToPrev = Math.sqrt(Math.pow(current.x - prev.x, 2) + Math.pow(current.y - prev.y, 2));
41
- const distToNext = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
39
+ // Calculate direction vectors
40
+ const dx1 = current.x - prev.x;
41
+ const dy1 = current.y - prev.y;
42
+ const dx2 = next.x - current.x;
43
+ const dy2 = next.y - current.y;
44
+
45
+ // Calculate distances
46
+ const dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
47
+ const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
42
48
 
43
49
  // Use the smaller of borderRadius or half the segment length
44
- const radius = Math.min(borderRadius, distToPrev / 2, distToNext / 2);
50
+ const radius = Math.min(borderRadius, dist1 / 2, dist2 / 2);
51
+
52
+ // Normalize direction vectors
53
+ const ndx1 = dx1 / dist1;
54
+ const ndy1 = dy1 / dist1;
55
+ const ndx2 = dx2 / dist2;
56
+ const ndy2 = dy2 / dist2;
45
57
 
46
- // Calculate the point before the corner (on the line from prev to current)
47
- const beforeCornerRatio = (distToPrev - radius) / distToPrev;
58
+ // Calculate the point before the corner
48
59
  const beforeCorner = {
49
- x: prev.x + (current.x - prev.x) * beforeCornerRatio,
50
- y: prev.y + (current.y - prev.y) * beforeCornerRatio
60
+ x: current.x - ndx1 * radius,
61
+ y: current.y - ndy1 * radius
51
62
  };
52
63
 
53
- // Calculate the point after the corner (on the line from current to next)
54
- const afterCornerRatio = radius / distToNext;
64
+ // Calculate the point after the corner
55
65
  const afterCorner = {
56
- x: current.x + (next.x - current.x) * afterCornerRatio,
57
- y: current.y + (next.y - current.y) * afterCornerRatio
66
+ x: current.x + ndx2 * radius,
67
+ y: current.y + ndy2 * radius
58
68
  };
59
69
 
60
70
  // Draw line to the point before corner
61
71
  path += ` L ${roundCoord(beforeCorner.x)} ${roundCoord(beforeCorner.y)}`;
62
72
 
63
73
  // Draw quadratic bezier curve for the rounded corner
74
+ // The control point is the actual corner point
64
75
  path += ` Q ${roundCoord(current.x)} ${roundCoord(current.y)} ${roundCoord(afterCorner.x)} ${roundCoord(afterCorner.y)}`;
65
76
  }
66
77
 
@@ -0,0 +1,15 @@
1
+ import { Point } from '../geometry';
2
+ import type { ObstacleMap } from '../obstacle';
3
+ import type { Direction } from '../options';
4
+ /**
5
+ * Get accessible anchor points using binary search optimization
6
+ *
7
+ * @param anchor - The anchor point (on node edge)
8
+ * @param position - The position/direction (right, left, top, bottom)
9
+ * @param extensionDistance - The preferred extension distance
10
+ * @param step - The step size for binary search
11
+ * @param obstacleMap - The obstacle map for accessibility checking
12
+ * @returns Array of accessible points, prioritized by distance
13
+ */
14
+ export declare function getAnchorPoints(anchor: Point, position: Direction, extensionDistance: number, step: number, obstacleMap: ObstacleMap): Point[];
15
+ //# sourceMappingURL=getAnchorPoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAnchorPoints.d.ts","sourceRoot":"","sources":["../../src/utils/getAnchorPoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,KAAK,EACb,QAAQ,EAAE,SAAS,EACnB,iBAAiB,EAAE,MAAM,EACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,GACvB,KAAK,EAAE,CA0DT"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getAnchorPoints = getAnchorPoints;
7
+ var _geometry = require("../geometry");
8
+ /**
9
+ * Get accessible anchor points using binary search optimization
10
+ *
11
+ * @param anchor - The anchor point (on node edge)
12
+ * @param position - The position/direction (right, left, top, bottom)
13
+ * @param extensionDistance - The preferred extension distance
14
+ * @param step - The step size for binary search
15
+ * @param obstacleMap - The obstacle map for accessibility checking
16
+ * @returns Array of accessible points, prioritized by distance
17
+ */
18
+ function getAnchorPoints(anchor, position, extensionDistance, step, obstacleMap) {
19
+ const points = [];
20
+
21
+ // Determine direction vector based on position
22
+ const directionMap = {
23
+ 'right': {
24
+ dx: 1,
25
+ dy: 0
26
+ },
27
+ 'left': {
28
+ dx: -1,
29
+ dy: 0
30
+ },
31
+ 'top': {
32
+ dx: 0,
33
+ dy: -1
34
+ },
35
+ 'bottom': {
36
+ dx: 0,
37
+ dy: 1
38
+ }
39
+ };
40
+ const dir = directionMap[position];
41
+ if (!dir) {
42
+ console.warn(`[getAnchorPoints] Invalid position: ${position}`);
43
+ return [anchor];
44
+ }
45
+ console.log(`[getAnchorPoints] Finding points for position '${position}' from (${anchor.x}, ${anchor.y})`);
46
+
47
+ // 1. First try extensionDistance
48
+ const extensionPoint = new _geometry.Point(anchor.x + dir.dx * extensionDistance, anchor.y + dir.dy * extensionDistance);
49
+ if (obstacleMap.isAccessible(extensionPoint)) {
50
+ console.log(`[getAnchorPoints] Extension point (${extensionPoint.x}, ${extensionPoint.y}) is accessible`);
51
+ points.push(extensionPoint);
52
+ return points;
53
+ }
54
+ console.log(`[getAnchorPoints] Extension point (${extensionPoint.x}, ${extensionPoint.y}) is blocked, trying binary search`);
55
+
56
+ // 2. If extensionDistance point is blocked, use binary search with step
57
+ // Try: step -> step/2 -> step/4 -> ... -> 1px
58
+ let distance = step;
59
+ while (distance >= 1) {
60
+ const testPoint = new _geometry.Point(anchor.x + dir.dx * distance, anchor.y + dir.dy * distance);
61
+ if (obstacleMap.isAccessible(testPoint)) {
62
+ console.log(`[getAnchorPoints] Found accessible point at distance ${distance}px: (${testPoint.x}, ${testPoint.y})`);
63
+ points.push(testPoint);
64
+ return points;
65
+ }
66
+
67
+ // Halve the distance for next iteration
68
+ distance = Math.floor(distance / 2);
69
+ }
70
+
71
+ // 3. If still no accessible point found, return the anchor itself
72
+ console.warn(`[getAnchorPoints] No accessible point found, using anchor itself: (${anchor.x}, ${anchor.y})`);
73
+ points.push(anchor);
74
+ return points;
75
+ }
@@ -4,4 +4,5 @@ export * from './rect';
4
4
  export * from './route';
5
5
  export * from './node';
6
6
  export * from './pathValidation';
7
+ export * from './getAnchorPoints';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA"}
@@ -68,4 +68,15 @@ Object.keys(_pathValidation).forEach(function (key) {
68
68
  return _pathValidation[key];
69
69
  }
70
70
  });
71
+ });
72
+ var _getAnchorPoints = require("./getAnchorPoints");
73
+ Object.keys(_getAnchorPoints).forEach(function (key) {
74
+ if (key === "default" || key === "__esModule") return;
75
+ if (key in exports && exports[key] === _getAnchorPoints[key]) return;
76
+ Object.defineProperty(exports, key, {
77
+ enumerable: true,
78
+ get: function () {
79
+ return _getAnchorPoints[key];
80
+ }
81
+ });
71
82
  });
@@ -25,7 +25,7 @@ export declare class Rectangle {
25
25
  */
26
26
  getCorner(): Point;
27
27
  /**
28
- * Check if a point is contained within this rectangle
28
+ * Check if a point is contained within this rectangle (interior only, excluding edges)
29
29
  */
30
30
  containsPoint(point: Point): boolean;
31
31
  /**
@@ -60,12 +60,12 @@ export var Rectangle = /*#__PURE__*/function () {
60
60
  }
61
61
 
62
62
  /**
63
- * Check if a point is contained within this rectangle
63
+ * Check if a point is contained within this rectangle (interior only, excluding edges)
64
64
  */
65
65
  }, {
66
66
  key: "containsPoint",
67
67
  value: function containsPoint(point) {
68
- return point.x >= this.x && point.x <= this.x + this.width && point.y >= this.y && point.y <= this.y + this.height;
68
+ return point.x > this.x && point.x < this.x + this.width && point.y > this.y && point.y < this.y + this.height;
69
69
  }
70
70
 
71
71
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAE,MAAM,eAAe,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAMnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAEhB,cAAc,EAAE,QAAQ,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC;IACzB;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAwRvE"}
1
+ {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAE,MAAM,eAAe,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAa,MAAM,WAAW,CAAA;AAM9E;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAEhB,cAAc,EAAE,QAAQ,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC;IACzB;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAgbvE"}