7.1 KiB
对碰撞检测的性能进行优化
问题描述
- 当前性能瓶颈:
- 动画生成建立时间长,对于某大型模型的日志: [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
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个对象
解决方案:
- 将缓存类型从
List<ModelItem>改为ModelItemCollection - 直接存储 Search API 返回的
ModelItemCollection - 更新 getter 方法返回
ModelItemCollection而非创建 List 副本
关键代码变更:
// 之前
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() 预过滤通道对象
解决方案:
- 直接迭代
ModelItemCollection - 在循环中使用
HashSet.Contains()过滤通道对象 - 避免创建中间 List
关键代码变更:
// 之前
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%提升)
第四阶段:缓存空间索引
问题根因: 空间索引与移动物体无关,但每次生成动画都会重建
解决方案:
- 在
PathAnimationManager.cs中检查空间索引是否已初始化 - 只在第一次时构建,后续复用缓存的空间索引
关键代码变更:
// 之前
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),所有缓存直接复用
剩余瓶颈分析
空间索引构建的99秒主要消耗在:
foreach (var item in allGeometryItems) { // 374,425个对象
var bbox = item.BoundingBox(); // ← 每次调用 ~0.26ms
// ...
}
根本原因: Navisworks API 的 BoundingBox() 方法本身较慢,且不支持多线程并行调用
可能的进一步优化方向:
- 减少需要索引的对象数量(预过滤)
- 延迟索引(仅索引动画路径附近的对象)
- 接受当前性能(对于37万+对象,100秒是合理的)
关键经验教训
- 避免不必要的集合转换:
ToList()在大数据集上代价极高 - 使用原生 API 数据结构:
ModelItemCollection比List<ModelItem>更高效 - 延迟过滤优于预过滤: 在迭代中过滤比预先创建过滤后的 List 更快
- 识别真正的瓶颈: 使用日志和计时器定位性能热点
- API 限制是硬约束: Navisworks API 调用速度无法通过代码优化突破
修改的文件
ClashDetectiveIntegration.cs:- 修改缓存类型为
ModelItemCollection - 优化
BuildAllGeometryItemsCache()使用 Search API - 添加
GetChannelObjectsCache()方法
- 修改缓存类型为
SpatialIndexManager.cs:- 优化索引构建逻辑,在迭代中过滤而非预过滤
- 改进日志输出,显示跳过的通道对象数量