1751 lines
77 KiB
C#
1751 lines
77 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Autodesk.Navisworks.Api;
|
||
using Autodesk.Navisworks.Api.Clash;
|
||
using NavisworksTransport.Core;
|
||
using NavisworksTransport.Core.Animation;
|
||
using NavisworksTransport.Utils;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// Clash Detective 集成管理器
|
||
/// 实现动态碰撞检测与Clash Detective窗口的联动
|
||
/// </summary>
|
||
public class ClashDetectiveIntegration
|
||
{
|
||
private static ClashDetectiveIntegration _instance;
|
||
|
||
private DocumentClash _documentClash;
|
||
private List<ClashResult> _currentCollisions;
|
||
// 通道对象缓存,用于优化碰撞检测性能
|
||
private static HashSet<ModelItem> _channelObjectsCache = null;
|
||
private static readonly object _cacheLock = new object();
|
||
|
||
// 几何对象列表缓存,用于避免重复获取对象列表
|
||
// 使用 ModelItemCollection 而非 List<ModelItem> 以避免昂贵的 ToList() 转换
|
||
private static ModelItemCollection _allGeometryItemsCache = null;
|
||
|
||
// 已过滤通道对象的几何对象列表缓存(供空间索引使用)
|
||
private static List<ModelItem> _nonChannelGeometryItemsCache = null;
|
||
|
||
// 🔥 移动物体引用(用于从空间索引中排除移动物体及其所有子节点)
|
||
private static ModelItem _animatedObject = null;
|
||
|
||
// 碰撞检测计数器
|
||
private int _animationCollisionCount = 0; // 动画过程中简单包围盒检测的碰撞数量
|
||
private int _clashDetectiveCollisionCount = 0; // Clash Detective最终检测的碰撞数量
|
||
|
||
/// <summary>
|
||
/// 动画过程中检测到的碰撞数量(仅供参考统计)
|
||
/// </summary>
|
||
public int AnimationCollisionCount
|
||
{
|
||
get { return _animationCollisionCount; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Clash Detective检测到的权威碰撞数量
|
||
/// </summary>
|
||
public int ClashDetectiveCollisionCount
|
||
{
|
||
get { return _clashDetectiveCollisionCount; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前ClashDetective测试名称
|
||
/// </summary>
|
||
public string CurrentTestName
|
||
{
|
||
get { return _currentTestName; }
|
||
}
|
||
|
||
private string _currentTestName;
|
||
|
||
/// <summary>
|
||
/// 单例实例
|
||
/// </summary>
|
||
public static ClashDetectiveIntegration Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = new ClashDetectiveIntegration();
|
||
}
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前检测到的碰撞结果
|
||
/// </summary>
|
||
public List<ClashResult> CurrentCollisions
|
||
{
|
||
get { return _currentCollisions ?? new List<ClashResult>(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞检测结果变化事件
|
||
/// </summary>
|
||
public event EventHandler<CollisionDetectedEventArgs> CollisionDetected;
|
||
|
||
/// <summary>
|
||
/// ClashDetective结果保存到数据库事件
|
||
/// </summary>
|
||
public event EventHandler<ClashDetectiveResultSavedEventArgs> ClashDetectiveResultSaved;
|
||
|
||
private ClashDetectiveIntegration()
|
||
{
|
||
_currentCollisions = new List<ClashResult>();
|
||
|
||
// 自动初始化 Clash Detective 集成
|
||
try
|
||
{
|
||
LogManager.Info("初始化Clash Detective集成(.NET API模式)...");
|
||
|
||
// 直接使用.NET API获取Clash文档
|
||
_documentClash = Application.ActiveDocument.GetClash();
|
||
if (_documentClash != null)
|
||
{
|
||
LogManager.Info("成功获取.NET API Clash文档");
|
||
LogManager.Info("Clash Detective集成初始化成功(.NET API模式)");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("无法获取Clash文档,Clash Detective可能未安装");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"初始化Clash Detective集成失败: {ex.Message}");
|
||
_documentClash = null;
|
||
}
|
||
}
|
||
|
||
private List<CollisionResult> _deduplicatedCollisionResults = new List<CollisionResult>(); // 去重后的预计算碰撞结果
|
||
private readonly object _resultsLock = new object();
|
||
|
||
// ClashDetective测试结果缓存:pathName -> List<CollisionResult>
|
||
// 使用自定义的CollisionResult类型,支持修改Item1和Item2
|
||
private Dictionary<string, List<CollisionResult>> _clashDetectiveResultsCache = new Dictionary<string, List<CollisionResult>>();
|
||
private readonly object _clashResultsCacheLock = new object();
|
||
|
||
/// <summary>
|
||
/// 获取去重后的预计算碰撞结果(第一次去重,按碰撞对象对去重)
|
||
/// </summary>
|
||
public List<CollisionResult> GetDeduplicatedCollisionResults()
|
||
{
|
||
lock (_resultsLock)
|
||
{
|
||
LogManager.Debug($"[GetDeduplicatedCollisionResults] 当前去重缓存数量: {_deduplicatedCollisionResults.Count}");
|
||
return new List<CollisionResult>(_deduplicatedCollisionResults);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存ClashDetective结果到数据库
|
||
/// </summary>
|
||
private void SaveClashDetectiveResultToDatabase(string routeId, List<CollisionResult> clashResults,
|
||
int frameRate, double duration, double detectionGap,
|
||
ModelItem animatedObject, bool isVirtualVehicle,
|
||
double virtualVehicleLength, double virtualVehicleWidth, double virtualVehicleHeight)
|
||
{
|
||
try
|
||
{
|
||
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
|
||
if (pathDatabase != null)
|
||
{
|
||
// 获取动画对象名称和路径信息
|
||
string animatedObjectName = "未知对象";
|
||
int? vehicleModelIndex = null;
|
||
string vehiclePathId = null;
|
||
|
||
if (!isVirtualVehicle && animatedObject != null)
|
||
{
|
||
animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject);
|
||
// 获取真实车辆的 PathId 信息
|
||
var pathId = Application.ActiveDocument.Models.CreatePathId(animatedObject);
|
||
vehicleModelIndex = pathId.ModelIndex;
|
||
vehiclePathId = pathId.PathId;
|
||
}
|
||
else if (isVirtualVehicle)
|
||
{
|
||
animatedObjectName = "虚拟车辆";
|
||
}
|
||
|
||
// 打印虚拟车辆尺寸(用于调试)
|
||
LogManager.Info($"[SaveClashDetectiveResultToDatabase] IsVirtualVehicle={isVirtualVehicle}, 虚拟车辆尺寸: Length={virtualVehicleLength:F2}m, Width={virtualVehicleWidth:F2}m, Height={virtualVehicleHeight:F2}m");
|
||
|
||
var record = new ClashDetectiveResultRecord
|
||
{
|
||
TestName = _currentTestName,
|
||
RouteId = routeId ?? "",
|
||
TestTime = DateTime.Now,
|
||
CollisionCount = clashResults.Count,
|
||
AnimationCollisionCount = _animationCollisionCount,
|
||
FrameRate = frameRate,
|
||
Duration = duration,
|
||
DetectionGap = detectionGap,
|
||
AnimatedObjectName = animatedObjectName,
|
||
IsVirtualVehicle = isVirtualVehicle,
|
||
VehicleModelIndex = vehicleModelIndex,
|
||
VehiclePathId = vehiclePathId,
|
||
VirtualVehicleLength = virtualVehicleLength,
|
||
VirtualVehicleWidth = virtualVehicleWidth,
|
||
VirtualVehicleHeight = virtualVehicleHeight,
|
||
CreatedAt = DateTime.Now
|
||
};
|
||
var resultId = pathDatabase.SaveClashDetectiveResult(record);
|
||
LogManager.Info($"ClashDetective结果已保存到数据库,Id={resultId}, IsVirtualVehicle={isVirtualVehicle}");
|
||
|
||
// 保存被撞物体信息(只保存Item2,不保存车辆Item1)
|
||
var collisionObjects = new List<ClashDetectiveCollisionObjectRecord>();
|
||
|
||
foreach (var collision in clashResults)
|
||
{
|
||
// 只保存被撞到的物体(Item2)
|
||
if (collision.Item2 != null)
|
||
{
|
||
// 使用 CreatePathId API 获取 ModelIndex 和 PathId
|
||
var pathId = Application.ActiveDocument.Models.CreatePathId(collision.Item2);
|
||
|
||
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
|
||
{
|
||
ResultId = resultId,
|
||
ModelIndex = pathId.ModelIndex,
|
||
PathId = pathId.PathId,
|
||
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2),
|
||
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2)
|
||
});
|
||
}
|
||
}
|
||
|
||
if (collisionObjects.Count > 0)
|
||
{
|
||
pathDatabase.SaveClashDetectiveCollisionObjects(resultId, collisionObjects);
|
||
LogManager.Info($"已保存 {collisionObjects.Count} 个被撞物体到数据库");
|
||
}
|
||
|
||
// 触发结果保存事件,通知UI刷新列表(从RouteId获取PathName)
|
||
var pathName = GetPathNameByRouteId(routeId);
|
||
ClashDetectiveResultSaved?.Invoke(this, new ClashDetectiveResultSavedEventArgs(pathName, _currentTestName, clashResults.Count));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"保存ClashDetective结果到数据库失败: {ex.Message}");
|
||
// 不影响主流程,继续执行
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据RouteId获取路径名称
|
||
/// </summary>
|
||
private string GetPathNameByRouteId(string routeId)
|
||
{
|
||
if (string.IsNullOrEmpty(routeId))
|
||
return "未知路径";
|
||
|
||
try
|
||
{
|
||
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
|
||
if (pathDatabase != null)
|
||
{
|
||
var pathRoute = pathDatabase.GetPathRouteSync(routeId);
|
||
return pathRoute?.Name ?? "未知路径";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"获取路径名称失败: {ex.Message}");
|
||
}
|
||
return "未知路径";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从数据库加载ClashDetective碰撞结果
|
||
/// </summary>
|
||
/// <param name="testName">测试名称</param>
|
||
/// <returns>碰撞结果列表,如果加载失败返回null</returns>
|
||
public List<CollisionResult> GetClashDetectiveResultsFromDatabase(string testName)
|
||
{
|
||
try
|
||
{
|
||
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
|
||
if (pathDatabase == null)
|
||
{
|
||
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] PathDatabase不可用");
|
||
return null;
|
||
}
|
||
|
||
// 1. 从数据库读取测试信息(添加JOIN获取PathName)
|
||
var testInfoSql = @"
|
||
SELECT cdr.Id, pr.Name AS PathName, cdr.RouteId, cdr.IsVirtualVehicle, cdr.VehicleModelIndex, cdr.VehiclePathId,
|
||
cdr.VirtualVehicleLength, cdr.VirtualVehicleWidth, cdr.VirtualVehicleHeight, cdr.ScreenshotPath
|
||
FROM ClashDetectiveResults cdr
|
||
INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
|
||
WHERE cdr.TestName = @testName
|
||
";
|
||
|
||
ClashDetectiveResultRecord testInfo = null;
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(testInfoSql, pathDatabase._connection))
|
||
{
|
||
cmd.Parameters.AddWithValue("@testName", testName);
|
||
using (var reader = cmd.ExecuteReader())
|
||
{
|
||
if (reader.Read())
|
||
{
|
||
testInfo = new ClashDetectiveResultRecord
|
||
{
|
||
Id = Convert.ToInt32(reader["Id"]),
|
||
PathName = reader["PathName"].ToString(),
|
||
RouteId = reader["RouteId"]?.ToString(),
|
||
IsVirtualVehicle = Convert.ToBoolean(reader["IsVirtualVehicle"]),
|
||
VehicleModelIndex = reader["VehicleModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["VehicleModelIndex"]) : (int?)null,
|
||
VehiclePathId = reader["VehiclePathId"] != DBNull.Value ? reader["VehiclePathId"].ToString() : null,
|
||
VirtualVehicleLength = reader["VirtualVehicleLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleLength"]) : 0.0,
|
||
VirtualVehicleWidth = reader["VirtualVehicleWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleWidth"]) : 0.0,
|
||
VirtualVehicleHeight = reader["VirtualVehicleHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleHeight"]) : 0.0,
|
||
ScreenshotPath = reader["ScreenshotPath"] != DBNull.Value ? reader["ScreenshotPath"].ToString() : null
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
if (testInfo == null)
|
||
{
|
||
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 未找到测试记录: {testName}");
|
||
return null;
|
||
}
|
||
|
||
// 2. 重建车辆对象
|
||
ModelItem vehicleObject = null;
|
||
if (testInfo.IsVirtualVehicle)
|
||
{
|
||
// 显示虚拟车辆
|
||
VirtualVehicleManager.Instance.ShowVirtualVehicle(
|
||
testInfo.VirtualVehicleLength,
|
||
testInfo.VirtualVehicleWidth,
|
||
testInfo.VirtualVehicleHeight
|
||
);
|
||
vehicleObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle
|
||
?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 显示虚拟车辆失败");
|
||
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已显示虚拟车辆: {testInfo.VirtualVehicleLength}×{testInfo.VirtualVehicleWidth}×{testInfo.VirtualVehicleHeight}m");
|
||
}
|
||
else if (testInfo.VehicleModelIndex.HasValue && !string.IsNullOrEmpty(testInfo.VehiclePathId))
|
||
{
|
||
// 通过 PathId 查找真实车辆
|
||
try
|
||
{
|
||
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
|
||
{
|
||
ModelIndex = testInfo.VehicleModelIndex.Value,
|
||
PathId = testInfo.VehiclePathId
|
||
};
|
||
vehicleObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj) ?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过 PathId 找到车辆对象: ModelIndex={testInfo.VehicleModelIndex}, PathId={testInfo.VehiclePathId}");
|
||
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已找到真实车辆: {ModelItemAnalysisHelper.GetSafeDisplayName(vehicleObject)}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={testInfo.VehicleModelIndex}, PathId={testInfo.VehiclePathId}", ex);
|
||
}
|
||
}
|
||
|
||
if (vehicleObject == null)
|
||
{
|
||
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法重建车辆对象: IsVirtualVehicle={testInfo.IsVirtualVehicle}, VehicleModelIndex={testInfo.VehicleModelIndex}, VehiclePathId={testInfo.VehiclePathId}");
|
||
}
|
||
|
||
// 如果是虚拟车辆,将其移动到路径起点
|
||
if (testInfo.IsVirtualVehicle && !string.IsNullOrEmpty(testInfo.RouteId))
|
||
{
|
||
try
|
||
{
|
||
var pathPlanningManager = PathPlanningManager.Instance;
|
||
var route = pathPlanningManager.GetAllRoutes().FirstOrDefault(r => r.Id == testInfo.RouteId);
|
||
|
||
if (route != null && route.Points != null && route.Points.Count > 0)
|
||
{
|
||
// 获取第一个点(起点)
|
||
var startPoint = route.Points.FirstOrDefault(p => p.Type == PathPointType.StartPoint) ?? route.Points[0];
|
||
var startPointPosition = startPoint.Position;
|
||
|
||
// 使用 PathAnimationManager 将车辆移动到起点
|
||
var pathAnimationManager = Core.Animation.PathAnimationManager.GetInstance();
|
||
pathAnimationManager.MoveVehicleToPathStart(vehicleObject, new List<Point3D> { startPointPosition, startPointPosition });
|
||
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 虚拟车辆已移动到路径起点: ({startPointPosition.X:F2}, {startPointPosition.Y:F2}, {startPointPosition.Z:F2})");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 移动虚拟车辆到起点失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 3. 从数据库读取被撞物体信息
|
||
var collisionObjectsSql = @"
|
||
SELECT ModelIndex, PathId, DisplayName, ObjectName
|
||
FROM ClashDetectiveCollisionObjects
|
||
WHERE ResultId = @resultId
|
||
";
|
||
|
||
var collisionObjects = new List<ClashDetectiveCollisionObjectRecord>();
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(collisionObjectsSql, pathDatabase._connection))
|
||
{
|
||
cmd.Parameters.AddWithValue("@resultId", testInfo.Id);
|
||
using (var reader = cmd.ExecuteReader())
|
||
{
|
||
while (reader.Read())
|
||
{
|
||
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
|
||
{
|
||
ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
|
||
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
|
||
DisplayName = reader["DisplayName"]?.ToString(),
|
||
ObjectName = reader["ObjectName"]?.ToString()
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. 重建碰撞结果
|
||
var results = new List<CollisionResult>();
|
||
|
||
foreach (var obj in collisionObjects)
|
||
{
|
||
ModelItem collidedObject = null;
|
||
|
||
// 优先使用 ResolvePathId API(新方式)
|
||
if (obj.ModelIndex.HasValue && !string.IsNullOrEmpty(obj.PathId))
|
||
{
|
||
try
|
||
{
|
||
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId();
|
||
pathIdObj.ModelIndex = obj.ModelIndex.Value;
|
||
pathIdObj.PathId = obj.PathId;
|
||
collidedObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={obj.ModelIndex}, PathId={obj.PathId}", ex);
|
||
}
|
||
}
|
||
|
||
if (collidedObject == null)
|
||
{
|
||
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到车辆对象: {obj.ModelIndex},{obj.PathId}");
|
||
}
|
||
|
||
if (!ModelItemAnalysisHelper.IsModelItemValid(collidedObject))
|
||
{
|
||
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 找到的 ModelItem 无效: {obj.ModelIndex},{obj.PathId}");
|
||
}
|
||
|
||
// 使用重建对象的 DisplayName,而不是数据库中保存的 DisplayName
|
||
var collidedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collidedObject);
|
||
|
||
var collisionResult = new CollisionResult
|
||
{
|
||
ClashGuid = Guid.NewGuid(),
|
||
DisplayName = $"历史碰撞: {collidedObjectName}",
|
||
Status = ClashResultStatus.Active,
|
||
Item1 = vehicleObject,
|
||
Item2 = collidedObject,
|
||
Center = collidedObject.BoundingBox().Center,
|
||
Distance = 0.0,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
|
||
results.Add(collisionResult);
|
||
}
|
||
|
||
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 从数据库加载测试 '{testName}' 完成,重建了 {results.Count} 个碰撞结果");
|
||
return results;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[LoadClashDetectiveResultsFromDatabase] 加载失败: {ex.Message}", ex);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取对象当前位置
|
||
/// </summary>
|
||
private Point3D GetObjectPosition(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
if (item == null) return new Point3D(0, 0, 0);
|
||
|
||
var bounds = item.BoundingBox();
|
||
if (bounds != null)
|
||
{
|
||
return new Point3D(
|
||
(bounds.Min.X + bounds.Max.X) / 2,
|
||
(bounds.Min.Y + bounds.Max.Y) / 2,
|
||
(bounds.Min.Z + bounds.Max.Z) / 2
|
||
);
|
||
}
|
||
return new Point3D(0, 0, 0);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"获取对象位置失败: {ex.Message}");
|
||
return new Point3D(0, 0, 0);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 运行ClashDetective测试并保存到数据库(公共方法,供批处理和非批处理调用)
|
||
/// </summary>
|
||
/// <returns>碰撞分组、主测试、确认的碰撞数量</returns>
|
||
public (ClashResultGroup collisionGroup, ClashTest addedMainTest, int confirmedCount) RunClashDetectiveTestsAndSaveToDatabase(
|
||
List<CollisionResult> precomputedCollisions,
|
||
double detectionGap,
|
||
string routeId,
|
||
ModelItem animatedObject,
|
||
bool isVirtualVehicle,
|
||
int frameRate,
|
||
double duration,
|
||
double virtualVehicleLength,
|
||
double virtualVehicleWidth,
|
||
double virtualVehicleHeight,
|
||
Progress progress = null)
|
||
{
|
||
LogManager.Info($"[ClashDetective] 开始运行碰撞检测并保存到数据库(容差: {detectionGap}米)");
|
||
|
||
// 过滤有效的碰撞
|
||
var validCollisions = precomputedCollisions.Where(collision =>
|
||
collision.HasPositionInfo &&
|
||
IsModelItemValid(collision.Item1) &&
|
||
IsModelItemValid(collision.Item2)
|
||
).ToList();
|
||
|
||
LogManager.Info($"[ClashDetective] 有效碰撞数量: {validCollisions.Count}");
|
||
|
||
// 第一步:创建主测试(不包含选择集,纯容器)
|
||
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmssfff}";
|
||
_currentTestName = mainTestName;
|
||
LogManager.Info($"[分组测试] 创建主测试: {mainTestName}");
|
||
|
||
// 创建主测试
|
||
var mainTest = new ClashTest
|
||
{
|
||
DisplayName = mainTestName,
|
||
TestType = ClashTestType.Hard,
|
||
Tolerance = detectionGap,
|
||
Guid = Guid.Empty,
|
||
MergeComposites = true
|
||
};
|
||
|
||
// 添加主测试到文档
|
||
_documentClash.TestsData.TestsAddCopy(mainTest);
|
||
LogManager.Info($"[分组测试] 主测试已添加到文档");
|
||
|
||
// 获取添加后的测试对象引用
|
||
var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest;
|
||
if (addedMainTest == null)
|
||
{
|
||
LogManager.Error("[分组测试] 无法获取添加后的主测试对象");
|
||
return (null, null, 0);
|
||
}
|
||
|
||
// 如果没有有效碰撞,直接保存空记录并返回
|
||
if (validCollisions.Count == 0)
|
||
{
|
||
LogManager.Warning("[ClashDetective] 没有有效的碰撞,保存空碰撞记录");
|
||
|
||
// 重置碰撞计数器(重要:避免保留上次测试的值)
|
||
_clashDetectiveCollisionCount = 0;
|
||
|
||
// 保存到数据库
|
||
SaveClashDetectiveResultToDatabase(
|
||
routeId,
|
||
new List<CollisionResult>(),
|
||
frameRate,
|
||
duration,
|
||
detectionGap,
|
||
animatedObject,
|
||
isVirtualVehicle,
|
||
virtualVehicleLength,
|
||
virtualVehicleWidth,
|
||
virtualVehicleHeight
|
||
);
|
||
|
||
return (null, addedMainTest, 0);
|
||
}
|
||
|
||
// 第二步:创建分组并添加碰撞结果(应用智能去重)
|
||
// 1. 分组:按碰撞对象对分组
|
||
var groupedCollisions = validCollisions
|
||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||
.ToList();
|
||
|
||
LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
|
||
|
||
// 缓存去重后的碰撞结果(每组取第一个)
|
||
lock (_resultsLock)
|
||
{
|
||
_deduplicatedCollisionResults.Clear();
|
||
foreach (var group in groupedCollisions)
|
||
{
|
||
_deduplicatedCollisionResults.Add(group.First());
|
||
}
|
||
LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果");
|
||
}
|
||
|
||
var collisionGroup = new ClashResultGroup
|
||
{
|
||
DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)"
|
||
};
|
||
|
||
LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}");
|
||
|
||
int confirmedCount = 0;
|
||
int skippedCount = 0;
|
||
var doc = Application.ActiveDocument;
|
||
|
||
// 收集所有临时测试,用于批量删除
|
||
var tempTestsToRemove = new List<ClashTest>();
|
||
|
||
// 2. 遍历每一组
|
||
for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++)
|
||
{
|
||
// 检查用户是否取消
|
||
if (progress != null && progress.IsCanceled)
|
||
{
|
||
LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组");
|
||
break;
|
||
}
|
||
|
||
var group = groupedCollisions[groupIndex];
|
||
|
||
// 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞
|
||
// 注意:Distance通常越小(或负值越大)表示碰撞越深,具体取决于计算方式
|
||
// 这里假设Distance越小越严重
|
||
var sortedCandidates = group.OrderBy(c => c.Distance).ToList();
|
||
|
||
bool pairConfirmed = false;
|
||
|
||
// 4. 验证即止:逐个检测候选帧
|
||
for (int i = 0; i < sortedCandidates.Count; i++)
|
||
{
|
||
var candidate = sortedCandidates[i];
|
||
|
||
try
|
||
{
|
||
var testAnimatedObject = candidate.Item1;
|
||
var modelItems = new ModelItemCollection { testAnimatedObject };
|
||
var targetPosition = candidate.Item1Position;
|
||
|
||
var currentBounds = testAnimatedObject.BoundingBox();
|
||
var currentPos = new Point3D(
|
||
(currentBounds.Min.X + currentBounds.Max.X) / 2,
|
||
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
|
||
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
|
||
);
|
||
|
||
var offset = new Vector3D(
|
||
targetPosition.X - currentPos.X,
|
||
targetPosition.Y - currentPos.Y,
|
||
targetPosition.Z - currentPos.Z
|
||
);
|
||
|
||
var transform = Transform3D.CreateTranslation(offset);
|
||
doc.Models.OverridePermanentTransform(modelItems, transform, false);
|
||
|
||
var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}";
|
||
|
||
// 🔥 优化3:预配置 ClashTest,避免后续 CreateCopy 和 TestsEditTestFromCopy 调用
|
||
var tempTest = new ClashTest
|
||
{
|
||
DisplayName = tempTestName,
|
||
TestType = ClashTestType.Hard,
|
||
Tolerance = detectionGap,
|
||
Guid = Guid.Empty,
|
||
MergeComposites = true
|
||
};
|
||
|
||
var selectionA = new ModelItemCollection { candidate.Item1 };
|
||
var selectionB = new ModelItemCollection { candidate.Item2 };
|
||
tempTest.SelectionA.Selection.CopyFrom(selectionA);
|
||
tempTest.SelectionB.Selection.CopyFrom(selectionB);
|
||
|
||
// 直接设置 PrimitiveTypes,避免后续的 CreateCopy 和 TestsEditTestFromCopy
|
||
tempTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles;
|
||
tempTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
|
||
|
||
_documentClash.TestsData.TestsAddCopy(tempTest);
|
||
var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
|
||
|
||
if (addedTempTest != null)
|
||
{
|
||
// 🔥 优化3:跳过了 addedTempTest.CreateCopy() 和 TestsEditTestFromCopy 调用
|
||
// 因为 PrimitiveTypes 已在创建 tempTest 时设置好
|
||
|
||
_documentClash.TestsData.TestsRunTest(addedTempTest);
|
||
|
||
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
|
||
if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0)
|
||
{
|
||
// !!!发现真实碰撞!!!
|
||
pairConfirmed = true;
|
||
confirmedCount++;
|
||
skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量
|
||
|
||
int subResultIndex = 1;
|
||
// 🔥 优化:预获取运动物体名称,避免在循环中重复获取
|
||
var animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject);
|
||
|
||
foreach (var child in refreshedTempTest.Children)
|
||
{
|
||
if (child is ClashResult result)
|
||
{
|
||
var copiedResult = result.CreateCopy() as ClashResult;
|
||
copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID
|
||
|
||
// 设置碰撞名称:运动物体名称直接使用预获取的,被撞物体查找父容器
|
||
var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2);
|
||
var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2);
|
||
|
||
var timeStamp = DateTime.Now.ToString("HHmmss");
|
||
copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {animatedObjectName} ↔ {object2Name}";
|
||
|
||
collisionGroup.Children.Add(copiedResult);
|
||
subResultIndex++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 🔥 优化4:收集测试用于批量删除,而不是立即删除
|
||
tempTestsToRemove.Add(refreshedTempTest ?? addedTempTest);
|
||
|
||
if (pairConfirmed)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception itemEx)
|
||
{
|
||
LogManager.Error($"[ClashDetective] 候选检测失败: {itemEx.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 🔥 优化4:批量删除所有临时测试
|
||
if (tempTestsToRemove.Count > 0)
|
||
{
|
||
LogManager.Debug($"[分组测试] 批量删除 {tempTestsToRemove.Count} 个临时测试");
|
||
foreach (var test in tempTestsToRemove)
|
||
{
|
||
try
|
||
{
|
||
_documentClash.TestsData.TestsRemove(test);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Debug($"[分组测试] 删除临时测试失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
|
||
|
||
// 第三步:处理碰撞结果
|
||
// 🔥 重要:ClashDetective 返回的是几何体级别的碰撞结果
|
||
// Item1 是移动物体的组件名(如"车轮1"、"车轮2"),需要统一为移动物体本身
|
||
// Item2 是被撞物体,需要向上查找有意义的父级容器
|
||
var clashResults = new List<CollisionResult>();
|
||
foreach (var child in collisionGroup.Children)
|
||
{
|
||
if (child is ClashResult clashResult)
|
||
{
|
||
var compositeItem1 = animatedObject;
|
||
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
|
||
|
||
var collisionResult = new CollisionResult
|
||
{
|
||
ClashGuid = clashResult.Guid,
|
||
DisplayName = clashResult.DisplayName,
|
||
Status = clashResult.Status,
|
||
Item1 = compositeItem1,
|
||
Item2 = compositeItem2,
|
||
Center = clashResult.Center,
|
||
Distance = clashResult.Distance,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
clashResults.Add(collisionResult);
|
||
}
|
||
}
|
||
|
||
// 🔥 新增:按碰撞对象对去重
|
||
// 原因:同一个移动物体与同一个被撞物体的多个组件碰撞,需要合并为一个记录
|
||
var finalClashResults = clashResults
|
||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||
.Select(g => g.First())
|
||
.ToList();
|
||
|
||
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对");
|
||
|
||
// 🔥 优化:在去重后为结果设置详细的 DisplayName
|
||
// 这样只对保留的结果进行名称计算,避免在子碰撞结果上浪费性能
|
||
foreach (var result in finalClashResults)
|
||
{
|
||
if (result.Item1 != null && result.Item2 != null)
|
||
{
|
||
var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(result.Item1);
|
||
var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(result.Item2);
|
||
result.DisplayName = $"物流碰撞: {object1Name} ↔ {object2Name}";
|
||
}
|
||
}
|
||
|
||
// 缓存最终结果
|
||
lock (_clashResultsCacheLock)
|
||
{
|
||
_clashDetectiveResultsCache[_currentTestName] = finalClashResults;
|
||
}
|
||
LogManager.Info($"[ClashDetective] 已缓存结果:{finalClashResults.Count}个碰撞,测试名称:{_currentTestName}");
|
||
|
||
// 更新碰撞计数器
|
||
_clashDetectiveCollisionCount = clashResults.Count;
|
||
|
||
// 保存到数据库(使用去重后的结果)
|
||
SaveClashDetectiveResultToDatabase(routeId, finalClashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle,
|
||
virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight);
|
||
|
||
LogManager.Info($"[ClashDetective] 结果已保存到数据库");
|
||
|
||
// 第四步:将分组添加到主测试(由调用方完成)
|
||
return (collisionGroup, addedMainTest, confirmedCount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画结束后统一创建和运行ClashDetective碰撞测试
|
||
/// </summary>
|
||
/// <param name="precomputedCollisions">预计算碰撞结果</param>
|
||
/// <param name="detectionGap">检测间隙容差</param>
|
||
/// <param name="routeId">路由ID</param>
|
||
/// <param name="animatedObject">动画对象(如果是真实车辆)</param>
|
||
/// <param name="isVirtualVehicle">是否使用虚拟车辆</param>
|
||
/// <param name="frameRate">帧率</param>
|
||
/// <param name="duration">动画时长</param>
|
||
/// <param name="virtualVehicleLength">虚拟车辆长度(米)</param>
|
||
/// <param name="virtualVehicleWidth">虚拟车辆宽度(米)</param>
|
||
/// <param name="virtualVehicleHeight">虚拟车辆高度(米)</param>
|
||
/// <param name="pathPoints">路径点列表(用于测试完成后恢复物体位置)</param>
|
||
public void CreateAllAnimationCollisionTests(
|
||
List<CollisionResult> precomputedCollisions,
|
||
double detectionGap,
|
||
string routeId,
|
||
ModelItem animatedObject,
|
||
bool isVirtualVehicle,
|
||
int frameRate,
|
||
double duration,
|
||
double virtualVehicleLength,
|
||
double virtualVehicleWidth,
|
||
double virtualVehicleHeight,
|
||
List<Point3D> pathPoints = null)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"=== 使用预计算碰撞数据创建ClashDetective测试(容差: {detectionGap}米)===");
|
||
|
||
LogManager.Info($"[预计算数据] 共有 {precomputedCollisions.Count} 个碰撞记录");
|
||
|
||
// 直接使用所有预计算结果,只过滤有效对象(不去重)
|
||
var collisionResults = precomputedCollisions
|
||
.Where(result => IsModelItemValid(result.Item1) && IsModelItemValid(result.Item2))
|
||
.ToList();
|
||
|
||
// 记录动画过程中的碰撞数量
|
||
_animationCollisionCount = collisionResults.Count;
|
||
LogManager.Info($"[预计算处理] 原始记录: {precomputedCollisions.Count},有效碰撞: {_animationCollisionCount}");
|
||
|
||
// 获取动画对象
|
||
if (collisionResults.Count > 0)
|
||
{
|
||
animatedObject = collisionResults[0].Item1;
|
||
}
|
||
|
||
var doc = Application.ActiveDocument;
|
||
|
||
// 调用公共方法运行ClashDetective测试并保存到数据库
|
||
Progress progress = Application.BeginProgress("碰撞检测数据分析中,请稍候...");
|
||
ClashTest addedMainTest = null;
|
||
ClashResultGroup collisionGroup = null;
|
||
int confirmedCount = 0;
|
||
|
||
try
|
||
{
|
||
var result = RunClashDetectiveTestsAndSaveToDatabase(
|
||
precomputedCollisions,
|
||
detectionGap,
|
||
routeId,
|
||
animatedObject,
|
||
isVirtualVehicle,
|
||
frameRate,
|
||
duration,
|
||
virtualVehicleLength,
|
||
virtualVehicleWidth,
|
||
virtualVehicleHeight,
|
||
progress
|
||
);
|
||
collisionGroup = result.collisionGroup;
|
||
addedMainTest = result.addedMainTest;
|
||
confirmedCount = result.confirmedCount;
|
||
}
|
||
finally
|
||
{
|
||
Application.EndProgress();
|
||
}
|
||
|
||
// UI操作:将分组添加到主测试
|
||
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
|
||
{
|
||
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试");
|
||
}
|
||
|
||
// 碰撞测试完成后,将物体恢复到路径起点位置(如果提供了路径点)
|
||
if (animatedObject != null && IsModelItemValid(animatedObject) && pathPoints != null && pathPoints.Count > 0)
|
||
{
|
||
try
|
||
{
|
||
PathAnimationManager.GetInstance().MoveVehicleToPathStart(animatedObject, pathPoints);
|
||
LogManager.Info($"已将 {animatedObject.DisplayName} 恢复到路径起点位置");
|
||
}
|
||
catch (Exception restoreEx)
|
||
{
|
||
LogManager.Error($"恢复物体到路径起点失败: {restoreEx.Message}");
|
||
}
|
||
}
|
||
|
||
// 检查是否成功创建了主测试
|
||
var finalMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName.Contains("碰撞检测")) as ClashTest;
|
||
if (finalMainTest != null)
|
||
{
|
||
// 刷新Clash Detective窗口
|
||
RefreshClashDetectiveUI();
|
||
|
||
// 自动高亮ClashDetective结果
|
||
ModelHighlightHelper.HighlightClashDetectiveResults(_currentTestName, GetCurrentPathClashResults);
|
||
LogManager.Info("自动高亮ClashDetective检测结果完成");
|
||
|
||
LogManager.Info($"=== 碰撞统计最终结果 ===");
|
||
LogManager.Info($"动画过程包围盒检测: {_animationCollisionCount}个碰撞 (参考值)");
|
||
LogManager.Info($"Clash Detective权威结果: {_clashDetectiveCollisionCount}个碰撞 (权威数据)");
|
||
}
|
||
|
||
// 🔥 无论是否有主测试,都触发碰撞检测完成事件,通知生成报告
|
||
// 使用ClashDetective权威结果,而不是预计算结果
|
||
// 如果权威结果为0个碰撞,传入空列表以触发祝贺对话框
|
||
var finalCollisions = _clashDetectiveCollisionCount > 0 ? collisionResults : new List<CollisionResult>();
|
||
LogManager.Info($"触发CollisionDetected事件,通知生成报告({_clashDetectiveCollisionCount}个碰撞,权威数据)");
|
||
var eventArgs = new CollisionDetectedEventArgs(finalCollisions);
|
||
OnCollisionDetected(eventArgs);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"动画结束后创建测试失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新Clash Detective UI - 安全版本
|
||
/// </summary>
|
||
private void RefreshClashDetectiveUI()
|
||
{
|
||
try
|
||
{
|
||
// 检查文档是否有效
|
||
var doc = Application.ActiveDocument;
|
||
if (doc == null || doc.IsClear)
|
||
{
|
||
LogManager.Warning("文档无效,跳过UI刷新");
|
||
return;
|
||
}
|
||
|
||
// 检查视图是否有效
|
||
if (doc.ActiveView != null)
|
||
{
|
||
try
|
||
{
|
||
doc.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
LogManager.Info("Clash Detective UI已刷新");
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
LogManager.Warning("视图对象已释放,跳过重绘");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"重绘视图失败: {ex.Message}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("活动视图无效,跳过UI刷新");
|
||
}
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
LogManager.Warning("文档或视图对象已释放,跳过UI刷新");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"刷新Clash Detective UI失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查ModelItem是否仍然有效
|
||
/// </summary>
|
||
private bool IsModelItemValid(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
if (item == null)
|
||
return false;
|
||
|
||
// 尝试访问对象的属性来检查是否有效
|
||
var displayName = item.DisplayName;
|
||
var hasGeometry = item.HasGeometry;
|
||
|
||
// 额外检查:确保对象没有被释放
|
||
var boundingBox = item.BoundingBox();
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Debug($"ModelItem无效: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有碰撞检测测试
|
||
/// </summary>
|
||
/// <returns>碰撞检测测试列表</returns>
|
||
public List<ClashTest> GetClashTests()
|
||
{
|
||
if (_documentClash == null)
|
||
{
|
||
throw new InvalidOperationException("ClashDetective文档未初始化");
|
||
}
|
||
|
||
return _documentClash.TestsData.Tests.Cast<ClashTest>()
|
||
.Where(t => t.DisplayName.Contains("碰撞检测"))
|
||
.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前路径的Clash结果
|
||
/// </summary>
|
||
/// <param name="pathName">路径名称</param>
|
||
/// <returns>当前路径的碰撞结果列表(如果没有结果返回空列表)</returns>
|
||
public List<CollisionResult> GetCurrentPathClashResults(string testName)
|
||
{
|
||
lock (_clashResultsCacheLock)
|
||
{
|
||
// 先尝试从缓存获取
|
||
if (_clashDetectiveResultsCache.TryGetValue(testName, out var cachedResults))
|
||
{
|
||
LogManager.Debug($"[ClashDetective结果] 从缓存获取 '{testName}' 的结果:{cachedResults.Count}个碰撞");
|
||
return cachedResults;
|
||
}
|
||
|
||
// 缓存中没有,尝试从数据库加载
|
||
LogManager.Info($"[ClashDetective结果] 缓存中没有 '{testName}',尝试从数据库加载");
|
||
var loadedResults = GetClashDetectiveResultsFromDatabase(testName);
|
||
|
||
if (loadedResults != null && loadedResults.Count > 0)
|
||
{
|
||
// 缓存加载的结果
|
||
_clashDetectiveResultsCache[testName] = loadedResults;
|
||
LogManager.Info($"[ClashDetective结果] 已从数据库加载 '{testName}' 的结果并缓存:{loadedResults.Count}个碰撞");
|
||
return loadedResults;
|
||
}
|
||
|
||
// 没有找到结果,返回空列表
|
||
LogManager.Info($"[ClashDetective结果] 未找到测试 '{testName}' 的碰撞结果,返回空列表");
|
||
return new List<CollisionResult>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前测试的碰撞结果(便捷方法)
|
||
/// </summary>
|
||
/// <returns>当前测试的碰撞结果列表</returns>
|
||
public List<CollisionResult> GetCurrentTestResults()
|
||
{
|
||
return GetCurrentPathClashResults(_currentTestName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理当前测试的缓存
|
||
/// </summary>
|
||
public void ClearCurrentTestCache()
|
||
{
|
||
if (string.IsNullOrEmpty(_currentTestName))
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_clashResultsCacheLock)
|
||
{
|
||
if (_clashDetectiveResultsCache.ContainsKey(_currentTestName))
|
||
{
|
||
_clashDetectiveResultsCache.Remove(_currentTestName);
|
||
LogManager.Info($"[ClashDetective缓存] 已清理当前测试 '{_currentTestName}' 的缓存");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空当前测试名称
|
||
/// </summary>
|
||
public void ClearCurrentTestName()
|
||
{
|
||
if (!string.IsNullOrEmpty(_currentTestName))
|
||
{
|
||
LogManager.Info($"[ClashDetective] 清空当前测试名称: {_currentTestName}");
|
||
_currentTestName = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置当前测试名称
|
||
/// </summary>
|
||
public void SetCurrentTestName(string testName)
|
||
{
|
||
_currentTestName = testName;
|
||
LogManager.Info($"[ClashDetective] 设置当前测试名称: {_currentTestName}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从测试中提取碰撞结果
|
||
/// </summary>
|
||
/// <param name="clashTest">测试</param>
|
||
/// <returns>碰撞结果列表</returns>
|
||
public List<ClashResult> ExtractClashResultsFromTest(ClashTest clashTest)
|
||
{
|
||
var results = new List<ClashResult>();
|
||
ExtractClashResultsFromTestRecursive(clashTest, results);
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归提取测试中的碰撞结果
|
||
/// </summary>
|
||
/// <param name="clashTest">测试</param>
|
||
/// <param name="results">结果列表</param>
|
||
private void ExtractClashResultsFromTestRecursive(ClashTest clashTest, List<ClashResult> results)
|
||
{
|
||
foreach (var child in clashTest.Children)
|
||
{
|
||
if (child is ClashResult clashResult)
|
||
{
|
||
results.Add(clashResult);
|
||
}
|
||
else if (child is ClashResultGroup resultGroup)
|
||
{
|
||
ExtractClashResultsFromGroupRecursive(resultGroup, results);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归提取分组中的碰撞结果
|
||
/// </summary>
|
||
/// <param name="group">分组</param>
|
||
/// <param name="results">结果列表</param>
|
||
private void ExtractClashResultsFromGroupRecursive(ClashResultGroup group, List<ClashResult> results)
|
||
{
|
||
foreach (var child in group.Children)
|
||
{
|
||
if (child is ClashResult result)
|
||
{
|
||
results.Add(result);
|
||
}
|
||
else if (child is ClashResultGroup subGroup)
|
||
{
|
||
ExtractClashResultsFromGroupRecursive(subGroup, results);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有碰撞检测的碰撞结果
|
||
/// </summary>
|
||
/// <returns>碰撞结果列表</returns>
|
||
public List<ClashResult> GetAllClashResults()
|
||
{
|
||
var tests = GetClashTests();
|
||
var allResults = new List<ClashResult>();
|
||
|
||
foreach (var test in tests)
|
||
{
|
||
var results = ExtractClashResultsFromTest(test);
|
||
allResults.AddRange(results);
|
||
}
|
||
|
||
return allResults;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询
|
||
/// </summary>
|
||
public void BuildChannelObjectsCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
if (_channelObjectsCache != null) return; // 双重检查锁定
|
||
|
||
var cacheStopwatch = new System.Diagnostics.Stopwatch();
|
||
cacheStopwatch.Start();
|
||
|
||
_channelObjectsCache = new HashSet<ModelItem>();
|
||
|
||
try
|
||
{
|
||
var document = Application.ActiveDocument;
|
||
|
||
// 获取所有可通行的物流模型项
|
||
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems();
|
||
|
||
if (allChannelItems.Count == 0)
|
||
{
|
||
LogManager.Warning("[通道缓存] ⚠️ 未找到任何可通行的物流元素,请检查模型中的物流属性设置");
|
||
cacheStopwatch.Stop();
|
||
return;
|
||
}
|
||
|
||
// 🔥 优化:只提取复合对象(IsComposite = true)
|
||
// 原因:通道对象通常是复合对象(如通道、楼梯、电梯)
|
||
// 我们只需要检测复合对象,不需要检测其子节点
|
||
foreach (var channelItem in allChannelItems)
|
||
{
|
||
try
|
||
{
|
||
// 只添加复合对象
|
||
if (channelItem.IsComposite)
|
||
{
|
||
_channelObjectsCache.Add(channelItem);
|
||
}
|
||
else if (channelItem.HasGeometry)
|
||
{
|
||
// 非复合对象但有几何数据(独立实体节点):也添加
|
||
_channelObjectsCache.Add(channelItem);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[通道收集] 处理通道节点 '{channelItem?.DisplayName ?? "NULL"}' 时出错: {ex.Message}");
|
||
// 出错时至少保证通道本身被添加
|
||
_channelObjectsCache.Add(channelItem);
|
||
}
|
||
}
|
||
|
||
cacheStopwatch.Stop();
|
||
LogManager.Info($"通道对象缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
|
||
LogManager.Info($" - 可通行物流根对象: {allChannelItems.Count} 个");
|
||
LogManager.Info($" - 缓存总对象数: {_channelObjectsCache.Count} 个");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"构建通道对象缓存时发生错误: {ex.Message}", ex);
|
||
_channelObjectsCache = new HashSet<ModelItem>(); // 创建空缓存,避免重复构建
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除通道对象缓存,在模型变化时调用
|
||
/// </summary>
|
||
public static void ClearChannelObjectsCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
_channelObjectsCache = null;
|
||
LogManager.Debug("通道对象缓存已清除");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置移动物体(用于从空间索引中排除移动物体及其所有子节点)
|
||
/// </summary>
|
||
/// <param name="animatedObject">移动物体</param>
|
||
public static void SetAnimatedObject(ModelItem animatedObject)
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
_animatedObject = animatedObject;
|
||
LogManager.Info($"移动物体已设置: {animatedObject?.DisplayName ?? "null"}");
|
||
|
||
// 🔥 关键:清除几何对象缓存,强制重新构建以排除移动物体
|
||
// 原因:BuildNonHidddenGeometryItemsCache 使用双重检查锁定,只构建一次
|
||
// 如果在第一次构建时移动物体为 null,摩托车会被添加到缓存
|
||
// 之后即使设置了移动物体,缓存也不会重新构建
|
||
_allGeometryItemsCache = null;
|
||
_nonChannelGeometryItemsCache = null;
|
||
LogManager.Info("几何对象缓存已清除,将重新构建以排除移动物体");
|
||
|
||
// 🔥 立即重新构建几何对象缓存(排除移动物体)
|
||
BuildNonHidddenGeometryItemsCache();
|
||
LogManager.Info("几何对象缓存已重新构建完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除移动物体引用
|
||
/// </summary>
|
||
public static void ClearAnimatedObject()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
_animatedObject = null;
|
||
LogManager.Debug("移动物体引用已清除");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化碰撞检测缓存(用于预计算前的准备工作)
|
||
/// </summary>
|
||
/// <param name="animatedObject">移动物体</param>
|
||
public static void InitializeCollisionDetectionCache(ModelItem animatedObject)
|
||
{
|
||
LogManager.Info("[碰撞检测] 开始初始化碰撞检测缓存...");
|
||
|
||
// 1. 清除所有缓存
|
||
ClearAllCaches();
|
||
|
||
// 2. 构建几何对象缓存(包含移动物体)
|
||
BuildNonHidddenGeometryItemsCache();
|
||
|
||
// 3. 构建通道对象缓存
|
||
Instance.BuildChannelObjectsCache();
|
||
|
||
// 4. 设置移动物体并重新构建几何对象缓存(排除移动物体)
|
||
SetAnimatedObject(animatedObject);
|
||
|
||
LogManager.Info("[碰撞检测] 碰撞检测缓存初始化完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取通道对象缓存(供外部使用)
|
||
/// </summary>
|
||
/// <returns>通道对象集合,如果缓存不存在则返回null</returns>
|
||
public static HashSet<ModelItem> GetChannelObjectsCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
return _channelObjectsCache;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建几何对象列表缓存,一次性获取非隐藏几何对象
|
||
/// </summary>
|
||
public static void BuildNonHidddenGeometryItemsCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
if (_allGeometryItemsCache != null) return; // 双重检查锁定
|
||
|
||
var cacheStopwatch = new System.Diagnostics.Stopwatch();
|
||
cacheStopwatch.Start();
|
||
|
||
try
|
||
{
|
||
// 优化方案:递归遍历 (Top-Down Traversal)
|
||
// 原因:Search API 的 Hidden 属性不检查父级可见性,且 IsHidden 属性检查自身。
|
||
// 解决方案:使用堆栈进行深度优先搜索(DFS),如果遭遇 Hidden 节点则停止深入该分支。
|
||
// 这天然地利用了 Navisworks 的层级可见性规则。
|
||
_allGeometryItemsCache = new ModelItemCollection();
|
||
int visibleCount = 0;
|
||
|
||
// 使用 Stack 模拟递归避免栈溢出,并保持对 .NET 版本的兼容性
|
||
// Stack 初始容量设为 1000 以减少扩容
|
||
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;
|
||
|
||
// 🔥 优化:排除移动物体及其所有子节点
|
||
// 算法原理:使用 continue 跳过当前节点,算法会自动跳过其所有子节点
|
||
// 因为对于复合对象和有几何体的节点,算法会添加到缓存但不继续遍历子节点
|
||
// 对于空节点才会继续遍历子节点,所以 continue 可以有效排除整个子树
|
||
if (_animatedObject != null && item.Equals(_animatedObject))
|
||
{
|
||
LogManager.Debug($"空间索引构建:跳过移动物体节点: {item.DisplayName}");
|
||
continue;
|
||
}
|
||
|
||
// 🔥 优化:只提取复合对象(IsComposite = true)
|
||
// 原因:复合对象(如窗户)包含多个实体节点(玻璃、框架、把手)
|
||
// 我们只需要检测复合对象,不需要检测其子节点
|
||
// Clash Detective API 会自动处理复合对象的子节点几何体
|
||
if (item.IsComposite)
|
||
{
|
||
// 复合对象:添加到缓存,不继续遍历子节点
|
||
_allGeometryItemsCache.Add(item);
|
||
visibleCount++;
|
||
}
|
||
else if (item.HasGeometry)
|
||
{
|
||
// 非复合对象但有几何数据(独立实体节点):添加到缓存
|
||
_allGeometryItemsCache.Add(item);
|
||
visibleCount++;
|
||
}
|
||
else
|
||
{
|
||
// 空节点:继续遍历子节点
|
||
foreach (var child in item.Children)
|
||
{
|
||
stack.Push(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
cacheStopwatch.Stop();
|
||
LogManager.Info($"复合对象列表缓存构建完成 (DFS遍历 + 自动剪枝),耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
|
||
LogManager.Info($" - 缓存可见对象: {visibleCount}");
|
||
LogManager.Info($" - 策略: 深度优先搜索 (父节点隐藏则跳过分支)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"构建几何对象列表缓存时发生错误: {ex.Message}", ex);
|
||
_allGeometryItemsCache = new ModelItemCollection(); // 创建空缓存,避免重复构建
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取复合对象缓存(供外部使用)
|
||
/// </summary>
|
||
/// <returns>复合对象集合,如果缓存不存在则返回null</returns>
|
||
public static ModelItemCollection GetAllGeometryItemsCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
return _allGeometryItemsCache;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取已排除通道对象的几何对象缓存(供空间索引使用)
|
||
/// </summary>
|
||
/// <returns>排除通道后的几何对象列表,如果缓存不存在则返回null</returns>
|
||
public static List<ModelItem> GetNonChannelGeometryItemsCache()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
// 如果已经有过滤后的缓存,直接返回
|
||
if (_nonChannelGeometryItemsCache != null)
|
||
{
|
||
return _nonChannelGeometryItemsCache;
|
||
}
|
||
|
||
// 如果原始缓存不存在,返回 null
|
||
if (_allGeometryItemsCache == null || _channelObjectsCache == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
// 第一次调用:构建并缓存过滤后的列表
|
||
LogManager.Info($"[空间索引] 构建非通道几何对象缓存...");
|
||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
_nonChannelGeometryItemsCache = _allGeometryItemsCache
|
||
.Where(item => !_channelObjectsCache.Contains(item))
|
||
.ToList();
|
||
|
||
sw.Stop();
|
||
LogManager.Info($"[空间索引] 非通道几何对象缓存构建完成,耗时: {sw.ElapsedMilliseconds}ms,对象数: {_nonChannelGeometryItemsCache.Count}");
|
||
|
||
return _nonChannelGeometryItemsCache;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除所有缓存,在模型变化时调用
|
||
/// </summary>
|
||
public static void ClearAllCaches()
|
||
{
|
||
lock (_cacheLock)
|
||
{
|
||
_channelObjectsCache = null;
|
||
_allGeometryItemsCache = null;
|
||
_nonChannelGeometryItemsCache = null;
|
||
_animatedObject = null; // 清除移动物体引用
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理碰撞缓存
|
||
/// </summary>
|
||
public void ClearCollisionCache()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[ClashDetectiveIntegration] 清理碰撞缓存");
|
||
|
||
// 清空当前碰撞列表
|
||
_currentCollisions?.Clear();
|
||
|
||
// 清空缓存的结果
|
||
lock (_resultsLock)
|
||
{
|
||
_deduplicatedCollisionResults?.Clear();
|
||
}
|
||
|
||
// 清除对象缓存
|
||
ClearAllCaches();
|
||
|
||
// 重置计数器
|
||
_animationCollisionCount = 0;
|
||
_clashDetectiveCollisionCount = 0;
|
||
|
||
LogManager.Info("[ClashDetectiveIntegration] 碰撞缓存已清理");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[ClashDetectiveIntegration] 清理碰撞缓存失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理资源
|
||
/// </summary>
|
||
public void Cleanup()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("开始Clash Detective集成资源清理...");
|
||
|
||
// 检查文档是否仍然有效,如果无效则跳过操作
|
||
var document = Application.ActiveDocument;
|
||
if (document == null || document.IsClear)
|
||
{
|
||
LogManager.Info("文档已无效,跳过资源清理操作");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
ModelHighlightHelper.ClearAllHighlights();
|
||
LogManager.Info("已清除临时材质高亮");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"清除临时材质失败: {ex.Message}");
|
||
}
|
||
|
||
// 清空内存中的结果
|
||
_currentCollisions?.Clear();
|
||
lock (_resultsLock)
|
||
{
|
||
_deduplicatedCollisionResults?.Clear();
|
||
}
|
||
|
||
// 清理.NET API引用
|
||
_documentClash = null;
|
||
|
||
LogManager.Info("Clash Detective集成资源清理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"清理Clash Detective资源时发生异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批处理版本:创建并运行ClashDetective测试(不触发UI操作)
|
||
/// 此方法专门用于批处理场景,不显示进度条,不触发UI刷新
|
||
/// </summary>
|
||
public string CreateAllAnimationCollisionTestsForBatch(
|
||
List<CollisionResult> precomputedCollisions,
|
||
double detectionGap,
|
||
string routeId,
|
||
ModelItem animatedObject,
|
||
bool isVirtualVehicle,
|
||
int frameRate,
|
||
double duration,
|
||
double virtualVehicleLength,
|
||
double virtualVehicleWidth,
|
||
double virtualVehicleHeight,
|
||
List<Point3D> pathPoints = null)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[批处理] 使用预计算碰撞数据创建ClashDetective测试(容差: {detectionGap}米)");
|
||
|
||
LogManager.Info($"[批处理] 共有 {precomputedCollisions.Count} 个碰撞记录");
|
||
|
||
// 记录动画过程中的碰撞数量
|
||
_animationCollisionCount = precomputedCollisions.Count;
|
||
LogManager.Info($"[批处理] 预计算碰撞: {_animationCollisionCount}");
|
||
|
||
// 调用公共方法运行ClashDetective测试并保存到数据库
|
||
var result = RunClashDetectiveTestsAndSaveToDatabase(
|
||
precomputedCollisions,
|
||
detectionGap,
|
||
routeId,
|
||
animatedObject,
|
||
isVirtualVehicle,
|
||
frameRate,
|
||
duration,
|
||
virtualVehicleLength,
|
||
virtualVehicleWidth,
|
||
virtualVehicleHeight,
|
||
null
|
||
);
|
||
|
||
var collisionGroup = result.collisionGroup;
|
||
var addedMainTest = result.addedMainTest;
|
||
var confirmedCount = result.confirmedCount;
|
||
|
||
// 添加分组到主测试(使用 TestsAddCopy 方法)
|
||
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
|
||
{
|
||
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
|
||
LogManager.Info($"[批处理] 碰撞分组已添加到主测试,共 {confirmedCount} 个碰撞");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[批处理] 分组为空(未检测到真实几何碰撞),未添加到主测试");
|
||
}
|
||
|
||
// 碰撞测试完成后,将物体恢复到路径起点位置(如果提供了路径点)
|
||
if (animatedObject != null && IsModelItemValid(animatedObject) && pathPoints != null && pathPoints.Count > 0)
|
||
{
|
||
try
|
||
{
|
||
PathAnimationManager.GetInstance().MoveVehicleToPathStart(animatedObject, pathPoints);
|
||
LogManager.Info($"[批处理] 已将 {animatedObject.DisplayName} 恢复到路径起点位置");
|
||
}
|
||
catch (Exception restoreEx)
|
||
{
|
||
LogManager.Error($"[批处理] 恢复物体到路径起点失败: {restoreEx.Message}");
|
||
}
|
||
}
|
||
|
||
return addedMainTest?.DisplayName;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[批处理] 创建ClashDetective测试失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发碰撞检测事件
|
||
/// </summary>
|
||
private void OnCollisionDetected(CollisionDetectedEventArgs e)
|
||
{
|
||
CollisionDetected?.Invoke(this, e);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞结果数据结构
|
||
/// </summary>
|
||
public class CollisionResult : IEquatable<CollisionResult>
|
||
{
|
||
public Guid ClashGuid { get; set; }
|
||
public string DisplayName { get; set; }
|
||
public ClashResultStatus Status { get; set; }
|
||
public string GridLocation { get; set; }
|
||
public ModelItem Item1 { get; set; }
|
||
public ModelItem Item2 { get; set; }
|
||
public Point3D Center { get; set; }
|
||
public double Distance { get; set; }
|
||
public DateTime CreatedTime { get; set; }
|
||
|
||
// 位置信息用于恢复测试
|
||
public Point3D Item1Position { get; set; }
|
||
public Point3D Item2Position { get; set; }
|
||
public bool HasPositionInfo { get; set; }
|
||
|
||
// IEquatable<CollisionResult> 实现:基于碰撞对象进行去重
|
||
public bool Equals(CollisionResult other)
|
||
{
|
||
if (other == null) return false;
|
||
|
||
// 使用 ModelItem.Equals 比较底层原生对象
|
||
bool item1Equal = (Item1 == null && other.Item1 == null) ||
|
||
(Item1 != null && Item1.Equals(other.Item1));
|
||
bool item2Equal = (Item2 == null && other.Item2 == null) ||
|
||
(Item2 != null && Item2.Equals(other.Item2));
|
||
|
||
return item1Equal && item2Equal;
|
||
}
|
||
|
||
public override bool Equals(object obj)
|
||
{
|
||
return Equals(obj as CollisionResult);
|
||
}
|
||
|
||
public override int GetHashCode()
|
||
{
|
||
unchecked
|
||
{
|
||
int hash = 17;
|
||
hash = hash * 31 + (Item1?.GetHashCode() ?? 0);
|
||
hash = hash * 31 + (Item2?.GetHashCode() ?? 0);
|
||
return hash;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞检测事件参数
|
||
/// </summary>
|
||
public class CollisionDetectedEventArgs : EventArgs
|
||
{
|
||
public List<CollisionResult> Results { get; private set; }
|
||
public int CollisionCount { get; private set; }
|
||
|
||
public CollisionDetectedEventArgs(List<CollisionResult> results)
|
||
{
|
||
Results = results;
|
||
CollisionCount = results.Count;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// ClashDetective结果保存事件参数
|
||
/// </summary>
|
||
public class ClashDetectiveResultSavedEventArgs : EventArgs
|
||
{
|
||
public string PathName { get; private set; }
|
||
public string TestName { get; private set; }
|
||
public int CollisionCount { get; private set; }
|
||
|
||
public ClashDetectiveResultSavedEventArgs(string pathName, string testName, int collisionCount)
|
||
{
|
||
PathName = pathName;
|
||
TestName = testName;
|
||
CollisionCount = collisionCount;
|
||
}
|
||
}
|
||
} |