From 1af3cf87c4d4bc8bd5a0ad6ded62dd589e946ffb Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 1 Apr 2026 01:25:16 +0800 Subject: [PATCH] Fix world rotation extraction with scaled parents --- engine/src/Core/Math/Matrix.cpp | 28 +++++++++++++- tests/Components/test_transform_component.cpp | 31 +++++++++++++++- .../test_scene_viewport_rotate_gizmo.cpp | 37 +++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/engine/src/Core/Math/Matrix.cpp b/engine/src/Core/Math/Matrix.cpp index b10d7b6c..f09aa5cd 100644 --- a/engine/src/Core/Math/Matrix.cpp +++ b/engine/src/Core/Math/Matrix.cpp @@ -308,7 +308,33 @@ Vector3 Matrix4::GetTranslation() const { } Quaternion Matrix4::GetRotation() const { - return Quaternion::FromRotationMatrix(*this); + Matrix4 rotationMatrix = *this; + const Vector3 scale = GetScale(); + + if (scale.x > EPSILON) { + rotationMatrix.m[0][0] /= scale.x; + rotationMatrix.m[1][0] /= scale.x; + rotationMatrix.m[2][0] /= scale.x; + } + if (scale.y > EPSILON) { + rotationMatrix.m[0][1] /= scale.y; + rotationMatrix.m[1][1] /= scale.y; + rotationMatrix.m[2][1] /= scale.y; + } + if (scale.z > EPSILON) { + rotationMatrix.m[0][2] /= scale.z; + rotationMatrix.m[1][2] /= scale.z; + rotationMatrix.m[2][2] /= scale.z; + } + + rotationMatrix.m[0][3] = 0.0f; + rotationMatrix.m[1][3] = 0.0f; + rotationMatrix.m[2][3] = 0.0f; + rotationMatrix.m[3][0] = 0.0f; + rotationMatrix.m[3][1] = 0.0f; + rotationMatrix.m[3][2] = 0.0f; + rotationMatrix.m[3][3] = 1.0f; + return Quaternion::FromRotationMatrix(rotationMatrix); } Vector3 Matrix4::GetScale() const { diff --git a/tests/Components/test_transform_component.cpp b/tests/Components/test_transform_component.cpp index b3801613..e81c09a9 100644 --- a/tests/Components/test_transform_component.cpp +++ b/tests/Components/test_transform_component.cpp @@ -96,6 +96,35 @@ TEST(TransformComponent_Test, WorldRotation_WithParent) { EXPECT_TRUE(worldRot.Magnitude() > 0.0f); } +TEST(TransformComponent_Test, WorldRotation_WithScaledParentPreservesRotation) { + TransformComponent parent; + TransformComponent child; + + parent.SetLocalScale(Vector3(0.38912f, 0.38912f, 0.38912f)); + parent.SetLocalRotation(Quaternion::FromAxisAngle(Vector3::Up(), PI * 0.25f)); + child.SetParent(&parent); + child.SetLocalRotation(Quaternion::FromAxisAngle(Vector3::Right(), PI * 0.125f)); + + const Quaternion expectedWorldRotation = parent.GetLocalRotation() * child.GetLocalRotation(); + const Quaternion actualWorldRotation = child.GetRotation(); + + EXPECT_GT(std::abs(actualWorldRotation.Dot(expectedWorldRotation)), 0.999f); +} + +TEST(TransformComponent_Test, SetWorldRotation_WithScaledParentPreservesTargetRotation) { + TransformComponent parent; + TransformComponent child; + + parent.SetLocalScale(Vector3(0.38912f, 0.38912f, 0.38912f)); + child.SetParent(&parent); + + const Quaternion targetWorldRotation = Quaternion::FromAxisAngle(Vector3::Forward(), PI * 0.33f); + child.SetRotation(targetWorldRotation); + + const Quaternion actualWorldRotation = child.GetRotation(); + EXPECT_GT(std::abs(actualWorldRotation.Dot(targetWorldRotation)), 0.999f); +} + TEST(TransformComponent_Test, WorldScale_WithParent) { TransformComponent parent; TransformComponent child; @@ -301,4 +330,4 @@ TEST(TransformComponent_Test, SetAsLastSibling) { EXPECT_EQ(child1.GetSiblingIndex(), 1); } -} // namespace \ No newline at end of file +} // namespace diff --git a/tests/editor/test_scene_viewport_rotate_gizmo.cpp b/tests/editor/test_scene_viewport_rotate_gizmo.cpp index 99aff85f..0481f7fb 100644 --- a/tests/editor/test_scene_viewport_rotate_gizmo.cpp +++ b/tests/editor/test_scene_viewport_rotate_gizmo.cpp @@ -253,6 +253,43 @@ TEST_F(SceneViewportRotateGizmoTest, DraggingEdgeOnXAxisFallsBackToScreenSpaceRo EXPECT_TRUE(m_context.GetUndoManager().CanUndo()); } +TEST_F(SceneViewportRotateGizmoTest, DraggingXAxisOnChildWithScaledParentRotatesObject) { + Components::GameObject* parent = GetSceneManager().CreateEntity("Parent"); + ASSERT_NE(parent, nullptr); + parent->GetTransform()->SetLocalScale(Math::Vector3(0.38912f, 0.38912f, 0.38912f)); + parent->GetTransform()->SetLocalRotation(Math::Quaternion::FromAxisAngle(Math::Vector3::Up(), Math::PI * 0.25f)); + + Components::GameObject* target = GetSceneManager().CreateEntity("Target", parent); + ASSERT_NE(target, nullptr); + target->GetTransform()->SetLocalPosition(Math::Vector3(1.0f, 0.0f, 0.0f)); + + SceneViewportRotateGizmo gizmo; + const SceneViewportOverlayData overlay = MakeIsometricOverlay(); + gizmo.Update(MakeContext(target, Math::Vector2(400.0f, 300.0f), overlay)); + + const SceneViewportRotateGizmoHandleDrawData* xHandle = + FindHandle(gizmo.GetDrawData(), SceneViewportRotateGizmoAxis::X); + ASSERT_NE(xHandle, nullptr); + const SceneViewportRotateGizmoSegmentDrawData* startSegment = FindLongestVisibleSegment(*xHandle, true); + ASSERT_NE(startSegment, nullptr); + const Math::Vector2 startMouse = SegmentMidpoint(*startSegment); + const SceneViewportRotateGizmoSegmentDrawData* endSegment = FindFarthestVisibleSegment(*xHandle, startMouse, true); + ASSERT_NE(endSegment, nullptr); + + const auto startContext = MakeContext(target, startMouse, overlay); + gizmo.Update(startContext); + ASSERT_TRUE(gizmo.IsHoveringHandle()); + ASSERT_TRUE(gizmo.TryBeginDrag(startContext, m_context.GetUndoManager())); + + const auto dragContext = MakeContext(target, SegmentMidpoint(*endSegment), overlay); + gizmo.Update(dragContext); + gizmo.UpdateDrag(dragContext); + gizmo.EndDrag(m_context.GetUndoManager()); + + const Math::Vector3 rotatedForward = target->GetTransform()->GetForward(); + EXPECT_GT(std::abs(rotatedForward.y), 0.05f); +} + TEST_F(SceneViewportRotateGizmoTest, ViewRingIsVisibleAndHoverable) { Components::GameObject* target = GetSceneManager().CreateEntity("Target"); ASSERT_NE(target, nullptr);