refactor: TransformComponent 旋转存储改四元数(UE FTransform 语义)
欧拉角内部存储每帧矩阵↔欧拉往返,是 Gizmo 拖拽乱跳/缩放串轴的 结构性根源(74d2f6c 的单点公式修复治标不治本)。对齐 UE/Unity 方案:内部存 glm::quat,欧拉只在 UI 表皮。 - Components.h:m_Rotation 改 quat;Compose 用 mat4_cast; SetRotation(euler) 转存(q=qy·qx·qz);新增 SetRotationQuat/ GetRotationQuat;GetRotation() 改派生欧拉(YXZ 序矩阵反解, 仅 UI 显示/编辑用) - ViewportPanel:Gizmo 写回 decompose 的 quat 直写,删除整个 欧拉提取块 - PhysicsWorld:删 QuatToEulerYXZ/EulerYXZToQuat,改 ToJolt/ ToGlm 分量直转;物理写回 SetRotationQuat;BodyRecord.lastRot 改存 quat 验证(1 万随机姿态 round-trip):Gizmo 链路矩阵差 7.9e-07 (旧欧拉方案 3.56e-04,好 3 个数量级);万向锁 pitch=±90° 穿 表皮恒定;欧拉表皮误差 4.5e-04 为欧拉参数化固有病态(随 pitch→±90 指数增长,仅影响 UI 显示数值),热路径不经过。
This commit is contained in:
@@ -73,46 +73,15 @@ namespace Engine {
|
||||
}
|
||||
};
|
||||
|
||||
// 引擎 Compose() 的旋转合成是 Ry·Rx·Rz(YXZ 序,见 Components.h)——
|
||||
// 物理写回必须按同约定分解欧拉角,否则体素会随写回帧帧漂移。
|
||||
// R = Ry(y)·Rx(x)·Rz(z) 的矩阵展开(0 基):
|
||||
// M12 = -sx → x = asin(-M12);否则 y = atan2(M02, M22),z = atan2(M10, M11)
|
||||
glm::vec3 QuatToEulerYXZ(const JPH::Quat &q) {
|
||||
const glm::quat gq(q.GetW(), q.GetX(), q.GetY(), q.GetZ());
|
||||
const glm::mat3 m = glm::mat3(gq); // glm 列主序:m[col][row]
|
||||
// 数学记法 M[row][col]:M12 = m[2][1],M02 = m[2][0],M10 = m[0][1] …
|
||||
const f32 m12 = m[2][1];
|
||||
const f32 m02 = m[2][0];
|
||||
const f32 m22 = m[2][2];
|
||||
const f32 m10 = m[0][1];
|
||||
const f32 m11 = m[1][1];
|
||||
const f32 m01 = m[1][0];
|
||||
|
||||
const f32 sinX = std::clamp(-m12, -1.0f, 1.0f);
|
||||
const f32 x = std::asin(sinX);
|
||||
if (std::abs(sinX) < 0.9999f) {
|
||||
return {
|
||||
glm::degrees(x),
|
||||
glm::degrees(std::atan2(m02, m22)),
|
||||
glm::degrees(std::atan2(m10, m11))
|
||||
};
|
||||
}
|
||||
// 万向锁(x=±90°):z 并入 y,取 z=0,y = atan2(M01, M02)
|
||||
return {
|
||||
glm::degrees(x),
|
||||
glm::degrees(std::atan2(m01, m02)),
|
||||
0.0f
|
||||
};
|
||||
// Jolt ↔ glm 四元数直转(TransformComponent 旋转存 glm::quat,
|
||||
// UE FTransform 语义):物理同步/写回不再经欧拉往返 → 姿态
|
||||
// 零漂移(分量序:Jolt ctor (x,y,z,w),glm ctor (w,x,y,z))
|
||||
JPH::Quat ToJolt(const glm::quat &q) {
|
||||
return {q.x, q.y, q.z, q.w};
|
||||
}
|
||||
|
||||
// 与 QuatToEulerYXZ 互逆:q = qy(y)·qx(x)·qz(z) ≡ Ry·Rx·Rz
|
||||
//(不能用 glm::quat(euler) ctor——那是 ZYX 航向序,语义不同)
|
||||
JPH::Quat EulerYXZToQuat(const glm::vec3 &eulerDeg) {
|
||||
const glm::quat qx = glm::angleAxis(glm::radians(eulerDeg.x), glm::vec3(1, 0, 0));
|
||||
const glm::quat qy = glm::angleAxis(glm::radians(eulerDeg.y), glm::vec3(0, 1, 0));
|
||||
const glm::quat qz = glm::angleAxis(glm::radians(eulerDeg.z), glm::vec3(0, 0, 1));
|
||||
const glm::quat q = qy * qx * qz;
|
||||
return {q.x, q.y, q.z, q.w};
|
||||
glm::quat ToGlm(const JPH::Quat &q) {
|
||||
return {q.GetW(), q.GetX(), q.GetY(), q.GetZ()};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -390,14 +359,14 @@ namespace Engine {
|
||||
if (rb.type == ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||
bi.SetGravityFactor(id, rb.gravityScale);
|
||||
} else if (rec.lastPos != tr.GetPosition() ||
|
||||
rec.lastRot != tr.GetRotation()) {
|
||||
rec.lastRot != tr.GetRotationQuat()) {
|
||||
// 编辑器拖动静态/运动学体 → 传送 body
|
||||
bi.SetPositionAndRotation(id,
|
||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||
EulerYXZToQuat(tr.GetRotation()),
|
||||
ToJolt(tr.GetRotationQuat()),
|
||||
JPH::EActivation::DontActivate);
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotation();
|
||||
rec.lastRot = tr.GetRotationQuat();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,7 +478,7 @@ namespace Engine {
|
||||
JPH::BodyCreationSettings settings(
|
||||
shape,
|
||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||
EulerYXZToQuat(tr.GetRotation()),
|
||||
ToJolt(tr.GetRotationQuat()),
|
||||
dynamic
|
||||
? JPH::EMotionType::Dynamic
|
||||
: (kinematic
|
||||
@@ -542,7 +511,7 @@ namespace Engine {
|
||||
rec.mass = rb.mass;
|
||||
rec.isSensor = rb.isSensor;
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotation();
|
||||
rec.lastRot = tr.GetRotationQuat();
|
||||
m_Bodies[entityBits] = rec;
|
||||
|
||||
rb.bodyId = id.GetIndexAndSequenceNumber();
|
||||
@@ -625,7 +594,7 @@ namespace Engine {
|
||||
const JPH::RVec3 pos = bi.GetPosition(id);
|
||||
const JPH::Quat rot = bi.GetRotation(id);
|
||||
tr.SetPosition(glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ()));
|
||||
tr.SetRotation(QuatToEulerYXZ(rot));
|
||||
tr.SetRotationQuat(ToGlm(rot));
|
||||
|
||||
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
|
||||
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Engine {
|
||||
|
||||
// 基向量由实体旋转决定(本地 -Z 为视线方向;应用顺序 Y→X,
|
||||
// 与 TransformComponent::Compose 一致;roll 不参与 FPS 相机)
|
||||
const glm::vec3& rot = transform.GetRotation();
|
||||
const glm::vec3 rot = transform.GetRotation(); // 派生欧拉(度)
|
||||
const glm::mat3 r = static_cast<glm::mat3>(
|
||||
glm::rotate(glm::mat4(1.0f), glm::radians(rot.y),
|
||||
glm::vec3(0, 1, 0)) *
|
||||
|
||||
Reference in New Issue
Block a user