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:
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
// GTX 扩展(实验性)需显式开启:matrix_decompose(矩阵→SRT 分解)、
|
// GTX 扩展(实验性)需显式开启:matrix_decompose(矩阵→SRT 分解)、
|
||||||
// quaternion(eulerAngles 四元数→欧拉角)
|
// quaternion(eulerAngles 四元数→欧拉角)
|
||||||
@@ -147,17 +148,41 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
|||||||
gizmo.dragging = ImGuizmo::IsUsing();
|
gizmo.dragging = ImGuizmo::IsUsing();
|
||||||
|
|
||||||
// 拖拽生效:分解 world 矩阵写回 SRT(Set* 标脏 →
|
// 拖拽生效:分解 world 矩阵写回 SRT(Set* 标脏 →
|
||||||
// UpdateTransforms 重算 → 渲染下帧生效)。
|
// UpdateTransforms 重算 → 渲染下帧生效)
|
||||||
// eulerAngles 与 Compose 的 YXZ 旋转序在复合旋转下
|
|
||||||
// 可能微差——每帧从 World 重读不累积;表现不对再换
|
|
||||||
// ImGuizmo::DecomposeMatrixToComponents(TODO)
|
|
||||||
if (ImGuizmo::IsUsing()) {
|
if (ImGuizmo::IsUsing()) {
|
||||||
glm::vec3 scale, transl, skew;
|
glm::vec3 scale, transl, skew;
|
||||||
glm::quat rot;
|
glm::quat rot;
|
||||||
glm::vec4 persp;
|
glm::vec4 persp;
|
||||||
if (glm::decompose(m, scale, rot, transl, skew, persp)) {
|
if (glm::decompose(m, scale, rot, transl, skew, persp)) {
|
||||||
const glm::vec3 euler =
|
// 旋转写回:glm::eulerAngles 的序约定(XYZ 系)
|
||||||
degrees(eulerAngles(rot));
|
// 与 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)
|
tr->SetPosition(transl)
|
||||||
.SetRotation(euler)
|
.SetRotation(euler)
|
||||||
.SetScale(scale);
|
.SetScale(scale);
|
||||||
|
|||||||
Reference in New Issue
Block a user