@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
package/esm/utils/grid.js CHANGED
@@ -1,31 +1,36 @@
1
1
  import { Point } from "../geometry";
2
+ import { GlobalGrid } from "./GlobalGrid";
2
3
 
3
4
  /**
4
5
  * Grid interface for dynamic grid system
5
6
  */
6
7
 
7
- /**
8
- * Get grid dimension for a single axis
9
- */
10
- function getGridDimension(diff, step) {
11
- // Always return fixed step size to maintain consistent padding
12
- // This ensures paths stay at least 'padding' distance from obstacles
13
- return step;
14
- }
15
-
16
8
  /**
17
9
  * Get grid size in x and y dimensions, adapted to source and target positions
18
10
  * Uses global grid system with origin at (0, 0) for path alignment
11
+ *
12
+ * @param step - The grid step size
13
+ * @param source - Source point (used for reference, not as origin)
14
+ * @param target - Target point (used for reference, not as origin)
15
+ * @returns Grid configuration with global origin
19
16
  */
20
17
  export function getGrid(step, source, target) {
21
18
  return {
22
19
  source: new Point(0, 0),
23
20
  // Use global origin for consistent grid alignment
24
- x: getGridDimension(target.x - source.x, step),
25
- y: getGridDimension(target.y - source.y, step)
21
+ x: step,
22
+ y: step
26
23
  };
27
24
  }
28
25
 
26
+ /**
27
+ * Create a GlobalGrid instance from step size
28
+ * This is the preferred way to create grids for new code
29
+ */
30
+ export function createGlobalGrid(step) {
31
+ return GlobalGrid.uniform(step);
32
+ }
33
+
29
34
  /**
30
35
  * Snap a value to grid
31
36
  */
@@ -34,9 +39,9 @@ export function snapToGrid(value, gridSize) {
34
39
  }
35
40
 
36
41
  /**
37
- * Snap a point to grid
42
+ * Snap a point to grid using global origin
38
43
  */
