Fix world rotation extraction with scaled parents

This commit is contained in:
2026-04-01 01:25:16 +08:00
parent 618ebed05d
commit 1af3cf87c4
3 changed files with 94 additions and 2 deletions

View File

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

View File

@@ -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
} // namespace

View File

@@ -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);