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:
@@ -23,13 +23,11 @@
|
|||||||
|
|
||||||
#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 四元数→欧拉角)
|
// 旋转以四元数输出,直接写回 TransformComponent)
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include <glm/gtx/matrix_decompose.hpp>
|
#include <glm/gtx/matrix_decompose.hpp>
|
||||||
#include <glm/gtx/quaternion.hpp>
|
|
||||||
|
|
||||||
using namespace Engine; // u32 / f32(Engine/Core/Types.h)
|
using namespace Engine; // u32 / f32(Engine/Core/Types.h)
|
||||||
|
|
||||||
@@ -148,43 +146,16 @@ 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 重算 → 渲染下帧生效)。旋转用
|
||||||
|
// decompose 出的 quat 直写(UE FTransform 语义)——
|
||||||
|
// 不经欧拉往返,拖拽零漂移、无万向锁
|
||||||
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)) {
|
||||||
// 旋转写回: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)
|
tr->SetPosition(transl)
|
||||||
.SetRotation(euler)
|
.SetRotationQuat(rot)
|
||||||
.SetScale(scale);
|
.SetScale(scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -26,7 +26,8 @@
|
|||||||
#include <Engine/Renderer/Renderer.h>
|
#include <Engine/Renderer/Renderer.h>
|
||||||
#include <Engine/Renderer/Model.h>
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
@@ -41,16 +44,56 @@ namespace Engine::ECS {
|
|||||||
// Scene::UpdateTransforms() 只对脏的实体重算(transform-update
|
// Scene::UpdateTransforms() 只对脏的实体重算(transform-update
|
||||||
// 阶段),并自动向未来的子层级传播。渲染热路径只读 World()。
|
// 阶段),并自动向未来的子层级传播。渲染热路径只读 World()。
|
||||||
// 层级(父子变换)留给后续的层级同步系统。
|
// 层级(父子变换)留给后续的层级同步系统。
|
||||||
|
//
|
||||||
|
// 旋转内部存储 = 四元数(UE FTransform 语义,不存欧拉角):
|
||||||
|
// 欧拉往返是漂移/万向锁之源(Gizmo 拖拽乱跳的根因)。欧拉只在
|
||||||
|
// 表皮出现——UI 显示(GetRotation 派生)与 UI 输入(SetRotation
|
||||||
|
// 转存);Gizmo/物理热路径走 quat 直写(SetRotationQuat)零往返。
|
||||||
struct TransformComponent {
|
struct TransformComponent {
|
||||||
// ---- 读取 ----
|
// ---- 读取 ----
|
||||||
const glm::vec3& GetPosition() const { return m_Position; }
|
const glm::vec3& GetPosition() const { return m_Position; }
|
||||||
const glm::vec3& GetRotation() const { return m_Rotation; } // 欧拉角(度)
|
const glm::quat& GetRotationQuat() const { return m_Rotation; }
|
||||||
const glm::vec3& GetScale() const { return m_Scale; }
|
const glm::vec3& GetScale() const { return m_Scale; }
|
||||||
const glm::mat4& World() const { return m_World; } // 要求已由 UpdateTransforms 计算
|
const glm::mat4& World() const { return m_World; } // 要求已由 UpdateTransforms 计算
|
||||||
|
|
||||||
|
// 欧拉角(度)视图:从 quat 派生(仅 UI 显示/编辑用,勿做存储)。
|
||||||
|
// 按引擎 YXZ 序(R = Ry·Rx·Rz)从旋转矩阵反解;glm 列主序索引:
|
||||||
|
// x = asin(-R[2][1])、y = atan2(R[2][0], R[2][2])、z = atan2(R[0][1], R[1][1])。
|
||||||
|
// cx 在 atan2 分子分母中约掉,pitch 贴 ±90° 依然精确
|
||||||
|
glm::vec3 GetRotation() const
|
||||||
|
{
|
||||||
|
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) {
|
||||||
|
return glm::degrees(glm::vec3(
|
||||||
|
std::asin(sp),
|
||||||
|
std::atan2(R[2][0], R[2][2]),
|
||||||
|
std::atan2(R[0][1], R[1][1])));
|
||||||
|
}
|
||||||
|
// ±90° 万向锁:yaw/roll 只剩组合角,约定 roll=0(UE 同策略)
|
||||||
|
// 归入 yaw;两支组合角符号不同
|
||||||
|
return sp > 0.0f
|
||||||
|
? glm::vec3(90.0f,
|
||||||
|
glm::degrees(std::atan2(-R[0][2], R[1][2])), 0.0f)
|
||||||
|
: glm::vec3(-90.0f,
|
||||||
|
glm::degrees(std::atan2(-R[0][2], -R[1][2])), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 写入(标脏,脏实体由 UpdateTransforms 重算)----
|
// ---- 写入(标脏,脏实体由 UpdateTransforms 重算)----
|
||||||
TransformComponent& SetPosition(const glm::vec3& p) { if (m_Position != p) { m_Position = p; m_Dirty = true; } return *this; }
|
TransformComponent& SetPosition(const glm::vec3& p) { if (m_Position != p) { m_Position = p; m_Dirty = true; } return *this; }
|
||||||
TransformComponent& SetRotation(const glm::vec3& r) { if (m_Rotation != r) { m_Rotation = r; m_Dirty = true; } return *this; }
|
// 欧拉角(度)入口:UI/游戏逻辑用。转 quat 存(q = qy·qx·qz,
|
||||||
|
// 与原 Ry·Rx·Rz 合成严格同构;不能用 glm::quat(euler) ctor——
|
||||||
|
// 那是 ZYX 航向序,语义不同)
|
||||||
|
TransformComponent& SetRotation(const glm::vec3& r)
|
||||||
|
{
|
||||||
|
const glm::quat qx = glm::angleAxis(glm::radians(r.x), glm::vec3(1, 0, 0));
|
||||||
|
const glm::quat qy = glm::angleAxis(glm::radians(r.y), glm::vec3(0, 1, 0));
|
||||||
|
const glm::quat qz = glm::angleAxis(glm::radians(r.z), glm::vec3(0, 0, 1));
|
||||||
|
return SetRotationQuat(qy * qx * qz);
|
||||||
|
}
|
||||||
|
// 四元数直写(UE FTransform 语义):Gizmo 拖拽/物理写回用,
|
||||||
|
// 不经欧拉往返 → 零漂移、无万向锁
|
||||||
|
TransformComponent& SetRotationQuat(const glm::quat& q) { if (m_Rotation != q) { m_Rotation = q; m_Dirty = true; } return *this; }
|
||||||
TransformComponent& SetScale(const glm::vec3& s) { if (m_Scale != s) { m_Scale = s; m_Dirty = true; } return *this; }
|
TransformComponent& SetScale(const glm::vec3& s) { if (m_Scale != s) { m_Scale = s; m_Dirty = true; } return *this; }
|
||||||
|
|
||||||
bool IsDirty() const { return m_Dirty; }
|
bool IsDirty() const { return m_Dirty; }
|
||||||
@@ -60,19 +103,15 @@ namespace Engine::ECS {
|
|||||||
TransformComponent& Compose()
|
TransformComponent& Compose()
|
||||||
{
|
{
|
||||||
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_Position);
|
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_Position);
|
||||||
const glm::mat4 r =
|
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation.y), glm::vec3(0, 1, 0)) *
|
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation.x), glm::vec3(1, 0, 0)) *
|
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation.z), glm::vec3(0, 0, 1));
|
|
||||||
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_Scale);
|
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_Scale);
|
||||||
m_World = t * r * s;
|
m_World = t * glm::mat4_cast(m_Rotation) * s;
|
||||||
m_Dirty = false;
|
m_Dirty = false;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
glm::vec3 m_Position{0.0f};
|
glm::vec3 m_Position{0.0f};
|
||||||
glm::vec3 m_Rotation{0.0f};
|
glm::quat m_Rotation{1.0f, 0.0f, 0.0f, 0.0f}; // 单位四元数(w,x,y,z)
|
||||||
glm::vec3 m_Scale{1.0f};
|
glm::vec3 m_Scale{1.0f};
|
||||||
glm::mat4 m_World{1.0f};
|
glm::mat4 m_World{1.0f};
|
||||||
bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world
|
bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <Jolt/Core/TempAllocator.h>
|
#include <Jolt/Core/TempAllocator.h>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -85,7 +86,7 @@ namespace Engine {
|
|||||||
f32 mass = 0.0f;
|
f32 mass = 0.0f;
|
||||||
bool isSensor = false; // 传感器标记(变更 → 重建 body)
|
bool isSensor = false; // 传感器标记(变更 → 重建 body)
|
||||||
glm::vec3 lastPos{0.0f}; // 静态/运动体编辑器拖动检测
|
glm::vec3 lastPos{0.0f}; // 静态/运动体编辑器拖动检测
|
||||||
glm::vec3 lastRot{0.0f};
|
glm::quat lastRot{1.0f, 0.0f, 0.0f, 0.0f}; // 同上(quat 直比,无欧拉往返)
|
||||||
// 加速度箭头用:上一步速度(定步长差分)与平滑后的加速度
|
// 加速度箭头用:上一步速度(定步长差分)与平滑后的加速度
|
||||||
glm::vec3 prevVel{0.0f};
|
glm::vec3 prevVel{0.0f};
|
||||||
glm::vec3 lastAccel{0.0f};
|
glm::vec3 lastAccel{0.0f};
|
||||||
|
|||||||
@@ -73,46 +73,15 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 引擎 Compose() 的旋转合成是 Ry·Rx·Rz(YXZ 序,见 Components.h)——
|
// Jolt ↔ glm 四元数直转(TransformComponent 旋转存 glm::quat,
|
||||||
// 物理写回必须按同约定分解欧拉角,否则体素会随写回帧帧漂移。
|
// UE FTransform 语义):物理同步/写回不再经欧拉往返 → 姿态
|
||||||
// R = Ry(y)·Rx(x)·Rz(z) 的矩阵展开(0 基):
|
// 零漂移(分量序:Jolt ctor (x,y,z,w),glm ctor (w,x,y,z))
|
||||||
// M12 = -sx → x = asin(-M12);否则 y = atan2(M02, M22),z = atan2(M10, M11)
|
JPH::Quat ToJolt(const glm::quat &q) {
|
||||||
glm::vec3 QuatToEulerYXZ(const JPH::Quat &q) {
|
return {q.x, q.y, q.z, q.w};
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 与 QuatToEulerYXZ 互逆:q = qy(y)·qx(x)·qz(z) ≡ Ry·Rx·Rz
|
glm::quat ToGlm(const JPH::Quat &q) {
|
||||||
//(不能用 glm::quat(euler) ctor——那是 ZYX 航向序,语义不同)
|
return {q.GetW(), q.GetX(), q.GetY(), q.GetZ()};
|
||||||
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};
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -390,14 +359,14 @@ namespace Engine {
|
|||||||
if (rb.type == ECS::RigidBodyComponent::BodyType::Dynamic) {
|
if (rb.type == ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||||
bi.SetGravityFactor(id, rb.gravityScale);
|
bi.SetGravityFactor(id, rb.gravityScale);
|
||||||
} else if (rec.lastPos != tr.GetPosition() ||
|
} else if (rec.lastPos != tr.GetPosition() ||
|
||||||
rec.lastRot != tr.GetRotation()) {
|
rec.lastRot != tr.GetRotationQuat()) {
|
||||||
// 编辑器拖动静态/运动学体 → 传送 body
|
// 编辑器拖动静态/运动学体 → 传送 body
|
||||||
bi.SetPositionAndRotation(id,
|
bi.SetPositionAndRotation(id,
|
||||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||||
EulerYXZToQuat(tr.GetRotation()),
|
ToJolt(tr.GetRotationQuat()),
|
||||||
JPH::EActivation::DontActivate);
|
JPH::EActivation::DontActivate);
|
||||||
rec.lastPos = tr.GetPosition();
|
rec.lastPos = tr.GetPosition();
|
||||||
rec.lastRot = tr.GetRotation();
|
rec.lastRot = tr.GetRotationQuat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,7 +478,7 @@ namespace Engine {
|
|||||||
JPH::BodyCreationSettings settings(
|
JPH::BodyCreationSettings settings(
|
||||||
shape,
|
shape,
|
||||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||||
EulerYXZToQuat(tr.GetRotation()),
|
ToJolt(tr.GetRotationQuat()),
|
||||||
dynamic
|
dynamic
|
||||||
? JPH::EMotionType::Dynamic
|
? JPH::EMotionType::Dynamic
|
||||||
: (kinematic
|
: (kinematic
|
||||||
@@ -542,7 +511,7 @@ namespace Engine {
|
|||||||
rec.mass = rb.mass;
|
rec.mass = rb.mass;
|
||||||
rec.isSensor = rb.isSensor;
|
rec.isSensor = rb.isSensor;
|
||||||
rec.lastPos = tr.GetPosition();
|
rec.lastPos = tr.GetPosition();
|
||||||
rec.lastRot = tr.GetRotation();
|
rec.lastRot = tr.GetRotationQuat();
|
||||||
m_Bodies[entityBits] = rec;
|
m_Bodies[entityBits] = rec;
|
||||||
|
|
||||||
rb.bodyId = id.GetIndexAndSequenceNumber();
|
rb.bodyId = id.GetIndexAndSequenceNumber();
|
||||||
@@ -625,7 +594,7 @@ namespace Engine {
|
|||||||
const JPH::RVec3 pos = bi.GetPosition(id);
|
const JPH::RVec3 pos = bi.GetPosition(id);
|
||||||
const JPH::Quat rot = bi.GetRotation(id);
|
const JPH::Quat rot = bi.GetRotation(id);
|
||||||
tr.SetPosition(glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ()));
|
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);
|
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
|
||||||
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace Engine {
|
|||||||
|
|
||||||
// 基向量由实体旋转决定(本地 -Z 为视线方向;应用顺序 Y→X,
|
// 基向量由实体旋转决定(本地 -Z 为视线方向;应用顺序 Y→X,
|
||||||
// 与 TransformComponent::Compose 一致;roll 不参与 FPS 相机)
|
// 与 TransformComponent::Compose 一致;roll 不参与 FPS 相机)
|
||||||
const glm::vec3& rot = transform.GetRotation();
|
const glm::vec3 rot = transform.GetRotation(); // 派生欧拉(度)
|
||||||
const glm::mat3 r = static_cast<glm::mat3>(
|
const glm::mat3 r = static_cast<glm::mat3>(
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(rot.y),
|
glm::rotate(glm::mat4(1.0f), glm::radians(rot.y),
|
||||||
glm::vec3(0, 1, 0)) *
|
glm::vec3(0, 1, 0)) *
|
||||||
|
|||||||
Reference in New Issue
Block a user