NavisworksTransport/doc/working/2017/步进式动画方案.md

47 KiB
Raw Blame History

步进式动画系统完整方案 │ │ │ │ 核心架构 │ │ │ │ - 步进式播放:基于帧索引而非时间 │ │ - 预计算碰撞:提前计算所有碰撞点 │ │ - 逐帧控制:支持单帧前进/后退 │ │ - 双向导航:可以在任意帧之间跳转 │ │ │ │ 实现方案 │ │ │ │ 1. 步进式动画数据结构 │ │ │ │ // 动画帧数据 │ │ public class AnimationFrame │ │ { │ │ public int FrameIndex { get; set; } │ │ public double Progress { get; set; } // 0-1之间的进度 │ │ public Point3D Position { get; set; } // 该帧的位置 │ │ public List 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 _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 pathPoints, double durationSeconds = 10.0) │ │ { │ │ // ... 现有验证代码 ... │ │ │ │ // 计算总帧数 │ │ _totalFrames = (int)(1.0 / _frameStepSize); // 如0.01步进=100帧 │ │ _animationFrames = new List(); │ │ │ │ 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, _detectionTolerance:F4); │ │ │ │ // 创建帧数据 │ │ var frame = new AnimationFrame │ │ { │ │ FrameIndex = i, │ │ Progress = progress, │ │ Position = framePosition, │ │ Collisions = new List(), │ │ 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} ({progress100: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.Progress100: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 _currentHighlightedItems = new HashSet(); │ │ │ │ private void HighlightFrameCollisions(AnimationFrame frame) │ │ { │ │ var newHighlights = new HashSet(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) - 停止并重置动画
  1. 优化性能的关键点 使用事务批量更新:通过 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)(根据实际情况测试)可能有助于提高复杂模型的交互性能。

  1. 调试提示 如果动画仍然卡顿,首先尝试减少同时动画的构件数量。

在Navisworks的选项 -> 显示中,可以尝试调整图形模式 (Graphics Mode) 等设置来优化性能。

使用性能分析工具如Visual Studio的诊断工具检查代码中是否存在瓶颈。

Navisworks设置 1、【界面】-【选取】中,可以设置精度,从几何体、最低层次的对象、最高层级的唯一对象、最高层级的对象、图层、文件,默认是最低层次的对象。