39
- function snapPointToGrid(point, grid) {
44
+ export function snapPointToGrid(point, grid) {
40
45
  var source = grid.source;
41
46
  var x = snapToGrid(point.x - source.x, grid.x) + source.x;
42
47
  var y = snapToGrid(point.y - source.y, grid.y) + source.y;
@@ -0,0 +1,144 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
4
+ /**
5
+ * Heuristics - 启发式函数模块
6
+ *
7
+ * 提供多种启发式函数用于 A* 算法:
8
+ * - Manhattan: 曼哈顿距离(默认)
9
+ * - Euclidean: 欧几里得距离
10
+ * - Octile: 八方向距离
11
+ *
12
+ * Implements: Requirements 3.1
13
+ */
14
+
15
+ /**
16
+ * 启发式函数类型
17
+ */
18
+
19
+ /**
20
+ * 启发式函数配置
21
+ */
22
+
23
+ /**
24
+ * 默认启发式配置
25
+ */
26
+ export var DEFAULT_HEURISTIC_CONFIG = {
27
+ type: 'manhattan',
28
+ weight: 1.0
29
+ };
30
+
31
+ /**
32
+ * 计算曼哈顿距离
33
+ * 适用于只能水平或垂直移动的场景
34
+ */
35
+ export function manhattanDistance(from, to) {
36
+ return Math.abs(from.x - to.x) + Math.abs(from.y - to.y);
37
+ }
38
+
39
+ /**
40
+ * 计算欧几里得距离
41
+ * 适用于可以任意方向移动的场景
42
+ */
43
+ export function euclideanDistance(from, to) {
44
+ var dx = from.x - to.x;
45
+ var dy = from.y - to.y;
46
+ return Math.sqrt(dx * dx + dy * dy);
47
+ }
48
+
49
+ /**
50
+ * 计算八方向距离(Octile/Chebyshev)
51
+ * 适用于可以对角移动的场景
52
+ */
53
+ export function octileDistance(from, to) {
54
+ var dx = Math.abs(from.x - to.x);
55
+ var dy = Math.abs(from.y - to.y);
56
+ // D = 1, D2 = sqrt(2) ≈ 1.414
57
+ var D = 1;
58
+ var D2 = Math.SQRT2;
59
+ return D * (dx + dy) + (D2 - 2 * D) * Math.min(dx, dy);
60
+ }
61
+
62
+ /**
63
+ * 根据配置计算启发式值
64
+ */
65
+ export function calculateHeuristic(from, to) {
66
+ var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_HEURISTIC_CONFIG;
67
+ var distance;
68
+ switch (config.type) {
69
+ case 'euclidean':
70
+ distance = euclideanDistance(from, to);
71
+ break;
72
+ case 'octile':
73
+ distance = octileDistance(from, to);
74
+ break;
75
+ case 'manhattan':
76
+ default:
77
+ distance = manhattanDistance(from, to);
78
+ break;
79
+ }
80
+ return distance * config.weight;
81
+ }
82
+
83
+ /**
84
+ * 计算到多个目标点的最小启发式值
85
+ */
86
+ export function calculateMinHeuristic(from, targets) {
87
+ var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_HEURISTIC_CONFIG;
88
+ var min = Infinity;
89
+ var _iterator = _createForOfIteratorHelper(targets),
90
+ _step;
91
+ try {
92
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
93
+ var target = _step.value;
94
+ var h = calculateHeuristic(from, target, config);
95
+ if (h < min) {
96
+ min = h;
97
+ }
98
+ }
99
+ } catch (err) {
100
+ _iterator.e(err);
101
+ } finally {
102
+ _iterator.f();
103
+ }
104
+ return min;
105
+ }
106
+
107
+ /**
108
+ * 方向优先级计算
109
+ * 优先考虑与目标方向一致的移动
110
+ */
111
+ export function calculateDirectionPriority(current, neighbor, target) {
112
+ var weight = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0.1;
113
+ // 计算当前到邻居的方向
114
+ var moveX = neighbor.x - current.x;
115
+ var moveY = neighbor.y - current.y;
116
+
117
+ // 计算当前到目标的方向
118
+ var targetX = target.x - current.x;
119
+ var targetY = target.y - current.y;
120
+
121
+ // 如果移动方向与目标方向一致,给予奖励(负惩罚)
122
+ var bonus = 0;
123
+
124
+ // 水平方向一致
125
+ if (moveX > 0 && targetX > 0 || moveX < 0 && targetX < 0) {
126
+ bonus -= weight;
127
+ }
128
+
129
+ // 垂直方向一致
130
+ if (moveY > 0 && targetY > 0 || moveY < 0 && targetY < 0) {
131
+ bonus -= weight;
132
+ }
133
+ return bonus;
134
+ }
135
+
136
+ /**
137
+ * 创建启发式函数
138
+ */
139
+ export function createHeuristicFunction() {
140
+ var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_HEURISTIC_CONFIG;
141
+ return function (from, targets) {
142
+ return calculateMinHeuristic(from, targets, config);
143
+ };
144
+ }
@@ -1,7 +1,13 @@
1
1
  export * from "./grid";
2
+ export * from "./GlobalGrid";
2
3
  export * from "./direction";
3
4
  export * from "./rect";
4
5
  export * from "./route";
5
6
  export * from "./node";
6
7
  export * from "./pathValidation";
7
- export * from "./getAnchorPoints";
8
+ export * from "./getAnchorPoints";
9
+ export * from "./AdaptiveStepCalculator";
10
+ export * from "./PerformanceMonitor";
11
+ export * from "./ErrorRecovery";
12
+ export * from "./heuristics";
13
+ export * from "./pathProcessing";
@@ -0,0 +1,270 @@
1
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
2
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
+ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
5
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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
+ * Path Processing Utilities
9
+ *
10
+ * 提供路径后处理功能,包括:
11
+ * - 移除冗余扩展点
12
+ * - 插入扩展点
13
+ * - 优化锯齿模式
14
+ *
15
+ * Implements: Requirements 5.3
16
+ */
17
+
18
+ import { Point } from "../geometry";
19
+ /**
20
+ * 检测锚点在边界框的哪条边上
21
+ */
22
+ export function detectAnchorEdge(anchor, bbox) {
23
+ var tolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
24
+ if (Math.abs(anchor.x - bbox.x) < tolerance) return 'left';
25
+ if (Math.abs(anchor.x - (bbox.x + bbox.width)) < tolerance) return 'right';
26
+ if (Math.abs(anchor.y - bbox.y) < tolerance) return 'top';
27
+ if (Math.abs(anchor.y - (bbox.y + bbox.height)) < tolerance) return 'bottom';
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * 移除路径起点的扩展点
33
+ */
34
+ export function removeSourceExtensionPoint(route, sourceAnchor, sourceBBox, step) {
35
+ var tolerance = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
36
+ if (route.length === 0) return route;
37
+ var firstPoint = route[0];
38
+ var edge = detectAnchorEdge(sourceAnchor, sourceBBox, tolerance);
39
+ var distToFirst = sourceAnchor.manhattanDistance(firstPoint);
40
+ if (distToFirst >= step * 2) return route;
41
+ var shouldRemove = edge === 'right' && firstPoint.x > sourceAnchor.x || edge === 'left' && firstPoint.x < sourceAnchor.x || edge === 'bottom' && firstPoint.y > sourceAnchor.y || edge === 'top' && firstPoint.y < sourceAnchor.y;
42
+ if (shouldRemove) {
43
+ return route.slice(1);
44
+ }
45
+ return route;
46
+ }
47
+
48
+ /**
49
+ * 移除路径终点的扩展点
50
+ */
51
+ export function removeTargetExtensionPoint(route, targetAnchor, targetBBox, step) {
52
+ var tolerance = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
53
+ if (route.length === 0) return route;
54
+ var lastPoint = route[route.length - 1];
55
+ var edge = detectAnchorEdge(targetAnchor, targetBBox, tolerance);
56
+ var distToLast = targetAnchor.manhattanDistance(lastPoint);
57
+ if (distToLast >= step * 2) return route;
58
+ var shouldRemove = edge === 'left' && lastPoint.x < targetAnchor.x || edge === 'right' && lastPoint.x > targetAnchor.x || edge === 'top' && lastPoint.y < targetAnchor.y || edge === 'bottom' && lastPoint.y > targetAnchor.y;
59
+ if (shouldRemove) {
60
+ return route.slice(0, -1);
61
+ }
62
+ return route;
63
+ }
64
+
65
+ /**
66
+ * 在源点插入扩展点
67
+ */
68
+ export function insertSourceExtension(route, sourceAnchor, sourceBBox, extensionDistance) {
69
+ var tolerance = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
70
+ if (route.length === 0) return route;
71
+ var result = _toConsumableArray(route);
72
+ var firstPoint = result[0];
73
+ var edge = detectAnchorEdge(sourceAnchor, sourceBBox, tolerance);
74
+ if (!edge) return result;
75
+ var extensionPoint;
76
+ var cornerPoint = null;
77
+ switch (edge) {
78
+ case 'right':
79
+ extensionPoint = new Point(sourceAnchor.x + extensionDistance, sourceAnchor.y);
80
+ if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
81
+ cornerPoint = new Point(extensionPoint.x, firstPoint.y);
82
+ }
83
+ break;
84
+ case 'left':
85
+ extensionPoint = new Point(sourceAnchor.x - extensionDistance, sourceAnchor.y);
86
+ if (Math.abs(extensionPoint.y - firstPoint.y) > tolerance) {
87
+ cornerPoint = new Point(extensionPoint.x, firstPoint.y);
88
+ }
89
+ break;
90
+ case 'bottom':
91
+ extensionPoint = new Point(sourceAnchor.x, sourceAnchor.y + extensionDistance);
92
+ if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
93
+ cornerPoint = new Point(firstPoint.x, extensionPoint.y);
94
+ }
95
+ break;
96
+ case 'top':
97
+ extensionPoint = new Point(sourceAnchor.x, sourceAnchor.y - extensionDistance);
98
+ if (Math.abs(extensionPoint.x - firstPoint.x) > tolerance) {
99
+ cornerPoint = new Point(firstPoint.x, extensionPoint.y);
100
+ }
101
+ break;
102
+ }
103
+ if (cornerPoint) {
104
+ result.unshift(cornerPoint);
105
+ }
106
+ result.unshift(extensionPoint);
107
+ return result;
108
+ }
109
+
110
+ /**
111
+ * 在目标点插入扩展点
112
+ */
113
+ export function insertTargetExtension(route, targetAnchor, targetBBox, extensionDistance) {
114
+ var tolerance = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
115
+ if (route.length === 0) return route;
116
+ var result = _toConsumableArray(route);
117
+ var lastPoint = result[result.length - 1];
118
+ var edge = detectAnchorEdge(targetAnchor, targetBBox, tolerance);
119
+ if (!edge) return result;
120
+ var extensionPoint;
121
+ var cornerPoint = null;
122
+ switch (edge) {
123
+ case 'left':
124
+ extensionPoint = new Point(targetAnchor.x - extensionDistance, targetAnchor.y);
125
+ if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
126
+ cornerPoint = new Point(extensionPoint.x, lastPoint.y);
127
+ }
128
+ break;
129
+ case 'right':
130
+ extensionPoint = new Point(targetAnchor.x + extensionDistance, targetAnchor.y);
131
+ if (Math.abs(extensionPoint.y - lastPoint.y) > tolerance) {
132
+ cornerPoint = new Point(extensionPoint.x, lastPoint.y);
133
+ }
134
+ break;
135
+ case 'top':
136
+ extensionPoint = new Point(targetAnchor.x, targetAnchor.y - extensionDistance);
137
+ if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
138
+ cornerPoint = new Point(lastPoint.x, extensionPoint.y);
139
+ }
140
+ break;
141
+ case 'bottom':
142
+ extensionPoint = new Point(targetAnchor.x, targetAnchor.y + extensionDistance);
143
+ if (Math.abs(extensionPoint.x - lastPoint.x) > tolerance) {
144
+ cornerPoint = new Point(lastPoint.x, extensionPoint.y);
145
+ }
146
+ break;
147
+ }
148
+ if (cornerPoint) {
149
+ result.push(cornerPoint);
150
+ }
151
+ result.push(extensionPoint);
152
+ return result;
153
+ }
154
+
155
+ /**
156
+ * 检测并移除锯齿模式
157
+ */
158
+ export function removeZigzagPatterns(route, targetBBox) {
159
+ var tolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
160
+ if (route.length < 3) return route;
161
+ var result = _toConsumableArray(route);
162
+ var i = 0;
163
+ while (i < result.length - 2) {
164
+ var p1 = result[i];
165
+ var p2 = result[i + 1];
166
+ var p3 = result[i + 2];
167
+
168
+ // 检查 p2 是否在目标边界框边缘
169
+ var p2OnEdge = isPointOnBBoxEdge(p2, targetBBox, tolerance);
170
+ if (p2OnEdge) {
171
+ var isZigzag = isZigzagPattern(p1, p2, p3, tolerance);
172
+ if (isZigzag) {
173
+ result.splice(i + 1, 2);
174
+ continue;
175
+ }
176
+ }
177
+ i++;
178
+ }
179
+ return result;
180
+ }
181
+
182
+ /**
183
+ * 检查点是否在边界框边缘
184
+ */
185
+ function isPointOnBBoxEdge(point, bbox, tolerance) {
186
+ return Math.abs(point.x - bbox.x) < tolerance || Math.abs(point.x - (bbox.x + bbox.width)) < tolerance || Math.abs(point.y - bbox.y) < tolerance || Math.abs(point.y - (bbox.y + bbox.height)) < tolerance;
187
+ }
188
+
189
+ /**
190
+ * 检查三个点是否形成锯齿模式
191
+ */
192
+ function isZigzagPattern(p1, p2, p3, tolerance) {
193
+ var p1ToP2Horizontal = Math.abs(p1.y - p2.y) < tolerance;
194
+ var p2ToP3Vertical = Math.abs(p2.x - p3.x) < tolerance;
195
+ var p1ToP2Vertical = Math.abs(p1.x - p2.x) < tolerance;
196
+ var p2ToP3Horizontal = Math.abs(p2.y - p3.y) < tolerance;
197
+ return p1ToP2Horizontal && p2ToP3Vertical || p1ToP2Vertical && p2ToP3Horizontal;
198
+ }
199
+
200
+ /**
201
+ * 移除冗余点
202
+ */
203
+ export function removeRedundantPoints(route, anchor) {
204
+ var tolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
205
+ if (route.length <= 3) return route;
206
+ var result = _toConsumableArray(route);
207
+
208
+ // 检查第三个点是否冗余
209
+ var thirdPoint = result[2];
210
+ var sameX = Math.abs(thirdPoint.x - anchor.x) < tolerance;
211
+ var sameY = Math.abs(thirdPoint.y - anchor.y) < tolerance;
212
+ if ((sameX || sameY) && result.length > 3) {
213
+ var secondPoint = result[1];
214
+ var fourthPoint = result[3];
215
+ var cornerToThird = Math.abs(secondPoint.x - thirdPoint.x) < tolerance || Math.abs(secondPoint.y - thirdPoint.y) < tolerance;
216
+ var thirdToFourth = Math.abs(thirdPoint.x - fourthPoint.x) < tolerance || Math.abs(thirdPoint.y - fourthPoint.y) < tolerance;
217
+ if (cornerToThird && thirdToFourth) {
218
+ result.splice(2, 1);
219
+ }
220
+ }
221
+ return result;
222
+ }
223
+
224
+ /**
225
+ * 完整的路径后处理流程
226
+ */
227
+ export function processRoute(route, sourceAnchor, targetAnchor, sourceBBox, targetBBox, options) {
228
+ var tolerance = 1;
229
+ var processed = _toConsumableArray(route);
230
+
231
+ // 1. 移除源点扩展点
232
+ processed = removeSourceExtensionPoint(processed, sourceAnchor, sourceBBox, options.step, tolerance);
233
+
234
+ // 2. 移除目标点扩展点
235
+ processed = removeTargetExtensionPoint(processed, targetAnchor, targetBBox, options.step, tolerance);
236
+
237
+ // 3. 插入源点扩展点
238
+ processed = insertSourceExtension(processed, sourceAnchor, sourceBBox, options.extensionDistance, tolerance);
239
+
240
+ // 4. 移除源点冗余点
241
+ processed = removeRedundantPoints(processed, sourceAnchor, tolerance);
242
+
243
+ // 5. 移除锯齿模式
244
+ var targetBBoxWithPadding = targetBBox.moveAndExpand(options.paddingBox);
245
+ processed = removeZigzagPatterns(processed, targetBBoxWithPadding, tolerance);
246
+
247
+ // 6. 插入目标点扩展点
248
+ processed = insertTargetExtension(processed, targetAnchor, targetBBox, options.extensionDistance, tolerance);
249
+
250
+ // 7. 移除目标点冗余点(从末尾)
251
+ if (processed.length > 3) {
252
+ var lastIdx = processed.length - 1;
253
+ var thirdLastPoint = processed[lastIdx - 2];
254
+ var sameX = Math.abs(thirdLastPoint.x - targetAnchor.x) < tolerance;
255
+ var sameY = Math.abs(thirdLastPoint.y - targetAnchor.y) < tolerance;
256
+ if ((sameX || sameY) && processed.length > 3) {
257
+ var secondLastPoint = processed[lastIdx - 1];
258
+ var fourthLastPoint = processed[lastIdx - 3];
259
+ var fourthToThird = Math.abs(fourthLastPoint.x - thirdLastPoint.x) < tolerance || Math.abs(fourthLastPoint.y - thirdLastPoint.y) < tolerance;
260
+ var thirdToSecond = Math.abs(thirdLastPoint.x - secondLastPoint.x) < tolerance || Math.abs(thirdLastPoint.y - secondLastPoint.y) < tolerance;
261
+ if (fourthToThird && thirdToSecond) {
262
+ processed.splice(lastIdx - 2, 1);
263
+ }
264
+ }
265
+ }
266
+
267
+ // 8. 再次移除锯齿模式
268
+ processed = removeZigzagPatterns(processed, targetBBox, tolerance);
269
+ return processed;
270
+ }
@@ -149,7 +149,6 @@ export function pathIntersectsObstacles(pathPoints, nodeLookup) {
149
149
 
150
150
  // If path has 2 or more unique intersections with this node, it crosses through it
151
151
  if (allIntersections.length >= 2) {
152
- console.log("[pathIntersectsObstacles] Path crosses node ".concat(nodeId, " with ").concat(allIntersections.length, " intersections"));
153
152
  return true;
154
153
  }
155
154
  }
