NavisworksTransport/doc/working/碰撞检测性能优化_20251208.md

199 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# 对碰撞检测的性能进行优化
## 问题描述
1. 当前性能瓶颈:
- 动画生成建立时间长,对于某大型模型的日志:
[2025-12-08 10:02:31.838] [INFO] [动画生成] 开始构建碰撞检测缓存
[2025-12-08 10:05:28.571] [INFO] 几何对象列表缓存构建完成,耗时: 176730ms
[2025-12-08 10:05:28.571] [INFO] - 缓存对象总数: 374425 个
[2025-12-08 10:05:28.750] [INFO] 通道对象缓存构建完成,耗时: 174ms
[2025-12-08 10:05:28.751] [INFO] - 可通行物流根对象: 2 个
[2025-12-08 10:05:28.751] [INFO] - 缓存总对象数: 5 个
[2025-12-08 10:05:28.751] [INFO] [动画生成] 碰撞检测缓存构建完成,耗时: 176911.7ms
[2025-12-08 10:05:28.774] [INFO] === 构建全局空间索引 ===
[2025-12-08 10:05:28.776] [INFO] [空间索引] 车辆宽度: 1.00米 → 格子大小: 3.28模型单位
[2025-12-08 10:05:28.781] [INFO] [空间索引] 开始构建全局空间索引
[2025-12-08 10:05:28.782] [INFO] [空间索引] 格子大小: 3.28 模型单位
[2025-12-08 10:05:28.807] [INFO] [空间索引] 从缓存获取 374422 个非通道几何对象(已过滤通道)
[2025-12-08 10:05:31.538] [INFO] [空间索引] 构建完成
[2025-12-08 10:05:31.538] [INFO] - 成功索引: 374422 个对象(通道已在缓存阶段过滤)
[2025-12-08 10:05:31.538] [INFO] - 失败: 0 个对象
[2025-12-08 10:05:31.538] [INFO] - 格子数量: 58986 个
[2025-12-08 10:05:31.540] [INFO] - 耗时: 2756 ms
[2025-12-08 10:05:31.782] [INFO] [动画生成] 动画生成完成,总耗时: 179942.9ms
- 如果碰撞检测点过多检测时间长。用户实际模型中有2000多个检测点耗时近300秒。
## 解决方案
- 优化碰撞检测算法
分组去重:代码现在会先将所有预计算的碰撞事件按 (移动物体, 被撞物体) 进行分组。
验证即止:对于每一对物体,按干涉深度排序。
逐帧验证:将物体移动到该帧的位置,进行精确 Clash Detective 检测。
智能跳过:一旦确认了该对物体发生了真实碰撞,就记录结果并立即停止对该对物体的后续检测。
统计信息:日志中现在会显示"确认碰撞 X 组,跳过 Y 个冗余检测点",方便评估优化效果。
---
## 缓存构建性能优化 (2025-12-08)
### 问题分析
初始性能测试显示缓存构建耗时过长:
- **几何对象缓存**: 177秒 (374,425个对象)
- **空间索引构建**: 111秒
- **总耗时**: ~288秒
### 优化过程
#### 第一阶段:使用 Search API 替代遍历
**问题根因**: `RootItemDescendantsAndSelf.Where(item => item.HasGeometry)` 遍历整个模型树效率低下
**解决方案**: 使用 Navisworks Search API
```csharp
var search = new Search();
search.SearchConditions.Add(
SearchCondition.HasCategoryByName(PropertyCategoryNames.Geometry));
search.Selection.SelectAll();
search.Locations = SearchLocations.DescendantsAndSelf;
var allItems = search.FindAll(Application.ActiveDocument, false);
```
**结果**: 177秒 → 113秒 (36%提升)
#### 第二阶段:消除 ToList() 转换瓶颈
**问题根因**: `allItems.ToList()` 强制枚举并分配内存给374k个对象
**解决方案**:
1. 将缓存类型从 `List<ModelItem>` 改为 `ModelItemCollection`
2. 直接存储 Search API 返回的 `ModelItemCollection`
3. 更新 getter 方法返回 `ModelItemCollection` 而非创建 List 副本
**关键代码变更**:
```csharp
// 之前
private static List<ModelItem> _allGeometryItemsCache = null;
_allGeometryItemsCache = allItems.ToList(); // 耗时100+秒
// 之后
private static ModelItemCollection _allGeometryItemsCache = null;
_allGeometryItemsCache = search.FindAll(Application.ActiveDocument, false); // <1秒
```
**结果**: 113秒 → 0.9秒 (**129倍提升!**)
#### 第三阶段:优化空间索引构建
**问题根因**: `GetNonChannelGeometryItemsCache()` 内部调用 `.Where().ToList()` 预过滤通道对象
**解决方案**:
1. 直接迭代 `ModelItemCollection`
2. 在循环中使用 `HashSet.Contains()` 过滤通道对象
3. 避免创建中间 List
**关键代码变更**:
```csharp
// 之前
var nonChannelItems = ClashIntegration.GetNonChannelGeometryItemsCache(); // ToList()
foreach (var item in nonChannelItems) { ... }
// 之后
var allGeometryItems = ClashIntegration.GetAllGeometryItemsCache();
var channelObjectsCache = ClashIntegration.GetChannelObjectsCache();
foreach (var item in allGeometryItems) {
if (channelObjectsCache != null && channelObjectsCache.Contains(item)) {
skippedChannelCount++;
continue;
}
// 索引对象...
}
```
**结果**: 111秒 → 99秒 (11%提升)
#### 第四阶段:缓存空间索引
**问题根因**: 空间索引与移动物体无关,但每次生成动画都会重建
**解决方案**:
1.`PathAnimationManager.cs` 中检查空间索引是否已初始化
2. 只在第一次时构建,后续复用缓存的空间索引
**关键代码变更**:
```csharp
// 之前
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
// 之后
if (!spatialIndexManager.IsInitialized)
{
LogManager.Info("[空间索引] 空间索引未初始化,开始构建...");
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
}
else
{
LogManager.Info("[空间索引] 使用已缓存的空间索引");
}
```
**结果**: 后续动画生成从 ~100秒 → **0.3秒** (333倍提升!)
### 最终性能对比(实测数据)
| 场景 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| **首次动画生成** | ~288秒 | **~120秒** | **2.4倍** |
| **后续动画生成** | ~180秒 | **0.3秒** | **600倍** 🚀 |
**说明**:
- 首次生成包含:几何缓存构建(0.9s) + 过滤缓存构建(~100s) + 空间索引构建(~2.7s) + 动画预计算(~16s)
- 后续生成仅需:动画预计算(~0.3s),所有缓存直接复用
### 剩余瓶颈分析
首次动画生成的120秒主要消耗在
1. **过滤缓存构建** (~100秒): `ToList()` 过滤374k对象
- 这是一次性成本,后续复用
- 无法进一步优化(需要遍历所有对象)
2. **空间索引构建** (~2.7秒): 调用 `BoundingBox()`
- 这是一次性成本,后续复用
- 已经是最优方案
3. **动画预计算** (~16秒): 碰撞检测计算
- 每次动画生成都需要(与路径和移动物体相关)
- 可能的优化:减少检测精度或帧数
**结论**: 首次120秒是合理的一次性成本。后续0.3秒的性能已经达到最优。
### 关键经验教训
1. **避免不必要的集合转换**: `ToList()` 在大数据集上代价极高
2. **使用原生 API 数据结构**: `ModelItemCollection``List<ModelItem>` 更高效
3. **智能缓存策略**: 区分一次性成本和重复成本
- 过滤后的列表应该缓存(避免重复 `ToList()`
- 空间索引应该缓存(与移动物体无关)
4. **识别真正的瓶颈**: 使用日志和计时器定位性能热点
5. **理解缓存的适用范围**:
- 几何缓存:全局,模型不变就不变
- 过滤缓存:全局,通道对象不变就不变
- 空间索引:全局,场景对象位置不变就不变
- 动画预计算:局部,每次路径/移动物体变化都要重算
### 修改的文件
- `ClashDetectiveIntegration.cs`:
- 修改缓存类型为 `ModelItemCollection`
- 优化 `BuildAllGeometryItemsCache()` 使用 Search API
- 添加 `GetChannelObjectsCache()` 方法
- `SpatialIndexManager.cs`:
- 优化索引构建逻辑,在迭代中过滤而非预过滤
- 改进日志输出,显示跳过的通道对象数量