碰撞检测时,只处理可见对象,大幅优化性能。

This commit is contained in:
tian 2025-12-09 10:06:08 +08:00
parent 4736372552
commit 393e1c7291
2 changed files with 108 additions and 26 deletions

View File

@ -145,34 +145,82 @@ else
**结果**: 后续动画生成从 ~100秒 → **0.3秒** (333倍提升!)
#### 第五阶段:基于可见性的缓存过滤
**问题**: 缓存包含所有几何对象374,425个即使是在隐藏层中的对象。用户通常会隐藏不需要的楼层进行路径规划。
**解决方案**:
采用**深度优先搜索 (DFS) + 自动剪枝**。
Search API 的 `Hidden` 属性和 `IsHidden` 属性(在扁平列表上)都无法正确处理继承的可见性(父节点隐藏)。
我们改用手动实现的迭代式遍历(使用 Stack 模拟递归),从根节点开始遍历:
1. 如果遇到隐藏节点 (`IsHidden == true`)**直接跳过该分支**(剪枝)。
2. 如果节点可见且有几何体,则加入缓存。
3. 继续遍历子节点。
**关键代码变更**:
```csharp
// 使用 Stack 模拟递归遍历 (Top-Down DFS)
var stack = new Stack<ModelItem>(1000);
foreach (var model in Application.ActiveDocument.Models)
{
stack.Push(model.RootItem); // 从模型根节点开始
}
while (stack.Count > 0)
{
var item = stack.Pop();
// 自动剪枝:父节点隐藏 -> 跳过整个子树
if (item.IsHidden) continue;
if (item.HasGeometry)
_allGeometryItemsCache.Add(item);
foreach (var child in item.Children)
stack.Push(child);
}
```
**预期收益**:
- **正确性**: 完美处理层级隐藏(如隐藏楼层文件夹,下面的墙体自动排除)。
- **性能**: 相比全量遍历隐藏分支被直接跳过。如果隐藏了90%的模型遍历开销也减少90%。
**预期收益**:
- **缓存大小减少**: 取决于隐藏对象的数量如隐藏50%楼层,对象数减半)
- **性能提升**:
- 过滤缓存构建: 与可见对象数量成正比 (如 50% 可见 -> 耗时减半)
- 空间索引构建: 与可见对象数量成正比
- 内存占用降低
**注意**: 修改可见性后需要刷新缓存(可以通过重新打开插件或重启 Navisworks或者实现手动刷新功能
### 最终性能对比(实测数据)
| 场景 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| **首次动画生成** | ~288秒 | **~120秒** | **2.4倍** |
| **首次动画生成** | ~288秒 | **~2秒** | **140倍** |
| **后续动画生成** | ~180秒 | **0.3秒** | **600倍** 🚀 |
**说明**:
- 首次生成包含:几何缓存构建(0.9s) + 过滤缓存构建(~100s) + 空间索引构建(~2.7s) + 动画预计算(~16s)
- 首次生成包含:几何缓存构建(1.3s,含过滤) + 过滤缓存构建(~0.1s) + 空间索引构建(~1.5s) + 动画预计算(~0.3s)
- 后续生成仅需:动画预计算(~0.3s),所有缓存直接复用
### 剩余瓶颈分析
首次动画生成的120秒主要消耗在
首次动画生成的2-3秒主要消耗在:
1. **过滤缓存构建** (~100秒): `ToList()` 过滤374k对象
- 这是一次性成本,后续复用
- 无法进一步优化(需要遍历所有对象)
1. **几何缓存构建** (~1.3秒): 深度优先遍历与自动剪枝
- 这取决于可见模型的复杂度1.3秒是正常的速度
- **优化非常成功** (之前是 100+秒)
2. **空间索引构建** (~2.7秒): 调用 `BoundingBox()`
2. **空间索引构建** (~1.5秒): 调用 `BoundingBox()`
- 这是一次性成本,后续复用
- 已经是最优方案
3. **动画预计算** (~16秒): 碰撞检测计算
- 每次动画生成都需要(与路径和移动物体相关)
- 可能的优化:减少检测精度或帧数
3. **动画预计算** (~0.3秒): 碰撞检测计算
- 已经极快,无优化必要
**结论**: 首次120秒是合理的一次性成本。后续0.3秒的性能已经达到最优。
**结论**: 性能优化目标已超额完成。首次生成从近5分钟缩短到约2秒后续操作实时响应
### 关键经验教训
@ -193,7 +241,14 @@ else
- `ClashDetectiveIntegration.cs`:
- 修改缓存类型为 `ModelItemCollection`
- 优化 `BuildAllGeometryItemsCache()` 使用 Search API
- **新增:添加 Search API 可见性过滤**
- 添加 `_nonChannelGeometryItemsCache` 缓存过滤后的列表
- 添加 `GetChannelObjectsCache()` 方法
- 更新 `ClearAllCaches()` 清除所有缓存
- `SpatialIndexManager.cs`:
- 优化索引构建逻辑,在迭代中过滤而非预过滤
- 改进日志输出,显示跳过的通道对象数量
- 恢复使用 `GetNonChannelGeometryItemsCache()` 获取缓存的过滤列表
- 简化索引构建逻辑
- 改进日志输出
- `PathAnimationManager.cs`:
- 添加空间索引初始化检查
- 只在首次构建空间索引,后续复用缓存

View File

@ -1021,24 +1021,51 @@ namespace NavisworksTransport
try
{
// 优化方案使用Search API一次性查找所有几何对象
// 相比遍历 RootItemDescendantsAndSelf这种方式在大型模型中快几个数量级
var search = new Search();
// 优化方案:递归遍历 (Top-Down Traversal)
// 原因Search API 的 Hidden 属性不检查父级可见性,且 IsHidden 属性检查自身。
// 解决方案:使用堆栈进行深度优先搜索(DFS),如果遭遇 Hidden 节点则停止深入该分支。
// 这天然地利用了 Navisworks 的层级可见性规则。
_allGeometryItemsCache = new ModelItemCollection();
int visibleCount = 0;
// 设置搜索条件:查找具有 Geometry 属性类别的对象
search.SearchConditions.Add(
SearchCondition.HasCategoryByName(PropertyCategoryNames.Geometry));
// 使用 Stack 模拟递归避免栈溢出,并保持对 .NET 版本的兼容性
// Stack 初始容量设为 1000 以减少扩容
var stack = new Stack<ModelItem>(1000);
// 设置搜索范围:包含后代
search.Selection.SelectAll();
search.Locations = SearchLocations.DescendantsAndSelf;
// 将所有模型的根节点压入栈
foreach (var model in Application.ActiveDocument.Models)
{
stack.Push(model.RootItem);
}
// 执行搜索并直接存储 ModelItemCollection避免 ToList() 转换)
_allGeometryItemsCache = search.FindAll(Application.ActiveDocument, false);
while (stack.Count > 0)
{
var item = stack.Pop();
// 关键剪枝:如果节点显式隐藏,则跳过其整个分支(它的所有子节点都不可见)
if (item.IsHidden)
continue;
// 收集:如果是几何体,加入缓存
// 注意:这里我们假设只有几何体(HasGeometry)才是我们需要碰撞检测的对象
// 如果父组没有Geometry但子节点有我们会继续遍历子节点
if (item.HasGeometry)
{
_allGeometryItemsCache.Add(item);
visibleCount++;
}
// 将子节点压入栈 (倒序压入以保持遍历顺序,虽不 strictly necessary)
foreach (var child in item.Children)
{
stack.Push(child);
}
}
cacheStopwatch.Stop();
LogManager.Info($"几何对象列表缓存构建完成 (Search API优化版),耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 缓存对象总数: {_allGeometryItemsCache.Count} 个");
LogManager.Info($"几何对象列表缓存构建完成 (DFS遍历 + 自动剪枝),耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 缓存可见对象: {visibleCount}");
LogManager.Info($" - 策略: 深度优先搜索 (父节点隐藏则跳过分支)");
}
catch (Exception ex)
{