406 lines
47 KiB
Markdown
406 lines
47 KiB
Markdown
步进式动画系统完整方案 │
|
||
│ │
|
||
│ 核心架构 │
|
||
│ │
|
||
│ - 步进式播放:基于帧索引而非时间 │
|
||
│ - 预计算碰撞:提前计算所有碰撞点 │
|
||
│ - 逐帧控制:支持单帧前进/后退 │
|
||
│ - 双向导航:可以在任意帧之间跳转 │
|
||
│ │
|
||
│ 实现方案 │
|
||
│ │
|
||
│ 1. 步进式动画数据结构 │
|
||
│ │
|
||
│ // 动画帧数据 │
|
||
│ public class AnimationFrame │
|
||
│ { │
|
||
│ public int FrameIndex { get; set; } │
|
||
│ public double Progress { get; set; } // 0-1之间的进度 │
|
||
│ public Point3D Position { get; set; } // 该帧的位置 │
|
||
│ public List<CollisionInfo> Collisions { get; set; } // 该帧的碰撞信息 │
|
||
│ public DateTime Timestamp { get; set; } // 帧时间戳(用于性能分析) │
|
||
│ } │
|
||
│ │
|
||
│ public class CollisionInfo │
|
||
│ { │
|
||
│ public ModelItem CollidingObject { get; set; } │
|
||
│ public Point3D CollidingPosition { get; set; } │
|
||
│ public double Distance { get; set; } │
|
||
│ } │
|
||
│ │
|
||
│ // 在PathAnimationManager中添加 │
|
||
│ private List<AnimationFrame> _animationFrames; // 所有动画帧 │
|
||
│ private int _currentFrameIndex = 0; // 当前帧索引 │
|
||
│ private int _totalFrames = 0; // 总帧数 │
|
||
│ private double _frameStepSize = 0.01; // 帧步进大小(1%) │
|
||
│ private bool _isSteppingMode = true; // 是否步进模式 │
|
||
│ │
|
||
│ 2. 预计算所有帧(SetupAnimation) │
|
||
│ │
|
||
│ public void SetupAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0) │
|
||
│ { │
|
||
│ // ... 现有验证代码 ... │
|
||
│ │
|
||
│ // 计算总帧数 │
|
||
│ _totalFrames = (int)(1.0 / _frameStepSize); // 如0.01步进=100帧 │
|
||
│ _animationFrames = new List<AnimationFrame>(); │
|
||
│ │
|
||
│ LogManager.Info($"=== 开始预计算动画帧 ==="); │
|
||
│ LogManager.Info($"总帧数: {_totalFrames}, 步进大小: {_frameStepSize:F3}"); │
|
||
│ │
|
||
│ PrecomputeAllFrames(); │
|
||
│ │
|
||
│ LogManager.Info($"=== 预计算完成 ==="); │
|
||
│ LogManager.Info($"总帧数: {_animationFrames.Count}"); │
|
||
│ LogManager.Info($"包含碰撞的帧: {_animationFrames.Count(f => f.Collisions.Count > 0)}"); │
|
||
│ │
|
||
│ // 移动到起点 │
|
||
│ MoveToFrame(0); │
|
||
│ } │
|
||
│ │
|
||
│ private void PrecomputeAllFrames() │
|
||
│ { │
|
||
│ var modelItems = new ModelItemCollection { _animatedObject }; │
|
||
│ var originalPosition = _currentPosition; │
|
||
│ │
|
||
│ // 初始化碰撞检测 │
|
||
│ ClashDetectiveIntegration.Instance.Initialize(); │
|
||
│ │
|
||
│ for (int i = 0; i <= _totalFrames; i++) │
|
||
│ { │
|
||
│ double progress = (double)i / _totalFrames; │
|
||
│ var framePosition = InterpolatePosition(progress); │
|
||
│ │
|
||
│ // 临时移动到该帧位置 │
|
||
│ MoveObjectToPosition(framePosition); │
|
||
│ │
|
||
│ // 执行碰撞检测 │
|
||
│ var collisions = ClashDetectiveIntegration.Instance.DetectCollisions( │
|
||
│ _animatedObject, null, _detectionGap); │
|
||
│ │
|
||
│ // 创建帧数据 │
|
||
│ var frame = new AnimationFrame │
|
||
│ { │
|
||
│ FrameIndex = i, │
|
||
│ Progress = progress, │
|
||
│ Position = framePosition, │
|
||
│ Collisions = new List<CollisionInfo>(), │
|
||
│ Timestamp = DateTime.Now │
|
||
│ }; │
|
||
│ │
|
||
│ // 记录碰撞信息 │
|
||
│ foreach (var collision in collisions) │
|
||
│ { │
|
||
│ frame.Collisions.Add(new CollisionInfo │
|
||
│ { │
|
||
│ CollidingObject = collision.Item2, │
|
||
│ CollidingPosition = GetObjectPosition(collision.Item2), │
|
||
│ Distance = collision.Distance │
|
||
│ }); │
|
||
│ } │
|
||
│ │
|
||
│ _animationFrames.Add(frame); │
|
||
│ │
|
||
│ // 进度报告 │
|
||
│ if (i % 10 == 0) │
|
||
│ { │
|
||
│ LogManager.Info($"预计算进度: {i}/{_totalFrames} ({progress*100:F1}%)"); │
|
||
│ } │
|
||
│ } │
|
||
│ │
|
||
│ // 恢复原始位置 │
|
||
│ MoveObjectToPosition(originalPosition); │
|
||
│ } │
|
||
│ │
|
||
│ 3. 步进式播放控制 │
|
||
│ │
|
||
│ // 播放模式枚举 │
|
||
│ public enum PlaybackMode │
|
||
│ { │
|
||
│ Continuous, // 连续播放 │
|
||
│ StepByStep, // 逐帧步进 │
|
||
│ Manual // 手动控制 │
|
||
│ } │
|
||
│ │
|
||
│ private PlaybackMode _playbackMode = PlaybackMode.StepByStep; │
|
||
│ │
|
||
│ // 修改OnApplicationIdle - 支持步进模式 │
|
||
│ private void OnApplicationIdle(object sender, EventArgs e) │
|
||
│ { │
|
||
│ if (_currentState != AnimationState.Playing) │
|
||
│ return; │
|
||
│ │
|
||
│ // 帧率控制 │
|
||
│ var now = DateTime.Now; │
|
||
│ var elapsed = (now - _lastFrameTime).TotalMilliseconds; │
|
||
│ │
|
||
│ if (elapsed < _frameInterval) │
|
||
│ return; │
|
||
│ │
|
||
│ // 根据播放模式执行 │
|
||
│ switch (_playbackMode) │
|
||
│ { │
|
||
│ case PlaybackMode.Continuous: │
|
||
│ // 自动步进到下一帧 │
|
||
│ if (_currentFrameIndex < _totalFrames) │
|
||
│ { │
|
||
│ MoveToFrame(_currentFrameIndex + 1); │
|
||
│ _lastFrameTime = now; │
|
||
│ } │
|
||
│ else │
|
||
│ { │
|
||
│ FinishAnimation(); │
|
||
│ } │
|
||
│ break; │
|
||
│ │
|
||
│ case PlaybackMode.StepByStep: │
|
||
│ // 等待用户触发下一帧 │
|
||
│ // 不自动前进 │
|
||
│ break; │
|
||
│ │
|
||
│ case PlaybackMode.Manual: │
|
||
│ // 完全手动控制 │
|
||
│ break; │
|
||
│ } │
|
||
│ } │
|
||
│ │
|
||
│ 4. 帧导航方法 │
|
||
│ │
|
||
│ // 移动到指定帧 │
|
||
│ public void MoveToFrame(int frameIndex) │
|
||
│ { │
|
||
│ if (frameIndex < 0 || frameIndex >= _animationFrames.Count) │
|
||
│ { │
|
||
│ LogManager.Warning($"帧索引 {frameIndex} 超出范围 [0, {_animationFrames.Count-1}]"); │
|
||
│ return; │
|
||
│ } │
|
||
│ │
|
||
│ var frame = _animationFrames[frameIndex]; │
|
||
│ _currentFrameIndex = frameIndex; │
|
||
│ │
|
||
│ // 更新位置 │
|
||
│ UpdateObjectPosition(frame.Position); │
|
||
│ _currentPosition = frame.Position; │
|
||
│ │
|
||
│ // 更新碰撞高亮 │
|
||
│ HighlightFrameCollisions(frame); │
|
||
│ │
|
||
│ // 触发进度事件 │
|
||
│ ProgressChanged?.Invoke(this, frame.Progress * 100); │
|
||
│ │
|
||
│ // 更新TimeLiner │
|
||
│ if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) │
|
||
│ { │
|
||
│ _timeLinerManager.UpdateTaskProgress(_currentTaskId, frame.Progress, _currentState); │
|
||
│ } │
|
||
│ │
|
||
│ LogManager.Debug($"移动到帧 {frameIndex}/{_totalFrames} (进度: {frame.Progress*100:F1}%)"); │
|
||
│ } │
|
||
│ │
|
||
│ // 前进一帧 │
|
||
│ public void StepForward() │
|
||
│ { │
|
||
│ if (_currentFrameIndex < _totalFrames - 1) │
|
||
│ { │
|
||
│ MoveToFrame(_currentFrameIndex + 1); │
|
||
│ } │
|
||
│ else │
|
||
│ { │
|
||
│ LogManager.Info("已到达最后一帧"); │
|
||
│ } │
|
||
│ } │
|
||
│ │
|
||
│ // 后退一帧 │
|
||
│ public void StepBackward() │
|
||
│ { │
|
||
│ if (_currentFrameIndex > 0) │
|
||
│ { │
|
||
│ MoveToFrame(_currentFrameIndex - 1); │
|
||
│ } │
|
||
│ else │
|
||
│ { │
|
||
│ LogManager.Info("已到达第一帧"); │
|
||
│ } │
|
||
│ } │
|
||
│ │
|
||
│ // 跳转到指定进度 │
|
||
│ public void JumpToProgress(double progress) │
|
||
│ { │
|
||
│ int targetFrame = (int)(progress * _totalFrames); │
|
||
│ MoveToFrame(targetFrame); │
|
||
│ } │
|
||
│ │
|
||
│ // 快进/快退 │
|
||
│ public void FastForward(int frames = 10) │
|
||
│ { │
|
||
│ int targetFrame = Math.Min(_currentFrameIndex + frames, _totalFrames - 1); │
|
||
│ MoveToFrame(targetFrame); │
|
||
│ } │
|
||
│ │
|
||
│ public void FastBackward(int frames = 10) │
|
||
│ { │
|
||
│ int targetFrame = Math.Max(_currentFrameIndex - frames, 0); │
|
||
│ MoveToFrame(targetFrame); │
|
||
│ } │
|
||
│ │
|
||
│ 5. 碰撞高亮管理 │
|
||
│ │
|
||
│ private HashSet<ModelItem> _currentHighlightedItems = new HashSet<ModelItem>(); │
|
||
│ │
|
||
│ private void HighlightFrameCollisions(AnimationFrame frame) │
|
||
│ { │
|
||
│ var newHighlights = new HashSet<ModelItem>(frame.Collisions.Select(c => c.CollidingObject)); │
|
||
│ │
|
||
│ // 只在高亮集合变化时更新 │
|
||
│ if (!newHighlights.SetEquals(_currentHighlightedItems)) │
|
||
│ { │
|
||
│ // 清除旧高亮 │
|
||
│ if (_currentHighlightedItems.Count > 0) │
|
||
│ { │
|
||
│ ClashDetectiveIntegration.Instance.ClearHighlight(); │
|
||
│ } │
|
||
│ │
|
||
│ // 应用新高亮 │
|
||
│ if (newHighlights.Count > 0) │
|
||
│ { │
|
||
│ var collisionResults = frame.Collisions.Select(c => new CollisionResult │
|
||
│ { │
|
||
│ Item1 = _animatedObject, │
|
||
│ Item2 = c.CollidingObject, │
|
||
│ Distance = c.Distance │
|
||
│ }).ToList(); │
|
||
│ │
|
||
│ ClashDetectiveIntegration.Instance.HighlightCollisions(collisionResults); │
|
||
│ } │
|
||
│ │
|
||
│ _currentHighlightedItems = newHighlights; │
|
||
│ │
|
||
│ if (frame.Collisions.Count > 0) │
|
||
│ { │
|
||
│ LogManager.Info($"帧 {frame.FrameIndex}: 高亮 {frame.Collisions.Count} 个碰撞"); │
|
||
│ } │
|
||
│ } │
|
||
│ } │
|
||
│ │
|
||
│ 6. 播放控制API │
|
||
│ │
|
||
│ // 开始连续播放 │
|
||
│ public void Play() │
|
||
│ { │
|
||
│ _playbackMode = PlaybackMode.Continuous; │
|
||
│ SetState(AnimationState.Playing); │
|
||
│ NavisApplication.Idle += OnApplicationIdle; │
|
||
│ } │
|
||
│ │
|
||
│ // 暂停(保持当前帧) │
|
||
│ public void Pause() │
|
||
│ { │
|
||
│ _playbackMode = PlaybackMode.StepByStep; │
|
||
│ SetState(AnimationState.Paused); │
|
||
│ } │
|
||
│ │
|
||
│ // 设置播放速度(通过调整帧间隔) │
|
||
│ public void SetPlaybackSpeed(double speed) │
|
||
│ { │
|
||
│ _frameInterval = (1000.0 / _animationFrameRate) / speed; │
|
||
│ LogManager.Info($"播放速度设置为 {speed}x"); │
|
||
│ } │
|
||
│ │
|
||
│ // 获取当前帧信息 │
|
||
│ public AnimationFrame GetCurrentFrame() │
|
||
│ { │
|
||
│ if (_currentFrameIndex >= 0 && _currentFrameIndex < _animationFrames.Count) │
|
||
│ return _animationFrames[_currentFrameIndex]; │
|
||
│ return null; │
|
||
│ } │
|
||
│ │
|
||
│ // 获取帧统计信息 │
|
||
│ public string GetFrameStatistics() │
|
||
│ { │
|
||
│ var current = GetCurrentFrame(); │
|
||
│ if (current != null) │
|
||
│ { │
|
||
│ return $"帧 {_currentFrameIndex}/{_totalFrames} | " + │
|
||
│ $"进度 {current.Progress*100:F1}% | " + │
|
||
│ $"碰撞 {current.Collisions.Count}"; │
|
||
│ } │
|
||
│ return "无帧数据"; │
|
||
│ } │
|
||
│ │
|
||
│ 7. UI集成建议 │
|
||
│ │
|
||
│ // 动画控制面板应添加的控件 │
|
||
│ public class AnimationControlPanel │
|
||
│ { │
|
||
│ // 播放控制 │
|
||
│ Button PlayButton; // 连续播放 │
|
||
│ Button PauseButton; // 暂停 │
|
||
│ Button StepForwardButton; // 前进一帧 │
|
||
│ Button StepBackButton; // 后退一帧 │
|
||
│ Button FastForwardButton; // 快进10帧 │
|
||
│ Button FastRewindButton; // 快退10帧 │
|
||
│ │
|
||
│ // 进度控制 │
|
||
│ Slider ProgressSlider; // 进度条(可拖动跳转) │
|
||
│ Label FrameLabel; // 当前帧信息 │
|
||
│ Label CollisionLabel; // 碰撞信息 │
|
||
│ │
|
||
│ // 速度控制 │
|
||
│ Slider SpeedSlider; // 播放速度(0.1x - 5x) │
|
||
│ ComboBox PlayModeCombo; // 播放模式选择 │
|
||
│ } │
|
||
│ │
|
||
│ 优势 │
|
||
│ │
|
||
│ 1. 完全可控:每一帧都可以精确控制 │
|
||
│ 2. 双向导航:支持前进和后退 │
|
||
│ 3. 性能优秀:预计算避免实时检测 │
|
||
│ 4. 调试友好:可以逐帧分析碰撞 │
|
||
│ 5. 灵活播放:支持多种播放模式 │
|
||
│ │
|
||
│ 实施步骤 │
|
||
│ │
|
||
│ 1. 添加AnimationFrame数据结构 │
|
||
│ 2. 实现预计算所有帧逻辑 │
|
||
│ 3. 改造播放控制为步进式 │
|
||
│ 4. 实现帧导航方法 │
|
||
│ 5. 更新UI添加步进控制 │
|
||
│ 6. 测试各种播放模式
|
||
|
||
|
||
|
||
🎮 媒体控制功能:
|
||
|
||
- 快退10帧 (Shift+←) - 快速后退功能
|
||
- 上一帧 (←) - 精确单帧后退
|
||
- 反向播放 (R键) - 连续反向播放动画
|
||
- 暂停 (Space) - 居中暂停控制
|
||
- 正向播放 - 标准前进播放
|
||
- 下一帧 (→) - 精确单帧前进
|
||
- 快进10帧 (Shift+→) - 快速前进功能
|
||
- 停止 (Esc) - 停止并重置动画
|
||
|
||
|
||
4. 优化性能的关键点
|
||
使用事务批量更新:通过 Application.ActiveDocument.BeginTransaction 将多个构件的变换操作包裹起来,最后一次性提交 (transaction.Commit()),这比单独更新每个构件高效得多。
|
||
|
||
优先使用DispatcherTimer而非Idle事件:DispatcherTimer 在UI线程上按固定间隔触发,比依赖不确定的Idle事件更能保证更新的及时性。优先级设置为 DispatcherPriority.Render。
|
||
|
||
控制帧率:Interval 设置为约16ms(约60FPS)。如果场景复杂导致卡顿,可以适当增大此值(如33ms对应~30FPS)。WPF中也可以通过 Timeline.DesiredFrameRate 附加属性来调整动画的帧率。
|
||
|
||
减少不必要的重绘:只在所有变换更新后调用一次 Redraw。
|
||
|
||
硬件加速:确保在Navisworks的选项 (Options) -> 显示 (Display) 中启用硬件加速 (Hardware Acceleration) 和 WPF硬件加速 (WPF Hardware Acceleration)(如果可用)。
|
||
|
||
优化图形设置:在Navisworks的显示设置中,适当降低细节层次 (Level of Detail) 或禁用保证帧频 (Guarantee Frame Rate)(根据实际情况测试)可能有助于提高复杂模型的交互性能。
|
||
|
||
5. 调试提示
|
||
如果动画仍然卡顿,首先尝试减少同时动画的构件数量。
|
||
|
||
在Navisworks的选项 -> 显示中,可以尝试调整图形模式 (Graphics Mode) 等设置来优化性能。
|
||
|
||
使用性能分析工具(如Visual Studio的诊断工具)检查代码中是否存在瓶颈。
|
||
|
||
|
||
Navisworks设置:
|
||
1、【界面】-【选取】中,可以设置精度,从几何体、最低层次的对象、最高层级的唯一对象、最高层级的对象、图层、文件,默认是最低层次的对象。 |