feat: 集成空间索引到碰撞检测系统(单层架构)

重构说明:
- 移除两层架构的 GetPotentialColliders() 方法
- 移除不再使用的 CalculatePathBounds() 方法
- 在 PrecomputeAnimationFrames() 中构建全局空间索引
- 每帧使用空间索引直接查询附近对象(O(1) 复杂度)

技术细节:
- 网格大小:基于动画对象包围盒尺寸
- 搜索半径:对象对角线的一半 + 检测间隙
- 自动排除动画对象本身

性能优势:
- 旧架构:290 对象 → 108 对象(预过滤)→ 每帧遍历 108 对象
- 新架构:290 对象 → 每帧空间查询 ~5-15 对象
- 预期性能提升:10-15x
This commit is contained in:
tian 2025-10-14 11:56:03 +08:00
parent 360d55ffa8
commit c7f6586fa9

View File

@ -377,17 +377,36 @@ namespace NavisworksTransport.Core.Animation
originalBoundingBox.Max.Y - originalBoundingBox.Min.Y,
originalBoundingBox.Max.Z - originalBoundingBox.Min.Z
);
// 获取场景中所有可能碰撞的对象
var potentialColliders = GetPotentialColliders();
LogManager.Info($"潜在碰撞对象数: {potentialColliders.Count}");
// 【新架构】构建全局空间索引(单层架构)
LogManager.Info("=== 构建全局空间索引 ===");
var spatialIndexManager = SpatialIndexManager.Instance;
// 使用包围盒对角线作为网格大小的参考(确保合理的网格粒度)
double cellSizeInModelUnits = Math.Max(
boundingBoxSize.X,
Math.Max(boundingBoxSize.Y, boundingBoxSize.Z)
);
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
LogManager.Info(spatialIndexManager.GetStatistics());
// 计算搜索半径:动画对象包围盒的对角线 + 检测间隙
double objectDiagonal = Math.Sqrt(
boundingBoxSize.X * boundingBoxSize.X +
boundingBoxSize.Y * boundingBoxSize.Y +
boundingBoxSize.Z * boundingBoxSize.Z
);
double searchRadiusInModelUnits = objectDiagonal / 2 + _detectionGap;
LogManager.Info($"空间查询半径: {searchRadiusInModelUnits:F2} 模型单位");
// 预计算每一帧
for (int i = 0; i < totalFrames; i++)
{
double progress = (double)i / totalFrames;
var framePosition = InterpolatePosition(progress);
// 创建帧数据
var frame = new AnimationFrame
{
@ -396,12 +415,19 @@ namespace NavisworksTransport.Core.Animation
Position = framePosition,
Collisions = new List<CollisionResult>()
};
// 虚拟碰撞检测(不移动实际物体)
var virtualBoundingBox = CreateVirtualBoundingBox(framePosition, boundingBoxSize);
// 检测与所有潜在对象的碰撞
foreach (var collider in potentialColliders)
// 【新架构】使用空间索引查询附近对象
var nearbyObjects = spatialIndexManager.FindNearbyObjects(
framePosition,
searchRadiusInModelUnits,
excludeObject: _animatedObject
);
// 检测与附近对象的碰撞
foreach (var collider in nearbyObjects)
{
var colliderBox = collider.BoundingBox();
@ -455,122 +481,6 @@ namespace NavisworksTransport.Core.Animation
}
}
/// <summary>
/// 获取潜在碰撞对象列表
/// </summary>
private List<ModelItem> GetPotentialColliders()
{
try
{
LogManager.Info("开始获取潜在碰撞对象");
// 构建通道缓存
ClashDetectiveIntegration.Instance.BuildChannelObjectsCache();
ClashDetectiveIntegration.BuildAllGeometryItemsCache();
// 获取所有有几何体的对象
var allItems = NavisApplication.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry)
.ToList();
// 构建排除列表:动画对象本身及其子对象
var excludeList = new ModelItemCollection();
excludeList.AddRange(_animatedObject.DescendantsAndSelf);
// 计算路径包围盒
var pathBounds = CalculatePathBounds();
// 计算路径包围盒中心点(模型单位)
var pathCenter = new Point3D(
(pathBounds.Min.X + pathBounds.Max.X) / 2,
(pathBounds.Min.Y + pathBounds.Max.Y) / 2,
(pathBounds.Min.Z + pathBounds.Max.Z) / 2
);
// 计算包围盒对角线长度
var diagonal = Math.Sqrt(
Math.Pow(pathBounds.Max.X - pathBounds.Min.X, 2) +
Math.Pow(pathBounds.Max.Y - pathBounds.Min.Y, 2) +
Math.Pow(pathBounds.Max.Z - pathBounds.Min.Z, 2)
);
// 检测半径 = 包围盒对角线的一半(精确覆盖整条路径)
var detectionGap = diagonal / 2;
LogManager.Info($"路径包围盒: Min=({pathBounds.Min.X:F2}, {pathBounds.Min.Y:F2}, {pathBounds.Min.Z:F2}), " +
$"Max=({pathBounds.Max.X:F2}, {pathBounds.Max.Y:F2}, {pathBounds.Max.Z:F2})");
LogManager.Info($"路径中心: ({pathCenter.X:F2}, {pathCenter.Y:F2}, {pathCenter.Z:F2})");
LogManager.Info($"包围盒对角线: {diagonal:F2} 模型单位");
// 临时移动物体到路径中心,以便在路径范围内检测潜在碰撞对象
var offset = ModelItemTransformHelper.MoveItemToPosition(_animatedObject, pathCenter);
try
{
LogManager.Info($"检测半径: {detectionGap:F2} 模型单位(对角线的一半)");
// 调用ClashDetectiveIntegration的DetectCollisions方法
// 现在物体在路径中心,检测路径长度半径内的对象
// 该方法内部会自动排除通道对象通过IsChannelObject方法
var collisionResults = ClashDetectiveIntegration.Instance.DetectCollisions(
_animatedObject,
excludeList,
detectionGap
);
// 从碰撞结果中提取唯一的碰撞对象
var potentialColliders = new HashSet<ModelItem>();
foreach (var result in collisionResults)
{
if (result.Item2 != null && !potentialColliders.Contains(result.Item2))
{
potentialColliders.Add(result.Item2);
}
}
var finalList = potentialColliders.ToList();
LogManager.Info($"获取潜在碰撞对象完成: 总对象={allItems.Count}, " +
$"排除自身及子对象={excludeList.Count}, " +
$"路径沿途潜在碰撞对象={finalList.Count}(已自动排除通道)");
return finalList;
}
finally
{
// 恢复物体到原位置
ModelItemTransformHelper.RestoreItemPosition(_animatedObject, offset);
LogManager.Info("物体已恢复到原始位置");
}
}
catch (Exception ex)
{
LogManager.Error($"获取潜在碰撞对象失败: {ex.Message}");
return new List<ModelItem>();
}
}
/// <summary>
/// 计算路径的包围盒
/// </summary>
private BoundingBox3D CalculatePathBounds()
{
if (_pathPoints == null || _pathPoints.Count == 0)
return new BoundingBox3D();
var minX = _pathPoints.Min(p => p.X);
var minY = _pathPoints.Min(p => p.Y);
var minZ = _pathPoints.Min(p => p.Z);
var maxX = _pathPoints.Max(p => p.X);
var maxY = _pathPoints.Max(p => p.Y);
var maxZ = _pathPoints.Max(p => p.Z);
return new BoundingBox3D(
new Point3D(minX, minY, minZ),
new Point3D(maxX, maxY, maxZ)
);
}
/// <summary>
/// 创建虚拟包围盒(用于碰撞检测)
/// </summary>