From c8d48755db51ccf38117c0be694d4e62cdbcd3f1 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Tue, 9 Jun 2026 03:32:26 +0800 Subject: [PATCH] fix: apply face-to-ground rotation directly via quaternion, skip Euler decomposition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use existing OverridePermanentTransform pattern from PathAnimationManager - Compute quaternion from face normal → ground direction - Apply directly as Rotation3D with center-preserving translation - Sync tracked state after application --- .../ViewModels/AnimationControlViewModel.cs | 78 ++++++++----------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index 5459efa..70365e3 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -2280,73 +2280,61 @@ namespace NavisworksTransport.UI.WPF.ViewModels return; } + if (_pathAnimationManager?.AnimatedObject == null) + { + UpdateMainStatus("贴地面失败: 未选择动画物体。"); + return; + } + var doc = NavisApplication.ActiveDocument; var worldUp = doc.CurrentViewpoint.Value.WorldUpVector; - var hostUp = new System.Numerics.Vector3((float)worldUp.X, (float)worldUp.Y, (float)worldUp.Z); - hostUp = System.Numerics.Vector3.Normalize(hostUp); + var hostUp = new Vector3((float)worldUp.X, (float)worldUp.Y, (float)worldUp.Z); + hostUp = Vector3.Normalize(hostUp); var groundDir = -hostUp; - var faceNormal = new System.Numerics.Vector3((float)result.Normal.X, (float)result.Normal.Y, (float)result.Normal.Z); - faceNormal = System.Numerics.Vector3.Normalize(faceNormal); + var faceNormal = new Vector3((float)result.Normal.X, (float)result.Normal.Y, (float)result.Normal.Z); + faceNormal = Vector3.Normalize(faceNormal); - float dot = System.Numerics.Vector3.Dot(faceNormal, groundDir); - System.Numerics.Quaternion rotation; + float dot = Vector3.Dot(faceNormal, groundDir); if (dot > 0.9999f) { DialogHelper.ShowStatusAndDialog("选中面已朝向地面,无需旋转。", "贴地面", "选中面已朝向地面,无需旋转。", UpdateMainStatus, System.Windows.MessageBoxImage.Information); return; } + + Quaternion rotation; if (dot < -0.9999f) { var perp = Math.Abs(hostUp.X) < 0.9f - ? System.Numerics.Vector3.Normalize(System.Numerics.Vector3.Cross(hostUp, new System.Numerics.Vector3(1, 0, 0))) - : System.Numerics.Vector3.Normalize(System.Numerics.Vector3.Cross(hostUp, new System.Numerics.Vector3(0, 0, 1))); - rotation = System.Numerics.Quaternion.CreateFromAxisAngle(perp, (float)Math.PI); + ? Vector3.Normalize(Vector3.Cross(hostUp, Vector3.UnitX)) + : Vector3.Normalize(Vector3.Cross(hostUp, Vector3.UnitZ)); + rotation = Quaternion.CreateFromAxisAngle(perp, (float)Math.PI); } else { - var axis = System.Numerics.Vector3.Normalize(System.Numerics.Vector3.Cross(faceNormal, groundDir)); + var axis = Vector3.Normalize(Vector3.Cross(faceNormal, groundDir)); float angle = (float)Math.Acos(dot); - rotation = System.Numerics.Quaternion.CreateFromAxisAngle(axis, angle); + rotation = Quaternion.CreateFromAxisAngle(axis, angle); } - // 分解为 ZYX Euler 角度 - double xDeg, yDeg, zDeg; - double sinY = -2.0 * (rotation.X * rotation.Z - rotation.W * rotation.Y); - sinY = Math.Max(-1.0, Math.Min(1.0, sinY)); - yDeg = Math.Asin(sinY) * (180.0 / Math.PI); - if (Math.Abs(Math.Abs(sinY) - 1.0) < 1e-9) - { - zDeg = 0.0; - xDeg = Math.Atan2(2.0 * (rotation.X * rotation.Y + rotation.W * rotation.Z), - rotation.W * rotation.W - rotation.Y * rotation.Y - rotation.Z * rotation.Z + rotation.X * rotation.X) * (180.0 / Math.PI); - } - else - { - xDeg = Math.Atan2(2.0 * (rotation.Y * rotation.Z + rotation.W * rotation.X), - rotation.W * rotation.W - rotation.X * rotation.X - rotation.Y * rotation.Y + rotation.Z * rotation.Z) * (180.0 / Math.PI); - zDeg = Math.Atan2(2.0 * (rotation.X * rotation.Y + rotation.W * rotation.Z), - rotation.W * rotation.W - rotation.X * rotation.X + rotation.Y * rotation.Y - rotation.Z * rotation.Z) * (180.0 / Math.PI); - } + // 直接应用四元数旋转 + var modelItem = _pathAnimationManager.AnimatedObject; + var modelItems = new ModelItemCollection { modelItem }; + doc.Models.ResetPermanentTransform(modelItems); + var center = modelItem.BoundingBox().Center; + var tp = new Vector3((float)center.X, (float)center.Y, (float)center.Z); + var rotatedTp = Vector3.Transform(tp, rotation); - var correction = new LocalEulerRotationCorrection( - NormalizeDegrees(xDeg), NormalizeDegrees(yDeg), NormalizeDegrees(zDeg)); + var identity = Autodesk.Navisworks.Api.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); - var placementRequest = ObjectStartPlacementRequest.CreateRotationCorrection( - correction, _objectGroundLiftHeightInMeters); - _pathAnimationManager?.ApplyObjectStartPlacementRequest(placementRequest); - ObjectRotationCorrection = correction; + _pathAnimationManager.SyncTrackedStateToCurrentDisplayPose(); UpdatePassageSpaceVisualization(); - LogManager.Info($"[贴地面] faceNormal=({faceNormal.X:F3},{faceNormal.Y:F3},{faceNormal.Z:F3}), Euler=({correction.XDegrees:F2},{correction.YDegrees:F2},{correction.ZDegrees:F2})"); - UpdateMainStatus($"贴地面完成: {correction}"); - } - - private static double NormalizeDegrees(double degrees) - { - double normalized = degrees % 360.0; - if (normalized < -180.0) normalized += 360.0; - if (normalized > 180.0) normalized -= 360.0; - return Math.Abs(normalized) < 1e-9 ? 0.0 : normalized; + LogManager.Info($"[贴地面] faceNormal=({faceNormal.X:F3},{faceNormal.Y:F3},{faceNormal.Z:F3}) -> ground, rotation=({rotation.X:F4},{rotation.Y:F4},{rotation.Z:F4},{rotation.W:F4})"); + UpdateMainStatus("贴地面完成。"); } private ObjectPassageProjectionOptimizationResult OptimizeObjectPassageProjectionByMeasuredAabb(