把动画从Timer改成Idle事件机制
This commit is contained in:
parent
bd74b42df3
commit
9924c3b304
296
doc/working/idle_event_animation_strategy.md
Normal file
296
doc/working/idle_event_animation_strategy.md
Normal file
@ -0,0 +1,296 @@
|
||||
# Idle事件动画实现策略
|
||||
|
||||
基于Autodesk官方文章和实践分析,采用Application.Idle事件替代Timer实现动画。
|
||||
|
||||
## 背景问题
|
||||
|
||||
当前使用Timer实现存在的问题:
|
||||
|
||||
1. Timer更新可能与UI刷新不同步,导致动画卡顿
|
||||
2. 使用Thread.Sleep()会导致"先延迟后一次性执行"的问题
|
||||
3. Timer在系统繁忙时可能积压更新
|
||||
|
||||
## Idle事件方案优势
|
||||
|
||||
1. **与UI刷新同步**:Idle事件在UI消息循环空闲时触发,确保画面刷新
|
||||
2. **避免积压**:不会出现更新积压问题
|
||||
3. **系统资源友好**:不会在系统繁忙时强制更新
|
||||
4. **实现简单**:不需要管理定时器生命周期
|
||||
|
||||
## 核心实现
|
||||
|
||||
### 1. 基础Idle动画框架
|
||||
|
||||
```csharp
|
||||
public class PathAnimationManager
|
||||
{
|
||||
// 帧率控制
|
||||
private int _targetFPS = 30; // 目标帧率
|
||||
private double _frameInterval; // 每帧间隔(毫秒)
|
||||
private DateTime _lastFrameTime = DateTime.MinValue;
|
||||
|
||||
// 动画状态
|
||||
private double _animationDuration = 10.0; // 秒
|
||||
private DateTime _animationStartTime;
|
||||
private AnimationState _currentState = AnimationState.Idle;
|
||||
|
||||
public void StartAnimation()
|
||||
{
|
||||
_frameInterval = 1000.0 / _targetFPS;
|
||||
_animationStartTime = DateTime.Now;
|
||||
_lastFrameTime = DateTime.MinValue;
|
||||
|
||||
// 使用Idle事件替代Timer
|
||||
Autodesk.Navisworks.Api.Application.Idle += OnApplicationIdle;
|
||||
|
||||
SetState(AnimationState.Playing);
|
||||
LogManager.Info($"动画开始:Idle事件模式,目标 {_targetFPS} FPS");
|
||||
}
|
||||
|
||||
private void OnApplicationIdle(object sender, EventArgs e)
|
||||
{
|
||||
// 帧率控制
|
||||
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);
|
||||
|
||||
// 更新动画
|
||||
UpdateAnimation(progress);
|
||||
|
||||
// 触发进度事件
|
||||
ProgressChanged?.Invoke(this, progress * 100);
|
||||
|
||||
// 记录帧时间
|
||||
_lastFrameTime = now;
|
||||
|
||||
// 检查完成
|
||||
if (progress >= 1.0)
|
||||
{
|
||||
StopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAnimation()
|
||||
{
|
||||
// 注销Idle事件
|
||||
Autodesk.Navisworks.Api.Application.Idle -= OnApplicationIdle;
|
||||
|
||||
SetState(AnimationState.Stopped);
|
||||
LogManager.Info("动画停止");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 暂停/恢复机制
|
||||
|
||||
```csharp
|
||||
public class PausableIdleAnimation
|
||||
{
|
||||
private double _pausedProgress = 0.0;
|
||||
private TimeSpan _pausedElapsed = TimeSpan.Zero;
|
||||
private bool _isPaused = false;
|
||||
|
||||
public void PauseAnimation()
|
||||
{
|
||||
if (_currentState != AnimationState.Playing)
|
||||
return;
|
||||
|
||||
// 记录暂停时的进度
|
||||
var elapsed = DateTime.Now - _animationStartTime;
|
||||
_pausedElapsed = elapsed;
|
||||
_pausedProgress = Math.Min(elapsed.TotalSeconds / _animationDuration, 1.0);
|
||||
|
||||
// 暂时注销Idle事件
|
||||
Autodesk.Navisworks.Api.Application.Idle -= OnApplicationIdle;
|
||||
|
||||
_isPaused = true;
|
||||
SetState(AnimationState.Paused);
|
||||
}
|
||||
|
||||
public void ResumeAnimation()
|
||||
{
|
||||
if (_currentState != AnimationState.Paused)
|
||||
return;
|
||||
|
||||
// 调整开始时间,使进度连续
|
||||
_animationStartTime = DateTime.Now - _pausedElapsed;
|
||||
_lastFrameTime = DateTime.MinValue;
|
||||
|
||||
// 重新订阅Idle事件
|
||||
Autodesk.Navisworks.Api.Application.Idle += OnApplicationIdle;
|
||||
|
||||
_isPaused = false;
|
||||
SetState(AnimationState.Playing);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 性能监控
|
||||
|
||||
```csharp
|
||||
public class PerformanceMonitor
|
||||
{
|
||||
private int _frameCount = 0;
|
||||
private DateTime _fpsCounterStart = DateTime.Now;
|
||||
private double _actualFPS = 0;
|
||||
private Queue<double> _frameIntervals = new Queue<double>(30);
|
||||
|
||||
public void RecordFrame(double interval)
|
||||
{
|
||||
_frameCount++;
|
||||
_frameIntervals.Enqueue(interval);
|
||||
|
||||
if (_frameIntervals.Count > 30)
|
||||
_frameIntervals.Dequeue();
|
||||
|
||||
// 每秒更新FPS
|
||||
var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds;
|
||||
if (elapsed >= 1.0)
|
||||
{
|
||||
_actualFPS = _frameCount / elapsed;
|
||||
LogManager.Debug($"实际FPS: {_actualFPS:F1}, 平均帧间隔: {_frameIntervals.Average():F1}ms");
|
||||
|
||||
_frameCount = 0;
|
||||
_fpsCounterStart = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 碰撞检测集成
|
||||
|
||||
```csharp
|
||||
private void OnApplicationIdle(object sender, EventArgs e)
|
||||
{
|
||||
// 帧率控制
|
||||
if (!ShouldUpdateFrame())
|
||||
return;
|
||||
|
||||
// 更新动画
|
||||
var progress = UpdateAnimation();
|
||||
|
||||
// 碰撞检测(降频执行)
|
||||
if (ShouldCheckCollision())
|
||||
{
|
||||
CheckCollisions();
|
||||
}
|
||||
|
||||
_lastFrameTime = DateTime.Now;
|
||||
}
|
||||
|
||||
private bool ShouldCheckCollision()
|
||||
{
|
||||
// 碰撞检测可以降频,比如每3帧检测一次
|
||||
return _frameCount % 3 == 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 第一步:保留原Timer代码(注释)
|
||||
|
||||
```csharp
|
||||
// === 原Timer实现(保留备用) ===
|
||||
/*
|
||||
private Timer _animationTimer;
|
||||
private Timer _collisionTimer;
|
||||
|
||||
private void StartAnimation_Timer()
|
||||
{
|
||||
_animationTimer = new Timer();
|
||||
_animationTimer.Interval = (int)(1000.0 / _animationFrameRate);
|
||||
_animationTimer.Tick += AnimationTimer_Tick;
|
||||
_animationTimer.Start();
|
||||
}
|
||||
|
||||
private void AnimationTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
// 原动画更新逻辑
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
### 第二步:实现Idle事件版本
|
||||
|
||||
```csharp
|
||||
// === 新Idle事件实现 ===
|
||||
private void StartAnimation_Idle()
|
||||
{
|
||||
_frameInterval = 1000.0 / _targetFPS;
|
||||
_animationStartTime = DateTime.Now;
|
||||
_lastFrameTime = DateTime.MinValue;
|
||||
|
||||
// 订阅Idle事件
|
||||
Autodesk.Navisworks.Api.Application.Idle += OnApplicationIdle;
|
||||
|
||||
LogManager.Info($"[Idle模式] 动画启动,目标FPS: {_targetFPS}");
|
||||
}
|
||||
```
|
||||
|
||||
### 第三步:添加切换机制
|
||||
|
||||
```csharp
|
||||
public enum AnimationMode
|
||||
{
|
||||
Timer, // 原Timer模式(备用)
|
||||
Idle // 新Idle事件模式(默认)
|
||||
}
|
||||
|
||||
private AnimationMode _animationMode = AnimationMode.Idle;
|
||||
|
||||
public void StartAnimation()
|
||||
{
|
||||
if (_animationMode == AnimationMode.Idle)
|
||||
{
|
||||
StartAnimation_Idle();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartAnimation_Timer(); // 备用方案
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **事件注销**:必须在适当时机注销Idle事件,避免内存泄漏
|
||||
2. **帧率控制**:通过时间检查控制实际更新频率
|
||||
3. **进度计算**:基于实际时间而非帧数,确保动画时长准确
|
||||
4. **异常处理**:Idle事件处理器中的异常要妥善处理
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 指标 | Timer方式 | Idle事件方式 |
|
||||
|-----|----------|-------------|
|
||||
| UI同步性 | 可能不同步 | 完美同步 |
|
||||
| CPU占用 | 固定开销 | 按需使用 |
|
||||
| 实现复杂度 | 需管理定时器 | 简单直接 |
|
||||
| 帧率稳定性 | 较稳定 | 受系统负载影响 |
|
||||
| 响应性 | 一般 | 更好 |
|
||||
|
||||
## 预期效果
|
||||
|
||||
1. **更流畅的动画**:与UI刷新完美同步
|
||||
2. **更低的资源占用**:只在需要时更新
|
||||
3. **更好的稳定性**:避免Timer积压问题
|
||||
4. **保持功能完整**:支持暂停、恢复、进度跟踪等
|
||||
|
||||
## 回滚方案
|
||||
|
||||
如果Idle事件方式出现问题,可以快速切换回Timer模式:
|
||||
|
||||
```csharp
|
||||
_animationMode = AnimationMode.Timer; // 切换回Timer模式
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
- Autodesk官方文章:《Transform Object with a Series of Moves》
|
||||
- Navisworks API文档:Application.Idle事件
|
||||
- 最佳实践:与UI消息循环同步的动画实现
|
||||
314
doc/working/idle_event_ui_improvement_strategy.md
Normal file
314
doc/working/idle_event_ui_improvement_strategy.md
Normal file
@ -0,0 +1,314 @@
|
||||
# Idle事件方法在项目其他地方的应用方案
|
||||
|
||||
## 背景
|
||||
|
||||
在成功将PathAnimationManager的动画系统从Timer改为Idle事件后,动画运行变得非常流畅。这证明了Idle事件方法的优势:
|
||||
- 与UI刷新同步
|
||||
- 避免更新积压
|
||||
- 系统资源友好
|
||||
|
||||
现在考虑将这种方法应用到项目的其他地方,改进UI卡顿问题。
|
||||
|
||||
## 当前问题分析
|
||||
|
||||
### 1. UIStateManager的Timer机制
|
||||
- 使用`System.Threading.Timer`定期刷新(500ms间隔)
|
||||
- 固定间隔可能造成延迟感
|
||||
- 与UI刷新不同步
|
||||
|
||||
### 2. 大量Task.Delay使用
|
||||
项目中发现多处使用Task.Delay:
|
||||
- AnimationControlViewModel: `Task.Delay(1000)` 等待碰撞检测
|
||||
- SystemManagementViewModel: `Thread.Sleep(2000)` 模拟检查更新
|
||||
- LogisticsControlViewModel: `Thread.Sleep(2000)`
|
||||
- 其他多处延迟操作
|
||||
|
||||
### 3. DispatcherTimer的固定间隔更新
|
||||
- SystemManagementViewModel: 5秒间隔更新性能信息
|
||||
- AnimationControlViewModel: 参数更新防抖机制
|
||||
- 固定间隔可能与实际需求不匹配
|
||||
|
||||
## 改进方案
|
||||
|
||||
### 1. UIStateManager改进 - 用Idle事件替代Timer刷新机制
|
||||
|
||||
**现有代码问题**:
|
||||
```csharp
|
||||
private System.Threading.Timer _flushTimer;
|
||||
private readonly int _flushInterval = 500; // 500ms保底刷新间隔
|
||||
```
|
||||
|
||||
**改进方案**:
|
||||
```csharp
|
||||
// 使用Idle事件处理更新队列
|
||||
private void OnApplicationIdle(object sender, EventArgs e)
|
||||
{
|
||||
// 时间控制,避免过于频繁
|
||||
if ((DateTime.Now - _lastProcessTime).TotalMilliseconds < 50) return;
|
||||
|
||||
// 处理更新队列
|
||||
ProcessUpdateQueue();
|
||||
_lastProcessTime = DateTime.Now;
|
||||
}
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 更新与UI刷新同步
|
||||
- 避免固定延迟
|
||||
- 减少不必要的更新
|
||||
|
||||
### 2. AnimationControlViewModel - 替换Task.Delay延迟检查
|
||||
|
||||
**现有代码问题**:
|
||||
```csharp
|
||||
// 给一个短暂延迟,让CreateAllAnimationCollisionTests有时间完成
|
||||
Task.Delay(1000).ContinueWith(_ => _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
// 检查碰撞检测结果
|
||||
}));
|
||||
```
|
||||
|
||||
**改进方案**:
|
||||
```csharp
|
||||
// 使用Idle事件监听完成状态
|
||||
private bool _waitingForCollisionResults = false;
|
||||
|
||||
private void OnAnimationCompleted()
|
||||
{
|
||||
_waitingForCollisionResults = true;
|
||||
Application.Idle += OnIdleCheckCollisionResults;
|
||||
}
|
||||
|
||||
private void OnIdleCheckCollisionResults(object sender, EventArgs e)
|
||||
{
|
||||
if (ClashDetectiveIntegration.Instance.IsProcessingComplete)
|
||||
{
|
||||
Application.Idle -= OnIdleCheckCollisionResults;
|
||||
UpdateCollisionResults();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 响应更及时
|
||||
- 避免固定1秒延迟
|
||||
- 实际完成时立即响应
|
||||
|
||||
### 3. SystemManagementViewModel - 性能监控改进
|
||||
|
||||
**现有代码问题**:
|
||||
```csharp
|
||||
_performanceTimer = new System.Windows.Threading.DispatcherTimer();
|
||||
_performanceTimer.Interval = TimeSpan.FromSeconds(5);
|
||||
```
|
||||
|
||||
**改进方案**:
|
||||
```csharp
|
||||
private DateTime _lastPerformanceUpdate = DateTime.MinValue;
|
||||
|
||||
private void OnApplicationIdle(object sender, EventArgs e)
|
||||
{
|
||||
// 控制更新频率:5秒更新一次
|
||||
if ((DateTime.Now - _lastPerformanceUpdate).TotalSeconds < 5) return;
|
||||
|
||||
UpdatePerformanceMetrics();
|
||||
_lastPerformanceUpdate = DateTime.Now;
|
||||
}
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 只在UI空闲时更新
|
||||
- 不会干扰用户操作
|
||||
- 更灵活的更新时机
|
||||
|
||||
### 4. 耗时操作优化 - 使用Idle事件分片处理
|
||||
|
||||
对于长时间操作,可以分解为小片段在Idle中执行:
|
||||
|
||||
```csharp
|
||||
public class IdleTaskProcessor
|
||||
{
|
||||
private Queue<Action> _taskQueue = new Queue<Action>();
|
||||
private DateTime _lastExecutionTime = DateTime.MinValue;
|
||||
|
||||
public void ProcessLargeOperation(IEnumerable<Item> items)
|
||||
{
|
||||
// 将大操作分解为小任务
|
||||
foreach (var batch in items.Batch(10))
|
||||
{
|
||||
_taskQueue.Enqueue(() => ProcessBatch(batch));
|
||||
}
|
||||
|
||||
Application.Idle += OnIdleProcessTasks;
|
||||
}
|
||||
|
||||
private void OnIdleProcessTasks(object sender, EventArgs e)
|
||||
{
|
||||
// 每次Idle只处理一个小任务
|
||||
if (_taskQueue.Count > 0)
|
||||
{
|
||||
var task = _taskQueue.Dequeue();
|
||||
task();
|
||||
|
||||
if (_taskQueue.Count == 0)
|
||||
{
|
||||
Application.Idle -= OnIdleProcessTasks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 创建通用的IdleEventManager类
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 统一的Idle事件管理器
|
||||
/// </summary>
|
||||
public class IdleEventManager
|
||||
{
|
||||
private static IdleEventManager _instance;
|
||||
private readonly Dictionary<string, IdleTask> _tasks = new Dictionary<string, IdleTask>();
|
||||
private bool _isSubscribed = false;
|
||||
|
||||
public static IdleEventManager Instance => _instance ??= new IdleEventManager();
|
||||
|
||||
/// <summary>
|
||||
/// 注册Idle任务
|
||||
/// </summary>
|
||||
public void RegisterTask(string taskId, Action action, double minIntervalMs = 0, int priority = 0)
|
||||
{
|
||||
_tasks[taskId] = new IdleTask
|
||||
{
|
||||
Action = action,
|
||||
MinInterval = minIntervalMs,
|
||||
Priority = priority,
|
||||
LastExecutionTime = DateTime.MinValue
|
||||
};
|
||||
|
||||
EnsureSubscribed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册任务
|
||||
/// </summary>
|
||||
public void UnregisterTask(string taskId)
|
||||
{
|
||||
_tasks.Remove(taskId);
|
||||
|
||||
if (_tasks.Count == 0)
|
||||
{
|
||||
Unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationIdle(object sender, EventArgs e)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 按优先级排序执行
|
||||
var tasksToRun = _tasks.Values
|
||||
.Where(t => (now - t.LastExecutionTime).TotalMilliseconds >= t.MinInterval)
|
||||
.OrderByDescending(t => t.Priority)
|
||||
.ToList();
|
||||
|
||||
foreach (var task in tasksToRun)
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Action();
|
||||
task.LastExecutionTime = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"Idle任务执行失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleTask
|
||||
{
|
||||
public Action Action { get; set; }
|
||||
public double MinInterval { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public DateTime LastExecutionTime { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 具体改进文件列表
|
||||
|
||||
### 高优先级改进
|
||||
1. **UIStateManager.cs** - 核心UI更新机制
|
||||
2. **AnimationControlViewModel.cs** - 移除Task.Delay
|
||||
3. **SystemManagementViewModel.cs** - 性能监控优化
|
||||
|
||||
### 中优先级改进
|
||||
4. **LogisticsControlViewModel.cs** - 替换Thread.Sleep
|
||||
5. **PathAnalysisViewModel.cs** - 优化分析延迟
|
||||
6. **TimeTagViewModel.cs** - 时间标签更新
|
||||
|
||||
### 低优先级改进
|
||||
7. **LogManager.cs** - 日志写入优化
|
||||
8. **CommandExecutor.cs** - 命令执行等待
|
||||
|
||||
## 预期效果
|
||||
|
||||
### 性能提升
|
||||
- **UI响应性提升30-50%** - 避免固定延迟和阻塞
|
||||
- **CPU使用率降低** - 只在需要时执行更新
|
||||
- **内存使用优化** - 减少Timer和Task对象创建
|
||||
|
||||
### 用户体验改进
|
||||
- **操作更流畅** - 与UI刷新完美同步
|
||||
- **延迟感减少** - 实时响应而非固定间隔
|
||||
- **卡顿现象消除** - 避免阻塞主线程
|
||||
|
||||
### 代码质量提升
|
||||
- **统一的更新模式** - 所有UI更新使用相同机制
|
||||
- **更易维护** - 集中管理Idle任务
|
||||
- **更好的错误处理** - 统一的异常捕获
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 第一阶段:基础设施
|
||||
1. 创建IdleEventManager类
|
||||
2. 添加单元测试
|
||||
3. 集成到现有框架
|
||||
|
||||
### 第二阶段:核心组件改进
|
||||
1. 改进UIStateManager
|
||||
2. 优化AnimationControlViewModel
|
||||
3. 测试UI响应性
|
||||
|
||||
### 第三阶段:全面推广
|
||||
1. 逐步替换所有Timer使用
|
||||
2. 消除Task.Delay和Thread.Sleep
|
||||
3. 性能测试和优化
|
||||
|
||||
### 第四阶段:监控和调优
|
||||
1. 添加性能监控
|
||||
2. 收集使用数据
|
||||
3. 持续优化
|
||||
|
||||
## 风险和注意事项
|
||||
|
||||
### 潜在风险
|
||||
1. **过度使用Idle事件** - 可能导致Idle处理过重
|
||||
2. **优先级冲突** - 需要合理安排任务优先级
|
||||
3. **兼容性问题** - 某些场景可能仍需要Timer
|
||||
|
||||
### 缓解措施
|
||||
1. **任务限流** - 控制每次Idle执行的任务数量
|
||||
2. **优先级队列** - 确保重要任务优先执行
|
||||
3. **混合模式** - 保留Timer作为备选方案
|
||||
|
||||
## 结论
|
||||
|
||||
通过将Idle事件方法推广到整个项目,可以显著改善UI性能和用户体验。这种方法已经在PathAnimationManager中得到验证,证明是可行和有效的。建议分阶段实施,逐步改进,确保稳定性。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- Autodesk官方文档:Application.Idle事件使用指南
|
||||
- PathAnimationManager.cs:成功的Idle事件实现案例
|
||||
- doc/working/idle_event_animation_strategy.md:动画系统改进文档
|
||||
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||||
|
||||
@ -20,6 +18,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
Finished // 已完成
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
|
||||
/// 注意:由于Navisworks API限制,无法直接使用Animator API,因此使用OverridePermanentTransform实现动画
|
||||
@ -30,19 +29,25 @@ namespace NavisworksTransport.Core.Animation
|
||||
private ModelItem _animatedObject;
|
||||
private List<Point3D> _pathPoints;
|
||||
|
||||
// 动画渲染系统
|
||||
private Timer _animationTimer;
|
||||
// === 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 Timer _collisionTimer;
|
||||
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; // 存储部件的当前位置
|
||||
@ -76,11 +81,16 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
_pathPoints = new List<Point3D>();
|
||||
|
||||
// 初始化动画模式
|
||||
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
|
||||
_lastFrameTime = DateTime.MinValue;
|
||||
_fpsCounterStart = DateTime.Now;
|
||||
|
||||
// 初始化 TimeLiner 集成
|
||||
try
|
||||
{
|
||||
_timeLinerManager = new TimeLinerIntegrationManager();
|
||||
LogManager.Info("PathAnimationManager 已集成 TimeLiner 功能");
|
||||
LogManager.Info($"PathAnimationManager 初始化完成 - Idle事件模式, 目标FPS: {_animationFrameRate}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -281,24 +291,18 @@ namespace NavisworksTransport.Core.Animation
|
||||
_animationFrameCount = 0; // 重置帧计数
|
||||
_pausedProgress = 0.0; // 重置暂停进度
|
||||
|
||||
// 创建并启动动画渲染定时器
|
||||
_animationTimer = new Timer();
|
||||
_animationTimer.Interval = (int)(1000.0 / _animationFrameRate); // 根据帧率计算间隔
|
||||
_animationTimer.Tick += AnimationTimer_Tick;
|
||||
_animationTimer.Start();
|
||||
|
||||
// 创建并启动碰撞检测定时器
|
||||
var collisionInterval = CalculateCollisionDetectionInterval();
|
||||
_collisionTimer = new Timer();
|
||||
_collisionTimer.Interval = collisionInterval;
|
||||
_collisionTimer.Tick += CollisionTimer_Tick;
|
||||
_collisionTimer.Start();
|
||||
|
||||
LogManager.Info($"动画定时器:{_animationTimer.Interval}ms间隔({_animationFrameRate}FPS)");
|
||||
LogManager.Info($"碰撞检测定时器:{collisionInterval}ms间隔");
|
||||
// 重置Idle模式状态
|
||||
_lastFrameTime = DateTime.MinValue;
|
||||
_collisionCheckCounter = 0;
|
||||
_fpsFrameCount = 0;
|
||||
_fpsCounterStart = DateTime.Now;
|
||||
_frameInterval = 1000.0 / _animationFrameRate;
|
||||
|
||||
// 订阅Idle事件
|
||||
NavisApplication.Idle += OnApplicationIdle;
|
||||
|
||||
SetState(AnimationState.Playing);
|
||||
LogManager.Info("动画开始播放");
|
||||
LogManager.Info("动画开始播放 - Idle事件模式");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -321,8 +325,9 @@ namespace NavisworksTransport.Core.Animation
|
||||
return;
|
||||
}
|
||||
|
||||
// 清理定时器资源
|
||||
CleanupTimers();
|
||||
// 注销Idle事件
|
||||
NavisApplication.Idle -= OnApplicationIdle;
|
||||
LogManager.Debug("[Idle模式] Idle事件已注销");
|
||||
|
||||
SetState(AnimationState.Stopped);
|
||||
_pausedProgress = 0.0; // 重置暂停进度
|
||||
@ -350,8 +355,8 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
try
|
||||
{
|
||||
// 清理定时器资源
|
||||
CleanupTimers();
|
||||
// 注销Idle事件
|
||||
NavisApplication.Idle -= OnApplicationIdle;
|
||||
|
||||
// 重置暂停进度
|
||||
_pausedProgress = 0.0;
|
||||
@ -359,7 +364,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
// 直接设置为完成状态,避免中间状态切换
|
||||
SetState(AnimationState.Finished);
|
||||
|
||||
LogManager.Info("动画播放完成");
|
||||
LogManager.Info($"动画播放完成,总帧数: {_animationFrameCount}, 平均FPS: {_actualFPS:F1}");
|
||||
|
||||
// 触发旧版完成事件(保持兼容性)
|
||||
AnimationCompleted?.Invoke(this, EventArgs.Empty);
|
||||
@ -380,26 +385,6 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理定时器资源的私有方法
|
||||
/// </summary>
|
||||
private void CleanupTimers()
|
||||
{
|
||||
if (_animationTimer != null)
|
||||
{
|
||||
_animationTimer.Stop();
|
||||
_animationTimer.Dispose();
|
||||
_animationTimer = null;
|
||||
}
|
||||
|
||||
if (_collisionTimer != null)
|
||||
{
|
||||
_collisionTimer.Stop();
|
||||
_collisionTimer.Dispose();
|
||||
_collisionTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停动画
|
||||
/// </summary>
|
||||
@ -413,16 +398,9 @@ namespace NavisworksTransport.Core.Animation
|
||||
double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;
|
||||
_pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0);
|
||||
|
||||
// 停止定时器
|
||||
if (_animationTimer != null)
|
||||
{
|
||||
_animationTimer.Stop();
|
||||
}
|
||||
|
||||
if (_collisionTimer != null)
|
||||
{
|
||||
_collisionTimer.Stop();
|
||||
}
|
||||
// 暂时注销Idle事件
|
||||
NavisApplication.Idle -= OnApplicationIdle;
|
||||
LogManager.Debug("[Idle模式] 暂停时注销Idle事件");
|
||||
|
||||
SetState(AnimationState.Paused);
|
||||
LogManager.Info($"动画已暂停,当前进度: {_pausedProgress:F3}");
|
||||
@ -453,16 +431,10 @@ namespace NavisworksTransport.Core.Animation
|
||||
// 设置开始时间,使得当前时间对应保存的暂停进度
|
||||
_animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration));
|
||||
|
||||
// 重新启动定时器
|
||||
if (_animationTimer != null)
|
||||
{
|
||||
_animationTimer.Start();
|
||||
}
|
||||
|
||||
if (_collisionTimer != null)
|
||||
{
|
||||
_collisionTimer.Start();
|
||||
}
|
||||
// 重新订阅Idle事件
|
||||
_lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行
|
||||
NavisApplication.Idle += OnApplicationIdle;
|
||||
LogManager.Debug("[Idle模式] 恢复时重新订阅Idle事件");
|
||||
|
||||
SetState(AnimationState.Playing);
|
||||
LogManager.Info($"动画已恢复,从进度 {_pausedProgress:F3} 继续播放");
|
||||
@ -571,41 +543,6 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画定时器事件处理
|
||||
/// </summary>
|
||||
private void AnimationTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;
|
||||
double progress = Math.Min(elapsedSeconds / _animationDuration, 1.0);
|
||||
|
||||
// 更新UI进度条(直接传递百分比double值)
|
||||
var progressPercent = progress * 100;
|
||||
ProgressChanged?.Invoke(this, progressPercent);
|
||||
|
||||
// 同步进度到 TimeLiner(如果可用)
|
||||
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
|
||||
{
|
||||
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
|
||||
}
|
||||
|
||||
Point3D newPosition = InterpolatePosition(progress);
|
||||
UpdateObjectPosition(newPosition);
|
||||
|
||||
if (progress >= 1.0)
|
||||
{
|
||||
// 避免双重状态设置,直接完成动画并清理资源
|
||||
FinishAnimation();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"动画帧更新失败: {ex.Message}");
|
||||
StopAnimation(); // 发生错误时停止动画
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据进度插值计算当前位置
|
||||
@ -982,7 +919,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
/// <summary>
|
||||
/// 获取动画状态
|
||||
/// </summary>
|
||||
public bool IsAnimating => _animationTimer != null && _animationTimer.Enabled;
|
||||
public bool IsAnimating => _currentState == AnimationState.Playing;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 TimeLiner 是否可用
|
||||
@ -1166,43 +1103,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 碰撞检测定时器事件处理
|
||||
/// </summary>
|
||||
private void CollisionTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只在动画播放时执行碰撞检测
|
||||
if (_currentState == AnimationState.Playing)
|
||||
{
|
||||
CheckAndHighlightCollisionsWithClashDetective();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"碰撞检测失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算碰撞检测间隔(毫秒)
|
||||
/// </summary>
|
||||
private int CalculateCollisionDetectionInterval()
|
||||
{
|
||||
// 碰撞检测频率 = 运动速度 / 检测精度 (次/秒)
|
||||
var detectionFrequency = _movementSpeed / _collisionDetectionAccuracy;
|
||||
|
||||
// 转换为毫秒间隔
|
||||
var intervalMs = (int)(1000.0 / detectionFrequency);
|
||||
|
||||
// 确保间隔不小于50ms(避免过于频繁)
|
||||
intervalMs = Math.Max(50, intervalMs);
|
||||
|
||||
LogManager.Info($"碰撞检测参数:速度={_movementSpeed}m/s, 精度={_collisionDetectionAccuracy}m, 频率={detectionFrequency:F2}次/s, 间隔={intervalMs}ms");
|
||||
|
||||
return intervalMs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置动画帧率
|
||||
@ -1289,5 +1190,106 @@ namespace NavisworksTransport.Core.Animation
|
||||
SetupAnimation(animatedObject, pathPoints, durationSeconds);
|
||||
SetState(AnimationState.Ready);
|
||||
}
|
||||
|
||||
|
||||
#region 动画实现方法
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Idle事件处理器 - 新的动画核心
|
||||
/// </summary>
|
||||
private void OnApplicationIdle(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查动画状态
|
||||
if (_currentState != AnimationState.Playing)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1363,7 +1363,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
_pathAnimationManager.SetMovementSpeed(_movementSpeed);
|
||||
_pathAnimationManager.SetDetectionGap(_detectionGap);
|
||||
|
||||
LogManager.Debug($"碰撞检测参数已更新: 帧率={_animationFrameRate}FPS, 精度={_collisionDetectionAccuracy:F2}m, 速度={_movementSpeed:F1}m/s, 间隙={_detectionGap:F2}m");
|
||||
LogManager.Debug($"碰撞检测参数已更新: 动画帧率={_animationFrameRate}FPS, 精度={_collisionDetectionAccuracy:F2}m, 速度={_movementSpeed:F1}m/s, 间隙={_detectionGap:F2}m");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user