NavisworksTransport/src/Core/Animation/PathAnimationManager.cs
tian f75dcb1f50 refactor: use PathAnimationManager.ApplyQuaternionRotationInPlace
- New method mirrors proven ApplyRotationCorrectionInPlace pattern
- ViewModel only calls existing project APIs, no raw OverridePermanentTransform
2026-06-09 03:39:56 +08:00

6717 lines
291 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.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
using NavisworksTransport.Core.Config;
using NavisworksTransport.Core.Spatial;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.Core.Animation
{
/// <summary>
/// 定义动画播放的状态
/// </summary>
public enum AnimationState
{
Idle, // 空闲,未生成动画
Ready, // 已就绪,动画已生成但未播放
Playing, // 播放中
Paused, // 暂停
Stopped, // 已停止
Finished // 已完成
}
/// <summary>
/// 碰撞对象对,用于去重
/// </summary>
public class CollisionPair : IEquatable<CollisionPair>
{
public ModelItem AnimatedObject { get; set; }
public ModelItem CollisionObject { get; set; }
public bool Equals(CollisionPair other)
{
if (other == null) return false;
return ReferenceEquals(AnimatedObject, other.AnimatedObject) &&
ReferenceEquals(CollisionObject, other.CollisionObject);
}
public override bool Equals(object obj)
{
return Equals(obj as CollisionPair);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + (AnimatedObject?.GetHashCode() ?? 0);
hash = hash * 31 + (CollisionObject?.GetHashCode() ?? 0);
return hash;
}
}
}
/// <summary>
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
/// 注意由于Navisworks API限制无法直接使用Animator API因此使用OverridePermanentTransform实现动画
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
/// </summary>
/// <summary>
/// 动画帧数据(包含位置、朝向和碰撞信息)
/// </summary>
public class AnimationFrame
{
public int Index { get; set; } // 帧索引
public double Progress { get; set; } // 进度(0-1)
public Point3D Position { get; set; } // 该帧的位置
public double YawRadians { get; set; } // 绕Z轴的偏航角弧度
public double? PlanarHostYawRadians { get; set; } // 平面真实物体逐帧宿主平面角
public Rotation3D Rotation { get; set; } // 可选的完整三维姿态
public bool HasCustomRotation { get; set; } // 是否使用完整三维姿态
public List<CollisionResult> Collisions { get; set; } // 该帧的碰撞结果
public bool HasCollision => Collisions?.Count > 0;
public AnimationFrame()
{
Collisions = new List<CollisionResult>();
YawRadians = 0.0;
PlanarHostYawRadians = null;
Rotation = Rotation3D.Identity;
HasCustomRotation = false;
}
}
internal enum AnimatedObjectMode
{
None,
RealObject,
VirtualObject
}
public class PathAnimationManager
{
private static PathAnimationManager _instance;
private static readonly Dictionary<string, int> _animationHashToRecordId = new Dictionary<string, int>(); // 动画配置哈希到检测记录ID的映射
private static readonly HashSet<string> _realObjectFragmentUpMismatchHintsShown = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private ModelItem _animatedObject;
private AnimatedObjectMode _animatedObjectMode = AnimatedObjectMode.None;
private double _virtualObjectLength = 0; // 虚拟物体长度(模型单位)
private double _virtualObjectWidth = 0; // 虚拟物体宽度(模型单位)
private double _virtualObjectHeight = 0; // 虚拟物体高度(模型单位)
private double _realObjectLength = 0; // 真实物体长度(模型单位,固定物理尺寸)
private double _realObjectWidth = 0; // 真实物体宽度(模型单位,固定物理尺寸)
private double _realObjectHeight = 0; // 真实物体高度(模型单位,固定物理尺寸)
private Quaternion _realObjectReferenceRotation = Quaternion.Identity;
private Vector3 _realObjectReferenceAxisX = Vector3.UnitX;
private Vector3 _realObjectReferenceAxisY = Vector3.UnitY;
private Vector3 _realObjectReferenceAxisZ = Vector3.UnitZ;
private LocalAxisDirection _realObjectReferenceHostWorldXAxisLocalAxis = LocalAxisDirection.PositiveX;
private LocalAxisDirection _realObjectReferenceHostWorldYAxisLocalAxis = LocalAxisDirection.PositiveY;
private LocalAxisDirection _realObjectReferenceHostWorldZAxisLocalAxis = LocalAxisDirection.PositiveZ;
private LocalAxisDirection _realObjectReferenceHostSemanticXAxisLocalAxis = LocalAxisDirection.PositiveX;
private LocalAxisDirection _realObjectReferenceHostSemanticYAxisLocalAxis = LocalAxisDirection.PositiveY;
private LocalAxisDirection _realObjectReferenceHostSemanticZAxisLocalAxis = LocalAxisDirection.PositiveZ;
private LocalAxisDirection _realObjectReferenceHostUpLocalAxis = LocalAxisDirection.PositiveY;
private bool _hasRealObjectReferenceRotation = false;
private LocalAxisDirection _realObjectPlanarSelectedForwardAxis = LocalAxisDirection.PositiveX;
private bool _hasRealObjectPlanarSelectedForwardAxis = false;
private List<Point3D> _pathPoints;
private List<ModelItem> _manualCollisionTargets = new List<ModelItem>();
private bool _manualCollisionOverrideEnabled = false;
// === 角度修正 ===
private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero; // 物体绕宿主 X/Y/Z 轴的角度修正
private double _groundPathObjectLiftHeight; // 地面路径物体上下偏移(模型单位,负值向下)
// === 碰撞排除列表缓存管理从LogisticsAnimationManager迁移===
private ModelItem _currentCachedAnimationObject;
private List<ModelItem> _cachedExclusionList;
private DateTime _cacheCreatedTime;
private readonly TimeSpan _cacheValidDuration = TimeSpan.FromMinutes(30); // 缓存有效期30分钟
// === 预计算动画系统 ===
private List<AnimationFrame> _animationFrames; // 所有帧数据
private int _currentFrameIndex = 0; // 当前帧索引
private List<CollisionResult> _allCollisionResults; // 所有碰撞结果(不去重)
// === Human-in-the-Loop 排除列表 ===
// 注意:排除列表是实时管理的,不与动画缓存绑定。用户选择排除哪些物体后,
// 这些物体会一直生效直到被清除或手动移除,不受动画缓存影响。
private HashSet<ModelItem> _excludedObjects = new HashSet<ModelItem>(); // 用户排除的物体列表
private bool _lastHighlightState = false; // 上一帧的高亮状态
private HashSet<ModelItem> _lastCollisionObjects = new HashSet<ModelItem>(); // 上一帧碰撞对象的集合
// === 路径相关 ===
private PathRoute _route = null; // 路径引用
// === 碰撞测试优化 ===
private string _currentAnimationHash; // 当前动画配置的哈希值
private int? _currentDetectionRecordId; // 当前检测记录ID关联CollisionDetectionRecords表
private bool _isUsingHistoryRecord; // 是否使用历史检测记录动画完成后跳过ClashDetective
// === 动画播放机制 ===
private double _frameInterval; // 帧间隔(毫秒)
private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间
private DispatcherTimer _animationTimer; // 备用DispatcherTimer定时器
// === 动画参数 ===
private double _animationDuration; // 动画总时长(秒)
private DateTime _animationStartTime;
private int _animationFrameRate; // 动画帧率默认30FPS
private double _collisionDetectionAccuracy; // 检测精度(内部存储:模型单位/帧)
private double _movementSpeed; // 运动速度(仅用于显示,单位:米/秒)
private double _detectionTolerance; // 检测容差从UI设置
private double _safetyMargin; // 安全间隙模型单位从CreateAnimation传入
private string _pathName; // 路径名称
private string _currentRouteId; // 当前路由ID
// === 双向播放和步进控制 ===
private int _playbackDirection = 1; // 播放方向1=正向,-1=反向
private double _playbackSpeed = 1.0; // 播放速度倍率
// === 性能监控 ===
private int _fpsFrameCount = 0;
private DateTime _fpsCounterStart = DateTime.Now;
private double _actualFPS = 0;
private Transform3D _originalTransform;
private Point3D _originalCenter; // 存储部件的原始中心位置
private Point3D _trackedPosition; // 动画管理器内部业务跟踪点,不等于宿主即时读回的实时包围盒中心
private AnimationState _currentState = AnimationState.Idle;
private double _pausedProgress = 0.0; // 暂停时的进度0-1之间
private double _currentYaw = 0.0; // 当前偏航角(弧度)
private Rotation3D _trackedRotation = Rotation3D.Identity; // 动画管理器内部跟踪的完整姿态
private bool _hasTrackedRotation = false; // 当前是否使用完整姿态
private Rotation3D _groundRealObjectBaseRotation = Rotation3D.Identity;
private double _groundRealObjectBaseYaw = 0.0;
private bool _hasGroundRealObjectBasePose = false;
private Vector3D _railPreservedPoseTrackedCenterOffset = new Vector3D(0, 0, 0);
private bool _hasRailPreservedPoseTrackedCenterOffset = false;
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
private string _currentTaskId;
// === 调试辅助 ===
private static HashSet<int> _arcInfoPrinted = new HashSet<int>(); // 记录已打印信息的圆弧段索引
// === 物体状态保存和恢复 ===
private Point3D _savedObjectPosition;
private double _savedObjectYaw;
private Rotation3D _savedObjectRotation = Rotation3D.Identity;
private bool _savedObjectHasCustomRotation = false;
private bool _hasSavedObjectState = false;
private ObjectStartPlacementMode _objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose;
private Rotation3D _pathPreservedPoseRotation = Rotation3D.Identity;
private bool _hasPathPreservedPoseRotation = false;
private bool IsVirtualObjectMode => _animatedObjectMode == AnimatedObjectMode.VirtualObject;
private bool IsRealObjectMode => _animatedObjectMode == AnimatedObjectMode.RealObject;
private ModelItem CurrentControlledObject =>
IsVirtualObjectMode ? VirtualObjectManager.Instance.CurrentVirtualObject : _animatedObject;
/// <summary>
/// 当前正在进行动画的对象
/// </summary>
public ModelItem AnimatedObject => _animatedObject;
/// <summary>
/// 当前检测记录ID关联CollisionDetectionRecords表
/// </summary>
public int? CurrentDetectionRecordId
{
get => _currentDetectionRecordId;
set => _currentDetectionRecordId = value;
}
/// <summary>
/// 是否使用历史检测记录动画完成后跳过ClashDetective测试
/// </summary>
public bool IsUsingHistoryRecord
{
get => _isUsingHistoryRecord;
set => _isUsingHistoryRecord = value;
}
// --- 新增事件 ---
/// <summary>
/// 当动画状态发生改变时触发
/// </summary>
public event EventHandler<AnimationState> StateChanged;
/// <summary>
/// 当动画进度更新时触发 (0.0-100.0)
/// </summary>
public event EventHandler<double> ProgressChanged;
public PathAnimationManager()
{
_pathPoints = new List<Point3D>();
_animationFrames = new List<AnimationFrame>();
_allCollisionResults = new List<CollisionResult>();
// 🔥 从配置初始化动画参数
var config = ConfigManager.Instance.Current;
_animationFrameRate = config.Animation.FrameRate;
_animationDuration = config.Animation.DurationSeconds;
// 初始化动画模式
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
_lastFrameTime = DateTime.MinValue;
_fpsCounterStart = DateTime.Now;
// 初始化备用DispatcherTimer
_animationTimer = new DispatcherTimer(DispatcherPriority.Render)
{
Interval = TimeSpan.FromMilliseconds(_frameInterval)
};
_animationTimer.Tick += OnTimerTick;
// 初始化 TimeLiner 集成
try
{
_timeLinerManager = new TimeLinerIntegrationManager();
LogManager.Info($"PathAnimationManager 初始化完成 - DispatcherTimer模式, 目标FPS: {_animationFrameRate}");
}
catch (Exception ex)
{
LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}");
_timeLinerManager = null;
}
// 订阅文档状态事件
DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated;
// 订阅配置变更事件
SubscribeToConfigChanges();
}
#region
/// <summary>
/// 订阅配置变更事件
/// </summary>
private void SubscribeToConfigChanges()
{
try
{
ConfigManager.Instance.CategoryConfigurationChanged += OnCategoryConfigurationChanged;
LogManager.Info("PathAnimationManager 已订阅配置变更事件");
}
catch (Exception ex)
{
LogManager.Error($"订阅配置变更事件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 取消订阅配置变更事件
/// </summary>
private void UnsubscribeFromConfigChanges()
{
try
{
ConfigManager.Instance.CategoryConfigurationChanged -= OnCategoryConfigurationChanged;
LogManager.Info("PathAnimationManager 已取消订阅配置变更事件");
}
catch (Exception ex)
{
LogManager.Warning($"取消订阅配置变更事件时发生异常: {ex.Message}");
}
}
/// <summary>
/// 配置变更事件处理
/// </summary>
private void OnCategoryConfigurationChanged(object sender, Tuple<ConfigCategory, ConfigurationChangedEventArgs> args)
{
try
{
var category = args.Item1;
var eventArgs = args.Item2;
// 处理 Animation 类别变更
if (category == ConfigCategory.Animation || category == ConfigCategory.All)
{
LogManager.Info("收到 Animation 配置变更通知,正在更新动画参数...");
var config = ConfigManager.Instance.Current;
bool needReset = false;
// 检查帧率是否变更
if (_animationFrameRate != config.Animation.FrameRate)
{
_animationFrameRate = config.Animation.FrameRate;
_frameInterval = 1000.0 / _animationFrameRate;
// 更新定时器间隔
if (_animationTimer != null)
{
_animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval);
}
needReset = true;
LogManager.Info($"动画帧率已更新: {_animationFrameRate} fps, 帧间隔: {_frameInterval:F1} ms");
}
// 检查持续时间是否变更
if (_animationDuration != config.Animation.DurationSeconds)
{
_animationDuration = config.Animation.DurationSeconds;
needReset = true;
LogManager.Info($"动画持续时间已更新: {_animationDuration} 秒");
}
// 如果动画正在运行,需要重置
if (needReset && (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused))
{
// 停止当前动画,让用户重新生成
ShutdownAnimation();
LogManager.Info("动画参数已变更,当前动画已停止,请重新生成动画");
}
}
}
catch (Exception ex)
{
LogManager.Error($"处理配置变更事件失败: {ex.Message}", ex);
}
}
#endregion
/// <summary>
/// 获取单例实例
/// </summary>
public static PathAnimationManager GetInstance()
{
if (_instance == null)
{
_instance = new PathAnimationManager();
}
return _instance;
}
/// <summary>
/// 清理当前所有的动画生成结果(帧列表、碰撞结果、状态)
/// </summary>
public void ClearAnimationResults()
{
_animationFrames = null;
_allCollisionResults = null;
_arcInfoPrinted.Clear(); // 清除圆弧段信息打印记录
SetState(AnimationState.Idle);
LogManager.Info("[PathAnimationManager] 动画数据和状态已重置为 Idle");
}
/// <summary>
/// 仅仅将物理模型同步到路径起点(不清理数据,不重置状态)
/// 用于加载碰撞检测结果时
/// </summary>
public void SyncToPathStart(ModelItem item, List<Point3D> points)
{
MoveObjectToPathStart(item, points);
}
/// <summary>
/// 在 CAD 原位仅施加宿主 X/Y/Z 三轴旋转修正,不移动物体。
/// <summary>
/// 用于自动调整评测——每次评测在固定 CAD 位置,以 X 轴为统一参考前向。
/// 调用前需确保物体处于 CAD 原位(已 ResetPermanentTransform
/// </summary>
public void ApplyRotationCorrectionInPlace(LocalEulerRotationCorrection correction)
{
var item = CurrentControlledObject;
if (item == null) return;
var modelItems = new ModelItemCollection { item };
var doc = NavisApplication.ActiveDocument;
doc.Models.ResetPermanentTransform(modelItems);
var center = GetLiveBoundingBoxCenter(item);
var tp = new Vector3((float)center.X, (float)center.Y, (float)center.Z);
// 与 TryApplyPlanarRealObjectStartIncrementalTransform 保持一致的分解顺序:
// qup * qnonUp * qx即先X、再nonUp、最后up
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var totalQ = adapter.CreateHostRotationCorrection(correction);
var rotatedTp = Vector3.Transform(tp, totalQ);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var comp = identity.Factor();
comp.Rotation = new Rotation3D(totalQ.X, totalQ.Y, totalQ.Z, totalQ.W);
comp.Translation = new Vector3D(
center.X - rotatedTp.X,
center.Y - rotatedTp.Y,
center.Z - rotatedTp.Z);
doc.Models.OverridePermanentTransform(modelItems, comp.Combine(), false);
}
/// <summary>
/// 直接用四元数绕物体包围盒中心旋转(增量模式)。
/// 用于贴地面等不需要走 Euler 角度链的场景。
/// </summary>
public void ApplyQuaternionRotationInPlace(Quaternion rotation)
{
var item = CurrentControlledObject;
if (item == null) return;
var modelItems = new ModelItemCollection { item };
var doc = NavisApplication.ActiveDocument;
var center = GetLiveBoundingBoxCenter(item);
var tp = new Vector3((float)center.X, (float)center.Y, (float)center.Z);
var rotatedTp = Vector3.Transform(tp, rotation);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var comp = identity.Factor();
comp.Rotation = new Rotation3D(rotation.X, rotation.Y, rotation.Z, rotation.W);
comp.Translation = new Vector3D(
center.X - rotatedTp.X,
center.Y - rotatedTp.Y,
center.Z - rotatedTp.Z);
doc.Models.OverridePermanentTransform(modelItems, comp.Combine(), false);
}
/// <summary>
/// 将当前显示姿态同步为跟踪状态yaw=0 对应对齐 X 轴)。
/// 用于自动调整确认前,替代 RestoreObjectToCADPosition 的复位→设置链路。
/// </summary>
public void SyncTrackedStateToCurrentDisplayPose()
{
var item = CurrentControlledObject;
if (item == null) return;
_trackedPosition = GetLiveBoundingBoxCenter(item);
_currentYaw = 0.0;
_trackedRotation = item.Transform.Factor().Rotation;
_hasTrackedRotation = true;
_hasGroundRealObjectBasePose = false;
}
/// <summary>
/// 将动画对象彻底恢复到 CAD 原始位置(清除所有覆盖变换)
/// 常用于模式切换或彻底清除动画干扰
/// </summary>
public void RestoreObjectToCADPosition()
{
try
{
var doc = NavisApplication.ActiveDocument;
if (doc == null) return;
// 🔥 支持虚拟物体的恢复
ModelItem objectToRestore = null;
bool isVirtual = IsVirtualObjectMode;
if (isVirtual)
{
objectToRestore = VirtualObjectManager.Instance.CurrentVirtualObject;
if (objectToRestore == null)
{
LogManager.Warning("[归位] 虚拟物体未激活,跳过恢复");
return;
}
}
else
{
objectToRestore = _animatedObject;
if (objectToRestore == null)
{
LogManager.Warning("[归位] 没有动画对象,跳过恢复");
return;
}
}
var modelItems = new ModelItemCollection { objectToRestore };
// 1. 彻底清除 Navisworks 中的所有覆盖变换,强制恢复到设计文件定义的原始状态
doc.Models.ResetPermanentTransform(modelItems);
// 2. 重置内部跟踪变量(同步到原始状态)
// 注意ResetPermanentTransform 后物体的 Transform 属性会自动变回原始值
if (isVirtual)
{
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(objectToRestore.Transform);
_trackedRotation = objectToRestore.Transform.Factor().Rotation;
_hasTrackedRotation = true;
}
else
{
SyncTrackedRotationToObjectReference(objectToRestore, isVirtualObject: false);
}
var originalBoundingBox = objectToRestore.BoundingBox();
_trackedPosition = ResolveInitialBusinessTrackedPosition(objectToRestore);
string objectName = isVirtual ? "虚拟物体" : objectToRestore.DisplayName;
_railPreservedPoseTrackedCenterOffset = new Vector3D(0, 0, 0);
_hasRailPreservedPoseTrackedCenterOffset = false;
LogManager.Debug($"[归位] {objectName} 已彻底恢复到原始位置, yaw={_currentYaw:F3}");
}
catch (Exception ex)
{
LogManager.Error($"[归位] 恢复物体到原始位置失败: {ex.Message}");
}
}
/// <summary>
/// 生成动画帧
/// </summary>
/// <param name="animatedObject">要动画化的模型对象</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public void SetRoute(PathRoute route)
{
_route = route;
}
/// <summary>
/// 设置动画对象(批处理专用)
/// </summary>
public void SetAnimatedObject(ModelItem animatedObject)
{
_animatedObject = animatedObject;
_animatedObjectMode = animatedObject == null
? AnimatedObjectMode.None
: AnimatedObjectMode.RealObject;
ResetRealObjectReferenceRotation();
if (animatedObject != null)
{
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_trackedPosition = ResolveInitialBusinessTrackedPosition(animatedObject);
SyncTrackedRotationToObjectReference(animatedObject, isVirtualObject: false);
}
}
/// <summary>
/// 设置虚拟物体参数(批处理专用)
/// 参数使用模型单位与Navisworks API一致
/// </summary>
public void SetVirtualObjectParameters(bool isVirtualObject, double length, double width, double height)
{
_animatedObjectMode = isVirtualObject
? AnimatedObjectMode.VirtualObject
: (_animatedObject != null ? AnimatedObjectMode.RealObject : AnimatedObjectMode.None);
if (isVirtualObject)
{
ResetRealObjectReferenceRotation();
}
_virtualObjectLength = length; // 模型单位
_virtualObjectWidth = width; // 模型单位
_virtualObjectHeight = height; // 模型单位
}
/// <summary>
/// 设置真实物体的固定物理尺寸(模型单位)。
/// 一旦动画开始生成,后续 Rail 帧和起点贴合应使用同一份尺寸语义,
/// 不能再从已经旋转后的当前 AABB 重新推导高度。
/// </summary>
public void SetRealObjectDimensions(double length, double width, double height)
{
_realObjectLength = length;
_realObjectWidth = width;
_realObjectHeight = height;
}
/// <summary>
/// 设置动画参数(批处理专用)
/// </summary>
public void SetAnimationParameters(int frameRate, double duration, double detectionTolerance)
{
_animationFrameRate = frameRate;
_animationDuration = duration;
// 注意:预计算使用安全间隙,不使用检测容差
}
/// <summary>
/// 检测容差(模型单位)- 用于ClashDetective精确检测
/// </summary>
public double DetectionTolerance
{
get
{
var config = ConfigManager.Instance.Current;
return config.Animation.DetectionTolerance;
}
}
/// <summary>
/// 设置手工碰撞目标(批处理专用)
/// </summary>
public void SetManualCollisionTargets(List<ModelItem> targets)
{
if (targets != null && targets.Count > 0)
{
_manualCollisionTargets = targets;
_manualCollisionOverrideEnabled = true;
}
else
{
_manualCollisionTargets = new List<ModelItem>();
_manualCollisionOverrideEnabled = false;
}
}
/// <summary>
/// 预计算动画帧(批处理专用内部方法)
/// </summary>
public List<AnimationFrame> PrecomputeAnimationFramesInternal()
{
PrecomputeAnimationFrames();
return _animationFrames;
}
public void SetupAnimation(
ModelItem animatedObject,
double durationSeconds,
PathRoute route)
{
try
{
if (route == null)
throw new ArgumentNullException(nameof(route), "必须提供路径");
_animatedObject = animatedObject;
_animationDuration = durationSeconds;
_route = route;
LogManager.Info($"使用路径,边数:{route.Edges.Count},总长度:{route.TotalLength:F2}米,动画时长:{durationSeconds:F1}秒");
// 计算动画配置哈希(基于路径边,可以区分不同的曲线化路径)
_currentAnimationHash = ComputeAnimationConfigHashFromRoute(animatedObject, route);
LogManager.Info($"动画配置哈希: {_currentAnimationHash}");
// 重新计算动画帧和碰撞结果(不使用缓存,确保排除列表变化后结果正确)
LogManager.Info("开始计算动画帧和碰撞结果...");
PrecomputeAnimationFrames();
LogManager.Info($"动画帧计算完成,帧数: {_animationFrames?.Count ?? 0}, 碰撞数: {_allCollisionResults?.Count ?? 0}");
// 验证预计算结果
if (_animationFrames == null || _animationFrames.Count == 0)
{
throw new InvalidOperationException("动画帧预计算失败,无法创建动画");
}
LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 时长={_animationDuration}秒");
}
catch (Exception ex)
{
LogManager.Error($"设置动画失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 将物体移动到路径起点
/// </summary>
/// <param name="animatedObject">动画对象可选如果不提供则使用当前的_animatedObject</param>
/// <param name="pathPoints">路径点可选如果不提供则使用当前的_pathPoints</param>
/// <returns>是否成功移动</returns>
public bool MoveObjectToPathStart(ModelItem animatedObject = null, List<Point3D> pathPoints = null, bool skipCadRestore = false)
{
_objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose;
// 前置检查:必须有路径
if (_route == null)
{
LogManager.Error("[MoveObjectToPathStart] 路径为空,无法移动物体到路径起点");
return false;
}
try
{
if (!skipCadRestore)
{
if (CurrentControlledObject != null || IsVirtualObjectMode)
{
RestoreObjectToCADPosition();
}
// 如果提供了参数,更新内部状态
if (animatedObject != null)
{
_animatedObject = animatedObject;
bool isVirtualObject =
VirtualObjectManager.Instance.IsVirtualObjectActive &&
ReferenceEquals(animatedObject, VirtualObjectManager.Instance.CurrentVirtualObject);
_animatedObjectMode = isVirtualObject
? AnimatedObjectMode.VirtualObject
: AnimatedObjectMode.RealObject;
ResetRealObjectReferenceRotation();
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_trackedPosition = ResolveInitialBusinessTrackedPosition(animatedObject);
if (isVirtualObject)
{
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
_trackedRotation = _originalTransform.Factor().Rotation;
_hasTrackedRotation = true;
}
else
{
SyncTrackedRotationToObjectReference(animatedObject, isVirtualObject: false);
}
LogManager.Info(
$"[移动到起点] 更新动画对象内部状态: 对象={animatedObject.DisplayName}, 虚拟物体={IsVirtualObjectMode}");
}
if (pathPoints != null)
{
_pathPoints = pathPoints;
}
}
// skipCadRestore: 调用方已设置好 _trackedPosition、_currentYaw、_objectRotationCorrection
// 检查路径点
if (_pathPoints == null || _pathPoints.Count < 2)
{
LogManager.Warning("[移动到起点] 没有可用的路径点");
return false;
}
bool shouldUsePlanarPureIncrementStart =
IsRealObjectMode &&
(_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting);
// 根据路径类型调整起点位置
Point3D pathStartPoint = _pathPoints[0];
Point3D startPosition = pathStartPoint;
if (_route.PathType == PathType.Hoisting)
{
// 吊装路径:第一个路径点(起吊点)是地面位置,动画跟踪点统一使用几何中心
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
LogManager.Debug($"[移动到起点] 吊装路径:起吊点已转换为物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})");
}
else if (_route.PathType == PathType.Rail)
{
Point3D previousPoint = _pathPoints[0];
Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0];
double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint);
if (TryResolveRailPreservedPoseTrackedCenter(startPosition, previousPoint, nextPoint, objectHeight, out Point3D preservedTrackedCenter))
{
startPosition = preservedTrackedCenter;
LogManager.Debug(
$"[移动到起点] Rail保持姿态补偿: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), " +
$"补偿后中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})");
}
else
{
startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight);
LogManager.Debug($"[移动到起点] Rail路径调整: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), 物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), 物体高度={objectHeight:F2}, 安装={_route.RailMountMode}, 参考面={(PathRoute.IsTopReferenceFaceForMountMode(_route.RailMountMode) ? "" : "")}");
}
if (TryCreateRailPathRotation(
previousPoint,
_pathPoints[0],
nextPoint,
out var railRotation))
{
var railLinearTransform = new Transform3D(railRotation).Linear;
LogManager.Info(
$"[移动到起点] Rail旋转姿态: " +
$"X=({railLinearTransform.Get(0, 0):F4},{railLinearTransform.Get(1, 0):F4},{railLinearTransform.Get(2, 0):F4}), " +
$"Y=({railLinearTransform.Get(0, 1):F4},{railLinearTransform.Get(1, 1):F4},{railLinearTransform.Get(2, 1):F4}), " +
$"Z=({railLinearTransform.Get(0, 2):F4},{railLinearTransform.Get(1, 2):F4},{railLinearTransform.Get(2, 2):F4})");
ApplyFullPoseUpdate(startPosition, railRotation);
LogManager.Info("[移动到起点] Rail路径已应用完整三维旋转姿态");
return true;
}
}
else
{
// 地面路径:路径点在接触面上,动画跟踪点统一使用几何中心
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
startPosition = ApplyGroundPathObjectLiftOffset(startPosition);
LogManager.Debug($"[移动到起点] 地面路径中心点=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})");
}
LogManager.Debug(
$"[路径起点诊断] 路径point0=({pathStartPoint.X:F3},{pathStartPoint.Y:F3},{pathStartPoint.Z:F3}), " +
$"起点目标trackedPoint=({startPosition.X:F3},{startPosition.Y:F3},{startPosition.Z:F3}), 路径类型={_route.PathType.GetDisplayName()}");
LogManager.Debug(
$"[移动到起点-诊断] _trackedPosition=({_trackedPosition.X:F3},{_trackedPosition.Y:F3},{_trackedPosition.Z:F3}), " +
$"startPosition=({startPosition.X:F3},{startPosition.Y:F3},{startPosition.Z:F3})");
// skipCadRestore 时校正已原地施加yaw 增量不应再包含校正的 up 轴分量
var savedCorrection = _objectRotationCorrection;
if (skipCadRestore)
{
_objectRotationCorrection = LocalEulerRotationCorrection.Zero;
}
if (shouldUsePlanarPureIncrementStart &&
TryApplyPlanarRealObjectStartIncrementalTransform(_route.PathType, startPosition, out double planarTargetYawRadians))
{
LogManager.Debug($"[移动到起点] {_route.PathType.GetDisplayName()} 真实物体已应用纯增量旋转+平移");
var startAppliedPoint = GetLiveBoundingBoxCenter(CurrentControlledObject ?? _animatedObject);
LogManager.Debug(
$"[路径起点诊断] 起点应用后实际包围盒中心=({startAppliedPoint.X:F3},{startAppliedPoint.Y:F3},{startAppliedPoint.Z:F3}), " +
$"相对目标偏差=({startAppliedPoint.X - startPosition.X:F3},{startAppliedPoint.Y - startPosition.Y:F3},{startAppliedPoint.Z - startPosition.Z:F3})");
var startOffset = new Vector3D(
startPosition.X - startAppliedPoint.X,
startPosition.Y - startAppliedPoint.Y,
startPosition.Z - startAppliedPoint.Z);
double startOffsetLength = Math.Sqrt(
startOffset.X * startOffset.X +
startOffset.Y * startOffset.Y +
startOffset.Z * startOffset.Z);
if (_route.PathType == PathType.Ground)
{
_groundRealObjectBaseRotation = _trackedRotation;
_groundRealObjectBaseYaw = planarTargetYawRadians;
_hasGroundRealObjectBasePose = true;
LogManager.Debug(
$"[Ground真实物体基姿态] {(CurrentControlledObject ?? _animatedObject)?.DisplayName ?? "()"} BaseYaw={_groundRealObjectBaseYaw * 180.0 / Math.PI:F2}°, " +
$"已记录基姿态={_hasGroundRealObjectBasePose} (纯增量链)");
}
else
{
_hasGroundRealObjectBasePose = false;
}
}
else if (shouldUsePlanarPureIncrementStart)
{
LogManager.Error($"[移动到起点] {_route.PathType.GetDisplayName()} 真实物体纯增量起点失败,已禁止退回旧姿态链。");
return false;
}
else if (_route.PathType != PathType.Rail &&
TryCreatePlanarPathRotationAtStart(out var planarRotation))
{
ApplyFullPoseUpdate(startPosition, planarRotation);
LogManager.Debug("[移动到起点] 地面/吊装路径已应用完整三维姿态");
var startAppliedPoint = GetLiveBoundingBoxCenter(CurrentControlledObject ?? _animatedObject);
LogManager.Debug(
$"[路径起点诊断] 起点应用后实际包围盒中心=({startAppliedPoint.X:F3},{startAppliedPoint.Y:F3},{startAppliedPoint.Z:F3}), " +
$"相对目标偏差=({startAppliedPoint.X - startPosition.X:F3},{startAppliedPoint.Y - startPosition.Y:F3},{startAppliedPoint.Z - startPosition.Z:F3})");
// Ground 起点不再进行二次补偿;业务跟踪点语义由 _trackedPosition 主链负责
if (IsRealObjectMode && _route.PathType == PathType.Ground)
{
var startOffset = new Vector3D(
startPosition.X - startAppliedPoint.X,
startPosition.Y - startAppliedPoint.Y,
startPosition.Z - startAppliedPoint.Z);
double startOffsetLength = Math.Sqrt(
startOffset.X * startOffset.X +
startOffset.Y * startOffset.Y +
startOffset.Z * startOffset.Z);
}
if (IsRealObjectMode && _route.PathType == PathType.Ground)
{
_groundRealObjectBaseRotation = planarRotation;
_hasGroundRealObjectBasePose = TryResolveGroundRealObjectBaseYaw(out _groundRealObjectBaseYaw);
LogManager.Debug(
$"[Ground真实物体基姿态] {(CurrentControlledObject ?? _animatedObject)?.DisplayName ?? "()"} BaseYaw={_groundRealObjectBaseYaw * 180.0 / Math.PI:F2}°, " +
$"已记录基姿态={_hasGroundRealObjectBasePose} (旋转偏移由逐帧方法内部处理)");
}
else if (IsRealObjectMode && _route.PathType == PathType.Hoisting)
{
}
else
{
_hasGroundRealObjectBasePose = false;
}
}
else if (_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting)
{
LogManager.Error("[移动到起点] 平面路径未能生成完整姿态,已禁止退回旧 yaw 链路");
return false;
}
else if (_route.PathType == PathType.Rail)
{
LogManager.Error("[移动到起点] Rail路径未能生成完整三维姿态已禁止退回旧 yaw 链路");
return false;
}
if (skipCadRestore)
{
_objectRotationCorrection = savedCorrection;
}
return true;
}
catch (Exception ex)
{
LogManager.Error($"初始化物体到路径起点失败: {ex.Message}");
return false;
}
}
public bool MoveObjectToPathStartPreservingInitialPose(ModelItem animatedObject = null, List<Point3D> pathPoints = null)
{
_objectStartPlacementMode = ObjectStartPlacementMode.PreserveInitialPose;
if (_route == null)
{
LogManager.Error("[平移到起点] 路径为空,无法移动物体到路径起点");
return false;
}
try
{
if (CurrentControlledObject != null || IsVirtualObjectMode)
{
RestoreObjectToCADPosition();
LogManager.Info($"[平移到起点] 已恢复物体到原始状态, _currentYaw={_currentYaw * 180 / Math.PI:F2}°");
}
if (animatedObject != null)
{
_animatedObject = animatedObject;
bool isVirtualObject =
VirtualObjectManager.Instance.IsVirtualObjectActive &&
ReferenceEquals(animatedObject, VirtualObjectManager.Instance.CurrentVirtualObject);
_animatedObjectMode = isVirtualObject
? AnimatedObjectMode.VirtualObject
: AnimatedObjectMode.RealObject;
ResetRealObjectReferenceRotation();
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_trackedPosition = ResolveInitialBusinessTrackedPosition(animatedObject);
if (isVirtualObject)
{
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
_trackedRotation = _originalTransform.Factor().Rotation;
_hasTrackedRotation = true;
}
else
{
SyncTrackedRotationToObjectReference(animatedObject, isVirtualObject: false);
}
}
if (pathPoints != null)
{
_pathPoints = pathPoints;
}
if (_pathPoints == null || _pathPoints.Count < 2)
{
LogManager.Warning("[平移到起点] 没有可用的路径点");
return false;
}
Point3D pathStartPoint = _pathPoints[0];
Point3D startPosition = pathStartPoint;
if (_route.PathType == PathType.Hoisting)
{
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
}
else if (_route.PathType == PathType.Rail)
{
Point3D previousPoint = _pathPoints[0];
Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0];
double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint);
// Rail 平移模式不是重新求一套“轨上/轨下安装姿态”,
// 而是以终点处真实箱体当前位姿为真值,
// 记录其相对终点路径参考点的固定残差,再整体搬运到起点。
Point3D terminalReferencePoint = _pathPoints[_pathPoints.Count - 1];
Point3D terminalPreviousPoint = _pathPoints.Count > 1 ? _pathPoints[_pathPoints.Count - 2] : terminalReferencePoint;
Point3D terminalNextPoint = terminalReferencePoint;
CaptureRailPreservedPoseTrackedCenterOffset(
terminalPreviousPoint,
terminalReferencePoint,
terminalNextPoint,
objectHeight);
if (!TryResolveRailPreservedPoseTrackedCenter(startPosition, previousPoint, nextPoint, objectHeight, out Point3D preservedTrackedCenter))
{
preservedTrackedCenter = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight);
}
startPosition = preservedTrackedCenter;
}
else
{
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
startPosition = ApplyGroundPathObjectLiftOffset(startPosition);
}
ApplyTrackedTranslationOnly(startPosition);
SyncTrackedRotationToDisplayedPose(CurrentControlledObject ?? _animatedObject);
_hasGroundRealObjectBasePose = false;
var startAppliedPoint = GetLiveBoundingBoxCenter(CurrentControlledObject ?? _animatedObject);
LogManager.Info(
$"[平移到起点] 已按终点原始位姿残差搬运到起点: 路径point0=({pathStartPoint.X:F3},{pathStartPoint.Y:F3},{pathStartPoint.Z:F3}), " +
$"目标trackedPoint=({startPosition.X:F3},{startPosition.Y:F3},{startPosition.Z:F3}), " +
$"实际包围盒中心=({startAppliedPoint.X:F3},{startAppliedPoint.Y:F3},{startAppliedPoint.Z:F3}), 路径类型={_route.PathType.GetDisplayName()}");
return true;
}
catch (Exception ex)
{
LogManager.Error($"保持初始位姿平移到路径起点失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 预计算所有动画帧和碰撞信息
/// </summary>
private void PrecomputeAnimationFrames()
{
try
{
LogManager.Info("=== 使用路径预计算动画帧 ===");
// 🔥 重要:预计算前先将物体移动到起点位置
if (_animatedObject != null && _route != null && _route.Points != null && _route.Points.Count > 0)
{
var pathPoints = _route.Points.Select(p => p.Position).ToList();
MoveObjectToPathStartUsingCurrentPlacementMode(_animatedObject, pathPoints);
if (ShouldPreservePathRotationForFrames(
_route.PathType,
_objectStartPlacementMode,
_hasTrackedRotation))
{
_pathPreservedPoseRotation = _trackedRotation;
_hasPathPreservedPoseRotation = true;
LogManager.Info($"[预计算] {_route.PathType.GetDisplayName()}平移模式已锁定起点姿态为整段动画旋转基线");
}
else
{
_pathPreservedPoseRotation = Rotation3D.Identity;
_hasPathPreservedPoseRotation = false;
}
LogManager.Info("[预计算] 物体已移动到路径起点");
}
int totalFrames = (int)(_animationDuration * _animationFrameRate);
// 🔥 判断路径类型空中路径使用Points地面路径使用Edges
bool isAerialPath = _route.PathType == PathType.Rail || _route.PathType == PathType.Hoisting;
List<Point3D> allSampledPoints = new List<Point3D>();
List<double> segmentLengths = new List<double>();
if (isAerialPath)
{
// === 空中路径:直接使用路径点进行线性插值 ===
string aerialSubTypeName = _route.PathType == PathType.Rail ? "空轨" : "吊装";
LogManager.Info($"[{aerialSubTypeName}路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}");
// 将路径点转换为Point3D列表
foreach (var point in _route.Points)
{
allSampledPoints.Add(point.Position);
}
// 计算每段长度(内部计算统一使用模型单位)
for (int i = 0; i < allSampledPoints.Count - 1; i++)
{
double lengthInModelUnits = GeometryHelper.CalculatePointDistance(allSampledPoints[i], allSampledPoints[i + 1]);
segmentLengths.Add(lengthInModelUnits);
}
}
else
{
// === 地面路径使用Edges进行曲线化插值 ===
var edgeStartIndices = new List<int>();
foreach (var edge in _route.Edges)
{
if (edge.SampledPoints == null || edge.SampledPoints.Count == 0)
{
var samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep;
edge.SampledPoints = PathCurveEngine.GenerateSampledPoints(edge, samplingStep);
}
edgeStartIndices.Add(allSampledPoints.Count);
allSampledPoints.AddRange(edge.SampledPoints);
segmentLengths.Add(edge.PhysicalLength);
}
}
// 🔥 内部计算统一使用模型单位
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
double totalLengthInModelUnits = _route.TotalLength * metersToModelUnits;
string pathTypeName = _route.PathType.GetDisplayName();
LogManager.Info($"路径总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {pathTypeName}");
// 2. 按帧数采样生成动画帧
_animationFrames = new List<AnimationFrame>();
_allCollisionResults = new List<CollisionResult>();
_currentFrameIndex = 0;
// 获取动画对象的包围盒信息
var originalBoundingBox = _animatedObject.BoundingBox();
var boundingBoxSize = new Vector3D(
originalBoundingBox.Max.X - originalBoundingBox.Min.X,
originalBoundingBox.Max.Y - originalBoundingBox.Min.Y,
originalBoundingBox.Max.Z - originalBoundingBox.Min.Z
);
// 空间索引设置
SpatialIndexManager spatialIndexManager = null;
var manualTargetsSnapshot = new List<ModelItem>();
bool manualOverrideActive = _manualCollisionOverrideEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0;
// 获取配置
var config = ConfigManager.Instance.Current;
if (manualOverrideActive)
{
manualTargetsSnapshot = _manualCollisionTargets
.Where(item => item != null && ModelItemAnalysisHelper.IsModelItemValid(item) && !ReferenceEquals(item, _animatedObject))
.Distinct()
.ToList();
if (manualTargetsSnapshot.Count == 0)
{
manualOverrideActive = false;
LogManager.Warning("[手工碰撞] 手工碰撞对象列表为空或无效,自动回退到空间索引模式");
}
else
{
LogManager.Info($"[手工碰撞] 启用手工碰撞对象模式,数量: {manualTargetsSnapshot.Count},跳过空间索引构建");
}
}
if (!manualOverrideActive)
{
LogManager.Info("=== 构建全局空间索引 ===");
spatialIndexManager = SpatialIndexManager.Instance;
// 使用动画专用的空间索引格子大小配置 - 使用模型单位接口
double cellSizeInModelUnits = config.Animation.SpatialIndexCellSize;
LogManager.Info($"[空间索引] 格子大小: {cellSizeInModelUnits:F2}模型单位 (动画配置)");
// 检查空间索引是否需要重新构建(格子大小变化或未初始化)
bool needRebuild = !spatialIndexManager.IsInitialized ||
Math.Abs(spatialIndexManager.CellSize - cellSizeInModelUnits) > 0.0001;
if (needRebuild)
{
LogManager.Info("[空间索引] 空间索引未初始化或格子大小已改变,开始重新构建...");
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
}
else
{
LogManager.Info("[空间索引] 使用已缓存的空间索引");
}
LogManager.Info(spatialIndexManager.GetStatistics());
// 🔥 优化使用AABB查询代替球形查询查询范围更精确
// 不再预计算球形半径改为每帧计算物体AABB + 安全间隙的搜索AABB
LogManager.Info($"空间查询使用AABB模式安全间隙: {_safetyMargin:F4}模型单位");
}
// 3. 生成每一帧
double distancePerFrameInModelUnits = totalFrames > 1
? totalLengthInModelUnits / (totalFrames - 1)
: 0.0;
LogManager.Info($"总帧数: {totalFrames}, 总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 每帧移动距离: {distancePerFrameInModelUnits / metersToModelUnits:F4}米");
for (int i = 0; i < totalFrames; i++)
{
double targetDistance = i * distancePerFrameInModelUnits; // 按距离采样(模型单位),最后一帧应精确到终点
if (targetDistance > totalLengthInModelUnits)
{
targetDistance = totalLengthInModelUnits;
}
Point3D framePosition;
Point3D previousFramePoint = Point3D.Origin;
Point3D nextFramePoint = Point3D.Origin;
double yawRadians;
if (isAerialPath)
{
// === 空中路径:在路径点之间进行线性插值 ===
int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths);
if (segmentIndex < 0 || segmentIndex >= segmentLengths.Count)
{
string subTypeName = _route.PathType.GetDisplayName();
LogManager.Warning($"[{subTypeName}路径] 无法找到线段,目标距离:{targetDistance / metersToModelUnits:F2}米");
continue;
}
double accumulatedLength = segmentLengths.Take(segmentIndex).Sum();
double segmentProgress = (targetDistance - accumulatedLength) / segmentLengths[segmentIndex];
// 线性插值
Point3D p1 = allSampledPoints[segmentIndex];
Point3D p2 = allSampledPoints[segmentIndex + 1];
previousFramePoint = p1;
nextFramePoint = p2;
framePosition = new Point3D(
p1.X + segmentProgress * (p2.X - p1.X),
p1.Y + segmentProgress * (p2.Y - p1.Y),
p1.Z + segmentProgress * (p2.Z - p1.Z)
);
// 计算朝向(使用当前线段的方向)
yawRadians = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
// 吊装路径所有线段都使用水平吊运方向与MoveObjectToPathStart保持一致
if (_route.PathType == PathType.Hoisting)
{
if (_pathPoints.Count >= 3)
{
yawRadians = Math.Atan2(_pathPoints[2].Y - _pathPoints[1].Y, _pathPoints[2].X - _pathPoints[1].X);
}
else
{
// 2点路径纯垂直无水平方向保持当前朝向
yawRadians = _currentYaw;
}
}
// 🔥 空中路径:根据路径类型和线段索引调整物体位置
double objectHeight = _route.PathType == PathType.Rail
? GetAnimatedObjectRailNormalExtent(p1, framePosition, p2)
: GetAnimatedObjectGroundContactHeight();
if (_route.PathType == PathType.Hoisting)
{
if (_route.IsTwoPointVertical)
{
// 2点纯垂直路径起点加半高补偿地面→中心终点不加终点即中心位置
var startCenter = ResolveGroundTrackedCenter(p1, GetAnimatedObjectGroundContactHeight());
var endCenter = p2;
framePosition = new Point3D(
startCenter.X + segmentProgress * (endCenter.X - startCenter.X),
startCenter.Y + segmentProgress * (endCenter.Y - startCenter.Y),
startCenter.Z + segmentProgress * (endCenter.Z - startCenter.Z));
LogManager.Debug($"[吊装路径-2点垂直] 进度={segmentProgress:F2}, 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})");
}
else
{
// 吊装路径:支持动态数量的路径点
// 路径点结构:
// 点0起吊点地面位置
// 点1提升点悬挂点
// 点2...点N-2中间悬挂点用户添加的转向点
// 点N-1下降点悬挂点
// 点N落地点地面位置
//
// 线段结构:
// 线段0起吊段地面→悬挂点- 垂直
// 线段1...线段N-2平移段悬挂点→悬挂点- 水平
// 线段N-1下降段悬挂点→地面- 垂直
int totalSegments = _pathPoints.Count - 1;
int firstSegment = 0; // 起吊段
int lastSegment = totalSegments - 1; // 下降段
if (segmentIndex == firstSegment)
{
framePosition = ResolveHoistingTrackedCenter(framePosition, objectHeight, segmentProgress);
LogManager.Debug($"[吊装路径-起吊段] 进度={segmentProgress:F2}, 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})");
}
else if (segmentIndex > firstSegment && segmentIndex < lastSegment)
{
// 平移段(中间的所有线段):参考点位于顶部悬挂点
framePosition = ResolveHoistingTrackedCenter(framePosition, objectHeight, 1.0);
LogManager.Debug($"[吊装路径-平移段] 线段{segmentIndex}, 进度={segmentProgress:F2}, 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})");
}
else if (segmentIndex == lastSegment)
{
framePosition = ResolveHoistingTrackedCenter(framePosition, objectHeight, 1.0 - segmentProgress);
LogManager.Debug($"[吊装路径-下降段] 进度={segmentProgress:F2}, 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})");
}
}
}
else if (_route.PathType == PathType.Rail)
{
if (TryResolveRailPreservedPoseTrackedCenter(framePosition, p1, p2, objectHeight, out Point3D preservedTrackedCenter))
{
framePosition = preservedTrackedCenter;
LogManager.Debug(
$"[Rail路径] 保持姿态补偿: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), " +
$"物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})");
}
else
{
framePosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, framePosition, p1, p2, objectHeight);
LogManager.Debug($"[Rail路径] 调整物体位置: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}), 安装={_route.RailMountMode}, 参考面={(PathRoute.IsTopReferenceFaceForMountMode(_route.RailMountMode) ? "" : "")}");
}
}
}
else
{
// === 地面路径:在边内插值 ===
int edgeIndex = FindEdgeForDistance(targetDistance, segmentLengths);
if (edgeIndex < 0 || edgeIndex >= _route.Edges.Count)
{
LogManager.Warning($"无法找到边,目标距离:{targetDistance / metersToModelUnits:F2}米");
continue;
}
var edge = _route.Edges[edgeIndex];
double accumulatedLength = segmentLengths.Take(edgeIndex).Sum();
double edgeProgress = (targetDistance - accumulatedLength) / edge.PhysicalLength;
Point3D sampledPreviousPoint;
Point3D sampledNextPoint;
// 在边内插值位置
if (edge.SegmentType == PathSegmentType.Straight)
{
int sampleCount = edge.SampledPoints.Count;
int pointIndex = (int)(edgeProgress * (sampleCount - 1));
pointIndex = Math.Max(0, Math.Min(pointIndex, sampleCount - 1));
framePosition = edge.SampledPoints[pointIndex];
sampledPreviousPoint = edge.SampledPoints[Math.Max(0, pointIndex - 1)];
sampledNextPoint = edge.SampledPoints[Math.Min(sampleCount - 1, pointIndex + 1)];
}
else // Arc
{
int sampleCount = edge.SampledPoints.Count;
int pointIndex = (int)(edgeProgress * (sampleCount - 1));
pointIndex = Math.Max(0, Math.Min(pointIndex, sampleCount - 1));
framePosition = edge.SampledPoints[pointIndex];
sampledPreviousPoint = edge.SampledPoints[Math.Max(0, pointIndex - 1)];
sampledNextPoint = edge.SampledPoints[Math.Min(sampleCount - 1, pointIndex + 1)];
}
framePosition = ResolveGroundTrackedCenter(framePosition, GetAnimatedObjectGroundContactHeight());
framePosition = ApplyGroundPathObjectLiftOffset(framePosition);
yawRadians = ComputeYawOnPath(i, allSampledPoints, edgeIndex, edgeProgress);
previousFramePoint = ApplyGroundPathObjectLiftOffset(
ResolveGroundTrackedCenter(sampledPreviousPoint, GetAnimatedObjectGroundContactHeight()));
nextFramePoint = ApplyGroundPathObjectLiftOffset(
ResolveGroundTrackedCenter(sampledNextPoint, GetAnimatedObjectGroundContactHeight()));
if ((_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting) &&
i > 0 &&
(Math.Abs(previousFramePoint.X - nextFramePoint.X) > 1e-6 ||
Math.Abs(previousFramePoint.Y - nextFramePoint.Y) > 1e-6 ||
Math.Abs(previousFramePoint.Z - nextFramePoint.Z) > 1e-6))
{
LogManager.Debug(
$"[平面转弯诊断] 帧={i}, edge={edgeIndex}, " +
$"prev=({previousFramePoint.X:F3},{previousFramePoint.Y:F3},{previousFramePoint.Z:F3}), " +
$"cur=({framePosition.X:F3},{framePosition.Y:F3},{framePosition.Z:F3}), " +
$"next=({nextFramePoint.X:F3},{nextFramePoint.Y:F3},{nextFramePoint.Z:F3}), " +
$"yaw={yawRadians * 180.0 / Math.PI:F2}°");
}
}
// 创建帧并检测碰撞
var frame = new AnimationFrame
{
Index = i,
Progress = (double)i / (totalFrames - 1),
Position = framePosition,
YawRadians = yawRadians,
Collisions = new List<CollisionResult>()
};
if (ShouldUsePlanarRealObjectPureIncrementFrames(_route.PathType, IsRealObjectMode))
{
double planarHostYawRadians = yawRadians;
if (_route.PathType == PathType.Ground &&
!TryResolveGroundHostPlanarYawFromFramePoints(
previousFramePoint,
framePosition,
nextFramePoint,
CoordinateSystemManager.Instance.ResolvedType,
out planarHostYawRadians))
{
throw new InvalidOperationException(
$"{_route.PathType.GetDisplayName()}真实物体第 {i} 帧未能解析宿主平面目标角。");
}
if (!TryResolveRecordedPlanarRealObjectFrameRotation(
_route.PathType,
previousFramePoint,
framePosition,
nextFramePoint,
out var recordedRotation))
{
throw new InvalidOperationException(
$"{_route.PathType.GetDisplayName()}真实物体第 {i} 帧未能记录完整姿态。");
}
ApplyRecordedPlanarRealObjectFramePose(
frame,
planarHostYawRadians,
recordedRotation);
}
else if (ShouldPreservePathRotationForFrames(
_route.PathType,
_objectStartPlacementMode,
_hasPathPreservedPoseRotation))
{
frame.Rotation = _pathPreservedPoseRotation;
frame.HasCustomRotation = true;
}
else if (_route.PathType == PathType.Rail &&
TryCreateRailPathRotation(
previousFramePoint,
framePosition,
nextFramePoint,
out var railRotation))
{
frame.Rotation = railRotation;
frame.HasCustomRotation = true;
}
else if ((_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting) &&
!_route.IsTwoPointVertical &&
TryCreatePlanarPathRotationForFrame(previousFramePoint, framePosition, nextFramePoint, out var planarRotation))
{
if (_route.PathType == PathType.Ground &&
IsRealObjectMode &&
TryCreateGroundRealObjectConstrainedRotation(previousFramePoint, framePosition, nextFramePoint, out var constrainedRotation))
{
frame.Rotation = constrainedRotation;
frame.HasCustomRotation = true;
}
else
{
frame.Rotation = planarRotation;
frame.HasCustomRotation = true;
}
}
else if ((_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting) &&
!_route.IsTwoPointVertical)
{
throw new InvalidOperationException(
$"平面路径第 {i} 帧未能生成完整姿态,已禁止退回旧 yaw 链路。");
}
// 记录第一帧的角度(用于调试)
if (i == 0)
{
LogManager.Debug($"[预计算] 第0帧: pos=({framePosition.X:F2},{framePosition.Y:F2}), yaw={yawRadians * 180 / Math.PI:F2}度");
}
// 虚拟碰撞检测
// 计算物体包围盒相对于framePosition的偏移
var currentObjectBoundingBox = _animatedObject.BoundingBox();
var originalCenter = new Point3D(
(currentObjectBoundingBox.Min.X + currentObjectBoundingBox.Max.X) / 2,
(currentObjectBoundingBox.Min.Y + currentObjectBoundingBox.Max.Y) / 2,
(currentObjectBoundingBox.Min.Z + currentObjectBoundingBox.Max.Z) / 2
);
// 计算偏移量当前包围盒中心到framePosition的偏移
var offsetX = framePosition.X - originalCenter.X;
var offsetY = framePosition.Y - originalCenter.Y;
var offsetZ = framePosition.Z - originalCenter.Z;
// 创建新的包围盒将当前包围盒中心移动到framePosition
var virtualBoundingBox = new BoundingBox3D(
new Point3D(
currentObjectBoundingBox.Min.X + offsetX,
currentObjectBoundingBox.Min.Y + offsetY,
currentObjectBoundingBox.Min.Z + offsetZ
),
new Point3D(
currentObjectBoundingBox.Max.X + offsetX,
currentObjectBoundingBox.Max.Y + offsetY,
currentObjectBoundingBox.Max.Z + offsetZ
)
);
IEnumerable<ModelItem> nearbyObjects;
if (manualOverrideActive)
{
// 🔥 Human-in-the-Loop: 手工模式下也要应用排除列表
nearbyObjects = manualTargetsSnapshot;
if (_excludedObjects.Count > 0)
{
nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj));
}
}
else
{
// 🔥 优化使用AABB查询代替球形查询避免球体扩大的无效范围
// 查询AABB = 物体AABB + 安全间隙扩展
var searchBounds = new BoundingBox3D(
new Point3D(
virtualBoundingBox.Min.X - _safetyMargin,
virtualBoundingBox.Min.Y - _safetyMargin,
virtualBoundingBox.Min.Z - _safetyMargin
),
new Point3D(
virtualBoundingBox.Max.X + _safetyMargin,
virtualBoundingBox.Max.Y + _safetyMargin,
virtualBoundingBox.Max.Z + _safetyMargin
)
);
nearbyObjects = spatialIndexManager.FindInAABB(
searchBounds,
excludeObject: _animatedObject
);
// 🔥 Human-in-the-Loop: 应用用户排除列表过滤
if (_excludedObjects.Count > 0)
{
nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj));
}
}
foreach (var collider in nearbyObjects)
{
var colliderBox = collider.BoundingBox();
// 使用包围盒距离检测方法
double distance = BoundingBoxGeometryUtils.CalculateDistance(virtualBoundingBox, colliderBox);
// _safetyMargin 已经是模型单位在CreateAnimation中已转换直接使用
bool intersects = distance <= _safetyMargin;
if (intersects)
{
LogManager.Debug($"帧 {i} 检测到碰撞: {_animatedObject.DisplayName} <-> {collider.DisplayName}, 距离: {distance:F4},阈值: {_safetyMargin:F4}");
LogManager.Debug($"移动物体位置: {framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}");
LogManager.Debug($"被撞物体位置:{GetObjectPosition(collider).X:F2},{GetObjectPosition(collider).Y:F2},{GetObjectPosition(collider).Z:F2}");
double actualYawRadians = ResolveAnimationFramePlaybackYawRadians(frame);
var collisionResult = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
DisplayName = $"预计算碰撞: {_animatedObject.DisplayName} <-> {collider.DisplayName}",
Status = ClashResultStatus.New,
Item1 = _animatedObject,
Item2 = collider,
CreatedTime = DateTime.Now,
Distance = BoundingBoxGeometryUtils.CalculateCenterDistance(virtualBoundingBox, colliderBox),
Center = new Point3D(
(virtualBoundingBox.Min.X + virtualBoundingBox.Max.X + colliderBox.Min.X + colliderBox.Max.X) / 4,
(virtualBoundingBox.Min.Y + virtualBoundingBox.Max.Y + colliderBox.Min.Y + colliderBox.Max.Y) / 4,
(virtualBoundingBox.Min.Z + virtualBoundingBox.Max.Z + colliderBox.Min.Z + colliderBox.Max.Z) / 4
),
// AnimatedObjectTrackedPosition 统一定义为动画主链路的跟踪点位置。
// 当前动画主链路已统一使用几何中心作为跟踪点,因此所有路径都直接记录 framePosition。
AnimatedObjectTrackedPosition = new Point3D(
framePosition.X,
framePosition.Y,
framePosition.Z),
Item2Position = GetObjectPosition(collider),
AnimatedObjectTrackedYawRadians = actualYawRadians, // 记录动画跟踪点对应的运动物体实际播放朝向
AnimatedObjectTrackedRotation = frame.HasCustomRotation ? frame.Rotation : Rotation3D.Identity,
AnimatedObjectHasTrackedRotation = frame.HasCustomRotation,
HasPositionInfo = true
};
LogManager.Debug(
$"[保存碰撞结果] 帧={i}, " +
$"位置语义=动画跟踪中心点, " +
$"原始Yaw={yawRadians * 180.0 / Math.PI:F2}°, " +
$"实际Yaw={actualYawRadians * 180.0 / Math.PI:F2}°, " +
$"customRotation={frame.HasCustomRotation}, " +
$"保存位置=({collisionResult.AnimatedObjectTrackedPosition.X:F3}, {collisionResult.AnimatedObjectTrackedPosition.Y:F3}, {collisionResult.AnimatedObjectTrackedPosition.Z:F3})");
frame.Collisions.Add(collisionResult);
_allCollisionResults.Add(collisionResult);
}
}
_animationFrames.Add(frame);
}
// 统计碰撞信息
var framesWithCollision = _animationFrames.Count(f => f.HasCollision);
var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count);
// 检查碰撞结果是否包含位置信息
var collisionsWithPosition = _allCollisionResults.Count(c => c.HasPositionInfo && c.AnimatedObjectTrackedPosition != null);
if (_route.PathType == PathType.Rail && _animationFrames.Count > 0)
{
AnimationFrame firstRailFrame = _animationFrames[0];
AnimationFrame lastRailFrame = _animationFrames[_animationFrames.Count - 1];
LogManager.Info(
$"[Rail帧诊断] 首帧跟踪中心=({firstRailFrame.Position.X:F3},{firstRailFrame.Position.Y:F3},{firstRailFrame.Position.Z:F3}), " +
$"末帧跟踪中心=({lastRailFrame.Position.X:F3},{lastRailFrame.Position.Y:F3},{lastRailFrame.Position.Z:F3}), 总帧数={_animationFrames.Count}");
}
LogManager.Info($"=== 预计算完成 ===");
LogManager.Info($"总帧数: {_animationFrames.Count}");
LogManager.Info($"包含碰撞的帧: {framesWithCollision}");
LogManager.Info($"总碰撞次数: {totalCollisions}");
LogManager.Info($"记录的碰撞结果总数: {_allCollisionResults.Count} 个");
LogManager.Info($"包含位置信息的碰撞: {collisionsWithPosition}/{_allCollisionResults.Count}");
// 🔥 Human-in-the-Loop: 记录排除统计
if (_excludedObjects.Count > 0)
{
LogManager.Info($"[排除列表] 本次预计算排除了 {_excludedObjects.Count} 个物体");
}
// 🔥 清除移动物体集合
ClashDetectiveIntegration.ClearAnimatedObject();
}
catch (Exception ex)
{
LogManager.Error($"预计算动画帧失败: {ex.Message}");
_animationFrames = new List<AnimationFrame>();
}
}
/// <summary>
/// 根据路径计算指定帧的yaw角度
/// </summary>
/// <summary>
/// 计算路径上的朝向(改进版)
/// </summary>
private double ComputeYawOnPath(
int frameIndex,
List<Point3D> allSampledPoints,
int currentEdgeIndex,
double edgeProgress)
{
var edge = _route.Edges[currentEdgeIndex];
if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
{
// 圆弧段: 使用切线法计算精确朝向
Vector3D startVec = (edge.Trajectory.Ts - edge.Trajectory.ArcCenter).Normalize();
double signedAngle = edge.Trajectory.DeflectionAngle;
double currentAngle = edgeProgress * signedAngle;
// 计算切点Ts的朝向进入切点的切线方向
Vector3D tsRadiusVec = (edge.Trajectory.Ts - edge.Trajectory.ArcCenter).Normalize();
Vector3D rotationAxis = GetArcRotationAxis(edge.Trajectory);
Point3D tsTangent;
if (signedAngle >= 0)
{
tsTangent = GeometryHelper.CrossProduct(
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z),
new Point3D(tsRadiusVec.X, tsRadiusVec.Y, tsRadiusVec.Z)
);
}
else
{
tsTangent = GeometryHelper.CrossProduct(
new Point3D(tsRadiusVec.X, tsRadiusVec.Y, tsRadiusVec.Z),
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z)
);
}
double tsYaw = Math.Atan2(tsTangent.Y, tsTangent.X);
// 计算切点Te的朝向退出切点的切线方向
Vector3D teRadiusVec = (edge.Trajectory.Te - edge.Trajectory.ArcCenter).Normalize();
Point3D teTangent;
if (signedAngle >= 0)
{
teTangent = GeometryHelper.CrossProduct(
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z),
new Point3D(teRadiusVec.X, teRadiusVec.Y, teRadiusVec.Z)
);
}
else
{
teTangent = GeometryHelper.CrossProduct(
new Point3D(teRadiusVec.X, teRadiusVec.Y, teRadiusVec.Z),
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z)
);
}
double teYaw = Math.Atan2(teTangent.Y, teTangent.X);
// 计算当前帧的朝向
Point3D rotatedPoint = GeometryHelper.RotatePointAroundAxis(
edge.Trajectory.Ts,
edge.Trajectory.ArcCenter,
rotationAxis,
currentAngle
);
Vector3D radiusVec = (rotatedPoint - edge.Trajectory.ArcCenter).Normalize();
// 计算切线方向:根据旋转方向确定切线顺序
Point3D tangentPoint;
if (signedAngle >= 0)
{
tangentPoint = GeometryHelper.CrossProduct(
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z),
new Point3D(radiusVec.X, radiusVec.Y, radiusVec.Z)
);
}
else
{
tangentPoint = GeometryHelper.CrossProduct(
new Point3D(radiusVec.X, radiusVec.Y, radiusVec.Z),
new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z)
);
}
double currentYaw = Math.Atan2(tangentPoint.Y, tangentPoint.X);
double yawChange = teYaw - tsYaw;
// 计算圆弧段前后直线段的朝向
double prevStraightYaw = 0;
double nextStraightYaw = 0;
// 圆弧段前的直线段朝向
if (currentEdgeIndex > 0 && _route.Edges[currentEdgeIndex - 1].SegmentType == PathSegmentType.Straight)
{
var prevEdge = _route.Edges[currentEdgeIndex - 1];
if (prevEdge.SampledPoints != null && prevEdge.SampledPoints.Count >= 2)
{
Point3D p1 = prevEdge.SampledPoints[0];
Point3D p2 = prevEdge.SampledPoints[prevEdge.SampledPoints.Count - 1];
prevStraightYaw = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
}
}
// 圆弧段后的直线段朝向
if (currentEdgeIndex < _route.Edges.Count - 1 && _route.Edges[currentEdgeIndex + 1].SegmentType == PathSegmentType.Straight)
{
var nextEdge = _route.Edges[currentEdgeIndex + 1];
if (nextEdge.SampledPoints != null && nextEdge.SampledPoints.Count >= 2)
{
Point3D p1 = nextEdge.SampledPoints[0];
Point3D p2 = nextEdge.SampledPoints[nextEdge.SampledPoints.Count - 1];
nextStraightYaw = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
}
}
// 在第一帧打印完整信息(使用静态变量确保每个圆弧段只打印一次)
if (!_arcInfoPrinted.Contains(currentEdgeIndex))
{
LogManager.Debug($"圆弧段[{currentEdgeIndex}]朝向信息:");
LogManager.Debug($" 前直线段朝向: {prevStraightYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 进入切点(Ts)朝向: {tsYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 圆弧偏转角度: {signedAngle * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 退出切点(Te)朝向: {teYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 后直线段朝向: {nextStraightYaw * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 前后直线段朝向差: {(nextStraightYaw - prevStraightYaw) * 180.0 / Math.PI:F2}度");
LogManager.Debug($" 圆弧段朝向变化: {yawChange * 180.0 / Math.PI:F2}度");
_arcInfoPrinted.Add(currentEdgeIndex);
}
// 每一帧都打印当前朝向
// LogManager.Debug($"帧[{frameIndex}] 圆弧段[{currentEdgeIndex}]: 进度={edgeProgress:F4}, 当前朝向={currentYaw * 180.0 / Math.PI:F2}度");
return currentYaw;
}
else
{
// 直线段: 使用直线方向向量,避免边界处的前后帧差分问题
Point3D startPos = edge.SampledPoints[0];
Point3D endPos = edge.SampledPoints[edge.SampledPoints.Count - 1];
double dx = endPos.X - startPos.X;
double dy = endPos.Y - startPos.Y;
double length = Math.Sqrt(dx * dx + dy * dy);
if (length < 1e-6)
{
// 直线段长度太短,尝试使用前后帧差分
Point3D currentPos = allSampledPoints.Count > 0 ? allSampledPoints[Math.Min(frameIndex, allSampledPoints.Count - 1)] : new Point3D();
int nextIndex = Math.Min(frameIndex + 1, allSampledPoints.Count - 1);
Point3D nextPos = allSampledPoints[nextIndex];
dx = nextPos.X - currentPos.X;
dy = nextPos.Y - currentPos.Y;
length = Math.Sqrt(dx * dx + dy * dy);
if (length < 1e-6)
{
return 0.0;
}
}
return Math.Atan2(dy, dx);
}
}
/// <summary>
/// 获取圆弧旋转轴
/// </summary>
private Vector3D GetArcRotationAxis(ArcTrajectory trajectory)
{
Vector3D startVec = (trajectory.Ts - trajectory.ArcCenter).Normalize();
Vector3D endVec = (trajectory.Te - trajectory.ArcCenter).Normalize();
Point3D cross = GeometryHelper.CrossProduct(
new Point3D(startVec.X, startVec.Y, startVec.Z),
new Point3D(endVec.X, endVec.Y, endVec.Z)
);
return new Vector3D(cross.X, cross.Y, cross.Z).Normalize();
}
/// <summary>
/// 开始播放动画
/// </summary>
public void StartAnimation()
{
try
{
if (_animatedObject == null || _pathPoints.Count < 2)
{
throw new InvalidOperationException("请先调用SetupAnimation设置动画参数");
}
// 🔥 关键:动画开始前确保物体在起点位置和朝向
// 动画结束时物体在终点,重新播放前必须重置
if (_animationFrames != null && _animationFrames.Count > 0)
{
var firstFrame = _animationFrames[0];
double distanceToStart;
bool needsReset;
string mismatchSummary;
if (firstFrame.HasCustomRotation)
{
var dx = _trackedPosition.X - firstFrame.Position.X;
var dy = _trackedPosition.Y - firstFrame.Position.Y;
var dz = _trackedPosition.Z - firstFrame.Position.Z;
distanceToStart = Math.Sqrt(dx * dx + dy * dy + dz * dz);
var currentLinear = new Transform3D(_trackedRotation).Linear;
var targetLinear = new Transform3D(firstFrame.Rotation).Linear;
double rotationDiff =
Math.Abs(currentLinear.Get(0, 0) - targetLinear.Get(0, 0)) +
Math.Abs(currentLinear.Get(0, 1) - targetLinear.Get(0, 1)) +
Math.Abs(currentLinear.Get(0, 2) - targetLinear.Get(0, 2)) +
Math.Abs(currentLinear.Get(1, 0) - targetLinear.Get(1, 0)) +
Math.Abs(currentLinear.Get(1, 1) - targetLinear.Get(1, 1)) +
Math.Abs(currentLinear.Get(1, 2) - targetLinear.Get(1, 2)) +
Math.Abs(currentLinear.Get(2, 0) - targetLinear.Get(2, 0)) +
Math.Abs(currentLinear.Get(2, 1) - targetLinear.Get(2, 1)) +
Math.Abs(currentLinear.Get(2, 2) - targetLinear.Get(2, 2));
needsReset = distanceToStart > 0.1 || rotationDiff > 0.01;
mismatchSummary = $"距离={distanceToStart:F2}m, 三维姿态差={rotationDiff:F4}";
}
else
{
var currentPos = _animatedObject.BoundingBox().Center;
distanceToStart = Math.Sqrt(
Math.Pow(currentPos.X - firstFrame.Position.X, 2) +
Math.Pow(currentPos.Y - firstFrame.Position.Y, 2)
);
var yawDiff = Math.Abs(_currentYaw - ResolveAnimationFramePlaybackYawRadians(firstFrame));
if (yawDiff > Math.PI) yawDiff = 2 * Math.PI - yawDiff;
needsReset = distanceToStart > 0.1 || yawDiff > 0.01;
mismatchSummary = $"距离={distanceToStart:F2}m, 角度差={yawDiff * 180 / Math.PI:F2}°";
}
if (needsReset)
{
LogManager.Info($"[动画开始] 物体不在起点,重置到起点: {mismatchSummary}");
MoveObjectToPathStartUsingCurrentPlacementMode();
}
}
// 创建 TimeLiner 任务(如果可用且未创建过)
if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable)
{
string objectName = "UnknownObject";
try
{
objectName = _animatedObject?.DisplayName ?? "UnknownObject";
}
catch (Exception)
{
objectName = "DisposedObject";
}
// 使用动画配置hash作为任务标识
var hashPrefix = _currentAnimationHash.Substring(0, Math.Min(8, _currentAnimationHash.Length));
var taskName = $"{objectName}_运输_{hashPrefix}";
// 检查是否已存在相同配置的任务
var existingTaskId = _timeLinerManager.FindTaskByName(taskName);
if (!string.IsNullOrEmpty(existingTaskId))
{
_currentTaskId = existingTaskId;
LogManager.Info($"复用现有 TimeLiner 任务: {taskName} (ID: {_currentTaskId})");
}
else
{
// 创建新任务
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 不可用,使用基础动画功能");
}
// 初始化动画状态
_animationStartTime = DateTime.Now;
_currentFrameIndex = 0; // 重置当前帧索引,确保从第一帧开始
_pausedProgress = 0.0; // 重置暂停进度
// 初始化动画状态时不再强行覆盖 _currentYaw 为第一帧的 yaw
// 由记录姿态应用链自行解释第一帧姿态。
if (_animationFrames != null && _animationFrames.Count > 0)
{
var firstFrame = _animationFrames[0];
LogManager.Debug($"[动画开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, 模式={_animatedObjectMode}");
// 🔥 确保物体在起点位置和朝向
// 注意MoveObjectToPathStart 已在 StartAnimation 中调用,这里只是日志记录
LogManager.Debug(
$"[动画开始] 物体应在起点: pos=({firstFrame.Position.X:F2},{firstFrame.Position.Y:F2}), " +
$"yaw={ResolveAnimationFramePlaybackYawRadians(firstFrame):F3}rad");
}
else
{
_currentYaw = 0.0;
}
// 重置动画状态
_lastFrameTime = DateTime.MinValue;
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
_frameInterval = 1000.0 / _animationFrameRate;
_lastHighlightState = false; // 重置高亮状态
_lastCollisionObjects.Clear(); // 重置碰撞对象集合
// 清除所有碰撞高亮
ModelHighlightHelper.ClearCollisionHighlights();
LogManager.Debug("[动画开始] 已清除所有碰撞高亮");
// 不再高亮物体对象,保持原始外观(客户要求)
// if (_animatedObject != null)
// {
// var objectItems = new List<ModelItem> { _animatedObject };
// ModelHighlightHelper.HighlightItems(ModelHighlightHelper.AnimatedObjectCategory, objectItems);
// LogManager.Debug("[动画开始] 已高亮物体对象为绿色");
// }
// 启动动画播放
StartAnimationPlayback();
SetState(AnimationState.Playing);
LogManager.Info($"动画开始播放 - DispatcherTimer模式");
}
catch (Exception ex)
{
LogManager.Error($"启动动画失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 启动动画播放DispatcherTimer
/// </summary>
private void StartAnimationPlayback()
{
try
{
// 停止之前的播放
StopAnimationPlayback();
// 使用DispatcherTimer模式
_animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval);
_animationTimer.Start();
LogManager.Debug("[播放模式] 启动DispatcherTimer模式");
}
catch (Exception ex)
{
LogManager.Error($"启动动画播放失败: {ex.Message}");
}
}
/// <summary>
/// 停止动画播放停止DispatcherTimer
/// </summary>
private void StopAnimationPlayback()
{
try
{
// 停止DispatcherTimer
if (_animationTimer != null)
{
_animationTimer.Stop();
}
LogManager.Debug("[播放模式] 已停止DispatcherTimer");
}
catch (Exception ex)
{
LogManager.Warning($"停止动画播放时出现警告: {ex.Message}");
}
}
/// <summary>
/// DispatcherTimer事件处理器 - 主要动画机制
/// </summary>
private void OnTimerTick(object sender, EventArgs e)
{
try
{
// 处理动画帧
ProcessAnimationFrame();
// 关键改进:显式请求重绘,解决卡顿
NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
}
catch (Exception ex)
{
LogManager.Error($"[DispatcherTimer模式] 动画更新异常: {ex.Message}");
// 发生异常时停止动画
ShutdownAnimation();
}
}
/// <summary>
/// 停止动画
/// </summary>
public void CancelAnimation()
{
try
{
// 检查动画状态,避免重复停止
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过重复停止操作");
return;
}
// 停止播放机制
StopAnimationPlayback();
// 收集整个路径上的所有碰撞对象
var allCollisionResults = new List<CollisionResult>();
var uniqueCollidedItems = new HashSet<ModelItem>();
foreach (var frame in _animationFrames)
{
if (frame.HasCollision)
{
foreach (var collision in frame.Collisions)
{
// 收集唯一的碰撞对象
if (!uniqueCollidedItems.Contains(collision.Item2))
{
uniqueCollidedItems.Add(collision.Item2);
allCollisionResults.Add(collision);
}
}
}
}
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
// 清除所有高亮(包括物体和碰撞)
ModelHighlightHelper.ClearAllHighlights();
LogManager.Info("动画停止:已清除所有高亮");
// 动画停止时保持物体在当前位置(不移回起点)
LogManager.Info("动画已停止,物体保持在当前位置");
// 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理
LogManager.Info("动画停止,等待动画完成事件统一处理碰撞测试...");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 0.0, AnimationState.Stopped);
}
}
catch (Exception ex)
{
LogManager.Error($"停止动画失败: {ex.Message}");
}
}
/// <summary>
/// 程序关闭时的动画清理不执行UI操作
/// </summary>
public void ShutdownAnimation()
{
try
{
LogManager.Info("[PathAnimationManager] 程序关闭,执行动画清理");
// 检查动画状态,避免重复停止
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过关闭清理");
return;
}
// 停止播放机制(关闭时不记录详细日志)
try
{
if (_animationTimer != null) _animationTimer.Stop();
}
catch (Exception stopEx)
{
LogManager.Debug($"关闭清理时停止播放机制异常: {stopEx.Message}");
}
// 直接设置状态为停止不执行任何UI操作
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
LogManager.Info("[PathAnimationManager] 动画关闭清理完成");
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 关闭动画清理失败: {ex.Message}");
}
}
/// <summary>
/// 完成动画(当动画自然播放结束时调用)- 优化版本,使用预计算碰撞结果
/// </summary>
private void FinishAnimation()
{
try
{
// 停止播放机制
StopAnimationPlayback();
// 重置暂停进度
_pausedProgress = 0.0;
// 显式落到最后一帧,避免播放结束时视觉上停在倒数第二帧,
// 但后续碰撞链又按最终姿态重新摆正,造成“结束后又往前窜一点”。
if (_animationFrames != null && _animationFrames.Count > 0)
{
int lastFrameIndex = _animationFrames.Count - 1;
if (_currentFrameIndex != lastFrameIndex)
{
_currentFrameIndex = lastFrameIndex;
ApplyRecordedPose(_animationFrames[_currentFrameIndex], _currentFrameIndex);
LogManager.Info("[动画结束] 当前尚未到最后一帧,已补落最终姿态");
}
else
{
LogManager.Info("[动画结束] 当前已在最后一帧,保持真实结束位置,不再重复落位");
}
ProgressChanged?.Invoke(this, 100.0);
try
{
NavisApplication.ActiveDocument?.ActiveView?.RequestDelayedRedraw(ViewRedrawRequests.All);
}
catch (Exception redrawEx)
{
LogManager.Warning($"动画结束时请求最终重绘失败: {redrawEx.Message}");
}
}
// 使用预计算的所有碰撞结果进行高亮显示(需要去重以避免重复高亮)
var allCollisionResults = _allCollisionResults
.GroupBy(c => new { c.Item1, c.Item2 })
.Select(g => g.First())
.ToList();
// 动画自然结束时保持物体在最终位置(不移回起点)
LogManager.Info("动画播放自然结束,物体保持在最终位置");
LogEndAlignmentDiagnostics();
// 清除所有高亮(包括物体和碰撞)
ModelHighlightHelper.ClearAllHighlights();
LogManager.Info("动画完成:已清除所有高亮");
// 修复:显示正确的总帧数,而不是最后一帧的索引
var actualTotalFrames = _animationFrames?.Count ?? 0;
LogManager.Info($"动画播放完成,总帧数: {actualTotalFrames}, 平均FPS: {_actualFPS:F1}");
// 🔥 检查是否使用历史记录如果是则跳过ClashDetective测试
if (_isUsingHistoryRecord)
{
LogManager.Info("[动画完成] 使用历史检测记录跳过ClashDetective测试");
}
else
{
// 🔥 执行碰撞检测(重复检查已在保存检测记录时完成,这里直接执行)
LogManager.Info($"开始创建碰撞测试汇总(基于 {_allCollisionResults.Count} 个预计算碰撞记录)...");
// 设置等待光标,避免进度条关闭后的无响应
var oldCursor = System.Windows.Input.Mouse.OverrideCursor;
System.Windows.Input.Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
bool testCompleted = false;
try
{
// 使用用户设置的检测容差(米)
testCompleted = ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests(
_allCollisionResults,
_detectionTolerance,
_currentRouteId,
_animatedObject,
IsVirtualObjectMode,
_animationFrameRate,
_animationDuration,
_virtualObjectLength,
_virtualObjectWidth,
_virtualObjectHeight,
_pathPoints,
_currentDetectionRecordId
);
}
finally
{
// 恢复光标
System.Windows.Input.Mouse.OverrideCursor = oldCursor;
}
if (testCompleted)
{
LogManager.Info($"碰撞测试汇总已创建完成");
}
else
{
LogManager.Info("碰撞检测被取消");
}
}
// 后处理全部完成后再进入 Finished。
// 否则 UI 会在碰撞汇总仍在运行时启动报告生成,和当前物体恢复链路并发,导致结束后姿态被再次改写。
SetState(AnimationState.Finished);
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 1.0, AnimationState.Finished);
}
}
catch (Exception ex)
{
LogManager.Error($"完成动画失败: {ex.Message}");
}
}
/// <summary>
/// 输出路径动画终点对齐诊断。
/// 只做日志,不改变业务行为,用于确认:
/// 1. 路径终点锚点
/// 2. 最后一帧动画跟踪点
/// 3. 动画结束时的实际跟踪点
/// 4. 沿路径前进方向的终点偏差
/// </summary>
private void LogEndAlignmentDiagnostics()
{
try
{
if (_route == null || _pathPoints == null || _pathPoints.Count < 2 || _animationFrames == null || _animationFrames.Count == 0)
{
return;
}
Point3D terminalAnchorPoint = _pathPoints[_pathPoints.Count - 1];
Point3D previousAnchorPoint = _pathPoints[_pathPoints.Count - 2];
var lastFrame = _animationFrames[_animationFrames.Count - 1];
Point3D finalTrackedPoint = _trackedPosition;
Point3D expectedTrackedEndPoint;
if (_route.PathType == PathType.Rail)
{
double objectHeight = GetAnimatedObjectRailNormalExtent(previousAnchorPoint, terminalAnchorPoint, terminalAnchorPoint);
if (!TryResolveRailPreservedPoseTrackedCenter(terminalAnchorPoint, previousAnchorPoint, terminalAnchorPoint, objectHeight, out expectedTrackedEndPoint))
{
expectedTrackedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
_route,
terminalAnchorPoint,
previousAnchorPoint,
terminalAnchorPoint,
objectHeight);
}
}
else if (_route.PathType == PathType.Hoisting)
{
expectedTrackedEndPoint = ResolveHoistingTrackedCenter(
terminalAnchorPoint,
GetAnimatedObjectGroundContactHeight(),
0.0);
}
else
{
expectedTrackedEndPoint = ResolveGroundTrackedCenter(
terminalAnchorPoint,
GetAnimatedObjectGroundContactHeight());
}
Vector3D forward = new Vector3D(
terminalAnchorPoint.X - previousAnchorPoint.X,
terminalAnchorPoint.Y - previousAnchorPoint.Y,
terminalAnchorPoint.Z - previousAnchorPoint.Z);
double forwardLength = Math.Sqrt(forward.X * forward.X + forward.Y * forward.Y + forward.Z * forward.Z);
if (forwardLength < 1e-9)
{
return;
}
Vector3D normalizedForward = new Vector3D(
forward.X / forwardLength,
forward.Y / forwardLength,
forward.Z / forwardLength);
Vector3D frameDelta = new Vector3D(
lastFrame.Position.X - expectedTrackedEndPoint.X,
lastFrame.Position.Y - expectedTrackedEndPoint.Y,
lastFrame.Position.Z - expectedTrackedEndPoint.Z);
Vector3D finalDelta = new Vector3D(
finalTrackedPoint.X - expectedTrackedEndPoint.X,
finalTrackedPoint.Y - expectedTrackedEndPoint.Y,
finalTrackedPoint.Z - expectedTrackedEndPoint.Z);
double frameForwardDelta = Dot(frameDelta, normalizedForward);
double finalForwardDelta = Dot(finalDelta, normalizedForward);
string pathLabel = _route.PathType.GetDisplayName();
LogManager.Info(
$"[{pathLabel}终点诊断] 终点锚点=({terminalAnchorPoint.X:F3},{terminalAnchorPoint.Y:F3},{terminalAnchorPoint.Z:F3}), " +
$"期望跟踪点=({expectedTrackedEndPoint.X:F3},{expectedTrackedEndPoint.Y:F3},{expectedTrackedEndPoint.Z:F3}), " +
$"最后一帧跟踪点=({lastFrame.Position.X:F3},{lastFrame.Position.Y:F3},{lastFrame.Position.Z:F3}), " +
$"动画结束跟踪点=({finalTrackedPoint.X:F3},{finalTrackedPoint.Y:F3},{finalTrackedPoint.Z:F3})");
LogManager.Info(
$"[{pathLabel}终点诊断] 前进方向=({normalizedForward.X:F4},{normalizedForward.Y:F4},{normalizedForward.Z:F4}), " +
$"最后一帧偏差=({frameDelta.X:F3},{frameDelta.Y:F3},{frameDelta.Z:F3}), 沿前进轴={frameForwardDelta:F3}, " +
$"结束偏差=({finalDelta.X:F3},{finalDelta.Y:F3},{finalDelta.Z:F3}), 沿前进轴={finalForwardDelta:F3}");
}
catch (Exception ex)
{
LogManager.Warning($"[终点诊断] 输出失败: {ex.Message}");
}
}
private static double Dot(Vector3D a, Vector3D b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
}
/// <summary>
/// 暂停动画
/// </summary>
public void PauseAnimation()
{
try
{
if (_currentState == AnimationState.Playing)
{
// 计算并保存当前进度
double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;
_pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0);
// 停止播放机制
StopAnimationPlayback();
SetState(AnimationState.Paused);
LogManager.Info($"动画已暂停,当前进度: {_pausedProgress:F3}");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, _pausedProgress, AnimationState.Paused);
}
}
}
catch (Exception ex)
{
LogManager.Error($"暂停动画失败: {ex.Message}");
}
}
/// <summary>
/// 恢复动画
/// </summary>
public void ResumeAnimation()
{
try
{
if (_currentState == AnimationState.Paused)
{
// 使用保存的暂停进度重新设置动画开始时间
// 设置开始时间,使得当前时间对应保存的暂停进度
_animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration));
// 重新启动播放机制
_lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行
StartAnimationPlayback();
SetState(AnimationState.Playing);
LogManager.Info($"动画已恢复,从进度 {_pausedProgress:F3} 继续播放");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, _pausedProgress, AnimationState.Playing);
}
}
}
catch (Exception ex)
{
LogManager.Error($"恢复动画失败: {ex.Message}");
}
}
/// <summary>
/// 重置动画对象到原始位置
/// </summary>
public void ResetAnimation()
{
try
{
// 只在需要时停止动画,避免重复调用
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
CancelAnimation(); // 停止当前动画
}
// 恢复对象的原始变换安全检查Navisworks对象可用性
if (_animatedObject != null)
{
try
{
// 检查Navisworks应用程序和文档是否仍然可用
var activeDoc = NavisApplication.ActiveDocument;
if (activeDoc != null && activeDoc.Models != null)
{
// 第一步:仅仅彻底归位到 CAD 原始位置(不涉及路径起点),这是“清除”按钮的逻辑
RestoreObjectToCADPosition();
// 第二步:如果已经生成了动画路径,则重新对齐到起点(带旋转)
if (_animationFrames != null && _animationFrames.Count > 0)
{
MoveObjectToPathStart();
LogManager.Info("已重新对齐到路径起点");
}
}
else
{
LogManager.Info("Navisworks文档已不可用跳回空闲状态");
}
}
catch (Exception resetEx)
{
LogManager.Warning($"重置对象位置时出现警告: {resetEx.Message}");
// 不再抛出异常,因为在资源清理阶段这是正常的
}
}
// 重置UI状态这些操作不依赖Navisworks对象
try
{
ProgressChanged?.Invoke(this, 0); // 重置进度条
SetState(AnimationState.Ready); // 重置后回到就绪状态
}
catch (Exception uiEx)
{
LogManager.Warning($"重置UI状态时出现警告: {uiEx.Message}");
}
}
catch (Exception ex)
{
LogManager.Error($"重置动画过程中发生异常: {ex.Message}");
// 在资源清理阶段不再抛出异常,避免影响程序正常关闭
}
}
/// <summary>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
/// <param name="item">ModelItem对象</param>
/// <returns>安全的显示名称</returns>
private string GetSafeObjectName(ModelItem item)
{
try
{
if (item == null)
{
return "NULL";
}
// 尝试访问DisplayName如果对象已被释放会抛出异常
return item.DisplayName ?? "未命名对象";
}
catch (System.ObjectDisposedException)
{
return "已释放对象";
}
catch (System.Runtime.InteropServices.COMException)
{
return "COM对象已释放";
}
catch (Exception ex)
{
return $"访问失败({ex.GetType().Name})";
}
}
/// <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 || i == _pathPoints.Count - 2)
{
// 在这个线段内,或者是最后一个线段
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 Point3D InterpolateOnStraightEdge(PathEdge edge, double progress)
{
if (edge.SampledPoints == null || edge.SampledPoints.Count < 2)
return edge.Trajectory?.Ts ?? new Point3D();
int pointIndex = (int)(progress * (edge.SampledPoints.Count - 1));
pointIndex = Math.Max(0, Math.Min(pointIndex, edge.SampledPoints.Count - 1));
return edge.SampledPoints[pointIndex];
}
/// <summary>
/// 在圆弧边上插值位置
/// </summary>
private Point3D InterpolateOnArcEdge(PathEdge edge, double progress)
{
if (edge.Trajectory == null || edge.SampledPoints == null || edge.SampledPoints.Count < 2)
return edge.Trajectory?.Ts ?? new Point3D();
int pointIndex = (int)(progress * (edge.SampledPoints.Count - 1));
pointIndex = Math.Max(0, Math.Min(pointIndex, edge.SampledPoints.Count - 1));
return edge.SampledPoints[pointIndex];
}
/// <summary>
/// 查找指定距离对应的边索引
/// </summary>
private int FindEdgeForDistance(double targetDistance, List<double> edgeLengths)
{
double accumulatedLength = 0.0;
for (int i = 0; i < edgeLengths.Count; i++)
{
if (accumulatedLength + edgeLengths[i] >= targetDistance)
{
return i;
}
accumulatedLength += edgeLengths[i];
}
return edgeLengths.Count - 1;
}
/// <summary>
/// 找到目标距离所在的线段索引(用于空轨路径的线性插值)
/// </summary>
/// <param name="targetDistance">目标距离</param>
/// <param name="segmentLengths">线段长度列表</param>
/// <returns>线段索引</returns>
private int FindSegmentForDistance(double targetDistance, List<double> segmentLengths)
{
double accumulatedLength = 0.0;
for (int i = 0; i < segmentLengths.Count; i++)
{
if (accumulatedLength + segmentLengths[i] >= targetDistance)
{
return i;
}
accumulatedLength += segmentLengths[i];
}
return segmentLengths.Count - 1;
}
/// <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 Point3D GetObjectPosition(ModelItem item)
{
try
{
if (item == null) return new Point3D(0, 0, 0);
var bounds = item.BoundingBox();
if (bounds != null)
{
return new Point3D(
(bounds.Min.X + bounds.Max.X) / 2,
(bounds.Min.Y + bounds.Max.Y) / 2,
(bounds.Min.Z + bounds.Max.Z) / 2
);
}
return new Point3D(0, 0, 0);
}
catch (Exception ex)
{
LogManager.Error($"获取对象位置失败: {ex.Message}");
return new Point3D(0, 0, 0);
}
}
/// <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>
/// 仅按业务跟踪点执行平移,不改变当前姿态语义。
/// PreserveInitialPose 起点搬运等链路应显式使用此入口,而不是借用旧 yaw 链。
/// </summary>
private void ApplyTrackedTranslationOnly(Point3D newPosition)
{
try
{
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 计算平移和旋转的增量
var deltaPos = new Vector3D(
newPosition.X - _trackedPosition.X,
newPosition.Y - _trackedPosition.Y,
newPosition.Z - _trackedPosition.Z
);
Transform3D incrementalTransform = Transform3D.CreateTranslation(deltaPos);
LogManager.Debug($"[TrackedTranslation] 纯平移: ({deltaPos.X:F2},{deltaPos.Y:F2},{deltaPos.Z:F2})");
// 应用增量变换false = 增量模式)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
// 更新跟踪位置
_trackedPosition = newPosition;
}
catch (Exception ex)
{
LogManager.Error($"更新部件位置失败: {ex.Message}");
}
}
/// <summary>
/// 使用完整三维姿态更新对象位置。
/// 当前先用于 Rail 路径,避免影响现有地面/吊装路径的 yaw 流程。
/// </summary>
private void ApplyFullPoseUpdate(Point3D newPosition, Rotation3D newRotation)
{
try
{
ModelItem controlledObject = CurrentControlledObject;
bool isRailRealObject = _route?.PathType == PathType.Rail && IsRealObjectMode && controlledObject != null;
if (controlledObject == null)
{
return;
}
if (IsVirtualObjectMode)
{
VirtualObjectManager.Instance.MoveVirtualObject(newPosition, newRotation);
_trackedPosition = newPosition;
_trackedRotation = newRotation;
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(newRotation);
if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
{
LogHostActualPoseAxes(CurrentControlledObject, "[平面姿态应用后宿主姿态]", false);
}
return;
}
Point3D currentPositionForTransform = _trackedPosition;
Rotation3D currentRotation;
Rotation3D appliedTargetRotation = newRotation;
Point3D appliedTargetPosition = newPosition;
if (_hasTrackedRotation)
{
currentRotation = _trackedRotation;
}
else if (IsRealObjectMode && _hasRealObjectReferenceRotation)
{
currentRotation = CoordinateSystemManager.Instance
.CreateHostAdapter()
.FromHostQuaternionDirect(_realObjectReferenceRotation);
}
else
{
currentRotation = controlledObject.Transform.Factor().Rotation;
}
try
{
var actualHostPosition = GetLiveBoundingBoxCenter(controlledObject);
if (!(IsRealObjectMode && _route?.PathType == PathType.Ground))
{
currentPositionForTransform = actualHostPosition;
}
LogManager.Info(
$"[动画姿态入口] {controlledObject.DisplayName} 宿主即时读回点=({actualHostPosition.X:F3},{actualHostPosition.Y:F3},{actualHostPosition.Z:F3})");
if (IsRealObjectMode &&
(_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting) &&
ModelItemTransformHelper.TryGetCurrentGeometryRotation(controlledObject, out var actualGeometryRotation))
{
var trackedLinear = new Transform3D(currentRotation).Linear;
var actualLinear = new Transform3D(actualGeometryRotation).Linear;
LogManager.Info(
$"[平面姿态基线诊断] {controlledObject.DisplayName} 跟踪旋转: " +
$"X=({trackedLinear.Get(0, 0):F4},{trackedLinear.Get(1, 0):F4},{trackedLinear.Get(2, 0):F4}), " +
$"Y=({trackedLinear.Get(0, 1):F4},{trackedLinear.Get(1, 1):F4},{trackedLinear.Get(2, 1):F4}), " +
$"Z=({trackedLinear.Get(0, 2):F4},{trackedLinear.Get(1, 2):F4},{trackedLinear.Get(2, 2):F4})");
LogManager.Info(
$"[平面姿态基线诊断] {controlledObject.DisplayName} 实际几何旋转: " +
$"X=({actualLinear.Get(0, 0):F4},{actualLinear.Get(1, 0):F4},{actualLinear.Get(2, 0):F4}), " +
$"Y=({actualLinear.Get(0, 1):F4},{actualLinear.Get(1, 1):F4},{actualLinear.Get(2, 1):F4}), " +
$"Z=({actualLinear.Get(0, 2):F4},{actualLinear.Get(1, 2):F4},{actualLinear.Get(2, 2):F4})");
}
}
catch (Exception ex)
{
LogManager.Warning($"[动画姿态入口] 读取宿主实际状态失败: {ex.Message}");
}
var currentLinear = new Transform3D(currentRotation).Linear;
var targetLinear = new Transform3D(appliedTargetRotation).Linear;
LogManager.Info(
$"[动画姿态入口] {controlledObject.DisplayName} 跟踪点=({_trackedPosition.X:F3},{_trackedPosition.Y:F3},{_trackedPosition.Z:F3}), " +
$"目标点=({appliedTargetPosition.X:F3},{appliedTargetPosition.Y:F3},{appliedTargetPosition.Z:F3})");
LogManager.Info(
$"[动画姿态入口] {controlledObject.DisplayName} 跟踪姿态: " +
$"X=({currentLinear.Get(0, 0):F4},{currentLinear.Get(1, 0):F4},{currentLinear.Get(2, 0):F4}), " +
$"Y=({currentLinear.Get(0, 1):F4},{currentLinear.Get(1, 1):F4},{currentLinear.Get(2, 1):F4}), " +
$"Z=({currentLinear.Get(0, 2):F4},{currentLinear.Get(1, 2):F4},{currentLinear.Get(2, 2):F4})");
LogManager.Info(
$"[动画姿态入口] {controlledObject.DisplayName} 目标姿态: " +
$"X=({targetLinear.Get(0, 0):F4},{targetLinear.Get(1, 0):F4},{targetLinear.Get(2, 0):F4}), " +
$"Y=({targetLinear.Get(0, 1):F4},{targetLinear.Get(1, 1):F4},{targetLinear.Get(2, 1):F4}), " +
$"Z=({targetLinear.Get(0, 2):F4},{targetLinear.Get(1, 2):F4},{targetLinear.Get(2, 2):F4})");
ModelItemTransformHelper.MoveItemIncrementallyToPositionAndRotation(
controlledObject,
currentPositionForTransform,
currentRotation,
appliedTargetPosition,
appliedTargetRotation);
_trackedPosition = appliedTargetPosition;
_trackedRotation = appliedTargetRotation;
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(appliedTargetRotation);
if (isRailRealObject)
{
Point3D hostActualAfter = GetLiveBoundingBoxCenter(controlledObject);
LogManager.Debug(
$"[Rail姿态应用] 宿主最终跟踪中心=({hostActualAfter.X:F3},{hostActualAfter.Y:F3},{hostActualAfter.Z:F3}), " +
$"目标跟踪中心=({newPosition.X:F3},{newPosition.Y:F3},{newPosition.Z:F3}), " +
$"偏差=({hostActualAfter.X - newPosition.X:F3},{hostActualAfter.Y - newPosition.Y:F3},{hostActualAfter.Z - newPosition.Z:F3})");
}
else if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
{
LogHostActualPoseAxes(CurrentControlledObject, "[平面姿态应用后宿主姿态]", false);
}
}
catch (Exception ex)
{
LogManager.Error($"使用完整姿态更新对象位置失败: {ex.Message}");
}
}
/// <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>
/// 设置动画持续时间
/// </summary>
public void SetAnimationDuration(double durationSeconds)
{
_animationDuration = Math.Max(1.0, durationSeconds);
}
/// <summary>
/// 获取动画状态
/// </summary>
public bool IsAnimating => _currentState == AnimationState.Playing;
/// <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;
#region
/// <summary>
/// 获取当前帧索引
/// </summary>
public int CurrentFrame => _currentFrameIndex;
/// <summary>
/// 获取总帧数
/// </summary>
public int TotalFrames => _animationFrames?.Count ?? 0;
/// <summary>
/// 获取物体当前位置和朝向
/// 🔥 关键返回实际当前朝向_currentYaw而不是 Transform 的 CAD 原始朝向
/// </summary>
public (Point3D Position, double Yaw) GetObjectCurrentPosition(ModelItem obj)
{
if (obj == null)
{
LogManager.Warning("GetObjectCurrentPosition: 对象为空");
return (null, 0);
}
var position = _trackedPosition;
// 🔥 关键:使用 _currentYaw实际当前朝向而不是 Transform 的 CAD 原始朝向
// Transform 返回的是 CAD 设计时的原始朝向,不是动画后的实际朝向
return (position, _currentYaw);
}
/// <summary>
/// 保存物体当前位置和朝向
/// </summary>
public void SaveObjectState(ModelItem obj)
{
if (obj == null)
{
LogManager.Warning("SaveObjectState: 对象为空");
return;
}
try
{
var (position, yaw) = GetObjectCurrentPosition(obj);
_savedObjectPosition = position;
_savedObjectYaw = yaw;
_savedObjectRotation = _trackedRotation;
_savedObjectHasCustomRotation = _hasTrackedRotation;
_hasSavedObjectState = true;
LogManager.Info($"已保存物体状态: pos=({position.X:F2},{position.Y:F2},{position.Z:F2}), yaw={yaw * 180 / Math.PI:F2}度, customRotation={_savedObjectHasCustomRotation}");
if (_savedObjectHasCustomRotation)
{
var linear = new Transform3D(_savedObjectRotation).Linear;
LogManager.Info(
$"[PAM保存姿态] {obj.DisplayName} 保存目标姿态: " +
$"位置=({_savedObjectPosition.X:F3},{_savedObjectPosition.Y:F3},{_savedObjectPosition.Z:F3}), " +
$"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})");
}
}
catch (Exception ex)
{
LogManager.Error($"SaveObjectState: 保存物体状态失败: {ex.Message}");
}
}
/// <summary>
/// 恢复动画物体到保存的位置和朝向
/// 此方法会更新 PathAnimationManager 的内部状态_currentYaw等
/// </summary>
public void RestoreAnimatedObjectState(ModelItem obj)
{
if (obj == null)
{
LogManager.Warning("RestoreAnimatedObjectState: 对象为空");
return;
}
if (!_hasSavedObjectState)
{
LogManager.Warning("RestoreAnimatedObjectState: 没有保存的物体状态");
return;
}
try
{
var originalAnimatedObject = _animatedObject;
_animatedObject = obj;
LogHostActualPoseAxes(obj, "[PAM恢复前宿主姿态]", false);
if (_savedObjectHasCustomRotation)
{
var linear = new Transform3D(_savedObjectRotation).Linear;
LogManager.Debug(
$"[PAM恢复姿态] {obj.DisplayName} 恢复目标姿态: " +
$"位置=({_savedObjectPosition.X:F3},{_savedObjectPosition.Y:F3},{_savedObjectPosition.Z:F3}), " +
$"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})");
}
ApplyResolvedObjectPose(
_savedObjectPosition,
_savedObjectRotation,
_savedObjectHasCustomRotation,
-1,
planarHostYawRadians: _savedObjectYaw);
LogHostActualPoseAxes(obj, "[PAM恢复后宿主姿态]", false);
_animatedObject = originalAnimatedObject;
LogManager.Debug($"已恢复物体状态: pos=({_savedObjectPosition.X:F2},{_savedObjectPosition.Y:F2},{_savedObjectPosition.Z:F2}), yaw={_savedObjectYaw * 180 / Math.PI:F2}度, customRotation={_savedObjectHasCustomRotation}");
}
catch (Exception ex)
{
LogManager.Error($"RestoreAnimatedObjectState: 恢复动画物体状态失败: {ex.Message}");
}
}
/// <summary>
/// 判断给定对象是否由当前动画管理器控制。
/// 对虚拟物体,判断当前激活的虚拟物体;对真实模型,判断当前动画对象引用。
/// </summary>
public bool ControlsAnimatedObject(ModelItem obj)
{
if (obj == null)
{
return false;
}
if (IsVirtualObjectMode)
{
return VirtualObjectManager.Instance.IsVirtualObjectActive &&
ReferenceEquals(VirtualObjectManager.Instance.CurrentVirtualObject, obj);
}
return ReferenceEquals(_animatedObject, obj);
}
/// <summary>
/// 使用当前动画链路将动画对象移动到指定姿态。
/// 适用于碰撞点定位、报告截图等场景,确保与动画播放使用同一套位置/姿态应用语义。
/// </summary>
public void MoveAnimatedObjectToPose(
ModelItem obj,
Point3D targetPosition,
double targetYawRadians,
Rotation3D targetRotation,
bool hasCustomRotation)
{
if (obj == null)
{
LogManager.Warning("MoveAnimatedObjectToPose: 对象为空");
return;
}
if (!ControlsAnimatedObject(obj))
{
LogManager.Warning($"MoveAnimatedObjectToPose: 对象不受当前动画管理器控制: {obj.DisplayName}");
return;
}
try
{
var originalAnimatedObject = _animatedObject;
if (!IsVirtualObjectMode)
{
_animatedObject = obj;
}
LogHostActualPoseAxes(obj, "[动画姿态复用前宿主姿态]", false);
ApplyResolvedObjectPose(
targetPosition,
targetRotation,
hasCustomRotation,
-1,
planarHostYawRadians: targetYawRadians);
LogHostActualPoseAxes(obj, "[动画姿态复用后宿主姿态]", false);
_animatedObject = originalAnimatedObject;
LogManager.Debug(
$"[动画姿态复用] {obj.DisplayName} 已移动到目标姿态: " +
$"位置=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3}), customRotation={hasCustomRotation}");
}
catch (Exception ex)
{
LogManager.Error($"MoveAnimatedObjectToPose: 应用目标姿态失败: {ex.Message}", ex);
throw;
}
}
private void LogHostActualPoseAxes(ModelItem obj, string prefix, bool important)
{
if (obj == null)
{
return;
}
try
{
Transform3D transform = obj.Transform;
Matrix3 linear = transform.Linear;
string message =
$"{prefix} {obj.DisplayName}: " +
$"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})";
if (important)
{
LogManager.Info(message);
}
else
{
LogManager.Debug(message);
}
}
catch (Exception ex)
{
LogManager.Warning($"{prefix} 读取失败: {ex.Message}");
}
}
/// <summary>
/// 获取播放方向1=正向,-1=反向)
/// </summary>
public int PlaybackDirection => _playbackDirection;
/// <summary>
/// 获取播放速度倍率
/// </summary>
public double PlaybackSpeed => _playbackSpeed;
/// <summary>
/// 获取是否正在反向播放
/// </summary>
public bool IsPlayingReverse => _playbackDirection == -1;
/// <summary>
/// 设置播放方向
/// </summary>
/// <param name="reverse">true=反向播放false=正向播放</param>
public void SetPlaybackDirection(bool reverse)
{
_playbackDirection = reverse ? -1 : 1;
LogManager.Info($"播放方向设置为: {(reverse ? "" : "")}");
}
/// <summary>
/// 设置播放速度
/// </summary>
/// <param name="speed">播放速度倍率,负值表示反向</param>
public void SetPlaybackSpeed(double speed)
{
_playbackSpeed = Math.Abs(speed);
_playbackDirection = speed >= 0 ? 1 : -1;
// 更新帧间隔
_frameInterval = 1000.0 / (_animationFrameRate * _playbackSpeed);
LogManager.Info($"播放速度设置为: {speed}x方向: {(speed >= 0 ? "" : "")}");
}
/// <summary>
/// 跳转到指定帧
/// </summary>
/// <param name="frameIndex">目标帧索引</param>
public void SeekToFrame(int frameIndex)
{
try
{
if (_animationFrames == null || _animationFrames.Count == 0)
{
LogManager.Warning("无动画帧数据,无法跳转");
return;
}
// 限制帧索引范围
frameIndex = Math.Max(0, Math.Min(frameIndex, _animationFrames.Count - 1));
if (frameIndex == _currentFrameIndex)
{
LogManager.Debug($"已处于目标帧 {frameIndex},无需跳转");
return;
}
_currentFrameIndex = frameIndex;
// 更新对象位置和朝向,复用动画主播放与恢复使用的同一入口。
var frameData = _animationFrames[_currentFrameIndex];
ApplyRecordedPose(frameData, _currentFrameIndex);
// 更新碰撞高亮
UpdateCollisionHighlightFromFrame();
// 计算并触发进度事件
double progress = TotalFrames > 1 ? (double)_currentFrameIndex / (TotalFrames - 1) * 100 : 0;
ProgressChanged?.Invoke(this, progress);
}
catch (Exception ex)
{
LogManager.Error($"跳转到帧失败: {ex.Message}");
}
}
/// <summary>
/// 跳转到指定进度
/// </summary>
/// <param name="progress">进度值0.0-1.0</param>
public void SeekToProgress(double progress)
{
try
{
progress = Math.Max(0.0, Math.Min(1.0, progress));
int targetFrame = TotalFrames > 1 ? (int)(progress * (TotalFrames - 1)) : 0;
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"跳转到进度失败: {ex.Message}");
}
}
/// <summary>
/// 单帧前进
/// </summary>
public void StepForward()
{
StepForward(1);
}
/// <summary>
/// 前进指定帧数
/// </summary>
/// <param name="frames">帧数</param>
public void StepForward(int frames)
{
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex + frames;
targetFrame = Math.Min(targetFrame, TotalFrames - 1);
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"前进步进失败: {ex.Message}");
}
}
/// <summary>
/// 单帧后退
/// </summary>
public void StepBackward()
{
StepBackward(1);
}
/// <summary>
/// 后退指定帧数
/// </summary>
/// <param name="frames">帧数</param>
public void StepBackward(int frames)
{
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex - frames;
targetFrame = Math.Max(targetFrame, 0);
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"后退步进失败: {ex.Message}");
}
}
/// <summary>
/// 快进10帧
/// </summary>
public void FastForward()
{
StepForward(10);
}
/// <summary>
/// 快退10帧
/// </summary>
public void FastBackward()
{
StepBackward(10);
}
/// <summary>
/// 开始正向播放
/// </summary>
public void PlayForward()
{
try
{
SetPlaybackDirection(false);
if (_currentState == AnimationState.Paused)
{
ResumeAnimation(); // 从暂停位置继续
}
else if (_currentState != AnimationState.Playing)
{
StartAnimation(); // 只有非播放和非暂停状态才从头开始
}
LogManager.Info("开始正向播放");
}
catch (Exception ex)
{
LogManager.Error($"正向播放失败: {ex.Message}");
}
}
/// <summary>
/// 开始反向播放
/// </summary>
public void PlayReverse()
{
try
{
SetPlaybackDirection(true);
if (_currentState == AnimationState.Paused)
{
ResumeAnimation(); // 从暂停位置继续
}
else if (_currentState != AnimationState.Playing)
{
StartAnimation(); // 只有非播放和非暂停状态才从头开始
}
LogManager.Info("开始反向播放");
}
catch (Exception ex)
{
LogManager.Error($"反向播放失败: {ex.Message}");
}
}
#endregion
/// <summary>
/// 清理对象引用
/// </summary>
public void ClearReferences()
{
try
{
LogManager.Info("[PathAnimationManager] 清理对象引用");
// 停止动画
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
ShutdownAnimation();
}
// 清空对象引用
_animatedObject = null;
_pathPoints?.Clear();
_trackedPosition = new Point3D(0, 0, 0);
_originalCenter = new Point3D(0, 0, 0);
LogManager.Info("[PathAnimationManager] 对象引用已清理");
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 清理对象引用失败: {ex.Message}");
}
}
/// <summary>
/// 文档失效事件处理
/// </summary>
private void OnDocumentInvalidated(object sender, EventArgs e)
{
try
{
LogManager.Info("[PathAnimationManager] 文档失效,停止动画并清理");
ClearReferences();
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 处理文档失效事件失败: {ex.Message}");
}
}
/// <summary>
/// 资源清理
/// </summary>
public void Dispose()
{
try
{
LogManager.Info("开始清理PathAnimationManager资源");
// 0. 取消配置变更事件订阅
UnsubscribeFromConfigChanges();
// 1. 优先停止动画(最小化的操作,不触发复杂的重置逻辑)
// 只有在动画运行时才停止,避免重复调用
try
{
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
ShutdownAnimation();
}
}
catch (Exception stopEx)
{
LogManager.Warning($"停止动画时出现警告: {stopEx.Message},继续清理其他资源");
}
// 2. 在Dispose中不进行重置操作避免访问已释放的对象
// 程序关闭时,对象位置重置是不必要的
LogManager.Info("跳过动画重置操作(程序关闭时不需要恢复对象位置)");
// 3. 清理 TimeLiner 资源
if (_timeLinerManager != null)
{
try
{
_timeLinerManager.Dispose();
_timeLinerManager = null;
LogManager.Info("TimeLiner 集成资源已清理");
}
catch (Exception ex)
{
LogManager.Warning($"清理 TimeLiner 资源时出现警告: {ex.Message}");
}
}
// 4. 清理DispatcherTimer资源
if (_animationTimer != null)
{
try
{
_animationTimer.Stop();
// DispatcherTimer不需要Dispose
_animationTimer = null;
LogManager.Info("DispatcherTimer资源已清理");
}
catch (Exception ex)
{
LogManager.Warning($"清理DispatcherTimer资源时出现警告: {ex.Message}");
}
}
// 5. 取消订阅文档事件
DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
LogManager.Info("PathAnimationManager资源清理完成");
}
catch (Exception ex)
{
LogManager.Error($"PathAnimationManager资源清理过程中发生异常: {ex.Message}");
// 不抛出异常,避免影响应用程序正常关闭
}
}
/// <summary>
/// 设置动画帧率
/// </summary>
public void SetAnimationFrameRate(int frameRate)
{
_animationFrameRate = Math.Max(10, Math.Min(60, frameRate)); // 限制在10-60FPS之间
_frameInterval = 1000.0 / _animationFrameRate; // 重新计算帧间隔
// 动态更新DispatcherTimer间隔
if (_animationTimer != null)
{
_animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval);
LogManager.Info($"DispatcherTimer间隔已更新: {_frameInterval:F1}ms");
}
LogManager.Info($"动画帧率设置为: {_animationFrameRate}FPS (间隔: {_frameInterval:F1}ms)");
}
/// <summary>
/// 设置碰撞检测精度(参数单位:米)
/// </summary>
public void SetCollisionDetectionAccuracy(double accuracyInMeters)
{
// 转换为模型单位存储
var accuracyInModelUnits = UnitsConverter.ConvertFromMeters(Math.Max(0.01, accuracyInMeters));
_collisionDetectionAccuracy = accuracyInModelUnits;
LogManager.Info($"碰撞检测精度设置为: {accuracyInMeters:F3}米");
}
/// <summary>
/// 设置运动速度
/// </summary>
public void SetMovementSpeed(double speed)
{
_movementSpeed = Math.Max(0.1, speed); // 最小0.1米/秒
LogManager.Info($"运动速度设置为: {_movementSpeed}m/s");
}
/// <summary>
/// 设置检测容差(参数单位:米)
/// </summary>
public void SetDetectionTolerance(double toleranceInMeters)
{
_detectionTolerance = Math.Max(0.0, toleranceInMeters);
LogManager.Info($"检测容差设置为: {_detectionTolerance:F2}米仅用于ClashDetective");
}
/// <summary>
/// 设置手工碰撞对象覆写列表
/// </summary>
public void SetManualCollisionTargets(IEnumerable<ModelItem> targets, bool enabled)
{
if (!enabled)
{
_manualCollisionOverrideEnabled = false;
_manualCollisionTargets = new List<ModelItem>();
LogManager.Info("[PathAnimationManager] 手工碰撞对象覆写已关闭");
return;
}
if (targets == null)
{
_manualCollisionOverrideEnabled = false;
_manualCollisionTargets = new List<ModelItem>();
LogManager.Warning("[PathAnimationManager] 手工碰撞对象覆写启用但目标为空,已回退到默认模式");
return;
}
var validTargets = targets
.Where(item => item != null && ModelItemAnalysisHelper.IsModelItemValid(item))
.Where(item => !ReferenceEquals(item, _animatedObject))
.Distinct()
.ToList();
if (validTargets.Count == 0)
{
_manualCollisionOverrideEnabled = false;
_manualCollisionTargets = new List<ModelItem>();
LogManager.Warning("[PathAnimationManager] 手工碰撞对象覆写启用但均无效,已回退到默认模式");
return;
}
_manualCollisionOverrideEnabled = true;
_manualCollisionTargets = validTargets;
LogManager.Info($"[PathAnimationManager] 启用手工碰撞对象覆写,共 {validTargets.Count} 个对象");
}
/// <summary>
/// 获取当前动画帧率
/// </summary>
public int AnimationFrameRate => _animationFrameRate;
/// <summary>
/// 获取所有碰撞结果(不去重)
/// </summary>
public List<CollisionResult> AllCollisionResults => _allCollisionResults;
/// <summary>
/// 获取当前物体的朝向(弧度)
/// 用于保存物体当前状态因为Transform返回的是CAD原始值
/// </summary>
public double CurrentYaw => _currentYaw;
public Rotation3D TrackedRotation => _trackedRotation;
public bool HasTrackedRotation => _hasTrackedRotation;
/// <summary>
/// 获取当前动画物体高度(模型单位)
/// </summary>
private double GetAnimatedObjectHeight()
{
if (IsVirtualObjectMode)
{
return _virtualObjectHeight;
}
if (_realObjectHeight > 0.0)
{
return _realObjectHeight;
}
if (_animatedObject == null)
{
throw new InvalidOperationException("动画对象为空,无法获取物体高度");
}
var boundingBox = _animatedObject.BoundingBox();
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
return ModelItemTransformHelper.GetHostHeight(boundingBox, adapter);
}
/// <summary>
/// 获取当前姿态下,地面/吊装路径用于“接触点 -> 几何中心”换算的有效法线尺寸(模型单位)。
/// 与通行空间使用的旋转后法线尺寸保持一致,避免物体旋转后仍按原始高度抬升中心。
/// </summary>
private double GetAnimatedObjectGroundContactHeight()
{
double rawHeight = GetAnimatedObjectHeight();
if (_objectRotationCorrection.IsZero)
{
return rawHeight;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
ModelAxisConvention axisConvention;
double forwardSize;
double sideSize;
double upSize;
if (IsVirtualObjectMode)
{
axisConvention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
forwardSize = _virtualObjectLength;
sideSize = _virtualObjectWidth;
upSize = _virtualObjectHeight;
}
else if (IsRealObjectMode && _realObjectLength > 0.0 && _realObjectWidth > 0.0 && _realObjectHeight > 0.0)
{
axisConvention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
forwardSize = _realObjectLength;
sideSize = _realObjectWidth;
upSize = _realObjectHeight;
}
else
{
return rawHeight;
}
Quaternion correctionQuaternion = IsVirtualObjectMode
? adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection)
: adapter.CreateHostRotationCorrection(_objectRotationCorrection);
var projectedExtents = RotatedObjectExtentHelper.CalculateProjectedSemanticExtents(
axisConvention,
forwardSize,
sideSize,
upSize,
correctionQuaternion);
return projectedExtents.upExtent;
}
/// <summary>
/// 获取当前碰撞检测精度
/// </summary>
public double CollisionDetectionAccuracy => _collisionDetectionAccuracy;
/// <summary>
/// 获取当前运动速度
/// </summary>
public double MovementSpeed => _movementSpeed;
/// <summary>
/// 获取当前检测间隙(单位:米)
/// </summary>
public double DetectionGap
{
get
{
return _detectionTolerance;
}
}
/// <summary>
/// 获取路径名称
/// </summary>
public string PathName => _pathName;
/// <summary>
/// 获取动画时长
/// </summary>
public double AnimationDuration => _animationDuration;
/// <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>
/// <param name="pathName">路径名称</param>
/// <param name="routeId">路由ID</param>
public void CreateAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0, string pathName = "未知路径", string routeId = null, bool isVirtualObject = false,
double virtualObjectLength = 0, double virtualObjectWidth = 0, double virtualObjectHeight = 0, double safetyMargin = 0)
{
// 🔥 清除之前的检测记录ID确保每次生成新动画都创建新的检测记录
if (_currentDetectionRecordId.HasValue)
{
LogManager.Info($"[CreateAnimation] 清除之前的检测记录ID ({_currentDetectionRecordId}),将创建新记录");
_currentDetectionRecordId = null;
}
// 🔥 清除历史记录标志,确保动画生成时状态正确
if (_isUsingHistoryRecord)
{
LogManager.Info("[CreateAnimation] 清除历史记录标志,将创建新检测");
_isUsingHistoryRecord = false;
}
_pathName = pathName;
_currentRouteId = routeId;
_animatedObjectMode = isVirtualObject ? AnimatedObjectMode.VirtualObject : AnimatedObjectMode.RealObject;
_virtualObjectLength = virtualObjectLength; // 模型单位
_virtualObjectWidth = virtualObjectWidth; // 模型单位
_virtualObjectHeight = virtualObjectHeight; // 模型单位
_safetyMargin = safetyMargin; // 模型单位
_pathPoints = pathPoints;
_animatedObject = animatedObject;
// 初始化物体位置和朝向
if (animatedObject != null)
{
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_trackedPosition = ResolveInitialBusinessTrackedPosition(animatedObject);
// 保持当前的 _currentYaw因为物体可能已经被 MoveObjectToPathStart 旋转)
// 不要从 Transform 中提取,因为 Transform 返回的是原始值,不是当前值
LogManager.Debug($"[CreateAnimation] 保持_currentYaw={_currentYaw * 180 / Math.PI:F2}度不变, 模式={_animatedObjectMode}");
}
// 设置动画参数并预计算动画帧
// 注意物体应该在调用CreateAnimation之前已经通过MoveObjectToPathStart旋转到起点
LogManager.Debug($"[CreateAnimation开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, 模式={_animatedObjectMode}");
SetupAnimation(animatedObject, durationSeconds, _route);
LogManager.Debug($"[CreateAnimation结束] _currentYaw之后={_currentYaw * 180 / Math.PI:F2}度");
// 设置动画状态为Ready动画已生成可以播放
SetState(AnimationState.Ready);
LogManager.Info($"[CreateAnimation] 动画已创建状态设置为Ready");
}
private Point3D GetLiveBoundingBoxCenter(ModelItem item)
{
if (item == null)
{
return new Point3D(0, 0, 0);
}
var bounds = item.BoundingBox();
return bounds?.Center ?? new Point3D(0, 0, 0);
}
private Point3D ResolveInitialBusinessTrackedPosition(ModelItem item)
{
if (item == null)
{
return new Point3D(0, 0, 0);
}
if (ShouldUseOriginalBoundingBoxCenterForBusinessTrackedPosition(
_route?.PathType ?? PathType.Ground,
IsRealObjectMode) &&
_originalCenter != null)
{
return _originalCenter;
}
return GetLiveBoundingBoxCenter(item);
}
private Point3D ResolveGroundTrackedCenter(Point3D groundContactPoint, double objectHeight)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalReferencePoint = ToNumerics(adapter.ToCanonicalPoint(groundContactPoint));
var canonicalCenter = CanonicalTrackedPositionResolver.ResolveGroundTrackedCenter(
canonicalReferencePoint,
HostCoordinateAdapter.CanonicalUpVector3,
objectHeight);
return adapter.FromCanonicalPoint(new Point3D(canonicalCenter.X, canonicalCenter.Y, canonicalCenter.Z));
}
private Point3D ResolveHoistingTrackedCenter(Point3D referencePoint, double objectHeight, double referenceContactFactor)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalReferencePoint = ToNumerics(adapter.ToCanonicalPoint(referencePoint));
var canonicalCenter = CanonicalTrackedPositionResolver.ResolveCenterFromContactReference(
canonicalReferencePoint,
HostCoordinateAdapter.CanonicalUpVector3,
objectHeight,
referenceContactFactor);
return adapter.FromCanonicalPoint(new Point3D(canonicalCenter.X, canonicalCenter.Y, canonicalCenter.Z));
}
private bool TryResolveRailPreservedPoseTrackedCenter(
Point3D referencePoint,
Point3D previousPoint,
Point3D nextPoint,
double objectHeight,
out Point3D trackedCenter)
{
trackedCenter = referencePoint;
if (!IsRealObjectMode ||
_route?.PathType != PathType.Rail ||
_objectStartPlacementMode != ObjectStartPlacementMode.PreserveInitialPose ||
!_hasRailPreservedPoseTrackedCenterOffset)
{
return false;
}
trackedCenter = RailPathPoseHelper.ResolvePreservedTrackedCenterPosition(
_route,
referencePoint,
previousPoint,
nextPoint,
objectHeight,
_railPreservedPoseTrackedCenterOffset);
return true;
}
private void CaptureRailPreservedPoseTrackedCenterOffset(
Point3D previousPoint,
Point3D referencePoint,
Point3D nextPoint,
double objectHeight)
{
_railPreservedPoseTrackedCenterOffset = new Vector3D(0, 0, 0);
_hasRailPreservedPoseTrackedCenterOffset = false;
if (!IsRealObjectMode ||
_route?.PathType != PathType.Rail ||
_objectStartPlacementMode != ObjectStartPlacementMode.PreserveInitialPose ||
CurrentControlledObject == null)
{
return;
}
Point3D currentTrackedCenter = GetLiveBoundingBoxCenter(CurrentControlledObject ?? _animatedObject);
Point3D semanticTrackedCenter = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
_route,
referencePoint,
previousPoint,
nextPoint,
objectHeight);
_railPreservedPoseTrackedCenterOffset = RailPathPoseHelper.CalculatePreservedTrackedCenterOffset(
_route,
currentTrackedCenter,
referencePoint,
previousPoint,
nextPoint,
objectHeight);
_hasRailPreservedPoseTrackedCenterOffset = true;
LogManager.Info(
$"[Rail终点原位残差] 当前跟踪中心=({currentTrackedCenter.X:F3},{currentTrackedCenter.Y:F3},{currentTrackedCenter.Z:F3}), " +
$"终点路径参考对应中心=({semanticTrackedCenter.X:F3},{semanticTrackedCenter.Y:F3},{semanticTrackedCenter.Z:F3}), " +
$"固定偏移=({_railPreservedPoseTrackedCenterOffset.X:F3},{_railPreservedPoseTrackedCenterOffset.Y:F3},{_railPreservedPoseTrackedCenterOffset.Z:F3})");
}
internal static Rotation3D ResolvePreservedPoseRotation(
bool preferActualGeometryRotation,
bool hasActualGeometryRotation,
Rotation3D actualGeometryRotation,
Rotation3D fallbackRotation)
{
return preferActualGeometryRotation && hasActualGeometryRotation
? actualGeometryRotation
: fallbackRotation;
}
internal static bool ShouldPreservePathRotationForFrames(
PathType pathType,
ObjectStartPlacementMode placementMode,
bool hasPreservedRotation)
{
if (!hasPreservedRotation || placementMode != ObjectStartPlacementMode.PreserveInitialPose)
{
return false;
}
return pathType == PathType.Rail || pathType == PathType.Hoisting;
}
internal static bool ShouldAllowFragmentPlanarFallback(PathType pathType)
{
return pathType != PathType.Hoisting && pathType != PathType.Ground;
}
internal static bool ShouldUseReferenceBasedRealObjectPlanarPose(PathType pathType)
{
return pathType != PathType.Ground && pathType != PathType.Hoisting;
}
internal static bool ShouldUsePlanarRealObjectPureIncrementFrames(PathType pathType, bool isRealObjectMode)
{
return isRealObjectMode &&
(pathType == PathType.Ground || pathType == PathType.Hoisting);
}
internal static void ApplyRecordedPlanarRealObjectFramePose(
AnimationFrame frame,
double planarHostYawRadians,
Rotation3D recordedRotation)
{
if (frame == null)
{
throw new ArgumentNullException(nameof(frame));
}
if (recordedRotation == null)
{
throw new ArgumentNullException(nameof(recordedRotation));
}
frame.PlanarHostYawRadians = planarHostYawRadians;
frame.Rotation = recordedRotation;
frame.HasCustomRotation = true;
}
internal static bool TryResolveDisplayedPlanarYawFromRotation(
Rotation3D rotation,
CoordinateSystemType hostType,
out double yawRadians)
{
yawRadians = 0.0;
if (rotation == null)
{
return false;
}
Matrix3 linear = new Transform3D(rotation).Linear;
Vector3 hostForward = new Vector3(
(float)linear.Get(0, 0),
(float)linear.Get(1, 0),
(float)linear.Get(2, 0));
return PathTargetFrameResolver.TryResolvePlanarHostYaw(hostForward, hostType, out yawRadians);
}
internal static double ResolvePlanarHostUpCorrectionRadians(
LocalEulerRotationCorrection correction,
CoordinateSystemType hostType)
{
double degrees = hostType == CoordinateSystemType.YUp
? correction.YDegrees
: correction.ZDegrees;
return degrees * Math.PI / 180.0;
}
internal static bool TryResolveGroundHostPlanarYawFromFramePoints(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
CoordinateSystemType hostType,
out double yawRadians)
{
yawRadians = 0.0;
Vector3 hostForward = new Vector3(
(float)(nextPoint.X - previousPoint.X),
(float)(nextPoint.Y - previousPoint.Y),
(float)(nextPoint.Z - previousPoint.Z));
if (hostForward.LengthSquared() < 1e-6f)
{
hostForward = new Vector3(
(float)(nextPoint.X - currentPoint.X),
(float)(nextPoint.Y - currentPoint.Y),
(float)(nextPoint.Z - currentPoint.Z));
}
if (hostForward.LengthSquared() < 1e-6f)
{
return false;
}
return PathTargetFrameResolver.TryResolvePlanarHostYaw(hostForward, hostType, out yawRadians);
}
internal static Rotation3D ResolveCurrentRotationBaseline(
PathType pathType,
bool isRealObjectMode,
bool hasTrackedRotation,
Rotation3D trackedRotation,
bool hasReferenceRotation,
Rotation3D referenceRotation,
Rotation3D transformRotation,
bool hasActualGeometryRotation,
Rotation3D actualGeometryRotation)
{
if (isRealObjectMode &&
pathType == PathType.Hoisting &&
hasActualGeometryRotation)
{
return actualGeometryRotation;
}
if (hasTrackedRotation)
{
return trackedRotation;
}
if (isRealObjectMode && hasReferenceRotation)
{
return referenceRotation;
}
return transformRotation;
}
internal static bool ShouldUseRealObjectOverrideRotation(bool isRealObjectMode)
{
return isRealObjectMode;
}
internal static bool ShouldUseOriginalBoundingBoxCenterForBusinessTrackedPosition(
PathType pathType,
bool isRealObjectMode)
{
return isRealObjectMode && pathType == PathType.Ground;
}
private void SyncTrackedRotationToDisplayedPose(ModelItem sourceObject)
{
if (sourceObject == null)
{
return;
}
Rotation3D actualGeometryRotation = Rotation3D.Identity;
bool hasActualGeometryRotation =
IsRealObjectMode &&
ModelItemTransformHelper.TryGetCurrentGeometryRotation(sourceObject, out actualGeometryRotation);
Rotation3D resolvedRotation = ResolvePreservedPoseRotation(
preferActualGeometryRotation: IsRealObjectMode,
hasActualGeometryRotation: hasActualGeometryRotation,
actualGeometryRotation: actualGeometryRotation,
fallbackRotation: _trackedRotation);
_trackedRotation = resolvedRotation;
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(resolvedRotation);
var linear = new Transform3D(resolvedRotation).Linear;
LogManager.Info(
$"[平移保持终点原始姿态] {sourceObject.DisplayName} 锁定姿态: " +
$"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4}), " +
$"来源={(hasActualGeometryRotation ? "姿" : "姿")}");
}
private bool TryResolvePlanarRealObjectBaseYaw(PathType pathType, out double yawRadians)
{
yawRadians = 0.0;
if (_route?.PathType != pathType ||
!IsRealObjectMode ||
_pathPoints == null ||
_pathPoints.Count < 2)
{
return false;
}
var hostType = CoordinateSystemManager.Instance.ResolvedType;
var hostPoints = new List<Vector3>(_pathPoints.Count);
for (int i = 0; i < _pathPoints.Count; i++)
{
hostPoints.Add(new Vector3((float)_pathPoints[i].X, (float)_pathPoints[i].Y, (float)_pathPoints[i].Z));
}
return PathTargetFrameResolver.TryResolvePlanarStartHostYaw(pathType, hostPoints, hostType, out yawRadians);
}
private bool TryCreateGroundRealObjectConstrainedRotation(
Point3D previousFramePoint,
Point3D currentFramePoint,
Point3D nextFramePoint,
out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (!IsRealObjectMode ||
_route?.PathType != PathType.Ground ||
!_hasGroundRealObjectBasePose)
{
return false;
}
if (!PathTargetFrameResolver.TryCreatePlanarHostFrame(
new Vector3(
(float)(nextFramePoint.X - previousFramePoint.X),
(float)(nextFramePoint.Y - previousFramePoint.Y),
(float)(nextFramePoint.Z - previousFramePoint.Z)),
CoordinateSystemManager.Instance.ResolvedType,
out var currentFrame))
{
return false;
}
if (!PathTargetFrameResolver.TryResolvePlanarHostYaw(
currentFrame.Forward,
CoordinateSystemManager.Instance.ResolvedType,
out double targetYawRadians))
{
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Vector3 hostUp = Vector3.Normalize(adapter.HostUpVector3);
double deltaYaw = NormalizeRadians(targetYawRadians - _groundRealObjectBaseYaw);
Quaternion deltaQuaternion = Quaternion.CreateFromAxisAngle(hostUp, (float)deltaYaw);
Quaternion baseQuaternion = Rotation3DToHostQuaternion(_groundRealObjectBaseRotation);
Quaternion targetQuaternion = Quaternion.Normalize(deltaQuaternion * baseQuaternion);
rotation = adapter.FromHostQuaternionDirect(targetQuaternion);
return true;
}
private bool TryCreateGroundRealObjectConstrainedRotationFromHostForward(
Vector3 hostForward,
out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (!IsRealObjectMode ||
_route?.PathType != PathType.Ground ||
!_hasGroundRealObjectBasePose)
{
return false;
}
if (!PathTargetFrameResolver.TryCreatePlanarHostFrame(
hostForward,
CoordinateSystemManager.Instance.ResolvedType,
out var currentFrame))
{
return false;
}
if (!PathTargetFrameResolver.TryResolvePlanarHostYaw(
currentFrame.Forward,
CoordinateSystemManager.Instance.ResolvedType,
out double targetYawRadians))
{
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Vector3 hostUp = Vector3.Normalize(adapter.HostUpVector3);
double deltaYaw = NormalizeRadians(targetYawRadians - _groundRealObjectBaseYaw);
Quaternion deltaQuaternion = Quaternion.CreateFromAxisAngle(hostUp, (float)deltaYaw);
Quaternion baseQuaternion = Rotation3DToHostQuaternion(_groundRealObjectBaseRotation);
Quaternion targetQuaternion = Quaternion.Normalize(deltaQuaternion * baseQuaternion);
rotation = adapter.FromHostQuaternionDirect(targetQuaternion);
return true;
}
private bool TryResolveRecordedPlanarRealObjectFrameRotation(
PathType pathType,
Point3D previousFramePoint,
Point3D currentFramePoint,
Point3D nextFramePoint,
out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (!IsRealObjectMode)
{
return false;
}
if (pathType == PathType.Ground)
{
return TryCreateGroundRealObjectConstrainedRotation(
previousFramePoint,
currentFramePoint,
nextFramePoint,
out rotation);
}
if (pathType == PathType.Hoisting)
{
Vector3 hostForward = new Vector3(
(float)(nextFramePoint.X - previousFramePoint.X),
(float)(nextFramePoint.Y - previousFramePoint.Y),
(float)(nextFramePoint.Z - previousFramePoint.Z));
if (hostForward.LengthSquared() <= 1e-6f)
{
return false;
}
return TryResolveHoistingActualPose(
hostForward,
out rotation,
out _,
out _,
out _,
out _);
}
return false;
}
private bool TryResolveHoistingActualPose(
Vector3 hostForward,
out Rotation3D rotation,
out Quaternion baselineQuaternion,
out LocalAxisDirection selectedAxisDirection,
out Vector3 selectedAxisLocal,
out Vector3 projectedForward)
{
rotation = Rotation3D.Identity;
baselineQuaternion = Quaternion.Identity;
selectedAxisDirection = LocalAxisDirection.PositiveX;
selectedAxisLocal = Vector3.UnitX;
projectedForward = Vector3.Zero;
if (!TryResolveHoistingStartBaselinePose(
hostForward,
out baselineQuaternion,
out selectedAxisDirection,
out selectedAxisLocal,
out projectedForward))
{
rotation = Rotation3D.Identity;
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Quaternion finalHostQuaternion = adapter.ComposeHostQuaternion(
baselineQuaternion,
_objectRotationCorrection);
rotation = adapter.FromHostQuaternionDirect(finalHostQuaternion);
Matrix4x4 hostBaselineLocalAxesLinear = Matrix4x4.CreateFromQuaternion(baselineQuaternion);
Matrix4x4 hostComposedLocalAxesLinear = Matrix4x4.CreateFromQuaternion(finalHostQuaternion);
LogManager.Debug(
$"[真实物体平面前进轴] Hoisting 已改用实际几何姿态基线,选中对象轴={selectedAxisDirection}。");
LogManager.Debug(
$"[真实物体起点姿态] 选中参考轴=({selectedAxisLocal.X:F3},{selectedAxisLocal.Y:F3},{selectedAxisLocal.Z:F3}), " +
$"投影前进=({projectedForward.X:F3},{projectedForward.Y:F3},{projectedForward.Z:F3}), " +
$"宿主修正={_objectRotationCorrection}, 本地修正=X=0.0°,Y=0.0°,Z=0.0°, " +
$"hostBaselineLocalXAxis=({hostBaselineLocalAxesLinear.M11:F4},{hostBaselineLocalAxesLinear.M21:F4},{hostBaselineLocalAxesLinear.M31:F4}), " +
$"hostBaselineLocalYAxis=({hostBaselineLocalAxesLinear.M12:F4},{hostBaselineLocalAxesLinear.M22:F4},{hostBaselineLocalAxesLinear.M32:F4}), " +
$"hostBaselineLocalZAxis=({hostBaselineLocalAxesLinear.M13:F4},{hostBaselineLocalAxesLinear.M23:F4},{hostBaselineLocalAxesLinear.M33:F4}), " +
$"hostComposedLocalXAxis=({hostComposedLocalAxesLinear.M11:F4},{hostComposedLocalAxesLinear.M21:F4},{hostComposedLocalAxesLinear.M31:F4}), " +
$"hostComposedLocalYAxis=({hostComposedLocalAxesLinear.M12:F4},{hostComposedLocalAxesLinear.M22:F4},{hostComposedLocalAxesLinear.M32:F4}), " +
$"hostComposedLocalZAxis=({hostComposedLocalAxesLinear.M13:F4},{hostComposedLocalAxesLinear.M23:F4},{hostComposedLocalAxesLinear.M33:F4})");
return true;
}
private bool TryResolveHoistingStartBaselinePose(
Vector3 hostForward,
out Quaternion baselineQuaternion,
out LocalAxisDirection selectedAxisDirection,
out Vector3 selectedAxisLocal,
out Vector3 projectedForward)
{
baselineQuaternion = Quaternion.Identity;
selectedAxisDirection = _hasRealObjectPlanarSelectedForwardAxis
? _realObjectPlanarSelectedForwardAxis
: LocalAxisDirection.PositiveX;
selectedAxisLocal = Vector3.UnitX;
projectedForward = Vector3.Zero;
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Vector3 hostUp = adapter.HostType == CoordinateSystemType.YUp ? Vector3.UnitY : Vector3.UnitZ;
Vector3 normalizedHostForward = hostForward.LengthSquared() > 1e-6f
? Vector3.Normalize(hostForward)
: Vector3.UnitX;
Vector3 planarProjectedForward = normalizedHostForward - Vector3.Dot(normalizedHostForward, hostUp) * hostUp;
projectedForward = planarProjectedForward.LengthSquared() > 1e-6f
? Vector3.Normalize(planarProjectedForward)
: Vector3.UnitX;
Rotation3D sourceRotation;
if (_animatedObject == null ||
!ModelItemTransformHelper.TryGetCurrentGeometryRotation(_animatedObject, out sourceRotation))
{
return false;
}
Quaternion sourceQuaternion = Rotation3DToHostQuaternion(sourceRotation);
return HoistingRealObjectPoseHelper.TryCreateRotationFromActualPose(
sourceQuaternion,
projectedForward,
hostUp,
out baselineQuaternion,
out selectedAxisDirection,
out selectedAxisLocal,
out projectedForward);
}
private static Quaternion Rotation3DToHostQuaternion(Rotation3D rotation)
{
var linear = new Transform3D(rotation).Linear;
var matrix = new Matrix4x4(
(float)linear.Get(0, 0), (float)linear.Get(0, 1), (float)linear.Get(0, 2), 0f,
(float)linear.Get(1, 0), (float)linear.Get(1, 1), (float)linear.Get(1, 2), 0f,
(float)linear.Get(2, 0), (float)linear.Get(2, 1), (float)linear.Get(2, 2), 0f,
0f, 0f, 0f, 1f);
return Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(matrix));
}
private bool TryResolveGroundRealObjectBaseYaw(out double yawRadians)
{
yawRadians = 0.0;
if (_route?.PathType != PathType.Ground ||
!IsRealObjectMode ||
_pathPoints == null ||
_pathPoints.Count < 2)
{
return false;
}
var hostType = CoordinateSystemManager.Instance.ResolvedType;
var hostPoints = new List<Vector3>(_pathPoints.Count);
for (int i = 0; i < _pathPoints.Count; i++)
{
hostPoints.Add(new Vector3((float)_pathPoints[i].X, (float)_pathPoints[i].Y, (float)_pathPoints[i].Z));
}
return PathTargetFrameResolver.TryResolvePlanarStartHostYaw(_route.PathType, hostPoints, hostType, out yawRadians);
}
private bool TryApplyPlanarRealObjectStartIncrementalTransform(
PathType pathType,
Point3D targetTrackedPosition,
out double targetYawRadians)
{
targetYawRadians = 0.0;
if (!IsRealObjectMode ||
(pathType != PathType.Ground && pathType != PathType.Hoisting) ||
_route?.PathType != pathType ||
CurrentControlledObject == null ||
_pathPoints == null ||
_pathPoints.Count < 2)
{
return false;
}
if (!PathTargetFrameResolver.TryResolvePlanarStartHostForward(
pathType,
_pathPoints,
out var hostForward))
{
LogManager.Error($"[{pathType.GetDisplayName()}纯增量起点] 无法解析路径起始方向。");
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (!PathTargetFrameResolver.TryResolvePlanarHostYaw(hostForward, adapter.HostType, out targetYawRadians))
{
// 纯垂直路径无水平方向,无法从路径计算 yaw保持物体当前朝向
LogManager.Debug($"[{pathType.GetDisplayName()}纯增量起点] 路径方向为纯垂直,保持当前朝向。");
targetYawRadians = _currentYaw;
}
// 提取非 up 轴修正角度
double angleXRadians = _objectRotationCorrection.XDegrees * Math.PI / 180.0;
double angleNonUpRadians = (adapter.HostType == CoordinateSystemType.YUp
? _objectRotationCorrection.ZDegrees
: _objectRotationCorrection.YDegrees) * Math.PI / 180.0;
var nonUpAxis = adapter.HostType == CoordinateSystemType.YUp
? Vector3.UnitZ : Vector3.UnitY;
double upAxisCorrectionRadians = ResolvePlanarHostUpCorrectionRadians(
_objectRotationCorrection,
adapter.HostType);
targetYawRadians = NormalizeRadians(targetYawRadians + upAxisCorrectionRadians);
double currentYawRadians = _currentYaw;
double deltaYawRadians = NormalizeRadians(targetYawRadians - currentYawRadians);
bool hasRotation = Math.Abs(angleXRadians) > 1e-9 ||
Math.Abs(angleNonUpRadians) > 1e-9 ||
Math.Abs(deltaYawRadians) > 1e-9;
double posDiff = Math.Abs(_trackedPosition.X - targetTrackedPosition.X) +
Math.Abs(_trackedPosition.Y - targetTrackedPosition.Y) +
Math.Abs(_trackedPosition.Z - targetTrackedPosition.Z);
bool hasTranslation = posDiff > 1e-6;
if (hasRotation || hasTranslation)
{
LogManager.Debug(
$"[{pathType.GetDisplayName()}纯增量起点] 当前Yaw={currentYawRadians * 180.0 / Math.PI:F2}°, " +
$"目标Yaw={targetYawRadians * 180.0 / Math.PI:F2}°, 增量Yaw={deltaYawRadians * 180.0 / Math.PI:F2}°, " +
$"当前跟踪点=({_trackedPosition.X:F3},{_trackedPosition.Y:F3},{_trackedPosition.Z:F3}), " +
$"目标跟踪点=({targetTrackedPosition.X:F3},{targetTrackedPosition.Y:F3},{targetTrackedPosition.Z:F3}), " +
$"旋转变化={hasRotation}, 位移变化={hasTranslation}");
ModelItemTransformHelper.MoveItemCombinedAxisRotationAndTranslation(
CurrentControlledObject,
_trackedPosition,
Vector3.UnitX, angleXRadians,
nonUpAxis, angleNonUpRadians,
adapter.HostUpVector3, deltaYawRadians,
targetTrackedPosition);
}
_trackedPosition = targetTrackedPosition;
if (ModelItemTransformHelper.TryGetCurrentGeometryRotation(CurrentControlledObject, out var appliedRotation))
{
_trackedRotation = appliedRotation;
_hasTrackedRotation = true;
}
_currentYaw = targetYawRadians;
LogHostActualPoseAxes(CurrentControlledObject, $"[{pathType.GetDisplayName()}纯增量起点应用后宿主姿态]", false);
return true;
}
private void ApplyPlanarNonUpAxisCorrectionsAtTrackedPosition(
PathType pathType,
Point3D trackedPosition,
CoordinateSystemType hostType)
{
if (CurrentControlledObject == null || _objectRotationCorrection.IsZero)
{
return;
}
var correctionSteps = new List<(string AxisName, Vector3 HostAxis, double AngleRadians)>();
correctionSteps.Add(("X", Vector3.UnitX, _objectRotationCorrection.XDegrees * Math.PI / 180.0));
if (hostType == CoordinateSystemType.YUp)
{
correctionSteps.Add(("Z", Vector3.UnitZ, _objectRotationCorrection.ZDegrees * Math.PI / 180.0));
}
else
{
correctionSteps.Add(("Y", Vector3.UnitY, _objectRotationCorrection.YDegrees * Math.PI / 180.0));
}
foreach (var step in correctionSteps)
{
if (Math.Abs(step.AngleRadians) < 1e-9)
{
continue;
}
LogManager.Info(
$"[{pathType.GetDisplayName()}纯增量起点角度修正] 宿主{step.AxisName}轴增量={step.AngleRadians * 180.0 / Math.PI:F2}°, " +
$"跟踪点=({trackedPosition.X:F3},{trackedPosition.Y:F3},{trackedPosition.Z:F3})");
ModelItemTransformHelper.MoveItemIncrementallyByAxisRotationAndTranslation(
CurrentControlledObject,
trackedPosition,
step.HostAxis,
step.AngleRadians,
trackedPosition);
}
}
private static double NormalizeRadians(double angle)
{
while (angle > Math.PI)
{
angle -= 2.0 * Math.PI;
}
while (angle < -Math.PI)
{
angle += 2.0 * Math.PI;
}
return angle;
}
private static Vector3 ToNumerics(Point3D point)
{
return new Vector3((float)point.X, (float)point.Y, (float)point.Z);
}
private bool TryGetCurrentRailModelAxisConvention(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
out ModelAxisConvention convention)
{
convention = null;
if (IsVirtualObjectMode)
{
convention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
return true;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (IsRealObjectMode && _hasRealObjectReferenceRotation)
{
if (PathTargetFrameResolver.TryCreateRailHostFrame(_route, previousPoint, currentPoint, nextPoint, out var targetFrame) &&
RealObjectRailAxisConventionResolver.TryResolve(
_realObjectReferenceAxisX,
_realObjectReferenceAxisY,
_realObjectReferenceAxisZ,
_realObjectReferenceHostUpLocalAxis,
targetFrame.Forward,
adapter.HostType,
out convention,
out var selectedForwardAxis,
out var selectedForwardWorldAxis,
out var selectedUpAxis,
out var selectedUpWorldAxis))
{
LogManager.Debug(
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={IsVirtualObjectMode}, " +
$"真实物体参考姿态生效, Forward={convention.ForwardAxis}, Up={convention.UpAxis}, " +
$"选中Forward轴={selectedForwardAxis}, 选中Forward世界轴=({selectedForwardWorldAxis.X:F4},{selectedForwardWorldAxis.Y:F4},{selectedForwardWorldAxis.Z:F4}), " +
$"选中Up轴={selectedUpAxis}, 选中Up世界轴=({selectedUpWorldAxis.X:F4},{selectedUpWorldAxis.Y:F4},{selectedUpWorldAxis.Z:F4}), " +
$"宿主Up对应本地轴={_realObjectReferenceHostUpLocalAxis}, " +
$"目标Forward=({targetFrame.Forward.X:F4},{targetFrame.Forward.Y:F4},{targetFrame.Forward.Z:F4}), " +
$"目标Up=({targetFrame.Up.X:F4},{targetFrame.Up.Y:F4},{targetFrame.Up.Z:F4})");
return true;
}
}
convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
LogManager.Debug(
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={IsVirtualObjectMode}, " +
$"Forward={convention.ForwardAxis}, Up={convention.UpAxis}, 来源=默认轴约定");
return true;
}
private static Vector3 ResolveReferenceWorldAxisForLocalDirection(
LocalAxisDirection axis,
Vector3 referenceAxisX,
Vector3 referenceAxisY,
Vector3 referenceAxisZ)
{
switch (axis)
{
case LocalAxisDirection.PositiveX: return Vector3.Normalize(referenceAxisX);
case LocalAxisDirection.NegativeX: return Vector3.Normalize(-referenceAxisX);
case LocalAxisDirection.PositiveY: return Vector3.Normalize(referenceAxisY);
case LocalAxisDirection.NegativeY: return Vector3.Normalize(-referenceAxisY);
case LocalAxisDirection.PositiveZ: return Vector3.Normalize(referenceAxisZ);
case LocalAxisDirection.NegativeZ: return Vector3.Normalize(-referenceAxisZ);
default:
throw new ArgumentOutOfRangeException(nameof(axis), axis, null);
}
}
private bool TryCalculateCurrentRealObjectRailProjectedExtents(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
out ModelAxisConvention convention,
out (double forwardExtent, double sideExtent, double upExtent) extents)
{
convention = null;
extents = (0.0, 0.0, 0.0);
if (!IsRealObjectMode ||
!_hasRealObjectReferenceRotation ||
_realObjectLength <= 0.0 ||
_realObjectWidth <= 0.0 ||
_realObjectHeight <= 0.0)
{
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (!PathTargetFrameResolver.TryCreateRailHostFrame(_route, previousPoint, currentPoint, nextPoint, out var targetFrame))
{
return false;
}
if (!TryGetCurrentRailModelAxisConvention(previousPoint, currentPoint, nextPoint, out var railConvention))
{
return false;
}
if (!RailPathPoseHelper.TryCreateRailRotation(
_route,
previousPoint,
currentPoint,
nextPoint,
railConvention,
LocalEulerRotationCorrection.Zero,
out var baselineRotation))
{
return false;
}
Quaternion baselineHostQuaternion = new Quaternion(
(float)baselineRotation.A,
(float)baselineRotation.B,
(float)baselineRotation.C,
(float)baselineRotation.D);
LocalEulerRotationCorrection localCorrection = ResolveRealObjectLocalRotationCorrection();
Quaternion finalHostQuaternion = adapter.ComposeHostQuaternion(
baselineHostQuaternion,
localCorrection);
return RealObjectRailExtentResolver.TryResolveProjectedSemanticExtents(
_realObjectReferenceAxisX,
_realObjectReferenceAxisY,
_realObjectReferenceAxisZ,
_realObjectReferenceHostUpLocalAxis,
targetFrame.Forward,
adapter.HostType,
_realObjectLength,
_realObjectWidth,
_realObjectHeight,
baselineHostQuaternion,
finalHostQuaternion,
out convention,
out extents);
}
private bool TryCalculateCurrentRealObjectPlanarProjectedExtents(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
out ModelAxisConvention convention,
out (double forwardExtent, double sideExtent, double upExtent) extents)
{
convention = null;
extents = (0.0, 0.0, 0.0);
bool requiresReferenceRotation = _route?.PathType != PathType.Hoisting;
if (!IsRealObjectMode ||
(requiresReferenceRotation && !_hasRealObjectReferenceRotation) ||
_realObjectLength <= 0.0 ||
_realObjectWidth <= 0.0 ||
_realObjectHeight <= 0.0)
{
return false;
}
Vector3 hostForward = ToNumerics(nextPoint) - ToNumerics(previousPoint);
if (hostForward.LengthSquared() < 1e-6f)
{
hostForward = ToNumerics(nextPoint) - ToNumerics(currentPoint);
}
return TryCalculateCurrentRealObjectPlanarProjectedExtents(
hostForward,
out convention,
out extents);
}
private bool TryCalculateCurrentRealObjectPlanarProjectedExtents(
Vector3 hostForward,
out ModelAxisConvention convention,
out (double forwardExtent, double sideExtent, double upExtent) extents)
{
convention = null;
extents = (0.0, 0.0, 0.0);
bool requiresReferenceRotation = ShouldUseReferenceBasedRealObjectPlanarPose(_route?.PathType ?? PathType.Ground);
if (!IsRealObjectMode ||
(requiresReferenceRotation && !_hasRealObjectReferenceRotation) ||
_realObjectLength <= 0.0 ||
_realObjectWidth <= 0.0 ||
_realObjectHeight <= 0.0)
{
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (!PathTargetFrameResolver.TryCreatePlanarHostFrame(hostForward, adapter.HostType, out var targetFrame))
{
return false;
}
if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
{
convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
extents = RotatedObjectExtentHelper.CalculateGroundSemanticExtents(
adapter.HostType,
_realObjectLength,
_realObjectWidth,
_realObjectHeight,
_objectRotationCorrection);
LogManager.Debug(
$"[{_route.PathType.GetDisplayName()}通行空间尺寸] 使用宿主语义尺寸: " +
$"Forward={extents.forwardExtent:F3}, Side={extents.sideExtent:F3}, Up={extents.upExtent:F3}");
return true;
}
if (!TryCreateRealObjectPlanarPoseSolution(
targetFrame.Forward,
targetFrame.Up,
out var solution))
{
return false;
}
LocalAxisDirection semanticUpAxis =
adapter.HostType == CoordinateSystemType.YUp
? LocalAxisDirection.PositiveY
: LocalAxisDirection.PositiveZ;
convention = new ModelAxisConvention(solution.SelectedReferenceAxisDirection, semanticUpAxis);
Quaternion finalHostQuaternion = adapter.ComposeHostQuaternion(
solution.BaselineRotation,
ResolveRealObjectLocalRotationCorrection());
extents = RealObjectProjectedExtentResolver.CalculateProjectedSemanticExtents(
convention,
_realObjectLength,
_realObjectWidth,
_realObjectHeight,
solution.BaselineRotation,
finalHostQuaternion,
targetFrame.Forward,
targetFrame.Up);
return true;
}
public bool TryGetCurrentRouteRealObjectPlanarProjectedExtents(
out double forwardExtent,
out double sideExtent,
out double upExtent)
{
forwardExtent = 0.0;
sideExtent = 0.0;
upExtent = 0.0;
if (_route == null ||
(_route.PathType != PathType.Ground && _route.PathType != PathType.Hoisting) ||
_pathPoints == null ||
_pathPoints.Count == 0)
{
return false;
}
if (!PathTargetFrameResolver.TryResolvePlanarStartHostForward(
_route.PathType,
_pathPoints,
out var hostForward))
{
return false;
}
if (!TryCalculateCurrentRealObjectPlanarProjectedExtents(
hostForward,
out _,
out var extents))
{
return false;
}
forwardExtent = extents.forwardExtent;
sideExtent = extents.sideExtent;
upExtent = extents.upExtent;
return true;
}
public bool TryGetCurrentRouteRealObjectRailProjectedExtents(
out double forwardExtent,
out double sideExtent,
out double upExtent)
{
forwardExtent = 0.0;
sideExtent = 0.0;
upExtent = 0.0;
if (_route == null || _route.PathType != PathType.Rail || _pathPoints == null || _pathPoints.Count == 0)
{
return false;
}
Point3D previousPoint = _pathPoints[0];
Point3D currentPoint = _pathPoints[0];
Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0];
if (!TryCalculateCurrentRealObjectRailProjectedExtents(
previousPoint,
currentPoint,
nextPoint,
out _,
out var extents))
{
return false;
}
forwardExtent = extents.forwardExtent;
sideExtent = extents.sideExtent;
upExtent = extents.upExtent;
return true;
}
private bool TryCreateRailPathRotation(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (!TryGetCurrentRailModelAxisConvention(previousPoint, currentPoint, nextPoint, out var railConvention))
{
return false;
}
if (IsRealObjectMode)
{
if (!RailPathPoseHelper.TryCreateRailRotation(
_route,
previousPoint,
currentPoint,
nextPoint,
railConvention,
LocalEulerRotationCorrection.Zero,
out var baselineRotation))
{
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Quaternion baselineHostQuaternion = new Quaternion(
(float)baselineRotation.A,
(float)baselineRotation.B,
(float)baselineRotation.C,
(float)baselineRotation.D);
LocalEulerRotationCorrection localCorrection = ResolveRealObjectLocalRotationCorrection();
Quaternion composedHostQuaternion = adapter.ComposeHostQuaternion(
baselineHostQuaternion,
localCorrection);
rotation = adapter.FromHostQuaternionDirect(composedHostQuaternion);
Matrix4x4 hostBaselineLocalAxesLinear = Matrix4x4.CreateFromQuaternion(baselineHostQuaternion);
Matrix4x4 hostComposedLocalAxesLinear = Matrix4x4.CreateFromQuaternion(composedHostQuaternion);
LogManager.Debug(
$"[Rail真实物体角度修正] hostBaselineLocalXAxis=({hostBaselineLocalAxesLinear.M11:F4},{hostBaselineLocalAxesLinear.M21:F4},{hostBaselineLocalAxesLinear.M31:F4}), " +
$"hostBaselineLocalYAxis=({hostBaselineLocalAxesLinear.M12:F4},{hostBaselineLocalAxesLinear.M22:F4},{hostBaselineLocalAxesLinear.M32:F4}), " +
$"hostBaselineLocalZAxis=({hostBaselineLocalAxesLinear.M13:F4},{hostBaselineLocalAxesLinear.M23:F4},{hostBaselineLocalAxesLinear.M33:F4}), " +
$"宿主修正={_objectRotationCorrection}, 本地修正={localCorrection}, " +
$"hostComposedLocalXAxis=({hostComposedLocalAxesLinear.M11:F4},{hostComposedLocalAxesLinear.M21:F4},{hostComposedLocalAxesLinear.M31:F4}), " +
$"hostComposedLocalYAxis=({hostComposedLocalAxesLinear.M12:F4},{hostComposedLocalAxesLinear.M22:F4},{hostComposedLocalAxesLinear.M32:F4}), " +
$"hostComposedLocalZAxis=({hostComposedLocalAxesLinear.M13:F4},{hostComposedLocalAxesLinear.M23:F4},{hostComposedLocalAxesLinear.M33:F4})");
return true;
}
return RailPathPoseHelper.TryCreateRailRotation(
_route,
previousPoint,
currentPoint,
nextPoint,
railConvention,
_objectRotationCorrection,
out rotation);
}
private ModelAxisConvention GetCurrentModelAxisConvention()
{
if (IsVirtualObjectMode)
{
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
return ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
}
private bool TryCreatePlanarPathRotationAtStart(out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (_pathPoints == null || _pathPoints.Count < 2)
{
return false;
}
if (PathTargetFrameResolver.TryResolvePlanarStartHostForward(
_route?.PathType ?? PathType.Ground,
_pathPoints,
out var hostForward))
{
return TryCreatePlanarPathRotationFromHostForward(
new Vector3D(hostForward.X, hostForward.Y, hostForward.Z),
out rotation);
}
return false;
}
private bool TryCreatePlanarPathRotationForFrame(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (_route?.PathType == PathType.Hoisting)
{
if (_pathPoints == null || _pathPoints.Count < 3)
{
return false;
}
return TryCreatePlanarPathRotationFromHostForward(
new Vector3D(
_pathPoints[2].X - _pathPoints[1].X,
_pathPoints[2].Y - _pathPoints[1].Y,
_pathPoints[2].Z - _pathPoints[1].Z),
out rotation);
}
return TryCreatePlanarPathRotationFromHostPoints(previousPoint, currentPoint, nextPoint, out rotation);
}
private bool TryCreatePlanarPathRotationFromHostPoints(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (IsRealObjectMode)
{
Vector3 hostForward = ToNumerics(nextPoint) - ToNumerics(previousPoint);
if (hostForward.LengthSquared() < 1e-6f)
{
hostForward = ToNumerics(nextPoint) - ToNumerics(currentPoint);
}
return TryCreateRealObjectPlanarRotationFromHostForward(hostForward, out rotation);
}
var convention = GetCurrentModelAxisConvention();
Vector3 canonicalPrevious = ToNumerics(adapter.ToCanonicalPoint(previousPoint));
Vector3 canonicalCurrent = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
Vector3 canonicalNext = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
Vector3 forward = canonicalNext - canonicalPrevious;
if (forward.LengthSquared() < 1e-6f)
{
forward = canonicalNext - canonicalCurrent;
}
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
forward,
HostCoordinateAdapter.CanonicalUpVector3,
convention,
correctionQuaternion,
out var quaternion))
{
return false;
}
rotation = adapter.FromCanonicalRotation(quaternion);
return true;
}
private double GetAnimatedObjectRailNormalExtent(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint)
{
if (TryCalculateCurrentRealObjectRailProjectedExtents(
previousPoint,
currentPoint,
nextPoint,
out var convention,
out var extents))
{
LogManager.Debug(
$"[Rail法向尺寸] 真实物体有效尺寸: Forward={extents.forwardExtent:F3}, Side={extents.sideExtent:F3}, Up={extents.upExtent:F3}, " +
$"ForwardAxis={convention.ForwardAxis}, UpAxis={convention.UpAxis}, 角度={_objectRotationCorrection}");
return extents.upExtent;
}
return GetAnimatedObjectHeight();
}
private bool TryCreatePlanarPathRotationFromHostForward(Vector3D hostForward, out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (IsRealObjectMode)
{
return TryCreateRealObjectPlanarRotationFromHostForward(
new Vector3((float)hostForward.X, (float)hostForward.Y, (float)hostForward.Z),
out rotation);
}
var convention = GetCurrentModelAxisConvention();
Vector3 canonicalForward = adapter.ToCanonicalVector3(new Vector3((float)hostForward.X, (float)hostForward.Y, (float)hostForward.Z));
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
canonicalForward,
HostCoordinateAdapter.CanonicalUpVector3,
convention,
correctionQuaternion,
out var quaternion))
{
return false;
}
rotation = adapter.FromCanonicalRotation(quaternion);
return true;
}
private bool TryCreateRealObjectPlanarRotationFromHostForward(Vector3 hostForward, out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
if (_route?.PathType == PathType.Hoisting &&
TryCreateHoistingRealObjectRotationFromActualPose(hostForward, out rotation))
{
return true;
}
if (_route?.PathType == PathType.Hoisting)
{
LogManager.Error("[吊装真实物体姿态] 无法基于实际几何姿态生成起点姿态,已禁止回退到 fragment 参考姿态。");
return false;
}
if (!PathTargetFrameResolver.TryCreatePlanarHostFrame(
hostForward,
CoordinateSystemManager.Instance.CreateHostAdapter().HostType,
out var targetFrame))
{
return false;
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
if (_route?.PathType == PathType.Ground)
{
Rotation3D actualRotation = Rotation3D.Identity;
bool hasActualRotation =
_animatedObject != null &&
ModelItemTransformHelper.TryGetCurrentGeometryRotation(_animatedObject, out actualRotation);
if (!hasActualRotation && _hasTrackedRotation)
{
actualRotation = _trackedRotation;
hasActualRotation = true;
}
if (!hasActualRotation)
{
LogManager.Error("[真实物体起点姿态] Ground 无法读取当前实际几何姿态,禁止回退到 reference-based pose。");
return false;
}
if (!TryResolveDisplayedPlanarYawFromRotation(actualRotation, adapter.HostType, out double currentYawRadians))
{
LogManager.Error("[真实物体起点姿态] Ground 无法从当前显示姿态解析宿主平面角。");
return false;
}
if (!PathTargetFrameResolver.TryResolvePlanarHostYaw(hostForward, adapter.HostType, out double targetYawRadians))
{
LogManager.Error("[真实物体起点姿态] Ground 无法从路径方向解析宿主平面角。");
return false;
}
Quaternion targetQuaternion = HoistingRealObjectPoseHelper.CreateRotationFromPlanarBasePose(
Rotation3DToHostQuaternion(actualRotation),
currentYawRadians,
targetYawRadians,
adapter.HostUpVector3);
rotation = adapter.FromHostQuaternionDirect(targetQuaternion);
Matrix4x4 targetLinear = Matrix4x4.CreateFromQuaternion(targetQuaternion);
LogManager.Debug(
$"[Ground宿主平面旋转] 当前Yaw={currentYawRadians * 180.0 / Math.PI:F2}°, 目标Yaw={targetYawRadians * 180.0 / Math.PI:F2}°, " +
$"hostXAxis=({targetLinear.M11:F4},{targetLinear.M21:F4},{targetLinear.M31:F4}), " +
$"hostYAxis=({targetLinear.M12:F4},{targetLinear.M22:F4},{targetLinear.M32:F4}), " +
$"hostZAxis=({targetLinear.M13:F4},{targetLinear.M23:F4},{targetLinear.M33:F4})");
return true;
}
if (!TryCreateRealObjectPlanarPoseSolution(
targetFrame.Forward,
targetFrame.Up,
out var solution))
{
return false;
}
LocalEulerRotationCorrection localCorrection = ResolveRealObjectLocalRotationCorrection();
Quaternion hostComposedQuaternion = adapter.ComposeHostQuaternion(solution.BaselineRotation, localCorrection);
rotation = adapter.FromHostQuaternionDirect(hostComposedQuaternion);
Matrix4x4 hostBaselineLocalAxesLinear = Matrix4x4.CreateFromQuaternion(solution.BaselineRotation);
Matrix4x4 hostComposedLocalAxesLinear = Matrix4x4.CreateFromQuaternion(hostComposedQuaternion);
LogManager.Debug(
$"[真实物体起点姿态] 选中参考轴=({solution.SelectedReferenceAxisLocal.X:F3},{solution.SelectedReferenceAxisLocal.Y:F3},{solution.SelectedReferenceAxisLocal.Z:F3}), " +
$"投影前进=({solution.ProjectedForward.X:F3},{solution.ProjectedForward.Y:F3},{solution.ProjectedForward.Z:F3}), " +
$"宿主修正={_objectRotationCorrection}, 本地修正={localCorrection}, " +
$"hostBaselineLocalXAxis=({hostBaselineLocalAxesLinear.M11:F4},{hostBaselineLocalAxesLinear.M21:F4},{hostBaselineLocalAxesLinear.M31:F4}), " +
$"hostBaselineLocalYAxis=({hostBaselineLocalAxesLinear.M12:F4},{hostBaselineLocalAxesLinear.M22:F4},{hostBaselineLocalAxesLinear.M32:F4}), " +
$"hostBaselineLocalZAxis=({hostBaselineLocalAxesLinear.M13:F4},{hostBaselineLocalAxesLinear.M23:F4},{hostBaselineLocalAxesLinear.M33:F4}), " +
$"hostComposedLocalXAxis=({hostComposedLocalAxesLinear.M11:F4},{hostComposedLocalAxesLinear.M21:F4},{hostComposedLocalAxesLinear.M31:F4}), " +
$"hostComposedLocalYAxis=({hostComposedLocalAxesLinear.M12:F4},{hostComposedLocalAxesLinear.M22:F4},{hostComposedLocalAxesLinear.M32:F4}), " +
$"hostComposedLocalZAxis=({hostComposedLocalAxesLinear.M13:F4},{hostComposedLocalAxesLinear.M23:F4},{hostComposedLocalAxesLinear.M33:F4})");
return true;
}
private bool TryCreateHoistingRealObjectRotationFromActualPose(Vector3 hostForward, out Rotation3D rotation)
{
if (!TryResolveHoistingActualPose(
hostForward,
out rotation,
out _,
out var selectedAxisDirection,
out var selectedAxisLocal,
out var projectedForward))
{
return false;
}
_realObjectPlanarSelectedForwardAxis = selectedAxisDirection;
_hasRealObjectPlanarSelectedForwardAxis = true;
return true;
}
private bool TryCreateRealObjectPlanarPoseSolution(
Vector3 targetForward,
Vector3 targetUp,
out RealObjectPlanarPoseSolution solution)
{
solution = null;
if (!ShouldUseReferenceBasedRealObjectPlanarPose(_route?.PathType ?? PathType.Ground))
{
return false;
}
if (!TryGetRealObjectReferenceRotation(out var referenceRotation))
{
return false;
}
LocalAxisDirection? fixedForwardAxis = null;
if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
{
// Ground/Hoisting 的真实物体前进轴采用固定对象语义:
// 真实物体默认以本地 +X 表示前进方向,不能因为路径更偏向 Z 就切换成 +Z。
fixedForwardAxis = LocalAxisDirection.PositiveX;
}
if (!RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
referenceRotation,
targetForward,
targetUp,
fixedForwardAxis,
out solution))
{
return false;
}
if ((_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting) &&
(!_hasRealObjectPlanarSelectedForwardAxis || _realObjectPlanarSelectedForwardAxis != LocalAxisDirection.PositiveX))
{
_realObjectPlanarSelectedForwardAxis = LocalAxisDirection.PositiveX;
_hasRealObjectPlanarSelectedForwardAxis = true;
LogManager.Debug("[真实物体平面前进轴] Ground/Hoisting 已固定使用 PositiveX 作为对象前进轴语义。");
}
return true;
}
private void ShowRealObjectFragmentUpMismatchHintIfNeeded(Vector3 representativeYAxis, Vector3 representativeZAxis)
{
if (_route == null || (_route.PathType != PathType.Ground && _route.PathType != PathType.Hoisting))
{
return;
}
if (!IsRealObjectMode)
{
return;
}
var doc = NavisApplication.ActiveDocument;
string hostUpAxis = FragmentDefaultUpContext.GetCurrentHostUpAxis(doc);
string configuredFragmentUpAxis = FragmentDefaultUpContext.GetCurrentDocumentFragmentDefaultUpAxis(doc);
Vector3 hostUpVector = string.Equals(hostUpAxis, "Y", StringComparison.OrdinalIgnoreCase)
? Vector3.UnitY
: Vector3.UnitZ;
float yAlignment = Math.Abs(Vector3.Dot(Vector3.Normalize(representativeYAxis), hostUpVector));
float zAlignment = Math.Abs(Vector3.Dot(Vector3.Normalize(representativeZAxis), hostUpVector));
string detectedFragmentUpAxis = zAlignment > yAlignment ? "Z" : "Y";
if (string.Equals(configuredFragmentUpAxis, detectedFragmentUpAxis, StringComparison.OrdinalIgnoreCase))
{
return;
}
string documentKey = FragmentDefaultUpContext.GetCurrentDocumentKey(doc);
if (string.IsNullOrWhiteSpace(documentKey))
{
return;
}
string hintKey = $"{documentKey}|{configuredFragmentUpAxis}|{detectedFragmentUpAxis}";
lock (_realObjectFragmentUpMismatchHintsShown)
{
if (_realObjectFragmentUpMismatchHintsShown.Contains(hintKey))
{
return;
}
_realObjectFragmentUpMismatchHintsShown.Add(hintKey);
}
string message =
$"请到系统管理的坐标系设置中,将 Fragment默认Up 调整为 {detectedFragmentUpAxis}。";
LogManager.Info($"[fragment默认Up提示] 当前值={configuredFragmentUpAxis}, 检测值={detectedFragmentUpAxis}, 模型Up={hostUpAxis}。{message}");
DialogHelper.ShowMessageBox(
message,
"Fragment默认Up提示",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
private bool TryGetRealObjectReferenceRotation(out Quaternion referenceRotation)
{
referenceRotation = Quaternion.Identity;
if (_hasRealObjectReferenceRotation)
{
referenceRotation = _realObjectReferenceRotation;
return true;
}
if (_originalTransform == null)
{
return false;
}
if (!ShouldUseReferenceBasedRealObjectPlanarPose(_route?.PathType ?? PathType.Ground))
{
return false;
}
if (!TryCaptureRealObjectReferenceRotation(_animatedObject, out referenceRotation))
{
string objectName = _animatedObject?.DisplayName ?? "未知对象";
LogManager.Error($"[真实物体参考姿态] {objectName} 无法获取 fragment 参考姿态,已禁止回退默认姿态。");
return false;
}
return true;
}
private bool TryCaptureRealObjectReferenceRotation(ModelItem sourceObject, out Quaternion referenceRotation)
{
referenceRotation = Quaternion.Identity;
if (sourceObject == null)
{
return false;
}
try
{
var doc = NavisApplication.ActiveDocument;
if (!RealObjectReferencePoseResolver.TryResolveFromModelItem(sourceObject, doc, out var referencePose))
{
LogManager.Warning($"[真实物体参考姿态] {sourceObject.DisplayName} fragment 姿态解析失败");
return false;
}
referenceRotation = referencePose.Rotation;
_realObjectReferenceRotation = referenceRotation;
_realObjectReferenceAxisX = referencePose.RawAxisX;
_realObjectReferenceAxisY = referencePose.RawAxisY;
_realObjectReferenceAxisZ = referencePose.RawAxisZ;
_realObjectReferenceHostWorldXAxisLocalAxis = referencePose.HostWorldXAxisLocalAxis;
_realObjectReferenceHostWorldYAxisLocalAxis = referencePose.HostWorldYAxisLocalAxis;
_realObjectReferenceHostWorldZAxisLocalAxis = referencePose.HostWorldZAxisLocalAxis;
_realObjectReferenceHostSemanticXAxisLocalAxis = referencePose.HostSemanticXAxisLocalAxis;
_realObjectReferenceHostSemanticYAxisLocalAxis = referencePose.HostSemanticYAxisLocalAxis;
_realObjectReferenceHostSemanticZAxisLocalAxis = referencePose.HostSemanticZAxisLocalAxis;
_realObjectReferenceHostUpLocalAxis = referencePose.HostUpLocalAxis;
_hasRealObjectReferenceRotation = true;
LogManager.Info(
$"[真实物体参考姿态] {sourceObject.DisplayName} 使用 fragment 代表姿态: " +
$"RawX=({_realObjectReferenceAxisX.X:F4},{_realObjectReferenceAxisX.Y:F4},{_realObjectReferenceAxisX.Z:F4}), " +
$"RawY=({_realObjectReferenceAxisY.X:F4},{_realObjectReferenceAxisY.Y:F4},{_realObjectReferenceAxisY.Z:F4}), " +
$"RawZ=({_realObjectReferenceAxisZ.X:F4},{_realObjectReferenceAxisZ.Y:F4},{_realObjectReferenceAxisZ.Z:F4}), " +
$"宿主世界X对应本地轴={referencePose.HostWorldXAxisLocalAxis}, 宿主世界Y对应本地轴={referencePose.HostWorldYAxisLocalAxis}, 宿主世界Z对应本地轴={referencePose.HostWorldZAxisLocalAxis}, " +
$"宿主语义X=({referencePose.AxisX.X:F4},{referencePose.AxisX.Y:F4},{referencePose.AxisX.Z:F4}), " +
$"宿主语义Y=({referencePose.AxisY.X:F4},{referencePose.AxisY.Y:F4},{referencePose.AxisY.Z:F4}), " +
$"宿主语义Z=({referencePose.AxisZ.X:F4},{referencePose.AxisZ.Y:F4},{referencePose.AxisZ.Z:F4}), " +
$"宿主语义X对应本地轴={referencePose.HostSemanticXAxisLocalAxis}, 宿主语义Y对应本地轴={referencePose.HostSemanticYAxisLocalAxis}, 宿主语义Z对应本地轴={referencePose.HostSemanticZAxisLocalAxis}, " +
$"宿主Up对应本地轴={referencePose.HostUpLocalAxis}, fragment数量={referencePose.FragmentCount}, Fragment默认Up={referencePose.FragmentDefaultUpAxis}, 模型Up={referencePose.HostUpAxis}");
return true;
}
catch (Exception ex)
{
LogManager.Warning($"[真实物体参考姿态] 从 fragment 提取代表姿态失败: {ex.Message}");
return false;
}
}
private void SyncTrackedRotationToObjectReference(ModelItem sourceObject, bool isVirtualObject)
{
if (sourceObject == null)
{
_trackedRotation = Rotation3D.Identity;
_hasTrackedRotation = false;
_currentYaw = 0.0;
return;
}
if (isVirtualObject)
{
_trackedRotation = CreateVirtualObjectReferenceRotation();
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(_trackedRotation);
var linear = new Transform3D(_trackedRotation).Linear;
LogManager.Info(
$"[虚拟物体参考姿态] {sourceObject.DisplayName} 使用资产约定基姿态: " +
$"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})");
return;
}
if (!isVirtualObject &&
!ShouldUseReferenceBasedRealObjectPlanarPose(_route?.PathType ?? PathType.Ground) &&
ModelItemTransformHelper.TryGetCurrentGeometryRotation(sourceObject, out var groundActualGeometryRotation))
{
_trackedRotation = groundActualGeometryRotation;
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(_trackedRotation);
var linear = new Transform3D(_trackedRotation).Linear;
LogManager.Debug(
$"[真实物体当前姿态] {sourceObject.DisplayName} Ground 路径已改用实际几何姿态同步,不再读取 fragment 参考姿态: " +
$"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})");
return;
}
if (!isVirtualObject && TryCaptureRealObjectReferenceRotation(sourceObject, out Quaternion referenceRotation))
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
_trackedRotation = adapter.FromHostQuaternionDirect(referenceRotation);
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(_trackedRotation);
return;
}
if (!ShouldUseReferenceBasedRealObjectPlanarPose(_route?.PathType ?? PathType.Ground))
{
// Ground/Hoisting 路径:无需参考姿态,使用单位旋转
_trackedRotation = Rotation3D.Identity;
_hasTrackedRotation = true;
_currentYaw = 0.0;
return;
}
throw new InvalidOperationException(
$"[真实物体参考姿态] {sourceObject.DisplayName} 无法同步参考姿态未能读取当前实际几何姿态fragment 代表姿态也不可用,已禁止回退 Transform。");
}
private static Rotation3D CreateVirtualObjectReferenceRotation()
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var convention = GetVirtualObjectAssetConvention();
return convention.CreateRotation(new Vector3D(1, 0, 0), adapter.HostUpVector);
}
private static ModelAxisConvention GetVirtualObjectAssetConvention()
{
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
}
private void ResetRealObjectReferenceRotation()
{
_realObjectReferenceRotation = Quaternion.Identity;
_realObjectReferenceAxisX = Vector3.UnitX;
_realObjectReferenceAxisY = Vector3.UnitY;
_realObjectReferenceAxisZ = Vector3.UnitZ;
_realObjectReferenceHostWorldXAxisLocalAxis = LocalAxisDirection.PositiveX;
_realObjectReferenceHostWorldYAxisLocalAxis = LocalAxisDirection.PositiveY;
_realObjectReferenceHostWorldZAxisLocalAxis = LocalAxisDirection.PositiveZ;
_realObjectReferenceHostSemanticXAxisLocalAxis = LocalAxisDirection.PositiveX;
_realObjectReferenceHostSemanticYAxisLocalAxis = LocalAxisDirection.PositiveY;
_realObjectReferenceHostSemanticZAxisLocalAxis = LocalAxisDirection.PositiveZ;
_realObjectReferenceHostUpLocalAxis = LocalAxisDirection.PositiveY;
_hasRealObjectReferenceRotation = false;
_realObjectPlanarSelectedForwardAxis = LocalAxisDirection.PositiveX;
_hasRealObjectPlanarSelectedForwardAxis = false;
}
private void ResetPlanarRealObjectBasePoseCache()
{
_groundRealObjectBaseRotation = Rotation3D.Identity;
_groundRealObjectBaseYaw = 0.0;
_hasGroundRealObjectBasePose = false;
}
private LocalEulerRotationCorrection ResolveRealObjectLocalRotationCorrection()
{
return HostCoordinateAdapter.RemapHostSemanticCorrectionToLocalAxes(
_objectRotationCorrection,
_realObjectReferenceHostWorldXAxisLocalAxis,
_realObjectReferenceHostWorldYAxisLocalAxis,
_realObjectReferenceHostWorldZAxisLocalAxis);
}
/// <summary>
/// 设置物体绕宿主 X/Y/Z 轴的角度修正
/// </summary>
public void SetObjectRotationCorrection(LocalEulerRotationCorrection rotationCorrection)
{
_objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose;
_hasPathPreservedPoseRotation = false;
_objectRotationCorrection = rotationCorrection;
// 如果动画已创建,更新物体到起点的朝向
if (_animatedObject != null && _pathPoints != null && _pathPoints.Count > 0)
{
try
{
// 重新计算并应用朝向
MoveObjectToPathStart();
LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection}");
}
catch (Exception ex)
{
LogManager.Error($"[角度修正] 更新物体角度失败: {ex.Message}");
}
}
}
public void ApplyObjectStartPlacementRequest(ObjectStartPlacementRequest request, bool skipCadRestore = false)
{
_objectStartPlacementMode = request.PlacementMode;
if (_objectStartPlacementMode != ObjectStartPlacementMode.PreserveInitialPose)
{
_hasPathPreservedPoseRotation = false;
}
_objectRotationCorrection = request.RotationCorrection;
_groundPathObjectLiftHeight = UnitsConverter.ConvertFromMeters(request.VerticalLiftInMeters);
LogManager.Debug(
$"[起点摆放] 已应用物体调整请求: 模式={_objectStartPlacementMode}, 角度={_objectRotationCorrection}, " +
$"上下偏移={request.VerticalLiftInMeters:F3}m");
if (_animatedObject != null && _pathPoints != null && _pathPoints.Count > 0)
{
try
{
MoveObjectToPathStartUsingCurrentPlacementMode(skipCadRestore: skipCadRestore);
}
catch (Exception ex)
{
LogManager.Error($"[起点摆放] 应用物体调整请求失败: {ex.Message}");
}
}
}
/// <summary>
/// 单轴兼容入口:按当前宿主 up 轴映射成三轴角度修正。
/// </summary>
public void SetObjectRotationCorrection(double rotationCorrection)
{
SetObjectRotationCorrection(CreateSingleAxisUpCorrection(rotationCorrection));
}
/// <summary>
/// 直接设置物体绕宿主 X/Y/Z 轴的角度修正(不触发物体旋转)
/// </summary>
public void SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection rotationCorrection)
{
_objectRotationCorrection = rotationCorrection;
ResetPlanarRealObjectBasePoseCache();
LogManager.Debug($"[角度修正] 直接设置角度修正值: {_objectRotationCorrection}(不触发旋转)");
}
public void SetObjectStartVerticalLiftDirect(double verticalLiftInMeters)
{
_groundPathObjectLiftHeight = UnitsConverter.ConvertFromMeters(verticalLiftInMeters);
LogManager.Debug($"[起点摆放] 直接设置地面路径上下偏移: {verticalLiftInMeters:F3}m");
}
public void SetObjectStartPlacementMode(ObjectStartPlacementMode placementMode)
{
_objectStartPlacementMode = placementMode;
if (placementMode != ObjectStartPlacementMode.PreserveInitialPose)
{
_hasPathPreservedPoseRotation = false;
}
LogManager.Debug($"[起点摆放] 当前模式已设置为: {_objectStartPlacementMode}");
}
private bool MoveObjectToPathStartUsingCurrentPlacementMode(ModelItem animatedObject = null, List<Point3D> pathPoints = null, bool skipCadRestore = false)
{
return _objectStartPlacementMode == ObjectStartPlacementMode.PreserveInitialPose
? MoveObjectToPathStartPreservingInitialPose(animatedObject, pathPoints)
: MoveObjectToPathStart(animatedObject, pathPoints, skipCadRestore);
}
/// <summary>
/// 单轴兼容入口:按当前宿主 up 轴映射成三轴角度修正。
/// </summary>
public void SetObjectRotationCorrectionDirect(double rotationCorrection)
{
SetObjectRotationCorrectionDirect(CreateSingleAxisUpCorrection(rotationCorrection));
}
private LocalEulerRotationCorrection CreateSingleAxisUpCorrection(double rotationCorrection)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
return adapter.HostType == CoordinateSystemType.YUp
? new LocalEulerRotationCorrection(0.0, rotationCorrection, 0.0)
: new LocalEulerRotationCorrection(0.0, 0.0, rotationCorrection);
}
internal static Vector3 ResolveGroundPathObjectLiftOffsetVector(
double verticalLiftModelUnits,
CoordinateSystemType hostType)
{
if (Math.Abs(verticalLiftModelUnits) < 1e-9)
{
return Vector3.Zero;
}
var adapter = new HostCoordinateAdapter(hostType);
return Vector3.Normalize(adapter.HostUpVector3) * (float)verticalLiftModelUnits;
}
private Point3D ApplyGroundPathObjectLiftOffset(Point3D trackedCenter)
{
if (_route == null || _route.PathType != PathType.Ground || Math.Abs(_groundPathObjectLiftHeight) < 1e-9)
{
return trackedCenter;
}
Vector3 offset = ResolveGroundPathObjectLiftOffsetVector(
_groundPathObjectLiftHeight,
CoordinateSystemManager.Instance.ResolvedType);
return new Point3D(
trackedCenter.X + offset.X,
trackedCenter.Y + offset.Y,
trackedCenter.Z + offset.Z);
}
#region
/// <summary>
/// DispatcherTimer动画处理核心
/// </summary>
/// <summary>
/// 根据当前帧索引更新碰撞高亮(状态跟踪优化版本)
/// </summary>
private void UpdateCollisionHighlightFromFrame()
{
try
{
// 确保帧索引有效
if (_currentFrameIndex < 0 || _currentFrameIndex >= _animationFrames.Count)
{
return;
}
// 获取当前帧数据
var currentFrame = _animationFrames[_currentFrameIndex];
bool currentHasCollision = currentFrame.HasCollision;
// 收集当前帧碰撞对象的集合
var currentCollisionObjects = new HashSet<ModelItem>();
if (currentHasCollision && currentFrame.Collisions != null)
{
foreach (var collision in currentFrame.Collisions)
{
if (collision.Item1 != null)
currentCollisionObjects.Add(collision.Item1);
if (collision.Item2 != null)
currentCollisionObjects.Add(collision.Item2);
}
}
// 检查是否需要更新高亮:
// 1. 碰撞状态改变(有碰撞 vs 无碰撞)
// 2. 碰撞对象集合改变(碰撞的物体变了)
bool stateChanged = currentHasCollision != _lastHighlightState;
bool objectsChanged = !currentCollisionObjects.SetEquals(_lastCollisionObjects);
if (stateChanged || objectsChanged)
{
if (currentHasCollision)
{
// 有碰撞高亮当前帧的碰撞对象使用CollisionResultsCategory的默认颜色
ModelHighlightHelper.ManageCollisionHighlightsByCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory, currentFrame.Collisions, clearOtherCategories: true);
LogManager.Debug($"[高亮状态] 帧{_currentFrameIndex}: 高亮碰撞 ({currentFrame.Collisions.Count}个, 对象{currentCollisionObjects.Count}个)");
}
else
{
// 无碰撞:清除碰撞高亮(保留物体高亮)
ModelHighlightHelper.ClearCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory);
LogManager.Debug($"[高亮状态] 帧{_currentFrameIndex}: 清除碰撞高亮");
}
// 更新状态记录
_lastHighlightState = currentHasCollision;
_lastCollisionObjects = currentCollisionObjects;
}
}
catch (Exception ex)
{
LogManager.Error($"更新碰撞高亮失败: {ex.Message}");
}
}
/// <summary>
/// 统一的动画帧处理逻辑DispatcherTimer专用
/// </summary>
private void ProcessAnimationFrame()
{
// 检查动画状态
if (_currentState != AnimationState.Playing)
{
return; // 非播放状态,跳过更新
}
// DispatcherTimer已经控制了调用间隔无需手动帧率控制
var now = DateTime.Now;
// 计算下一帧索引(考虑播放方向和速度)
int nextFrameIndex = _currentFrameIndex;
if (_playbackDirection == 1) // 正向播放
{
nextFrameIndex = _currentFrameIndex + 1;
if (nextFrameIndex >= _animationFrames.Count)
{
// 正向播放到达终点
nextFrameIndex = _animationFrames.Count - 1;
StopAnimationPlayback();
FinishAnimation();
return;
}
}
else // 反向播放
{
nextFrameIndex = _currentFrameIndex - 1;
if (nextFrameIndex < 0)
{
// 反向播放到达起点
nextFrameIndex = 0;
StopAnimationPlayback();
FinishAnimation();
return;
}
}
// 更新帧索引和位置
if (nextFrameIndex != _currentFrameIndex)
{
_currentFrameIndex = nextFrameIndex;
// 使用预计算的帧位置和朝向
if (_currentFrameIndex < _animationFrames.Count)
{
var frameData = _animationFrames[_currentFrameIndex];
ApplyRecordedPose(frameData, _currentFrameIndex);
// 更新碰撞高亮(基于预计算结果)
UpdateCollisionHighlightFromFrame();
// 计算当前进度(基于帧索引)
double progress = _animationFrames.Count > 1 ? (double)_currentFrameIndex / (_animationFrames.Count - 1) : 0;
// 触发进度事件
var progressPercent = progress * 100;
ProgressChanged?.Invoke(this, progressPercent);
// 同步进度到 TimeLiner如果可用
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
}
}
// 记录帧时间和统计
_lastFrameTime = now;
// 性能监控和自动切换
UpdateFPSCounterAndCheckSwitch();
}
private void ApplyRecordedPose(AnimationFrame frameData, int frameIndex)
{
if (frameData == null)
{
return;
}
ApplyResolvedObjectPose(
frameData.Position,
frameData.Rotation,
frameData.HasCustomRotation,
frameIndex,
frameData.PlanarHostYawRadians.HasValue
? (double?)ResolveAnimationFramePlaybackYawRadians(frameData)
: null);
}
private void ApplyResolvedObjectPose(
Point3D targetPosition,
Rotation3D targetRotation,
bool hasCustomRotation,
int frameIndex,
double? planarHostYawRadians = null)
{
if (ShouldUsePlanarRealObjectPureIncrementFrames(_route?.PathType ?? PathType.Ground, IsRealObjectMode) &&
planarHostYawRadians.HasValue)
{
ApplyPlanarTrackedPose(targetPosition, planarHostYawRadians.Value);
return;
}
if (hasCustomRotation)
{
ApplyFullPoseUpdate(targetPosition, targetRotation);
return;
}
string pathTypeName = (_route?.PathType ?? PathType.Ground).GetDisplayName();
throw new InvalidOperationException(
$"{pathTypeName}记录姿态 {frameIndex} 缺少完整姿态,禁止退回旧 yaw 链路。");
}
private double ResolveAnimationFramePlaybackYawRadians(AnimationFrame frameData)
{
return ResolveAnimationFramePlaybackYawRadians(
frameData,
_objectRotationCorrection,
CoordinateSystemManager.Instance.ResolvedType);
}
internal static double ResolveAnimationFramePlaybackYawRadians(
AnimationFrame frameData,
LocalEulerRotationCorrection rotationCorrection,
CoordinateSystemType hostType)
{
if (frameData == null)
{
return 0.0;
}
if (frameData.PlanarHostYawRadians.HasValue)
{
return NormalizeRadians(
frameData.PlanarHostYawRadians.Value +
ResolvePlanarHostUpCorrectionRadians(
rotationCorrection,
hostType));
}
double resolvedYaw = frameData.YawRadians;
if (!frameData.HasCustomRotation && !rotationCorrection.IsZero)
{
resolvedYaw += rotationCorrection.ZDegrees * Math.PI / 180.0;
}
return NormalizeRadians(resolvedYaw);
}
/// <summary>
/// 应用平面路径的已记录姿态。使用业务跟踪点 _trackedPosition 作为当前位置,不读取实时包围盒中心回写主链。
/// </summary>
private void ApplyPlanarTrackedPose(Point3D targetPosition, double targetYawRadians)
{
var controlledObject = CurrentControlledObject;
if (controlledObject == null) return;
Point3D currentPosition = _trackedPosition;
double currentYawRadians = _currentYaw;
double deltaYawRadians = NormalizeRadians(targetYawRadians - currentYawRadians);
Vector3 hostUp = Vector3.Normalize(CoordinateSystemManager.Instance.CreateHostAdapter().HostUpVector3);
string pathTypeName = (_route?.PathType ?? PathType.Ground).GetDisplayName();
LogManager.Debug(
$"[{pathTypeName}纯增量逐帧] 当前Yaw={currentYawRadians * 180.0 / Math.PI:F2}°, " +
$"目标Yaw={targetYawRadians * 180.0 / Math.PI:F2}°, 增量Yaw={deltaYawRadians * 180.0 / Math.PI:F2}°, " +
$"当前跟踪点=({currentPosition.X:F3},{currentPosition.Y:F3},{currentPosition.Z:F3}), " +
$"目标跟踪点=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3})");
ModelItemTransformHelper.MoveItemIncrementallyByAxisRotationAndTranslation(
controlledObject,
currentPosition,
hostUp,
deltaYawRadians,
targetPosition);
_trackedPosition = targetPosition;
if (ModelItemTransformHelper.TryGetCurrentGeometryRotation(controlledObject, out Rotation3D appliedRotation))
{
_trackedRotation = appliedRotation;
_hasTrackedRotation = true;
}
_currentYaw = targetYawRadians;
}
/// <summary>
/// 更新FPS计数器DispatcherTimer模式
/// </summary>
private void UpdateFPSCounterAndCheckSwitch()
{
_fpsFrameCount++;
var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds;
// 每秒更新一次FPS统计
if (elapsed >= 1.0)
{
_actualFPS = _fpsFrameCount / elapsed;
LogManager.Debug($"[性能监控] 实际FPS: {_actualFPS:F1} (目标: {_animationFrameRate}) - DispatcherTimer模式");
// 重置计数器
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
}
}
#region
/// <summary>
/// 计算动画配置的哈希值(基于路径点和对象)
/// </summary>
private string ComputeAnimationConfigHash(ModelItem animatedObject, List<Point3D> pathPoints)
{
try
{
var sb = new StringBuilder();
// 包含对象名称
sb.Append(animatedObject?.DisplayName ?? "null");
sb.Append("|");
// 包含路径点信息
if (pathPoints != null)
{
foreach (var point in pathPoints)
{
sb.Append($"{point.X:F2},{point.Y:F2},{point.Z:F2};");
}
}
// 计算哈希
using (var md5 = System.Security.Cryptography.MD5.Create())
{
var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
var hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
}
catch (Exception ex)
{
LogManager.Warning($"计算动画配置哈希失败: {ex.Message}");
return DateTime.Now.Ticks.ToString(); // 回退到时间戳
}
}
/// <summary>
/// 计算动画配置的哈希值基于PathRoute的Edges可以区分不同的曲线化路径
/// </summary>
public string ComputeAnimationConfigHashFromRoute(ModelItem animatedObject, PathRoute route)
{
try
{
var sb = new StringBuilder();
// 包含对象的唯一标识使用PathId而不是DisplayName
if (animatedObject != null)
{
try
{
var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(animatedObject);
sb.Append($"{pathId.ModelIndex}:{pathId.PathId}");
}
catch
{
sb.Append(animatedObject.DisplayName ?? "null");
}
}
else
{
sb.Append("null");
}
sb.Append("|");
// 包含路径ID区分不同的路径
sb.Append(route?.Id ?? "null");
sb.Append("|");
// 包含动画参数(确保参数改变时重新检测)
sb.Append($"Duration:{_animationDuration:F2}s");
sb.Append($"|FrameRate:{_animationFrameRate}");
sb.Append($"|DetectionTolerance:{_detectionTolerance:F4}");
sb.Append("|");
// 包含虚拟物体尺寸(确保物体尺寸改变时重新检测)
// 使用传入的物体尺寸参数而不是从ConfigManager读取
var objectSizeString = $"Object:{_virtualObjectLength:F2}x{_virtualObjectWidth:F2}x{_virtualObjectHeight:F2}";
sb.Append(objectSizeString);
sb.Append("|");
// 包含角度修正(确保角度修正改变时重新检测)
sb.Append($"RotationCorrection:{_objectRotationCorrection}");
sb.Append("|");
// 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测)
// 使用PathId作为唯一标识而不是DisplayName
if (_manualCollisionOverrideEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0)
{
sb.Append("ManualTargets:");
foreach (var target in _manualCollisionTargets.OrderBy(t => t.DisplayName))
{
try
{
var targetPathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target);
sb.Append($"{targetPathId.ModelIndex}:{targetPathId.PathId}");
sb.Append(",");
}
catch (Exception)
{
// 忽略无效对象
}
}
sb.Append("|");
}
// 包含路径边的摘要信息(区分不同的曲线化结果)
if (route?.Edges != null && route.Edges.Count > 0)
{
foreach (var edge in route.Edges)
{
sb.Append(edge.SegmentType.ToString());
sb.Append($"({edge.PhysicalLength:F2})");
if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
{
sb.Append($"_R{edge.Trajectory.ActualRadius:F2}");
sb.Append($"_A{edge.Trajectory.DeflectionAngle:F2}");
}
sb.Append(";");
}
}
// 包含路径最后修改时间(确保路径修改后重新生成动画)
sb.Append($"LastModified:{route?.LastModified:yyyyMMddHHmmss}");
sb.Append("|");
// 🔥 包含排除列表(确保排除列表变化时重新检测)
if (_excludedObjects != null && _excludedObjects.Count > 0)
{
sb.Append("Excluded:");
foreach (var excluded in _excludedObjects.OrderBy(e => e.DisplayName))
{
try
{
var excludedPathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(excluded);
sb.Append($"{excludedPathId.ModelIndex}:{excludedPathId.PathId}");
sb.Append(",");
}
catch (Exception)
{
// 忽略无效对象
}
}
sb.Append("|");
}
// 计算哈希
using (var md5 = System.Security.Cryptography.MD5.Create())
{
var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
var hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
}
catch (Exception ex)
{
LogManager.Warning($"计算动画配置哈希失败: {ex.Message}");
return DateTime.Now.Ticks.ToString(); // 回退到时间戳
}
}
/// <summary>
/// 清除所有配置哈希映射
/// </summary>
public static void ClearAllCollisionTestRecords()
{
_animationHashToRecordId.Clear();
LogManager.Info("已清除所有配置哈希映射");
}
/// <summary>
/// 根据动画配置哈希获取检测记录ID
/// </summary>
public int? GetDetectionRecordIdByHash(string animationHash)
{
if (string.IsNullOrEmpty(animationHash))
return null;
if (_animationHashToRecordId.TryGetValue(animationHash, out var recordId))
{
return recordId;
}
return null;
}
/// <summary>
/// 注册动画配置哈希与检测记录ID的映射
/// </summary>
public void RegisterAnimationHashToRecordId(string animationHash, int recordId)
{
if (!string.IsNullOrEmpty(animationHash))
{
_animationHashToRecordId[animationHash] = recordId;
LogManager.Info($"[AnimationHashMapping] 注册映射: Hash={animationHash.Substring(0, 8)}... -> RecordId={recordId}");
}
}
/// <summary>
/// 移除动画配置哈希映射
/// </summary>
public void RemoveDetectionRecordHash(string animationHash)
{
if (!string.IsNullOrEmpty(animationHash) && _animationHashToRecordId.ContainsKey(animationHash))
{
_animationHashToRecordId.Remove(animationHash);
LogManager.Info($"[AnimationHashMapping] 移除映射: Hash={animationHash.Substring(0, 8)}...");
}
}
#endregion
#region LogisticsAnimationManager迁移
/// <summary>
/// 为动画对象预计算并缓存排除列表
/// </summary>
/// <param name="animationObject">动画对象</param>
/// <returns>是否成功缓存</returns>
public bool PrecomputeCollisionExclusions(ModelItem animationObject)
{
try
{
LogManager.Info($"[缓存管理] 开始为动画对象预计算排除列表: {animationObject?.DisplayName ?? "NULL"}");
if (animationObject == null)
{
LogManager.Warning("[缓存管理] 动画对象为空,清除缓存");
ClearExclusionCache();
return false;
}
// 检查是否需要重新计算
if (IsCacheValid(animationObject))
{
LogManager.Info($"[缓存管理] 缓存仍然有效,跳过重新计算");
return true;
}
var startTime = DateTime.Now;
// 只构建特定于动画对象的逻辑排除列表
LogManager.Info("[缓存管理] 构建逻辑排除列表...");
var exclusionList = ModelItemAnalysisHelper.CollectRelatedNodes(animationObject);
// 更新缓存
_currentCachedAnimationObject = animationObject;
_cachedExclusionList = exclusionList;
_cacheCreatedTime = DateTime.Now;
var elapsedMs = (DateTime.Now - startTime).TotalMilliseconds;
LogManager.Info($"[缓存管理] 预计算完成,耗时 {elapsedMs:F1}ms缓存 {exclusionList.Count} 个排除对象");
return true;
}
catch (Exception ex)
{
LogManager.Error($"[缓存管理] 预计算排除列表失败: {ex.Message}", ex);
ClearExclusionCache();
return false;
}
}
/// <summary>
/// 获取缓存的排除列表
/// </summary>
/// <param name="animationObject">动画对象</param>
/// <returns>排除列表如果缓存无效则返回null</returns>
public List<ModelItem> GetCachedExclusionList(ModelItem animationObject)
{
try
{
if (!IsCacheValid(animationObject))
{
LogManager.Debug($"[缓存管理] 缓存无效,建议重新预计算");
return null;
}
LogManager.Debug($"[缓存管理] 返回缓存的排除列表: {_cachedExclusionList?.Count ?? 0} 个对象");
return _cachedExclusionList;
}
catch (Exception ex)
{
LogManager.Warning($"[缓存管理] 获取缓存失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 检查缓存是否有效
/// </summary>
/// <param name="animationObject">当前动画对象</param>
/// <returns>缓存是否有效</returns>
private bool IsCacheValid(ModelItem animationObject)
{
try
{
// 检查是否有缓存
if (_currentCachedAnimationObject == null || _cachedExclusionList == null)
{
return false;
}
// 检查对象是否相同
if (!_currentCachedAnimationObject.Equals(animationObject))
{
string cachedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
string currentObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animationObject);
LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'");
return false;
}
// 检查缓存时间是否过期
if (DateTime.Now - _cacheCreatedTime > _cacheValidDuration)
{
LogManager.Debug($"[缓存验证] 缓存已过期: {(DateTime.Now - _cacheCreatedTime).TotalMinutes:F1} 分钟");
return false;
}
return true;
}
catch (Exception ex)
{
LogManager.Warning($"[缓存验证] 验证过程异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 清除排除列表缓存
/// </summary>
public void ClearExclusionCache()
{
try
{
if (_currentCachedAnimationObject != null)
{
try
{
// 安全访问DisplayName防止WeakRef已释放的警告
string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
LogManager.Debug($"[缓存管理] 清除缓存: {objectName}");
}
catch (Exception ex)
{
LogManager.Debug($"[缓存管理] 清除缓存时访问对象名称失败: {ex.Message}");
}
}
_currentCachedAnimationObject = null;
_cachedExclusionList = null;
_cacheCreatedTime = DateTime.MinValue;
}
catch (Exception ex)
{
LogManager.Warning($"[缓存管理] 清除缓存失败: {ex.Message}");
}
}
/// <summary>
/// 获取缓存统计信息
/// </summary>
/// <returns>缓存统计信息</returns>
public string GetCacheStats()
{
try
{
if (_currentCachedAnimationObject == null)
{
return "缓存状态: 无缓存";
}
var age = DateTime.Now - _cacheCreatedTime;
string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject);
var count = _cachedExclusionList?.Count ?? 0;
return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒";
}
catch (Exception ex)
{
return $"缓存状态: 获取失败 - {ex.Message}";
}
}
#endregion
#region Human-in-the-Loop
/// <summary>
/// 添加排除物体
/// </summary>
/// <param name="obj">要排除的物体</param>
public void AddExcludedObject(ModelItem obj)
{
if (obj != null)
{
_excludedObjects.Add(obj);
LogManager.Debug($"[排除列表] 添加排除物体: {ModelItemAnalysisHelper.GetSafeDisplayName(obj)}, 当前共 {_excludedObjects.Count} 个");
}
}
/// <summary>
/// 批量添加排除物体
/// </summary>
/// <param name="objects">要排除的物体列表</param>
public void AddExcludedObjects(IEnumerable<ModelItem> objects)
{
if (objects != null)
{
foreach (var obj in objects)
{
if (obj != null) _excludedObjects.Add(obj);
}
LogManager.Info($"[排除列表] 批量添加 {objects.Count()} 个排除物体, 当前共 {_excludedObjects.Count} 个");
}
}
/// <summary>
/// 移除排除物体
/// </summary>
/// <param name="obj">要移除的物体</param>
public void RemoveExcludedObject(ModelItem obj)
{
if (obj != null && _excludedObjects.Remove(obj))
{
LogManager.Debug($"[排除列表] 移除排除物体: {ModelItemAnalysisHelper.GetSafeDisplayName(obj)}, 当前共 {_excludedObjects.Count} 个");
}
}
/// <summary>
/// 清除所有排除物体
/// </summary>
public void ClearExcludedObjects()
{
var count = _excludedObjects.Count;
_excludedObjects.Clear();
LogManager.Info($"[排除列表] 清除所有 {count} 个排除物体");
}
/// <summary>
/// 获取当前排除物体列表
/// </summary>
/// <returns>排除物体列表</returns>
public IReadOnlyCollection<ModelItem> GetExcludedObjects()
{
return _excludedObjects.ToList().AsReadOnly();
}
/// <summary>
/// 检查物体是否在排除列表中
/// </summary>
/// <param name="obj">要检查的物体</param>
/// <returns>是否被排除</returns>
public bool IsObjectExcluded(ModelItem obj)
{
return obj != null && _excludedObjects.Contains(obj);
}
/// <summary>
/// 获取排除物体数量
/// </summary>
public int ExcludedObjectCount => _excludedObjects.Count;
/// <summary>
/// 【已废弃】排除列表现在是实时管理的,不绑定到动画缓存
/// </summary>
[Obsolete("排除列表现在是实时管理的,此方法不再执行任何操作", false)]
public void SaveExclusionsToCache()
{
// 排除列表是实时管理的,不保存到动画缓存
LogManager.Debug($"[排除列表] 实时管理模式,当前共 {_excludedObjects.Count} 个排除物体");
}
/// <summary>
/// 【已废弃】排除列表现在是实时管理的,不绑定到动画缓存
/// </summary>
[Obsolete("排除列表现在是实时管理的此方法始终返回false", false)]
public bool LoadExclusionsFromCache()
{
// 排除列表是实时管理的,不从缓存加载
LogManager.Debug("[排除列表] 实时管理模式,不从缓存加载");
return false;
}
/// <summary>
/// 【已废弃】排除列表现在是实时管理的,不绑定到动画缓存
/// </summary>
[Obsolete("排除列表现在是实时管理的,此方法不再执行任何操作", false)]
public static void ClearAllExclusionCaches()
{
// 排除列表是实时管理的,没有缓存需要清除
LogManager.Debug("[排除列表] 实时管理模式,无缓存需要清除");
}
/// <summary>
/// 设置排除物体并清除当前动画缓存(用于重新生成)
/// </summary>
/// <param name="objects">新的排除物体列表</param>
public void SetExcludedObjectsAndClearCache(IEnumerable<ModelItem> objects)
{
// 1. 更新排除列表
_excludedObjects.Clear();
if (objects != null)
{
foreach (var obj in objects)
{
if (obj != null) _excludedObjects.Add(obj);
}
}
LogManager.Info($"[排除列表] 已更新排除列表({_excludedObjects.Count}个),下次生成动画时将重新计算碰撞结果");
// 🔥 清除当前检测记录ID确保下次生成时创建新记录
if (_currentDetectionRecordId.HasValue)
{
LogManager.Info($"[排除列表] 清除当前检测记录ID ({_currentDetectionRecordId}),下次生成将创建新记录");
_currentDetectionRecordId = null;
}
}
/// <summary>
/// 从数据库加载排除对象
/// </summary>
public void LoadExcludedObjectsFromDatabase(PathDatabase database, string routeId = null)
{
try
{
if (database == null)
{
LogManager.Warning("[排除列表] 数据库为空,无法加载排除对象");
return;
}
// 从数据库获取排除对象记录
var records = database.GetExcludedObjects(routeId, includeGlobal: true);
if (records == null || records.Count == 0)
{
LogManager.Info($"[排除列表] 数据库中没有排除对象记录");
return;
}
// 将记录转换为 ModelItem
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
LogManager.Warning("[排除列表] 没有活动文档,无法加载排除对象");
return;
}
var loadedObjects = new List<ModelItem>();
int notFoundCount = 0;
foreach (var record in records)
{
try
{
// 尝试通过 PathId 查找对象
var modelItem = FindModelItemByPathId(doc, record.PathId);
if (modelItem != null)
{
loadedObjects.Add(modelItem);
}
else
{
notFoundCount++;
LogManager.Debug($"[排除列表] 未找到排除对象: PathId={record.PathId}, Name={record.DisplayName}");
}
}
catch (Exception ex)
{
LogManager.Error($"[排除列表] 加载排除对象失败: {ex.Message}");
}
}
// 添加到排除列表
foreach (var obj in loadedObjects)
{
_excludedObjects.Add(obj);
}
LogManager.Info($"[排除列表] 从数据库加载 {loadedObjects.Count} 个排除对象,{notFoundCount} 个未找到");
}
catch (Exception ex)
{
LogManager.Error($"[排除列表] 从数据库加载排除对象失败: {ex.Message}", ex);
}
}
/// <summary>
/// 保存排除对象到数据库
/// </summary>
public void SaveExcludedObjectsToDatabase(PathDatabase database, string routeId = null)
{
try
{
if (database == null)
{
LogManager.Warning("[排除列表] 数据库为空,无法保存排除对象");
return;
}
// 清除该路径的旧排除对象记录
database.ClearExcludedObjects(routeId);
// 保存当前排除对象
var records = new List<ExcludedObjectRecord>();
foreach (var obj in _excludedObjects)
{
try
{
var record = new ExcludedObjectRecord
{
RouteId = routeId,
ModelIndex = GetModelIndex(obj),
PathId = GetModelItemPathId(obj),
DisplayName = obj.DisplayName,
ObjectName = GetModelItemObjectName(obj),
ExcludedTime = DateTime.Now,
Reason = "用户排除",
IsGlobal = string.IsNullOrEmpty(routeId)
};
records.Add(record);
}
catch (Exception ex)
{
LogManager.Error($"[排除列表] 转换排除对象记录失败: {ex.Message}");
}
}
// 批量保存到数据库
database.SaveExcludedObjects(records);
LogManager.Info($"[排除列表] 已保存 {records.Count} 个排除对象到数据库");
}
catch (Exception ex)
{
LogManager.Error($"[排除列表] 保存排除对象到数据库失败: {ex.Message}", ex);
}
}
/// <summary>
/// 根据 PathId 查找 ModelItem
/// </summary>
private ModelItem FindModelItemByPathId(Document doc, string pathId)
{
try
{
// 解析 PathId (格式: "模型索引:路径")
var parts = pathId.Split(':');
if (parts.Length < 2) return null;
if (!int.TryParse(parts[0], out int modelIndex)) return null;
// 获取模型
var model = doc.Models[modelIndex];
if (model == null) return null;
// 解析路径索引
var pathIndices = parts[1].Split(',')
.Select(p => int.TryParse(p, out int idx) ? idx : -1)
.Where(idx => idx >= 0)
.ToArray();
if (pathIndices.Length == 0) return null;
// 从根节点开始遍历
ModelItem current = model.RootItem;
foreach (var index in pathIndices)
{
if (current == null) return null;
// 将 Children 转换为列表以便索引访问
var childrenList = current.Children.ToList();
if (index >= childrenList.Count) return null;
current = childrenList[index];
}
return current;
}
catch (Exception ex)
{
LogManager.Error($"[排除列表] 查找 ModelItem 失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 获取 ModelItem 的 PathId
/// </summary>
private string GetModelItemPathId(ModelItem item)
{
try
{
if (item == null) return string.Empty;
// 构建路径索引数组
var indices = new List<int>();
var current = item;
while (current != null && current.Parent != null)
{
var parent = current.Parent;
// 在父节点的子节点中查找当前节点的索引
var childrenList = parent.Children.ToList();
int index = childrenList.FindIndex(c => c == current);
if (index < 0) break;
indices.Insert(0, index);
current = parent;
}
// 获取模型索引
int modelIndex = GetModelIndex(item);
return $"{modelIndex}:{string.Join(",", indices)}";
}
catch (Exception ex)
{
LogManager.Error($"[排除列表] 获取 PathId 失败: {ex.Message}");
return string.Empty;
}
}
/// <summary>
/// 获取 ModelItem 所在的模型索引
/// </summary>
private int GetModelIndex(ModelItem item)
{
try
{
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (doc == null) return -1;
// 找到根节点
var root = item;
while (root.Parent != null) root = root.Parent;
// 在模型列表中查找
for (int i = 0; i < doc.Models.Count; i++)
{
if (doc.Models[i].RootItem == root)
return i;
}
return -1;
}
catch
{
return -1;
}
}
/// <summary>
/// 获取 ModelItem 的对象名称
/// </summary>
private string GetModelItemObjectName(ModelItem item)
{
try
{
return item.ClassName ?? item.DisplayName ?? "Unknown";
}
catch
{
return "Unknown";
}
}
#endregion
#endregion
}
}