NavisworksTransport/doc/design/2026/AStar库的使用方法.md

279 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Roy-T.AStar库的使用方法与经验总结
## 概述
Roy-T.AStar是一个高性能的C# A*寻路算法库,位于`C:\Users\Tellme\apps\OpenSource\AStar-master`。本文档记录了在NavisworksTransport项目中集成和使用该库的经验教训。
## 核心概念理解
### 1. 坐标系统
**关键发现**Roy-T.AStar使用**米坐标系统**,而不是网格索引。
```csharp
// 创建网格时,传入的是物理尺寸
var cellSize = new Size(
Distance.FromMeters((float)cellSizeInMeters),
Distance.FromMeters((float)cellSizeInMeters)
);
// GridPosition构造函数接受的是网格索引
var gridPos = new GridPosition(x, y); // x,y是网格索引如(0,0), (1,0)等
// 但Node.Position返回的是米坐标
// 例如:网格(1,0)的Node.Position可能是(2.0, 0.0)米假设cellSize=2米
```
### 2. 路径数据结构
**Path对象结构**
- `Path.Edges`: 边的列表IReadOnlyList<IEdge>
- 每条边包含:
- `Start`: 起始节点
- `End`: 终止节点
- `Distance`: 边的长度
- `TraversalVelocity`: 遍历速度
**重要特性**:连续边的关系
```
Edge[0]: Start=A, End=B
Edge[1]: Start=B, End=C // 注意Edge[0].End == Edge[1].Start
Edge[2]: Start=C, End=D
```
## 常见陷阱与解决方案
### 陷阱1坐标转换时的重复点问题
**错误做法**
```csharp
// ❌ 错误:会产生重复点
var gridPath = new List<GridPoint2D>();
gridPath.Add(ConvertToGrid(edges[0].Start)); // 添加起点
foreach (var edge in edges) {
gridPath.Add(ConvertToGrid(edge.End)); // 每条边的终点
}
// 结果:[A, B, B, C, C, D] - 转弯点重复!
```
**正确做法**
```csharp
// ✅ 正确:避免重复
var gridPath = new List<GridPoint2D>();
if (edges.Count > 0) {
gridPath.Add(ConvertToGrid(edges[0].Start)); // 只添加第一个起点
foreach (var edge in edges) {
var gridPoint = ConvertToGrid(edge.End);
// 检查是否与上一个点重复
if (gridPath.Count == 0 || !gridPath.Last().Equals(gridPoint)) {
gridPath.Add(gridPoint);
}
}
}
// 结果:[A, B, C, D] - 完美的连续路径
```
### 陷阱2米坐标到网格坐标的转换
**关键代码**
```csharp
// 从A*的米坐标转换为网格索引
double cellSizeInMeters = UnitsConverter.ConvertToMeters(gridMap.CellSize);
int gridX = (int)Math.Floor(node.Position.X / cellSizeInMeters);
int gridY = (int)Math.Floor(node.Position.Y / cellSizeInMeters);
```
### 陷阱3网格创建时的节点连接
**默认创建方法的限制**
```csharp
// Roy-T.AStar提供的默认方法会连接所有节点
var grid = Grid.CreateGridWithLateralConnections(gridSize, cellSize, velocity);
```
**自定义障碍物处理**
```csharp
// 1. 先断开所有连接
for (int x = 0; x < gridMap.Width; x++) {
for (int y = 0; y < gridMap.Height; y++) {
grid.DisconnectNode(new GridPosition(x, y));
}
}
// 2. 只连接可通行的节点
for (int x = 0; x < gridMap.Width; x++) {
for (int y = 0; y < gridMap.Height; y++) {
if (IsWalkable(x, y)) {
// 连接到右侧邻居
if (x + 1 < width && IsWalkable(x + 1, y)) {
grid.AddEdge(new GridPosition(x, y),
new GridPosition(x + 1, y), velocity);
}
// 连接到下方邻居
if (y + 1 < height && IsWalkable(x, y + 1)) {
grid.AddEdge(new GridPosition(x, y),
new GridPosition(x, y + 1), velocity);
}
}
}
}
```
## 路径优化策略
### 1. 网格路径优化算法
**问题**A*输出的路径包含大量中间点,需要优化。
**解决方案**:基于方向变化的路径简化
```csharp
private List<GridPoint2D> SimplifyPath(List<GridPoint2D> path) {
if (path.Count < 3) return path;
var simplified = new List<GridPoint2D> { path[0] };
// 计算初始方向(归一化)
int dx = path[1].X - path[0].X;
int dy = path[1].Y - path[0].Y;
var prevDirection = new GridPoint2D(
dx == 0 ? 0 : Math.Sign(dx),
dy == 0 ? 0 : Math.Sign(dy)
);
// 检测方向变化
for (int i = 2; i < path.Count; i++) {
dx = path[i].X - path[i-1].X;
dy = path[i].Y - path[i-1].Y;
var currentDirection = new GridPoint2D(
dx == 0 ? 0 : Math.Sign(dx),
dy == 0 ? 0 : Math.Sign(dy)
);
// 方向改变时保留转弯点
if (!currentDirection.Equals(prevDirection)) {
simplified.Add(path[i - 1]);
prevDirection = currentDirection;
}
}
simplified.Add(path.Last()); // 添加终点
return simplified;
}
```
### 2. 方向归一化的重要性
**问题**:不同步长的移动被误判为转弯
- `(-1, 0)``(-2, 0)`:都是向左,但步长不同
- `(0, -1)``(0, -3)`:都是向上,但步长不同
**解决**:使用`Math.Sign()`归一化方向向量
```csharp
// 归一化为单位方向向量 (-1, 0, 1)
int dirX = dx == 0 ? 0 : Math.Sign(dx);
int dirY = dy == 0 ? 0 : Math.Sign(dy);
```
## 性能优化建议
### 1. 路径缓存
对于频繁查询的路径,考虑缓存结果:
```csharp
private Dictionary<(Point3D, Point3D), Path> _pathCache;
```
### 2. 分层寻路
对于大型地图可以使用分层A*算法:
- 高层:粗略网格,快速找到大致路径
- 低层:精细网格,优化局部路径
### 3. 动态障碍物
Roy-T.AStar支持动态修改网格连接
```csharp
// 添加障碍物
grid.DisconnectNode(position);
// 移除障碍物
grid.AddEdge(from, to, velocity);
```
## 2.5D路径规划扩展
### 高度约束处理
```csharp
// 检查节点是否满足高度约束
if (cell.PassableHeights != null && cell.PassableHeights.Any()) {
bool heightOk = cell.PassableHeights.Any(
interval => interval.GetSpan() >= vehicleHeight
);
if (!heightOk) {
grid.DisconnectNode(position); // 不满足高度要求,断开连接
}
}
```
## 调试技巧
### 1. 日志输出
```csharp
LogManager.Info($"[A*执行] 找到路径,包含 {path.Edges.Count} 条边");
LogManager.Debug($"[路径优化] 方向从({prev.X},{prev.Y})变为({curr.X},{curr.Y})");
```
### 2. 路径验证
```csharp
// 验证路径连续性
for (int i = 1; i < path.Count; i++) {
var dist = Math.Abs(path[i].X - path[i-1].X) +
Math.Abs(path[i].Y - path[i-1].Y);
if (dist > 1) {
LogManager.Warning($"路径不连续:从{path[i-1]}到{path[i]}");
}
}
```
## 实际优化效果
在NavisworksTransport项目中的实测结果
- **原始A*输出**101个路径点
- **优化后**19个关键转弯点
- **优化率**81.2%
- **处理时间**约8ms
## 总结
使用Roy-T.AStar库的关键要点
1. 理解米坐标系统,正确进行坐标转换
2. 注意Path.Edges的连续性避免重复点
3. 使用方向归一化进行路径优化
4. 灵活运用DisconnectNode和AddEdge处理障碍物
5. 对于2.5D场景,在网格连接阶段处理高度约束
## 参考资源
- Roy-T.AStar源码`C:\Users\Tellme\apps\OpenSource\AStar-master`
- NavisworksTransport集成代码
- `src\PathPlanning\AutoPathFinder.cs`
- `src\PathPlanning\PathOptimizer.cs`