CedricGuillemet / ImGuizmo

Immediate mode 3D gizmo for scene editing and other controls based on Dear Imgui

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Singularity in DecomposeMatrixToComponents

GeTechG opened this issue · comments

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];
         ...
gta_sa.2022-05-20.16-47-37-414.mp4

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.

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.

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.

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:

  • grab selected actors transform , convert the quaternion to a rotator
    -user manipulates slider or gizmo
    -take new values, convert rotator back to quaternion, and apply it to model

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!

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

2022-11-27.11-13-17.mp4

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;
	}
}