CedricGuillemet / ImGuizmo

Immediate mode 3D gizmo for scene editing and other controls based on Dear Imgui
MIT License
3.15k stars 885 forks source link

Singularity in DecomposeMatrixToComponents #244

Open GeTechG opened 2 years ago

GeTechG commented 2 years ago

All axes are fine, but when you rotate the Y axis on the -90 and 90 edges the angle is inverted and does not behave correctly. (In the video Y axis is Z, as I have a different stored rotation)

        ...
    std::array rot = { -rotates[1], rotates[2], -rotates[0] };
    std::array<float, 16> matrix{};
    ImGuizmo::RecomposeMatrixFromComponents(pos.data(), rot.data(), scaleVec.data(), matrix.data());

    Perspective(TheCamera.FindCamFOV() / 2.0f, ImGui::GetIO().DisplaySize.x / ImGui::GetIO().DisplaySize.y, 0.1f, 100.f, cameraProjection);

    ImGuizmo::SetOrthographic(false);
    ImGuizmo::BeginFrame();

    EditTransform(cameraView, cameraProjection, matrix.data(), true);

    if (ImGuizmo::IsUsing()) {
        float newScale[3];
        float newRot[3];
        float newPos[3];
        ImGuizmo::DecomposeMatrixToComponents(matrix.data(), newPos, newRot, newScale);
        rotates[0] = -newRot[2];
        rotates[1] = -newRot[0];
        rotates[2] =  newRot[1];
         ...

https://user-images.githubusercontent.com/20752579/169524827-671b3085-a3b5-4ab8-90a3-af6a5088e27d.mp4

jkinz3 commented 2 years ago

I'm not the dev but What you have right there is called a "singularity". It's not the fault of imguizmo but an inherent flaw in Euler angles. They occur when one of the rotation axes (specifically the second one that's applied) is at or close to 90/-90 degrees. The solution is to store the models rotation as something other than Euler angles. You can use a rotation matrix, axis-angle, or quaternions.

GeTechG commented 2 years ago

Yes, but that's the result of the DecomposeMatrixToComponents method, I have trouble understanding how I can turn an imguizmo matrix into what you listed, I would be fine with quaternions.

GeTechG commented 2 years ago

Yes, the only way out is to switch to matrices or quaternions. But the question of how to defeat singularity is open, because in such editors as unity, unreal, godot, etc. have the ability to change angles via sliders and there is somehow this problem solved.

I found this one - https://euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm, but it doesn't help much, or I did something wrong.

jkinz3 commented 2 years ago

Apologies for not responding before. I dont know unity at all but in unreal engine, all actors use the FTransform struct. It is composed of 2 vectors for Translation and Scale as well ad a quaternion for orientation. So no actor in unreal uses Euler angles. In order to make them actually useful for humans, they have an FRotator struct that represents orientation as Euler angles. Then they have functions that convert back and forth between them. Now I don't know how it works exactly in unreal, but I imagine it works like this:

If you have your GitHub tied to your epic account, you can find the headers in Engine/Source/Runtime/Core/Public/Math They're Rotator.h and Quat.h. The implementations of the functions are in

Engine/Source/Runtime/Core/Private/Math/UnrealMath.cpp

Also here is an article on converting between the two. It's actually not a trivial thing.

That should be enough to get you started. Depending on the math library you're using, it might already have functions for those conversions. Good luck!

gaetandezeiraud commented 1 year ago

@GeTechG Have you find a way to fix this issue on your side? I have the same problem with ImGuizmo. When I change rotation from my ImGui interface, no problem. Only with the ImGuizmo ROTATE. And I already have quaternion for the renderer. The question is for RecomposeMatrixFromComponents and DecomposeMatrixToComponents.

https://user-images.githubusercontent.com/1573282/204129900-972091e4-e4df-458d-8ac6-46d1408451b8.mp4

gaetandezeiraud commented 1 year ago

Okay, I found a solution. Here an example, maybe it can help someone in the futur.

XMFLOAT4X4 tempViewMatrix;
XMStoreFloat4x4(&tempViewMatrix, viewMatrix);

XMFLOAT4X4 tempProjMatrix;
XMStoreFloat4x4(&tempProjMatrix, camComponent.Camera.GetProjMatrix());

XMFLOAT4X4 tempTransformMatrix;
XMStoreFloat4x4(&tempTransformMatrix, transform.GetTransform());

ImGuizmo::SetDrawlist(ImGui::GetBackgroundDrawList());
ImGuizmo::Manipulate(*tempViewMatrix.m, *tempProjMatrix.m, mCurrentGizmoOperation, mCurrentGizmoMode, *tempTransformMatrix.m);
if (ImGuizmo::IsUsing())
{
    float Ftranslation[3] = { 0.0f, 0.0f, 0.0f }, Frotation[3] = { 0.0f, 0.0f, 0.0f }, Fscale[3] = { 0.0f, 0.0f, 0.0f };
    ImGuizmo::DecomposeMatrixToComponents(*tempTransformMatrix.m, Ftranslation, Frotation, Fscale);

    switch (mCurrentGizmoOperation)
    {
    case ImGuizmo::TRANSLATE:
        transform.Translation = DirectX::XMFLOAT3(Ftranslation);
        break;
    case ImGuizmo::ROTATE:
    {
        XMMATRIX tempMatrix = XMLoadFloat4x4(&tempTransformMatrix);
        XMVECTOR tempVectorMatrix = XMQuaternionRotationMatrix(tempMatrix);
        transform.SetRotationQuaternion(tempVectorMatrix);
        break;
    }
    case ImGuizmo::SCALE:
        transform.Scale = DirectX::XMFLOAT3(Fscale);
        break;
    }
}
Benualdo commented 2 days ago

In case it helps anyone, I encountered the exact same issue but solved it differently: instead of using the DecomposeMatrixToComponents/RecomposeMatrixFromComponents idiom, I only used DecomposeMatrixToComponents and applied the transformations directly to the matrix as my engine is using float4x4 internally.