From 810f874a50dce8e0a26d3246d19b2489e9e8d2ef Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 15 Sep 2025 00:29:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BA=86=E7=BD=91=E6=A0=BC?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E7=9A=84z=E5=9D=90=E6=A0=87=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=94=B9=E8=BF=9B=E4=BA=86=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=AD=A5=E9=AA=A4=EF=BC=8C=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=9C=A8=E5=8F=B0=E9=98=B6=E5=A4=84=E4=BF=9D?= =?UTF-8?q?=E7=95=992=E4=B8=AA=E8=B7=AF=E5=BE=84=E7=82=B9=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E5=A5=BD=E8=B4=B4=E5=90=88=E9=80=9A=E9=81=93=E8=A1=A8?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Collision/ClashDetectiveIntegration.cs | 8 ++ src/PathPlanning/AutoPathFinder.cs | 36 +++++++-- src/PathPlanning/ChannelBasedGridBuilder.cs | 12 ++- src/PathPlanning/GridMap.cs | 78 +++---------------- src/PathPlanning/GridMapGenerator.cs | 34 ++++++-- src/PathPlanning/PathOptimizer.cs | 71 +++++++++++++++-- .../ViewModels/AnimationControlViewModel.cs | 13 +++- 7 files changed, 160 insertions(+), 92 deletions(-) diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index 1d2f131..05d7209 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -626,6 +626,14 @@ namespace NavisworksTransport LogManager.Info($"=== 统计分离完成 ==="); LogManager.Info("=== 动画碰撞测试(分组方案)完成 ==="); + + // 触发碰撞检测完成事件,通知生成报告 + if (validCollisions.Count > 0) + { + LogManager.Info($"触发CollisionDetected事件,通知生成报告({validCollisions.Count}个碰撞)"); + var eventArgs = new CollisionDetectedEventArgs(validCollisions); + OnCollisionDetected(eventArgs); + } } } catch (Exception ex) diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index 38bd13a..330cc4e 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -704,8 +704,17 @@ namespace NavisworksTransport.PathPlanning var endCell = gridMap.GetCell(endGridPos); if (endCell.HasValue) { + // 调试:检查门网格的Z坐标 + if (endCell.Value.CellType == CategoryAttributeManager.LogisticsElementType.门) + { + LogManager.Info($"[路径转换-门] 门网格({endGridPos.X},{endGridPos.Y}) WorldPosition.Z={endCell.Value.WorldPosition.Z:F3}, 位置({endWorldPos.X:F2},{endWorldPos.Y:F2})"); + } endWorldPos = new Point3D(endWorldPos.X, endWorldPos.Y, endCell.Value.WorldPosition.Z); } + else + { + LogManager.Warning($"[路径转换] 网格({endGridPos.X},{endGridPos.Y})获取失败,位置({endWorldPos.X:F2},{endWorldPos.Y:F2}),Z坐标保持为0"); + } // 检查是否与上一个点重复(A*可能在转弯点有重复) bool isDuplicate = worldPath.Count > 0 && @@ -751,10 +760,27 @@ namespace NavisworksTransport.PathPlanning { var startPoint = optimizedPath[currentIndex]; var endPoint = optimizedPath[testIndex]; - var distance = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) + + var distance = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) + Math.Pow(endPoint.Y - startPoint.Y, 2)); - - if (IsDirectPathClear(startPoint, endPoint, gridMap)) + + // 🔥 新增:在尝试连接远点之前,先检查是否会跳过高度变化点 + bool canSkip = true; + const double heightTolerance = 1e-6; // 与PathOptimizer保持一致 + + for (int checkIdx = currentIndex + 1; checkIdx < testIndex; checkIdx++) + { + // 检查被跳过的点是否有高度变化 + if (Math.Abs(optimizedPath[checkIdx].Z - optimizedPath[checkIdx-1].Z) > heightTolerance || + (checkIdx + 1 < optimizedPath.Count && + Math.Abs(optimizedPath[checkIdx+1].Z - optimizedPath[checkIdx].Z) > heightTolerance)) + { + canSkip = false; + LogManager.Debug($"[斜线优化] 不能跳过点{checkIdx},存在高度变化"); + break; + } + } + + if (canSkip && IsDirectPathClear(startPoint, endPoint, gridMap)) { // 可以直线连接,更新最远索引 farthestIndex = testIndex; @@ -1367,9 +1393,9 @@ namespace NavisworksTransport.PathPlanning if (validIntervals.Any()) { - // 选择最低的有效区间的底部高度 + // 选择最低的有效区间的底部高度,转换为绝对坐标 var bestInterval = validIntervals.First(); - return bestInterval.MinZ; + return cell.WorldPosition.Z + bestInterval.MinZ; } } diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs index 93b0367..b1d1ef1 100644 --- a/src/PathPlanning/ChannelBasedGridBuilder.cs +++ b/src/PathPlanning/ChannelBasedGridBuilder.cs @@ -221,6 +221,9 @@ namespace NavisworksTransport.PathPlanning var minGrid = gridMap.WorldToGrid(new Point3D(minX, minY, 0)); var maxGrid = gridMap.WorldToGrid(new Point3D(maxX, maxY, 0)); + // 计算三角形的平均高度作为通道顶面高度 + double triangleHeight = (triangle.Point1.Z + triangle.Point2.Z + triangle.Point3.Z) / 3.0; + // 遍历边界框内的所有网格点 for (int x = minGrid.X; x <= maxGrid.X; x++) { @@ -234,7 +237,8 @@ namespace NavisworksTransport.PathPlanning // 检查点是否在三角形内部 if (GeometryHelper.IsPointInTriangle2D(worldPos, triangle)) { - SetCellAsChannel(gridMap, gridPos, null); + // 传递实际的三角形高度 + SetCellAsChannel(gridMap, gridPos, null, triangleHeight); } } } @@ -248,10 +252,10 @@ namespace NavisworksTransport.PathPlanning /// 网格地图 /// 网格位置 /// 关联的模型物品 - private void SetCellAsChannel(GridMap gridMap, GridPoint2D gridPos, ModelItem relatedItem) + private void SetCellAsChannel(GridMap gridMap, GridPoint2D gridPos, ModelItem relatedItem, double channelHeight = 0.0) { - // 使用GridMap提供的方法设置通道单元格 - gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem); + // 使用GridMap提供的方法设置通道单元格,传递实际高度 + gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem, channelHeight); } diff --git a/src/PathPlanning/GridMap.cs b/src/PathPlanning/GridMap.cs index 305b73b..d7a8539 100644 --- a/src/PathPlanning/GridMap.cs +++ b/src/PathPlanning/GridMap.cs @@ -153,8 +153,10 @@ namespace NavisworksTransport.PathPlanning { for (int y = 0; y < Height; y++) { - // 只计算2D世界坐标,Z坐标将在有规划点信息后单独设置 - var worldPos = GridToWorld2D(new GridPoint2D(x, y)); // 直接使用返回的Point3D,Z=0 + // 计算2D世界坐标,并设置合理的初始Z坐标(使用地面高度而不是0) + var worldPos2D = GridToWorld2D(new GridPoint2D(x, y)); + // 使用边界框最小Z值(地面高度)作为Unknown网格的初始高度 + var worldPos = new Point3D(worldPos2D.X, worldPos2D.Y, Bounds.Min.Z); var cell = new GridCell { @@ -209,68 +211,7 @@ namespace NavisworksTransport.PathPlanning return new Point3D(world2D.X, world2D.Y, z); } - /// - /// 线性插值Z坐标计算 - /// - /// 世界坐标X - /// 世界坐标Y - /// 插值后的Z坐标 - private double CalculateInterpolatedZ(double worldX, double worldY) - { - // 必须先设置规划点才能进行Z坐标插值计算 - if (!HasPlanningPoints) - { - throw new InvalidOperationException("必须先设置PlanningStartPoint和PlanningEndPoint才能计算网格Z坐标"); - } - var start = PlanningStartPoint; - var end = PlanningEndPoint; - - // 计算当前点到起点和终点的距离 - double distanceToStart = Math.Sqrt(Math.Pow(worldX - start.X, 2) + Math.Pow(worldY - start.Y, 2)); - double distanceToEnd = Math.Sqrt(Math.Pow(worldX - end.X, 2) + Math.Pow(worldY - end.Y, 2)); - double totalDistance = Math.Sqrt(Math.Pow(end.X - start.X, 2) + Math.Pow(end.Y - start.Y, 2)); - - // 防止除零错误 - if (totalDistance < 0.001) - { - return (start.Z + end.Z) / 2.0; // 返回平均高度 - } - - // 线性插值:根据到起点的距离比例计算Z坐标 - double ratio = distanceToStart / totalDistance; - double interpolatedZ = start.Z + (end.Z - start.Z) * ratio; - - return interpolatedZ; - } - - /// - /// 更新所有网格单元格的WorldPosition.Z坐标 - /// 必须在设置PlanningStartPoint和PlanningEndPoint之后调用 - /// - public void UpdateCellWorldPositionZ() - { - if (!HasPlanningPoints) - { - throw new InvalidOperationException("必须先设置PlanningStartPoint和PlanningEndPoint才能更新网格Z坐标"); - } - - for (int x = 0; x < Width; x++) - { - for (int y = 0; y < Height; y++) - { - var cell = Cells[x, y]; - var world2D = GridToWorld2D(new GridPoint2D(x, y)); - double interpolatedZ = CalculateInterpolatedZ(world2D.X, world2D.Y); - - // 更新单元格的WorldPosition.Z坐标 - cell.WorldPosition = new Point3D(world2D.X, world2D.Y, interpolatedZ); - Cells[x, y] = cell; - } - } - - LogManager.Info($"[网格Z坐标更新] 已更新所有 {Width}x{Height} 个网格单元格的Z坐标"); - } /// /// 检查网格坐标是否在有效范围内 @@ -342,13 +283,18 @@ namespace NavisworksTransport.PathPlanning /// 网格坐标 /// 通道类型 /// 关联的模型物品 - public void SetCellAsChannel(GridPoint2D gridPosition, ChannelType channelType = ChannelType.Corridor, ModelItem relatedItem = null) + public void SetCellAsChannel(GridPoint2D gridPosition, ChannelType channelType = ChannelType.Corridor, ModelItem relatedItem = null, double channelHeight = 0.0) { if (!IsValidGridPosition(gridPosition)) return; - var existingZ = Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z; - var worldPos = GridToWorld3D(gridPosition, existingZ); + // 使用传入的通道高度,如果没有提供则使用现有的Z坐标或边界最大Z + double actualZ = channelHeight > 0 ? channelHeight : + (Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z > 0 ? + Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z : + Bounds.Max.Z); + + var worldPos = GridToWorld3D(gridPosition, actualZ); var cell = new GridCell { IsWalkable = true, diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index 4afb17f..15c8d55 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -174,8 +174,7 @@ namespace NavisworksTransport.PathPlanning channelCoverage.GridMap.PlanningEndPoint = planningEndPoint; channelCoverage.GridMap.HasPlanningPoints = true; - // 9. 更新所有网格单元格的Z坐标(现在有了规划点信息) - channelCoverage.GridMap.UpdateCellWorldPositionZ(); + // 注意:网格的Z坐标已在通道生成时设置为实际通道高度,无需重新计算 } // 🔥 关键修复:将通道数据设置到GridMap中,供后续PathPlanningManager使用 @@ -270,8 +269,7 @@ namespace NavisworksTransport.PathPlanning channelCoverage.GridMap.PlanningEndPoint = planningEndPoint; channelCoverage.GridMap.HasPlanningPoints = true; - // 5. 更新所有网格单元格的Z坐标(现在有了规划点信息) - channelCoverage.GridMap.UpdateCellWorldPositionZ(); + // 注意:网格的Z坐标已在通道生成时设置为实际通道高度,无需重新计算 } // 将通道数据设置到GridMap中,供后续PathPlanningManager使用 @@ -358,12 +356,34 @@ namespace NavisworksTransport.PathPlanning LogManager.Debug($"【门元素处理】门元素 {doorItem.DisplayName} 限高: {doorHeight:F2} 模型单位"); } - // 设置开口区域的网格为可通行的门类型,使用正确的高度 + // 获取门底部的Z坐标 + double doorBottomZ = bbox.Min.Z; + + // 设置开口区域的网格为可通行的门类型 foreach (var (x, y) in coveredCells) { var gridPos = new GridPoint2D(x, y); - // 使用正确的门高度设置网格 - gridMap.SetCell(gridPos, true, doorHeight, CategoryAttributeManager.LogisticsElementType.门); + // 设置门网格,cost传0让SetCell自动调用GetCost()计算(门的默认cost是1.2) + gridMap.SetCell(gridPos, true, 0, CategoryAttributeManager.LogisticsElementType.门); + + // 由于GridCell是结构体,需要重新获取、修改、再设置回去 + var cell = gridMap.Cells[x, y]; + + // 更新门网格的Z坐标为门底部位置 + var worldPos = gridMap.GridToWorld3D(gridPos, doorBottomZ); + cell.WorldPosition = worldPos; + + // 将门的高度范围存储在PassableHeights中供后续使用 + if (doorHeight > 0) + { + cell.PassableHeights = new List + { + new HeightInterval(0, doorHeight) + }; + } + + // 将修改后的结构体设置回数组 + gridMap.Cells[x, y] = cell; } processedCount++; diff --git a/src/PathPlanning/PathOptimizer.cs b/src/PathPlanning/PathOptimizer.cs index cb11a46..cdb049c 100644 --- a/src/PathPlanning/PathOptimizer.cs +++ b/src/PathPlanning/PathOptimizer.cs @@ -174,24 +174,36 @@ namespace NavisworksTransport.PathPlanning initialDy == 0 ? 0 : Math.Sign(initialDy) ); - // 简化路径:只保留转弯点(使用归一化方向) + // 简化路径:只保留转弯点和高度变化点(使用归一化方向) for (int i = 2; i < dedupedGridPath.Count; i++) { // 计算当前线段的位移 int dx = dedupedGridPath[i].X - dedupedGridPath[i-1].X; int dy = dedupedGridPath[i].Y - dedupedGridPath[i-1].Y; - + // 归一化为单位方向向量 (-1, 0, 1) var currentDirection = new GridPoint2D( dx == 0 ? 0 : Math.Sign(dx), dy == 0 ? 0 : Math.Sign(dy) ); - - // 只有真正的方向改变时才保留转弯点 - if (!currentDirection.Equals(prevDirection)) + + // 检查高度变化 + bool hasHeightChange = HasHeightChange( + dedupedPoints[i-2], + dedupedPoints[i-1], + dedupedPoints[i] + ); + + // 只有真正的方向改变或高度变化时才保留转折点 + if (!currentDirection.Equals(prevDirection) || hasHeightChange) { simplified.Add(dedupedGridPath[i - 1]); prevDirection = currentDirection; + + if (hasHeightChange) + { + LogManager.Debug($"[网格路径优化] 保留高度变化点 {i-1}"); + } } } @@ -257,6 +269,51 @@ namespace NavisworksTransport.PathPlanning return simplified; } + /// + /// 检查两个路径点之间是否有显著的高度变化 + /// 用于路径优化时判断是否需要保留中间点 + /// + /// 第一个路径点 + /// 第二个路径点 + /// 如果有高度变化返回true,需要保留中间点 + private bool HasHeightChange(PathPoint point1, PathPoint point2) + { + const double heightTolerance = 1e-6; // 仅覆盖浮点数精度误差 + return Math.Abs(point1.Position.Z - point2.Position.Z) > heightTolerance; + } + + /// + /// 检查三个点之间是否有高度变化(用于判断是否可以跳过中间点) + /// + /// 第一个点的3D坐标 + /// 中间点的3D坐标 + /// 第三个点的3D坐标 + /// 如果有高度变化返回true,不能跳过中间点 + private bool HasHeightChange(Point3D p1, Point3D p2, Point3D p3) + { + const double heightTolerance = 1e-6; // 仅覆盖浮点数精度误差 + + // 检查任意两点间是否有高度变化 + if (Math.Abs(p1.Z - p2.Z) > heightTolerance || + Math.Abs(p2.Z - p3.Z) > heightTolerance) + { + return true; + } + return false; + } + + /// + /// 检查三个点之间是否有高度变化(PathPoint版本) + /// + /// 第一个路径点 + /// 中间路径点 + /// 第三个路径点 + /// 如果有高度变化返回true,不能跳过中间点 + private bool HasHeightChange(PathPoint point1, PathPoint point2, PathPoint point3) + { + return HasHeightChange(point1.Position, point2.Position, point3.Position); + } + /// /// 检查三点是否真正共线(只有当三点都在同一条直线上且方向一致时才返回true) /// 🔥 关键修复:严格检查两个线段的方向是否一致,防止在转折点处错误合并 @@ -269,8 +326,8 @@ namespace NavisworksTransport.PathPlanning { const double tolerance = 0.1; // 容差值,单位:模型单位 - // 🔥 修复:首先检查Z坐标,确保是2D路径 - if (Math.Abs(p1.Z - p2.Z) > tolerance || Math.Abs(p2.Z - p3.Z) > tolerance) + // 🔥 修复:使用统一的高度检查函数 + if (HasHeightChange(p1, p2, p3)) { LogManager.Debug($"[共线检测] ❌ 拒绝Z方向变化:({p1.X:F3},{p1.Y:F3},{p1.Z:F3}) -> ({p2.X:F3},{p2.Y:F3},{p2.Z:F3}) -> ({p3.X:F3},{p3.Y:F3},{p3.Z:F3})"); return false; diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index 1be3d14..6b75999 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -430,8 +430,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 初始化命令 InitializeCommands(); - // 订阅碰撞检测事件 - _clashIntegration.CollisionDetected += OnCollisionDetected; + // 移除重复的碰撞检测事件订阅,只在带参构造函数中保留 + // _clashIntegration.CollisionDetected += OnCollisionDetected; LogManager.Info("AnimationControlViewModel初始化完成(含缓存管理)"); } @@ -1039,11 +1039,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels await _uiStateManager.ExecuteUIUpdateAsync(() => { HasCollisionResults = e.Results.Count > 0; - var summary = e.Results.Count > 0 + var summary = e.Results.Count > 0 ? $"发现 {e.Results.Count} 个碰撞点" : "未发现碰撞"; UpdateMainStatus(summary); }); + + // 自动生成并显示碰撞报告 + if (HasCollisionResults) + { + LogManager.Info("碰撞检测完成,自动生成报告"); + await ExecuteViewCollisionReportAsync(); + } } ///