NavisworksTransport/src/Core/Animation/PathAnimationManager.cs

3036 lines
128 KiB
C#
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.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Core.Config;
using NavisworksTransport.Core.Spatial;
using NavisworksTransport.Utils;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.Core.Animation
{
/// <summary>
/// 定义动画播放的状态
/// </summary>
public enum AnimationState
{
Idle, // 空闲,未生成动画
Ready, // 已就绪,动画已生成但未播放
Playing, // 播放中
Paused, // 暂停
Stopped, // 已停止
Finished // 已完成
}
/// <summary>
/// 碰撞对象对,用于去重
/// </summary>
public class CollisionPair : IEquatable<CollisionPair>
{
public ModelItem AnimatedObject { get; set; }
public ModelItem CollisionObject { get; set; }
public bool Equals(CollisionPair other)
{
if (other == null) return false;
return ReferenceEquals(AnimatedObject, other.AnimatedObject) &&
ReferenceEquals(CollisionObject, other.CollisionObject);
}
public override bool Equals(object obj)
{
return Equals(obj as CollisionPair);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + (AnimatedObject?.GetHashCode() ?? 0);
hash = hash * 31 + (CollisionObject?.GetHashCode() ?? 0);
return hash;
}
}
}
/// <summary>
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
/// 注意由于Navisworks API限制无法直接使用Animator API因此使用OverridePermanentTransform实现动画
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
/// </summary>
/// <summary>
/// 动画帧数据(包含位置、朝向和碰撞信息)
/// </summary>
public class AnimationFrame
{
public int Index { get; set; } // 帧索引
public double Progress { get; set; } // 进度(0-1)
public Point3D Position { get; set; } // 该帧的位置
public double YawRadians { get; set; } // 绕Z轴的偏航角弧度
public List<CollisionResult> Collisions { get; set; } // 该帧的碰撞结果
public bool HasCollision => Collisions?.Count > 0;
public AnimationFrame()
{
Collisions = new List<CollisionResult>();
YawRadians = 0.0;
}
}
public class PathAnimationManager
{
private static PathAnimationManager _instance;
private static readonly HashSet<string> _completedCollisionTests = new HashSet<string>(); // 记录已完成碰撞检测的动画配置
private static readonly Dictionary<string, List<AnimationFrame>> _animationFrameCache = new Dictionary<string, List<AnimationFrame>>(); // 动画帧缓存
private static readonly Dictionary<string, List<CollisionResult>> _collisionResultCache = new Dictionary<string, List<CollisionResult>>(); // 碰撞结果缓存
private ModelItem _animatedObject;
private bool _isVirtualVehicle = false; // 是否使用虚拟车辆
private double _virtualVehicleLength = 0; // 虚拟车辆长度(米)
private double _virtualVehicleWidth = 0; // 虚拟车辆宽度(米)
private double _virtualVehicleHeight = 0; // 虚拟车辆高度(米)
private List<Point3D> _pathPoints;
private List<ModelItem> _manualCollisionTargets = new List<ModelItem>();
private bool _manualCollisionOverrideEnabled = false;
// === 角度修正 ===
private double _objectRotationCorrection = 0.0; // 物体角度修正值(度,顺时针)
// === 碰撞排除列表缓存管理从LogisticsAnimationManager迁移===
private ModelItem _currentCachedAnimationObject;
private List<ModelItem> _cachedExclusionList;
private DateTime _cacheCreatedTime;
private readonly TimeSpan _cacheValidDuration = TimeSpan.FromMinutes(30); // 缓存有效期30分钟
// === 预计算动画系统 ===
private List<AnimationFrame> _animationFrames; // 所有帧数据
private int _currentFrameIndex = 0; // 当前帧索引
private List<CollisionResult> _allCollisionResults; // 所有碰撞结果(不去重)
private bool _lastHighlightState = false; // 上一帧的高亮状态
private HashSet<ModelItem> _lastCollisionObjects = new HashSet<ModelItem>(); // 上一帧碰撞对象的集合
// === 路径相关 ===
private PathRoute _route = null; // 路径引用
// === 碰撞测试优化 ===
private string _currentAnimationHash; // 当前动画配置的哈希值
// === 动画播放机制 ===
private double _frameInterval; // 帧间隔(毫秒)
private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间
private DispatcherTimer _animationTimer; // 备用DispatcherTimer定时器
// === 动画参数 ===
private double _animationDuration; // 动画总时长(秒)
private DateTime _animationStartTime;
private int _animationFrameRate; // 动画帧率默认30FPS
private double _collisionDetectionAccuracy; // 检测精度(内部存储:模型单位/帧)
private double _movementSpeed; // 运动速度(仅用于显示,单位:米/秒)
private double _detectionGap; // 检测间隙(内部存储:模型单位)
private double _precomputedDetectionGap; // 预计算时使用的扩大检测间隙(内部存储:模型单位)
private string _pathName; // 路径名称
private string _currentRouteId; // 当前路由ID
// === 双向播放和步进控制 ===
private int _playbackDirection = 1; // 播放方向1=正向,-1=反向
private double _playbackSpeed = 1.0; // 播放速度倍率
// === 性能监控 ===
private int _fpsFrameCount = 0;
private DateTime _fpsCounterStart = DateTime.Now;
private double _actualFPS = 0;
private Transform3D _originalTransform;
private Point3D _originalCenter; // 存储部件的原始中心位置
private Point3D _currentPosition; // 存储部件的当前位置
private AnimationState _currentState = AnimationState.Idle;
private double _pausedProgress = 0.0; // 暂停时的进度0-1之间
private double _currentYaw = 0.0; // 当前偏航角(弧度)
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
private string _currentTaskId;
// === 调试辅助 ===
private static HashSet<int> _arcInfoPrinted = new HashSet<int>(); // 记录已打印信息的圆弧段索引
/// <summary>
/// 当前正在进行动画的对象
/// </summary>
public ModelItem AnimatedObject => _animatedObject;
// --- 新增事件 ---
/// <summary>
/// 当动画状态发生改变时触发
/// </summary>
public event EventHandler<AnimationState> StateChanged;
/// <summary>
/// 当动画进度更新时触发 (0.0-100.0)
/// </summary>
public event EventHandler<double> ProgressChanged;
public PathAnimationManager()
{
_pathPoints = new List<Point3D>();
_animationFrames = new List<AnimationFrame>();
_allCollisionResults = new List<CollisionResult>();
// 🔥 从配置初始化动画参数
var config = ConfigManager.Instance.Current;
_animationFrameRate = config.Animation.FrameRate;
_animationDuration = config.Animation.DurationSeconds;
var detectionGapMeters = config.Animation.DetectionGapMeters;
_detectionGap = UnitsConverter.ConvertFromMeters(detectionGapMeters);
_precomputedDetectionGap = _detectionGap;
// 初始化动画模式
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
_lastFrameTime = DateTime.MinValue;
_fpsCounterStart = DateTime.Now;
// 初始化备用DispatcherTimer
_animationTimer = new DispatcherTimer(DispatcherPriority.Render)
{
Interval = TimeSpan.FromMilliseconds(_frameInterval)
};
_animationTimer.Tick += OnTimerTick;
// 初始化 TimeLiner 集成
try
{
_timeLinerManager = new TimeLinerIntegrationManager();
LogManager.Info($"PathAnimationManager 初始化完成 - DispatcherTimer模式, 目标FPS: {_animationFrameRate}");
}
catch (Exception ex)
{
LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}");
_timeLinerManager = null;
}
// 订阅文档状态事件
DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated;
}
/// <summary>
/// 获取单例实例
/// </summary>
public static PathAnimationManager GetInstance()
{
if (_instance == null)
{
_instance = new PathAnimationManager();
}
return _instance;
}
/// <summary>
/// 清理当前所有的动画生成结果(帧列表、碰撞结果、状态)
/// </summary>
public void ClearAnimationResults()
{
_animationFrames = null;
_allCollisionResults = null;
_arcInfoPrinted.Clear(); // 清除圆弧段信息打印记录
SetState(AnimationState.Idle);
LogManager.Info("[PathAnimationManager] 动画数据和状态已重置为 Idle");
}
/// <summary>
/// 仅仅将物理模型同步到路径起点(不清理数据,不重置状态)
/// 用于加载碰撞检测结果时
/// </summary>
public void SyncToPathStart(ModelItem item, List<Point3D> points)
{
MoveVehicleToPathStart(item, points);
}
/// <summary>
/// 将动画对象彻底恢复到 CAD 原始位置(清除所有覆盖变换)
/// 常用于模式切换或彻底清除动画干扰
/// </summary>
public void RestoreObjectToCADPosition()
{
try
{
var doc = NavisApplication.ActiveDocument;
if (doc == null || _animatedObject == null) return;
var modelItems = new ModelItemCollection { _animatedObject };
// 1. 彻底清除 Navisworks 中的所有覆盖变换,强制恢复到设计文件定义的原始状态
doc.Models.ResetPermanentTransform(modelItems);
// 2. 重置内部跟踪变量(同步到 CAD 原始状态)
// 注意ResetPermanentTransform 后物体的 Transform 属性会自动变回 CAD 原始值
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_animatedObject.Transform);
var originalBoundingBox = _animatedObject.BoundingBox();
_currentPosition = new Point3D(
originalBoundingBox.Center.X,
originalBoundingBox.Center.Y,
originalBoundingBox.Min.Z
);
LogManager.Info($"[归位] 物体 {_animatedObject.DisplayName} 已彻底恢复到 CAD 原始位置, yaw={_currentYaw:F3}");
}
catch (Exception ex)
{
LogManager.Error($"[归位] 恢复物体到 CAD 位置失败: {ex.Message}");
}
}
/// <summary>
/// 生成动画帧
/// </summary>
/// <param name="animatedObject">要动画化的模型对象</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public void SetRoute(PathRoute route)
{
_route = route;
}
public void SetupAnimation(
ModelItem animatedObject,
double durationSeconds,
PathRoute route)
{
try
{
if (route == null)
throw new ArgumentNullException(nameof(route), "必须提供路径");
_animatedObject = animatedObject;
_animationDuration = durationSeconds;
_route = route;
LogManager.Info($"使用路径,边数:{route.Edges.Count},总长度:{route.TotalLength:F2}米,动画时长:{durationSeconds:F1}秒");
// 计算动画配置哈希(基于路径边,可以区分不同的曲线化路径)
_currentAnimationHash = ComputeAnimationConfigHashFromRoute(animatedObject, route);
LogManager.Info($"动画配置哈希: {_currentAnimationHash}");
// 检查动画帧缓存
if (_animationFrameCache.TryGetValue(_currentAnimationHash, out var cachedFrames))
{
_animationFrames = cachedFrames;
// 同时加载缓存的碰撞结果
if (_collisionResultCache.TryGetValue(_currentAnimationHash, out var cachedCollisions))
{
_allCollisionResults = cachedCollisions;
LogManager.Info($"使用缓存的动画帧和碰撞结果,帧数: {_animationFrames.Count}, 碰撞数: {_allCollisionResults.Count}");
}
else
{
_allCollisionResults = new List<CollisionResult>();
LogManager.Info($"使用缓存的动画帧,但碰撞结果缓存缺失,帧数: {_animationFrames.Count}");
}
}
else
{
// 缓存未命中,重新计算动画帧
LogManager.Info("缓存未命中,重新计算动画帧和碰撞结果");
PrecomputeAnimationFrames();
// 缓存动画帧和碰撞结果
if (_animationFrames != null && _animationFrames.Count > 0)
{
_animationFrameCache[_currentAnimationHash] = _animationFrames;
if (_allCollisionResults != null)
{
_collisionResultCache[_currentAnimationHash] = _allCollisionResults;
}
LogManager.Info($"动画帧和碰撞结果已缓存,帧数: {_animationFrames.Count}, 碰撞数: {_allCollisionResults?.Count ?? 0}");
}
}
// 验证预计算结果
if (_animationFrames == null || _animationFrames.Count == 0)
{
throw new InvalidOperationException("动画帧预计算失败,无法创建动画");
}
LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 时长={_animationDuration}秒");
}
catch (Exception ex)
{
LogManager.Error($"设置动画失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 将车辆移动到路径起点
/// </summary>
/// <param name="animatedObject">动画对象可选如果不提供则使用当前的_animatedObject</param>
/// <param name="pathPoints">路径点可选如果不提供则使用当前的_pathPoints</param>
public void MoveVehicleToPathStart(ModelItem animatedObject = null, List<Point3D> pathPoints = null)
{
try
{
// 如果提供了参数,更新内部状态
if (animatedObject != null)
{
_animatedObject = animatedObject;
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_currentPosition = new Point3D(_originalCenter.X, _originalCenter.Y, animatedObject.BoundingBox().Min.Z);
// 统一逻辑:从当前 Transform 中提取实际朝向(无论虚拟车辆还是普通物体)
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
LogManager.Debug($"[MoveVehicleToPathStart初始化] 当前实际yaw={_currentYaw * 180 / Math.PI:F2}度, _isVirtualVehicle={_isVirtualVehicle}");
}
if (pathPoints != null)
{
_pathPoints = pathPoints;
}
// 检查路径点
if (_pathPoints == null || _pathPoints.Count < 2)
{
LogManager.Warning("[移动到起点] 没有可用的路径点");
return;
}
// 计算朝向(使用前两个路径点的方向)
double pathDirectionYaw = Math.Atan2(_pathPoints[1].Y - _pathPoints[0].Y, _pathPoints[1].X - _pathPoints[0].X);
double yaw;
LogManager.Info($"[移动到起点] 路径方向yaw: {pathDirectionYaw * 180 / Math.PI:F2}度, 角度修正: {_objectRotationCorrection:F1}度");
// 根据路径类型调整朝向
if (_route?.PathType == PathType.Aerial && _route.AerialSubType == AerialSubType.ThreeStepHoisting)
{
// 吊装路径:使用水平吊运方向(与通行空间方向一致)
// 从第2个路径点提升点到第3个路径点平移终点的水平方向
yaw = Math.Atan2(_pathPoints[2].Y - _pathPoints[1].Y, _pathPoints[2].X - _pathPoints[1].X);
LogManager.Debug($"[移动到起点] 吊装路径使用水平吊运方向: {yaw * 180 / Math.PI:F2}度");
}
else
{
// 地面路径和空轨路径:直接使用路径方向
yaw = pathDirectionYaw;
LogManager.Debug($"[移动到起点] 地面/空轨路径使用路径方向: {yaw * 180 / Math.PI:F2}度");
}
// 应用角度修正值(顺时针,转换为弧度)
if (_objectRotationCorrection != 0.0)
{
double correctionRad = _objectRotationCorrection * Math.PI / 180.0;
yaw += correctionRad;
LogManager.Debug($"[移动到起点] 应用角度修正: {_objectRotationCorrection:F1}°, 修正后yaw: {yaw * 180 / Math.PI:F2}度");
}
// 根据路径类型调整起点位置
Point3D startPosition = _pathPoints[0];
if (_route?.PathType == PathType.Aerial)
{
if (_route.AerialSubType == AerialSubType.ThreeStepHoisting)
{
// 吊装路径:第一个路径点(起吊点)是地面位置,物体底面应该在这里
// 不需要向下移动车辆高度
LogManager.Debug($"[移动到起点] 吊装路径起吊点是地面位置物体底面Z={startPosition.Z:F2}");
}
else if (_route.AerialSubType == AerialSubType.Rail)
{
// 空轨路径:路径点是悬挂点,物体悬挂在下方
double vehicleHeight = _animatedObject.BoundingBox().Max.Z - _animatedObject.BoundingBox().Min.Z;
startPosition = new Point3D(startPosition.X, startPosition.Y, startPosition.Z - vehicleHeight);
LogManager.Debug($"[移动到起点] 空轨路径调整: 悬挂点Z={_pathPoints[0].Z:F2}, 物体底面Z={startPosition.Z:F2}, 物体高度={vehicleHeight:F2}");
}
}
else
{
// 地面路径points[0] 是地面位置,车辆底面应该在这里
LogManager.Debug($"[移动到起点] 地面路径: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})");
}
// 使用 UpdateObjectPosition 统一处理移动和旋转
UpdateObjectPosition(startPosition, yaw);
string pathTypeName;
if (_route?.PathType == PathType.Aerial)
{
pathTypeName = _route.AerialSubType == AerialSubType.Rail ? "空轨" : "吊装";
}
else
{
pathTypeName = "地面";
}
LogManager.Info($"物体已初始化到路径起点并对齐朝向: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), yaw={yaw:F3}rad, 路径类型={pathTypeName}");
}
catch (Exception ex)
{
LogManager.Error($"初始化物体到路径起点失败: {ex.Message}");
}
}
/// <summary>
/// 预计算所有动画帧和碰撞信息
/// </summary>
private void PrecomputeAnimationFrames()
{
try
{
LogManager.Info("=== 使用路径预计算动画帧 ===");
int totalFrames = (int)(_animationDuration * _animationFrameRate);
// 🔥 判断路径类型空中路径使用Points地面路径使用Edges
bool isAerialPath = _route.PathType == PathType.Aerial;
List<Point3D> allSampledPoints = new List<Point3D>();
List<double> segmentLengths = new List<double>();
if (isAerialPath)
{
// === 空中路径:直接使用路径点进行线性插值 ===
string aerialSubTypeName = _route.AerialSubType == AerialSubType.Rail ? "空轨" : "吊装";
LogManager.Info($"[{aerialSubTypeName}路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}");
// 将路径点转换为Point3D列表
foreach (var point in _route.Points)
{
allSampledPoints.Add(point.Position);
}
// 计算每段长度(内部计算统一使用模型单位)
for (int i = 0; i < allSampledPoints.Count - 1; i++)
{
double lengthInModelUnits = GeometryHelper.CalculatePointDistance(allSampledPoints[i], allSampledPoints[i + 1]);
segmentLengths.Add(lengthInModelUnits);
}
}
else
{
// === 地面路径使用Edges进行曲线化插值 ===
var edgeStartIndices = new List<int>();
foreach (var edge in _route.Edges)
{
if (edge.SampledPoints == null || edge.SampledPoints.Count == 0)
{
var samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep;
edge.SampledPoints = PathCurveEngine.GenerateSampledPoints(edge, samplingStep);
}
edgeStartIndices.Add(allSampledPoints.Count);
allSampledPoints.AddRange(edge.SampledPoints);
segmentLengths.Add(edge.PhysicalLength);
}
}
// 🔥 内部计算统一使用模型单位
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
double totalLengthInModelUnits = _route.TotalLength * metersToModelUnits;
string pathTypeName;
if (_route.PathType == PathType.Aerial)
{
pathTypeName = _route.AerialSubType == AerialSubType.Rail ? "空轨" : "吊装";
}
else
{
pathTypeName = "地面";
}
LogManager.Info($"路径总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {pathTypeName}");
// 2. 按帧数采样生成动画帧
_animationFrames = new List<AnimationFrame>();
_allCollisionResults = new List<CollisionResult>();
_currentFrameIndex = 0;
// 获取动画对象的包围盒信息
var originalBoundingBox = _animatedObject.BoundingBox();
var boundingBoxSize = new Vector3D(
originalBoundingBox.Max.X - originalBoundingBox.Min.X,
originalBoundingBox.Max.Y - originalBoundingBox.Min.Y,
originalBoundingBox.Max.Z - originalBoundingBox.Min.Z
);
// 空间索引设置
SpatialIndexManager spatialIndexManager = null;
double searchRadiusInModelUnits = 0;
var manualTargetsSnapshot = new List<ModelItem>();
bool manualOverrideActive = _manualCollisionOverrideEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0;
if (manualOverrideActive)
{
manualTargetsSnapshot = _manualCollisionTargets
.Where(item => item != null && ModelItemAnalysisHelper.IsModelItemValid(item) && !ReferenceEquals(item, _animatedObject))
.Distinct()
.ToList();
if (manualTargetsSnapshot.Count == 0)
{
manualOverrideActive = false;
LogManager.Warning("[手工碰撞] 手工碰撞对象列表为空或无效,自动回退到空间索引模式");
}
else
{
LogManager.Info($"[手工碰撞] 启用手工碰撞对象模式,数量: {manualTargetsSnapshot.Count},跳过空间索引构建");
}
}
if (!manualOverrideActive)
{
LogManager.Info("=== 构建全局空间索引 ===");
// 🔥 设置移动物体到 ClashDetectiveIntegration以便从空间索引中排除
ClashDetectiveIntegration.SetAnimatedObject(_animatedObject);
spatialIndexManager = SpatialIndexManager.Instance;
// 使用动画专用的空间索引格子大小配置
double cellSizeInModelUnits = UnitsConverter.ConvertFromMeters(ConfigManager.Instance.Current.Animation.SpatialIndexCellSize);
LogManager.Info($"[空间索引] 格子大小: {cellSizeInModelUnits:F2}模型单位 (动画配置)");
// 检查空间索引是否需要重新构建(格子大小变化或未初始化)
bool needRebuild = !spatialIndexManager.IsInitialized ||
Math.Abs(spatialIndexManager.CellSize - cellSizeInModelUnits) > 0.0001;
if (needRebuild)
{
LogManager.Info("[空间索引] 空间索引未初始化或格子大小已改变,开始重新构建...");
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
}
else
{
LogManager.Info("[空间索引] 使用已缓存的空间索引");
}
LogManager.Info(spatialIndexManager.GetStatistics());
// 🔥 修改:使用车辆的最大水平尺寸而不是对角线
// 原因:车辆在通道上水平移动,主要检测侧面碰撞
double maxHorizontalDimension = Math.Max(boundingBoxSize.X, boundingBoxSize.Y);
searchRadiusInModelUnits = maxHorizontalDimension + _precomputedDetectionGap;
LogManager.Info($"空间查询半径: {searchRadiusInModelUnits:F2} 模型单位 (基于最大水平尺寸: {maxHorizontalDimension:F2})");
}
// 3. 生成每一帧
double distancePerFrameInModelUnits = totalLengthInModelUnits / totalFrames;
LogManager.Info($"总帧数: {totalFrames}, 总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 每帧移动距离: {distancePerFrameInModelUnits / metersToModelUnits:F4}米");
for (int i = 0; i < totalFrames; i++)
{
double targetDistance = i * distancePerFrameInModelUnits; // 按距离采样(模型单位),不是按进度
Point3D framePosition;
double yawRadians;
if (isAerialPath)
{
// === 空中路径:在路径点之间进行线性插值 ===
int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths);
if (segmentIndex < 0 || segmentIndex >= segmentLengths.Count)
{
string subTypeName = _route.AerialSubType == AerialSubType.Rail ? "空轨" : "吊装";
LogManager.Warning($"[{subTypeName}路径] 无法找到线段,目标距离:{targetDistance / metersToModelUnits:F2}米");
continue;
}
double accumulatedLength = segmentLengths.Take(segmentIndex).Sum();
double segmentProgress = (targetDistance - accumulatedLength) / segmentLengths[segmentIndex];
// 线性插值
Point3D p1 = allSampledPoints[segmentIndex];
Point3D p2 = allSampledPoints[segmentIndex + 1];
framePosition = new Point3D(
p1.X + segmentProgress * (p2.X - p1.X),
p1.Y + segmentProgress * (p2.Y - p1.Y),
p1.Z + segmentProgress * (p2.Z - p1.Z)
);
// 计算朝向(使用当前线段的方向)
yawRadians = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
// 吊装路径所有线段都使用水平吊运方向与MoveVehicleToPathStart保持一致
if (_route.AerialSubType == AerialSubType.ThreeStepHoisting)
{
yawRadians = Math.Atan2(_pathPoints[2].Y - _pathPoints[1].Y, _pathPoints[2].X - _pathPoints[1].X);
}
// 🔥 空中路径:根据路径类型和线段索引调整物体位置
double vehicleHeight = _animatedObject.BoundingBox().Max.Z - _animatedObject.BoundingBox().Min.Z;
if (_route.AerialSubType == AerialSubType.ThreeStepHoisting)
{
// 吊装路径有4个路径点3个线段
// 路径点0起吊点地面位置物体底面应该在这里
// 路径点1提升点悬挂点物体顶面应该在这里
// 路径点2平移终点悬挂点物体顶面应该在这里
// 路径点3下降点地面位置物体底面应该在这里
//
// 线段0起吊段地面→悬挂点
// 线段1平移段悬挂点→悬挂点
// 线段2下降段悬挂点→地面
if (segmentIndex == 0)
{
// 起吊段:从地面逐渐上升到悬挂点-车辆高度
// 进度0时地面物体底面在地面不向下移动
// 进度1时悬挂点物体顶面在悬挂点物体底面=悬挂点-车辆高度
double heightOffset = segmentProgress * vehicleHeight;
framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - heightOffset);
LogManager.Debug($"[吊装路径-起吊段] 进度={segmentProgress:F2}, 高度偏移={heightOffset:F2}, 物体底面Z={framePosition.Z:F2}");
}
else if (segmentIndex == 1)
{
// 平移段:全程都是悬挂点,物体顶面在悬挂点,物体底面=悬挂点-车辆高度
framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - vehicleHeight);
LogManager.Debug($"[吊装路径-平移段] 进度={segmentProgress:F2}, 物体底面Z={framePosition.Z:F2}");
}
else if (segmentIndex == 2)
{
// 下降段:从悬挂点-车辆高度逐渐下降到地面
// 进度0时悬挂点物体顶面在悬挂点物体底面=悬挂点-车辆高度
// 进度1时地面物体底面在地面不向下移动
double heightOffset = (1 - segmentProgress) * vehicleHeight;
framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - heightOffset);
LogManager.Debug($"[吊装路径-下降段] 进度={segmentProgress:F2}, 高度偏移={heightOffset:F2}, 物体底面Z={framePosition.Z:F2}");
}
}
else if (_route.AerialSubType == AerialSubType.Rail)
{
// 空轨路径:路径点是悬挂点,物体悬挂在下方
// 物体顶面应该在路径点,所以物体底面 = 路径点 - 物体高度
framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - vehicleHeight);
LogManager.Debug($"[空轨路径] 调整物体位置: 悬挂点Z={framePosition.Z + vehicleHeight:F2}, 物体底面Z={framePosition.Z:F2}");
}
}
else
{
// === 地面路径:在边内插值 ===
int edgeIndex = FindEdgeForDistance(targetDistance, segmentLengths);
if (edgeIndex < 0 || edgeIndex >= _route.Edges.Count)
{
LogManager.Warning($"无法找到边,目标距离:{targetDistance / metersToModelUnits:F2}米");
continue;
}
var edge = _route.Edges[edgeIndex];
double accumulatedLength = segmentLengths.Take(edgeIndex).Sum();
double edgeProgress = (targetDistance - accumulatedLength) / edge.PhysicalLength;
// 在边内插值位置
if (edge.SegmentType == PathSegmentType.Straight)
{
framePosition = InterpolateOnStraightEdge(edge, edgeProgress);
}
else // Arc
{
framePosition = InterpolateOnArcEdge(edge, edgeProgress);
}
yawRadians = ComputeYawOnPath(i, allSampledPoints, edgeIndex, edgeProgress);
}
// 创建帧并检测碰撞
var frame = new AnimationFrame
{
Index = i,
Progress = (double)i / (totalFrames - 1),
Position = framePosition,
YawRadians = yawRadians,
Collisions = new List<CollisionResult>()
};
// 记录第一帧的角度(用于调试)
if (i == 0)
{
LogManager.Debug($"[预计算] 第0帧: pos=({framePosition.X:F2},{framePosition.Y:F2}), yaw={yawRadians * 180 / Math.PI:F2}度");
}
// 虚拟碰撞检测
// 计算车辆包围盒相对于framePosition的偏移
var currentVehicleBoundingBox = _animatedObject.BoundingBox();
var originalCenter = new Point3D(
(currentVehicleBoundingBox.Min.X + currentVehicleBoundingBox.Max.X) / 2,
(currentVehicleBoundingBox.Min.Y + currentVehicleBoundingBox.Max.Y) / 2,
(currentVehicleBoundingBox.Min.Z + currentVehicleBoundingBox.Max.Z) / 2
);
// 计算偏移量当前包围盒中心到framePosition的偏移
var offsetX = framePosition.X - originalCenter.X;
var offsetY = framePosition.Y - originalCenter.Y;
var offsetZ = framePosition.Z - currentVehicleBoundingBox.Min.Z; // Z方向framePosition是底面originalCenter是中心
// 创建新的包围盒将当前包围盒移动到framePosition
var virtualBoundingBox = new BoundingBox3D(
new Point3D(
currentVehicleBoundingBox.Min.X + offsetX,
currentVehicleBoundingBox.Min.Y + offsetY,
framePosition.Z // 底面Z坐标
),
new Point3D(
currentVehicleBoundingBox.Max.X + offsetX,
currentVehicleBoundingBox.Max.Y + offsetY,
framePosition.Z + (currentVehicleBoundingBox.Max.Z - currentVehicleBoundingBox.Min.Z) // 保持高度
)
);
IEnumerable<ModelItem> nearbyObjects;
if (manualOverrideActive)
{
nearbyObjects = manualTargetsSnapshot;
}
else
{
nearbyObjects = spatialIndexManager.FindNearbyObjects(
framePosition,
searchRadiusInModelUnits,
excludeObject: _animatedObject
);
}
foreach (var collider in nearbyObjects)
{
var colliderBox = collider.BoundingBox();
// 使用包围盒距离检测方法
double distance = BoundingBoxGeometryUtils.CalculateDistance(virtualBoundingBox, colliderBox);
bool intersects = distance <= _precomputedDetectionGap;
if (intersects)
{
LogManager.Debug($"帧 {i} 检测到碰撞: {_animatedObject.DisplayName} <-> {collider.DisplayName}, 距离: {distance:F4},阈值: {_precomputedDetectionGap:F4}");
LogManager.Debug($"移动物体位置: {framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}");
LogManager.Debug($"被撞物体位置:{GetObjectPosition(collider).X:F2},{GetObjectPosition(collider).Y:F2},{GetObjectPosition(collider).Z:F2}");
var collisionResult = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
DisplayName = $"预计算碰撞: {_animatedObject.DisplayName} <-> {collider.DisplayName}",
Status = ClashResultStatus.New,
Item1 = _animatedObject,
Item2 = collider,
CreatedTime = DateTime.Now,
Distance = BoundingBoxGeometryUtils.CalculateCenterDistance(virtualBoundingBox, colliderBox),
Center = new Point3D(
(virtualBoundingBox.Min.X + virtualBoundingBox.Max.X + colliderBox.Min.X + colliderBox.Max.X) / 4,
(virtualBoundingBox.Min.Y + virtualBoundingBox.Max.Y + colliderBox.Min.Y + colliderBox.Max.Y) / 4,
(virtualBoundingBox.Min.Z + virtualBoundingBox.Max.Z + colliderBox.Min.Z + colliderBox.Max.Z) / 4
),
// 🔥 重要:记录虚拟车辆的包围盒中心,而不是底面中心
// 原因:
// 1. framePosition是路径点代表车辆在地面上的位置底面中心
// 2. ClashDetective移动逻辑使用包围盒中心进行定位
// 3. 如果记录底面中心ClashDetective会把包围盒中心移动到底面位置导致车辆下沉半个高度
// 4. 记录包围盒中心可以确保ClashDetective移动后的位置与预计算时的虚拟车辆位置一致
Item1Position = new Point3D(
framePosition.X,
framePosition.Y,
framePosition.Z + (virtualBoundingBox.Max.Z - virtualBoundingBox.Min.Z) / 2
),
Item2Position = GetObjectPosition(collider),
HasPositionInfo = true
};
frame.Collisions.Add(collisionResult);
_allCollisionResults.Add(collisionResult);
}
}
_animationFrames.Add(frame);
}
// 统计碰撞信息
var framesWithCollision = _animationFrames.Count(f => f.HasCollision);
var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count);
LogManager.Info($"=== 预计算完成 ===");
LogManager.Info($"总帧数: {_animationFrames.Count}");
LogManager.Info($"包含碰撞的帧: {framesWithCollision}");
LogManager.Info($"总碰撞次数: {totalCollisions}");
LogManager.Info($"记录的碰撞结果总数: {_allCollisionResults.Count} 个");
// 🔥 清除移动物体集合
ClashDetectiveIntegration.ClearAnimatedObject();
}
catch (Exception ex)
{
LogManager.Error($"预计算动画帧失败: {ex.Message}");
_animationFrames = new List<AnimationFrame>();
}
}
/// <summary>
/// 根据路径计算指定帧的yaw角度
/// </summary>
/// <summary>
/// 计算路径上的朝向(改进版)
/// </summary>
private double ComputeYawOnPath(
int frameIndex,
List<Point3D> allSampledPoints,
int currentEdgeIndex,
double edgeProgress)
{
var edge = _route.Edges[currentEdgeIndex];
if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
{
// 圆弧段: 使用切线法计算精确朝向
Vector3D startVec = (edge.Trajectory.Ts - edge.Trajectory.ArcCenter).Normalize();
double signedAngle = edge.Trajectory.DeflectionAngle;
double currentAngle = edgeProgress * signedAngle;
// 计算切点Ts的朝向进入切点的切线方向
Vector3D tsRadiusVec = (edge.Trajectory.Ts - edge.Trajectory.ArcCenter).Normalize();
Vector3D rotationAxis = GetArcRotationAxis(edge.Trajectory);
Point3D tsTangent;
if (signedAngle >= 0)
{
tsTangent = GeometryHelper.CrossProduct(
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z),
new Point3D(tsRadiusVec.X, tsRadiusVec.Y, tsRadiusVec.Z)
);
}
else
{
tsTangent = GeometryHelper.CrossProduct(
new Point3D(tsRadiusVec.X, tsRadiusVec.Y, tsRadiusVec.Z),
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z)
);
}
double tsYaw = Math.Atan2(tsTangent.Y, tsTangent.X);
// 计算切点Te的朝向退出切点的切线方向
Vector3D teRadiusVec = (edge.Trajectory.Te - edge.Trajectory.ArcCenter).Normalize();
Point3D teTangent;
if (signedAngle >= 0)
{
teTangent = GeometryHelper.CrossProduct(
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z),
new Point3D(teRadiusVec.X, teRadiusVec.Y, teRadiusVec.Z)
);
}
else
{
teTangent = GeometryHelper.CrossProduct(
new Point3D(teRadiusVec.X, teRadiusVec.Y, teRadiusVec.Z),
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z)
);
}
double teYaw = Math.Atan2(teTangent.Y, teTangent.X);
// 计算当前帧的朝向
Point3D rotatedPoint = GeometryHelper.RotatePointAroundAxis(
edge.Trajectory.Ts,
edge.Trajectory.ArcCenter,
rotationAxis,
currentAngle
);
Vector3D radiusVec = (rotatedPoint - edge.Trajectory.ArcCenter).Normalize();
// 计算切线方向:根据旋转方向确定切线顺序
Point3D tangentPoint;
if (signedAngle >= 0)
{
tangentPoint = GeometryHelper.CrossProduct(
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z),
new Point3D(radiusVec.X, radiusVec.Y, radiusVec.Z)
);
}
else
{
tangentPoint = GeometryHelper.CrossProduct(
new Point3D(radiusVec.X, radiusVec.Y, radiusVec.Z),
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z)
);
}
double currentYaw = Math.Atan2(tangentPoint.Y, tangentPoint.X);
double yawChange = teYaw - tsYaw;
// 计算圆弧段前后直线段的朝向
double prevStraightYaw = 0;
double nextStraightYaw = 0;
// 圆弧段前的直线段朝向
if (currentEdgeIndex > 0 && _route.Edges[currentEdgeIndex - 1].SegmentType == PathSegmentType.Straight)
{
var prevEdge = _route.Edges[currentEdgeIndex - 1];
if (prevEdge.SampledPoints != null && prevEdge.SampledPoints.Count >= 2)
{
Point3D p1 = prevEdge.SampledPoints[0];
Point3D p2 = prevEdge.SampledPoints[prevEdge.SampledPoints.Count - 1];
prevStraightYaw = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
}
}
// 圆弧段后的直线段朝向
if (currentEdgeIndex < _route.Edges.Count - 1 && _route.Edges[currentEdgeIndex + 1].SegmentType == PathSegmentType.Straight)
{
var nextEdge = _route.Edges[currentEdgeIndex + 1];
if (nextEdge.SampledPoints != null && nextEdge.SampledPoints.Count >= 2)
{
Point3D p1 = nextEdge.SampledPoints[0];
Point3D p2 = nextEdge.SampledPoints[nextEdge.SampledPoints.Count - 1];
nextStraightYaw = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
}
}
// 在第一帧打印完整信息(使用静态变量确保每个圆弧段只打印一次)
if (!_arcInfoPrinted.Contains(currentEdgeIndex))
{
LogManager.Debug($"圆弧段[{currentEdgeIndex}]朝向信息:");
LogManager.Debug($" 前直线段朝向: {prevStraightYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 进入切点(Ts)朝向: {tsYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 圆弧偏转角度: {signedAngle * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 退出切点(Te)朝向: {teYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 后直线段朝向: {nextStraightYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 前后直线段朝向差: {(nextStraightYaw - prevStraightYaw) * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 圆弧段朝向变化: {yawChange * 180.0 / Math.PI:F2}度");
_arcInfoPrinted.Add(currentEdgeIndex);
}
// 每一帧都打印当前朝向
LogManager.Debug($"帧[{frameIndex}] 圆弧段[{currentEdgeIndex}]: 进度={edgeProgress:F4}, 当前朝向={currentYaw * 180.0 / Math.PI:F2}度");
return currentYaw;
}
else
{
// 直线段: 使用直线方向向量,避免边界处的前后帧差分问题
Point3D startPos = edge.SampledPoints[0];
Point3D endPos = edge.SampledPoints[edge.SampledPoints.Count - 1];
double dx = endPos.X - startPos.X;
double dy = endPos.Y - startPos.Y;
double length = Math.Sqrt(dx * dx + dy * dy);
if (length < 1e-6)
{
// 直线段长度太短,尝试使用前后帧差分
Point3D currentPos = allSampledPoints.Count > 0 ? allSampledPoints[Math.Min(frameIndex, allSampledPoints.Count - 1)] : new Point3D();
int nextIndex = Math.Min(frameIndex + 1, allSampledPoints.Count - 1);
Point3D nextPos = allSampledPoints[nextIndex];
dx = nextPos.X - currentPos.X;
dy = nextPos.Y - currentPos.Y;
length = Math.Sqrt(dx * dx + dy * dy);
if (length < 1e-6)
{
return 0.0;
}
}
return Math.Atan2(dy, dx);
}
}
/// <summary>
/// 获取圆弧旋转轴
/// </summary>
private Vector3D GetArcRotationAxis(ArcTrajectory trajectory)
{
Vector3D startVec = (trajectory.Ts - trajectory.ArcCenter).Normalize();
Vector3D endVec = (trajectory.Te - trajectory.ArcCenter).Normalize();
Point3D cross = GeometryHelper.CrossProduct(
new Point3D(startVec.X, startVec.Y, startVec.Z),
new Point3D(endVec.X, endVec.Y, endVec.Z)
);
return new Vector3D(cross.X, cross.Y, cross.Z).Normalize();
}
/// <summary>
/// 开始播放动画
/// </summary>
public void StartAnimation()
{
try
{
if (_animatedObject == null || _pathPoints.Count < 2)
{
throw new InvalidOperationException("请先调用SetupAnimation设置动画参数");
}
// 创建 TimeLiner 任务(如果可用且未创建过)
if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable)
{
string objectName = "UnknownObject";
try
{
objectName = _animatedObject?.DisplayName ?? "UnknownObject";
}
catch (Exception)
{
objectName = "DisposedObject";
}
// 使用动画配置hash作为任务标识
var hashPrefix = _currentAnimationHash.Substring(0, Math.Min(8, _currentAnimationHash.Length));
var taskName = $"{objectName}_运输_{hashPrefix}";
// 检查是否已存在相同配置的任务
var existingTaskId = _timeLinerManager.FindTaskByName(taskName);
if (!string.IsNullOrEmpty(existingTaskId))
{
_currentTaskId = existingTaskId;
LogManager.Info($"复用现有 TimeLiner 任务: {taskName} (ID: {_currentTaskId})");
}
else
{
// 创建新任务
var duration = TimeSpan.FromSeconds(_animationDuration);
LogManager.Info($"创建 TimeLiner 任务: {taskName}");
_currentTaskId = _timeLinerManager.CreateTransportTask(
taskName,
_pathPoints,
duration,
_animatedObject);
if (!string.IsNullOrEmpty(_currentTaskId))
{
LogManager.Info($"✓ TimeLiner 任务创建成功: {taskName} (ID: {_currentTaskId})");
}
else
{
LogManager.Warning("TimeLiner 任务创建失败,继续使用基础动画功能");
}
}
}
else
{
LogManager.Info("TimeLiner 不可用,使用基础动画功能");
}
// 初始化动画状态
_animationStartTime = DateTime.Now;
_currentFrameIndex = 0; // 重置当前帧索引,确保从第一帧开始
_pausedProgress = 0.0; // 重置暂停进度
// 初始化动画状态时不再强行覆盖 _currentYaw 为第一帧的 yaw
// 这样在 UpdateObjectPosition(firstFrame.Position, firstFrame.YawRadians) 时
// deltaYaw = firstFrame.YawRadians - 物体初始Yaw从而产生旋转增量让物体转向
if (_animationFrames != null && _animationFrames.Count > 0)
{
var firstFrame = _animationFrames[0];
LogManager.Debug($"[动画开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, _isVirtualVehicle={_isVirtualVehicle}");
// 物体已经在 MoveVehicleToPathStart 中被正确旋转到起点(包含角度修正)
// 不需要再次旋转,否则会破坏角度修正
LogManager.Debug($"[动画开始] 跳过初始位置设置,物体已在起点: pos=({firstFrame.Position.X:F2},{firstFrame.Position.Y:F2}), yaw={firstFrame.YawRadians:F3}rad");
}
else
{
_currentYaw = 0.0;
}
// 重置动画状态
_lastFrameTime = DateTime.MinValue;
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
_frameInterval = 1000.0 / _animationFrameRate;
_lastHighlightState = false; // 重置高亮状态
_lastCollisionObjects.Clear(); // 重置碰撞对象集合
// 清除所有碰撞高亮
ModelHighlightHelper.ClearCollisionHighlights();
LogManager.Debug("[动画开始] 已清除所有碰撞高亮");
// 高亮车辆对象(绿色)
if (_animatedObject != null)
{
var vehicleItems = new List<ModelItem> { _animatedObject };
ModelHighlightHelper.HighlightItems(ModelHighlightHelper.AnimatedObjectCategory, vehicleItems);
LogManager.Debug("[动画开始] 已高亮车辆对象为绿色");
}
// 启动动画播放
StartAnimationPlayback();
SetState(AnimationState.Playing);
LogManager.Info($"动画开始播放 - DispatcherTimer模式");
}
catch (Exception ex)
{
LogManager.Error($"启动动画失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 启动动画播放DispatcherTimer
/// </summary>
private void StartAnimationPlayback()
{
try
{
// 停止之前的播放
StopAnimationPlayback();
// 使用DispatcherTimer模式
_animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval);
_animationTimer.Start();
LogManager.Debug("[播放模式] 启动DispatcherTimer模式");
}
catch (Exception ex)
{
LogManager.Error($"启动动画播放失败: {ex.Message}");
}
}
/// <summary>
/// 停止动画播放停止DispatcherTimer
/// </summary>
private void StopAnimationPlayback()
{
try
{
// 停止DispatcherTimer
if (_animationTimer != null)
{
_animationTimer.Stop();
}
LogManager.Debug("[播放模式] 已停止DispatcherTimer");
}
catch (Exception ex)
{
LogManager.Warning($"停止动画播放时出现警告: {ex.Message}");
}
}
/// <summary>
/// DispatcherTimer事件处理器 - 主要动画机制
/// </summary>
private void OnTimerTick(object sender, EventArgs e)
{
try
{
// 处理动画帧
ProcessAnimationFrame();
// 关键改进:显式请求重绘,解决卡顿
NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
}
catch (Exception ex)
{
LogManager.Error($"[DispatcherTimer模式] 动画更新异常: {ex.Message}");
// 发生异常时停止动画
ShutdownAnimation();
}
}
/// <summary>
/// 停止动画
/// </summary>
public void CancelAnimation()
{
try
{
// 检查动画状态,避免重复停止
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过重复停止操作");
return;
}
// 停止播放机制
StopAnimationPlayback();
// 收集整个路径上的所有碰撞对象
var allCollisionResults = new List<CollisionResult>();
var uniqueCollidedItems = new HashSet<ModelItem>();
foreach (var frame in _animationFrames)
{
if (frame.HasCollision)
{
foreach (var collision in frame.Collisions)
{
// 收集唯一的碰撞对象
if (!uniqueCollidedItems.Contains(collision.Item2))
{
uniqueCollidedItems.Add(collision.Item2);
allCollisionResults.Add(collision);
}
}
}
}
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
// 清除所有高亮(包括车辆和碰撞)
ModelHighlightHelper.ClearAllHighlights();
LogManager.Info("动画停止:已清除所有高亮");
// 将物体移回起点位置并恢复初始朝向
if (_pathPoints != null && _pathPoints.Count > 0 && _animatedObject != null)
{
MoveVehicleToPathStart();
LogManager.Info($"物体已移回起点位置并恢复初始朝向");
}
LogManager.Info("动画已停止");
// 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理
LogManager.Info("动画停止,等待动画完成事件统一处理碰撞测试...");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 0.0, AnimationState.Stopped);
}
}
catch (Exception ex)
{
LogManager.Error($"停止动画失败: {ex.Message}");
}
}
/// <summary>
/// 程序关闭时的动画清理不执行UI操作
/// </summary>
public void ShutdownAnimation()
{
try
{
LogManager.Info("[PathAnimationManager] 程序关闭,执行动画清理");
// 检查动画状态,避免重复停止
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过关闭清理");
return;
}
// 停止播放机制(关闭时不记录详细日志)
try
{
if (_animationTimer != null) _animationTimer.Stop();
}
catch (Exception stopEx)
{
LogManager.Debug($"关闭清理时停止播放机制异常: {stopEx.Message}");
}
// 直接设置状态为停止不执行任何UI操作
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
LogManager.Info("[PathAnimationManager] 动画关闭清理完成");
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 关闭动画清理失败: {ex.Message}");
}
}
/// <summary>
/// 完成动画(当动画自然播放结束时调用)- 优化版本,使用预计算碰撞结果
/// </summary>
private void FinishAnimation()
{
try
{
// 停止播放机制
StopAnimationPlayback();
// 重置暂停进度
_pausedProgress = 0.0;
// 使用预计算的所有碰撞结果进行高亮显示(需要去重以避免重复高亮)
var allCollisionResults = _allCollisionResults
.GroupBy(c => new { c.Item1, c.Item2 })
.Select(g => g.First())
.ToList();
// 将物体移回起点位置并恢复初始朝向
if (_pathPoints != null && _pathPoints.Count > 0 && _animatedObject != null)
{
RestoreObjectToCADPosition();
MoveVehicleToPathStart();
LogManager.Info($"动画播放自然结束,物体已移回起点位置并恢复初始朝向");
}
// 直接设置为完成状态,避免中间状态切换
SetState(AnimationState.Finished);
// 清除所有高亮(包括车辆和碰撞)
ModelHighlightHelper.ClearAllHighlights();
LogManager.Info("动画完成:已清除所有高亮");
// 修复:显示正确的总帧数,而不是最后一帧的索引
var actualTotalFrames = _animationFrames?.Count ?? 0;
LogManager.Info($"动画播放完成,总帧数: {actualTotalFrames}, 平均FPS: {_actualFPS:F1}");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 1.0, AnimationState.Finished);
}
// 检查此动画配置是否已经进行过碰撞检测
if (!_completedCollisionTests.Contains(_currentAnimationHash))
{
if (_allCollisionResults.Count > 0)
{
LogManager.Info($"此动画配置首次完成,开始创建碰撞测试汇总(基于 {_allCollisionResults.Count} 个预计算碰撞记录)...");
// 🔥 设置等待光标,避免进度条关闭后的无响应
var oldCursor = System.Windows.Input.Mouse.OverrideCursor;
System.Windows.Input.Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
try
{
ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests(
_allCollisionResults,
_detectionGap,
_pathName,
_currentRouteId,
_animatedObject,
_isVirtualVehicle,
_animationFrameRate,
_animationDuration,
_virtualVehicleLength,
_virtualVehicleWidth,
_virtualVehicleHeight
);
}
finally
{
// 恢复光标
System.Windows.Input.Mouse.OverrideCursor = oldCursor;
}
}
else
{
LogManager.Info("无碰撞数据,跳过碰撞测试创建");
}
_completedCollisionTests.Add(_currentAnimationHash); // 记录此配置已完成碰撞检测
LogManager.Info($"碰撞测试汇总已创建并记录,此配置后续播放将跳过碰撞检测");
}
else if (_completedCollisionTests.Contains(_currentAnimationHash))
{
LogManager.Info("此动画配置的碰撞测试已存在,跳过重复创建");
}
}
catch (Exception ex)
{
LogManager.Error($"完成动画失败: {ex.Message}");
}
}
/// <summary>
/// 暂停动画
/// </summary>
public void PauseAnimation()
{
try
{
if (_currentState == AnimationState.Playing)
{
// 计算并保存当前进度
double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;
_pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0);
// 停止播放机制
StopAnimationPlayback();
SetState(AnimationState.Paused);
LogManager.Info($"动画已暂停,当前进度: {_pausedProgress:F3}");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, _pausedProgress, AnimationState.Paused);
}
}
}
catch (Exception ex)
{
LogManager.Error($"暂停动画失败: {ex.Message}");
}
}
/// <summary>
/// 恢复动画
/// </summary>
public void ResumeAnimation()
{
try
{
if (_currentState == AnimationState.Paused)
{
// 使用保存的暂停进度重新设置动画开始时间
// 设置开始时间,使得当前时间对应保存的暂停进度
_animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration));
// 重新启动播放机制
_lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行
StartAnimationPlayback();
SetState(AnimationState.Playing);
LogManager.Info($"动画已恢复,从进度 {_pausedProgress:F3} 继续播放");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, _pausedProgress, AnimationState.Playing);
}
}
}
catch (Exception ex)
{
LogManager.Error($"恢复动画失败: {ex.Message}");
}
}
/// <summary>
/// 重置动画对象到原始位置
/// </summary>
public void ResetAnimation()
{
try
{
// 只在需要时停止动画,避免重复调用
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
CancelAnimation(); // 停止当前动画
}
// 恢复对象的原始变换安全检查Navisworks对象可用性
if (_animatedObject != null)
{
try
{
// 检查Navisworks应用程序和文档是否仍然可用
var activeDoc = NavisApplication.ActiveDocument;
if (activeDoc != null && activeDoc.Models != null)
{
// 第一步:仅仅彻底归位到 CAD 原始位置(不涉及路径起点),这是“清除”按钮的逻辑
RestoreObjectToCADPosition();
// 第二步:如果已经生成了动画路径,则重新对齐到起点(带旋转)
if (_animationFrames != null && _animationFrames.Count > 0)
{
MoveVehicleToPathStart();
LogManager.Info("已重新对齐到路径起点");
}
}
else
{
LogManager.Info("Navisworks文档已不可用跳回空闲状态");
}
}
catch (Exception resetEx)
{
LogManager.Warning($"重置对象位置时出现警告: {resetEx.Message}");
// 不再抛出异常,因为在资源清理阶段这是正常的
}
}
// 重置UI状态这些操作不依赖Navisworks对象
try
{
ProgressChanged?.Invoke(this, 0); // 重置进度条
SetState(AnimationState.Ready); // 重置后回到就绪状态
}
catch (Exception uiEx)
{
LogManager.Warning($"重置UI状态时出现警告: {uiEx.Message}");
}
}
catch (Exception ex)
{
LogManager.Error($"重置动画过程中发生异常: {ex.Message}");
// 在资源清理阶段不再抛出异常,避免影响程序正常关闭
}
}
/// <summary>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
/// <param name="item">ModelItem对象</param>
/// <returns>安全的显示名称</returns>
private string GetSafeObjectName(ModelItem item)
{
try
{
if (item == null)
{
return "NULL";
}
// 尝试访问DisplayName如果对象已被释放会抛出异常
return item.DisplayName ?? "未命名对象";
}
catch (System.ObjectDisposedException)
{
return "已释放对象";
}
catch (System.Runtime.InteropServices.COMException)
{
return "COM对象已释放";
}
catch (Exception ex)
{
return $"访问失败({ex.GetType().Name})";
}
}
/// <summary>
/// 根据进度插值计算当前位置
/// </summary>
private Point3D InterpolatePosition(double progress)
{
if (_pathPoints.Count < 2)
return _pathPoints[0];
// 确保进度在0-1范围内
progress = Math.Max(0.0, Math.Min(1.0, progress));
// 如果进度达到100%,直接返回终点
if (progress >= 1.0)
{
return _pathPoints[_pathPoints.Count - 1];
}
// 计算总路径长度
var totalDistance = CalculateTotalPathDistance();
// 检查总距离是否有效
if (totalDistance <= 0.0 || double.IsNaN(totalDistance) || double.IsInfinity(totalDistance))
{
LogManager.Error($"路径总长度无效: {totalDistance},返回起点坐标");
return _pathPoints[0];
}
var targetDistance = totalDistance * progress;
// 找到当前应该在哪两个点之间
var accumulatedDistance = 0.0;
for (int i = 0; i < _pathPoints.Count - 1; i++)
{
var segmentDistance = CalculateDistance(_pathPoints[i], _pathPoints[i + 1]);
// 检查段距离是否有效
if (double.IsNaN(segmentDistance) || double.IsInfinity(segmentDistance))
{
LogManager.Error($"路径段[{i}-{i + 1}]距离无效: {segmentDistance},跳过此段");
continue;
}
if (accumulatedDistance + segmentDistance > targetDistance || i == _pathPoints.Count - 2)
{
// 在这个线段内,或者是最后一个线段
var segmentProgress = segmentDistance > 0 ? (targetDistance - accumulatedDistance) / segmentDistance : 0.0;
// 确保段内进度在0-1范围内
segmentProgress = Math.Max(0.0, Math.Min(1.0, segmentProgress));
var interpolatedPoint = InterpolatePoints(_pathPoints[i], _pathPoints[i + 1], segmentProgress);
// 验证插值结果
if (double.IsNaN(interpolatedPoint.X) || double.IsNaN(interpolatedPoint.Y) || double.IsNaN(interpolatedPoint.Z))
{
LogManager.Error($"插值计算结果无效: ({interpolatedPoint.X},{interpolatedPoint.Y},{interpolatedPoint.Z}),返回起点");
return _pathPoints[0];
}
return interpolatedPoint;
}
accumulatedDistance += segmentDistance;
}
// 如果到达这里,返回最后一个点
return _pathPoints[_pathPoints.Count - 1];
}
/// <summary>
/// 在两点间插值
/// </summary>
private Point3D InterpolatePoints(Point3D point1, Point3D point2, double t)
{
return new Point3D(
point1.X + (point2.X - point1.X) * t,
point1.Y + (point2.Y - point1.Y) * t,
point1.Z + (point2.Z - point1.Z) * t
);
}
/// <summary>
/// 在直线边上插值位置
/// </summary>
private Point3D InterpolateOnStraightEdge(PathEdge edge, double progress)
{
if (edge.SampledPoints == null || edge.SampledPoints.Count < 2)
return edge.Trajectory?.Ts ?? new Point3D();
int pointIndex = (int)(progress * (edge.SampledPoints.Count - 1));
pointIndex = Math.Max(0, Math.Min(pointIndex, edge.SampledPoints.Count - 1));
return edge.SampledPoints[pointIndex];
}
/// <summary>
/// 在圆弧边上插值位置
/// </summary>
private Point3D InterpolateOnArcEdge(PathEdge edge, double progress)
{
if (edge.Trajectory == null || edge.SampledPoints == null || edge.SampledPoints.Count < 2)
return edge.Trajectory?.Ts ?? new Point3D();
int pointIndex = (int)(progress * (edge.SampledPoints.Count - 1));
pointIndex = Math.Max(0, Math.Min(pointIndex, edge.SampledPoints.Count - 1));
return edge.SampledPoints[pointIndex];
}
/// <summary>
/// 查找指定距离对应的边索引
/// </summary>
private int FindEdgeForDistance(double targetDistance, List<double> edgeLengths)
{
double accumulatedLength = 0.0;
for (int i = 0; i < edgeLengths.Count; i++)
{
if (accumulatedLength + edgeLengths[i] >= targetDistance)
{
return i;
}
accumulatedLength += edgeLengths[i];
}
return edgeLengths.Count - 1;
}
/// <summary>
/// 找到目标距离所在的线段索引(用于空轨路径的线性插值)
/// </summary>
/// <param name="targetDistance">目标距离</param>
/// <param name="segmentLengths">线段长度列表</param>
/// <returns>线段索引</returns>
private int FindSegmentForDistance(double targetDistance, List<double> segmentLengths)
{
double accumulatedLength = 0.0;
for (int i = 0; i < segmentLengths.Count; i++)
{
if (accumulatedLength + segmentLengths[i] >= targetDistance)
{
return i;
}
accumulatedLength += segmentLengths[i];
}
return segmentLengths.Count - 1;
}
/// <summary>
/// 计算路径总长度
/// </summary>
private double CalculateTotalPathDistance()
{
var totalDistance = 0.0;
for (int i = 0; i < _pathPoints.Count - 1; i++)
{
totalDistance += CalculateDistance(_pathPoints[i], _pathPoints[i + 1]);
}
return totalDistance;
}
/// <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>
/// 计算两点间距离
/// </summary>
private double CalculateDistance(Point3D point1, Point3D point2)
{
var dx = point2.X - point1.X;
var dy = point2.Y - point1.Y;
var dz = point2.Z - point1.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
/// <summary>
/// 更新对象位置和朝向(支持绕物体中心旋转)
/// </summary>
private void UpdateObjectPosition(Point3D newPosition, double newYaw = double.NaN)
{
try
{
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 计算平移和旋转的增量
var deltaPos = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
);
Transform3D incrementalTransform;
if (!double.IsNaN(newYaw))
{
// 有旋转:需要实现"绕物体当前位置自转"
// 由于Transform3DComponents.Rotation总是绕世界原点旋转
// 我们需要手动计算旋转导致的位置偏移,并补偿
double deltaYaw = newYaw - _currentYaw;
LogManager.Debug($"[UpdateObjectPosition] 当前yaw={_currentYaw * 180 / Math.PI:F2}度, 目标yaw={newYaw * 180 / Math.PI:F2}度, 旋转增量deltaYaw={deltaYaw * 180 / Math.PI:F2}度");
// 计算绕当前位置旋转的等效变换:
// 1. 如果绕原点旋转deltaYaw当前位置_currentPosition会移动到哪里
double cos = Math.Cos(deltaYaw);
double sin = Math.Sin(deltaYaw);
double rotatedX = _currentPosition.X * cos - _currentPosition.Y * sin;
double rotatedY = _currentPosition.X * sin + _currentPosition.Y * cos;
// 2. 但我们希望物体绕自己旋转位置移动到newPosition
// 所以需要的平移 = newPosition - (旋转后的位置)
var compensatedTranslation = new Vector3D(
newPosition.X - rotatedX,
newPosition.Y - rotatedY,
newPosition.Z - _currentPosition.Z // Z保持deltaPos
);
// 3. 组合:先旋转(绕原点),再平移(补偿+目标位置)
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = compensatedTranslation;
incrementalTransform = components.Combine();
_currentYaw = newYaw;
}
else
{
// 纯平移
incrementalTransform = Transform3D.CreateTranslation(deltaPos);
LogManager.Debug($"[UpdateObjectPosition] 纯平移: ({deltaPos.X:F2},{deltaPos.Y:F2},{deltaPos.Z:F2})");
}
// 应用增量变换false = 增量模式)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
// 更新当前位置
_currentPosition = newPosition; }
catch (Exception ex)
{
LogManager.Error($"更新部件位置失败: {ex.Message}");
}
}
/// <summary>
/// 获取文档单位信息(用于日志记录)
/// </summary>
private string GetDocumentUnitsInfo()
{
try
{
var doc = NavisApplication.ActiveDocument;
var units = doc.Units;
LogManager.Debug($"[单位检测] 文档单位: {units}");
return units.ToString();
}
catch (Exception ex)
{
LogManager.Error($"获取文档单位信息失败: {ex.Message}");
return "Unknown";
}
}
/// <summary>
/// 获取模型当前变换
/// </summary>
private Transform3D GetCurrentTransform(ModelItem item)
{
// 获取包围盒中心作为参考点
var boundingBox = item.BoundingBox();
var center = boundingBox.Center;
// 创建基于中心点的单位变换
return Transform3D.CreateTranslation(new Vector3D(center.X, center.Y, center.Z));
}
/// <summary>
/// 设置动画持续时间
/// </summary>
public void SetAnimationDuration(double durationSeconds)
{
_animationDuration = Math.Max(1.0, durationSeconds);
}
/// <summary>
/// 获取动画状态
/// </summary>
public bool IsAnimating => _currentState == AnimationState.Playing;
/// <summary>
/// 获取 TimeLiner 是否可用
/// </summary>
public bool IsTimeLinerAvailable => _timeLinerManager?.IsTimeLinerAvailable ?? false;
/// <summary>
/// 获取当前动画状态
/// </summary>
public AnimationState CurrentState => _currentState;
/// <summary>
/// 获取当前 TimeLiner 任务ID
/// </summary>
public string CurrentTaskId => _currentTaskId;
/// <summary>
/// 获取 TimeLiner 集成管理器
/// </summary>
public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager;
#region
/// <summary>
/// 获取当前帧索引
/// </summary>
public int CurrentFrame => _currentFrameIndex;
/// <summary>
/// 获取总帧数
/// </summary>
public int TotalFrames => _animationFrames?.Count ?? 0;
/// <summary>
/// 获取播放方向1=正向,-1=反向)
/// </summary>
public int PlaybackDirection => _playbackDirection;
/// <summary>
/// 获取播放速度倍率
/// </summary>
public double PlaybackSpeed => _playbackSpeed;
/// <summary>
/// 获取是否正在反向播放
/// </summary>
public bool IsPlayingReverse => _playbackDirection == -1;
/// <summary>
/// 设置播放方向
/// </summary>
/// <param name="reverse">true=反向播放false=正向播放</param>
public void SetPlaybackDirection(bool reverse)
{
_playbackDirection = reverse ? -1 : 1;
LogManager.Info($"播放方向设置为: {(reverse ? "" : "")}");
}
/// <summary>
/// 设置播放速度
/// </summary>
/// <param name="speed">播放速度倍率,负值表示反向</param>
public void SetPlaybackSpeed(double speed)
{
_playbackSpeed = Math.Abs(speed);
_playbackDirection = speed >= 0 ? 1 : -1;
// 更新帧间隔
_frameInterval = 1000.0 / (_animationFrameRate * _playbackSpeed);
LogManager.Info($"播放速度设置为: {speed}x方向: {(speed >= 0 ? "" : "")}");
}
/// <summary>
/// 跳转到指定帧
/// </summary>
/// <param name="frameIndex">目标帧索引</param>
public void SeekToFrame(int frameIndex)
{
try
{
if (_animationFrames == null || _animationFrames.Count == 0)
{
LogManager.Warning("无动画帧数据,无法跳转");
return;
}
// 限制帧索引范围
frameIndex = Math.Max(0, Math.Min(frameIndex, _animationFrames.Count - 1));
if (frameIndex == _currentFrameIndex)
{
LogManager.Debug($"已处于目标帧 {frameIndex},无需跳转");
return;
}
_currentFrameIndex = frameIndex;
// 更新对象位置和朝向
var frameData = _animationFrames[_currentFrameIndex];
UpdateObjectPosition(frameData.Position, frameData.YawRadians);
// 更新碰撞高亮
UpdateCollisionHighlightFromFrame();
// 计算并触发进度事件
double progress = TotalFrames > 1 ? (double)_currentFrameIndex / (TotalFrames - 1) * 100 : 0;
ProgressChanged?.Invoke(this, progress);
}
catch (Exception ex)
{
LogManager.Error($"跳转到帧失败: {ex.Message}");
}
}
/// <summary>
/// 跳转到指定进度
/// </summary>
/// <param name="progress">进度值0.0-1.0</param>
public void SeekToProgress(double progress)
{
try
{
progress = Math.Max(0.0, Math.Min(1.0, progress));
int targetFrame = TotalFrames > 1 ? (int)(progress * (TotalFrames - 1)) : 0;
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"跳转到进度失败: {ex.Message}");
}
}
/// <summary>
/// 单帧前进
/// </summary>
public void StepForward()
{
StepForward(1);
}
/// <summary>
/// 前进指定帧数
/// </summary>
/// <param name="frames">帧数</param>
public void StepForward(int frames)
{
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex + frames;
targetFrame = Math.Min(targetFrame, TotalFrames - 1);
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"前进步进失败: {ex.Message}");
}
}
/// <summary>
/// 单帧后退
/// </summary>
public void StepBackward()
{
StepBackward(1);
}
/// <summary>
/// 后退指定帧数
/// </summary>
/// <param name="frames">帧数</param>
public void StepBackward(int frames)
{
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex - frames;
targetFrame = Math.Max(targetFrame, 0);
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"后退步进失败: {ex.Message}");
}
}
/// <summary>
/// 快进10帧
/// </summary>
public void FastForward()
{
StepForward(10);
}
/// <summary>
/// 快退10帧
/// </summary>
public void FastBackward()
{
StepBackward(10);
}
/// <summary>
/// 开始正向播放
/// </summary>
public void PlayForward()
{
try
{
SetPlaybackDirection(false);
if (_currentState == AnimationState.Paused)
{
ResumeAnimation(); // 从暂停位置继续
}
else if (_currentState != AnimationState.Playing)
{
StartAnimation(); // 只有非播放和非暂停状态才从头开始
}
LogManager.Info("开始正向播放");
}
catch (Exception ex)
{
LogManager.Error($"正向播放失败: {ex.Message}");
}
}
/// <summary>
/// 开始反向播放
/// </summary>
public void PlayReverse()
{
try
{
SetPlaybackDirection(true);
if (_currentState == AnimationState.Paused)
{
ResumeAnimation(); // 从暂停位置继续
}
else if (_currentState != AnimationState.Playing)
{
StartAnimation(); // 只有非播放和非暂停状态才从头开始
}
LogManager.Info("开始反向播放");
}
catch (Exception ex)
{
LogManager.Error($"反向播放失败: {ex.Message}");
}
}
#endregion
/// <summary>
/// 清理对象引用
/// </summary>
public void ClearReferences()
{
try
{
LogManager.Info("[PathAnimationManager] 清理对象引用");
// 停止动画
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
ShutdownAnimation();
}
// 清空对象引用
_animatedObject = null;
_pathPoints?.Clear();
_currentPosition = new Point3D(0, 0, 0);
_originalCenter = new Point3D(0, 0, 0);
LogManager.Info("[PathAnimationManager] 对象引用已清理");
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 清理对象引用失败: {ex.Message}");
}
}
/// <summary>
/// 验证动画对象是否有效
/// </summary>
private bool IsAnimatedObjectValid()
{
if (_animatedObject == null)
return false;
// 使用DocumentStateManager验证
return DocumentStateManager.Instance.IsModelItemValid(_animatedObject);
}
/// <summary>
/// 文档失效事件处理
/// </summary>
private void OnDocumentInvalidated(object sender, EventArgs e)
{
try
{
LogManager.Info("[PathAnimationManager] 文档失效,停止动画并清理");
ClearReferences();
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 处理文档失效事件失败: {ex.Message}");
}
}
/// <summary>
/// 资源清理
/// </summary>
public void Dispose()
{
try
{
LogManager.Info("开始清理PathAnimationManager资源");
// 1. 优先停止动画(最小化的操作,不触发复杂的重置逻辑)
// 只有在动画运行时才停止,避免重复调用
try
{
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
ShutdownAnimation();
}
}
catch (Exception stopEx)
{
LogManager.Warning($"停止动画时出现警告: {stopEx.Message},继续清理其他资源");
}
// 2. 在Dispose中不进行重置操作避免访问已释放的对象
// 程序关闭时,对象位置重置是不必要的
LogManager.Info("跳过动画重置操作(程序关闭时不需要恢复对象位置)");
// 3. 清理 TimeLiner 资源
if (_timeLinerManager != null)
{
try
{
_timeLinerManager.Dispose();
_timeLinerManager = null;
LogManager.Info("TimeLiner 集成资源已清理");
}
catch (Exception ex)
{
LogManager.Warning($"清理 TimeLiner 资源时出现警告: {ex.Message}");
}
}
// 4. 清理DispatcherTimer资源
if (_animationTimer != null)
{
try
{
_animationTimer.Stop();
// DispatcherTimer不需要Dispose
_animationTimer = null;
LogManager.Info("DispatcherTimer资源已清理");
}
catch (Exception ex)
{
LogManager.Warning($"清理DispatcherTimer资源时出现警告: {ex.Message}");
}
}
// 5. 取消订阅文档事件
DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
LogManager.Info("PathAnimationManager资源清理完成");
}
catch (Exception ex)
{
LogManager.Error($"PathAnimationManager资源清理过程中发生异常: {ex.Message}");
// 不抛出异常,避免影响应用程序正常关闭
}
}
/// <summary>
/// 设置动画帧率
/// </summary>
public void SetAnimationFrameRate(int frameRate)
{
_animationFrameRate = Math.Max(10, Math.Min(60, frameRate)); // 限制在10-60FPS之间
_frameInterval = 1000.0 / _animationFrameRate; // 重新计算帧间隔
// 动态更新DispatcherTimer间隔
if (_animationTimer != null)
{
_animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval);
LogManager.Info($"DispatcherTimer间隔已更新: {_frameInterval:F1}ms");
}
LogManager.Info($"动画帧率设置为: {_animationFrameRate}FPS (间隔: {_frameInterval:F1}ms)");
}
/// <summary>
/// 设置碰撞检测精度(参数单位:米)
/// </summary>
public void SetCollisionDetectionAccuracy(double accuracyInMeters)
{
// 转换为模型单位存储
var accuracyInModelUnits = UnitsConverter.ConvertFromMeters(Math.Max(0.01, accuracyInMeters));
_collisionDetectionAccuracy = accuracyInModelUnits;
LogManager.Info($"碰撞检测精度设置为: {accuracyInMeters:F3}米");
}
/// <summary>
/// 设置运动速度
/// </summary>
public void SetMovementSpeed(double speed)
{
_movementSpeed = Math.Max(0.1, speed); // 最小0.1米/秒
LogManager.Info($"运动速度设置为: {_movementSpeed}m/s");
}
/// <summary>
/// 设置检测间隙(参数单位:米)
/// </summary>
public void SetDetectionGap(double gapInMeters)
{
// 转换为模型单位存储
var gapInModelUnits = UnitsConverter.ConvertFromMeters(Math.Max(0.0, gapInMeters));
var roundedGapInModelUnits = Math.Round(gapInModelUnits, 4); // 模型单位保留更多精度
// 只在值真正改变时才更新和记录日志
if (Math.Abs(_detectionGap - roundedGapInModelUnits) > 0.0001)
{
_detectionGap = roundedGapInModelUnits;
LogManager.Info($"检测间隙设置为: {gapInMeters:F2}米");
// 预计算检测间隙与检测间隙相同
_precomputedDetectionGap = _detectionGap;
}
}
/// <summary>
/// 设置手工碰撞对象覆写列表
/// </summary>
public void SetManualCollisionTargets(IEnumerable<ModelItem> targets, bool enabled)
{
if (!enabled)
{
_manualCollisionOverrideEnabled = false;
_manualCollisionTargets = new List<ModelItem>();
LogManager.Info("[PathAnimationManager] 手工碰撞对象覆写已关闭");
return;
}
if (targets == null)
{
_manualCollisionOverrideEnabled = false;
_manualCollisionTargets = new List<ModelItem>();
LogManager.Warning("[PathAnimationManager] 手工碰撞对象覆写启用但目标为空,已回退到默认模式");
return;
}
var validTargets = targets
.Where(item => item != null && ModelItemAnalysisHelper.IsModelItemValid(item))
.Where(item => !ReferenceEquals(item, _animatedObject))
.Distinct()
.ToList();
if (validTargets.Count == 0)
{
_manualCollisionOverrideEnabled = false;
_manualCollisionTargets = new List<ModelItem>();
LogManager.Warning("[PathAnimationManager] 手工碰撞对象覆写启用但均无效,已回退到默认模式");
return;
}
_manualCollisionOverrideEnabled = true;
_manualCollisionTargets = validTargets;
LogManager.Info($"[PathAnimationManager] 启用手工碰撞对象覆写,共 {validTargets.Count} 个对象");
}
/// <summary>
/// 获取当前动画帧率
/// </summary>
public int AnimationFrameRate => _animationFrameRate;
/// <summary>
/// 获取所有碰撞结果(不去重)
/// </summary>
public List<CollisionResult> AllCollisionResults => _allCollisionResults;
/// <summary>
/// 获取当前碰撞检测精度
/// </summary>
public double CollisionDetectionAccuracy => _collisionDetectionAccuracy;
/// <summary>
/// 获取当前运动速度
/// </summary>
public double MovementSpeed => _movementSpeed;
/// <summary>
/// 获取当前检测间隙(单位:米)
/// </summary>
public double DetectionGap
{
get
{
// 将内部模型单位转换为米制单位
var modelUnitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor();
return _detectionGap * modelUnitsToMeters;
}
}
/// <summary>
/// 获取路径名称
/// </summary>
public string PathName => _pathName;
/// <summary>
/// 获取动画时长
/// </summary>
public double AnimationDuration => _animationDuration;
/// <summary>
/// 设置并触发状态变更事件
/// </summary>
/// <param name="newState">新的动画状态</param>
private void SetState(AnimationState newState)
{
if (_currentState != newState)
{
_currentState = newState;
StateChanged?.Invoke(this, _currentState);
}
}
/// <summary>
/// 创建动画
/// </summary>
/// <param name="animatedObject">要动画化的模型对象</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
/// <param name="pathName">路径名称</param>
/// <param name="routeId">路由ID</param>
public void CreateAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0, string pathName = "未知路径", string routeId = null, bool isVirtualVehicle = false,
double virtualVehicleLength = 0, double virtualVehicleWidth = 0, double virtualVehicleHeight = 0)
{
_pathName = pathName;
_currentRouteId = routeId;
_isVirtualVehicle = isVirtualVehicle; // 设置是否使用虚拟车辆
_virtualVehicleLength = virtualVehicleLength;
_virtualVehicleWidth = virtualVehicleWidth;
_virtualVehicleHeight = virtualVehicleHeight;
_pathPoints = pathPoints;
_animatedObject = animatedObject;
// 初始化物体位置和朝向
if (animatedObject != null)
{
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_currentPosition = new Point3D(_originalCenter.X, _originalCenter.Y, animatedObject.BoundingBox().Min.Z);
// 保持当前的 _currentYaw因为物体可能已经被 MoveVehicleToPathStart 旋转)
// 不要从 Transform 中提取,因为 Transform 返回的是原始值,不是当前值
LogManager.Debug($"[CreateAnimation] 保持_currentYaw={_currentYaw * 180 / Math.PI:F2}度不变, _isVirtualVehicle={_isVirtualVehicle}");
}
// 设置动画参数并预计算动画帧
// 注意物体应该在调用CreateAnimation之前已经通过MoveVehicleToPathStart旋转到起点
LogManager.Debug($"[CreateAnimation开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, _isVirtualVehicle={_isVirtualVehicle}");
SetupAnimation(animatedObject, durationSeconds, _route);
LogManager.Debug($"[CreateAnimation结束] _currentYaw之后={_currentYaw * 180 / Math.PI:F2}度");
// 设置动画状态为Ready动画已生成可以播放
SetState(AnimationState.Ready);
LogManager.Info($"[CreateAnimation] 动画已创建状态设置为Ready");
}
/// <summary>
/// 设置物体角度修正值(度,顺时针)
/// </summary>
/// <param name="rotationCorrection">角度修正值(度)</param>
public void SetObjectRotationCorrection(double rotationCorrection)
{
_objectRotationCorrection = rotationCorrection;
// 如果动画已创建,更新物体到起点的朝向
if (_animatedObject != null && _pathPoints != null && _pathPoints.Count > 0)
{
try
{
// 重新计算并应用朝向
MoveVehicleToPathStart();
LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection:F1}°");
}
catch (Exception ex)
{
LogManager.Error($"[角度修正] 更新物体角度失败: {ex.Message}");
}
}
}
/// <summary>
/// 直接设置物体角度修正值(不触发物体旋转)
/// </summary>
/// <param name="rotationCorrection">角度修正值(度)</param>
public void SetObjectRotationCorrectionDirect(double rotationCorrection)
{
_objectRotationCorrection = rotationCorrection;
LogManager.Debug($"[角度修正] 直接设置角度修正值: {_objectRotationCorrection:F1}°(不触发旋转)");
}
#region
/// <summary>
/// DispatcherTimer动画处理核心
/// </summary>
/// <summary>
/// 根据当前帧索引更新碰撞高亮(状态跟踪优化版本)
/// </summary>
private void UpdateCollisionHighlightFromFrame()
{
try
{
// 确保帧索引有效
if (_currentFrameIndex < 0 || _currentFrameIndex >= _animationFrames.Count)
{
return;
}
// 获取当前帧数据
var currentFrame = _animationFrames[_currentFrameIndex];
bool currentHasCollision = currentFrame.HasCollision;
// 收集当前帧碰撞对象的集合
var currentCollisionObjects = new HashSet<ModelItem>();
if (currentHasCollision && currentFrame.Collisions != null)
{
foreach (var collision in currentFrame.Collisions)
{
if (collision.Item1 != null)
currentCollisionObjects.Add(collision.Item1);
if (collision.Item2 != null)
currentCollisionObjects.Add(collision.Item2);
}
}
// 检查是否需要更新高亮:
// 1. 碰撞状态改变(有碰撞 vs 无碰撞)
// 2. 碰撞对象集合改变(碰撞的物体变了)
bool stateChanged = currentHasCollision != _lastHighlightState;
bool objectsChanged = !currentCollisionObjects.SetEquals(_lastCollisionObjects);
if (stateChanged || objectsChanged)
{
if (currentHasCollision)
{
// 有碰撞高亮当前帧的碰撞对象使用CollisionResultsCategory的默认颜色
ModelHighlightHelper.HighlightCollisionResults(currentFrame.Collisions);
LogManager.Debug($"[高亮状态] 帧{_currentFrameIndex}: 高亮碰撞 ({currentFrame.Collisions.Count}个, 对象{currentCollisionObjects.Count}个)");
}
else
{
// 无碰撞:清除碰撞高亮(保留车辆高亮)
ModelHighlightHelper.ClearCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory);
LogManager.Debug($"[高亮状态] 帧{_currentFrameIndex}: 清除碰撞高亮");
}
// 更新状态记录
_lastHighlightState = currentHasCollision;
_lastCollisionObjects = currentCollisionObjects;
}
}
catch (Exception ex)
{
LogManager.Error($"更新碰撞高亮失败: {ex.Message}");
}
}
/// <summary>
/// 统一的动画帧处理逻辑DispatcherTimer专用
/// </summary>
private void ProcessAnimationFrame()
{
// 检查动画状态
if (_currentState != AnimationState.Playing)
{
return; // 非播放状态,跳过更新
}
// 验证动画对象是否仍然有效
if (!IsAnimatedObjectValid())
{
LogManager.Warning("[PathAnimationManager] 动画对象已失效,停止动画");
ShutdownAnimation();
return;
}
// DispatcherTimer已经控制了调用间隔无需手动帧率控制
var now = DateTime.Now;
// 计算下一帧索引(考虑播放方向和速度)
int nextFrameIndex = _currentFrameIndex;
if (_playbackDirection == 1) // 正向播放
{
nextFrameIndex = _currentFrameIndex + 1;
if (nextFrameIndex >= _animationFrames.Count)
{
// 正向播放到达终点
nextFrameIndex = _animationFrames.Count - 1;
StopAnimationPlayback();
FinishAnimation();
return;
}
}
else // 反向播放
{
nextFrameIndex = _currentFrameIndex - 1;
if (nextFrameIndex < 0)
{
// 反向播放到达起点
nextFrameIndex = 0;
StopAnimationPlayback();
FinishAnimation();
return;
}
}
// 更新帧索引和位置
if (nextFrameIndex != _currentFrameIndex)
{
_currentFrameIndex = nextFrameIndex;
// 使用预计算的帧位置和朝向
if (_currentFrameIndex < _animationFrames.Count)
{
var frameData = _animationFrames[_currentFrameIndex];
// 计算实际朝向 = 路径方向 + 角度修正
double actualYaw = frameData.YawRadians;
if (_objectRotationCorrection != 0.0)
{
double correctionRad = _objectRotationCorrection * Math.PI / 180.0;
actualYaw += correctionRad;
}
UpdateObjectPosition(frameData.Position, actualYaw);
// 更新碰撞高亮(基于预计算结果)
UpdateCollisionHighlightFromFrame();
// 计算当前进度(基于帧索引)
double progress = _animationFrames.Count > 1 ? (double)_currentFrameIndex / (_animationFrames.Count - 1) : 0;
// 触发进度事件
var progressPercent = progress * 100;
ProgressChanged?.Invoke(this, progressPercent);
// 同步进度到 TimeLiner如果可用
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
}
}
// 记录帧时间和统计
_lastFrameTime = now;
// 性能监控和自动切换
UpdateFPSCounterAndCheckSwitch();
}
/// <summary>
/// 更新FPS计数器DispatcherTimer模式
/// </summary>
private void UpdateFPSCounterAndCheckSwitch()
{
_fpsFrameCount++;
var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds;
// 每秒更新一次FPS统计
if (elapsed >= 1.0)
{
_actualFPS = _fpsFrameCount / elapsed;
LogManager.Debug($"[性能监控] 实际FPS: {_actualFPS:F1} (目标: {_animationFrameRate}) - DispatcherTimer模式");
// 重置计数器
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
}
}
#region
/// <summary>
/// 计算动画配置的哈希值(基于路径点和对象)
/// </summary>
private string ComputeAnimationConfigHash(ModelItem animatedObject, List<Point3D> pathPoints)
{
try
{
var sb = new StringBuilder();
// 包含对象名称
sb.Append(animatedObject?.DisplayName ?? "null");
sb.Append("|");
// 包含路径点信息
if (pathPoints != null)
{
foreach (var point in pathPoints)
{
sb.Append($"{point.X:F2},{point.Y:F2},{point.Z:F2};");
}
}
// 计算哈希
using (var md5 = System.Security.Cryptography.MD5.Create())
{
var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
var hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
}
catch (Exception ex)
{
LogManager.Warning($"计算动画配置哈希失败: {ex.Message}");
return DateTime.Now.Ticks.ToString(); // 回退到时间戳
}
}
/// <summary>
/// 计算动画配置的哈希值基于PathRoute的Edges可以区分不同的曲线化路径
/// </summary>
private string ComputeAnimationConfigHashFromRoute(ModelItem animatedObject, PathRoute route)
{
try
{
var sb = new StringBuilder();
// 包含对象名称
sb.Append(animatedObject?.DisplayName ?? "null");
sb.Append("|");
// 包含路径ID区分不同的路径
sb.Append(route?.Id ?? "null");
sb.Append("|");
// 包含动画参数(确保参数改变时重新检测)
sb.Append($"Duration:{_animationDuration:F2}s");
sb.Append($"|FrameRate:{_animationFrameRate}");
sb.Append($"|DetectionGap:{_detectionGap:F4}");
sb.Append("|");
// 包含虚拟车辆尺寸(确保车辆尺寸改变时重新检测)
// 使用传入的车辆尺寸参数而不是从ConfigManager读取
var vehicleSizeString = $"Vehicle:{_virtualVehicleLength:F2}x{_virtualVehicleWidth:F2}x{_virtualVehicleHeight:F2}";
sb.Append(vehicleSizeString);
sb.Append("|");
// 包含角度修正(确保角度修正改变时重新检测)
sb.Append($"RotationCorrection:{_objectRotationCorrection:F2}deg");
sb.Append("|");
// 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测)
if (_manualCollisionOverrideEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0)
{
sb.Append("ManualTargets:");
foreach (var target in _manualCollisionTargets.OrderBy(t => t.DisplayName))
{
try
{
sb.Append(target.DisplayName);
sb.Append(",");
}
catch (Exception)
{
// 忽略无效对象
}
}
sb.Append("|");
}
// 包含路径边的摘要信息(区分不同的曲线化结果)
if (route?.Edges != null && route.Edges.Count > 0)
{
foreach (var edge in route.Edges)
{
sb.Append(edge.SegmentType.ToString());
sb.Append($"({edge.PhysicalLength:F2})");
if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
{
sb.Append($"_R{edge.Trajectory.ActualRadius:F2}");
sb.Append($"_A{edge.Trajectory.DeflectionAngle:F2}");
}
sb.Append(";");
}
}
// 包含路径最后修改时间(确保路径修改后重新生成动画)
sb.Append($"LastModified:{route?.LastModified:yyyyMMddHHmmss}");
// 计算哈希
using (var md5 = System.Security.Cryptography.MD5.Create())
{
var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
var hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
}
catch (Exception ex)
{
LogManager.Warning($"计算动画配置哈希失败: {ex.Message}");
return DateTime.Now.Ticks.ToString(); // 回退到时间戳
}
}
/// <summary>
/// 强制重新创建碰撞测试(清除指定配置的碰撞检测记录)
/// </summary>
public void ForceRecreateCollisionTest()
{
if (!string.IsNullOrEmpty(_currentAnimationHash))
{
_completedCollisionTests.Remove(_currentAnimationHash);
LogManager.Info($"已清除动画配置 {_currentAnimationHash} 的碰撞检测记录,下次播放时将重新创建");
}
}
/// <summary>
/// 清除所有碰撞测试记录
/// </summary>
public static void ClearAllCollisionTestRecords()
{
_completedCollisionTests.Clear();
LogManager.Info("已清除所有碰撞测试记录");
}
#endregion
#region LogisticsAnimationManager迁移
/// <summary>
/// 为动画对象预计算并缓存排除列表
/// </summary>
/// <param name="animationObject">动画对象</param>
/// <returns>是否成功缓存</returns>
public bool PrecomputeCollisionExclusions(ModelItem animationObject)
{
try
{
LogManager.Info($"[缓存管理] 开始为动画对象预计算排除列表: {animationObject?.DisplayName ?? "NULL"}");
if (animationObject == null)
{
LogManager.Warning("[缓存管理] 动画对象为空,清除缓存");
ClearExclusionCache();
return false;
}
// 检查是否需要重新计算
if (IsCacheValid(animationObject))
{
LogManager.Info($"[缓存管理] 缓存仍然有效,跳过重新计算");
return true;
}
var startTime = DateTime.Now;
// 只构建特定于动画对象的逻辑排除列表
LogManager.Info("[缓存管理] 构建逻辑排除列表...");
var exclusionList = ModelItemAnalysisHelper.CollectRelatedNodes(animationObject);
// 更新缓存
_currentCachedAnimationObject = animationObject;
_cachedExclusionList = exclusionList;
_cacheCreatedTime = DateTime.Now;
var elapsedMs = (DateTime.Now - startTime).TotalMilliseconds;
LogManager.Info($"[缓存管理] 预计算完成,耗时 {elapsedMs:F1}ms缓存 {exclusionList.Count} 个排除对象");
return true;
}
catch (Exception ex)
{
LogManager.Error($"[缓存管理] 预计算排除列表失败: {ex.Message}", ex);
ClearExclusionCache();
return false;
}
}
/// <summary>
/// 获取缓存的排除列表
/// </summary>
/// <param name="animationObject">动画对象</param>
/// <returns>排除列表如果缓存无效则返回null</returns>
public List<ModelItem> GetCachedExclusionList(ModelItem animationObject)
{
try
{
if (!IsCacheValid(animationObject))
{
LogManager.Debug($"[缓存管理] 缓存无效,建议重新预计算");
return null;
}
LogManager.Debug($"[缓存管理] 返回缓存的排除列表: {_cachedExclusionList?.Count ?? 0} 个对象");
return _cachedExclusionList;
}
catch (Exception ex)
{
LogManager.Warning($"[缓存管理] 获取缓存失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 检查缓存是否有效
/// </summary>
/// <param name="animationObject">当前动画对象</param>
/// <returns>缓存是否有效</returns>
private bool IsCacheValid(ModelItem animationObject)
{
try
{
// 检查是否有缓存
if (_currentCachedAnimationObject == null || _cachedExclusionList == null)
{
return false;
}
// 检查对象是否相同
if (!_currentCachedAnimationObject.Equals(animationObject))
{
string cachedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
string currentObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animationObject);
LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'");
return false;
}
// 检查缓存时间是否过期
if (DateTime.Now - _cacheCreatedTime > _cacheValidDuration)
{
LogManager.Debug($"[缓存验证] 缓存已过期: {(DateTime.Now - _cacheCreatedTime).TotalMinutes:F1} 分钟");
return false;
}
return true;
}
catch (Exception ex)
{
LogManager.Warning($"[缓存验证] 验证过程异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 清除排除列表缓存
/// </summary>
public void ClearExclusionCache()
{
try
{
if (_currentCachedAnimationObject != null)
{
try
{
// 安全访问DisplayName防止WeakRef已释放的警告
string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
LogManager.Debug($"[缓存管理] 清除缓存: {objectName}");
}
catch (Exception ex)
{
LogManager.Debug($"[缓存管理] 清除缓存时访问对象名称失败: {ex.Message}");
}
}
_currentCachedAnimationObject = null;
_cachedExclusionList = null;
_cacheCreatedTime = DateTime.MinValue;
}
catch (Exception ex)
{
LogManager.Warning($"[缓存管理] 清除缓存失败: {ex.Message}");
}
}
/// <summary>
/// 获取缓存统计信息
/// </summary>
/// <returns>缓存统计信息</returns>
public string GetCacheStats()
{
try
{
if (_currentCachedAnimationObject == null)
{
return "缓存状态: 无缓存";
}
var age = DateTime.Now - _cacheCreatedTime;
string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
var count = _cachedExclusionList?.Count ?? 0;
return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒";
}
catch (Exception ex)
{
return $"缓存状态: 获取失败 - {ex.Message}";
}
}
#endregion
#endregion
}
}