370 lines
13 KiB
C#
370 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Windows.Forms;
|
||
using Autodesk.Navisworks.Api;
|
||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
|
||
/// 注意:由于Navisworks API限制,无法直接使用Animator API,因此使用OverridePermanentTransform实现动画
|
||
/// </summary>
|
||
public class PathAnimationManager
|
||
{
|
||
private ModelItem _animatedObject;
|
||
private List<Point3D> _pathPoints;
|
||
private Timer _animationTimer;
|
||
private int _currentPathIndex;
|
||
private double _animationDuration = 10.0; // 动画总时长(秒)
|
||
private DateTime _animationStartTime;
|
||
private Transform3D _originalTransform;
|
||
|
||
public PathAnimationManager()
|
||
{
|
||
_pathPoints = new List<Point3D>();
|
||
_currentPathIndex = 0;
|
||
}
|
||
|
||
/// <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));
|
||
|
||
_animatedObject = animatedObject;
|
||
_pathPoints = new List<Point3D>(pathPoints);
|
||
_animationDuration = durationSeconds;
|
||
|
||
// 保存原始变换以便重置
|
||
_originalTransform = GetCurrentTransform(_animatedObject);
|
||
|
||
LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 路径点数={_pathPoints.Count}, 时长={_animationDuration}秒");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"设置动画失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始播放动画
|
||
/// </summary>
|
||
public void StartAnimation()
|
||
{
|
||
try
|
||
{
|
||
if (_animatedObject == null || _pathPoints.Count < 2)
|
||
{
|
||
throw new InvalidOperationException("请先调用SetupAnimation设置动画参数");
|
||
}
|
||
|
||
// 停止之前的动画
|
||
StopAnimation();
|
||
|
||
// 设置动态碰撞检测(简化版本)
|
||
SetupDynamicClashDetection();
|
||
|
||
// 初始化动画状态
|
||
_currentPathIndex = 0;
|
||
_animationStartTime = DateTime.Now;
|
||
|
||
// 创建并启动定时器(每50ms更新一次,实现流畅动画)
|
||
_animationTimer = new Timer();
|
||
_animationTimer.Interval = 50; // 20 FPS
|
||
_animationTimer.Tick += AnimationTimer_Tick;
|
||
_animationTimer.Start();
|
||
|
||
LogManager.Info("动画开始播放");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"启动动画失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止动画
|
||
/// </summary>
|
||
public void StopAnimation()
|
||
{
|
||
try
|
||
{
|
||
if (_animationTimer != null)
|
||
{
|
||
_animationTimer.Stop();
|
||
_animationTimer.Dispose();
|
||
_animationTimer = null;
|
||
}
|
||
|
||
LogManager.Info("动画已停止");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"停止动画失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置动画对象到原始位置
|
||
/// </summary>
|
||
public void ResetAnimation()
|
||
{
|
||
try
|
||
{
|
||
StopAnimation();
|
||
|
||
if (_animatedObject != null && _originalTransform != null)
|
||
{
|
||
var doc = NavisApplication.ActiveDocument;
|
||
var modelItems = new ModelItemCollection { _animatedObject };
|
||
doc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
|
||
|
||
// 清除碰撞高亮
|
||
doc.Models.ResetAllTemporaryMaterials();
|
||
}
|
||
|
||
_currentPathIndex = 0;
|
||
LogManager.Info("动画已重置到初始状态");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"重置动画失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画定时器事件处理
|
||
/// </summary>
|
||
private void AnimationTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var elapsedTime = (DateTime.Now - _animationStartTime).TotalSeconds;
|
||
var progress = elapsedTime / _animationDuration;
|
||
|
||
if (progress >= 1.0)
|
||
{
|
||
// 动画完成
|
||
StopAnimation();
|
||
LogManager.Info("动画播放完成");
|
||
return;
|
||
}
|
||
|
||
// 计算当前应该在的位置
|
||
var currentPosition = InterpolatePosition(progress);
|
||
|
||
// 更新模型位置
|
||
UpdateObjectPosition(currentPosition);
|
||
|
||
// 检查碰撞(简化版本)
|
||
CheckAndHighlightCollisions();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"动画更新失败: {ex.Message}");
|
||
StopAnimation();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据进度插值计算当前位置
|
||
/// </summary>
|
||
private Point3D InterpolatePosition(double progress)
|
||
{
|
||
if (_pathPoints.Count < 2)
|
||
return _pathPoints[0];
|
||
|
||
// 计算总路径长度
|
||
var totalDistance = CalculateTotalPathDistance();
|
||
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 (accumulatedDistance + segmentDistance >= targetDistance)
|
||
{
|
||
// 在这个线段内
|
||
var segmentProgress = (targetDistance - accumulatedDistance) / segmentDistance;
|
||
return InterpolatePoints(_pathPoints[i], _pathPoints[i + 1], segmentProgress);
|
||
}
|
||
|
||
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 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)
|
||
{
|
||
var doc = NavisApplication.ActiveDocument;
|
||
var modelItems = new ModelItemCollection { _animatedObject };
|
||
|
||
// 创建平移变换
|
||
var translation = new Vector3D(newPosition.X, newPosition.Y, newPosition.Z);
|
||
var transform = Transform3D.CreateTranslation(translation);
|
||
|
||
// 应用变换
|
||
doc.Models.OverridePermanentTransform(modelItems, transform, false);
|
||
}
|
||
|
||
/// <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 API在2017版本中有限制)
|
||
/// </summary>
|
||
private void SetupDynamicClashDetection()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("动态碰撞检测设置完成(简化版本)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"设置动态碰撞检测失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查并高亮碰撞(简化版本)
|
||
/// </summary>
|
||
private void CheckAndHighlightCollisions()
|
||
{
|
||
try
|
||
{
|
||
// 简化的碰撞检测:检查动画对象是否与其他对象的包围盒相交
|
||
var doc = NavisApplication.ActiveDocument;
|
||
var animatedBoundingBox = _animatedObject.BoundingBox();
|
||
|
||
// 获取所有其他有几何体的对象
|
||
var allItems = doc.Models.RootItemDescendantsAndSelf
|
||
.Where(item => item.HasGeometry && !item.Equals(_animatedObject))
|
||
.ToList();
|
||
|
||
var collidingItems = new ModelItemCollection();
|
||
|
||
foreach (var item in allItems)
|
||
{
|
||
var itemBoundingBox = item.BoundingBox();
|
||
if (BoundingBoxesIntersect(animatedBoundingBox, itemBoundingBox))
|
||
{
|
||
collidingItems.Add(item);
|
||
}
|
||
}
|
||
|
||
// 清除之前的高亮
|
||
doc.Models.ResetAllTemporaryMaterials();
|
||
|
||
// 高亮碰撞对象(红色)
|
||
if (collidingItems.Count > 0)
|
||
{
|
||
doc.Models.OverrideTemporaryColor(collidingItems, Color.Red);
|
||
LogManager.Debug($"检测到 {collidingItems.Count} 处碰撞");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Debug($"碰撞检测更新失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <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 => _animationTimer != null && _animationTimer.Enabled;
|
||
|
||
/// <summary>
|
||
/// 资源清理
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
StopAnimation();
|
||
ResetAnimation();
|
||
}
|
||
}
|
||
} |