package/esm/utils/rect.js CHANGED
@@ -67,6 +67,13 @@ export function getRectPoints(anchor, bbox, directionList, grid, options) {
67
67
  if (!isOutward) {
68
68
  continue; // Skip inward directions
69
69
  }
70
+
71
+ // When anchor is on edge and direction is outward, create extension point directly
72
+ // instead of trying to find intersection (which won't exist for outward directions)
73
+ var extensionPoint = new Point(anchor.x + direction.x * grid.x, anchor.y + direction.y * grid.y);
74
+ var target = align(extensionPoint, grid, precision);
75
+ rectPoints.push(target);
76
+ continue;
70
77
  }
71
78
 
72
79
  // Create a line that is guaranteed to intersect the bbox if bbox
@@ -97,13 +104,13 @@ export function getRectPoints(anchor, bbox, directionList, grid, options) {
97
104
  _iterator2.f();
98
105
  }
99
106
  if (farthestIntersection) {
100
- var target = align(farthestIntersection, grid, precision);
107
+ var _target = align(farthestIntersection, grid, precision);
101
108
 
102
109
  // If the rectPoint lies inside the bbox, offset it by one more step
103
- if (bbox.containsPoint(target)) {
104
- target = align(target.translate(direction.x * grid.x, direction.y * grid.y), grid, precision);
110
+ if (bbox.containsPoint(_target)) {
111
+ _target = align(_target.translate(direction.x * grid.x, direction.y * grid.y), grid, precision);
105
112
  }
106
- rectPoints.push(target);
113
+ rectPoints.push(_target);
107
114
  }
108
115
  }
