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

406 lines
47 KiB
Markdown
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.

步进式动画系统完整方案 │
│ │
│ 核心架构 │
│ │
│ - 步进式播放:基于帧索引而非时间 │
│ - 预计算碰撞:提前计算所有碰撞点 │
│ - 逐帧控制:支持单帧前进/后退 │
│ - 双向导航:可以在任意帧之间跳转 │
│ │
│ 实现方案 │
│ │
│ 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, _detectionTolerance:F4); │
│ │
│ // 创建帧数据 │
│ 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、【界面】-【选取】中,可以设置精度,从几何体、最低层次的对象、最高层级的唯一对象、最高层级的对象、图层、文件,默认是最低层次的对象。