NavisworksTransport/PathAnimationManager.cs

837 lines
32 KiB
C#
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.

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>
/// 定义动画播放的状态
/// </summary>
public enum AnimationState
{
Idle, // 空闲,未生成动画
Ready, // 已就绪,动画已生成但未播放
Playing, // 播放中
Paused, // 暂停
Stopped, // 已停止
Finished // 已完成
}
/// <summary>
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
/// 注意由于Navisworks API限制无法直接使用Animator API因此使用OverridePermanentTransform实现动画
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
/// </summary>
public class PathAnimationManager
{
private ModelItem _animatedObject;
private List<Point3D> _pathPoints;
private Timer _animationTimer;
private double _animationDuration = 10.0; // 动画总时长(秒)
private DateTime _animationStartTime;
private Transform3D _originalTransform;
private Point3D _originalCenter; // 存储部件的原始中心位置
private Point3D _currentPosition; // 存储部件的当前位置
private AnimationState _currentState = AnimationState.Idle;
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
private string _currentTaskId;
// --- 新增事件 ---
/// <summary>
/// 当动画状态发生改变时触发
/// </summary>
public event EventHandler<AnimationState> StateChanged;
/// <summary>
/// 当动画进度更新时触发 (0-100)
/// </summary>
public event EventHandler<int> ProgressChanged;
// 动画完成事件 (旧版,保留兼容性)
public event EventHandler AnimationCompleted;
public PathAnimationManager()
{
_pathPoints = new List<Point3D>();
// 初始化 TimeLiner 集成
try
{
_timeLinerManager = new TimeLinerIntegrationManager();
LogManager.Info("PathAnimationManager 已集成 TimeLiner 功能");
}
catch (Exception ex)
{
LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}");
_timeLinerManager = null;
}
}
/// <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));
// 添加路径点坐标有效性验证
LogManager.Info("=== 动画管理器坐标验证 ===");
bool hasInvalidCoordinates = false;
for (int i = 0; i < pathPoints.Count; i++)
{
var point = pathPoints[i];
bool isValid = !double.IsNaN(point.X) && !double.IsNaN(point.Y) && !double.IsNaN(point.Z) &&
!double.IsInfinity(point.X) && !double.IsInfinity(point.Y) && !double.IsInfinity(point.Z);
LogManager.Info($"路径点[{i}]坐标验证: ({point.X:F6},{point.Y:F6},{point.Z:F6}) - {(isValid ? "" : "")}");
if (!isValid)
{
hasInvalidCoordinates = true;
LogManager.Error($"检测到无效坐标: 路径点[{i}] = ({point.X},{point.Y},{point.Z})");
}
// 检查是否为零坐标(可能表示数据丢失)
if (isValid && point.X == 0.0 && point.Y == 0.0 && point.Z == 0.0)
{
LogManager.Warning($"路径点[{i}]坐标为零,可能存在数据丢失问题");
}
}
if (hasInvalidCoordinates)
{
throw new ArgumentException("路径点包含无效坐标NaN或无穷大无法创建动画");
}
LogManager.Info("=== 坐标验证完成 ===");
_animatedObject = animatedObject;
_pathPoints = new List<Point3D>(pathPoints);
_animationDuration = durationSeconds;
// 保存原始变换以便重置
_originalTransform = GetCurrentTransform(_animatedObject);
// 保存车辆的原始中心位置
var originalBoundingBox = animatedObject.BoundingBox();
_originalCenter = originalBoundingBox.Center;
// 关键修复:将车辆立即移动到路径起点
// 这样动画就从路径起点开始,而不是从车辆当前位置开始
MoveVehicleToPathStart();
// 记录文档单位信息
var documentUnits = GetDocumentUnitsInfo();
// 添加详细的调试信息
LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 路径点数={_pathPoints.Count}, 时长={_animationDuration}秒");
LogManager.Info($"文档单位: {documentUnits}");
LogManager.Info($"路径起点: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
LogManager.Info($"路径终点: ({_pathPoints[_pathPoints.Count-1].X:F2},{_pathPoints[_pathPoints.Count-1].Y:F2},{_pathPoints[_pathPoints.Count-1].Z:F2})");
var totalDist = CalculateTotalPathDistance();
LogManager.Info($"路径总长度: {totalDist:F2}");
LogManager.Info($"车辆已移动到路径起点,动画将从起点开始");
LogManager.Info($"=== 调试信息结束 ===");
}
catch (Exception ex)
{
LogManager.Error($"设置动画失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 将车辆移动到路径起点
/// </summary>
private void MoveVehicleToPathStart()
{
try
{
if (_pathPoints.Count == 0) return;
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 计算从部件原始中心到路径起点的偏移
var startOffset = new Vector3D(
_pathPoints[0].X - _originalCenter.X,
_pathPoints[0].Y - _originalCenter.Y,
_pathPoints[0].Z - _originalCenter.Z
);
// 创建变换并应用
var startTransform = Transform3D.CreateTranslation(startOffset);
doc.Models.OverridePermanentTransform(modelItems, startTransform, false);
// 更新当前位置为路径起点
_currentPosition = _pathPoints[0];
LogManager.Info($"部件已移动到路径起点,偏移: ({startOffset.X:F2},{startOffset.Y:F2},{startOffset.Z:F2})");
}
catch (Exception ex)
{
LogManager.Error($"移动部件到路径起点失败: {ex.Message}");
}
}
/// <summary>
/// 开始播放动画
/// </summary>
public void StartAnimation()
{
try
{
if (_animatedObject == null || _pathPoints.Count < 2)
{
throw new InvalidOperationException("请先调用SetupAnimation设置动画参数");
}
// 停止之前的动画
StopAnimation();
// 创建 TimeLiner 任务(如果可用)
if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable)
{
var taskName = $"{_animatedObject.DisplayName}_运输_{DateTime.Now:HHmmss}";
var duration = TimeSpan.FromSeconds(_animationDuration);
LogManager.Info($"创建 TimeLiner 任务: {taskName}");
_currentTaskId = _timeLinerManager.CreateTransportTask(
taskName,
_pathPoints,
duration,
_animatedObject);
if (!string.IsNullOrEmpty(_currentTaskId))
{
LogManager.Info($"✓ TimeLiner 任务创建成功: {taskName} (ID: {_currentTaskId})");
}
else
{
LogManager.Warning("TimeLiner 任务创建失败,继续使用基础动画功能");
}
}
else
{
LogManager.Info("TimeLiner 不可用,使用基础动画功能");
}
// 设置动态碰撞检测(简化版本)
SetupDynamicClashDetection();
// 初始化动画状态
_animationStartTime = DateTime.Now;
// 创建并启动定时器每50ms更新一次实现流畅动画
_animationTimer = new Timer();
_animationTimer.Interval = 50; // 20 FPS
_animationTimer.Tick += AnimationTimer_Tick;
_animationTimer.Start();
SetState(AnimationState.Playing);
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;
SetState(AnimationState.Stopped);
LogManager.Info("动画已停止");
}
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 0.0, AnimationState.Stopped);
}
}
catch (Exception ex)
{
LogManager.Error($"停止动画失败: {ex.Message}");
}
}
/// <summary>
/// 重置动画对象到原始位置
/// </summary>
public void ResetAnimation()
{
try
{
StopAnimation(); // 停止当前动画
// 恢复对象的原始变换
if (_animatedObject != null)
{
var modelItems = new ModelItemCollection { _animatedObject };
NavisApplication.ActiveDocument.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
LogManager.Info($"部件 {_animatedObject.DisplayName} 已重置到原始位置");
}
ProgressChanged?.Invoke(this, 0); // 重置进度条
SetState(AnimationState.Ready); // 重置后回到就绪状态
}
catch (Exception ex)
{
LogManager.Error($"重置动画失败: {ex.Message}");
throw;
}
}
/// <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进度条
ProgressChanged?.Invoke(this, (int)(progress * 100));
// 同步进度到 TimeLiner如果可用
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
Point3D newPosition = InterpolatePosition(progress);
UpdateObjectPosition(newPosition);
// 检查碰撞
CheckAndHighlightCollisions();
if (progress >= 1.0)
{
StopAnimation();
SetState(AnimationState.Finished); // 标记为完成
AnimationCompleted?.Invoke(this, EventArgs.Empty); // 触发旧版完成事件
LogManager.Info("动画播放完成");
}
}
catch (Exception ex)
{
LogManager.Error($"动画帧更新失败: {ex.Message}");
StopAnimation(); // 发生错误时停止动画
}
}
/// <summary>
/// 根据进度插值计算当前位置
/// </summary>
private Point3D InterpolatePosition(double progress)
{
if (_pathPoints.Count < 2)
return _pathPoints[0];
// 确保进度在0-1范围内
progress = Math.Max(0.0, Math.Min(1.0, progress));
// 如果进度达到100%,直接返回终点
if (progress >= 1.0)
{
return _pathPoints[_pathPoints.Count - 1];
}
// 计算总路径长度
var totalDistance = CalculateTotalPathDistance();
// 检查总距离是否有效
if (totalDistance <= 0.0 || double.IsNaN(totalDistance) || double.IsInfinity(totalDistance))
{
LogManager.Error($"路径总长度无效: {totalDistance},返回起点坐标");
return _pathPoints[0];
}
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 (double.IsNaN(segmentDistance) || double.IsInfinity(segmentDistance))
{
LogManager.Error($"路径段[{i}-{i+1}]距离无效: {segmentDistance},跳过此段");
continue;
}
if (accumulatedDistance + segmentDistance >= targetDistance)
{
// 在这个线段内
var segmentProgress = segmentDistance > 0 ? (targetDistance - accumulatedDistance) / segmentDistance : 0.0;
// 确保段内进度也在0-1范围内
segmentProgress = Math.Max(0.0, Math.Min(1.0, segmentProgress));
var interpolatedPoint = InterpolatePoints(_pathPoints[i], _pathPoints[i + 1], segmentProgress);
// 验证插值结果
if (double.IsNaN(interpolatedPoint.X) || double.IsNaN(interpolatedPoint.Y) || double.IsNaN(interpolatedPoint.Z))
{
LogManager.Error($"插值计算结果无效: ({interpolatedPoint.X},{interpolatedPoint.Y},{interpolatedPoint.Z}),返回起点");
return _pathPoints[0];
}
return interpolatedPoint;
}
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)
{
try
{
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 正确的增量变换:计算从当前位置到新位置的偏移
var incrementalOffset = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
);
// 创建增量变换
var incrementalTransform = Transform3D.CreateTranslation(incrementalOffset);
// 应用增量变换(不重置之前的变换)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
LogManager.Debug($"部件位置更新:从({_currentPosition.X:F2},{_currentPosition.Y:F2},{_currentPosition.Z:F2})到({newPosition.X:F2},{newPosition.Y:F2},{newPosition.Z:F2}),增量偏移({incrementalOffset.X:F2},{incrementalOffset.Y:F2},{incrementalOffset.Z:F2})");
// 更新当前位置
_currentPosition = newPosition;
}
catch (Exception ex)
{
LogManager.Error($"更新部件位置失败: {ex.Message}");
}
}
/// <summary>
/// 从车辆变换矩阵中获取真实的缩放系数
/// 注意:此方法已废弃但保留,用于车辆模型的缩放处理。
/// 现在使用场馆部件进行动画,通常不需要复杂的缩放计算。
/// </summary>
private double GetVehicleScaleFactor()
{
try
{
var transform = _animatedObject.Transform;
var linear = transform.Linear;
// 获取变换矩阵的第一行的长度作为缩放系数
// 变换矩阵: (a, b, c)
// (d, e, f)
// (g, h, i)
// 缩放系数 ≈ sqrt(a² + b² + c²)
var scaleX = Math.Sqrt(
linear.Get(0, 0) * linear.Get(0, 0) +
linear.Get(0, 1) * linear.Get(0, 1) +
linear.Get(0, 2) * linear.Get(0, 2)
);
LogManager.Debug($"车辆缩放系数计算: {scaleX:F2} (变换矩阵第一行模长)");
// 如果缩放系数接近155039.37²),说明是米到英寸的平方缩放
if (Math.Abs(scaleX - 1550.0) < 10.0)
{
var linearScale = Math.Sqrt(scaleX);
LogManager.Info($"检测到车辆使用平方缩放系数: {scaleX:F2},已修正为线性缩放系数: {linearScale:F2}");
return linearScale;
}
// 如果缩放系数接近39.37,说明是简单的米到英寸缩放
else if (Math.Abs(scaleX - 39.37) < 1.0)
{
LogManager.Info($"检测到车辆使用线性缩放系数: {scaleX:F2}");
return scaleX;
}
// 如果接近1说明没有缩放
else if (Math.Abs(scaleX - 1.0) < 0.1)
{
LogManager.Info($"车辆无缩放: {scaleX:F2}");
return 1.0;
}
else
{
LogManager.Warning($"未知的车辆缩放系数: {scaleX:F2},使用原值");
return scaleX;
}
}
catch (Exception ex)
{
LogManager.Error($"获取车辆缩放系数失败: {ex.Message}使用默认值1.0");
return 1.0;
}
}
/// <summary>
/// 获取文档单位信息(用于日志记录)
/// </summary>
private string GetDocumentUnitsInfo()
{
try
{
var doc = NavisApplication.ActiveDocument;
var units = doc.Units;
LogManager.Debug($"[单位检测] 文档单位: {units}");
return units.ToString();
}
catch (Exception ex)
{
LogManager.Error($"获取文档单位信息失败: {ex.Message}");
return "Unknown";
}
}
/// <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>
/// 获取 TimeLiner 是否可用
/// </summary>
public bool IsTimeLinerAvailable => _timeLinerManager?.IsTimeLinerAvailable ?? false;
/// <summary>
/// 获取当前动画状态
/// </summary>
public AnimationState CurrentState => _currentState;
/// <summary>
/// 获取当前 TimeLiner 任务ID
/// </summary>
public string CurrentTaskId => _currentTaskId;
/// <summary>
/// 获取 TimeLiner 集成管理器
/// </summary>
public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager;
/// <summary>
/// 资源清理
/// </summary>
public void Dispose()
{
StopAnimation();
ResetAnimation();
// 清理 TimeLiner 资源
if (_timeLinerManager != null)
{
try
{
_timeLinerManager.Dispose();
_timeLinerManager = null;
LogManager.Info("TimeLiner 集成资源已清理");
}
catch (Exception ex)
{
LogManager.Warning($"清理 TimeLiner 资源时出现警告: {ex.Message}");
}
}
}
/// <summary>
/// 快速测试TimeLiner API的可用性
/// </summary>
public bool TestTimeLinerAPI()
{
try
{
var doc = NavisApplication.ActiveDocument;
// 尝试访问TimeLiner
var timeliner = doc.Timeliner;
if (timeliner != null)
{
LogManager.Info("TimeLiner API基本可用");
return true;
}
else
{
LogManager.Info("TimeLiner API不可用");
return false;
}
}
catch (Exception ex)
{
LogManager.Info($"TimeLiner API测试失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 简化的动画设置方法,支持直接传入部件和路径
/// </summary>
/// <param name="component">部件模型</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public bool SetupSimpleAnimation(ModelItem component, List<Point3D> pathPoints, double durationSeconds = 10.0)
{
try
{
if (component == null)
{
LogManager.Error("部件模型不能为空");
return false;
}
if (pathPoints == null || pathPoints.Count < 2)
{
LogManager.Error("路径点数量必须至少为2个");
return false;
}
// 使用现有的SetupAnimation方法
SetupAnimation(component, pathPoints, durationSeconds);
LogManager.Info($"简化动画设置成功:部件={component.DisplayName}, 路径点数={pathPoints.Count}, 时长={durationSeconds}秒");
return true;
}
catch (Exception ex)
{
LogManager.Error($"简化动画设置失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 获取当前选中的部件模型(简化版本)
/// </summary>
/// <returns>选中的部件模型如果没有选中或选中多个则返回null</returns>
public static ModelItem GetSelectedComponent()
{
try
{
var doc = NavisApplication.ActiveDocument;
var selectedItems = doc.CurrentSelection.SelectedItems;
if (selectedItems.Count == 1)
{
var item = selectedItems.First;
if (item.HasGeometry)
{
LogManager.Info($"已获取选中部件: {item.DisplayName}");
return item;
}
else
{
LogManager.Warning("选中的项目没有几何体,可能不适合用于动画");
return null;
}
}
else if (selectedItems.Count == 0)
{
LogManager.Info("没有选中任何项目");
return null;
}
else
{
LogManager.Info($"选中了{selectedItems.Count}个项目,请只选择一个部件");
return null;
}
}
catch (Exception ex)
{
LogManager.Error($"获取选中部件失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 设置并触发状态变更事件
/// </summary>
/// <param name="newState">新的动画状态</param>
private void SetState(AnimationState newState)
{
if (_currentState != newState)
{
_currentState = newState;
StateChanged?.Invoke(this, _currentState);
}
}
/// <summary>
/// 创建动画
/// </summary>
/// <param name="animatedObject">要动画化的模型对象</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public void CreateAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0)
{
SetupAnimation(animatedObject, pathPoints, durationSeconds);
SetState(AnimationState.Ready);
}
}
}