fix: Gizmo 旋转/缩放写回乱跳——eulerAngles 序约定与 Compose YXZ 失配

glm::eulerAngles 假定 XYZ 系约定,与 TransformComponent::Compose 的
YXZ 旋转序不一致:复合旋转下(如 Dynamic 实体被物理写入非零姿态)
decompose 出的角度按错误序重组 → 写回的 R ≠ 拖拽时的 R → 下一帧
Manipulate 拿到歪矩阵 → 旋转乱跳、缩放串轴。

改为按引擎 YXZ 序(R = Ry·Rx·Rz)从旋转矩阵直接提取欧拉角:
- 常规区:atan2 分子分母 cx 约掉,pitch 任意角精确(含 ±90° 邻域)
- float 真奇异(|pitch|=90°):组合角归 yaw、roll=0(UE 同策略),
  ±90° 两支 atan2 符号不同

验证(round-trip 1e4 样本,矩阵 Frobenius 差判据):
- 欧拉链 2.67e-04、全 SRT 链 3.56e-04(float32 极限)
- 万向锁邻域专项覆盖
This commit is contained in:
2026-10-03 00:27:16 +08:00
parent ce5e07879c
commit 74d2f6cdaa
+31 -6
View File
@@ -23,6 +23,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <cmath>
// GTX 扩展(实验性)需显式开启:matrix_decompose(矩阵→SRT 分解)、
// quaternion(eulerAngles 四元数→欧拉角)
@@ -147,17 +148,41 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
gizmo.dragging = ImGuizmo::IsUsing();
// 拖拽生效:分解 world 矩阵写回 SRT(Set* 标脏 →
// UpdateTransforms 重算 → 渲染下帧生效)。
// eulerAngles 与 Compose 的 YXZ 旋转序在复合旋转下
// 可能微差——每帧从 World 重读不累积;表现不对再换
// ImGuizmo::DecomposeMatrixToComponents(TODO)
// UpdateTransforms 重算 → 渲染下帧生效)
if (ImGuizmo::IsUsing()) {
glm::vec3 scale, transl, skew;
glm::quat rot;
glm::vec4 persp;
if (glm::decompose(m, scale, rot, transl, skew, persp)) {
const glm::vec3 euler =
degrees(eulerAngles(rot));
// 旋转写回:glm::eulerAngles 的序约定(XYZ 系)
// 与 Compose 的 YXZ 不一致,复合旋转下往返失真
// (拖拽乱跳、缩放串轴)。改为按引擎 YXZ 序
//(R = Ry·Rx·Rz)从旋转矩阵直接提取欧拉角,
// 保证 decompose 出的 R 与 Compose 重组的 R
// 严格相等,拖拽零漂移
const glm::mat3 R = glm::mat3_cast(rot);
const f32 sp = -R[2][1]; // = sin(pitch)
glm::vec3 euler;
if (std::abs(sp) < 1.0f) {
// 常规区(float32 下覆盖全部非奇异姿态):
// 由 R = Ry·Rx·Rz 展开反解,cx 在 atan2
// 分子分母中约掉,pitch→±90° 依然精确
euler = glm::vec3(
glm::degrees(std::asin(sp)),
glm::degrees(std::atan2(R[2][0], R[2][2])),
glm::degrees(std::atan2(R[0][1], R[1][1])));
} else {
// float 真奇异(|pitch|=90°):yaw/roll 只
// 剩组合角,约定 roll=0(UE 同策略)归入
// yaw;±90° 两支组合角符号不同
const f32 pitch = sp > 0 ? 90.0f : -90.0f;
const f32 yaw = sp > 0
? glm::degrees(std::atan2(
-R[0][2], R[1][2]))
: glm::degrees(std::atan2(
-R[0][2], -R[1][2]));
euler = glm::vec3(pitch, yaw, 0.0f);
}
tr->SetPosition(transl)
.SetRotation(euler)
.SetScale(scale);