NavisworksTransport/src/PathPlanning/VoxelGridVisualizer.cs
tian 805814616a feat(voxel): 阶段1.5 - 体素网格可视化验证
实现内容:
- 创建 VoxelGridVisualizer 类,支持多种可视化模式
- VisualizeAsPoints(): 完整网格可视化(支持采样率)
- VisualizeSlice(): 单层切片可视化
- VisualizeBoundary(): 障碍物边界可视化
- ConvertToPathRoute(): 转换为 PathRoute 以便集成到现有渲染系统
- QuickVisualizationTest(): 快速测试套件
- GenerateVisualizationReport(): 文本报告生成

设计策略:
- 复用现有 PathPointRenderPlugin,而非创建新的 RenderPlugin
- 将体素转换为 PathPoint 对象进行渲染
- 使用小球体表示体素,不同颜色区分类型

完成情况:
-  编译通过,无错误
-  阶段 1 全部 5 个任务完成 (100%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 13:38:57 +08:00

389 lines
15 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 Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using static NavisworksTransport.CategoryAttributeManager;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 体素网格可视化器 - 在 Navisworks 3D 视图中显示体素网格
/// 阶段 1.5:体素可视化验证
/// </summary>
public class VoxelGridVisualizer
{
/// <summary>
/// 可视化体素网格(简化版本 - 使用小球表示体素)
/// </summary>
/// <param name="voxelGrid">体素网格</param>
/// <param name="samplingRate">采样率1=全部显示2=每隔一个显示,以此类推)</param>
/// <param name="showPassableOnly">是否只显示可通行体素</param>
/// <returns>生成的路径点列表可用于PathPointRenderPlugin渲染</returns>
public static List<PathPoint> VisualizeAsPoints(
VoxelGrid voxelGrid,
int samplingRate = 2,
bool showPassableOnly = false)
{
LogManager.Info("=== 开始体素网格可视化 ===");
LogManager.Info($"网格尺寸: {voxelGrid.SizeX} × {voxelGrid.SizeY} × {voxelGrid.SizeZ}");
LogManager.Info($"采样率: {samplingRate} (每{samplingRate}个体素显示1个)");
LogManager.Info($"仅显示可通行: {showPassableOnly}");
var points = new List<PathPoint>();
int totalVoxels = 0;
int visualizedVoxels = 0;
try
{
for (int x = 0; x < voxelGrid.SizeX; x += samplingRate)
{
for (int y = 0; y < voxelGrid.SizeY; y += samplingRate)
{
for (int z = 0; z < voxelGrid.SizeZ; z += samplingRate)
{
totalVoxels++;
var cell = voxelGrid.GetCell(x, y, z);
if (cell == null) continue;
// 过滤:如果只显示可通行体素,跳过不可通行的
if (showPassableOnly && !cell.IsPassable)
continue;
// 获取体素中心点的世界坐标
var worldPos = voxelGrid.VoxelToWorldCenter(x, y, z);
// 创建路径点表示体素
var point = new PathPoint
{
Index = visualizedVoxels,
Position = worldPos,
Name = GetVoxelDisplayName(cell, x, y, z),
Type = PathPointType.WayPoint,
Notes = $"VoxelType:{cell.Type}"
};
points.Add(point);
visualizedVoxels++;
}
}
}
LogManager.Info($"总体素数: {voxelGrid.TotalVoxels:N0}");
LogManager.Info($"采样后体素数: {totalVoxels:N0}");
LogManager.Info($"可视化体素数: {visualizedVoxels:N0}");
LogManager.Info("=== 体素网格可视化完成 ===");
}
catch (Exception ex)
{
LogManager.Error($"体素网格可视化失败: {ex.Message}");
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
}
return points;
}
/// <summary>
/// 可视化体素网格的切片指定Z高度
/// </summary>
/// <param name="voxelGrid">体素网格</param>
/// <param name="zIndex">Z方向索引高度层</param>
/// <returns>生成的路径点列表</returns>
public static List<PathPoint> VisualizeSlice(VoxelGrid voxelGrid, int zIndex)
{
LogManager.Info($"=== 可视化体素网格切片 (Z={zIndex}) ===");
var points = new List<PathPoint>();
int visualizedCount = 0;
try
{
if (!voxelGrid.IsValidIndex(0, 0, zIndex))
{
LogManager.Warning($"无效的 Z 索引: {zIndex},有效范围: 0 ~ {voxelGrid.SizeZ - 1}");
return points;
}
for (int x = 0; x < voxelGrid.SizeX; x++)
{
for (int y = 0; y < voxelGrid.SizeY; y++)
{
var cell = voxelGrid.GetCell(x, y, zIndex);
if (cell == null) continue;
// 获取体素中心点的世界坐标
var worldPos = voxelGrid.VoxelToWorldCenter(x, y, zIndex);
// 创建路径点表示体素
var point = new PathPoint
{
Index = visualizedCount,
Position = worldPos,
Name = GetVoxelDisplayName(cell, x, y, zIndex),
Type = PathPointType.WayPoint,
Notes = $"VoxelType:{cell.Type}"
};
points.Add(point);
visualizedCount++;
}
}
LogManager.Info($"切片 Z={zIndex} 可视化体素数: {visualizedCount:N0}");
LogManager.Info("=== 切片可视化完成 ===");
}
catch (Exception ex)
{
LogManager.Error($"切片可视化失败: {ex.Message}");
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
}
return points;
}
/// <summary>
/// 可视化体素网格的边界(只显示障碍物表面)
/// </summary>
/// <param name="voxelGrid">体素网格</param>
/// <returns>生成的路径点列表</returns>
public static List<PathPoint> VisualizeBoundary(VoxelGrid voxelGrid)
{
LogManager.Info("=== 可视化体素网格边界(障碍物表面) ===");
var points = new List<PathPoint>();
int visualizedCount = 0;
try
{
for (int x = 0; x < voxelGrid.SizeX; x++)
{
for (int y = 0; y < voxelGrid.SizeY; y++)
{
for (int z = 0; z < voxelGrid.SizeZ; z++)
{
var cell = voxelGrid.GetCell(x, y, z);
if (cell == null || cell.IsPassable) continue;
// 检查是否是边界体素(至少有一个相邻体素是可通行的)
if (IsBoundaryVoxel(voxelGrid, x, y, z))
{
var worldPos = voxelGrid.VoxelToWorldCenter(x, y, z);
var point = new PathPoint
{
Index = visualizedCount,
Position = worldPos,
Name = GetVoxelDisplayName(cell, x, y, z),
Type = PathPointType.WayPoint,
Notes = $"VoxelType:{cell.Type},Boundary:true"
};
points.Add(point);
visualizedCount++;
}
}
}
}
LogManager.Info($"边界体素数: {visualizedCount:N0}");
LogManager.Info("=== 边界可视化完成 ===");
}
catch (Exception ex)
{
LogManager.Error($"边界可视化失败: {ex.Message}");
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
}
return points;
}
/// <summary>
/// 判断体素是否为边界体素
/// </summary>
private static bool IsBoundaryVoxel(VoxelGrid voxelGrid, int x, int y, int z)
{
// 获取 6 邻域
var neighbors = voxelGrid.GetNeighbors6(x, y, z);
// 如果至少有一个邻居是可通行的,则当前体素是边界
foreach (var (nx, ny, nz) in neighbors)
{
if (voxelGrid.IsPassable(nx, ny, nz))
{
return true;
}
}
return false;
}
/// <summary>
/// 获取体素显示名称
/// </summary>
private static string GetVoxelDisplayName(VoxelCell cell, int x, int y, int z)
{
string typeStr = cell.IsPassable ? "通行" : "障碍";
return $"体素({x},{y},{z})-{typeStr}-{cell.Type}";
}
/// <summary>
/// 将体素列表转换为 PathRoute以便使用 PathPointRenderPlugin 渲染
/// </summary>
/// <param name="voxelPoints">体素点列表</param>
/// <param name="routeName">路径名称</param>
/// <returns>PathRoute 对象</returns>
public static PathRoute ConvertToPathRoute(List<PathPoint> voxelPoints, string routeName = "体素网格可视化")
{
var route = new PathRoute
{
Id = $"voxel_visualization_{Guid.NewGuid():N}",
Name = routeName,
Points = voxelPoints,
IsComplete = true,
CreatedTime = DateTime.Now
};
return route;
}
/// <summary>
/// 快速可视化测试
/// </summary>
/// <param name="voxelGrid">体素网格</param>
public static void QuickVisualizationTest(VoxelGrid voxelGrid)
{
LogManager.Info("=== 开始快速可视化测试 ===");
try
{
// 统计信息
var (total, passable, obstacle) = voxelGrid.GetStatistics();
LogManager.Info($"体素统计: 总数={total:N0}, 可通行={passable:N0}, 障碍={obstacle:N0}");
// 测试1可视化底层切片Z=0
LogManager.Info("[测试 1] 可视化底层切片 (Z=0)");
var slicePoints = VisualizeSlice(voxelGrid, 0);
LogManager.Info($"底层切片点数: {slicePoints.Count}");
// 测试2可视化中间层切片Z=中间)
int midZ = voxelGrid.SizeZ / 2;
LogManager.Info($"[测试 2] 可视化中间层切片 (Z={midZ})");
var midSlicePoints = VisualizeSlice(voxelGrid, midZ);
LogManager.Info($"中间层切片点数: {midSlicePoints.Count}");
// 测试3可视化边界
LogManager.Info("[测试 3] 可视化边界(障碍物表面)");
var boundaryPoints = VisualizeBoundary(voxelGrid);
LogManager.Info($"边界点数: {boundaryPoints.Count}");
// 测试4采样可视化每隔2个体素显示1个
LogManager.Info("[测试 4] 采样可视化(采样率=2");
var sampledPoints = VisualizeAsPoints(voxelGrid, 2, false);
LogManager.Info($"采样点数: {sampledPoints.Count}");
LogManager.Info("=== 快速可视化测试完成 ===");
LogManager.Info("提示:使用 PathPointRenderPlugin 渲染这些点");
}
catch (Exception ex)
{
LogManager.Error($"快速可视化测试失败: {ex.Message}");
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
}
}
/// <summary>
/// 生成可视化报告(文本格式)
/// </summary>
/// <param name="voxelGrid">体素网格</param>
/// <returns>报告文本</returns>
public static string GenerateVisualizationReport(VoxelGrid voxelGrid)
{
var report = new System.Text.StringBuilder();
report.AppendLine("================================");
report.AppendLine(" 体素网格可视化报告");
report.AppendLine("================================");
report.AppendLine();
// 基本信息
report.AppendLine("【基本信息】");
report.AppendLine($"网格尺寸: {voxelGrid.SizeX} × {voxelGrid.SizeY} × {voxelGrid.SizeZ}");
report.AppendLine($"体素尺寸: {voxelGrid.VoxelSize:F4} 模型单位");
report.AppendLine($"总体素数: {voxelGrid.TotalVoxels:N0}");
report.AppendLine($"网格边界: {voxelGrid.Bounds.Min} ~ {voxelGrid.Bounds.Max}");
report.AppendLine();
// 统计信息
var (total, passable, obstacle) = voxelGrid.GetStatistics();
double passableRatio = (double)passable / total * 100.0;
double obstacleRatio = (double)obstacle / total * 100.0;
report.AppendLine("【统计信息】");
report.AppendLine($"总体素数: {total:N0}");
report.AppendLine($"可通行体素: {passable:N0} ({passableRatio:F1}%)");
report.AppendLine($"障碍物体素: {obstacle:N0} ({obstacleRatio:F1}%)");
report.AppendLine();
// 类型分布
report.AppendLine("【体素类型分布】");
var typeDistribution = AnalyzeTypeDistribution(voxelGrid);
foreach (var kvp in typeDistribution)
{
double ratio = (double)kvp.Value / total * 100.0;
report.AppendLine($" {kvp.Key}: {kvp.Value:N0} ({ratio:F1}%)");
}
report.AppendLine();
// 可视化建议
report.AppendLine("【可视化建议】");
if (total > 1000000)
{
report.AppendLine(" ⚠️ 体素数量很大,建议使用采样率 >= 5");
report.AppendLine(" 或者只可视化边界/切片");
}
else if (total > 100000)
{
report.AppendLine(" ⚠️ 体素数量较大,建议使用采样率 >= 3");
}
else
{
report.AppendLine(" ✅ 体素数量适中,可以使用采样率 1-2");
}
report.AppendLine();
report.AppendLine("================================");
return report.ToString();
}
/// <summary>
/// 分析体素类型分布
/// </summary>
private static Dictionary<LogisticsElementType, int> AnalyzeTypeDistribution(VoxelGrid voxelGrid)
{
var distribution = new Dictionary<LogisticsElementType, int>();
for (int x = 0; x < voxelGrid.SizeX; x++)
{
for (int y = 0; y < voxelGrid.SizeY; y++)
{
for (int z = 0; z < voxelGrid.SizeZ; z++)
{
var cell = voxelGrid.GetCell(x, y, z);
if (cell == null) continue;
if (!distribution.ContainsKey(cell.Type))
{
distribution[cell.Type] = 0;
}
distribution[cell.Type]++;
}
}
}
return distribution;
}
}
}