1379 lines
54 KiB
C#
1379 lines
54 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Autodesk.Navisworks.Api;
|
||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||
|
||
namespace NavisworksTransport.Core.Animation
|
||
{
|
||
/// <summary>
|
||
/// 定义动画播放的状态
|
||
/// </summary>
|
||
public enum AnimationState
|
||
{
|
||
Idle, // 空闲,未生成动画
|
||
Ready, // 已就绪,动画已生成但未播放
|
||
Playing, // 播放中
|
||
Paused, // 暂停
|
||
Stopped, // 已停止
|
||
Finished // 已完成
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
|
||
/// 注意:由于Navisworks API限制,无法直接使用Animator API,因此使用OverridePermanentTransform实现动画
|
||
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
|
||
/// </summary>
|
||
public class PathAnimationManager
|
||
{
|
||
private static PathAnimationManager _instance;
|
||
private ModelItem _animatedObject;
|
||
private List<Point3D> _pathPoints;
|
||
|
||
// === Idle事件动画系统 ===
|
||
private double _frameInterval; // 帧间隔(毫秒)
|
||
private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间
|
||
private int _collisionCheckCounter = 0; // 碰撞检测计数器(用于降频)
|
||
|
||
// === 动画参数 ===
|
||
private double _animationDuration = 10.0; // 动画总时长(秒)
|
||
private DateTime _animationStartTime;
|
||
private int _animationFrameRate = 30; // 动画帧率(默认30FPS)
|
||
private int _animationFrameCount = 0; // 动画帧计数
|
||
private double _collisionDetectionAccuracy = 0.1; // 检测精度(默认0.1米)
|
||
private double _movementSpeed = 1.0; // 运动速度(默认1米/秒)
|
||
private double _detectionGap = 0.05; // 检测间隙(默认0.05米)
|
||
|
||
// === 性能监控 ===
|
||
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之间)
|
||
|
||
// TimeLiner 集成
|
||
private TimeLinerIntegrationManager _timeLinerManager;
|
||
private string _currentTaskId;
|
||
|
||
// --- 新增事件 ---
|
||
/// <summary>
|
||
/// 当动画状态发生改变时触发
|
||
/// </summary>
|
||
public event EventHandler<AnimationState> StateChanged;
|
||
|
||
/// <summary>
|
||
/// 当动画进度更新时触发 (0.0-100.0)
|
||
/// </summary>
|
||
public event EventHandler<double> ProgressChanged;
|
||
|
||
// 动画完成事件 (旧版,保留兼容性)
|
||
public event EventHandler AnimationCompleted;
|
||
|
||
/// <summary>
|
||
/// 当检测到碰撞时触发
|
||
/// </summary>
|
||
public event EventHandler<CollisionDetectedEventArgs> CollisionDetected;
|
||
|
||
public PathAnimationManager()
|
||
{
|
||
_pathPoints = new List<Point3D>();
|
||
|
||
// 初始化动画模式
|
||
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
|
||
_lastFrameTime = DateTime.MinValue;
|
||
_fpsCounterStart = DateTime.Now;
|
||
|
||
// 初始化 TimeLiner 集成
|
||
try
|
||
{
|
||
_timeLinerManager = new TimeLinerIntegrationManager();
|
||
LogManager.Info($"PathAnimationManager 初始化完成 - Idle事件模式, 目标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>
|
||
/// <param name="animatedObject">要动画化的模型对象</param>
|
||
/// <param name="pathPoints">路径点列表</param>
|
||
/// <param name="durationSeconds">动画持续时间(秒)</param>
|
||
public void SetupAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0)
|
||
{
|
||
try
|
||
{
|
||
if (animatedObject == null)
|
||
throw new ArgumentNullException(nameof(animatedObject));
|
||
|
||
if (pathPoints == null || pathPoints.Count < 2)
|
||
throw new ArgumentException("路径点数量必须至少为2个", nameof(pathPoints));
|
||
|
||
// 添加路径点坐标有效性验证
|
||
LogManager.Info("=== 动画管理器坐标验证 ===");
|
||
bool hasInvalidCoordinates = false;
|
||
for (int i = 0; i < pathPoints.Count; i++)
|
||
{
|
||
var point = pathPoints[i];
|
||
bool isValid = !double.IsNaN(point.X) && !double.IsNaN(point.Y) && !double.IsNaN(point.Z) &&
|
||
!double.IsInfinity(point.X) && !double.IsInfinity(point.Y) && !double.IsInfinity(point.Z);
|
||
|
||
LogManager.Info($"路径点[{i}]坐标验证: ({point.X:F6},{point.Y:F6},{point.Z:F6}) - {(isValid ? "有效" : "无效")}");
|
||
|
||
if (!isValid)
|
||
{
|
||
hasInvalidCoordinates = true;
|
||
LogManager.Error($"检测到无效坐标: 路径点[{i}] = ({point.X},{point.Y},{point.Z})");
|
||
}
|
||
|
||
// 检查是否为零坐标(可能表示数据丢失)
|
||
if (isValid && point.X == 0.0 && point.Y == 0.0 && point.Z == 0.0)
|
||
{
|
||
LogManager.Warning($"路径点[{i}]坐标为零,可能存在数据丢失问题");
|
||
}
|
||
}
|
||
|
||
if (hasInvalidCoordinates)
|
||
{
|
||
throw new ArgumentException("路径点包含无效坐标(NaN或无穷大),无法创建动画");
|
||
}
|
||
|
||
LogManager.Info("=== 坐标验证完成 ===");
|
||
|
||
_animatedObject = animatedObject;
|
||
_pathPoints = new List<Point3D>(pathPoints);
|
||
_animationDuration = durationSeconds;
|
||
|
||
// 保存原始变换以便重置
|
||
_originalTransform = GetCurrentTransform(_animatedObject);
|
||
|
||
// 保存车辆的原始中心位置
|
||
var originalBoundingBox = animatedObject.BoundingBox();
|
||
_originalCenter = originalBoundingBox.Center;
|
||
|
||
// 关键修复:将车辆立即移动到路径起点
|
||
// 这样动画就从路径起点开始,而不是从车辆当前位置开始
|
||
MoveVehicleToPathStart();
|
||
|
||
// 记录文档单位信息
|
||
var documentUnits = GetDocumentUnitsInfo();
|
||
|
||
// 添加详细的调试信息
|
||
LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 路径点数={_pathPoints.Count}, 时长={_animationDuration}秒");
|
||
LogManager.Info($"文档单位: {documentUnits}");
|
||
LogManager.Info($"路径起点: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
|
||
LogManager.Info($"路径终点: ({_pathPoints[_pathPoints.Count-1].X:F2},{_pathPoints[_pathPoints.Count-1].Y:F2},{_pathPoints[_pathPoints.Count-1].Z:F2})");
|
||
|
||
var totalDist = CalculateTotalPathDistance();
|
||
LogManager.Info($"路径总长度: {totalDist:F2}");
|
||
LogManager.Info($"车辆已移动到路径起点,动画将从起点开始");
|
||
LogManager.Info($"=== 调试信息结束 ===");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"设置动画失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将车辆移动到路径起点
|
||
/// </summary>
|
||
private void MoveVehicleToPathStart()
|
||
{
|
||
try
|
||
{
|
||
if (_pathPoints.Count == 0) return;
|
||
|
||
var doc = NavisApplication.ActiveDocument;
|
||
var modelItems = new ModelItemCollection { _animatedObject };
|
||
|
||
// 获取物体的包围盒
|
||
var boundingBox = _animatedObject.BoundingBox();
|
||
|
||
// 计算物体底面中心点(而不是整体中心点)
|
||
var objectBottomCenter = new Point3D(
|
||
_originalCenter.X,
|
||
_originalCenter.Y,
|
||
boundingBox.Min.Z // 使用包围盒的最小Z值作为底面
|
||
);
|
||
|
||
// 计算从物体底面中心到路径起点的偏移
|
||
var startOffset = new Vector3D(
|
||
_pathPoints[0].X - objectBottomCenter.X,
|
||
_pathPoints[0].Y - objectBottomCenter.Y,
|
||
_pathPoints[0].Z - objectBottomCenter.Z
|
||
);
|
||
|
||
// 创建变换并应用
|
||
var startTransform = Transform3D.CreateTranslation(startOffset);
|
||
doc.Models.OverridePermanentTransform(modelItems, startTransform, false);
|
||
|
||
// 更新当前位置为路径起点
|
||
_currentPosition = _pathPoints[0];
|
||
|
||
LogManager.Info($"物体已移动到路径起点,底面对齐地面");
|
||
LogManager.Info($"物体包围盒: Min=({boundingBox.Min.X:F2},{boundingBox.Min.Y:F2},{boundingBox.Min.Z:F2}), Max=({boundingBox.Max.X:F2},{boundingBox.Max.Y:F2},{boundingBox.Max.Z:F2})");
|
||
LogManager.Info($"物体原始中心: ({_originalCenter.X:F2},{_originalCenter.Y:F2},{_originalCenter.Z:F2})");
|
||
LogManager.Info($"物体底面中心: ({objectBottomCenter.X:F2},{objectBottomCenter.Y:F2},{objectBottomCenter.Z:F2})");
|
||
LogManager.Info($"路径起点坐标: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
|
||
LogManager.Info($"应用的偏移量: ({startOffset.X:F2},{startOffset.Y:F2},{startOffset.Z:F2})");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"移动部件到路径起点失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始播放动画
|
||
/// </summary>
|
||
public void StartAnimation()
|
||
{
|
||
try
|
||
{
|
||
if (_animatedObject == null || _pathPoints.Count < 2)
|
||
{
|
||
throw new InvalidOperationException("请先调用SetupAnimation设置动画参数");
|
||
}
|
||
|
||
// 停止之前的动画
|
||
StopAnimation();
|
||
|
||
// 创建 TimeLiner 任务(如果可用)
|
||
if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable)
|
||
{
|
||
string objectName = "UnknownObject";
|
||
try
|
||
{
|
||
objectName = _animatedObject?.DisplayName ?? "UnknownObject";
|
||
}
|
||
catch (Exception)
|
||
{
|
||
objectName = "DisposedObject";
|
||
}
|
||
var taskName = $"{objectName}_运输_{DateTime.Now:HHmmss}";
|
||
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 不可用,使用基础动画功能");
|
||
}
|
||
|
||
// 设置动态碰撞检测(简化版本)
|
||
SetupDynamicClashDetection();
|
||
|
||
// 初始化动画状态
|
||
_animationStartTime = DateTime.Now;
|
||
_animationFrameCount = 0; // 重置帧计数
|
||
_pausedProgress = 0.0; // 重置暂停进度
|
||
|
||
// 重置Idle模式状态
|
||
_lastFrameTime = DateTime.MinValue;
|
||
_collisionCheckCounter = 0;
|
||
_fpsFrameCount = 0;
|
||
_fpsCounterStart = DateTime.Now;
|
||
_frameInterval = 1000.0 / _animationFrameRate;
|
||
|
||
// 订阅Idle事件
|
||
NavisApplication.Idle += OnApplicationIdle;
|
||
|
||
SetState(AnimationState.Playing);
|
||
LogManager.Info("动画开始播放 - Idle事件模式");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"启动动画失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止动画
|
||
/// </summary>
|
||
public void StopAnimation()
|
||
{
|
||
try
|
||
{
|
||
// 检查动画状态,避免重复停止
|
||
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
|
||
{
|
||
LogManager.Debug($"动画已处于{_currentState}状态,跳过重复停止操作");
|
||
return;
|
||
}
|
||
|
||
// 注销Idle事件
|
||
NavisApplication.Idle -= OnApplicationIdle;
|
||
LogManager.Debug("[Idle模式] Idle事件已注销");
|
||
|
||
SetState(AnimationState.Stopped);
|
||
_pausedProgress = 0.0; // 重置暂停进度
|
||
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>
|
||
/// 完成动画(当动画自然播放结束时调用)
|
||
/// </summary>
|
||
private void FinishAnimation()
|
||
{
|
||
try
|
||
{
|
||
// 注销Idle事件
|
||
NavisApplication.Idle -= OnApplicationIdle;
|
||
|
||
// 重置暂停进度
|
||
_pausedProgress = 0.0;
|
||
|
||
// 直接设置为完成状态,避免中间状态切换
|
||
SetState(AnimationState.Finished);
|
||
|
||
LogManager.Info($"动画播放完成,总帧数: {_animationFrameCount}, 平均FPS: {_actualFPS:F1}");
|
||
|
||
// 触发旧版完成事件(保持兼容性)
|
||
AnimationCompleted?.Invoke(this, EventArgs.Empty);
|
||
|
||
// 更新 TimeLiner 任务状态
|
||
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
|
||
{
|
||
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 1.0, AnimationState.Finished);
|
||
}
|
||
|
||
// 动画结束后统一创建所有碰撞测试(基于官方示例的批量处理)
|
||
LogManager.Info($"动画播放完成,开始创建最终的碰撞测试汇总(检测间隙: {_detectionGap}米)...");
|
||
ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests(_detectionGap);
|
||
}
|
||
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);
|
||
|
||
// 暂时注销Idle事件
|
||
NavisApplication.Idle -= OnApplicationIdle;
|
||
LogManager.Debug("[Idle模式] 暂停时注销Idle事件");
|
||
|
||
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));
|
||
|
||
// 重新订阅Idle事件
|
||
_lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行
|
||
NavisApplication.Idle += OnApplicationIdle;
|
||
LogManager.Debug("[Idle模式] 恢复时重新订阅Idle事件");
|
||
|
||
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)
|
||
{
|
||
StopAnimation(); // 停止当前动画
|
||
}
|
||
|
||
// 恢复对象的原始变换(安全检查Navisworks对象可用性)
|
||
if (_animatedObject != null)
|
||
{
|
||
try
|
||
{
|
||
// 检查Navisworks应用程序和文档是否仍然可用
|
||
var activeDoc = NavisApplication.ActiveDocument;
|
||
if (activeDoc != null && activeDoc.Models != null)
|
||
{
|
||
// 先安全获取对象名称,避免访问已释放对象的属性
|
||
string objectName = GetSafeObjectName(_animatedObject);
|
||
|
||
var modelItems = new ModelItemCollection { _animatedObject };
|
||
activeDoc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
|
||
LogManager.Info($"部件 {objectName} 已重置到原始位置");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info("Navisworks文档已不可用,跳过对象重置操作");
|
||
}
|
||
}
|
||
catch (Exception resetEx)
|
||
{
|
||
LogManager.Warning($"重置对象位置时出现警告(可能因为Navisworks对象已释放): {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 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)
|
||
{
|
||
try
|
||
{
|
||
var doc = NavisApplication.ActiveDocument;
|
||
var modelItems = new ModelItemCollection { _animatedObject };
|
||
|
||
// 正确的增量变换:计算从当前位置到新位置的偏移
|
||
var incrementalOffset = new Vector3D(
|
||
newPosition.X - _currentPosition.X,
|
||
newPosition.Y - _currentPosition.Y,
|
||
newPosition.Z - _currentPosition.Z
|
||
);
|
||
|
||
// 创建增量变换
|
||
var incrementalTransform = Transform3D.CreateTranslation(incrementalOffset);
|
||
|
||
// 应用增量变换(不重置之前的变换)
|
||
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
|
||
|
||
LogManager.Debug($"部件位置更新:从({_currentPosition.X:F2},{_currentPosition.Y:F2},{_currentPosition.Z:F2})到({newPosition.X:F2},{newPosition.Y:F2},{newPosition.Z:F2}),增量偏移({incrementalOffset.X:F2},{incrementalOffset.Y:F2},{incrementalOffset.Z:F2})");
|
||
|
||
// 更新当前位置
|
||
_currentPosition = newPosition;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"更新部件位置失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从车辆变换矩阵中获取真实的缩放系数
|
||
/// 注意:此方法已废弃但保留,用于车辆模型的缩放处理。
|
||
/// 现在使用场馆部件进行动画,通常不需要复杂的缩放计算。
|
||
/// </summary>
|
||
private double GetVehicleScaleFactor()
|
||
{
|
||
try
|
||
{
|
||
var transform = _animatedObject.Transform;
|
||
var linear = transform.Linear;
|
||
|
||
// 获取变换矩阵的第一行的长度作为缩放系数
|
||
// 变换矩阵: (a, b, c)
|
||
// (d, e, f)
|
||
// (g, h, i)
|
||
// 缩放系数 ≈ sqrt(a² + b² + c²)
|
||
var scaleX = Math.Sqrt(
|
||
linear.Get(0, 0) * linear.Get(0, 0) +
|
||
linear.Get(0, 1) * linear.Get(0, 1) +
|
||
linear.Get(0, 2) * linear.Get(0, 2)
|
||
);
|
||
|
||
LogManager.Debug($"车辆缩放系数计算: {scaleX:F2} (变换矩阵第一行模长)");
|
||
|
||
// 如果缩放系数接近1550(39.37²),说明是米到英寸的平方缩放
|
||
if (Math.Abs(scaleX - 1550.0) < 10.0)
|
||
{
|
||
var linearScale = Math.Sqrt(scaleX);
|
||
LogManager.Info($"检测到车辆使用平方缩放系数: {scaleX:F2},已修正为线性缩放系数: {linearScale:F2}");
|
||
return linearScale;
|
||
}
|
||
// 如果缩放系数接近39.37,说明是简单的米到英寸缩放
|
||
else if (Math.Abs(scaleX - 39.37) < 1.0)
|
||
{
|
||
LogManager.Info($"检测到车辆使用线性缩放系数: {scaleX:F2}");
|
||
return scaleX;
|
||
}
|
||
// 如果接近1,说明没有缩放
|
||
else if (Math.Abs(scaleX - 1.0) < 0.1)
|
||
{
|
||
LogManager.Info($"车辆无缩放: {scaleX:F2}");
|
||
return 1.0;
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning($"未知的车辆缩放系数: {scaleX:F2},使用原值");
|
||
return scaleX;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"获取车辆缩放系数失败: {ex.Message},使用默认值1.0");
|
||
return 1.0;
|
||
}
|
||
}
|
||
|
||
/// <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>
|
||
/// 使用 Clash Detective 集成进行碰撞检测并使用缓存的碰撞结果
|
||
/// </summary>
|
||
private void CheckAndHighlightCollisionsWithClashDetective()
|
||
{
|
||
try
|
||
{
|
||
if (_animatedObject == null)
|
||
return;
|
||
|
||
// 使用 Clash Detective 集成进行碰撞检测,传递检测间隙参数
|
||
var collisionResults = ClashDetectiveIntegration.Instance.DetectCollisions(_animatedObject, null, _detectionGap);
|
||
|
||
// 高亮显示碰撞对象
|
||
ClashDetectiveIntegration.Instance.HighlightCollisions(collisionResults);
|
||
|
||
// 缓存碰撞结果,动画结束后统一处理
|
||
if (collisionResults.Count > 0)
|
||
{
|
||
LogManager.Info($"=== [动画运行中] 检测到 {collisionResults.Count} 个碰撞,开始记录详细位置 ===");
|
||
|
||
// 缓存所有碰撞结果,包含位置信息
|
||
var animatedObjectPosition = GetObjectPosition(_animatedObject);
|
||
LogManager.Info($"[动画位置] 动画对象 {_animatedObject.DisplayName}: ({animatedObjectPosition.X:F2},{animatedObjectPosition.Y:F2},{animatedObjectPosition.Z:F2})");
|
||
|
||
int collisionIndex = 0;
|
||
foreach (var collision in collisionResults)
|
||
{
|
||
collisionIndex++;
|
||
var collisionObjectPosition = GetObjectPosition(collision.Item2);
|
||
LogManager.Info($"[碰撞位置{collisionIndex}] 动画对象 vs {collision.Item2.DisplayName}:");
|
||
LogManager.Info($" 动画物体位置: ({animatedObjectPosition.X:F2},{animatedObjectPosition.Y:F2},{animatedObjectPosition.Z:F2})");
|
||
LogManager.Info($" 碰撞物体位置: ({collisionObjectPosition.X:F2},{collisionObjectPosition.Y:F2},{collisionObjectPosition.Z:F2})");
|
||
LogManager.Info($" 两物体距离: {CalculateDistance(animatedObjectPosition, collisionObjectPosition):F2}");
|
||
LogManager.Info($" 碰撞状态: 已检测到碰撞");
|
||
|
||
ClashDetectiveIntegration.Instance.CacheCollisionDuringAnimation(_animatedObject, animatedObjectPosition, collision.Item2, collisionObjectPosition);
|
||
}
|
||
|
||
LogManager.Info($"=== [动画运行中] 位置记录完成 ===");
|
||
}
|
||
|
||
LogManager.Debug($"碰撞检测完成: {collisionResults.Count} 个碰撞 (已缓存)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"碰撞检测失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置动态碰撞检测(实时模式,集成 Clash Detective)
|
||
/// </summary>
|
||
private void SetupDynamicClashDetection()
|
||
{
|
||
try
|
||
{
|
||
// 初始化 Clash Detective 集成
|
||
ClashDetectiveIntegration.Instance.Initialize();
|
||
|
||
// 订阅碰撞检测事件
|
||
ClashDetectiveIntegration.Instance.CollisionDetected += OnClashDetectiveCollisionDetected;
|
||
|
||
LogManager.Info("动态碰撞检测设置完成(实时模式)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"设置动态碰撞检测失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理 Clash Detective 碰撞检测事件
|
||
/// </summary>
|
||
private void OnClashDetectiveCollisionDetected(object sender, CollisionDetectedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 将碰撞结果转发到动画管理器的事件
|
||
OnCollisionDetected(e);
|
||
|
||
LogManager.Debug($"Clash Detective 检测到 {e.CollisionCount} 个碰撞");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"处理 Clash Detective 碰撞事件失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发碰撞检测事件
|
||
/// </summary>
|
||
protected virtual void OnCollisionDetected(CollisionDetectedEventArgs e)
|
||
{
|
||
CollisionDetected?.Invoke(this, e);
|
||
}
|
||
|
||
// 已删除:CheckAndHighlightCollisions - 使用Clash Detective集成代替
|
||
|
||
/// <summary>
|
||
/// 检查两个包围盒是否相交
|
||
/// </summary>
|
||
private bool BoundingBoxesIntersect(BoundingBox3D box1, BoundingBox3D box2)
|
||
{
|
||
return !(box1.Max.X < box2.Min.X || box2.Max.X < box1.Min.X ||
|
||
box1.Max.Y < box2.Min.Y || box2.Max.Y < box1.Min.Y ||
|
||
box1.Max.Z < box2.Min.Z || box2.Max.Z < box1.Min.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;
|
||
|
||
/// <summary>
|
||
/// 清理对象引用
|
||
/// </summary>
|
||
public void ClearReferences()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[PathAnimationManager] 清理对象引用");
|
||
|
||
// 停止动画
|
||
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
|
||
{
|
||
StopAnimation();
|
||
}
|
||
|
||
// 清空对象引用
|
||
_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)
|
||
{
|
||
StopAnimation();
|
||
}
|
||
else
|
||
{
|
||
LogManager.Debug($"动画已处于{_currentState}状态,跳过停止操作");
|
||
}
|
||
}
|
||
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. 取消订阅文档事件
|
||
DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
|
||
|
||
LogManager.Info("PathAnimationManager资源清理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"PathAnimationManager资源清理过程中发生异常: {ex.Message}");
|
||
// 不抛出异常,避免影响应用程序正常关闭
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 快速测试TimeLiner API的可用性
|
||
/// </summary>
|
||
public bool TestTimeLinerAPI()
|
||
{
|
||
try
|
||
{
|
||
var doc = NavisApplication.ActiveDocument;
|
||
|
||
// 尝试访问TimeLiner
|
||
var timeliner = doc.Timeliner;
|
||
if (timeliner != null)
|
||
{
|
||
LogManager.Info("TimeLiner API基本可用");
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info("TimeLiner API不可用");
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Info($"TimeLiner API测试失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 简化的动画设置方法,支持直接传入部件和路径
|
||
/// </summary>
|
||
/// <param name="component">部件模型</param>
|
||
/// <param name="pathPoints">路径点列表</param>
|
||
/// <param name="durationSeconds">动画持续时间(秒)</param>
|
||
public bool SetupSimpleAnimation(ModelItem component, List<Point3D> pathPoints, double durationSeconds = 10.0)
|
||
{
|
||
try
|
||
{
|
||
if (component == null)
|
||
{
|
||
LogManager.Error("部件模型不能为空");
|
||
return false;
|
||
}
|
||
|
||
if (pathPoints == null || pathPoints.Count < 2)
|
||
{
|
||
LogManager.Error("路径点数量必须至少为2个");
|
||
return false;
|
||
}
|
||
|
||
// 使用现有的SetupAnimation方法
|
||
SetupAnimation(component, pathPoints, durationSeconds);
|
||
|
||
LogManager.Info($"简化动画设置成功:部件={component.DisplayName}, 路径点数={pathPoints.Count}, 时长={durationSeconds}秒");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"简化动画设置失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前选中的部件模型(简化版本)
|
||
/// </summary>
|
||
/// <returns>选中的部件模型,如果没有选中或选中多个则返回null</returns>
|
||
public static ModelItem GetSelectedComponent()
|
||
{
|
||
try
|
||
{
|
||
var doc = NavisApplication.ActiveDocument;
|
||
var selectedItems = doc.CurrentSelection.SelectedItems;
|
||
|
||
if (selectedItems.Count == 1)
|
||
{
|
||
var item = selectedItems.First;
|
||
if (item.HasGeometry)
|
||
{
|
||
LogManager.Info($"已获取选中部件: {item.DisplayName}");
|
||
return item;
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("选中的项目没有几何体,可能不适合用于动画");
|
||
return null;
|
||
}
|
||
}
|
||
else if (selectedItems.Count == 0)
|
||
{
|
||
LogManager.Info("没有选中任何项目");
|
||
return null;
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info($"选中了{selectedItems.Count}个项目,请只选择一个部件");
|
||
return null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"获取选中部件失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 设置动画帧率
|
||
/// </summary>
|
||
public void SetAnimationFrameRate(int frameRate)
|
||
{
|
||
_animationFrameRate = Math.Max(10, Math.Min(60, frameRate)); // 限制在10-60FPS之间
|
||
LogManager.Info($"动画帧率设置为: {_animationFrameRate}FPS");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置碰撞检测精度
|
||
/// </summary>
|
||
public void SetCollisionDetectionAccuracy(double accuracy)
|
||
{
|
||
_collisionDetectionAccuracy = Math.Max(0.01, accuracy); // 最小0.01米
|
||
LogManager.Info($"碰撞检测精度设置为: {_collisionDetectionAccuracy}m");
|
||
}
|
||
|
||
/// <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 gap)
|
||
{
|
||
var roundedGap = Math.Round(Math.Max(0.0, gap), 2);
|
||
// 只在值真正改变时才更新和记录日志
|
||
if (Math.Abs(_detectionGap - roundedGap) > 0.001)
|
||
{
|
||
_detectionGap = roundedGap;
|
||
LogManager.Info($"间隙={_detectionGap:F2}m");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前动画帧率
|
||
/// </summary>
|
||
public int AnimationFrameRate => _animationFrameRate;
|
||
|
||
/// <summary>
|
||
/// 获取当前碰撞检测精度
|
||
/// </summary>
|
||
public double CollisionDetectionAccuracy => _collisionDetectionAccuracy;
|
||
|
||
/// <summary>
|
||
/// 获取当前运动速度
|
||
/// </summary>
|
||
public double MovementSpeed => _movementSpeed;
|
||
|
||
/// <summary>
|
||
/// 获取当前检测间隙
|
||
/// </summary>
|
||
public double DetectionGap => _detectionGap;
|
||
|
||
/// <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>
|
||
public void CreateAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0)
|
||
{
|
||
SetupAnimation(animatedObject, pathPoints, durationSeconds);
|
||
SetState(AnimationState.Ready);
|
||
}
|
||
|
||
|
||
#region 动画实现方法
|
||
|
||
|
||
/// <summary>
|
||
/// Idle事件处理器 - 新的动画核心
|
||
/// </summary>
|
||
private void OnApplicationIdle(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 检查动画状态
|
||
if (_currentState != AnimationState.Playing)
|
||
{
|
||
return; // 非播放状态,跳过更新
|
||
}
|
||
|
||
// 验证动画对象是否仍然有效
|
||
if (!IsAnimatedObjectValid())
|
||
{
|
||
LogManager.Warning("[PathAnimationManager] 动画对象已失效,停止动画");
|
||
StopAnimation();
|
||
return;
|
||
}
|
||
|
||
// 帧率控制
|
||
var now = DateTime.Now;
|
||
var elapsed = (now - _lastFrameTime).TotalMilliseconds;
|
||
|
||
if (elapsed < _frameInterval)
|
||
{
|
||
return; // 还没到下一帧时间,跳过本次Idle
|
||
}
|
||
|
||
// 计算动画进度
|
||
var totalElapsed = (now - _animationStartTime).TotalSeconds;
|
||
var progress = Math.Min(totalElapsed / _animationDuration, 1.0);
|
||
|
||
// 更新动画位置(使用原有逻辑)
|
||
Point3D newPosition = InterpolatePosition(progress);
|
||
UpdateObjectPosition(newPosition);
|
||
|
||
// 触发进度事件
|
||
var progressPercent = progress * 100;
|
||
ProgressChanged?.Invoke(this, progressPercent);
|
||
|
||
// 同步进度到 TimeLiner(如果可用)
|
||
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
|
||
{
|
||
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
|
||
}
|
||
|
||
// 碰撞检测(降频执行,每3帧检测一次)
|
||
_collisionCheckCounter++;
|
||
if (_collisionCheckCounter >= 3)
|
||
{
|
||
_collisionCheckCounter = 0;
|
||
if (_currentState == AnimationState.Playing)
|
||
{
|
||
CheckAndHighlightCollisionsWithClashDetective();
|
||
}
|
||
}
|
||
|
||
// 记录帧时间
|
||
_lastFrameTime = now;
|
||
_animationFrameCount++;
|
||
|
||
// 性能监控
|
||
UpdateFPSCounter();
|
||
|
||
// 检查动画完成
|
||
if (progress >= 1.0)
|
||
{
|
||
// 先注销事件,避免重复调用
|
||
NavisApplication.Idle -= OnApplicationIdle;
|
||
FinishAnimation();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[Idle模式] 动画更新异常: {ex.Message}");
|
||
// 发生异常时停止动画,避免持续错误
|
||
StopAnimation();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新FPS计数器
|
||
/// </summary>
|
||
private void UpdateFPSCounter()
|
||
{
|
||
_fpsFrameCount++;
|
||
var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds;
|
||
|
||
// 每秒更新一次FPS统计
|
||
if (elapsed >= 1.0)
|
||
{
|
||
_actualFPS = _fpsFrameCount / elapsed;
|
||
LogManager.Debug($"[性能监控] 实际FPS: {_actualFPS:F1} (目标: {_animationFrameRate})");
|
||
|
||
// 重置计数器
|
||
_fpsFrameCount = 0;
|
||
_fpsCounterStart = DateTime.Now;
|
||
}
|
||
}
|
||
|
||
|
||
#endregion
|
||
}
|
||
} |