@esengine/pathfinding 13.0.0 → 13.2.0
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/dist/{KDTree-Bw4Faf2O.d.ts → IIncrementalPathfinding-3qs7e_pO.d.ts} +1 -371
- package/dist/KDTree-2rs2EXvm.d.ts +407 -0
- package/dist/LinearProgram-DyD3pI6v.d.ts +56 -0
- package/dist/avoidance.d.ts +31 -0
- package/dist/avoidance.js +31 -0
- package/dist/avoidance.js.map +1 -0
- package/dist/chunk-JTZP55BJ.js +935 -0
- package/dist/chunk-JTZP55BJ.js.map +1 -0
- package/dist/chunk-KEYTX37K.js +1 -0
- package/dist/chunk-KEYTX37K.js.map +1 -0
- package/dist/chunk-T626JPC7.js +10 -0
- package/dist/chunk-T626JPC7.js.map +1 -0
- package/dist/{chunk-OA7ZZQMH.js → chunk-VNC2YAAL.js} +6 -908
- package/dist/chunk-VNC2YAAL.js.map +1 -0
- package/dist/{chunk-GTFFYRZM.js → chunk-YKA3PWU3.js} +1 -8
- package/dist/{chunk-GTFFYRZM.js.map → chunk-YKA3PWU3.js.map} +1 -1
- package/dist/ecs.d.ts +2 -1
- package/dist/ecs.js +10 -6
- package/dist/ecs.js.map +1 -1
- package/dist/index.d.ts +130 -52
- package/dist/index.js +887 -324
- package/dist/index.js.map +1 -1
- package/dist/nodes.js +4 -2
- package/dist/nodes.js.map +1 -1
- package/package.json +7 -3
- package/dist/chunk-OA7ZZQMH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CatmullRomSmoother,
|
|
3
3
|
CombinedSmoother,
|
|
4
|
-
DEFAULT_AGENT_PARAMS,
|
|
5
4
|
DEFAULT_GRID_OPTIONS,
|
|
6
|
-
DEFAULT_ORCA_CONFIG,
|
|
7
5
|
DEFAULT_PATHFINDING_OPTIONS,
|
|
8
6
|
DEFAULT_PATH_CACHE_CONFIG,
|
|
9
7
|
DIRECTIONS_4,
|
|
@@ -13,9 +11,7 @@ import {
|
|
|
13
11
|
GridNode,
|
|
14
12
|
IncrementalAStarPathfinder,
|
|
15
13
|
IndexedBinaryHeap,
|
|
16
|
-
KDTree,
|
|
17
14
|
LineOfSightSmoother,
|
|
18
|
-
ORCASolver,
|
|
19
15
|
ObstacleChangeManager,
|
|
20
16
|
PathCache,
|
|
21
17
|
PathValidator,
|
|
@@ -25,9 +21,7 @@ import {
|
|
|
25
21
|
createCombinedSmoother,
|
|
26
22
|
createGridMap,
|
|
27
23
|
createIncrementalAStarPathfinder,
|
|
28
|
-
createKDTree,
|
|
29
24
|
createLineOfSightSmoother,
|
|
30
|
-
createORCASolver,
|
|
31
25
|
createObstacleChangeManager,
|
|
32
26
|
createPathCache,
|
|
33
27
|
createPathValidator,
|
|
@@ -35,16 +29,27 @@ import {
|
|
|
35
29
|
euclideanDistance,
|
|
36
30
|
manhattanDistance,
|
|
37
31
|
octileDistance,
|
|
38
|
-
raycastLineOfSight
|
|
39
|
-
|
|
40
|
-
} from "./chunk-OA7ZZQMH.js";
|
|
32
|
+
raycastLineOfSight
|
|
33
|
+
} from "./chunk-VNC2YAAL.js";
|
|
41
34
|
import {
|
|
42
35
|
DEFAULT_REPLANNING_CONFIG,
|
|
43
36
|
EMPTY_PROGRESS,
|
|
44
|
-
PathfindingState
|
|
37
|
+
PathfindingState
|
|
38
|
+
} from "./chunk-YKA3PWU3.js";
|
|
39
|
+
import "./chunk-KEYTX37K.js";
|
|
40
|
+
import {
|
|
41
|
+
DEFAULT_AGENT_PARAMS,
|
|
42
|
+
DEFAULT_ORCA_CONFIG,
|
|
43
|
+
KDTree,
|
|
44
|
+
ORCASolver,
|
|
45
|
+
createKDTree,
|
|
46
|
+
createORCASolver,
|
|
47
|
+
solveORCALinearProgram
|
|
48
|
+
} from "./chunk-JTZP55BJ.js";
|
|
49
|
+
import {
|
|
45
50
|
__name,
|
|
46
51
|
__publicField
|
|
47
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-T626JPC7.js";
|
|
48
53
|
|
|
49
54
|
// src/core/BinaryHeap.ts
|
|
50
55
|
var _BinaryHeap = class _BinaryHeap {
|
|
@@ -1404,24 +1409,255 @@ __name(createJPSPathfinder, "createJPSPathfinder");
|
|
|
1404
1409
|
|
|
1405
1410
|
// src/core/HPAPathfinder.ts
|
|
1406
1411
|
var DEFAULT_HPA_CONFIG = {
|
|
1407
|
-
clusterSize:
|
|
1408
|
-
maxEntranceWidth:
|
|
1409
|
-
cacheInternalPaths: true
|
|
1412
|
+
clusterSize: 64,
|
|
1413
|
+
maxEntranceWidth: 16,
|
|
1414
|
+
cacheInternalPaths: true,
|
|
1415
|
+
entranceStrategy: "end",
|
|
1416
|
+
lazyIntraEdges: true
|
|
1410
1417
|
};
|
|
1418
|
+
var _a3;
|
|
1419
|
+
var SubMap = (_a3 = class {
|
|
1420
|
+
constructor(parentMap, originX, originY, width, height) {
|
|
1421
|
+
__publicField(this, "parentMap");
|
|
1422
|
+
__publicField(this, "originX");
|
|
1423
|
+
__publicField(this, "originY");
|
|
1424
|
+
__publicField(this, "width");
|
|
1425
|
+
__publicField(this, "height");
|
|
1426
|
+
this.parentMap = parentMap;
|
|
1427
|
+
this.originX = originX;
|
|
1428
|
+
this.originY = originY;
|
|
1429
|
+
this.width = width;
|
|
1430
|
+
this.height = height;
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* @zh 局部坐标转全局坐标
|
|
1434
|
+
* @en Convert local to global coordinates
|
|
1435
|
+
*/
|
|
1436
|
+
localToGlobal(localX, localY) {
|
|
1437
|
+
return {
|
|
1438
|
+
x: this.originX + localX,
|
|
1439
|
+
y: this.originY + localY
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* @zh 全局坐标转局部坐标
|
|
1444
|
+
* @en Convert global to local coordinates
|
|
1445
|
+
*/
|
|
1446
|
+
globalToLocal(globalX, globalY) {
|
|
1447
|
+
return {
|
|
1448
|
+
x: globalX - this.originX,
|
|
1449
|
+
y: globalY - this.originY
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
isWalkable(x, y) {
|
|
1453
|
+
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
return this.parentMap.isWalkable(this.originX + x, this.originY + y);
|
|
1457
|
+
}
|
|
1458
|
+
getNodeAt(x, y) {
|
|
1459
|
+
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
const globalNode = this.parentMap.getNodeAt(this.originX + x, this.originY + y);
|
|
1463
|
+
if (!globalNode) return null;
|
|
1464
|
+
return {
|
|
1465
|
+
id: y * this.width + x,
|
|
1466
|
+
position: {
|
|
1467
|
+
x,
|
|
1468
|
+
y
|
|
1469
|
+
},
|
|
1470
|
+
cost: globalNode.cost,
|
|
1471
|
+
walkable: globalNode.walkable
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
getNeighbors(node) {
|
|
1475
|
+
const neighbors = [];
|
|
1476
|
+
const { x, y } = node.position;
|
|
1477
|
+
const directions = [
|
|
1478
|
+
{
|
|
1479
|
+
dx: 0,
|
|
1480
|
+
dy: -1
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
dx: 1,
|
|
1484
|
+
dy: -1
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
dx: 1,
|
|
1488
|
+
dy: 0
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
dx: 1,
|
|
1492
|
+
dy: 1
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
dx: 0,
|
|
1496
|
+
dy: 1
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
dx: -1,
|
|
1500
|
+
dy: 1
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
dx: -1,
|
|
1504
|
+
dy: 0
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
dx: -1,
|
|
1508
|
+
dy: -1
|
|
1509
|
+
}
|
|
1510
|
+
// NW
|
|
1511
|
+
];
|
|
1512
|
+
for (const dir of directions) {
|
|
1513
|
+
const nx = x + dir.dx;
|
|
1514
|
+
const ny = y + dir.dy;
|
|
1515
|
+
if (nx < 0 || nx >= this.width || ny < 0 || ny >= this.height) {
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
if (!this.isWalkable(nx, ny)) {
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (dir.dx !== 0 && dir.dy !== 0) {
|
|
1522
|
+
if (!this.isWalkable(x + dir.dx, y) || !this.isWalkable(x, y + dir.dy)) {
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
const neighborNode = this.getNodeAt(nx, ny);
|
|
1527
|
+
if (neighborNode) {
|
|
1528
|
+
neighbors.push(neighborNode);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return neighbors;
|
|
1532
|
+
}
|
|
1533
|
+
heuristic(a, b) {
|
|
1534
|
+
const dx = Math.abs(a.x - b.x);
|
|
1535
|
+
const dy = Math.abs(a.y - b.y);
|
|
1536
|
+
return dx + dy + (Math.SQRT2 - 2) * Math.min(dx, dy);
|
|
1537
|
+
}
|
|
1538
|
+
getMovementCost(from, to) {
|
|
1539
|
+
const dx = Math.abs(to.position.x - from.position.x);
|
|
1540
|
+
const dy = Math.abs(to.position.y - from.position.y);
|
|
1541
|
+
const baseCost = dx !== 0 && dy !== 0 ? Math.SQRT2 : 1;
|
|
1542
|
+
return baseCost * to.cost;
|
|
1543
|
+
}
|
|
1544
|
+
}, __name(_a3, "SubMap"), _a3);
|
|
1545
|
+
var _a4;
|
|
1546
|
+
var Cluster = (_a4 = class {
|
|
1547
|
+
constructor(id, originX, originY, width, height, parentMap) {
|
|
1548
|
+
__publicField(this, "id");
|
|
1549
|
+
__publicField(this, "originX");
|
|
1550
|
+
__publicField(this, "originY");
|
|
1551
|
+
__publicField(this, "width");
|
|
1552
|
+
__publicField(this, "height");
|
|
1553
|
+
__publicField(this, "subMap");
|
|
1554
|
+
/** @zh 集群内的抽象节点 ID 列表 @en Abstract node IDs in this cluster */
|
|
1555
|
+
__publicField(this, "nodeIds", []);
|
|
1556
|
+
/** @zh 预计算的距离缓存 @en Precomputed distance cache */
|
|
1557
|
+
__publicField(this, "distanceCache", /* @__PURE__ */ new Map());
|
|
1558
|
+
/** @zh 预计算的路径缓存 @en Precomputed path cache */
|
|
1559
|
+
__publicField(this, "pathCache", /* @__PURE__ */ new Map());
|
|
1560
|
+
this.id = id;
|
|
1561
|
+
this.originX = originX;
|
|
1562
|
+
this.originY = originY;
|
|
1563
|
+
this.width = width;
|
|
1564
|
+
this.height = height;
|
|
1565
|
+
this.subMap = new SubMap(parentMap, originX, originY, width, height);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* @zh 检查点是否在集群内
|
|
1569
|
+
* @en Check if point is in cluster
|
|
1570
|
+
*/
|
|
1571
|
+
containsPoint(x, y) {
|
|
1572
|
+
return x >= this.originX && x < this.originX + this.width && y >= this.originY && y < this.originY + this.height;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* @zh 添加节点 ID
|
|
1576
|
+
* @en Add node ID
|
|
1577
|
+
*/
|
|
1578
|
+
addNodeId(nodeId) {
|
|
1579
|
+
if (!this.nodeIds.includes(nodeId)) {
|
|
1580
|
+
this.nodeIds.push(nodeId);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* @zh 移除节点 ID
|
|
1585
|
+
* @en Remove node ID
|
|
1586
|
+
*/
|
|
1587
|
+
removeNodeId(nodeId) {
|
|
1588
|
+
const idx = this.nodeIds.indexOf(nodeId);
|
|
1589
|
+
if (idx !== -1) {
|
|
1590
|
+
this.nodeIds.splice(idx, 1);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* @zh 生成缓存键
|
|
1595
|
+
* @en Generate cache key
|
|
1596
|
+
*/
|
|
1597
|
+
getCacheKey(fromId, toId) {
|
|
1598
|
+
return `${fromId}->${toId}`;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* @zh 设置缓存
|
|
1602
|
+
* @en Set cache
|
|
1603
|
+
*/
|
|
1604
|
+
setCache(fromId, toId, cost, path) {
|
|
1605
|
+
const key = this.getCacheKey(fromId, toId);
|
|
1606
|
+
this.distanceCache.set(key, cost);
|
|
1607
|
+
this.pathCache.set(key, path);
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* @zh 获取缓存的距离
|
|
1611
|
+
* @en Get cached distance
|
|
1612
|
+
*/
|
|
1613
|
+
getCachedDistance(fromId, toId) {
|
|
1614
|
+
return this.distanceCache.get(this.getCacheKey(fromId, toId));
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* @zh 获取缓存的路径
|
|
1618
|
+
* @en Get cached path
|
|
1619
|
+
*/
|
|
1620
|
+
getCachedPath(fromId, toId) {
|
|
1621
|
+
return this.pathCache.get(this.getCacheKey(fromId, toId));
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* @zh 清除缓存
|
|
1625
|
+
* @en Clear cache
|
|
1626
|
+
*/
|
|
1627
|
+
clearCache() {
|
|
1628
|
+
this.distanceCache.clear();
|
|
1629
|
+
this.pathCache.clear();
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* @zh 获取缓存大小
|
|
1633
|
+
* @en Get cache size
|
|
1634
|
+
*/
|
|
1635
|
+
getCacheSize() {
|
|
1636
|
+
return this.distanceCache.size;
|
|
1637
|
+
}
|
|
1638
|
+
}, __name(_a4, "Cluster"), _a4);
|
|
1411
1639
|
var _HPAPathfinder = class _HPAPathfinder {
|
|
1412
1640
|
constructor(map, config) {
|
|
1413
1641
|
__publicField(this, "map");
|
|
1414
1642
|
__publicField(this, "config");
|
|
1415
|
-
__publicField(this, "
|
|
1416
|
-
__publicField(this, "
|
|
1643
|
+
__publicField(this, "mapWidth");
|
|
1644
|
+
__publicField(this, "mapHeight");
|
|
1645
|
+
// 集群管理
|
|
1417
1646
|
__publicField(this, "clusters", []);
|
|
1418
|
-
__publicField(this, "entrances", []);
|
|
1419
|
-
__publicField(this, "abstractNodes", /* @__PURE__ */ new Map());
|
|
1420
1647
|
__publicField(this, "clusterGrid", []);
|
|
1421
|
-
__publicField(this, "
|
|
1648
|
+
__publicField(this, "clustersX", 0);
|
|
1649
|
+
__publicField(this, "clustersY", 0);
|
|
1650
|
+
// 抽象图
|
|
1651
|
+
__publicField(this, "abstractNodes", /* @__PURE__ */ new Map());
|
|
1652
|
+
__publicField(this, "nodesByCluster", /* @__PURE__ */ new Map());
|
|
1422
1653
|
__publicField(this, "nextNodeId", 0);
|
|
1423
|
-
|
|
1654
|
+
// 入口统计
|
|
1655
|
+
__publicField(this, "entranceCount", 0);
|
|
1656
|
+
// 内部寻路器
|
|
1424
1657
|
__publicField(this, "localPathfinder");
|
|
1658
|
+
// 完整路径缓存
|
|
1659
|
+
__publicField(this, "pathCache");
|
|
1660
|
+
__publicField(this, "mapVersion", 0);
|
|
1425
1661
|
__publicField(this, "preprocessed", false);
|
|
1426
1662
|
this.map = map;
|
|
1427
1663
|
this.config = {
|
|
@@ -1429,28 +1665,26 @@ var _HPAPathfinder = class _HPAPathfinder {
|
|
|
1429
1665
|
...config
|
|
1430
1666
|
};
|
|
1431
1667
|
const bounds = this.getMapBounds();
|
|
1432
|
-
this.
|
|
1433
|
-
this.
|
|
1668
|
+
this.mapWidth = bounds.width;
|
|
1669
|
+
this.mapHeight = bounds.height;
|
|
1434
1670
|
this.localPathfinder = new AStarPathfinder(map);
|
|
1671
|
+
this.pathCache = new PathCache({
|
|
1672
|
+
maxEntries: 1e3,
|
|
1673
|
+
ttlMs: 0
|
|
1674
|
+
});
|
|
1435
1675
|
}
|
|
1676
|
+
// =========================================================================
|
|
1677
|
+
// 公共 API | Public API
|
|
1678
|
+
// =========================================================================
|
|
1436
1679
|
/**
|
|
1437
1680
|
* @zh 预处理地图(构建抽象图)
|
|
1438
1681
|
* @en Preprocess map (build abstract graph)
|
|
1439
|
-
*
|
|
1440
|
-
* @zh 在地图变化后需要重新调用
|
|
1441
|
-
* @en Need to call again after map changes
|
|
1442
1682
|
*/
|
|
1443
1683
|
preprocess() {
|
|
1444
|
-
this.
|
|
1445
|
-
this.entrances = [];
|
|
1446
|
-
this.abstractNodes.clear();
|
|
1447
|
-
this.clusterGrid = [];
|
|
1448
|
-
this.internalPathCache.clear();
|
|
1449
|
-
this.nextEntranceId = 0;
|
|
1450
|
-
this.nextNodeId = 0;
|
|
1684
|
+
this.clear();
|
|
1451
1685
|
this.buildClusters();
|
|
1452
|
-
this.
|
|
1453
|
-
this.
|
|
1686
|
+
this.buildEntrances();
|
|
1687
|
+
this.buildIntraEdges();
|
|
1454
1688
|
this.preprocessed = true;
|
|
1455
1689
|
}
|
|
1456
1690
|
/**
|
|
@@ -1481,23 +1715,33 @@ var _HPAPathfinder = class _HPAPathfinder {
|
|
|
1481
1715
|
nodesSearched: 1
|
|
1482
1716
|
};
|
|
1483
1717
|
}
|
|
1718
|
+
const cached = this.pathCache.get(startX, startY, endX, endY, this.mapVersion);
|
|
1719
|
+
if (cached) {
|
|
1720
|
+
return cached;
|
|
1721
|
+
}
|
|
1484
1722
|
const startCluster = this.getClusterAt(startX, startY);
|
|
1485
1723
|
const endCluster = this.getClusterAt(endX, endY);
|
|
1486
1724
|
if (!startCluster || !endCluster) {
|
|
1487
1725
|
return EMPTY_PATH_RESULT;
|
|
1488
1726
|
}
|
|
1727
|
+
let result;
|
|
1489
1728
|
if (startCluster.id === endCluster.id) {
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1729
|
+
result = this.findLocalPath(startX, startY, endX, endY, opts);
|
|
1730
|
+
} else {
|
|
1731
|
+
const startTemp = this.insertTempNode(startX, startY, startCluster);
|
|
1732
|
+
const endTemp = this.insertTempNode(endX, endY, endCluster);
|
|
1733
|
+
const abstractPath = this.abstractSearch(startTemp, endTemp, opts);
|
|
1734
|
+
this.removeTempNode(startTemp, startCluster);
|
|
1735
|
+
this.removeTempNode(endTemp, endCluster);
|
|
1736
|
+
if (!abstractPath || abstractPath.length === 0) {
|
|
1737
|
+
return EMPTY_PATH_RESULT;
|
|
1738
|
+
}
|
|
1739
|
+
result = this.refinePath(abstractPath, startX, startY, endX, endY, opts);
|
|
1740
|
+
}
|
|
1741
|
+
if (result.found) {
|
|
1742
|
+
this.pathCache.set(startX, startY, endX, endY, result, this.mapVersion);
|
|
1499
1743
|
}
|
|
1500
|
-
return
|
|
1744
|
+
return result;
|
|
1501
1745
|
}
|
|
1502
1746
|
/**
|
|
1503
1747
|
* @zh 清理状态
|
|
@@ -1505,10 +1749,13 @@ var _HPAPathfinder = class _HPAPathfinder {
|
|
|
1505
1749
|
*/
|
|
1506
1750
|
clear() {
|
|
1507
1751
|
this.clusters = [];
|
|
1508
|
-
this.entrances = [];
|
|
1509
|
-
this.abstractNodes.clear();
|
|
1510
1752
|
this.clusterGrid = [];
|
|
1511
|
-
this.
|
|
1753
|
+
this.abstractNodes.clear();
|
|
1754
|
+
this.nodesByCluster.clear();
|
|
1755
|
+
this.nextNodeId = 0;
|
|
1756
|
+
this.entranceCount = 0;
|
|
1757
|
+
this.pathCache.invalidateAll();
|
|
1758
|
+
this.mapVersion++;
|
|
1512
1759
|
this.preprocessed = false;
|
|
1513
1760
|
}
|
|
1514
1761
|
/**
|
|
@@ -1516,19 +1763,34 @@ var _HPAPathfinder = class _HPAPathfinder {
|
|
|
1516
1763
|
* @en Notify map region change
|
|
1517
1764
|
*/
|
|
1518
1765
|
notifyRegionChange(minX, minY, maxX, maxY) {
|
|
1519
|
-
this.
|
|
1520
|
-
|
|
1766
|
+
const affectedClusters = this.getAffectedClusters(minX, minY, maxX, maxY);
|
|
1767
|
+
for (const cluster of affectedClusters) {
|
|
1768
|
+
cluster.clearCache();
|
|
1769
|
+
for (const nodeId of cluster.nodeIds) {
|
|
1770
|
+
const node = this.abstractNodes.get(nodeId);
|
|
1771
|
+
if (node) {
|
|
1772
|
+
node.edges = node.edges.filter((e) => e.isInterEdge);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
this.buildClusterIntraEdges(cluster);
|
|
1776
|
+
}
|
|
1777
|
+
this.pathCache.invalidateRegion(minX, minY, maxX, maxY);
|
|
1778
|
+
this.mapVersion++;
|
|
1521
1779
|
}
|
|
1522
1780
|
/**
|
|
1523
1781
|
* @zh 获取预处理统计信息
|
|
1524
1782
|
* @en Get preprocessing statistics
|
|
1525
1783
|
*/
|
|
1526
1784
|
getStats() {
|
|
1785
|
+
let cacheSize = 0;
|
|
1786
|
+
for (const cluster of this.clusters) {
|
|
1787
|
+
cacheSize += cluster.getCacheSize();
|
|
1788
|
+
}
|
|
1527
1789
|
return {
|
|
1528
1790
|
clusters: this.clusters.length,
|
|
1529
|
-
entrances: this.
|
|
1791
|
+
entrances: this.entranceCount,
|
|
1530
1792
|
abstractNodes: this.abstractNodes.size,
|
|
1531
|
-
cacheSize
|
|
1793
|
+
cacheSize
|
|
1532
1794
|
};
|
|
1533
1795
|
}
|
|
1534
1796
|
// =========================================================================
|
|
@@ -1547,354 +1809,657 @@ var _HPAPathfinder = class _HPAPathfinder {
|
|
|
1547
1809
|
height: 1e3
|
|
1548
1810
|
};
|
|
1549
1811
|
}
|
|
1812
|
+
/**
|
|
1813
|
+
* @zh 构建集群
|
|
1814
|
+
* @en Build clusters
|
|
1815
|
+
*/
|
|
1550
1816
|
buildClusters() {
|
|
1551
1817
|
const clusterSize = this.config.clusterSize;
|
|
1552
|
-
|
|
1553
|
-
|
|
1818
|
+
this.clustersX = Math.ceil(this.mapWidth / clusterSize);
|
|
1819
|
+
this.clustersY = Math.ceil(this.mapHeight / clusterSize);
|
|
1554
1820
|
this.clusterGrid = [];
|
|
1555
|
-
for (let
|
|
1556
|
-
this.clusterGrid[
|
|
1821
|
+
for (let cx = 0; cx < this.clustersX; cx++) {
|
|
1822
|
+
this.clusterGrid[cx] = [];
|
|
1823
|
+
for (let cy = 0; cy < this.clustersY; cy++) {
|
|
1824
|
+
this.clusterGrid[cx][cy] = null;
|
|
1825
|
+
}
|
|
1557
1826
|
}
|
|
1558
1827
|
let clusterId = 0;
|
|
1559
|
-
for (let cy = 0; cy < clustersY; cy++) {
|
|
1560
|
-
for (let cx = 0; cx < clustersX; cx++) {
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
height: Math.min(clusterSize, this.height - cy * clusterSize),
|
|
1567
|
-
entrances: []
|
|
1568
|
-
};
|
|
1828
|
+
for (let cy = 0; cy < this.clustersY; cy++) {
|
|
1829
|
+
for (let cx = 0; cx < this.clustersX; cx++) {
|
|
1830
|
+
const originX = cx * clusterSize;
|
|
1831
|
+
const originY = cy * clusterSize;
|
|
1832
|
+
const width = Math.min(clusterSize, this.mapWidth - originX);
|
|
1833
|
+
const height = Math.min(clusterSize, this.mapHeight - originY);
|
|
1834
|
+
const cluster = new Cluster(clusterId, originX, originY, width, height, this.map);
|
|
1569
1835
|
this.clusters.push(cluster);
|
|
1570
|
-
this.clusterGrid[cx][cy] =
|
|
1836
|
+
this.clusterGrid[cx][cy] = clusterId;
|
|
1837
|
+
this.nodesByCluster.set(clusterId, []);
|
|
1838
|
+
clusterId++;
|
|
1571
1839
|
}
|
|
1572
1840
|
}
|
|
1573
1841
|
}
|
|
1574
|
-
|
|
1842
|
+
/**
|
|
1843
|
+
* @zh 检测入口并创建抽象节点
|
|
1844
|
+
* @en Detect entrances and create abstract nodes
|
|
1845
|
+
*/
|
|
1846
|
+
buildEntrances() {
|
|
1575
1847
|
const clusterSize = this.config.clusterSize;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
const
|
|
1581
|
-
if (
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
this.
|
|
1848
|
+
for (let cy = 0; cy < this.clustersY; cy++) {
|
|
1849
|
+
for (let cx = 0; cx < this.clustersX; cx++) {
|
|
1850
|
+
const clusterId = this.clusterGrid[cx][cy];
|
|
1851
|
+
if (clusterId === null) continue;
|
|
1852
|
+
const cluster1 = this.clusters[clusterId];
|
|
1853
|
+
if (cx < this.clustersX - 1) {
|
|
1854
|
+
const cluster2Id = this.clusterGrid[cx + 1][cy];
|
|
1855
|
+
if (cluster2Id !== null) {
|
|
1856
|
+
const cluster2 = this.clusters[cluster2Id];
|
|
1857
|
+
this.detectAndCreateEntrances(cluster1, cluster2, "vertical");
|
|
1586
1858
|
}
|
|
1587
1859
|
}
|
|
1588
|
-
if (cy < clustersY - 1) {
|
|
1589
|
-
const
|
|
1590
|
-
if (
|
|
1591
|
-
this.
|
|
1860
|
+
if (cy < this.clustersY - 1) {
|
|
1861
|
+
const cluster2Id = this.clusterGrid[cx][cy + 1];
|
|
1862
|
+
if (cluster2Id !== null) {
|
|
1863
|
+
const cluster2 = this.clusters[cluster2Id];
|
|
1864
|
+
this.detectAndCreateEntrances(cluster1, cluster2, "horizontal");
|
|
1592
1865
|
}
|
|
1593
1866
|
}
|
|
1594
1867
|
}
|
|
1595
1868
|
}
|
|
1596
1869
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1870
|
+
/**
|
|
1871
|
+
* @zh 检测并创建两个相邻集群之间的入口
|
|
1872
|
+
* @en Detect and create entrances between two adjacent clusters
|
|
1873
|
+
*/
|
|
1874
|
+
detectAndCreateEntrances(cluster1, cluster2, boundaryDirection) {
|
|
1875
|
+
const spans = this.detectEntranceSpans(cluster1, cluster2, boundaryDirection);
|
|
1876
|
+
for (const span of spans) {
|
|
1877
|
+
this.createEntranceNodes(cluster1, cluster2, span, boundaryDirection);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* @zh 检测边界上的连续可通行区间
|
|
1882
|
+
* @en Detect continuous walkable spans on boundary
|
|
1883
|
+
*/
|
|
1884
|
+
detectEntranceSpans(cluster1, cluster2, boundaryDirection) {
|
|
1885
|
+
const spans = [];
|
|
1886
|
+
if (boundaryDirection === "vertical") {
|
|
1887
|
+
const x1 = cluster1.originX + cluster1.width - 1;
|
|
1888
|
+
const x2 = cluster2.originX;
|
|
1889
|
+
const startY = Math.max(cluster1.originY, cluster2.originY);
|
|
1890
|
+
const endY = Math.min(cluster1.originY + cluster1.height, cluster2.originY + cluster2.height);
|
|
1891
|
+
let spanStart = null;
|
|
1606
1892
|
for (let y = startY; y < endY; y++) {
|
|
1607
1893
|
const walkable1 = this.map.isWalkable(x1, y);
|
|
1608
1894
|
const walkable2 = this.map.isWalkable(x2, y);
|
|
1609
1895
|
if (walkable1 && walkable2) {
|
|
1610
|
-
if (
|
|
1611
|
-
|
|
1612
|
-
entranceLength = 1;
|
|
1613
|
-
} else {
|
|
1614
|
-
entranceLength++;
|
|
1896
|
+
if (spanStart === null) {
|
|
1897
|
+
spanStart = y;
|
|
1615
1898
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1899
|
+
} else {
|
|
1900
|
+
if (spanStart !== null) {
|
|
1901
|
+
spans.push({
|
|
1902
|
+
start: spanStart,
|
|
1903
|
+
end: y - 1
|
|
1904
|
+
});
|
|
1905
|
+
spanStart = null;
|
|
1620
1906
|
}
|
|
1621
|
-
} else if (entranceStart !== null) {
|
|
1622
|
-
this.createEntrance(cluster1, cluster2, x1, x2, entranceStart, entranceStart + entranceLength - 1, "horizontal");
|
|
1623
|
-
entranceStart = null;
|
|
1624
|
-
entranceLength = 0;
|
|
1625
1907
|
}
|
|
1626
1908
|
}
|
|
1909
|
+
if (spanStart !== null) {
|
|
1910
|
+
spans.push({
|
|
1911
|
+
start: spanStart,
|
|
1912
|
+
end: endY - 1
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1627
1915
|
} else {
|
|
1628
|
-
const y1 = cluster1.
|
|
1629
|
-
const y2 = cluster2.
|
|
1630
|
-
const startX = Math.max(cluster1.
|
|
1631
|
-
const endX = Math.min(cluster1.
|
|
1916
|
+
const y1 = cluster1.originY + cluster1.height - 1;
|
|
1917
|
+
const y2 = cluster2.originY;
|
|
1918
|
+
const startX = Math.max(cluster1.originX, cluster2.originX);
|
|
1919
|
+
const endX = Math.min(cluster1.originX + cluster1.width, cluster2.originX + cluster2.width);
|
|
1920
|
+
let spanStart = null;
|
|
1632
1921
|
for (let x = startX; x < endX; x++) {
|
|
1633
1922
|
const walkable1 = this.map.isWalkable(x, y1);
|
|
1634
1923
|
const walkable2 = this.map.isWalkable(x, y2);
|
|
1635
1924
|
if (walkable1 && walkable2) {
|
|
1636
|
-
if (
|
|
1637
|
-
|
|
1638
|
-
entranceLength = 1;
|
|
1639
|
-
} else {
|
|
1640
|
-
entranceLength++;
|
|
1925
|
+
if (spanStart === null) {
|
|
1926
|
+
spanStart = x;
|
|
1641
1927
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1928
|
+
} else {
|
|
1929
|
+
if (spanStart !== null) {
|
|
1930
|
+
spans.push({
|
|
1931
|
+
start: spanStart,
|
|
1932
|
+
end: x - 1
|
|
1933
|
+
});
|
|
1934
|
+
spanStart = null;
|
|
1646
1935
|
}
|
|
1647
|
-
} else if (entranceStart !== null) {
|
|
1648
|
-
this.createEntrance(cluster1, cluster2, entranceStart, entranceStart + entranceLength - 1, y1, y2, "vertical");
|
|
1649
|
-
entranceStart = null;
|
|
1650
|
-
entranceLength = 0;
|
|
1651
1936
|
}
|
|
1652
1937
|
}
|
|
1938
|
+
if (spanStart !== null) {
|
|
1939
|
+
spans.push({
|
|
1940
|
+
start: spanStart,
|
|
1941
|
+
end: endX - 1
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1653
1944
|
}
|
|
1945
|
+
return spans;
|
|
1654
1946
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
x: coord2Start,
|
|
1667
|
-
y: midY
|
|
1668
|
-
};
|
|
1669
|
-
center = {
|
|
1670
|
-
x: coord1Start,
|
|
1671
|
-
y: midY
|
|
1672
|
-
};
|
|
1947
|
+
/**
|
|
1948
|
+
* @zh 为入口区间创建抽象节点
|
|
1949
|
+
* @en Create abstract nodes for entrance span
|
|
1950
|
+
*/
|
|
1951
|
+
createEntranceNodes(cluster1, cluster2, span, boundaryDirection) {
|
|
1952
|
+
const spanLength = span.end - span.start + 1;
|
|
1953
|
+
const maxWidth = this.config.maxEntranceWidth;
|
|
1954
|
+
const strategy = this.config.entranceStrategy;
|
|
1955
|
+
const positions = [];
|
|
1956
|
+
if (spanLength <= maxWidth) {
|
|
1957
|
+
positions.push(Math.floor((span.start + span.end) / 2));
|
|
1673
1958
|
} else {
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1959
|
+
const numNodes = Math.ceil(spanLength / maxWidth);
|
|
1960
|
+
const spacing = spanLength / numNodes;
|
|
1961
|
+
for (let i = 0; i < numNodes; i++) {
|
|
1962
|
+
const pos = Math.floor(span.start + spacing * (i + 0.5));
|
|
1963
|
+
positions.push(Math.min(pos, span.end));
|
|
1964
|
+
}
|
|
1965
|
+
if (strategy === "end") {
|
|
1966
|
+
if (!positions.includes(span.start)) {
|
|
1967
|
+
positions.unshift(span.start);
|
|
1968
|
+
}
|
|
1969
|
+
if (!positions.includes(span.end)) {
|
|
1970
|
+
positions.push(span.end);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1687
1973
|
}
|
|
1688
|
-
const
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1974
|
+
for (const pos of positions) {
|
|
1975
|
+
let p1, p2;
|
|
1976
|
+
if (boundaryDirection === "vertical") {
|
|
1977
|
+
p1 = {
|
|
1978
|
+
x: cluster1.originX + cluster1.width - 1,
|
|
1979
|
+
y: pos
|
|
1980
|
+
};
|
|
1981
|
+
p2 = {
|
|
1982
|
+
x: cluster2.originX,
|
|
1983
|
+
y: pos
|
|
1984
|
+
};
|
|
1985
|
+
} else {
|
|
1986
|
+
p1 = {
|
|
1987
|
+
x: pos,
|
|
1988
|
+
y: cluster1.originY + cluster1.height - 1
|
|
1989
|
+
};
|
|
1990
|
+
p2 = {
|
|
1991
|
+
x: pos,
|
|
1992
|
+
y: cluster2.originY
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
const node1 = this.createAbstractNode(p1, cluster1);
|
|
1996
|
+
const node2 = this.createAbstractNode(p2, cluster2);
|
|
1997
|
+
const interCost = 1;
|
|
1704
1998
|
node1.edges.push({
|
|
1705
1999
|
targetNodeId: node2.id,
|
|
1706
|
-
cost:
|
|
1707
|
-
isInterEdge: true
|
|
2000
|
+
cost: interCost,
|
|
2001
|
+
isInterEdge: true,
|
|
2002
|
+
innerPath: null
|
|
1708
2003
|
});
|
|
1709
2004
|
node2.edges.push({
|
|
1710
2005
|
targetNodeId: node1.id,
|
|
1711
|
-
cost:
|
|
1712
|
-
isInterEdge: true
|
|
2006
|
+
cost: interCost,
|
|
2007
|
+
isInterEdge: true,
|
|
2008
|
+
innerPath: null
|
|
1713
2009
|
});
|
|
1714
|
-
|
|
1715
|
-
for (const cluster of this.clusters) {
|
|
1716
|
-
this.connectIntraClusterNodes(cluster);
|
|
2010
|
+
this.entranceCount++;
|
|
1717
2011
|
}
|
|
1718
2012
|
}
|
|
1719
|
-
|
|
2013
|
+
/**
|
|
2014
|
+
* @zh 创建抽象节点
|
|
2015
|
+
* @en Create abstract node
|
|
2016
|
+
*/
|
|
2017
|
+
createAbstractNode(position, cluster) {
|
|
2018
|
+
const concreteId = position.y * this.mapWidth + position.x;
|
|
2019
|
+
for (const nodeId of cluster.nodeIds) {
|
|
2020
|
+
const existing = this.abstractNodes.get(nodeId);
|
|
2021
|
+
if (existing && existing.concreteNodeId === concreteId) {
|
|
2022
|
+
return existing;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
1720
2025
|
const node = {
|
|
1721
2026
|
id: this.nextNodeId++,
|
|
1722
|
-
position
|
|
1723
|
-
|
|
1724
|
-
|
|
2027
|
+
position: {
|
|
2028
|
+
x: position.x,
|
|
2029
|
+
y: position.y
|
|
2030
|
+
},
|
|
2031
|
+
clusterId: cluster.id,
|
|
2032
|
+
concreteNodeId: concreteId,
|
|
1725
2033
|
edges: []
|
|
1726
2034
|
};
|
|
1727
2035
|
this.abstractNodes.set(node.id, node);
|
|
2036
|
+
cluster.addNodeId(node.id);
|
|
2037
|
+
const clusterNodes = this.nodesByCluster.get(cluster.id);
|
|
2038
|
+
if (clusterNodes) {
|
|
2039
|
+
clusterNodes.push(node.id);
|
|
2040
|
+
}
|
|
1728
2041
|
return node;
|
|
1729
2042
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
2043
|
+
/**
|
|
2044
|
+
* @zh 构建所有集群的 intra-edges
|
|
2045
|
+
* @en Build intra-edges for all clusters
|
|
2046
|
+
*/
|
|
2047
|
+
buildIntraEdges() {
|
|
2048
|
+
for (const cluster of this.clusters) {
|
|
2049
|
+
this.buildClusterIntraEdges(cluster);
|
|
1736
2050
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* @zh 构建单个集群的 intra-edges
|
|
2054
|
+
* @en Build intra-edges for single cluster
|
|
2055
|
+
*/
|
|
2056
|
+
buildClusterIntraEdges(cluster) {
|
|
2057
|
+
const nodeIds = cluster.nodeIds;
|
|
2058
|
+
if (nodeIds.length < 2) return;
|
|
2059
|
+
if (this.config.lazyIntraEdges) {
|
|
2060
|
+
this.buildLazyIntraEdges(cluster);
|
|
2061
|
+
} else {
|
|
2062
|
+
this.buildEagerIntraEdges(cluster);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* @zh 延迟构建 intra-edges(只用启发式距离)
|
|
2067
|
+
* @en Build lazy intra-edges (using heuristic distance only)
|
|
2068
|
+
*/
|
|
2069
|
+
buildLazyIntraEdges(cluster) {
|
|
2070
|
+
const nodeIds = cluster.nodeIds;
|
|
2071
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
2072
|
+
for (let j = i + 1; j < nodeIds.length; j++) {
|
|
2073
|
+
const node1 = this.abstractNodes.get(nodeIds[i]);
|
|
2074
|
+
const node2 = this.abstractNodes.get(nodeIds[j]);
|
|
2075
|
+
const heuristicCost = this.heuristic(node1.position, node2.position);
|
|
1742
2076
|
node1.edges.push({
|
|
1743
2077
|
targetNodeId: node2.id,
|
|
1744
|
-
cost,
|
|
1745
|
-
isInterEdge: false
|
|
2078
|
+
cost: heuristicCost,
|
|
2079
|
+
isInterEdge: false,
|
|
2080
|
+
innerPath: null
|
|
2081
|
+
// 标记为未计算
|
|
1746
2082
|
});
|
|
1747
2083
|
node2.edges.push({
|
|
1748
2084
|
targetNodeId: node1.id,
|
|
1749
|
-
cost,
|
|
1750
|
-
isInterEdge: false
|
|
2085
|
+
cost: heuristicCost,
|
|
2086
|
+
isInterEdge: false,
|
|
2087
|
+
innerPath: null
|
|
1751
2088
|
});
|
|
1752
2089
|
}
|
|
1753
2090
|
}
|
|
1754
2091
|
}
|
|
2092
|
+
/**
|
|
2093
|
+
* @zh 立即构建 intra-edges(计算真实路径)
|
|
2094
|
+
* @en Build eager intra-edges (compute actual paths)
|
|
2095
|
+
*/
|
|
2096
|
+
buildEagerIntraEdges(cluster) {
|
|
2097
|
+
const nodeIds = cluster.nodeIds;
|
|
2098
|
+
const subPathfinder = new AStarPathfinder(cluster.subMap);
|
|
2099
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
2100
|
+
for (let j = i + 1; j < nodeIds.length; j++) {
|
|
2101
|
+
const node1 = this.abstractNodes.get(nodeIds[i]);
|
|
2102
|
+
const node2 = this.abstractNodes.get(nodeIds[j]);
|
|
2103
|
+
const local1 = cluster.subMap.globalToLocal(node1.position.x, node1.position.y);
|
|
2104
|
+
const local2 = cluster.subMap.globalToLocal(node2.position.x, node2.position.y);
|
|
2105
|
+
const result = subPathfinder.findPath(local1.x, local1.y, local2.x, local2.y);
|
|
2106
|
+
if (result.found && result.path.length > 0) {
|
|
2107
|
+
const globalPath = result.path.map((p) => {
|
|
2108
|
+
const global = cluster.subMap.localToGlobal(p.x, p.y);
|
|
2109
|
+
return global.y * this.mapWidth + global.x;
|
|
2110
|
+
});
|
|
2111
|
+
if (this.config.cacheInternalPaths) {
|
|
2112
|
+
cluster.setCache(node1.id, node2.id, result.cost, globalPath);
|
|
2113
|
+
cluster.setCache(node2.id, node1.id, result.cost, [
|
|
2114
|
+
...globalPath
|
|
2115
|
+
].reverse());
|
|
2116
|
+
}
|
|
2117
|
+
node1.edges.push({
|
|
2118
|
+
targetNodeId: node2.id,
|
|
2119
|
+
cost: result.cost,
|
|
2120
|
+
isInterEdge: false,
|
|
2121
|
+
innerPath: this.config.cacheInternalPaths ? globalPath : null
|
|
2122
|
+
});
|
|
2123
|
+
node2.edges.push({
|
|
2124
|
+
targetNodeId: node1.id,
|
|
2125
|
+
cost: result.cost,
|
|
2126
|
+
isInterEdge: false,
|
|
2127
|
+
innerPath: this.config.cacheInternalPaths ? [
|
|
2128
|
+
...globalPath
|
|
2129
|
+
].reverse() : null
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* @zh 按需计算 intra-edge 的真实路径
|
|
2137
|
+
* @en Compute actual path for intra-edge on demand
|
|
2138
|
+
*/
|
|
2139
|
+
computeIntraEdgePath(fromNode, toNode, edge) {
|
|
2140
|
+
const cluster = this.clusters[fromNode.clusterId];
|
|
2141
|
+
if (!cluster) return null;
|
|
2142
|
+
const cachedPath = cluster.getCachedPath(fromNode.id, toNode.id);
|
|
2143
|
+
const cachedCost = cluster.getCachedDistance(fromNode.id, toNode.id);
|
|
2144
|
+
if (cachedPath && cachedCost !== void 0) {
|
|
2145
|
+
edge.cost = cachedCost;
|
|
2146
|
+
edge.innerPath = cachedPath;
|
|
2147
|
+
return {
|
|
2148
|
+
cost: cachedCost,
|
|
2149
|
+
path: cachedPath
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
const subPathfinder = new AStarPathfinder(cluster.subMap);
|
|
2153
|
+
const local1 = cluster.subMap.globalToLocal(fromNode.position.x, fromNode.position.y);
|
|
2154
|
+
const local2 = cluster.subMap.globalToLocal(toNode.position.x, toNode.position.y);
|
|
2155
|
+
const result = subPathfinder.findPath(local1.x, local1.y, local2.x, local2.y);
|
|
2156
|
+
if (result.found && result.path.length > 0) {
|
|
2157
|
+
const globalPath = result.path.map((p) => {
|
|
2158
|
+
const global = cluster.subMap.localToGlobal(p.x, p.y);
|
|
2159
|
+
return global.y * this.mapWidth + global.x;
|
|
2160
|
+
});
|
|
2161
|
+
if (this.config.cacheInternalPaths) {
|
|
2162
|
+
cluster.setCache(fromNode.id, toNode.id, result.cost, globalPath);
|
|
2163
|
+
cluster.setCache(toNode.id, fromNode.id, result.cost, [
|
|
2164
|
+
...globalPath
|
|
2165
|
+
].reverse());
|
|
2166
|
+
}
|
|
2167
|
+
edge.cost = result.cost;
|
|
2168
|
+
edge.innerPath = globalPath;
|
|
2169
|
+
const reverseEdge = toNode.edges.find((e) => e.targetNodeId === fromNode.id);
|
|
2170
|
+
if (reverseEdge) {
|
|
2171
|
+
reverseEdge.cost = result.cost;
|
|
2172
|
+
reverseEdge.innerPath = [
|
|
2173
|
+
...globalPath
|
|
2174
|
+
].reverse();
|
|
2175
|
+
}
|
|
2176
|
+
return {
|
|
2177
|
+
cost: result.cost,
|
|
2178
|
+
path: globalPath
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
return null;
|
|
2182
|
+
}
|
|
1755
2183
|
// =========================================================================
|
|
1756
2184
|
// 搜索方法 | Search Methods
|
|
1757
2185
|
// =========================================================================
|
|
2186
|
+
/**
|
|
2187
|
+
* @zh 获取指定位置的集群
|
|
2188
|
+
* @en Get cluster at position
|
|
2189
|
+
*/
|
|
1758
2190
|
getClusterAt(x, y) {
|
|
2191
|
+
const cx = Math.floor(x / this.config.clusterSize);
|
|
2192
|
+
const cy = Math.floor(y / this.config.clusterSize);
|
|
2193
|
+
if (cx < 0 || cx >= this.clustersX || cy < 0 || cy >= this.clustersY) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
const clusterId = this.clusterGrid[cx]?.[cy];
|
|
2197
|
+
if (clusterId === null || clusterId === void 0) {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
return this.clusters[clusterId] || null;
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* @zh 获取受影响的集群
|
|
2204
|
+
* @en Get affected clusters
|
|
2205
|
+
*/
|
|
2206
|
+
getAffectedClusters(minX, minY, maxX, maxY) {
|
|
2207
|
+
const affected = [];
|
|
1759
2208
|
const clusterSize = this.config.clusterSize;
|
|
1760
|
-
const
|
|
1761
|
-
const
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2209
|
+
const minCX = Math.floor(minX / clusterSize);
|
|
2210
|
+
const maxCX = Math.floor(maxX / clusterSize);
|
|
2211
|
+
const minCY = Math.floor(minY / clusterSize);
|
|
2212
|
+
const maxCY = Math.floor(maxY / clusterSize);
|
|
2213
|
+
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
2214
|
+
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
2215
|
+
if (cx >= 0 && cx < this.clustersX && cy >= 0 && cy < this.clustersY) {
|
|
2216
|
+
const clusterId = this.clusterGrid[cx]?.[cy];
|
|
2217
|
+
if (clusterId !== null && clusterId !== void 0) {
|
|
2218
|
+
affected.push(this.clusters[clusterId]);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
return affected;
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* @zh 插入临时节点
|
|
2227
|
+
* @en Insert temporary node
|
|
2228
|
+
*/
|
|
2229
|
+
insertTempNode(x, y, cluster) {
|
|
2230
|
+
const concreteId = y * this.mapWidth + x;
|
|
2231
|
+
for (const nodeId of cluster.nodeIds) {
|
|
2232
|
+
const existing = this.abstractNodes.get(nodeId);
|
|
2233
|
+
if (existing && existing.concreteNodeId === concreteId) {
|
|
2234
|
+
return existing;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
const tempNode = {
|
|
2238
|
+
id: this.nextNodeId++,
|
|
2239
|
+
position: {
|
|
2240
|
+
x,
|
|
2241
|
+
y
|
|
2242
|
+
},
|
|
2243
|
+
clusterId: cluster.id,
|
|
2244
|
+
concreteNodeId: concreteId,
|
|
2245
|
+
edges: []
|
|
2246
|
+
};
|
|
2247
|
+
this.abstractNodes.set(tempNode.id, tempNode);
|
|
2248
|
+
cluster.addNodeId(tempNode.id);
|
|
2249
|
+
const subPathfinder = new AStarPathfinder(cluster.subMap);
|
|
2250
|
+
const localPos = cluster.subMap.globalToLocal(x, y);
|
|
2251
|
+
for (const existingNodeId of cluster.nodeIds) {
|
|
2252
|
+
if (existingNodeId === tempNode.id) continue;
|
|
2253
|
+
const existingNode = this.abstractNodes.get(existingNodeId);
|
|
2254
|
+
if (!existingNode) continue;
|
|
2255
|
+
const targetLocalPos = cluster.subMap.globalToLocal(existingNode.position.x, existingNode.position.y);
|
|
2256
|
+
const result = subPathfinder.findPath(localPos.x, localPos.y, targetLocalPos.x, targetLocalPos.y);
|
|
2257
|
+
if (result.found && result.path.length > 0) {
|
|
2258
|
+
const globalPath = result.path.map((p) => {
|
|
2259
|
+
const global = cluster.subMap.localToGlobal(p.x, p.y);
|
|
2260
|
+
return global.y * this.mapWidth + global.x;
|
|
2261
|
+
});
|
|
1777
2262
|
tempNode.edges.push({
|
|
1778
|
-
targetNodeId:
|
|
1779
|
-
cost,
|
|
1780
|
-
isInterEdge: false
|
|
2263
|
+
targetNodeId: existingNode.id,
|
|
2264
|
+
cost: result.cost,
|
|
2265
|
+
isInterEdge: false,
|
|
2266
|
+
innerPath: globalPath
|
|
1781
2267
|
});
|
|
1782
|
-
|
|
2268
|
+
existingNode.edges.push({
|
|
1783
2269
|
targetNodeId: tempNode.id,
|
|
1784
|
-
cost,
|
|
1785
|
-
isInterEdge: false
|
|
2270
|
+
cost: result.cost,
|
|
2271
|
+
isInterEdge: false,
|
|
2272
|
+
innerPath: [
|
|
2273
|
+
...globalPath
|
|
2274
|
+
].reverse()
|
|
1786
2275
|
});
|
|
1787
2276
|
}
|
|
1788
2277
|
}
|
|
1789
|
-
return
|
|
2278
|
+
return tempNode;
|
|
1790
2279
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
2280
|
+
/**
|
|
2281
|
+
* @zh 移除临时节点
|
|
2282
|
+
* @en Remove temporary node
|
|
2283
|
+
*/
|
|
2284
|
+
removeTempNode(node, cluster) {
|
|
2285
|
+
for (const existingNodeId of cluster.nodeIds) {
|
|
2286
|
+
if (existingNodeId === node.id) continue;
|
|
2287
|
+
const existingNode = this.abstractNodes.get(existingNodeId);
|
|
2288
|
+
if (existingNode) {
|
|
2289
|
+
existingNode.edges = existingNode.edges.filter((e) => e.targetNodeId !== node.id);
|
|
1798
2290
|
}
|
|
1799
|
-
this.abstractNodes.delete(node.id);
|
|
1800
2291
|
}
|
|
2292
|
+
cluster.removeNodeId(node.id);
|
|
2293
|
+
this.abstractNodes.delete(node.id);
|
|
1801
2294
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
const openList = new
|
|
1808
|
-
const
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2295
|
+
/**
|
|
2296
|
+
* @zh 在抽象图上进行 A* 搜索
|
|
2297
|
+
* @en Perform A* search on abstract graph
|
|
2298
|
+
*/
|
|
2299
|
+
abstractSearch(startNode, endNode, opts) {
|
|
2300
|
+
const openList = new IndexedBinaryHeap((a, b) => a.f - b.f);
|
|
2301
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
2302
|
+
const endPosition = endNode.position;
|
|
2303
|
+
const h = this.heuristic(startNode.position, endPosition) * opts.heuristicWeight;
|
|
2304
|
+
const startSearchNode = {
|
|
2305
|
+
node: startNode,
|
|
2306
|
+
g: 0,
|
|
2307
|
+
h,
|
|
2308
|
+
f: h,
|
|
2309
|
+
parent: null,
|
|
2310
|
+
closed: false,
|
|
2311
|
+
opened: true,
|
|
2312
|
+
heapIndex: -1
|
|
2313
|
+
};
|
|
2314
|
+
openList.push(startSearchNode);
|
|
2315
|
+
nodeMap.set(startNode.id, startSearchNode);
|
|
1819
2316
|
let nodesSearched = 0;
|
|
1820
2317
|
while (!openList.isEmpty && nodesSearched < opts.maxNodes) {
|
|
1821
2318
|
const current = openList.pop();
|
|
2319
|
+
current.closed = true;
|
|
1822
2320
|
nodesSearched++;
|
|
1823
|
-
if (
|
|
1824
|
-
return this.
|
|
1825
|
-
}
|
|
1826
|
-
if (closedSet.has(current.abstractNode.id)) {
|
|
1827
|
-
continue;
|
|
2321
|
+
if (current.node.id === endNode.id) {
|
|
2322
|
+
return this.reconstructPath(current);
|
|
1828
2323
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
if (
|
|
1832
|
-
|
|
2324
|
+
for (const edge of current.node.edges) {
|
|
2325
|
+
let neighbor = nodeMap.get(edge.targetNodeId);
|
|
2326
|
+
if (!neighbor) {
|
|
2327
|
+
const neighborNode = this.abstractNodes.get(edge.targetNodeId);
|
|
2328
|
+
if (!neighborNode) continue;
|
|
2329
|
+
const nh = this.heuristic(neighborNode.position, endPosition) * opts.heuristicWeight;
|
|
2330
|
+
neighbor = {
|
|
2331
|
+
node: neighborNode,
|
|
2332
|
+
g: Infinity,
|
|
2333
|
+
h: nh,
|
|
2334
|
+
f: Infinity,
|
|
2335
|
+
parent: null,
|
|
2336
|
+
closed: false,
|
|
2337
|
+
opened: false,
|
|
2338
|
+
heapIndex: -1
|
|
2339
|
+
};
|
|
2340
|
+
nodeMap.set(edge.targetNodeId, neighbor);
|
|
1833
2341
|
}
|
|
1834
|
-
|
|
1835
|
-
if (!neighbor) continue;
|
|
2342
|
+
if (neighbor.closed) continue;
|
|
1836
2343
|
const tentativeG = current.g + edge.cost;
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2344
|
+
if (!neighbor.opened) {
|
|
2345
|
+
neighbor.g = tentativeG;
|
|
2346
|
+
neighbor.f = tentativeG + neighbor.h;
|
|
2347
|
+
neighbor.parent = current;
|
|
2348
|
+
neighbor.opened = true;
|
|
2349
|
+
openList.push(neighbor);
|
|
2350
|
+
} else if (tentativeG < neighbor.g) {
|
|
2351
|
+
neighbor.g = tentativeG;
|
|
2352
|
+
neighbor.f = tentativeG + neighbor.h;
|
|
2353
|
+
neighbor.parent = current;
|
|
2354
|
+
openList.update(neighbor);
|
|
2355
|
+
}
|
|
1845
2356
|
}
|
|
1846
2357
|
}
|
|
1847
2358
|
return null;
|
|
1848
2359
|
}
|
|
1849
|
-
|
|
2360
|
+
/**
|
|
2361
|
+
* @zh 重建抽象路径
|
|
2362
|
+
* @en Reconstruct abstract path
|
|
2363
|
+
*/
|
|
2364
|
+
reconstructPath(endNode) {
|
|
1850
2365
|
const path = [];
|
|
1851
2366
|
let current = endNode;
|
|
1852
2367
|
while (current) {
|
|
1853
|
-
path.unshift(current.
|
|
2368
|
+
path.unshift(current.node);
|
|
1854
2369
|
current = current.parent;
|
|
1855
2370
|
}
|
|
1856
2371
|
return path;
|
|
1857
2372
|
}
|
|
2373
|
+
/**
|
|
2374
|
+
* @zh 细化抽象路径为具体路径
|
|
2375
|
+
* @en Refine abstract path to concrete path
|
|
2376
|
+
*/
|
|
1858
2377
|
refinePath(abstractPath, startX, startY, endX, endY, opts) {
|
|
2378
|
+
if (abstractPath.length === 0) {
|
|
2379
|
+
return EMPTY_PATH_RESULT;
|
|
2380
|
+
}
|
|
1859
2381
|
const fullPath = [];
|
|
1860
2382
|
let totalCost = 0;
|
|
1861
2383
|
let nodesSearched = abstractPath.length;
|
|
1862
|
-
let
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2384
|
+
for (let i = 0; i < abstractPath.length - 1; i++) {
|
|
2385
|
+
const fromNode = abstractPath[i];
|
|
2386
|
+
const toNode = abstractPath[i + 1];
|
|
2387
|
+
const edge = fromNode.edges.find((e) => e.targetNodeId === toNode.id);
|
|
2388
|
+
if (!edge) {
|
|
2389
|
+
const segResult = this.findLocalPath(fromNode.position.x, fromNode.position.y, toNode.position.x, toNode.position.y, opts);
|
|
2390
|
+
if (segResult.found) {
|
|
2391
|
+
this.appendPath(fullPath, segResult.path);
|
|
2392
|
+
totalCost += segResult.cost;
|
|
2393
|
+
nodesSearched += segResult.nodesSearched;
|
|
2394
|
+
}
|
|
2395
|
+
} else if (edge.isInterEdge) {
|
|
2396
|
+
if (fullPath.length === 0 || fullPath[fullPath.length - 1].x !== fromNode.position.x || fullPath[fullPath.length - 1].y !== fromNode.position.y) {
|
|
2397
|
+
fullPath.push({
|
|
2398
|
+
x: fromNode.position.x,
|
|
2399
|
+
y: fromNode.position.y
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
fullPath.push({
|
|
2403
|
+
x: toNode.position.x,
|
|
2404
|
+
y: toNode.position.y
|
|
2405
|
+
});
|
|
2406
|
+
totalCost += edge.cost;
|
|
2407
|
+
} else if (edge.innerPath && edge.innerPath.length > 0) {
|
|
2408
|
+
const concretePath = edge.innerPath.map((id) => ({
|
|
2409
|
+
x: id % this.mapWidth,
|
|
2410
|
+
y: Math.floor(id / this.mapWidth)
|
|
2411
|
+
}));
|
|
2412
|
+
this.appendPath(fullPath, concretePath);
|
|
2413
|
+
totalCost += edge.cost;
|
|
2414
|
+
} else {
|
|
2415
|
+
const computed = this.computeIntraEdgePath(fromNode, toNode, edge);
|
|
2416
|
+
if (computed && computed.path.length > 0) {
|
|
2417
|
+
const concretePath = computed.path.map((id) => ({
|
|
2418
|
+
x: id % this.mapWidth,
|
|
2419
|
+
y: Math.floor(id / this.mapWidth)
|
|
2420
|
+
}));
|
|
2421
|
+
this.appendPath(fullPath, concretePath);
|
|
2422
|
+
totalCost += computed.cost;
|
|
2423
|
+
} else {
|
|
2424
|
+
const segResult = this.findLocalPath(fromNode.position.x, fromNode.position.y, toNode.position.x, toNode.position.y, opts);
|
|
2425
|
+
if (segResult.found) {
|
|
2426
|
+
this.appendPath(fullPath, segResult.path);
|
|
2427
|
+
totalCost += segResult.cost;
|
|
2428
|
+
nodesSearched += segResult.nodesSearched;
|
|
1878
2429
|
}
|
|
1879
|
-
return EMPTY_PATH_RESULT;
|
|
1880
2430
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
if (fullPath.length > 0 && (fullPath[0].x !== startX || fullPath[0].y !== startY)) {
|
|
2434
|
+
const firstPoint = fullPath[0];
|
|
2435
|
+
if (Math.abs(firstPoint.x - startX) <= 1 && Math.abs(firstPoint.y - startY) <= 1) {
|
|
2436
|
+
fullPath.unshift({
|
|
2437
|
+
x: startX,
|
|
2438
|
+
y: startY
|
|
2439
|
+
});
|
|
2440
|
+
} else {
|
|
2441
|
+
const segResult = this.findLocalPath(startX, startY, firstPoint.x, firstPoint.y, opts);
|
|
2442
|
+
if (segResult.found) {
|
|
2443
|
+
fullPath.splice(0, 0, ...segResult.path.slice(0, -1));
|
|
2444
|
+
totalCost += segResult.cost;
|
|
1883
2445
|
}
|
|
1884
|
-
totalCost += segment.cost;
|
|
1885
|
-
nodesSearched += segment.nodesSearched;
|
|
1886
2446
|
}
|
|
1887
|
-
currentX = targetX;
|
|
1888
|
-
currentY = targetY;
|
|
1889
2447
|
}
|
|
1890
|
-
if (
|
|
1891
|
-
const
|
|
1892
|
-
if (
|
|
1893
|
-
|
|
1894
|
-
fullPath.push(
|
|
2448
|
+
if (fullPath.length > 0) {
|
|
2449
|
+
const lastPoint = fullPath[fullPath.length - 1];
|
|
2450
|
+
if (lastPoint.x !== endX || lastPoint.y !== endY) {
|
|
2451
|
+
if (Math.abs(lastPoint.x - endX) <= 1 && Math.abs(lastPoint.y - endY) <= 1) {
|
|
2452
|
+
fullPath.push({
|
|
2453
|
+
x: endX,
|
|
2454
|
+
y: endY
|
|
2455
|
+
});
|
|
2456
|
+
} else {
|
|
2457
|
+
const segResult = this.findLocalPath(lastPoint.x, lastPoint.y, endX, endY, opts);
|
|
2458
|
+
if (segResult.found) {
|
|
2459
|
+
fullPath.push(...segResult.path.slice(1));
|
|
2460
|
+
totalCost += segResult.cost;
|
|
2461
|
+
}
|
|
1895
2462
|
}
|
|
1896
|
-
totalCost += finalSegment.cost;
|
|
1897
|
-
nodesSearched += finalSegment.nodesSearched;
|
|
1898
2463
|
}
|
|
1899
2464
|
}
|
|
1900
2465
|
return {
|
|
@@ -1904,39 +2469,37 @@ var _HPAPathfinder = class _HPAPathfinder {
|
|
|
1904
2469
|
nodesSearched
|
|
1905
2470
|
};
|
|
1906
2471
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
return cached;
|
|
2472
|
+
/**
|
|
2473
|
+
* @zh 追加路径(避免重复点)
|
|
2474
|
+
* @en Append path (avoid duplicate points)
|
|
2475
|
+
*/
|
|
2476
|
+
appendPath(fullPath, segment) {
|
|
2477
|
+
if (segment.length === 0) return;
|
|
2478
|
+
let startIdx = 0;
|
|
2479
|
+
if (fullPath.length > 0) {
|
|
2480
|
+
const last = fullPath[fullPath.length - 1];
|
|
2481
|
+
if (last.x === segment[0].x && last.y === segment[0].y) {
|
|
2482
|
+
startIdx = 1;
|
|
1919
2483
|
}
|
|
1920
2484
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2485
|
+
for (let i = startIdx; i < segment.length; i++) {
|
|
2486
|
+
fullPath.push({
|
|
2487
|
+
x: segment[i].x,
|
|
2488
|
+
y: segment[i].y
|
|
2489
|
+
});
|
|
1926
2490
|
}
|
|
1927
|
-
return result.found ? [
|
|
1928
|
-
...result.path
|
|
1929
|
-
] : null;
|
|
1930
2491
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
}
|
|
1938
|
-
return cost;
|
|
2492
|
+
/**
|
|
2493
|
+
* @zh 局部寻路
|
|
2494
|
+
* @en Local pathfinding
|
|
2495
|
+
*/
|
|
2496
|
+
findLocalPath(startX, startY, endX, endY, opts) {
|
|
2497
|
+
return this.localPathfinder.findPath(startX, startY, endX, endY, opts);
|
|
1939
2498
|
}
|
|
2499
|
+
/**
|
|
2500
|
+
* @zh 启发式函数(Octile 距离)
|
|
2501
|
+
* @en Heuristic function (Octile distance)
|
|
2502
|
+
*/
|
|
1940
2503
|
heuristic(a, b) {
|
|
1941
2504
|
const dx = Math.abs(a.x - b.x);
|
|
1942
2505
|
const dy = Math.abs(a.y - b.y);
|
|
@@ -1951,8 +2514,8 @@ function createHPAPathfinder(map, config) {
|
|
|
1951
2514
|
__name(createHPAPathfinder, "createHPAPathfinder");
|
|
1952
2515
|
|
|
1953
2516
|
// src/navmesh/NavMesh.ts
|
|
1954
|
-
var
|
|
1955
|
-
var NavMeshNode = (
|
|
2517
|
+
var _a5;
|
|
2518
|
+
var NavMeshNode = (_a5 = class {
|
|
1956
2519
|
constructor(polygon) {
|
|
1957
2520
|
__publicField(this, "id");
|
|
1958
2521
|
__publicField(this, "position");
|
|
@@ -1965,7 +2528,7 @@ var NavMeshNode = (_a3 = class {
|
|
|
1965
2528
|
this.walkable = true;
|
|
1966
2529
|
this.polygon = polygon;
|
|
1967
2530
|
}
|
|
1968
|
-
}, __name(
|
|
2531
|
+
}, __name(_a5, "NavMeshNode"), _a5);
|
|
1969
2532
|
var _NavMesh = class _NavMesh {
|
|
1970
2533
|
constructor() {
|
|
1971
2534
|
__publicField(this, "polygons", /* @__PURE__ */ new Map());
|