From 4fc48c21ac861e5e865d688f8d0660e45bb30a24 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Sun, 4 Oct 2026 01:37:49 +0800 Subject: [PATCH] fix(editor): euler display precision at gimbal lock (90 -> 89.98) Typing 90 degrees on the pitch axis showed 89.98. The stored quat is exact (matrices differ by 0.0) -- the noise is display-side: quat's sin(90) components square to 0.99999994 in float, and asin() diverges at |sin|->1, amplifying 6e-8 into 0.02 degrees. Yaw/roll use atan2 and were always exact, matching the user's report being X-axis only. Two fixes, both aligned with how UE gets away with it (internally UE is quat + equally pathological, its Details panel just shows one decimal): - GetRotation gimbal-lock threshold 1.0 -> 1-1e-6: pitch near +-90 now takes the composite-angle atan2 branch (ratio-stable) instead of asin, so typing 90.0 reads back exactly 90.0. Probed: X=90 and X=-90 now round-trip to 0 error; generic poses stay ~1e-6 deg. - Rotation row shows one decimal ("%.1f", UE Details default), masking the residual asin noise for angles near (not at) the pole; Ctrl+Click input stays full precision. Negative zero from atan2 cleaned up. Remaining asin noise for e.g. 89.9 (~9e-4 deg) is below the 1-decimal display and unreachable in practice. --- App/editor/InspectorPanel.cpp | 17 +++++++++++++---- Engine/include/Engine/ECS/Components.h | 7 ++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/App/editor/InspectorPanel.cpp b/App/editor/InspectorPanel.cpp index eebda38..c022e2a 100644 --- a/App/editor/InspectorPanel.cpp +++ b/App/editor/InspectorPanel.cpp @@ -73,9 +73,12 @@ namespace // UE 风变换行:label + 三轴色 DragFloat(轴色对齐 Gizmo:X 红 / // Y 绿 / Z 蓝)。拖拽粗调;Ctrl+Click 进输入框键入精确值。 - // 任一轴变化返回 true + // 任一轴变化返回 true。fmt = 显示格式(旋转行传 "%.1f" 对齐 + // UE Details 默认 1 位小数——欧拉显示端有固有反解噪声,见 + // TransformComponent::GetRotation 注释) bool DragVec3Row(const char* label, glm::vec3& v, float speed, - float mn = -3.4e38f, float mx = 3.4e38f) + float mn = -3.4e38f, float mx = 3.4e38f, + const char* fmt = "%.2f") { ImGui::TextUnformatted(label); ImGui::SameLine(72.0f); // 固定标签列宽 → 三行输入框对齐 @@ -97,7 +100,7 @@ namespace ImGui::SetNextItemWidth(boxW); changed |= ImGui::DragFloat( i == 0 ? "##x" : i == 1 ? "##y" : "##z", - &v[i], speed, mn, mx, "%.2f"); + &v[i], speed, mn, mx, fmt); ImGui::PopStyleColor(); if (i < 2) ImGui::SameLine(); } @@ -213,7 +216,13 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) t->SetPosition(pos); } glm::vec3 rot = t->GetRotation(); // 派生欧拉(度) - if (DragVec3Row("rotation", rot, 0.5f)) + for (int i = 0; i < 3; ++i) + { + if (rot[i] == 0.0f) rot[i] = 0.0f; // 清 -0.0(atan2 副产物) + } + // "%.1f" = UE Details 默认显示精度(反解噪声 ~0.02° @ pitch 90°, + // 存储端 quat 精确;手输 Ctrl+Click 仍全精度写入) + if (DragVec3Row("rotation", rot, 0.5f, -3.4e38f, 3.4e38f, "%.1f")) { t->SetRotation(rot); } diff --git a/Engine/include/Engine/ECS/Components.h b/Engine/include/Engine/ECS/Components.h index 0f7fdf4..e1ba268 100644 --- a/Engine/include/Engine/ECS/Components.h +++ b/Engine/include/Engine/ECS/Components.h @@ -65,7 +65,12 @@ namespace Engine::ECS { { const glm::mat3 R = glm::mat3_cast(m_Rotation); const f32 sp = std::clamp(-R[2][1], -1.0f, 1.0f); // = sin(pitch) - if (std::abs(sp) < 1.0f) { + // 万向锁分支阈值 1e-6:asin 在 |sin|→1 处导数发散(sin90 的 + // float 表示差 6e-8 → asin 放大成 0.02° 显示噪声,用户实测 + // 90 → 89.98)。贴锁区改走组合角 atan2(分子分母同源缩放, + // 比值稳定)→ 手输 90.0 显示 90.0。落入该窄区(±0.00057°) + // 的真实 yaw/roll 被归并为组合角,UI 输入不可达,可接受 + if (std::abs(sp) < 1.0f - 1e-6f) { return glm::degrees(glm::vec3( std::asin(sp), std::atan2(R[2][0], R[2][2]),