fix: apply face-to-ground rotation directly via quaternion, skip Euler decomposition

- 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
This commit is contained in:
tian 2026-06-09 03:32:26 +08:00
parent 10a0ad754c
commit c8d48755db

View File

@ -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(