@rxflow/manhattan 0.0.2-alpha.8 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -10
- package/esm/getManHattanPath.js +92 -69
- package/esm/obstacle/ObstacleMap.js +218 -99
- package/esm/obstacle/QuadTree.js +488 -0
- package/esm/options/resolver.js +147 -18
- package/esm/pathfinder/PathCache.js +278 -0
- package/esm/pathfinder/findRoute.js +98 -44
- package/esm/pathfinder/index.js +2 -1
- package/esm/svg/pathConverter.js +170 -1
- package/esm/utils/AdaptiveStepCalculator.js +252 -0
- package/esm/utils/ErrorRecovery.js +499 -0
- package/esm/utils/GlobalGrid.js +259 -0
- package/esm/utils/PerformanceMonitor.js +360 -0
- package/esm/utils/getAnchorPoints.js +0 -4
- package/esm/utils/grid.js +18 -13
- package/esm/utils/heuristics.js +144 -0
- package/esm/utils/index.js +7 -1
- package/esm/utils/pathProcessing.js +270 -0
- package/esm/utils/pathValidation.js +0 -1
- package/esm/utils/rect.js +11 -4
- package/esm/utils/route.js +18 -2
- package/package.json +10 -2
- package/cjs/geometry/Line.d.ts +0 -21
- package/cjs/geometry/Line.d.ts.map +0 -1
- package/cjs/geometry/Line.js +0 -88
- package/cjs/geometry/Point.d.ts +0 -49
- package/cjs/geometry/Point.d.ts.map +0 -1
- package/cjs/geometry/Point.js +0 -94
- package/cjs/geometry/Rectangle.d.ts +0 -41
- package/cjs/geometry/Rectangle.d.ts.map +0 -1
- package/cjs/geometry/Rectangle.js +0 -65
- package/cjs/geometry/collision.d.ts +0 -15
- package/cjs/geometry/collision.d.ts.map +0 -1
- package/cjs/geometry/collision.js +0 -81
- package/cjs/geometry/index.d.ts +0 -5
- package/cjs/geometry/index.d.ts.map +0 -1
- package/cjs/geometry/index.js +0 -45
- package/cjs/getManHattanPath.d.ts +0 -53
- package/cjs/getManHattanPath.d.ts.map +0 -1
- package/cjs/getManHattanPath.js +0 -418
- package/cjs/index.d.ts +0 -16
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -117
- package/cjs/obstacle/ObstacleMap.d.ts +0 -34
- package/cjs/obstacle/ObstacleMap.d.ts.map +0 -1
- package/cjs/obstacle/ObstacleMap.js +0 -223
- package/cjs/obstacle/index.d.ts +0 -2
- package/cjs/obstacle/index.d.ts.map +0 -1
- package/cjs/obstacle/index.js +0 -12
- package/cjs/options/defaults.d.ts +0 -16
- package/cjs/options/defaults.d.ts.map +0 -1
- package/cjs/options/defaults.js +0 -39
- package/cjs/options/index.d.ts +0 -4
- package/cjs/options/index.d.ts.map +0 -1
- package/cjs/options/index.js +0 -38
- package/cjs/options/resolver.d.ts +0 -10
- package/cjs/options/resolver.d.ts.map +0 -1
- package/cjs/options/resolver.js +0 -120
- package/cjs/options/types.d.ts +0 -169
- package/cjs/options/types.d.ts.map +0 -1
- package/cjs/options/types.js +0 -5
- package/cjs/pathfinder/SortedSet.d.ts +0 -35
- package/cjs/pathfinder/SortedSet.d.ts.map +0 -1
- package/cjs/pathfinder/SortedSet.js +0 -95
- package/cjs/pathfinder/findRoute.d.ts +0 -8
- package/cjs/pathfinder/findRoute.d.ts.map +0 -1
- package/cjs/pathfinder/findRoute.js +0 -330
- package/cjs/pathfinder/index.d.ts +0 -3
- package/cjs/pathfinder/index.d.ts.map +0 -1
- package/cjs/pathfinder/index.js +0 -19
- package/cjs/svg/index.d.ts +0 -3
- package/cjs/svg/index.d.ts.map +0 -1
- package/cjs/svg/index.js +0 -31
- package/cjs/svg/pathConverter.d.ts +0 -10
- package/cjs/svg/pathConverter.d.ts.map +0 -1
- package/cjs/svg/pathConverter.js +0 -116
- package/cjs/svg/pathParser.d.ts +0 -11
- package/cjs/svg/pathParser.d.ts.map +0 -1
- package/cjs/svg/pathParser.js +0 -76
- package/cjs/utils/direction.d.ts +0 -24
- package/cjs/utils/direction.d.ts.map +0 -1
- package/cjs/utils/direction.js +0 -54
- package/cjs/utils/getAnchorPoints.d.ts +0 -15
- package/cjs/utils/getAnchorPoints.d.ts.map +0 -1
- package/cjs/utils/getAnchorPoints.js +0 -75
- package/cjs/utils/grid.d.ts +0 -27
- package/cjs/utils/grid.d.ts.map +0 -1
- package/cjs/utils/grid.js +0 -66
- package/cjs/utils/index.d.ts +0 -8
- package/cjs/utils/index.d.ts.map +0 -1
- package/cjs/utils/index.js +0 -82
- package/cjs/utils/node.d.ts +0 -27
- package/cjs/utils/node.d.ts.map +0 -1
- package/cjs/utils/node.js +0 -36
- package/cjs/utils/pathValidation.d.ts +0 -11
- package/cjs/utils/pathValidation.d.ts.map +0 -1
- package/cjs/utils/pathValidation.js +0 -130
- package/cjs/utils/rect.d.ts +0 -9
- package/cjs/utils/rect.d.ts.map +0 -1
- package/cjs/utils/rect.js +0 -103
- package/cjs/utils/route.d.ts +0 -19
- package/cjs/utils/route.d.ts.map +0 -1
- package/cjs/utils/route.js +0 -76
- package/esm/geometry/Line.d.ts +0 -21
- package/esm/geometry/Line.d.ts.map +0 -1
- package/esm/geometry/Point.d.ts +0 -49
- package/esm/geometry/Point.d.ts.map +0 -1
- package/esm/geometry/Rectangle.d.ts +0 -41
- package/esm/geometry/Rectangle.d.ts.map +0 -1
- package/esm/geometry/collision.d.ts +0 -15
- package/esm/geometry/collision.d.ts.map +0 -1
- package/esm/geometry/index.d.ts +0 -5
- package/esm/geometry/index.d.ts.map +0 -1
- package/esm/getManHattanPath.d.ts +0 -53
- package/esm/getManHattanPath.d.ts.map +0 -1
- package/esm/index.d.ts +0 -16
- package/esm/index.d.ts.map +0 -1
- package/esm/obstacle/ObstacleMap.d.ts +0 -34
- package/esm/obstacle/ObstacleMap.d.ts.map +0 -1
- package/esm/obstacle/index.d.ts +0 -2
- package/esm/obstacle/index.d.ts.map +0 -1
- package/esm/options/defaults.d.ts +0 -16
- package/esm/options/defaults.d.ts.map +0 -1
- package/esm/options/index.d.ts +0 -4
- package/esm/options/index.d.ts.map +0 -1
- package/esm/options/resolver.d.ts +0 -10
- package/esm/options/resolver.d.ts.map +0 -1
- package/esm/options/types.d.ts +0 -169
- package/esm/options/types.d.ts.map +0 -1
- package/esm/pathfinder/SortedSet.d.ts +0 -35
- package/esm/pathfinder/SortedSet.d.ts.map +0 -1
- package/esm/pathfinder/findRoute.d.ts +0 -8
- package/esm/pathfinder/findRoute.d.ts.map +0 -1
- package/esm/pathfinder/index.d.ts +0 -3
- package/esm/pathfinder/index.d.ts.map +0 -1
- package/esm/svg/index.d.ts +0 -3
- package/esm/svg/index.d.ts.map +0 -1
- package/esm/svg/pathConverter.d.ts +0 -10
- package/esm/svg/pathConverter.d.ts.map +0 -1
- package/esm/svg/pathParser.d.ts +0 -11
- package/esm/svg/pathParser.d.ts.map +0 -1
- package/esm/utils/direction.d.ts +0 -24
- package/esm/utils/direction.d.ts.map +0 -1
- package/esm/utils/getAnchorPoints.d.ts +0 -15
- package/esm/utils/getAnchorPoints.d.ts.map +0 -1
- package/esm/utils/grid.d.ts +0 -27
- package/esm/utils/grid.d.ts.map +0 -1
- package/esm/utils/index.d.ts +0 -8
- package/esm/utils/index.d.ts.map +0 -1
- package/esm/utils/node.d.ts +0 -27
- package/esm/utils/node.d.ts.map +0 -1
- package/esm/utils/pathValidation.d.ts +0 -11
- package/esm/utils/pathValidation.d.ts.map +0 -1
- package/esm/utils/rect.d.ts +0 -9
- package/esm/utils/rect.d.ts.map +0 -1
- package/esm/utils/route.d.ts +0 -19
- package/esm/utils/route.d.ts.map +0 -1
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SortedSet class for managing open and closed sets in A* algorithm
|
|
3
|
-
* Maintains items sorted by their values
|
|
4
|
-
*/
|
|
5
|
-
export declare class SortedSet {
|
|
6
|
-
private items;
|
|
7
|
-
private hash;
|
|
8
|
-
private values;
|
|
9
|
-
constructor();
|
|
10
|
-
/**
|
|
11
|
-
* Add an item with its value, maintaining sorted order
|
|
12
|
-
*/
|
|
13
|
-
add(item: string, value: number): void;
|
|
14
|
-
/**
|
|
15
|
-
* Pop the item with minimum value and mark it as closed
|
|
16
|
-
*/
|
|
17
|
-
pop(): string | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* Check if item is in open set
|
|
20
|
-
*/
|
|
21
|
-
isOpen(item: string): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* Check if item is in closed set
|
|
24
|
-
*/
|
|
25
|
-
isClose(item: string): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Check if open set is empty
|
|
28
|
-
*/
|
|
29
|
-
isEmpty(): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Find sorted insertion index for an item
|
|
32
|
-
*/
|
|
33
|
-
private sortedIndexBy;
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=SortedSet.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SortedSet.d.ts","sourceRoot":"","sources":["../../src/pathfinder/SortedSet.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAqB;;IAQnC;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAkBtC;;OAEG;IACH,GAAG,IAAI,MAAM,GAAG,SAAS;IAQzB;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI7B;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI9B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,OAAO,CAAC,aAAa;CAiBtB"}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.SortedSet = void 0;
|
|
7
|
-
const OPEN = 1;
|
|
8
|
-
const CLOSE = 2;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* SortedSet class for managing open and closed sets in A* algorithm
|
|
12
|
-
* Maintains items sorted by their values
|
|
13
|
-
*/
|
|
14
|
-
class SortedSet {
|
|
15
|
-
items;
|
|
16
|
-
hash;
|
|
17
|
-
values;
|
|
18
|
-
constructor() {
|
|
19
|
-
this.items = [];
|
|
20
|
-
this.hash = new Map();
|
|
21
|
-
this.values = new Map();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Add an item with its value, maintaining sorted order
|
|
26
|
-
*/
|
|
27
|
-
add(item, value) {
|
|
28
|
-
if (this.hash.get(item)) {
|
|
29
|
-
// Item removal - remove from items array
|
|
30
|
-
const index = this.items.indexOf(item);
|
|
31
|
-
if (index !== -1) {
|
|
32
|
-
this.items.splice(index, 1);
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
this.hash.set(item, OPEN);
|
|
36
|
-
}
|
|
37
|
-
this.values.set(item, value);
|
|
38
|
-
|
|
39
|
-
// Find insertion index using binary search
|
|
40
|
-
const index = this.sortedIndexBy(item);
|
|
41
|
-
this.items.splice(index, 0, item);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Pop the item with minimum value and mark it as closed
|
|
46
|
-
*/
|
|
47
|
-
pop() {
|
|
48
|
-
const item = this.items.shift();
|
|
49
|
-
if (item) {
|
|
50
|
-
this.hash.set(item, CLOSE);
|
|
51
|
-
}
|
|
52
|
-
return item;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if item is in open set
|
|
57
|
-
*/
|
|
58
|
-
isOpen(item) {
|
|
59
|
-
return this.hash.get(item) === OPEN;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Check if item is in closed set
|
|
64
|
-
*/
|
|
65
|
-
isClose(item) {
|
|
66
|
-
return this.hash.get(item) === CLOSE;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if open set is empty
|
|
71
|
-
*/
|
|
72
|
-
isEmpty() {
|
|
73
|
-
return this.items.length === 0;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Find sorted insertion index for an item
|
|
78
|
-
*/
|
|
79
|
-
sortedIndexBy(item) {
|
|
80
|
-
const value = this.values.get(item);
|
|
81
|
-
let low = 0;
|
|
82
|
-
let high = this.items.length;
|
|
83
|
-
while (low < high) {
|
|
84
|
-
const mid = low + high >>> 1;
|
|
85
|
-
const midValue = this.values.get(this.items[mid]);
|
|
86
|
-
if (midValue < value) {
|
|
87
|
-
low = mid + 1;
|
|
88
|
-
} else {
|
|
89
|
-
high = mid;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return low;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
exports.SortedSet = SortedSet;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Point, Rectangle } from '../geometry';
|
|
2
|
-
import type { ResolvedOptions } from '../options';
|
|
3
|
-
import type { ObstacleMap } from '../obstacle';
|
|
4
|
-
/**
|
|
5
|
-
* Find route between two points using A* algorithm
|
|
6
|
-
*/
|
|
7
|
-
export declare function findRoute(sourceBBox: Rectangle, targetBBox: Rectangle, sourceAnchor: Point, targetAnchor: Point, map: ObstacleMap, options: ResolvedOptions): Point[] | null;
|
|
8
|
-
//# sourceMappingURL=findRoute.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"findRoute.d.ts","sourceRoot":"","sources":["../../src/pathfinder/findRoute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AA+I9C;;GAEG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,SAAS,EACrB,UAAU,EAAE,SAAS,EACrB,YAAY,EAAE,KAAK,EACnB,YAAY,EAAE,KAAK,EACnB,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,eAAe,GACvB,KAAK,EAAE,GAAG,IAAI,CAiThB"}
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.findRoute = findRoute;
|
|
7
|
-
var _geometry = require("../geometry");
|
|
8
|
-
var _SortedSet = require("./SortedSet");
|
|
9
|
-
var _utils = require("../utils");
|
|
10
|
-
/**
|
|
11
|
-
* Generate smart points based on position using extensionDistance and binary search
|
|
12
|
-
*
|
|
13
|
-
* Algorithm:
|
|
14
|
-
* 1. First try extensionDistance in the specified direction
|
|
15
|
-
* 2. If blocked, use binary search starting from step, halving until finding accessible point
|
|
16
|
-
* 3. For target points, approach from opposite direction
|
|
17
|
-
*/
|
|
18
|
-
function generateSmartPoints(anchor, bbox, position, grid, map, options, isTarget = false) {
|
|
19
|
-
const directionMap = {
|
|
20
|
-
'right': {
|
|
21
|
-
x: 1,
|
|
22
|
-
y: 0
|
|
23
|
-
},
|
|
24
|
-
'left': {
|
|
25
|
-
x: -1,
|
|
26
|
-
y: 0
|
|
27
|
-
},
|
|
28
|
-
'top': {
|
|
29
|
-
x: 0,
|
|
30
|
-
y: -1
|
|
31
|
-
},
|
|
32
|
-
'bottom': {
|
|
33
|
-
x: 0,
|
|
34
|
-
y: 1
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
const direction = directionMap[position];
|
|
38
|
-
if (!direction) {
|
|
39
|
-
console.warn(`[generateSmartPoints] Unknown position: ${position}, falling back to anchor`);
|
|
40
|
-
return [anchor];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Both source and target extend in the specified direction
|
|
44
|
-
// - Source: extends away from node in sourcePosition direction
|
|
45
|
-
// - Target: extends away from node in targetPosition direction (path approaches from this direction)
|
|
46
|
-
const actualDirection = direction;
|
|
47
|
-
const points = [];
|
|
48
|
-
|
|
49
|
-
// 1. First try extensionDistance
|
|
50
|
-
const extensionPoint = new _geometry.Point(anchor.x + actualDirection.x * options.extensionDistance, anchor.y + actualDirection.y * options.extensionDistance).round(options.precision);
|
|
51
|
-
console.log(`[generateSmartPoints] ${isTarget ? 'Target' : 'Source'} position=${position}, trying extension point: (${extensionPoint.x}, ${extensionPoint.y})`);
|
|
52
|
-
if (map.isAccessible(extensionPoint)) {
|
|
53
|
-
points.push(extensionPoint);
|
|
54
|
-
console.log(`[generateSmartPoints] Extension point is accessible`);
|
|
55
|
-
return points;
|
|
56
|
-
}
|
|
57
|
-
console.log(`[generateSmartPoints] Extension point blocked, using step-based search`);
|
|
58
|
-
|
|
59
|
-
// 2. Step-based search with binary refinement
|
|
60
|
-
// First, extend outward by step increments until we find an accessible point
|
|
61
|
-
let stepMultiplier = 1;
|
|
62
|
-
let maxSteps = 20; // Prevent infinite loop
|
|
63
|
-
let foundAccessibleDistance = -1;
|
|
64
|
-
console.log(`[generateSmartPoints] Starting outward search with step=${options.step}`);
|
|
65
|
-
while (stepMultiplier <= maxSteps) {
|
|
66
|
-
const distance = stepMultiplier * options.step;
|
|
67
|
-
const testPoint = new _geometry.Point(anchor.x + actualDirection.x * distance, anchor.y + actualDirection.y * distance).round(options.precision);
|
|
68
|
-
console.log(`[generateSmartPoints] Testing ${stepMultiplier}*step (distance=${distance}): (${testPoint.x}, ${testPoint.y})`);
|
|
69
|
-
if (map.isAccessible(testPoint)) {
|
|
70
|
-
foundAccessibleDistance = distance;
|
|
71
|
-
console.log(`[generateSmartPoints] Found accessible point at ${stepMultiplier}*step (distance=${distance})`);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
stepMultiplier++;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 3. If we found an accessible point, refine by binary search within the last step interval
|
|
78
|
-
if (foundAccessibleDistance > 0) {
|
|
79
|
-
const outerDistance = foundAccessibleDistance;
|
|
80
|
-
const innerDistance = foundAccessibleDistance - options.step;
|
|
81
|
-
console.log(`[generateSmartPoints] Refining between ${innerDistance} and ${outerDistance}`);
|
|
82
|
-
|
|
83
|
-
// Binary search within the last step interval to find the closest accessible point
|
|
84
|
-
let left = innerDistance;
|
|
85
|
-
let right = outerDistance;
|
|
86
|
-
let bestDistance = outerDistance;
|
|
87
|
-
|
|
88
|
-
// Binary search with precision of 1px
|
|
89
|
-
while (right - left > 1) {
|
|
90
|
-
const mid = (left + right) / 2;
|
|
91
|
-
const testPoint = new _geometry.Point(anchor.x + actualDirection.x * mid, anchor.y + actualDirection.y * mid).round(options.precision);
|
|
92
|
-
console.log(`[generateSmartPoints] Binary search testing distance ${mid.toFixed(1)}: (${testPoint.x}, ${testPoint.y})`);
|
|
93
|
-
if (map.isAccessible(testPoint)) {
|
|
94
|
-
bestDistance = mid;
|
|
95
|
-
right = mid;
|
|
96
|
-
console.log(`[generateSmartPoints] Point accessible, searching closer (right=${right})`);
|
|
97
|
-
} else {
|
|
98
|
-
left = mid;
|
|
99
|
-
console.log(`[generateSmartPoints] Point blocked, searching further (left=${left})`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Use the best distance found
|
|
104
|
-
const finalPoint = new _geometry.Point(anchor.x + actualDirection.x * bestDistance, anchor.y + actualDirection.y * bestDistance).round(options.precision);
|
|
105
|
-
points.push(finalPoint);
|
|
106
|
-
console.log(`[generateSmartPoints] Final point at distance ${bestDistance}: (${finalPoint.x}, ${finalPoint.y})`);
|
|
107
|
-
} else {
|
|
108
|
-
// 4. If no accessible point found after maxSteps, use anchor as fallback
|
|
109
|
-
console.log(`[generateSmartPoints] No accessible point found after ${maxSteps} steps, using anchor: (${anchor.x}, ${anchor.y})`);
|
|
110
|
-
points.push(anchor);
|
|
111
|
-
}
|
|
112
|
-
return points;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Find route between two points using A* algorithm
|
|
117
|
-
*/
|
|
118
|
-
function findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, map, options) {
|
|
119
|
-
const precision = options.precision;
|
|
120
|
-
|
|
121
|
-
// Round anchor points
|
|
122
|
-
const sourceEndpoint = (0, _utils.round)(sourceAnchor.clone(), precision);
|
|
123
|
-
const targetEndpoint = (0, _utils.round)(targetAnchor.clone(), precision);
|
|
124
|
-
|
|
125
|
-
// Get grid for this route
|
|
126
|
-
const grid = (0, _utils.getGrid)(options.step, sourceEndpoint, targetEndpoint);
|
|
127
|
-
|
|
128
|
-
// Get pathfinding points
|
|
129
|
-
const startPoint = sourceEndpoint;
|
|
130
|
-
const endPoint = targetEndpoint;
|
|
131
|
-
|
|
132
|
-
// Get start and end points around rectangles
|
|
133
|
-
// Use smart point generation based on position if available
|
|
134
|
-
let startPoints;
|
|
135
|
-
let endPoints;
|
|
136
|
-
|
|
137
|
-
// Generate smart start points based on sourcePosition
|
|
138
|
-
if (options.sourcePosition) {
|
|
139
|
-
startPoints = generateSmartPoints(startPoint, sourceBBox, options.sourcePosition, grid, map, options, false);
|
|
140
|
-
console.log('[findRoute] Start points from smart generation:', startPoints.map(p => `(${p.x}, ${p.y})`));
|
|
141
|
-
} else {
|
|
142
|
-
startPoints = (0, _utils.getRectPoints)(startPoint, sourceBBox, options.startDirections, grid, options);
|
|
143
|
-
console.log('[findRoute] Start points from getRectPoints:', startPoints.map(p => `(${p.x}, ${p.y})`));
|
|
144
|
-
// Take into account only accessible rect points
|
|
145
|
-
startPoints = startPoints.filter(p => map.isAccessible(p));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Generate smart end points based on targetPosition
|
|
149
|
-
if (options.targetPosition) {
|
|
150
|
-
endPoints = generateSmartPoints(targetEndpoint, targetBBox, options.targetPosition, grid, map, options, true);
|
|
151
|
-
console.log('[findRoute] End points from smart generation:', endPoints.map(p => `(${p.x}, ${p.y})`));
|
|
152
|
-
} else {
|
|
153
|
-
endPoints = (0, _utils.getRectPoints)(targetEndpoint, targetBBox, options.endDirections, grid, options);
|
|
154
|
-
console.log('[findRoute] End points from getRectPoints:', endPoints.map(p => `(${p.x}, ${p.y})`));
|
|
155
|
-
// Take into account only accessible rect points
|
|
156
|
-
endPoints = endPoints.filter(p => map.isAccessible(p));
|
|
157
|
-
}
|
|
158
|
-
console.log('[findRoute] Start points after filter:', startPoints.map(p => `(${p.x}, ${p.y})`));
|
|
159
|
-
console.log('[findRoute] End points after filter:', endPoints.map(p => `(${p.x}, ${p.y})`));
|
|
160
|
-
|
|
161
|
-
// Ensure we always have at least the anchor points
|
|
162
|
-
// This handles edge cases where anchor is on the node boundary
|
|
163
|
-
if (startPoints.length === 0) {
|
|
164
|
-
startPoints = [(0, _utils.round)(startPoint, precision)];
|
|
165
|
-
}
|
|
166
|
-
if (endPoints.length === 0) {
|
|
167
|
-
endPoints = [(0, _utils.round)(endPoint, precision)];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Initialize A* data structures
|
|
171
|
-
const openSet = new _SortedSet.SortedSet();
|
|
172
|
-
const points = new Map();
|
|
173
|
-
const parents = new Map();
|
|
174
|
-
const costs = new Map();
|
|
175
|
-
|
|
176
|
-
// Add all start points to open set
|
|
177
|
-
for (const startPoint of startPoints) {
|
|
178
|
-
const key = (0, _utils.getKey)(startPoint);
|
|
179
|
-
openSet.add(key, (0, _utils.getCost)(startPoint, endPoints));
|
|
180
|
-
points.set(key, startPoint);
|
|
181
|
-
costs.set(key, 0);
|
|
182
|
-
}
|
|
183
|
-
const previousRouteDirectionAngle = options.previousDirectionAngle;
|
|
184
|
-
const isPathBeginning = previousRouteDirectionAngle === undefined;
|
|
185
|
-
|
|
186
|
-
// Get directions with grid offsets
|
|
187
|
-
const directions = (0, _utils.getGridOffsets)(grid, options);
|
|
188
|
-
const numDirections = directions.length;
|
|
189
|
-
|
|
190
|
-
// Create set of end point keys for quick lookup
|
|
191
|
-
const endPointsKeys = new Set(endPoints.map(p => (0, _utils.getKey)(p)));
|
|
192
|
-
|
|
193
|
-
// Check if start and end points are the same
|
|
194
|
-
const sameStartEndPoints = startPoints.length === endPoints.length && startPoints.every((sp, i) => sp.equals(endPoints[i]));
|
|
195
|
-
|
|
196
|
-
// Main A* loop
|
|
197
|
-
let loopsRemaining = options.maxLoopCount;
|
|
198
|
-
while (!openSet.isEmpty() && loopsRemaining > 0) {
|
|
199
|
-
// Get the closest item and mark it CLOSED
|
|
200
|
-
const currentKey = openSet.pop();
|
|
201
|
-
if (!currentKey) break;
|
|
202
|
-
const currentPoint = points.get(currentKey);
|
|
203
|
-
const currentParent = parents.get(currentKey);
|
|
204
|
-
const currentCost = costs.get(currentKey);
|
|
205
|
-
const isStartPoint = currentPoint.equals(startPoint);
|
|
206
|
-
const isRouteBeginning = currentParent === undefined;
|
|
207
|
-
|
|
208
|
-
// Calculate previous direction angle
|
|
209
|
-
let previousDirectionAngle;
|
|
210
|
-
if (!isRouteBeginning) {
|
|
211
|
-
previousDirectionAngle = (0, _utils.getDirectionAngle)(currentParent, currentPoint, numDirections, grid, options);
|
|
212
|
-
} else if (!isPathBeginning) {
|
|
213
|
-
previousDirectionAngle = previousRouteDirectionAngle;
|
|
214
|
-
} else if (!isStartPoint) {
|
|
215
|
-
previousDirectionAngle = (0, _utils.getDirectionAngle)(startPoint, currentPoint, numDirections, grid, options);
|
|
216
|
-
} else {
|
|
217
|
-
previousDirectionAngle = null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Check if we reached any endpoint
|
|
221
|
-
const skipEndCheck = isRouteBeginning && sameStartEndPoints;
|
|
222
|
-
if (!skipEndCheck && endPointsKeys.has(currentKey)) {
|
|
223
|
-
options.previousDirectionAngle = previousDirectionAngle;
|
|
224
|
-
return (0, _utils.reconstructRoute)(parents, points, currentPoint, startPoint, endPoint);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Explore neighbors in all directions
|
|
228
|
-
for (const direction of directions) {
|
|
229
|
-
const directionAngle = direction.angle;
|
|
230
|
-
const directionChange = (0, _utils.getDirectionChange)(previousDirectionAngle ?? 0, directionAngle);
|
|
231
|
-
|
|
232
|
-
// Don't use the point if direction changed too rapidly
|
|
233
|
-
if (!(isPathBeginning && isStartPoint) && directionChange > options.maxDirectionChange) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Calculate neighbor point and align to global grid
|
|
238
|
-
const rawNeighbor = currentPoint.clone().translate(direction.gridOffsetX || 0, direction.gridOffsetY || 0);
|
|
239
|
-
|
|
240
|
-
// Align to global grid for consistent path alignment
|
|
241
|
-
const neighborPoint = new _geometry.Point(Math.round(rawNeighbor.x / grid.x) * grid.x, Math.round(rawNeighbor.y / grid.y) * grid.y).round(precision);
|
|
242
|
-
const neighborKey = (0, _utils.getKey)(neighborPoint);
|
|
243
|
-
|
|
244
|
-
// Skip if closed or not accessible
|
|
245
|
-
if (openSet.isClose(neighborKey) || !map.isAccessible(neighborPoint)) {
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Check if we can reach any end point directly from this neighbor
|
|
250
|
-
// This allows connecting to end points that are not on the grid
|
|
251
|
-
let canReachEndPoint = false;
|
|
252
|
-
let reachableEndPoint = null;
|
|
253
|
-
for (const endPt of endPoints) {
|
|
254
|
-
const distanceToEnd = neighborPoint.manhattanDistance(endPt);
|
|
255
|
-
|
|
256
|
-
// If close enough to end point (within step distance), try direct connection
|
|
257
|
-
if (distanceToEnd < options.step * 1.5) {
|
|
258
|
-
// Check if we can move directly to the end point
|
|
259
|
-
const dx = endPt.x - neighborPoint.x;
|
|
260
|
-
const dy = endPt.y - neighborPoint.y;
|
|
261
|
-
|
|
262
|
-
// Allow direct connection if it's orthogonal or close to orthogonal
|
|
263
|
-
const isOrthogonal = Math.abs(dx) < 0.1 || Math.abs(dy) < 0.1;
|
|
264
|
-
if (isOrthogonal && map.isAccessible(endPt)) {
|
|
265
|
-
canReachEndPoint = true;
|
|
266
|
-
reachableEndPoint = endPt;
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// If we can reach an end point directly, add it as the final step
|
|
273
|
-
if (canReachEndPoint && reachableEndPoint) {
|
|
274
|
-
const endKey = (0, _utils.getKey)(reachableEndPoint);
|
|
275
|
-
const endCost = neighborPoint.manhattanDistance(reachableEndPoint);
|
|
276
|
-
const totalCost = currentCost + direction.cost + endCost;
|
|
277
|
-
if (!openSet.isOpen(endKey) || totalCost < (costs.get(endKey) || Infinity)) {
|
|
278
|
-
points.set(endKey, reachableEndPoint);
|
|
279
|
-
parents.set(endKey, neighborPoint);
|
|
280
|
-
costs.set(endKey, totalCost);
|
|
281
|
-
|
|
282
|
-
// Also add the neighbor point if not already added
|
|
283
|
-
if (!points.has(neighborKey)) {
|
|
284
|
-
points.set(neighborKey, neighborPoint);
|
|
285
|
-
parents.set(neighborKey, currentPoint);
|
|
286
|
-
costs.set(neighborKey, currentCost + direction.cost);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Check if this is our target end point
|
|
290
|
-
if (endPointsKeys.has(endKey)) {
|
|
291
|
-
options.previousDirectionAngle = directionAngle;
|
|
292
|
-
return (0, _utils.reconstructRoute)(parents, points, reachableEndPoint, startPoint, endPoint);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Check if neighbor is an end point (exact match)
|
|
298
|
-
if (endPointsKeys.has(neighborKey)) {
|
|
299
|
-
const isEndPoint = neighborPoint.equals(endPoint);
|
|
300
|
-
if (!isEndPoint) {
|
|
301
|
-
const endDirectionAngle = (0, _utils.getDirectionAngle)(neighborPoint, endPoint, numDirections, grid, options);
|
|
302
|
-
const endDirectionChange = (0, _utils.getDirectionChange)(directionAngle, endDirectionAngle);
|
|
303
|
-
if (endDirectionChange > options.maxDirectionChange) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Calculate costs
|
|
310
|
-
const neighborCost = direction.cost;
|
|
311
|
-
const neighborPenalty = isStartPoint ? 0 : options.penalties[directionChange] || 0;
|
|
312
|
-
const costFromStart = currentCost + neighborCost + neighborPenalty;
|
|
313
|
-
|
|
314
|
-
// Update if not in open set or found better path
|
|
315
|
-
if (!openSet.isOpen(neighborKey) || costFromStart < (costs.get(neighborKey) || Infinity)) {
|
|
316
|
-
points.set(neighborKey, neighborPoint);
|
|
317
|
-
parents.set(neighborKey, currentPoint);
|
|
318
|
-
costs.set(neighborKey, costFromStart);
|
|
319
|
-
openSet.add(neighborKey, costFromStart + (0, _utils.getCost)(neighborPoint, endPoints));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
loopsRemaining -= 1;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// No path found, try fallback
|
|
326
|
-
if (options.fallbackRoute) {
|
|
327
|
-
return options.fallbackRoute(startPoint, endPoint);
|
|
328
|
-
}
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pathfinder/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA"}
|
package/cjs/pathfinder/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "SortedSet", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _SortedSet.SortedSet;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(exports, "findRoute", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _findRoute.findRoute;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
var _SortedSet = require("./SortedSet");
|
|
19
|
-
var _findRoute = require("./findRoute");
|
package/cjs/svg/index.d.ts
DELETED
package/cjs/svg/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
|
package/cjs/svg/index.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "parseSVGPath", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _pathParser.parseSVGPath;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(exports, "pointsToPath", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _pathConverter.pointsToPath;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
Object.defineProperty(exports, "simplifyPath", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () {
|
|
21
|
-
return _pathParser.simplifyPath;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
Object.defineProperty(exports, "snapPathToGrid", {
|
|
25
|
-
enumerable: true,
|
|
26
|
-
get: function () {
|
|
27
|
-
return _pathConverter.snapPathToGrid;
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
var _pathConverter = require("./pathConverter");
|
|
31
|
-
var _pathParser = require("./pathParser");
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Point } from '../geometry';
|
|
2
|
-
/**
|
|
3
|
-
* Convert array of points to SVG path string
|
|
4
|
-
*/
|
|
5
|
-
export declare function pointsToPath(points: Point[], precision: number, borderRadius?: number): string;
|
|
6
|
-
/**
|
|
7
|
-
* Snap path to grid by aligning consecutive points
|
|
8
|
-
*/
|
|
9
|
-
export declare function snapPathToGrid(points: Point[], gridSize: number): Point[];
|
|
10
|
-
//# sourceMappingURL=pathConverter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/cjs/svg/pathConverter.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.pointsToPath = pointsToPath;
|
|
7
|
-
exports.snapPathToGrid = snapPathToGrid;
|
|
8
|
-
/**
|
|
9
|
-
* Convert array of points to SVG path string
|
|
10
|
-
*/
|
|
11
|
-
function pointsToPath(points, precision, borderRadius = 0) {
|
|
12
|
-
if (points.length === 0) {
|
|
13
|
-
return '';
|
|
14
|
-
}
|
|
15
|
-
const factor = Math.pow(10, precision);
|
|
16
|
-
const roundCoord = value => Math.round(value * factor) / factor;
|
|
17
|
-
|
|
18
|
-
// If no border radius or only 2 points, use simple line path
|
|
19
|
-
if (borderRadius === 0 || points.length <= 2) {
|
|
20
|
-
const firstPoint = points[0];
|
|
21
|
-
let path = `M ${roundCoord(firstPoint.x)} ${roundCoord(firstPoint.y)}`;
|
|
22
|
-
for (let i = 1; i < points.length; i++) {
|
|
23
|
-
const point = points[i];
|
|
24
|
-
path += ` L ${roundCoord(point.x)} ${roundCoord(point.y)}`;
|
|
25
|
-
}
|
|
26
|
-
return path;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Start with M (moveTo) command for first point
|
|
30
|
-
const firstPoint = points[0];
|
|
31
|
-
let path = `M ${roundCoord(firstPoint.x)} ${roundCoord(firstPoint.y)}`;
|
|
32
|
-
|
|
33
|
-
// Process each segment with rounded corners
|
|
34
|
-
for (let i = 1; i < points.length - 1; i++) {
|
|
35
|
-
const prev = points[i - 1];
|
|
36
|
-
const current = points[i];
|
|
37
|
-
const next = points[i + 1];
|
|
38
|
-
|
|
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);
|
|
48
|
-
|
|
49
|
-
// Use the smaller of borderRadius or half the segment length
|
|
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;
|
|
57
|
-
|
|
58
|
-
// Calculate the point before the corner
|
|
59
|
-
const beforeCorner = {
|
|
60
|
-
x: current.x - ndx1 * radius,
|
|
61
|
-
y: current.y - ndy1 * radius
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Calculate the point after the corner
|
|
65
|
-
const afterCorner = {
|
|
66
|
-
x: current.x + ndx2 * radius,
|
|
67
|
-
y: current.y + ndy2 * radius
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Draw line to the point before corner
|
|
71
|
-
path += ` L ${roundCoord(beforeCorner.x)} ${roundCoord(beforeCorner.y)}`;
|
|
72
|
-
|
|
73
|
-
// Draw quadratic bezier curve for the rounded corner
|
|
74
|
-
// The control point is the actual corner point
|
|
75
|
-
path += ` Q ${roundCoord(current.x)} ${roundCoord(current.y)} ${roundCoord(afterCorner.x)} ${roundCoord(afterCorner.y)}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Draw line to the last point
|
|
79
|
-
const lastPoint = points[points.length - 1];
|
|
80
|
-
path += ` L ${roundCoord(lastPoint.x)} ${roundCoord(lastPoint.y)}`;
|
|
81
|
-
return path;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Snap path to grid by aligning consecutive points
|
|
86
|
-
*/
|
|
87
|
-
function snapPathToGrid(points, gridSize) {
|
|
88
|
-
if (points.length <= 1) {
|
|
89
|
-
return points;
|
|
90
|
-
}
|
|
91
|
-
const snappedPoints = [...points];
|
|
92
|
-
|
|
93
|
-
// Don't snap the first and last points (anchors)
|
|
94
|
-
// Don't snap the second and second-to-last points (extension points)
|
|
95
|
-
// Only snap intermediate segments
|
|
96
|
-
for (let i = 2; i < snappedPoints.length - 3; i++) {
|
|
97
|
-
const first = snappedPoints[i];
|
|
98
|
-
const second = snappedPoints[i + 1];
|
|
99
|
-
if (first.x === second.x) {
|
|
100
|
-
// Vertical line - snap X coordinate
|
|
101
|
-
const x = gridSize * Math.round(first.x / gridSize);
|
|
102
|
-
if (first.x !== x) {
|
|
103
|
-
first.x = x;
|
|
104
|
-
second.x = x;
|
|
105
|
-
}
|
|
106
|
-
} else if (first.y === second.y) {
|
|
107
|
-
// Horizontal line - snap Y coordinate
|
|
108
|
-
const y = gridSize * Math.round(first.y / gridSize);
|
|
109
|
-
if (first.y !== y) {
|
|
110
|
-
first.y = y;
|
|
111
|
-
second.y = y;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return snappedPoints;
|
|
116
|
-
}
|