NavisworksTransport/src/Core/PathPointRenderPlugin.cs
tian 07f8f5b2bf 修复网格可视化性能问题
根本原因:新代码在循环中调用了 AddPoint() 方法,该方法每次都会触发 UpdateTotalLength(),导致:
算法复杂度:从 O(n) 变成 O(n² log n)
排序次数:12,480 个点 = 78,000,000 次排序
性能损失:从 76 毫秒 → 12.5 秒(130倍慢)
修复方案:直接使用 Points.Add() 绕过昂贵的 UpdateTotalLength() 调用
2026-01-16 20:35:33 +08:00

2734 lines
100 KiB
C#
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.

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using NavisworksTransport.Utils;
namespace NavisworksTransport
{
/// <summary>
/// 路径可视化模式枚举
/// </summary>
public enum PathVisualizationMode
{
/// <summary>
/// 标准连线模式 - 圆柱形连线
/// </summary>
StandardLine,
/// <summary>
/// 带状连线模式 - 长方体连线
/// </summary>
RibbonLine,
/// <summary>
/// 车辆通行空间模式 - 矩形通道
/// </summary>
VehicleSpace
}
/// <summary>
/// 网格点类型枚举
/// </summary>
public enum GridPointType
{
/// <summary>
/// 球形
/// </summary>
Sphere,
/// <summary>
/// 正方形
/// </summary>
Rectangle,
/// <summary>
/// 圆
/// </summary>
Circle
}
/// <summary>
/// 可视化元素样式名称枚举
/// </summary>
public enum RenderStyleName
{
/// <summary>
/// 路径起点样式(绿色)
/// </summary>
StartPoint,
/// <summary>
/// 路径终点样式(红色)
/// </summary>
EndPoint,
/// <summary>
/// 路径中间点样式(蓝色)
/// </summary>
WayPoint,
/// <summary>
/// 连线样式(橙色)
/// </summary>
Line,
/// <summary>
/// 车辆通行空间样式(灰色)
/// </summary>
VehicleSpace,
/// <summary>
/// 预览点样式(白色)
/// </summary>
PreviewPoint,
/// <summary>
/// 预览连线样式(灰色)
/// </summary>
PreviewLine,
/// <summary>
/// 未到达终点样式(深红色)
/// </summary>
UnreachedEndPoint,
/// <summary>
/// 网格通道样式(浅绿色)
/// </summary>
GridChannel,
/// <summary>
/// 网格障碍物样式(灰色)
/// </summary>
GridObstacle,
/// <summary>
/// 网格未知区域样式(深橙色)
/// </summary>
GridUnknown,
/// <summary>
/// 切点样式(深红色)
/// </summary>
TangentPoint,
/// <summary>
/// 安全警告样式(红色)
/// </summary>
SafetyWarning,
/// <summary>
/// 空轨基准路径样式(浅红色)
/// </summary>
RailBaseline
}
/// <summary>
/// 渲染样式,包含颜色和透明度
/// </summary>
public readonly struct RenderStyle
{
/// <summary>
/// 颜色
/// </summary>
public Color Color { get; }
/// <summary>
/// 透明度 (0.0 = 完全透明, 1.0 = 完全不透明)
/// </summary>
public double Alpha { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="color">颜色</param>
/// <param name="alpha">透明度</param>
public RenderStyle(Color color, double alpha)
{
Color = color;
Alpha = alpha;
}
public override string ToString()
{
return $"RenderStyle[Color={Color}, Alpha={Alpha:F2}]";
}
}
/// <summary>
/// 车辆通行空间标记,用于渲染车辆通道空间
/// </summary>
public class VehicleSpaceMarker
{
/// <summary>
/// 通道起点
/// </summary>
public Point3D StartPoint { get; set; }
/// <summary>
/// 通道终点
/// </summary>
public Point3D EndPoint { get; set; }
/// <summary>
/// 通道宽度(车辆长宽最大值+2倍安全间隙
/// </summary>
public double Width { get; set; }
/// <summary>
/// 通道高度(车辆高度+安全间隙)
/// </summary>
public double Height { get; set; }
/// <summary>
/// 通道颜色
/// </summary>
public Color Color { get; set; }
/// <summary>
/// 透明度0.0-1.0默认0.5即50%透明)
/// </summary>
public double Alpha { get; set; } = 0.5;
/// <summary>
/// 起点在路径中的索引
/// </summary>
public int FromIndex { get; set; }
/// <summary>
/// 终点在路径中的索引
/// </summary>
public int ToIndex { get; set; }
public override string ToString()
{
return $"VehicleSpaceMarker[{FromIndex}->{ToIndex}, 宽度={Width:F2}m, 高度={Height:F2}m, 透明度={Alpha:F1}]";
}
}
/// <summary>
/// 连线标记,用于渲染路径点之间的连接线
/// </summary>
public class LineMarker
{
/// <summary>
/// 连线起点
/// </summary>
public Point3D StartPoint { get; set; }
/// <summary>
/// 连线终点
/// </summary>
public Point3D EndPoint { get; set; }
/// <summary>
/// 连线颜色
/// </summary>
public Color Color { get; set; }
/// <summary>
/// 连线半径
/// </summary>
public double Radius { get; set; }
/// <summary>
/// 起点在路径中的索引
/// </summary>
public int FromIndex { get; set; }
/// <summary>
/// 终点在路径中的索引
/// </summary>
public int ToIndex { get; set; }
/// <summary>
/// 路径段类型
/// </summary>
public PathSegmentType SegmentType { get; set; } = PathSegmentType.Straight;
/// <summary>
/// 圆弧轨迹数据仅当SegmentType=Arc时有效
/// </summary>
public ArcTrajectory Trajectory { get; set; }
/// <summary>
/// 采样点列表(用于圆弧段的多段圆柱体渲染)
/// </summary>
public List<Point3D> SampledPoints { get; set; }
/// <summary>
/// 带状连线高度(仅用于 RibbonLine 和 VehicleSpace 模式)
/// </summary>
public double Height { get; set; }
/// <summary>
/// 带状连线宽度(仅用于 VehicleSpace 模式)
/// </summary>
public double Width { get; set; }
/// <summary>
/// 透明度0.0=完全透明1.0=完全不透明)
/// </summary>
public double Opacity { get; set; } = 1.0;
public override string ToString()
{
return $"LineMarker[{FromIndex}->{ToIndex}, 类型={SegmentType}, 起点=({StartPoint.X:F2},{StartPoint.Y:F2},{StartPoint.Z:F2}), 终点=({EndPoint.X:F2},{EndPoint.Y:F2},{EndPoint.Z:F2})]";
}
}
/// <summary>
/// 路径可视化数据,包含一个完整路径的所有可视化元素
/// </summary>
public class PathVisualization
{
/// <summary>
/// 路径唯一标识
/// </summary>
public string PathId { get; set; }
/// <summary>
/// 路径数据引用
/// </summary>
public PathRoute PathRoute { get; set; }
/// <summary>
/// 点标记集合
/// </summary>
public List<CircleMarker> PointMarkers { get; set; }
/// <summary>
/// 控制点连线集合(用户意图,半透明)
/// </summary>
public List<LineMarker> ControlLineMarkers { get; set; }
/// <summary>
/// 路径连线集合(真实路径,不透明)
/// </summary>
public List<LineMarker> PathLineMarkers { get; set; }
/// <summary>
/// 切点标记集合
/// </summary>
public List<SquareMarker> TangentMarkers { get; set; }
/// <summary>
/// 车辆通行空间标记集合
/// </summary>
public List<VehicleSpaceMarker> VehicleSpaceMarkers { get; set; }
/// <summary>
/// 是否显示控制点可视化(用户意图)
/// </summary>
public bool ShowControlVisualization { get; set; } = true;
/// <summary>
/// 最后更新时间
/// </summary>
public DateTime LastUpdated { get; set; }
/// <summary>
/// 构造函数
/// </summary>
public PathVisualization()
{
PointMarkers = new List<CircleMarker>();
ControlLineMarkers = new List<LineMarker>();
PathLineMarkers = new List<LineMarker>();
TangentMarkers = new List<SquareMarker>();
VehicleSpaceMarkers = new List<VehicleSpaceMarker>();
LastUpdated = DateTime.Now;
}
public override string ToString()
{
return $"PathVisualization[PathId={PathId}, 点数={PointMarkers.Count}, 控制连线={ControlLineMarkers.Count}, 路径连线={PathLineMarkers.Count}]";
}
}
/// <summary>
/// 路径点圆形标记渲染插件
/// 使用Graphics.Circle在3D空间绘制圆形标记
/// </summary>
[Plugin("PathPointRenderPlugin", "NavisworksTransport", DisplayName = "路径点圆形标记渲染器")]
public class PathPointRenderPlugin : RenderPlugin
{
private readonly object _lockObject = new object();
private Dictionary<string, PathVisualization> _pathVisualizations = new Dictionary<string, PathVisualization>();
private bool _isEnabled = true;
// 预览点标记
private CircleMarker _previewMarker = null;
// 预览连线标记
private List<LineMarker> _previewLines = new List<LineMarker>();
// 空轨基准路径可视化
private Dictionary<string, PathVisualization> _railBaselineVisualizations = new Dictionary<string, PathVisualization>();
// 当前网格大小(米),用于自适应点大小计算
private double _currentGridSizeInMeters;
// 路径可视化模式配置
private PathVisualizationMode _visualizationMode = PathVisualizationMode.RibbonLine;
// 网格点类型配置
private GridPointType _gridPointType = GridPointType.Rectangle;
// 车辆参数必须通过SetVehicleParameters方法设置
private double _vehicleLength;
private double _vehicleWidth;
private double _vehicleHeight;
private double _safetyMargin;
// 静态实例,用于外部访问
private static PathPointRenderPlugin _instance;
// 防抖机制:记录上次刷新时间(使用高精度计数器)
private long _lastRefreshTicks = 0;
/// <summary>
/// 构造函数
/// </summary>
public PathPointRenderPlugin()
{
_instance = this;
}
/// <summary>
/// 获取静态实例
/// </summary>
public static PathPointRenderPlugin Instance => _instance;
/// <summary>
/// 路径可视化模式
/// </summary>
public PathVisualizationMode VisualizationMode
{
get { return _visualizationMode; }
set
{
_visualizationMode = value;
// 模式改变时刷新所有普通路径(排除网格)
RefreshNormalPaths();
}
}
/// <summary>
/// 网格点类型
/// </summary>
public GridPointType GridPointType
{
get { return _gridPointType; }
set
{
_gridPointType = value;
// 类型改变时刷新所有网格路径
RefreshGridPaths();
}
}
/// <summary>
/// 是否启用渲染
/// </summary>
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
// 触发视图刷新
if (Application.ActiveDocument?.ActiveView != null)
{
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
}
}
}
/// <summary>
/// 当前路径总数
/// </summary>
public int PathCount
{
get
{
lock (_lockObject)
{
return _pathVisualizations.Count;
}
}
}
/// <summary>
/// 当前点标记总数
/// </summary>
public int MarkerCount
{
get
{
lock (_lockObject)
{
return _pathVisualizations.Values.Sum(v => v.PointMarkers.Count);
}
}
}
/// <summary>
/// Navisworks渲染回调方法
/// </summary>
/// <param name="view">当前视图</param>
/// <param name="graphics">图形上下文</param>
public override void Render(View view, Graphics graphics)
{
if (!_isEnabled)
{
return;
}
try
{
// 快速检查避免频繁的API调用
var activeDoc = Application.ActiveDocument;
if (activeDoc?.Models == null || activeDoc.Models.Count == 0)
{
return; // 静默返回
}
// 检查是否有路径或预览点需要渲染
int pathCount;
int railBaselineCount;
bool hasPreviewPoint;
lock (_lockObject)
{
pathCount = _pathVisualizations.Count;
railBaselineCount = _railBaselineVisualizations.Count;
hasPreviewPoint = _previewMarker != null;
}
if (pathCount == 0 && railBaselineCount == 0 && !hasPreviewPoint)
{
return;
}
// 使用BeginModelContext确保正确的渲染上下文
graphics.BeginModelContext();
lock (_lockObject)
{
// 渲染所有正式路径
foreach (var visualization in _pathVisualizations.Values)
{
// 渲染控制点可视化(用户意图)
if (visualization.ShowControlVisualization)
{
// 渲染所有点标记
foreach (var pointMarker in visualization.PointMarkers)
{
graphics.Color(pointMarker.Color, pointMarker.Alpha);
RenderPointMarker(graphics, pointMarker);
}
// 渲染控制点连线(半透明)
foreach (var controlLineMarker in visualization.ControlLineMarkers)
{
var style = GetRenderStyle(RenderStyleName.PreviewLine);
graphics.Color(style.Color, style.Alpha); // 完全使用PreviewLine的颜色和透明度
graphics.Cylinder(controlLineMarker.StartPoint, controlLineMarker.EndPoint, controlLineMarker.Radius);
}
}
// 渲染真实路径可视化(根据模式选择)
if (_visualizationMode == PathVisualizationMode.VehicleSpace)
{
// VehicleSpace 模式:使用车辆通行空间渲染(高高度长方体)
foreach (var pathLineMarker in visualization.PathLineMarkers)
{
RenderRibbonLineMarker(graphics, pathLineMarker);
}
}
else if (_visualizationMode == PathVisualizationMode.RibbonLine)
{
// RibbonLine 模式:使用带状连线渲染(低高度长方体)
// 先渲染切点(在底层)
foreach (var tangentMarker in visualization.TangentMarkers)
{
RenderSquareMarker(graphics, tangentMarker);
}
// 再渲染路径连线
foreach (var pathLineMarker in visualization.PathLineMarkers)
{
RenderRibbonLineMarker(graphics, pathLineMarker);
}
}
else
{
// StandardLine 模式:使用圆柱体渲染
// 先渲染切点(在底层)
foreach (var tangentMarker in visualization.TangentMarkers)
{
RenderSquareMarker(graphics, tangentMarker);
}
// 再渲染路径连线
foreach (var pathLineMarker in visualization.PathLineMarkers)
{
RenderLineMarker(graphics, pathLineMarker);
}
}
}
// 渲染预览点(灰色)
if (_previewMarker != null && _previewMarker.IsVisible)
{
graphics.Color(_previewMarker.Color, 0.7); // 使用半透明效果
RenderPointMarker(graphics, _previewMarker);
}
// 渲染预览连线(灰色)
if (_previewLines.Count > 0)
{
foreach (var previewLine in _previewLines)
{
graphics.Color(previewLine.Color, 0.7); // 使用半透明效果
graphics.Cylinder(previewLine.StartPoint, previewLine.EndPoint, previewLine.Radius);
}
}
// 渲染空轨基准路径(浅红色)
if (_railBaselineVisualizations.Count > 0)
{
foreach (var visualization in _railBaselineVisualizations.Values)
{
foreach (var pathLineMarker in visualization.PathLineMarkers)
{
graphics.Color(pathLineMarker.Color, 0.7); // 使用70%透明度
graphics.Cylinder(pathLineMarker.StartPoint, pathLineMarker.EndPoint, pathLineMarker.Radius);
}
}
}
}
graphics.EndModelContext();
}
catch (Exception ex)
{
// 渲染异常应该静默处理避免影响Navisworks主程序
LogManager.Error($"[渲染异常] {ex.Message}", ex);
// 不输出堆栈信息,避免日志过量
}
}
#region
/// <summary>
/// 渲染完整路径
/// </summary>
/// <param name="pathRoute">路径数据</param>
public void RenderPath(PathRoute pathRoute)
{
if (pathRoute == null)
{
return;
}
try
{
var visualization = new PathVisualization
{
PathId = pathRoute.Id,
PathRoute = pathRoute
};
BuildVisualization(visualization);
lock (_lockObject)
{
_pathVisualizations[pathRoute.Id] = visualization;
}
RequestViewRefresh();
}
catch (Exception ex)
{
LogManager.Error($"[路径渲染] 渲染路径失败: {ex.Message}", ex);
}
}
/// <summary>
/// 只渲染点标记,不绘制连线(用于自动路径规划的起终点显示)
/// </summary>
public void RenderPointOnly(PathRoute pathRoute)
{
if (pathRoute == null)
{
return;
}
try
{
// 检查是否是网格可视化路径
bool isGridVisualization = pathRoute.Id == "grid_visualization_all" ||
pathRoute.Id == "grid_visualization_channel" ||
pathRoute.Id == "grid_visualization_door" ||
pathRoute.Id == "grid_visualization_unknown" ||
pathRoute.Id == "grid_visualization_obstacle";
var visualization = new PathVisualization
{
PathId = pathRoute.Id,
PathRoute = pathRoute
};
// 只构建点标记,不构建连线
BuildPointMarkersOnly(visualization);
lock (_lockObject)
{
_pathVisualizations[pathRoute.Id] = visualization;
}
RequestViewRefresh();
}
catch (Exception ex)
{
LogManager.Error($"[点标记渲染] 渲染点标记失败: {ex.Message}", ex);
}
}
/// <summary>
/// 只构建点标记,不构建连线
/// </summary>
private void BuildPointMarkersOnly(PathVisualization visualization)
{
// 清空现有标记
visualization.PointMarkers.Clear();
visualization.ControlLineMarkers.Clear(); // 确保没有控制点连线
visualization.PathLineMarkers.Clear(); // 确保没有路径连线
var points = visualization.PathRoute.Points;
if (points.Count == 0) return;
// 检查是否是网格可视化路径
bool isGridVisualization = visualization.PathId == "grid_visualization_all" ||
visualization.PathId == "grid_visualization_channel" ||
visualization.PathId == "grid_visualization_door" ||
visualization.PathId == "grid_visualization_unknown" ||
visualization.PathId == "grid_visualization_obstacle";
// 按索引排序点
var sortedPoints = points.OrderBy(p => p.Index).ToList();
// 只构建点标记,不构建连线
foreach (var point in sortedPoints)
{
var pointMarker = CreatePointMarker(point);
visualization.PointMarkers.Add(pointMarker);
}
visualization.LastUpdated = DateTime.Now;
}
/// <summary>
/// 刷新路径可视化
/// </summary>
/// <param name="pathId">路径ID</param>
public void RefreshPath(string pathId)
{
lock (_lockObject)
{
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
{
BuildVisualization(visualization);
RequestViewRefresh();
}
}
}
/// <summary>
/// 设置控制点可视化的显示状态
/// </summary>
/// <param name="pathId">路径ID</param>
/// <param name="show">是否显示</param>
public void SetControlVisualizationVisibility(string pathId, bool show)
{
lock (_lockObject)
{
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
{
if (visualization.ShowControlVisualization != show)
{
visualization.ShowControlVisualization = show;
RequestViewRefresh();
}
}
}
}
/// <summary>
/// 切换控制点可视化的显示状态
/// </summary>
/// <param name="pathId">路径ID</param>
public void ToggleControlVisualization(string pathId)
{
lock (_lockObject)
{
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
{
visualization.ShowControlVisualization = !visualization.ShowControlVisualization;
RequestViewRefresh();
}
}
}
/// <summary>
/// 获取所有路径可视化对象
/// </summary>
/// <returns>所有路径可视化对象的列表</returns>
public List<PathVisualization> GetAllPathVisualizations()
{
lock (_lockObject)
{
return _pathVisualizations.Values.ToList();
}
}
/// <summary>
/// 判断是否为网格可视化路径
/// </summary>
private bool IsGridVisualizationPath(string pathId)
{
return pathId != null && (
pathId.StartsWith("grid_visualization_") ||
pathId == "grid_visualization_all" ||
pathId == "voxel_grid_visualization"); // 体素网格可视化
}
/// <summary>
/// 刷新所有普通路径(排除网格可视化)
/// </summary>
private void RefreshNormalPaths()
{
lock (_lockObject)
{
var normalPathIds = _pathVisualizations.Keys
.Where(id => !IsGridVisualizationPath(id))
.ToList();
foreach (var pathId in normalPathIds)
{
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
{
BuildVisualization(visualization);
}
}
if (normalPathIds.Count > 0)
{
RequestViewRefresh();
}
}
}
/// <summary>
/// 刷新所有网格路径
/// </summary>
private void RefreshGridPaths()
{
lock (_lockObject)
{
var gridPathIds = _pathVisualizations.Keys
.Where(id => IsGridVisualizationPath(id))
.ToList();
foreach (var pathId in gridPathIds)
{
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
{
BuildPointMarkersOnly(visualization);
}
}
if (gridPathIds.Count > 0)
{
RequestViewRefresh();
}
}
}
/// <summary>
/// 移除路径
/// </summary>
/// <param name="pathId">路径ID</param>
public void RemovePath(string pathId)
{
lock (_lockObject)
{
if (_pathVisualizations.Remove(pathId))
{
RequestViewRefresh();
}
}
}
/// <summary>
/// 渲染空轨基准路径
/// </summary>
/// <param name="railModelId">空轨模型ID</param>
/// <param name="baselinePoints">基准路径点列表</param>
public void RenderRailBaseline(string railModelId, List<Point3D> baselinePoints)
{
if (string.IsNullOrEmpty(railModelId) || baselinePoints == null || baselinePoints.Count < 2)
{
LogManager.Warning($"[路径渲染] RenderRailBaseline参数无效: railModelId={railModelId}, points={(baselinePoints?.Count ?? 0)}");
return;
}
try
{
LogManager.Info($"[路径渲染] 开始渲染空轨基准路径: {railModelId}, {baselinePoints.Count} 个点");
// 先清除该模型的旧基准路径可视化
ClearRailBaseline(railModelId);
// 创建PathVisualization对象
var visualization = new PathVisualization
{
PathId = railModelId,
ShowControlVisualization = false, // 不显示控制点
LastUpdated = DateTime.Now
};
var renderStyle = GetRenderStyle(RenderStyleName.RailBaseline);
LogManager.Info($"[路径渲染] RailBaseline样式: R={renderStyle.Color.R}, G={renderStyle.Color.G}, B={renderStyle.Color.B}, Alpha={renderStyle.Alpha}");
// 创建连线标记
for (int i = 0; i < baselinePoints.Count - 1; i++)
{
var start = baselinePoints[i];
var end = baselinePoints[i + 1];
var lineMarker = new LineMarker
{
StartPoint = start,
EndPoint = end,
Color = renderStyle.Color,
Radius = GetLineRadius() * 0.2,
SegmentType = PathSegmentType.Straight,
FromIndex = i,
ToIndex = i + 1
};
visualization.PathLineMarkers.Add(lineMarker);
}
LogManager.Info($"[路径渲染] 创建了 {visualization.PathLineMarkers.Count} 个连线标记");
lock (_lockObject)
{
_railBaselineVisualizations[railModelId] = visualization;
LogManager.Info($"[路径渲染] 已添加到字典,当前共有 {_railBaselineVisualizations.Count} 个基准路径");
}
RequestViewRefresh();
LogManager.Info($"[路径渲染] 已请求视图刷新");
}
catch (Exception ex)
{
LogManager.Error($"[路径渲染] 渲染空轨基准路径失败: {ex.Message}", ex);
}
}
/// <summary>
/// 清除指定空轨的基准路径可视化
/// </summary>
/// <param name="railModelId">空轨模型ID</param>
public void ClearRailBaseline(string railModelId)
{
if (string.IsNullOrEmpty(railModelId))
{
return;
}
lock (_lockObject)
{
if (_railBaselineVisualizations.Remove(railModelId))
{
RequestViewRefresh();
LogManager.Info($"[路径渲染] 已清除空轨基准路径: {railModelId}");
}
}
}
/// <summary>
/// 获取所有空轨基准路径
/// </summary>
/// <returns>所有空轨基准路径的字典</returns>
public Dictionary<string, List<Point3D>> GetAllRailBaselinePaths()
{
lock (_lockObject)
{
var result = new Dictionary<string, List<Point3D>>();
foreach (var kvp in _railBaselineVisualizations)
{
result[kvp.Key] = kvp.Value.PathLineMarkers.Select(m => m.StartPoint).ToList();
}
return result;
}
}
/// <summary>
/// 清除所有空轨基准路径可视化
/// </summary>
public void ClearAllRailBaselines()
{
lock (_lockObject)
{
int count = _railBaselineVisualizations.Count;
_railBaselineVisualizations.Clear();
if (count > 0)
{
RequestViewRefresh();
LogManager.Info($"[路径渲染] 已清除所有空轨基准路径, 共 {count} 个");
}
}
}
/// <summary>
/// 清空所有路径
/// </summary>
public void ClearAllPaths()
{
lock (_lockObject)
{
var count = _pathVisualizations.Count;
_pathVisualizations.Clear();
RequestViewRefresh();
}
}
/// <summary>
/// 清空路径,但保留指定的路径
/// </summary>
/// <param name="excludedPathIds">要保留的路径ID列表</param>
public void ClearPathsExcept(params string[] excludedPathIds)
{
try
{
lock (_lockObject)
{
// 防御性编程:处理空参数
if (excludedPathIds == null)
{
excludedPathIds = new string[0];
}
var excludedSet = new HashSet<string>(excludedPathIds.Where(id => !string.IsNullOrEmpty(id)));
var toRemove = _pathVisualizations.Keys.Where(id => !excludedSet.Contains(id)).ToList();
// 检查排除的路径是否实际存在
var existingExcluded = excludedSet.Where(id => _pathVisualizations.ContainsKey(id)).ToList();
var nonExistingExcluded = excludedSet.Where(id => !_pathVisualizations.ContainsKey(id)).ToList();
int removedCount = 0;
foreach (var pathId in toRemove)
{
if (_pathVisualizations.Remove(pathId))
{
removedCount++;
}
}
if (removedCount > 0)
{
RequestViewRefresh();
}
}
}
catch (Exception ex)
{
LogManager.Error($"[选择性清空] 清理路径时发生异常: {ex.Message}", ex);
// 即使发生异常也不抛出,避免影响主流程
}
}
/// <summary>
/// 构建路径可视化
/// </summary>
/// <param name="visualization">路径可视化对象</param>
private void BuildVisualization(PathVisualization visualization)
{
// 清空现有标记
visualization.PointMarkers.Clear();
visualization.ControlLineMarkers.Clear();
visualization.PathLineMarkers.Clear();
visualization.TangentMarkers.Clear();
visualization.VehicleSpaceMarkers.Clear();
var points = visualization.PathRoute.Points;
if (points.Count == 0) return;
// 按索引排序点
var sortedPoints = points.OrderBy(p => p.Index).ToList();
// 构建点标记(所有控制点)
foreach (var point in sortedPoints)
{
var pointMarker = CreatePointMarker(point);
visualization.PointMarkers.Add(pointMarker);
}
// 构建控制点连线(用户意图,半透明)
BuildControlLines(visualization, sortedPoints);
// 地面路径使用曲线化后的路径Edges
// 空轨路径只显示控制点连线,不显示路径连线
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
{
// 空轨路径:只显示控制点连线,不构建路径连线
LogManager.Debug($"[路径渲染] 空轨路径只显示控制点连线,不显示路径连线");
}
else if (visualization.PathRoute.Edges != null && visualization.PathRoute.Edges.Count > 0)
{
// 地面路径:使用曲线化后的路径
BuildPathLines(visualization, sortedPoints);
}
// 如果路径未完成,添加灰色的原始终点标记
if (!visualization.PathRoute.IsComplete && visualization.PathRoute.OriginalEndPoint != null)
{
var originalEndPoint = visualization.PathRoute.OriginalEndPoint;
var unreachedStyle = GetRenderStyle(RenderStyleName.UnreachedEndPoint);
var grayEndMarker = new CircleMarker
{
Center = originalEndPoint,
Normal = new Vector3D(0, 0, 1),
Radius = GetRadiusForPointType(PathPointType.EndPoint),
Color = unreachedStyle.Color, // 未到达终点颜色
Alpha = unreachedStyle.Alpha, // 透明度,统一管理
Filled = true,
PointType = PathPointType.EndPoint,
SequenceNumber = -1, // 特殊序号表示这是未到达的终点
CreatedTime = DateTime.Now
};
visualization.PointMarkers.Add(grayEndMarker);
}
visualization.LastUpdated = DateTime.Now;
}
/// <summary>
/// 构建控制点连线(用户意图,半透明)
/// </summary>
/// <param name="visualization">路径可视化对象</param>
/// <param name="sortedPoints">排序后的路径点列表</param>
private void BuildControlLines(PathVisualization visualization, List<PathPoint> sortedPoints)
{
// 构建控制点之间的连线(所有相邻控制点)
for (int i = 0; i < sortedPoints.Count - 1; i++)
{
var currentPoint = sortedPoints[i];
var nextPoint = sortedPoints[i + 1];
var controlLineMarker = new LineMarker
{
StartPoint = currentPoint.Position,
EndPoint = nextPoint.Position,
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color,
Radius = GetLineRadius() * 0.5, // 比实际路径细
SegmentType = PathSegmentType.Straight,
FromIndex = currentPoint.Index,
ToIndex = nextPoint.Index
};
visualization.ControlLineMarkers.Add(controlLineMarker);
}
}
/// <summary>
/// 构建路径连线(真实路径,不透明)
/// </summary>
/// <param name="visualization">路径可视化对象</param>
/// <param name="sortedPoints">排序后的路径点列表</param>
private void BuildPathLines(PathVisualization visualization, List<PathPoint> sortedPoints)
{
var edges = visualization.PathRoute.Edges;
// 1. 收集在路径上的控制点ID
var onPathPointIds = new HashSet<string>();
foreach (var edge in edges)
{
if (!string.IsNullOrEmpty(edge.StartPointId))
onPathPointIds.Add(edge.StartPointId);
if (!string.IsNullOrEmpty(edge.EndPointId))
onPathPointIds.Add(edge.EndPointId);
}
// 2. 更新点标记的透明度
foreach (var pointMarker in visualization.PointMarkers)
{
var point = sortedPoints.FirstOrDefault(p => p.Id == pointMarker.PathPoint?.Id);
if (point != null)
{
bool isOnPath = onPathPointIds.Contains(point.Id);
pointMarker.IsOffPath = !isOnPath;
}
}
// 获取单位转换系数
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
// 根据可视化模式计算高度和宽度
double heightInMeters;
double widthInMeters;
Color lineColor;
double lineOpacity;
if (_visualizationMode == PathVisualizationMode.VehicleSpace)
{
// VehicleSpace 模式:使用车辆高度和宽度
heightInMeters = _vehicleHeight + _safetyMargin;
widthInMeters = Math.Max(_vehicleLength, _vehicleWidth) + 2 * _safetyMargin;
var renderStyle = GetRenderStyle(RenderStyleName.VehicleSpace);
lineColor = renderStyle.Color;
lineOpacity = renderStyle.Alpha;
}
else if (_visualizationMode == PathVisualizationMode.RibbonLine)
{
// RibbonLine 模式:使用较低的带状高度
heightInMeters = GetLineRadius() / metersToModelUnits * 0.1;
widthInMeters = GetLineRadius() / metersToModelUnits * 2; // 路径宽度(转换为米)
var renderStyle = GetRenderStyle(RenderStyleName.Line);
lineColor = renderStyle.Color;
lineOpacity = renderStyle.Alpha;
}
else
{
// StandardLine 模式:不需要高度和宽度(圆柱体)
heightInMeters = 0;
widthInMeters = 0;
var renderStyle = GetRenderStyle(RenderStyleName.Line);
lineColor = renderStyle.Color;
lineOpacity = renderStyle.Alpha;
}
// 转换为模型单位
double heightInModelUnits = heightInMeters * metersToModelUnits;
double widthInModelUnits = widthInMeters * metersToModelUnits;
// 3. 构建路径连线Edges包含直线段和圆弧段
foreach (var edge in edges)
{
if (edge.SegmentType == PathSegmentType.Straight)
{
// 直线段检查SampledPoints是否有效
if (edge.SampledPoints != null && edge.SampledPoints.Count >= 2)
{
var lineMarker = new LineMarker
{
StartPoint = edge.SampledPoints.First(),
EndPoint = edge.SampledPoints.Last(),
Color = lineColor,
Radius = GetLineRadius(),
SegmentType = PathSegmentType.Straight,
SampledPoints = edge.SampledPoints,
Height = heightInModelUnits, // 根据模式设置高度
Width = widthInModelUnits, // 根据模式设置宽度
Opacity = lineOpacity // 根据模式设置透明度
};
visualization.PathLineMarkers.Add(lineMarker);
}
}
else if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
{
// 圆弧段检查Trajectory是否有效
// 检查是否发生安全截断
bool hasSafetyTruncation = edge.Trajectory.ActualRadius < edge.Trajectory.RequestedRadius;
// 如果发生安全截断,使用安全警告样式
Color arcLineColor = lineColor;
double arcLineOpacity = lineOpacity;
if (hasSafetyTruncation)
{
var warningStyle = GetRenderStyle(RenderStyleName.SafetyWarning);
arcLineColor = warningStyle.Color;
arcLineOpacity = warningStyle.Alpha;
}
var arcMarker = new LineMarker
{
StartPoint = edge.Trajectory.Ts,
EndPoint = edge.Trajectory.Te,
Color = arcLineColor,
Radius = GetLineRadius(),
SegmentType = PathSegmentType.Arc,
Trajectory = edge.Trajectory,
SampledPoints = edge.SampledPoints,
Height = heightInModelUnits, // 根据模式设置高度
Width = widthInModelUnits, // 根据模式设置宽度
Opacity = arcLineOpacity // 根据模式设置透明度
};
visualization.PathLineMarkers.Add(arcMarker);
// 添加切点标记StandardLine 和 RibbonLine 模式才添加)
if (_visualizationMode != PathVisualizationMode.VehicleSpace)
{
AddTangentMarkers(visualization, edge);
}
}
}
}
/// <summary>
/// 添加切点标记
/// </summary>
/// <param name="visualization">路径可视化对象</param>
/// <param name="edge">路径边</param>
private void AddTangentMarkers(PathVisualization visualization, PathEdge edge)
{
if (edge.Trajectory == null) return;
// 使用统一样式
var tangentStyle = GetRenderStyle(RenderStyleName.TangentPoint);
var tangentColor = tangentStyle.Color;
// 路径宽度基准值(柱状和带状都是 GetLineRadius() * 2
var pathWidth = GetLineRadius() * 2;
// 切点半径:路径半径的一半(切点直径 = 路径宽度的一半)
var tangentRadius = GetLineRadius() * 0.5;
// 切点高度:路径宽度的一半
var tangentHeight = pathWidth * 0.5;
// 进入切点Ts
var tsMarker = new SquareMarker
{
Center = edge.Trajectory.Ts,
Normal = new Vector3D(0, 0, 1),
Size = tangentRadius * 2, // 直径
Height = tangentHeight, // 高度
Color = tangentColor,
Alpha = 0.8, // 80%透明度
EdgeId = edge.Id,
TangentType = TangentPointType.EntryTangent
};
visualization.TangentMarkers.Add(tsMarker);
// 退出切点Te
var teMarker = new SquareMarker
{
Center = edge.Trajectory.Te,
Normal = new Vector3D(0, 0, 1),
Size = tangentRadius * 2, // 直径
Height = tangentHeight, // 高度
Color = tangentColor,
Alpha = 0.8, // 80%透明度
EdgeId = edge.Id,
TangentType = TangentPointType.ExitTangent
};
visualization.TangentMarkers.Add(teMarker);
}
/// <summary>
/// 创建车辆通行空间标记
/// </summary>
/// <param name="fromPoint">起点</param>
/// <param name="toPoint">终点</param>
/// <returns>车辆通行空间标记</returns>
private VehicleSpaceMarker CreateVehicleSpaceMarker(PathPoint fromPoint, PathPoint toPoint)
{
// 获取单位转换系数
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
// 计算车辆通行空间宽度(米)
double vehicleInflationWidthInMeters = Math.Max(_vehicleLength, _vehicleWidth) + 2 * _safetyMargin;
// 计算车辆通行空间高度(米):车辆高度 + 安全间隙
double vehicleSpaceHeightInMeters = _vehicleHeight + _safetyMargin;
var style = GetRenderStyle(RenderStyleName.VehicleSpace);
return new VehicleSpaceMarker
{
StartPoint = fromPoint.Position,
EndPoint = toPoint.Position,
Width = vehicleInflationWidthInMeters * metersToModelUnits, // 转换为模型单位
Height = vehicleSpaceHeightInMeters * metersToModelUnits, // 转换为模型单位
Color = style.Color, // 车辆通行空间颜色
Alpha = style.Alpha, // 透明度,统一管理
FromIndex = fromPoint.Index,
ToIndex = toPoint.Index
};
}
/// <summary>
/// 创建点标记
/// </summary>
/// <param name="point">路径点</param>
/// <returns>圆形标记</returns>
private CircleMarker CreatePointMarker(PathPoint point)
{
// 检查是否是网格/体素可视化点(通过名称判断)
bool isGridVisualization = point.Name.StartsWith("网格(") || point.Name.StartsWith("网格_") ||
point.Name.StartsWith("体素("); // 体素点
// 确定网格点样式(颜色+透明度)
RenderStyle gridStyle = new RenderStyle(Color.White, 1.0); // 默认样式
if (isGridVisualization)
{
// 根据网格类型确定样式
if (point.Name.Contains("空洞") || point.Name.Contains("Unknown"))
{
gridStyle = GetRenderStyle(RenderStyleName.GridUnknown);
}
else if (point.Name.Contains("障碍") || point.Name.Contains("Obstacle"))
{
gridStyle = GetRenderStyle(RenderStyleName.GridObstacle);
}
else if (point.Name.Contains("通道") || point.Name.Contains("Channel") || point.Name.Contains("开放"))
{
gridStyle = GetRenderStyle(RenderStyleName.GridChannel);
}
// 也可以通过Notes字段检查
else if (!string.IsNullOrEmpty(point.Notes) && point.Notes.StartsWith("GridType:"))
{
var gridTypeStr = point.Notes.Substring(9); // 去掉"GridType:"前缀
if (gridTypeStr == "Unknown")
gridStyle = GetRenderStyle(RenderStyleName.GridUnknown);
else if (gridTypeStr == "Obstacle")
gridStyle = GetRenderStyle(RenderStyleName.GridObstacle);
else
gridStyle = GetRenderStyle(RenderStyleName.GridChannel);
}
// 门网格点使用50%透明度覆盖默认透明度
if (point.Name.Contains("门"))
{
gridStyle = new RenderStyle(gridStyle.Color, 0.5);
}
}
return new CircleMarker
{
Center = point.Position,
Normal = new Vector3D(0, 0, 1),
Radius = isGridVisualization ? GetRadiusForGridVisualization() : GetRadiusForPointType(point.Type),
Color = isGridVisualization ? gridStyle.Color : GetColorForPointType(point.Type),
Alpha = isGridVisualization ? gridStyle.Alpha : 1.0,
Filled = true,
PointType = point.Type,
GridPointType = isGridVisualization ? _gridPointType : GridPointType.Sphere, // 网格点使用配置的类型,其他点使用球形
SequenceNumber = point.Index, // 使用Index而不是任意序号
CreatedTime = DateTime.Now
};
}
/// <summary>
/// 获取网格可视化小球的半径(比正常路径点小)
/// </summary>
/// <returns>网格可视化半径</returns>
private double GetRadiusForGridVisualization()
{
// 使用标准尺寸的1/5作为网格点尺寸
double standardRadiusInMeters = GetStandardRadiusInMeters();
double radiusInMeters = standardRadiusInMeters / 5.0;
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
return radiusInMeters * metersToModelUnits;
}
/// <summary>
/// 设置当前网格大小(米),用于自适应点大小计算
/// </summary>
/// <param name="gridSizeInMeters">网格大小(米)</param>
public void SetGridSize(double gridSizeInMeters)
{
_currentGridSizeInMeters = gridSizeInMeters;
}
/// <summary>
/// 设置车辆参数从PathPlanningManager同步
/// </summary>
/// <param name="vehicleLength">车辆长度(米)</param>
/// <param name="vehicleWidth">车辆宽度(米)</param>
/// <param name="vehicleHeight">车辆高度(米)</param>
/// <param name="safetyMargin">安全间隙(米)</param>
public void SetVehicleParameters(double vehicleLength, double vehicleWidth, double vehicleHeight, double safetyMargin)
{
_vehicleLength = vehicleLength;
_vehicleWidth = vehicleWidth;
_vehicleHeight = vehicleHeight;
_safetyMargin = safetyMargin;
// 车辆参数改变时刷新车辆空间模式下的所有普通路径
if (_visualizationMode == PathVisualizationMode.VehicleSpace)
{
RefreshNormalPaths();
}
}
#region
/// <summary>
/// 根据样式名称获取对应的渲染样式(颜色+透明度)
/// </summary>
/// <param name="styleName">样式名称</param>
/// <returns>对应的渲染样式</returns>
private RenderStyle GetRenderStyle(RenderStyleName styleName)
{
switch (styleName)
{
case RenderStyleName.StartPoint:
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.9); // Material Green起点10%透明
case RenderStyleName.EndPoint:
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.9); // Material Red终点10%透明
case RenderStyleName.WayPoint:
return new RenderStyle(Color.FromByteRGB(33, 150, 243), 0.9); // Material Blue路径点10%透明
case RenderStyleName.Line:
return new RenderStyle(Color.FromByteRGB(255, 152, 0), 0.8); // Material Orange连线20%透明
case RenderStyleName.VehicleSpace:
return new RenderStyle(Color.FromByteRGB(158, 158, 158), 0.4); // Material Grey车辆空间60%透明
case RenderStyleName.PreviewPoint:
return new RenderStyle(Color.White, 0.7); // 白色预览点30%透明
case RenderStyleName.PreviewLine:
return new RenderStyle(Color.FromByteRGB(128, 128, 128), 0.5); // 灰色预览连线50%透明
case RenderStyleName.UnreachedEndPoint:
return new RenderStyle(Color.FromByteRGB(139, 0, 0), 0.7); // 深红色未到达终点30%透明
case RenderStyleName.GridChannel:
return new RenderStyle(Color.FromByteRGB(129, 199, 132), 0.8); // Material Light Green网格通道20%透明
case RenderStyleName.GridObstacle:
return new RenderStyle(Color.FromByteRGB(117, 117, 117), 0.8); // Material Grey网格障碍物20%透明
case RenderStyleName.GridUnknown:
return new RenderStyle(Color.FromByteRGB(255, 112, 67), 0.8); // Material Deep Orange网格未知区域20%透明
case RenderStyleName.TangentPoint:
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.8); // Material Red切点20%透明
case RenderStyleName.SafetyWarning:
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.85); // Material Red安全警告15%透明
case RenderStyleName.RailBaseline:
return new RenderStyle(Color.FromByteRGB(255, 138, 128), 0.7); // 浅红色30%透明
default:
return new RenderStyle(Color.White, 1.0); // 默认白色,完全不透明
}
}
#endregion
/// <summary>
/// 获取连线半径
/// </summary>
/// <returns>连线半径</returns>
private double GetLineRadius()
{
// 获取标准尺寸(起点尺寸)
double standardRadiusInMeters = GetStandardRadiusInMeters();
// 连线半径为标准尺寸的40%
double lineRadiusInMeters = standardRadiusInMeters * 0.4;
return lineRadiusInMeters * UnitsConverter.GetMetersToUnitsConversionFactor();
}
/// <summary>
/// 添加路径点到指定路径
/// </summary>
/// <param name="pathId">路径ID</param>
/// <param name="newPoint">新的路径点</param>
/// <param name="insertIndex">插入位置索引,-1表示添加到末尾</param>
public void AddPointToPath(string pathId, PathPoint newPoint, int insertIndex = -1)
{
var visualization = GetPathVisualization(pathId);
if (visualization == null)
{
return;
}
try
{
var pathRoute = visualization.PathRoute;
if (insertIndex == -1)
{
// 添加到末尾
newPoint.Index = pathRoute.Points.Count;
pathRoute.Points.Add(newPoint);
}
else
{
// 插入到指定位置
pathRoute.Points.Insert(insertIndex, newPoint);
// 重新分配所有点的索引
ReindexPoints(pathRoute);
}
// 重建可视化
BuildVisualization(visualization);
RequestViewRefresh();
}
catch (Exception ex)
{
LogManager.Error($"[路径编辑] 添加路径点失败: {ex.Message}", ex);
}
}
/// <summary>
/// 从指定路径移除路径点
/// </summary>
/// <param name="pathId">路径ID</param>
/// <param name="pointIndex">要移除的点索引</param>
public void RemovePointFromPath(string pathId, int pointIndex)
{
var visualization = GetPathVisualization(pathId);
if (visualization == null)
{
return;
}
try
{
var pathRoute = visualization.PathRoute;
var pointToRemove = pathRoute.Points.FirstOrDefault(p => p.Index == pointIndex);
if (pointToRemove != null)
{
pathRoute.Points.Remove(pointToRemove);
// 重新分配索引
ReindexPoints(pathRoute);
// 重建可视化
BuildVisualization(visualization);
RequestViewRefresh();
}
else
{
LogManager.Warning($"[路径编辑] 未找到索引为 {pointIndex} 的路径点");
}
}
catch (Exception ex)
{
LogManager.Error($"[路径编辑] 移除路径点失败: {ex.Message}", ex);
}
}
/// <summary>
/// 更新指定路径中的路径点
/// </summary>
/// <param name="pathId">路径ID</param>
/// <param name="pointIndex">要更新的点索引</param>
/// <param name="updatedPoint">更新后的路径点</param>
public void UpdatePointInPath(string pathId, int pointIndex, PathPoint updatedPoint)
{
var visualization = GetPathVisualization(pathId);
if (visualization == null)
{
return;
}
try
{
var pathRoute = visualization.PathRoute;
var existingPoint = pathRoute.Points.FirstOrDefault(p => p.Index == pointIndex);
if (existingPoint != null)
{
// 保持索引不变
updatedPoint.Index = pointIndex;
// 替换点
var index = pathRoute.Points.IndexOf(existingPoint);
pathRoute.Points[index] = updatedPoint;
// 重建可视化
BuildVisualization(visualization);
RequestViewRefresh();
}
else
{
LogManager.Warning($"[路径编辑] 未找到索引为 {pointIndex} 的路径点");
}
}
catch (Exception ex)
{
LogManager.Error($"[路径编辑] 更新路径点失败: {ex.Message}", ex);
}
}
/// <summary>
/// 重新分配路径点索引
/// </summary>
/// <param name="pathRoute">路径对象</param>
private void ReindexPoints(PathRoute pathRoute)
{
// 按照当前在List中的位置重新分配连续索引
for (int i = 0; i < pathRoute.Points.Count; i++)
{
pathRoute.Points[i].Index = i;
}
}
/// <summary>
/// 获取路径可视化对象
/// </summary>
/// <param name="pathId">路径ID</param>
/// <returns>路径可视化对象如果不存在返回null</returns>
private PathVisualization GetPathVisualization(string pathId)
{
lock (_lockObject)
{
_pathVisualizations.TryGetValue(pathId, out var visualization);
return visualization;
}
}
#endregion
/// <summary>
/// 渲染预览点(灰色显示)
/// </summary>
/// <param name="previewPoint">预览点对象</param>
public void RenderPreviewPoint(PathPoint previewPoint)
{
try
{
if (previewPoint == null)
{
return;
}
lock (_lockObject)
{
// 获取适当的半径
double radius = GetRadiusForPointType(previewPoint.Type);
// 获取预览点样式(颜色+透明度)
var previewStyle = GetRenderStyle(RenderStyleName.PreviewPoint);
// 创建预览点标记
_previewMarker = new CircleMarker
{
Position = previewPoint.Position,
Radius = radius,
Color = previewStyle.Color,
Alpha = previewStyle.Alpha,
IsVisible = true,
PathPoint = previewPoint
};
// 请求刷新视图
RequestViewRefresh();
}
}
catch (Exception ex)
{
LogManager.Error($"[预览点渲染] 渲染预览点失败: {ex.Message}", ex);
}
}
/// <summary>
/// 清除预览点
/// </summary>
public void ClearPreviewPoint()
{
try
{
lock (_lockObject)
{
if (_previewMarker != null || _previewLines.Count > 0)
{
_previewMarker = null;
_previewLines.Clear();
// 请求刷新视图
RequestViewRefresh();
}
}
}
catch (Exception ex)
{
LogManager.Error($"[预览点渲染] 清除预览点失败: {ex.Message}", ex);
}
}
/// <summary>
/// 渲染预览连线
/// </summary>
/// <param name="previewPoint">预览点</param>
/// <param name="pathPoints">当前路径点列表</param>
public void RenderPreviewLines(PathPoint previewPoint, List<PathPoint> pathPoints)
{
try
{
if (previewPoint == null || pathPoints == null || pathPoints.Count == 0)
{
return;
}
lock (_lockObject)
{
// 清除旧的预览连线
_previewLines.Clear();
// 找到预览点应该插入的最近线段
var nearestSegment = FindNearestLineSegment(previewPoint.Position, pathPoints);
if (nearestSegment.HasValue)
{
var (prevPoint, nextPoint) = nearestSegment.Value;
// 创建两条预览连线
// 1. 前一个点 -> 预览点
var line1 = new LineMarker
{
StartPoint = prevPoint.Position,
EndPoint = previewPoint.Position,
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
Radius = GetLineRadius() // 使用与正常连线相同的直径
};
_previewLines.Add(line1);
// 2. 预览点 -> 后一个点
var line2 = new LineMarker
{
StartPoint = previewPoint.Position,
EndPoint = nextPoint.Position,
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
Radius = GetLineRadius()
};
_previewLines.Add(line2);
}
else
{
LogManager.Warning("[预览连线渲染] 未找到合适的线段插入预览点");
}
// 请求刷新视图
RequestViewRefresh();
}
}
catch (Exception ex)
{
LogManager.Error($"[预览连线渲染] 渲染预览连线失败: {ex.Message}", ex);
}
}
/// <summary>
/// 清除预览连线
/// </summary>
public void ClearPreviewLines()
{
try
{
lock (_lockObject)
{
if (_previewLines.Count > 0)
{
_previewLines.Clear();
// 请求刷新视图
RequestViewRefresh();
}
}
}
catch (Exception ex)
{
LogManager.Error($"[预览连线渲染] 清除预览连线失败: {ex.Message}", ex);
}
}
/// <summary>
/// 渲染预览路径(用于修改路径点时的预览)
/// </summary>
/// <param name="previewRoute">包含预览修改的路径</param>
public void RenderPreviewPath(PathRoute previewRoute)
{
try
{
if (previewRoute == null || previewRoute.Points.Count == 0)
{
return;
}
lock (_lockObject)
{
// 只清理预览元素,不影响原有路径
ClearPreviewPoint();
ClearPreviewLines();
// 找到预览点及其索引
var previewPointIndex = -1;
PathPoint previewPoint = null;
for (int i = 0; i < previewRoute.Points.Count; i++)
{
if (previewRoute.Points[i].Name.Contains("_预览"))
{
previewPointIndex = i;
previewPoint = previewRoute.Points[i];
break;
}
}
if (previewPoint != null && previewPointIndex >= 0)
{
// 只渲染预览点
RenderPreviewPoint(previewPoint);
// 只渲染与预览点相关的连线
// 连接前一个点到预览点
if (previewPointIndex > 0)
{
var prevPoint = previewRoute.Points[previewPointIndex - 1];
var line1 = new LineMarker
{
StartPoint = prevPoint.Position,
EndPoint = previewPoint.Position,
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
Radius = GetLineRadius()
};
_previewLines.Add(line1);
}
// 连接预览点到下一个点
if (previewPointIndex < previewRoute.Points.Count - 1)
{
var nextPoint = previewRoute.Points[previewPointIndex + 1];
var line2 = new LineMarker
{
StartPoint = previewPoint.Position,
EndPoint = nextPoint.Position,
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
Radius = GetLineRadius()
};
_previewLines.Add(line2);
}
}
else
{
LogManager.Warning("[预览路径渲染] 未找到预览点,跳过渲染");
}
RequestViewRefresh();
}
}
catch (Exception ex)
{
LogManager.Error($"[预览路径渲染] 渲染预览路径失败: {ex.Message}", ex);
}
}
/// <summary>
/// 清理所有预览渲染
/// </summary>
public void ClearPreview()
{
try
{
lock (_lockObject)
{
// 清理预览点
ClearPreviewPoint();
// 清理预览连线
ClearPreviewLines();
RequestViewRefresh();
}
}
catch (Exception ex)
{
LogManager.Error($"[清理预览] 清理预览失败: {ex.Message}", ex);
}
}
#region
/// <returns>标准尺寸(米)</returns>
/// <summary>
/// 计算标准尺寸(起点尺寸),其他元素以此为基准按比例计算
/// </summary>
/// <returns>标准尺寸(米)</returns>
private double GetStandardRadiusInMeters()
{
// 起点尺寸为网格大小的100%,并限制在合理范围内
double standardRadius = _currentGridSizeInMeters * 1.0;
// 边界限制最小0.1米最大0.5米
return Math.Max(0.1, Math.Min(0.5, standardRadius));
}
public double GetRadiusForPointType(PathPointType pointType)
{
// 获取标准尺寸(起点尺寸)
double standardRadiusInMeters = GetStandardRadiusInMeters();
// 根据点类型应用比例系数
double baseRadiusInMeters;
if (pointType == PathPointType.WayPoint)
{
// 路径点为标准尺寸的1/4
baseRadiusInMeters = standardRadiusInMeters * 0.25;
}
else
{
// 起点/终点使用标准尺寸的1/2
baseRadiusInMeters = standardRadiusInMeters * 0.5;
}
// 获取真实文档单位转换系数
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
// 转换为模型单位
double radiusInModelUnits = baseRadiusInMeters * metersToModelUnits;
return radiusInModelUnits;
}
/// <summary>
/// 根据路径点类型获取颜色
/// </summary>
public Color GetColorForPointType(PathPointType pointType)
{
switch (pointType)
{
case PathPointType.StartPoint:
return GetRenderStyle(RenderStyleName.StartPoint).Color; // 起点绿色
case PathPointType.EndPoint:
return GetRenderStyle(RenderStyleName.EndPoint).Color; // 终点红色
case PathPointType.WayPoint:
default:
return GetRenderStyle(RenderStyleName.WayPoint).Color; // 路径中间点蓝色
}
}
/// <summary>
/// 计算两点间距离
/// </summary>
private double CalculateDistance(Point3D point1, Point3D point2)
{
var dx = point1.X - point2.X;
var dy = point1.Y - point2.Y;
var dz = point1.Z - point2.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
/// <summary>
/// 渲染长方体标记(通用方法)
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="startPoint">起点</param>
/// <param name="endPoint">终点</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
private void RenderCuboidMarker(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height)
{
try
{
// 计算方向向量
var direction = new Vector3D(
endPoint.X - startPoint.X,
endPoint.Y - startPoint.Y,
endPoint.Z - startPoint.Z
);
// 计算长度
var length = Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z);
if (length < 0.001) // 避免零长度线段
return;
// 归一化方向向量
direction = new Vector3D(direction.X / length, direction.Y / length, direction.Z / length);
// 计算垂直向量在XY平面内垂直于方向向量
var right = new Vector3D(-direction.Y, direction.X, 0);
// 如果方向向量垂直于XY平面则使用不同的right向量
if (Math.Abs(direction.Z) > 0.9)
{
right = new Vector3D(1, 0, 0);
}
// 归一化right向量
var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z);
if (rightLength > 0.001)
{
right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength);
}
// 计算长方体的边界框和向量
var halfWidth = width / 2.0;
// 计算立方体原点(起点左下角)
var origin = new Point3D(
startPoint.X - halfWidth * right.X,
startPoint.Y - halfWidth * right.Y,
startPoint.Z
);
// 定义立方体的三个轴向量
var xVector = new Vector3D(direction.X * length, direction.Y * length, direction.Z * length); // 长度方向
var yVector = new Vector3D(right.X * width, right.Y * width, right.Z * width); // 宽度方向
var zVector = new Vector3D(0, 0, height); // 高度方向
// 使用Cuboid API渲染长方体
graphics.Cuboid(origin, xVector, yVector, zVector, true);
}
catch (Exception ex)
{
LogManager.Error($"[长方体渲染] 渲染长方体失败: {ex.Message}", ex);
}
}
/// <summary>
/// 渲染车辆通行空间(矩形通道)- 使用Cuboid API简化实现
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="vehicleSpace">车辆通行空间标记</param>
private void RenderVehicleSpace(Graphics graphics, VehicleSpaceMarker vehicleSpace)
{
var style = GetRenderStyle(RenderStyleName.VehicleSpace);
// 设置颜色和透明度
graphics.Color(vehicleSpace.Color, vehicleSpace.Alpha);
RenderCuboidMarker(
graphics,
vehicleSpace.StartPoint,
vehicleSpace.EndPoint,
vehicleSpace.Width,
vehicleSpace.Height
);
}
/// <summary>
/// 渲染长方体标记(指定长度)
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="startPoint">起点</param>
/// <param name="endPoint">终点</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="length">指定长度(覆盖默认计算值)</param>
private void RenderCuboidMarkerWithLength(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, double length)
{
try
{
// 计算方向向量
var direction = new Vector3D(
endPoint.X - startPoint.X,
endPoint.Y - startPoint.Y,
endPoint.Z - startPoint.Z
);
// 归一化方向向量
var directionLength = Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z);
if (directionLength < 0.001) // 避免零长度线段
return;
direction = new Vector3D(direction.X / directionLength, direction.Y / directionLength, direction.Z / directionLength);
// 计算垂直向量在XY平面内垂直于方向向量
var right = new Vector3D(-direction.Y, direction.X, 0);
// 如果方向向量垂直于XY平面则使用不同的right向量
if (Math.Abs(direction.Z) > 0.9)
{
right = new Vector3D(1, 0, 0);
}
// 归一化right向量
var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z);
if (rightLength > 0.001)
{
right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength);
}
// 计算长方体的边界框和向量
var halfWidth = width / 2.0;
// 计算立方体原点(起点左下角)
var origin = new Point3D(
startPoint.X - halfWidth * right.X,
startPoint.Y - halfWidth * right.Y,
startPoint.Z
);
// 定义立方体的三个轴向量
var xVector = new Vector3D(direction.X * length, direction.Y * length, direction.Z * length); // 使用指定长度
var yVector = new Vector3D(right.X * width, right.Y * width, right.Z * width); // 宽度方向
var zVector = new Vector3D(0, 0, height); // 高度方向
// 使用Cuboid API渲染长方体
graphics.Cuboid(origin, xVector, yVector, zVector, true);
}
catch (Exception ex)
{
LogManager.Error($"[长方体渲染] 渲染长方体失败: {ex.Message}", ex);
}
}
/// <summary>
/// 渲染带状连线(长方体)
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="lineMarker">连线标记</param>
private void RenderRibbonLineMarker(Graphics graphics, LineMarker lineMarker)
{
// 使用 LineMarker 的 Width 属性(如果没有设置则使用 Radius * 2
var width = lineMarker.Width > 0 ? lineMarker.Width : lineMarker.Radius * 2;
// 检查是否是圆弧段且有多段采样点
if (lineMarker.SegmentType == PathSegmentType.Arc &&
lineMarker.SampledPoints != null &&
lineMarker.SampledPoints.Count >= 2)
{
// 圆弧段:多段长方体拼接
// 为了避免外侧出现缝隙,需要根据外侧弧线长度调整每个长方体的长度(厚度)
// 计算外侧弧线长度 = (圆弧半径 + 宽度/2) * 偏转角
// 注意:宽度是长方体的总宽度,所以半径是 width/2
double outerArcRadius = width / 2.0; // 长方体的半径(宽度的一半)
double outerArcLength = 0;
double centerArcRadius = 0;
if (lineMarker.Trajectory != null)
{
centerArcRadius = lineMarker.Trajectory.ActualRadius;
outerArcLength = (centerArcRadius + outerArcRadius) * lineMarker.Trajectory.DeflectionAngle;
}
// 计算每个长方体在外侧应该覆盖的长度
int segmentCount = lineMarker.SampledPoints.Count - 1;
double outerSegmentLength = outerArcLength / segmentCount;
// 提前计算比例因子,避免在循环中重复计算
double lengthScaleFactor = (centerArcRadius + outerArcRadius) / centerArcRadius;
// 设置颜色和透明度
graphics.Color(lineMarker.Color, lineMarker.Opacity);
// 添加过渡切片(在第一个切片前)
// 从第一个切片反向画一个切片,填补外侧缝隙
// 计算第一个切片的方向(从 Ts 到 SampledPoints[1]跳过第一个点因为它就是Ts
var firstSliceDirection = new Vector3D(
lineMarker.SampledPoints[1].X - lineMarker.Trajectory.Ts.X,
lineMarker.SampledPoints[1].Y - lineMarker.Trajectory.Ts.Y,
lineMarker.SampledPoints[1].Z - lineMarker.Trajectory.Ts.Z
);
var firstSliceLength = Math.Sqrt(
firstSliceDirection.X * firstSliceDirection.X +
firstSliceDirection.Y * firstSliceDirection.Y +
firstSliceDirection.Z * firstSliceDirection.Z
);
firstSliceDirection = new Vector3D(
firstSliceDirection.X / firstSliceLength,
firstSliceDirection.Y / firstSliceLength,
firstSliceDirection.Z / firstSliceLength
);
// 反向切片:从 Ts 沿第一个切片的反方向延伸
// 使用完整的 outerSegmentLength 作为长度
var reverseSliceEndPoint = new Point3D(
lineMarker.Trajectory.Ts.X - firstSliceDirection.X * outerSegmentLength,
lineMarker.Trajectory.Ts.Y - firstSliceDirection.Y * outerSegmentLength,
lineMarker.Trajectory.Ts.Z - firstSliceDirection.Z * outerSegmentLength
);
RenderCuboidMarkerWithLength(
graphics,
reverseSliceEndPoint,
lineMarker.Trajectory.Ts,
width,
lineMarker.Height,
outerSegmentLength
);
for (int i = 0; i < lineMarker.SampledPoints.Count - 1; i++)
{
// 计算当前段的中心线长度
double centerSegmentLength = GeometryHelper.CalculatePointDistance(
lineMarker.SampledPoints[i],
lineMarker.SampledPoints[i + 1]
);
// 计算长方体应该使用的长度(基于外侧弧线)
double cuboidLength = centerSegmentLength * lengthScaleFactor;
// 渲染长方体,使用调整后的长度
RenderCuboidMarkerWithLength(
graphics,
lineMarker.SampledPoints[i],
lineMarker.SampledPoints[i + 1],
width,
lineMarker.Height,
cuboidLength
);
}
}
else
{
// 直线段:单个长方体
// 设置颜色和透明度
graphics.Color(lineMarker.Color, lineMarker.Opacity);
RenderCuboidMarker(
graphics,
lineMarker.StartPoint,
lineMarker.EndPoint,
width,
lineMarker.Height
);
}
}
/// <summary>
/// 渲染点标记
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="pointMarker">点标记</param>
private void RenderPointMarker(Graphics graphics, CircleMarker pointMarker)
{
switch (pointMarker.GridPointType)
{
case GridPointType.Rectangle:
// 渲染立方体
RenderCubeMarker(graphics, pointMarker);
break;
case GridPointType.Circle:
// 渲染圆
RenderCircleMarker(graphics, pointMarker);
break;
case GridPointType.Sphere:
default:
// 渲染球形(默认)
graphics.Sphere(pointMarker.Center, pointMarker.Radius);
break;
}
}
/// <summary>
/// 渲染立方体标记
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="pointMarker">点标记</param>
private void RenderCubeMarker(Graphics graphics, CircleMarker pointMarker)
{
// 计算正方形的边长(直径)
double sideLength = pointMarker.Radius * 2;
// 定义正方形的两个轴向量沿X、Y轴
var xVector = new Vector3D(sideLength, 0, 0);
var yVector = new Vector3D(0, sideLength, 0);
// 计算正方形原点(中心点减去各轴向量的一半),略微抬高以避免与模型重叠
var origin = new Point3D(
pointMarker.Center.X - pointMarker.Radius,
pointMarker.Center.Y - pointMarker.Radius,
pointMarker.Center.Z + 0.01
);
// 使用Rectangle API渲染正方形
graphics.Rectangle(origin, xVector, yVector, pointMarker.Filled);
}
/// <summary>
/// 渲染圆形标记
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="pointMarker">点标记</param>
private void RenderCircleMarker(Graphics graphics, CircleMarker pointMarker)
{
// 调整中心点位置,略微抬高以避免与模型重叠
Point3D adjustCenterPoint = new Point3D(
pointMarker.Center.X,
pointMarker.Center.Y,
pointMarker.Center.Z + 0.01
);
// 使用Circle API渲染圆形
graphics.Circle(adjustCenterPoint, pointMarker.Normal, pointMarker.Radius, true);
}
/// <summary>
/// 渲染方块标记(切点)- 使用圆锥体
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="marker">方块标记</param>
private void RenderSquareMarker(Graphics graphics, SquareMarker marker)
{
try
{
// 计算圆锥体半径(底部半径)
var radius = marker.Size / 2.0;
// 计算圆锥体高度
var height = marker.Height;
// 计算圆锥体起点和终点(底面放在通道上)
var startPoint = new Point3D(
marker.Center.X,
marker.Center.Y,
marker.Center.Z // 底面Z坐标 = 中心Z坐标
);
var endPoint = new Point3D(
marker.Center.X,
marker.Center.Y,
marker.Center.Z + height // 顶面Z坐标 = 中心Z坐标 + 高度
);
// 使用统一样式
graphics.Color(marker.Color, marker.Alpha);
// 使用Cone API渲染圆锥体底部半径 = radius顶部半径 = 0
graphics.Cone(startPoint, endPoint, radius, 0);
}
catch (Exception ex)
{
LogManager.Error($"[切点渲染] 渲染切点失败: {ex.Message}", ex);
}
}
/// <summary>
/// 渲染连线标记(支持直线段和圆弧段)
/// </summary>
/// <param name="graphics">图形上下文</param>
/// <param name="lineMarker">连线标记</param>
private void RenderLineMarker(Graphics graphics, LineMarker lineMarker)
{
if (_visualizationMode == PathVisualizationMode.VehicleSpace || _visualizationMode == PathVisualizationMode.RibbonLine)
{
// VehicleSpace 或 RibbonLine 模式:使用长方体渲染
RenderRibbonLineMarker(graphics, lineMarker);
}
else
{
// StandardLine 模式:使用圆柱体渲染
var style = GetRenderStyle(RenderStyleName.Line);
graphics.Color(lineMarker.Color, style.Alpha);
if (lineMarker.SegmentType == PathSegmentType.Arc &&
lineMarker.SampledPoints != null &&
lineMarker.SampledPoints.Count >= 2)
{
// 圆弧段:多段圆柱体
// 为了避免外侧出现缝隙,需要根据外侧弧线长度调整每个圆柱体的厚度(半径)
double cylinderRadius = lineMarker.Radius; // 默认使用原始半径
if (lineMarker.Trajectory != null)
{
// 计算外侧弧线长度 = (圆弧半径 + 连线半径) * 偏转角
double outerArcLength = (lineMarker.Trajectory.ActualRadius + lineMarker.Radius) * lineMarker.Trajectory.DeflectionAngle;
// 计算每个圆柱体在外侧应该覆盖的长度
int segmentCount = lineMarker.SampledPoints.Count - 1;
double outerSegmentLength = outerArcLength / segmentCount;
// 圆柱体的半径(厚度)应该等于外侧段长度的一半
// 这样圆柱体在外侧的覆盖长度 = 2 * radius = outerSegmentLength正好填满外侧弧线
cylinderRadius = outerSegmentLength / 2.0;
}
for (int i = 0; i < lineMarker.SampledPoints.Count - 1; i++)
{
graphics.Cylinder(lineMarker.SampledPoints[i], lineMarker.SampledPoints[i + 1], cylinderRadius);
}
}
else
{
// 直线段:单个圆柱体
graphics.Cylinder(lineMarker.StartPoint, lineMarker.EndPoint, lineMarker.Radius);
}
}
}
/// <summary>
/// 请求视图刷新(带防抖机制)
/// </summary>
private void RequestViewRefresh()
{
try
{
// 防抖机制最小间隔50ms避免过于频繁的Navisworks重绘
// 使用高精度Ticks避免毫秒精度不足的问题
long currentTicks = DateTime.Now.Ticks;
long elapsedTicks = currentTicks - _lastRefreshTicks;
long intervalMs = elapsedTicks / TimeSpan.TicksPerMillisecond;
if (intervalMs < 50)
{
LogManager.Debug($"[RequestViewRefresh] 防抖触发,忽略刷新请求 (间隔: {intervalMs:F1}ms)");
return;
}
_lastRefreshTicks = currentTicks;
if (Application.ActiveDocument?.ActiveView != null)
{
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
LogManager.Debug($"[RequestViewRefresh] 已请求视图刷新");
}
else
{
LogManager.Warning($"[RequestViewRefresh] 活动文档或视图为空,无法请求刷新");
}
}
catch (Exception ex)
{
LogManager.Error($"[视图刷新] 失败: {ex.Message}", ex);
}
}
/// <summary>
/// 找到预览点应该插入的最近线段
/// </summary>
/// <param name="previewPosition">预览点位置</param>
/// <param name="pathPoints">路径点列表</param>
/// <returns>最近线段的前后两个点如果找不到则返回null</returns>
private (PathPoint prevPoint, PathPoint nextPoint)? FindNearestLineSegment(Point3D previewPosition, List<PathPoint> pathPoints)
{
if (pathPoints == null || pathPoints.Count < 2)
{
return null;
}
// 按索引排序路径点
var sortedPoints = pathPoints.OrderBy(p => p.Index).ToList();
double minDistance = double.MaxValue;
(PathPoint prevPoint, PathPoint nextPoint)? nearestSegment = null;
// 遍历相邻的路径点对,找到距离预览点最近的线段
for (int i = 0; i < sortedPoints.Count - 1; i++)
{
var currentPoint = sortedPoints[i];
var nextPoint = sortedPoints[i + 1];
// 计算预览点到线段的距离
var distance = CalculatePointToLineSegmentDistance(previewPosition, currentPoint.Position, nextPoint.Position);
if (distance < minDistance)
{
minDistance = distance;
nearestSegment = (currentPoint, nextPoint);
}
}
return nearestSegment;
}
/// <summary>
/// 计算点到线段的距离
/// </summary>
/// <param name="point">目标点</param>
/// <param name="lineStart">线段起点</param>
/// <param name="lineEnd">线段终点</param>
/// <returns>点到线段的最短距离</returns>
private double CalculatePointToLineSegmentDistance(Point3D point, Point3D lineStart, Point3D lineEnd)
{
// 线段向量
var lineVector = new Point3D(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y, lineEnd.Z - lineStart.Z);
// 点到线段起点的向量
var pointVector = new Point3D(point.X - lineStart.X, point.Y - lineStart.Y, point.Z - lineStart.Z);
// 计算线段长度的平方
var lineLengthSquared = lineVector.X * lineVector.X + lineVector.Y * lineVector.Y + lineVector.Z * lineVector.Z;
if (lineLengthSquared == 0)
{
// 线段退化为点,返回点到点的距离
return CalculateDistance(point, lineStart);
}
// 计算投影参数t
var t = (pointVector.X * lineVector.X + pointVector.Y * lineVector.Y + pointVector.Z * lineVector.Z) / lineLengthSquared;
// 将t限制在[0,1]范围内
t = Math.Max(0, Math.Min(1, t));
// 计算线段上最近点
var closestPoint = new Point3D(
lineStart.X + t * lineVector.X,
lineStart.Y + t * lineVector.Y,
lineStart.Z + t * lineVector.Z
);
// 返回点到最近点的距离
return CalculateDistance(point, closestPoint);
}
#endregion
}
/// <summary>
/// 切点类型枚举
/// </summary>
public enum TangentPointType
{
/// <summary>
/// 进入切点Ts
/// </summary>
EntryTangent,
/// <summary>
/// 退出切点Te
/// </summary>
ExitTangent
}
/// <summary>
/// 方块标记数据结构(用于切点)
/// </summary>
public class SquareMarker
{
/// <summary>
/// 中心坐标
/// </summary>
public Point3D Center { get; set; }
/// <summary>
/// 方块面法向量
/// </summary>
public Vector3D Normal { get; set; }
/// <summary>
/// 方块边长
/// </summary>
public double Size { get; set; }
/// <summary>
/// 方块颜色
/// </summary>
public Color Color { get; set; }
/// <summary>
/// 不透明度 (1.0 = 完全不透明, 0.0 = 完全透明)
/// </summary>
public double Alpha { get; set; }
/// <summary>
/// 所属边ID
/// </summary>
public string EdgeId { get; set; }
/// <summary>
/// 切点类型
/// </summary>
public TangentPointType TangentType { get; set; }
/// <summary>
/// 路径方向(用于渲染垂直薄片)
/// </summary>
public Vector3D Direction { get; set; }
/// <summary>
/// 高度(用于圆柱体渲染)
/// </summary>
public double Height { get; set; }
public override string ToString()
{
return $"SquareMarker[类型={TangentType}, 中心=({Center.X:F2},{Center.Y:F2},{Center.Z:F2}), 边长={Size:F2}]";
}
}
/// <summary>
/// 圆形标记数据结构
/// </summary>
public class CircleMarker
{
/// <summary>
/// 圆心坐标
/// </summary>
public Point3D Center { get; set; }
/// <summary>
/// 圆面法向量
/// </summary>
public Vector3D Normal { get; set; }
/// <summary>
/// 圆形半径
/// </summary>
public double Radius { get; set; }
/// <summary>
/// 圆形颜色
/// </summary>
public Color Color { get; set; }
/// <summary>
/// 不透明度 (1.0 = 完全不透明, 0.0 = 完全透明)
/// </summary>
public double Alpha { get; set; }
/// <summary>
/// 是否填充
/// </summary>
public bool Filled { get; set; }
/// <summary>
/// 路径点类型
/// </summary>
public PathPointType PointType { get; set; }
/// <summary>
/// 网格点类型
/// </summary>
public GridPointType GridPointType { get; set; } = GridPointType.Sphere;
/// <summary>
/// 序号
/// </summary>
public int SequenceNumber { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedTime { get; set; }
/// <summary>
/// 是否可见
/// </summary>
public bool IsVisible { get; set; }
/// <summary>
/// 是否为不在路径上的控制点(半透明)
/// </summary>
public bool IsOffPath { get; set; } = false;
/// <summary>
/// 位置Center的别名用于预览点兼容性
/// </summary>
public Point3D Position
{
get { return Center; }
set { Center = value; }
}
/// <summary>
/// 关联的路径点对象
/// </summary>
public PathPoint PathPoint { get; set; }
public override string ToString()
{
return $"CircleMarker[序号={SequenceNumber}, 类型={PointType}, 中心=({Center.X:F2},{Center.Y:F2},{Center.Z:F2}), 半径={Radius:F2}]";
}
}
}