109
116
 
@@ -52,7 +52,13 @@ export function reconstructRoute(parents, points, tailPoint, startPoint, endPoin
52
52
  var currentKey = getKey(tailPoint);
53
53
  var parent = parents.get(currentKey);
54
54
  var point;
55
- while (parent) {
55
+ var iterationCount = 0;
56
+ var maxIterations = 10000; // Safety limit
57
+
58
+ while (parent && iterationCount < maxIterations) {
59
+ iterationCount++;
60
+ if (iterationCount % 100 === 0) {}
61
+
56
62
  // point is assumed to be aligned already
57
63
  point = points.get(currentKey);
58
64
  if (point) {
@@ -64,9 +70,19 @@ export function reconstructRoute(parents, points, tailPoint, startPoint, endPoin
64
70
  }
65
71
 
66
72
  // parent is assumed to be aligned already
67
- currentKey = getKey(parent);
73
+ var nextKey = getKey(parent);
74
+
75
+ // CRITICAL: Check for circular reference
76
+ if (nextKey === currentKey) {
77
+ console.error('[reconstructRoute] CIRCULAR REFERENCE DETECTED! currentKey === nextKey:', currentKey);
78
+ break;
79
+ }
80
+ currentKey = nextKey;
68
81
  parent = parents.get(currentKey);
69
82
  }
83
+ if (iterationCount >= maxIterations) {
84
+ console.error('[reconstructRoute] MAX ITERATIONS REACHED! Possible infinite loop.');
85
+ }
70
86
 
71
87
  // leadPoint is assumed to be aligned already
72
88
  var leadPoint = points.get(currentKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxflow/manhattan",
3
- "version": "0.0.2-alpha.8",
3
+ "version": "0.0.2",
4
4
  "description": "Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance",
5
5
  "keywords": [
6
6
  "reactflow",
@@ -34,10 +34,18 @@
34
34
  "cjs"
35
35
  ],
36
36
  "scripts": {
37
- "build": "father build"
37
+ "build": "father build",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "test:coverage": "vitest run --coverage"
38
41
  },
39
42
  "peerDependencies": {
40
43
  "@xyflow/react": ">=12.8.6"
41
44
  },
45
+ "devDependencies": {
46
+ "@vitest/coverage-v8": "^2.1.0",
47
+ "fast-check": "^3.22.0",
48
+ "vitest": "^2.1.0"
49
+ },
42
50
  "gitHead": "dcdc06b5032aac044fa2094877d28c509c30d9eb"
43
51
  }
@@ -1,21 +0,0 @@
1
- import { Point } from './Point';
2
- import { Rectangle } from './Rectangle';
3
- /**
4
- * Line class representing a line segment
5
- */
6
- export declare class Line {
7
- start: Point;
8
- end: Point;
9
- constructor(start: Point, end: Point);
10
- /**
11
- * Calculate intersection points with a rectangle
12
- * Returns an array of intersection points
13
- */
14
- intersect(rect: Rectangle): Point[];
15
- /**
16
- * Calculate intersection point between two line segments
17
- * Returns null if lines don't intersect
18
- */
19
- private lineIntersection;
20
- }
21
- //# sourceMappingURL=Line.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Line.d.ts","sourceRoot":"","sources":["../../src/geometry/Line.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;GAEG;AACH,qBAAa,IAAI;IACf,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,KAAK,CAAA;gBAEE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK;IAKpC;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,KAAK,EAAE;IA+BnC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;CA+BzB"}