Fix world rotation extraction with scaled parents
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user