400 lines
11 KiB
Markdown
400 lines
11 KiB
Markdown
# 剖面盒优化碰撞检测方案
|
||
|
||
## 概述
|
||
|
||
通过 Navisworks 剖面盒功能,只处理路径周围的对象,大幅提升大型模型碰撞检测性能。
|
||
|
||
## 预期性能提升
|
||
|
||
| 模型规模 | 优化前 | 优化后 | 提升倍数 |
|
||
|---------|--------|--------|---------|
|
||
| 10层建筑(全模型) | 80秒 | ~1秒 | **80x** |
|
||
| 单层检测 | 80秒 | ~0.5秒 | **160x** |
|
||
|
||
## 核心思路
|
||
|
||
1. **路径范围分析** → 计算路径的3D包围盒
|
||
2. **设置剖面盒** → 只保留路径周围一定范围内的模型
|
||
3. **裁剪缓存构建** → 遍历模型时快速跳过剖面外对象
|
||
4. **ClashDetective检测** → 在裁剪后的空间内执行碰撞检测
|
||
|
||
---
|
||
|
||
## 技术实现
|
||
|
||
### 1. 剖面盒辅助类 (`src/Utils/SectionClipHelper.cs`)
|
||
|
||
提供剖面盒设置、查询、测试等基础功能。
|
||
|
||
#### 关键实现:JSON 方式设置剖面盒
|
||
|
||
> ⚠️ **重要发现**:Navisworks 的 `ClipPlaneSet` 对象在用户手工打开剖面功能后会变为**只读**,直接修改属性会导致 `NotSupportedException: Object is Read-Only` 错误。
|
||
>
|
||
> **解决方案**:使用 `View.TrySetClippingPlanes(string json)` 方法,通过 JSON 字符串设置剖面盒配置。
|
||
|
||
```csharp
|
||
/// <summary>
|
||
/// 构建 ClipPlaneSet 的 JSON 字符串
|
||
/// </summary>
|
||
private static string BuildClipPlaneSetJson(BoundingBox3D box, bool enabled)
|
||
{
|
||
// Navisworks ClipPlaneSet JSON 格式:
|
||
// {
|
||
// "Type": "ClipPlaneSet",
|
||
// "Version": 1,
|
||
// "OrientedBox": {
|
||
// "Type": "OrientedBox3D",
|
||
// "Version": 1,
|
||
// "Box": [[minX, minY, minZ], [maxX, maxY, maxZ]],
|
||
// "Rotation": [0, 0, 0]
|
||
// },
|
||
// "Enabled": true/false
|
||
// }
|
||
|
||
return string.Format(
|
||
"{{\"Type\":\"ClipPlaneSet\",\"Version\":1," +
|
||
"\"OrientedBox\":{{\"Type\":\"OrientedBox3D\",\"Version\":1," +
|
||
"\"Box\":[[{0},{1},{2}],[{3},{4},{5}]]," +
|
||
"\"Rotation\":[0,0,0]}},\"Enabled\":{6}}}",
|
||
box.Min.X.ToString("G17"),
|
||
box.Min.Y.ToString("G17"),
|
||
box.Min.Z.ToString("G17"),
|
||
box.Max.X.ToString("G17"),
|
||
box.Max.Y.ToString("G17"),
|
||
box.Max.Z.ToString("G17"),
|
||
enabled.ToString().ToLowerInvariant());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用剖面盒到视口
|
||
/// </summary>
|
||
private static void ApplyClipBox(BoundingBox3D box)
|
||
{
|
||
string json = BuildClipPlaneSetJson(box, true);
|
||
var view = Application.ActiveDocument.ActiveView;
|
||
|
||
// 使用 TrySetClippingPlanes 避免异常
|
||
bool success = view.TrySetClippingPlanes(json);
|
||
|
||
if (!success)
|
||
{
|
||
// 备选方案:使用 SetClippingPlanes(可能抛出异常)
|
||
view.SetClippingPlanes(json);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### API 方法列表
|
||
|
||
| 方法 | 功能 | 参数 |
|
||
|------|------|------|
|
||
| `SetClipBoxByPath` | 根据路径点列表设置剖面盒 | `pathPoints`, `marginMeters`, `heightMarginMeters` |
|
||
| `SetClipBoxByPoint` | 根据中心点设置剖面盒 | `centerPoint`, `rangeMeters`, `heightRangeMeters` |
|
||
| `SetClipBoxByFloor` | 根据楼层设置剖面盒 | `floorItem`, `marginMeters` |
|
||
| `ClearClipBox` | 清除剖面盒 | - |
|
||
| `IsPointInClipBox` | 测试点是否在剖面盒内 | `point` |
|
||
| `IntersectsClipBox` | 测试包围盒是否与剖面盒相交 | `box` |
|
||
| `CountObjectsInClipBox` | 统计剖面盒内/外对象数量 | - |
|
||
|
||
---
|
||
|
||
### 2. 几何缓存优化 (`Core/Collision/ClashDetectiveIntegration.cs`)
|
||
|
||
修改 `BuildNonHidddenGeometryItemsCache` 方法,支持剖面过滤:
|
||
|
||
```csharp
|
||
private void BuildNonHidddenGeometryItemsCache()
|
||
{
|
||
// ... 原有代码 ...
|
||
|
||
// 获取当前剖面盒(如果启用)
|
||
BoundingBox3D clipBox;
|
||
bool hasClipBox = SectionClipHelper.TryGetCurrentClipBox(out clipBox);
|
||
|
||
// 遍历模型树
|
||
var stack = new Stack<ModelItem>();
|
||
foreach (var model in _document.Models)
|
||
{
|
||
stack.Push(model.RootItem);
|
||
}
|
||
|
||
while (stack.Count > 0)
|
||
{
|
||
var item = stack.Pop();
|
||
|
||
// 剖面盒过滤:如果对象在剖面盒外,整棵子树跳过
|
||
if (hasClipBox)
|
||
{
|
||
BoundingBox3D itemBox = item.BoundingBox();
|
||
if (!clipBox.Intersects(itemBox))
|
||
{
|
||
continue; // 剪枝:跳过此节点及其所有子节点
|
||
}
|
||
}
|
||
|
||
// ... 原有缓存逻辑 ...
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 通道缓存优化
|
||
|
||
修改 `BuildChannelObjectsCache` 方法:
|
||
|
||
```csharp
|
||
private void BuildChannelObjectsCache()
|
||
{
|
||
// ... 原有代码 ...
|
||
|
||
// 获取当前剖面盒
|
||
BoundingBox3D clipBox;
|
||
bool hasClipBox = SectionClipHelper.TryGetCurrentClipBox(out clipBox);
|
||
|
||
foreach (var channel in channels)
|
||
{
|
||
// 剖面盒过滤
|
||
if (hasClipBox)
|
||
{
|
||
BoundingBox3D channelBox = channel.BoundingBox();
|
||
if (!clipBox.Intersects(channelBox))
|
||
{
|
||
continue; // 跳过剖面外的通道
|
||
}
|
||
}
|
||
|
||
// ... 原有通道缓存逻辑 ...
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 空间索引优化(可选)
|
||
|
||
`SpatialIndexManager` 利用剖面盒二次过滤:
|
||
|
||
```csharp
|
||
public IEnumerable<ModelItem> QueryVisibleItems()
|
||
{
|
||
// 先查询空间索引
|
||
var candidates = _spatialIndex.Query(_viewFrustum);
|
||
|
||
// 再用剖面盒过滤
|
||
return candidates.Where(item =>
|
||
!SectionClipHelper.IsClipBoxEnabled ||
|
||
SectionClipHelper.IntersectsClipBox(item.BoundingBox()));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## API 使用说明
|
||
|
||
### 设置剖面盒
|
||
|
||
```csharp
|
||
// 根据路径设置(推荐用于路径规划场景)
|
||
SectionClipHelper.SetClipBoxByPath(
|
||
pathPoints,
|
||
marginMeters: 3.0, // 路径周围水平边距
|
||
heightMarginMeters: 2.0 // 上下高度边距
|
||
);
|
||
|
||
// 根据点设置(推荐用于吊装路径单点场景)
|
||
SectionClipHelper.SetClipBoxByPoint(
|
||
centerPoint,
|
||
rangeMeters: 10.0, // 水平范围
|
||
heightRangeMeters: 5.0 // 高度范围
|
||
);
|
||
|
||
// 根据楼层设置(推荐用于单层检测)
|
||
SectionClipHelper.SetClipBoxByFloor(
|
||
floorItem,
|
||
marginMeters: 1.0 // 楼层周围边距
|
||
);
|
||
|
||
// 清除剖面盒(恢复全模型显示)
|
||
SectionClipHelper.ClearClipBox();
|
||
```
|
||
|
||
### 测试点/包围盒
|
||
|
||
```csharp
|
||
// 测试点是否在剖面内
|
||
bool inside = SectionClipHelper.IsPointInClipBox(point);
|
||
|
||
// 测试包围盒是否与剖面相交
|
||
bool intersects = SectionClipHelper.IntersectsClipBox(box);
|
||
|
||
// 获取当前剖面盒
|
||
BoundingBox3D clipBox;
|
||
if (SectionClipHelper.TryGetCurrentClipBox(out clipBox))
|
||
{
|
||
// 使用 clipBox 进行自定义过滤
|
||
}
|
||
```
|
||
|
||
### 统计对象数量(调试用)
|
||
|
||
```csharp
|
||
// 输出剖面盒内/外对象数量到日志
|
||
SectionClipHelper.CountObjectsInClipBox(
|
||
out int total,
|
||
out int inside,
|
||
out int outside);
|
||
|
||
LogManager.Info($"过滤率: {(outside * 100.0 / total):F1}%");
|
||
```
|
||
|
||
---
|
||
|
||
## 测试结果
|
||
|
||
### 基础功能测试(✅ 已完成)
|
||
|
||
**测试场景**:10层建筑模型,在模型中心设置 10m x 10m x 5m 剖面盒
|
||
|
||
| 指标 | 数值 | 说明 |
|
||
|------|------|------|
|
||
| 总对象数 | 848 | 模型总对象 |
|
||
| 剖面盒内 | 10 | 需要检测的对象 |
|
||
| 剖面盒外 | 838 | 可跳过的对象 |
|
||
| **过滤率** | **98.8%** | 性能提升关键指标 |
|
||
|
||
**点检测测试**:
|
||
- 中心点 (-108.50, -2.94, 27.24): `True` ✅
|
||
- 远点 (-8.50, 97.06, 127.24): `False` ✅
|
||
|
||
### 性能测试计划
|
||
|
||
| 测试项 | 状态 | 预期结果 |
|
||
|--------|------|----------|
|
||
| 10层建筑全模型 | ⏳ 待测 | 80秒 → ~1秒 (80x) |
|
||
| 单层检测 | ⏳ 待测 | 80秒 → ~0.5秒 (160x) |
|
||
| 内存占用分析 | ⏳ 待测 | 无显著增加 |
|
||
|
||
---
|
||
|
||
## 已知问题与解决方案
|
||
|
||
### 问题1:Object is Read-Only 错误
|
||
|
||
**现象**:用户手工打开剖面功能后,调用 `clipPlanes.Box = box` 抛出异常。
|
||
|
||
**原因**:Navisworks 的 `ClipPlaneSet` 对象在手工操作后变为只读。
|
||
|
||
**解决**:使用 JSON 字符串方式设置剖面盒(见上文技术实现)。
|
||
|
||
### 问题2:路径跨越多层时优化效果降低
|
||
|
||
**现象**:垂直跨越多个楼层的长路径,剖面盒范围大,过滤率下降。
|
||
|
||
**建议**:
|
||
1. 分段设置剖面盒(每层单独检测)
|
||
2. 使用多个剖面盒(Navisworks 支持最多 6 个剖面)
|
||
3. 动态调整剖面盒大小
|
||
|
||
### 问题3:坐标系单位转换
|
||
|
||
**注意**:`SectionClipHelper` 的输入参数使用**米**为单位,内部自动转换为模型单位。
|
||
|
||
```csharp
|
||
// 正确:使用米作为输入
|
||
SectionClipHelper.SetClipBoxByPoint(centerPoint, rangeMeters: 10.0);
|
||
|
||
// 内部转换逻辑
|
||
double factor = UnitsConverter.GetMetersToUnitsConversionFactor(document.Units);
|
||
double rangeInModelUnits = rangeMeters * factor;
|
||
```
|
||
|
||
---
|
||
|
||
## 文件变更清单
|
||
|
||
### 新增文件
|
||
|
||
| 文件路径 | 说明 |
|
||
|----------|------|
|
||
| `src/Utils/SectionClipHelper.cs` | 剖面盒辅助类(JSON 方式设置) |
|
||
|
||
### 修改文件
|
||
|
||
| 文件路径 | 修改内容 |
|
||
|----------|----------|
|
||
| `src/Core/Collision/ClashDetectiveIntegration.cs` | 添加剖面过滤支持到缓存构建 |
|
||
| `src/UI/WPF/Views/SystemManagementView.xaml` | 添加剖面盒测试按钮 |
|
||
| `src/UI/WPF/ViewModels/SystemManagementViewModel.cs` | 添加 `ExecuteTestSectionClip()` 方法 |
|
||
|
||
---
|
||
|
||
## 集成到碰撞检测流程
|
||
|
||
### 步骤1:路径规划后自动设置剖面盒
|
||
|
||
```csharp
|
||
// 在 PathPlanningManager 中
|
||
public void GeneratePath(PathRequest request)
|
||
{
|
||
// ... 路径计算 ...
|
||
|
||
// 自动设置剖面盒
|
||
if (pathPoints != null && pathPoints.Count > 0)
|
||
{
|
||
SectionClipHelper.SetClipBoxByPath(
|
||
pathPoints,
|
||
marginMeters: 3.0, // 可配置
|
||
heightMarginMeters: 2.0);
|
||
}
|
||
|
||
// ... 后续处理 ...
|
||
}
|
||
```
|
||
|
||
### 步骤2:碰撞检测前应用过滤
|
||
|
||
```csharp
|
||
// 在 ClashDetectiveIntegration 中
|
||
public async Task<CollisionResult> PerformCollisionDetectionAsync()
|
||
{
|
||
// 1. 构建缓存时自动应用剖面过滤
|
||
BuildNonHidddenGeometryItemsCache();
|
||
|
||
// 2. 执行检测(只处理剖面内对象)
|
||
var result = await RunClashDetectionAsync();
|
||
|
||
// 3. 可选:检测完成后清除剖面盒
|
||
// SectionClipHelper.ClearClipBox();
|
||
|
||
return result;
|
||
}
|
||
```
|
||
|
||
### 步骤3:UI 交互控制
|
||
|
||
```xml
|
||
<!-- 在路径编辑界面添加剖面盒控制 -->
|
||
<CheckBox Content="启用剖面盒优化" IsChecked="{Binding EnableSectionClip}" />
|
||
<Button Content="设置剖面盒到当前路径" Command="{Binding SetClipBoxCommand}" />
|
||
<Button Content="清除剖面盒" Command="{Binding ClearClipBoxCommand}" />
|
||
```
|
||
|
||
---
|
||
|
||
## 后续优化方向
|
||
|
||
1. **多剖面盒支持**:利用 Navisworks 支持 6 个剖面的特性,处理复杂路径
|
||
2. **动态剖面盒**:动画播放时动态调整剖面盒位置
|
||
3. **智能边距计算**:根据车辆尺寸自动计算合适的边距
|
||
4. **剖面盒可视化**:在 3D 视图中显示剖面盒边界
|
||
|
||
---
|
||
|
||
## 参考文档
|
||
|
||
- Navisworks API: `doc/navisworks_api/NET/documentation/NetAPIHtml/html/T_Autodesk_Navisworks_Api_ClipPlaneSet.htm`
|
||
- `View.SetClippingPlanes` - JSON 方式设置剖面盒
|
||
- `View.TrySetClippingPlanes` - 安全设置剖面盒(返回 bool)
|
||
- `ClipPlaneSet` 属性(只读场景下慎用)
|