feat: parent-child transform hierarchy (UE Actor attach semantics)
- TransformComponent TRS = local (relative to parent); cached world SRT composed hierarchically (parent pos/rot/scale, no matrix decomposition shear under non-uniform parent scale) - HierarchyComponent (parent + children, lazily attached); Scene:: SetParent (keepWorld, cycle-safe) / GetParent / IsDescendantOf / SetWorldPose (world->local inverse, used by physics writeback & gizmo) - UpdateTransforms: DFS from roots, recompose only dirty-or-dirty- ancestor subtrees (parents before children) - DestroyEntity: descendants buried with parent (UE cascade) - Serialization v2: optional Parent uuid key, two-pass relink on load - Hierarchy panel: tree view, drag-drop reparent / drop-to-root detach, Create Child / Detach context menu; Inspector shows parent name - Consumers moved to world-space reads: lights, camera pos, physics bodies/characters/joints/collider baking, gizmo, F-focus, AlignTo
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
#include <Engine/Core/AssetRef.h>
|
||||
#include <Engine/Renderer/Camera.h>
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
@@ -40,22 +42,29 @@ namespace Engine::ECS {
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// 世界变换:UE USceneComponent 的 encapsulation 模式——
|
||||
// SRT 分量私有,所有写入口标脏;world 矩阵由帧循环的
|
||||
// Scene::UpdateTransforms() 只对脏的实体重算(transform-update
|
||||
// 阶段),并自动向未来的子层级传播。渲染热路径只读 World()。
|
||||
// 层级(父子变换)留给后续的层级同步系统。
|
||||
// 变换(UE FTransform 语义):TRS 分量 = 相对父的 **local** 值
|
||||
//(无父实体 local 即 world,旧场景零迁移)。分量私有,所有写入口
|
||||
// 标脏;world 缓存(SRT 三分量 + 矩阵)由帧循环的
|
||||
// Scene::UpdateTransforms() 从根 DFS 只对脏子树重算(transform-
|
||||
// update 阶段)。子 world = 父 world SRT 组合而非矩阵乘法——
|
||||
// 非均匀父缩放下组合式仍是干净 SRT(矩阵分解会引入剪切)。
|
||||
// 渲染热路径只读 World();灯光/相机/物理/Gizmo 读 world 三分量。
|
||||
//
|
||||
// 旋转内部存储 = 四元数(UE FTransform 语义,不存欧拉角):
|
||||
// 欧拉往返是漂移/万向锁之源(Gizmo 拖拽乱跳的根因)。欧拉只在
|
||||
// 表皮出现——UI 显示(GetRotation 派生)与 UI 输入(SetRotation
|
||||
// 转存);Gizmo/物理热路径走 quat 直写(SetRotationQuat)零往返。
|
||||
struct TransformComponent {
|
||||
// ---- 读取 ----
|
||||
// ---- local 读取(相对父;UI/游戏逻辑用)----
|
||||
const glm::vec3& GetPosition() const { return m_Position; }
|
||||
const glm::quat& GetRotationQuat() const { return m_Rotation; }
|
||||
const glm::vec3& GetScale() const { return m_Scale; }
|
||||
const glm::mat4& World() const { return m_World; } // 要求已由 UpdateTransforms 计算
|
||||
|
||||
// ---- world 缓存读取(要求已由 UpdateTransforms 计算)----
|
||||
const glm::mat4& World() const { return m_World; }
|
||||
const glm::vec3& GetWorldPosition() const { return m_WorldPosition; }
|
||||
const glm::quat& GetWorldRotationQuat() const { return m_WorldRotation; }
|
||||
const glm::vec3& GetWorldScale() const { return m_WorldScale; }
|
||||
|
||||
// 欧拉角(度)视图:从 quat 派生(仅 UI 显示/编辑用,勿做存储)。
|
||||
// 按引擎 YXZ 序(R = Ry·Rx·Rz)从旋转矩阵反解;glm 列主序索引:
|
||||
@@ -120,25 +129,66 @@ namespace Engine::ECS {
|
||||
|
||||
bool IsDirty() const { return m_Dirty; }
|
||||
|
||||
// 显式标脏:层级系统改父/挂载后触发 world 重算用
|
||||
//(local 值未变但 world 缓存已失效)
|
||||
TransformComponent& MarkDirty() { m_Dirty = true; return *this; }
|
||||
|
||||
// SRT 分量 → world 矩阵(T * R * S),清除脏标记。
|
||||
// 由 Scene::UpdateTransforms 调用(或层级系统),勿手动
|
||||
// 根实体版(world = local);由 Scene::UpdateTransforms 调用,
|
||||
// 勿手动
|
||||
TransformComponent& Compose()
|
||||
{
|
||||
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_Position);
|
||||
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_Scale);
|
||||
m_World = t * glm::mat4_cast(m_Rotation) * s;
|
||||
m_WorldPosition = m_Position;
|
||||
m_WorldRotation = m_Rotation;
|
||||
m_WorldScale = m_Scale;
|
||||
RebuildWorldMatrix();
|
||||
m_Dirty = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 子实体版:world = 父 world SRT 组合(UE FTransform 语义):
|
||||
// wPos = pPos + pRot·(pScale⊙lPos) wRot = pRot·lRot wScl = pScale⊙lScl
|
||||
// 组合式(非矩阵分解)保证非均匀父缩放下结果仍是干净 SRT。
|
||||
// 要求父已 Compose(DFS 父先于子保证)。由 Scene 调用,勿手动
|
||||
TransformComponent& ComposeFromParent(const TransformComponent& parent)
|
||||
{
|
||||
m_WorldPosition = parent.m_WorldPosition +
|
||||
parent.m_WorldRotation * (parent.m_WorldScale * m_Position);
|
||||
m_WorldRotation = parent.m_WorldRotation * m_Rotation;
|
||||
m_WorldScale = parent.m_WorldScale * m_Scale;
|
||||
RebuildWorldMatrix();
|
||||
m_Dirty = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void RebuildWorldMatrix()
|
||||
{
|
||||
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_WorldPosition);
|
||||
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_WorldScale);
|
||||
m_World = t * glm::mat4_cast(m_WorldRotation) * s;
|
||||
}
|
||||
|
||||
// ---- local TRS(相对父)----
|
||||
glm::vec3 m_Position{0.0f};
|
||||
glm::quat m_Rotation{1.0f, 0.0f, 0.0f, 0.0f}; // 单位四元数(w,x,y,z)
|
||||
glm::vec3 m_Scale{1.0f};
|
||||
// ---- world 缓存(UpdateTransforms 重算)----
|
||||
glm::vec3 m_WorldPosition{0.0f};
|
||||
glm::quat m_WorldRotation{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 m_WorldScale{1.0f};
|
||||
glm::mat4 m_World{1.0f};
|
||||
bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world
|
||||
};
|
||||
|
||||
// 层级链接(UE Actor attach):父句柄 + 子表。惰性创建——
|
||||
// 只有参与父子关系的实体才有本组件,独立实体零存储成本。
|
||||
// entt 句柄仅在 registry 存续期有效;序列化换 UUID,加载后重链
|
||||
struct HierarchyComponent {
|
||||
entt::entity parent = entt::null; // entt::null = 根实体
|
||||
std::vector<entt::entity> children; // 挂载顺序
|
||||
};
|
||||
|
||||
// 网格渲染体:引用共享的 Model 资产。
|
||||
// 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)——
|
||||
// ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。
|
||||
|
||||
@@ -52,7 +52,8 @@ namespace Engine::ECS {
|
||||
|
||||
// 返回句柄表皮:e.AddComponent<T>() / e.GetComponent<T>() / e.Destroy()
|
||||
EntityHandle CreateEntity(std::string name = "new entity");
|
||||
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
|
||||
// 延迟:帧尾统一销毁。层级级联(UE 语义):销毁父 → 子树随葬
|
||||
void DestroyEntity(Entity e);
|
||||
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
|
||||
|
||||
// 整场景换血(序列化加载):立即销毁全部实体并清空延迟队列。
|
||||
@@ -66,10 +67,33 @@ namespace Engine::ECS {
|
||||
|
||||
// 帧循环统一重算脏 transform 的 world 矩阵(UE transform-update
|
||||
// 阶段模式)——由 Application 在 OnUpdate 之后、渲染之前调用,
|
||||
// 用户代码永远不需要手动 Compose
|
||||
// 用户代码永远不需要手动 Compose。层级语义:从根 DFS,脏实体
|
||||
// 或脏祖先的后代才重算(父先于子,ComposeFromParent 组合)
|
||||
void UpdateTransforms();
|
||||
bool IsAlive(Entity e) const { return m_Registry.valid(e); }
|
||||
|
||||
// ---- 层级(UE Actor attach 语义)----
|
||||
|
||||
// 改父/挂根。keepWorld=true(编辑器拖拽语义):换算 local 保持
|
||||
// world 位姿不变;false(序列化加载语义):local 已就位只建链。
|
||||
// 防环(新父是 child 后代 → 拒绝);成功返回 true
|
||||
bool SetParent(Entity child, Entity parent, bool keepWorld = true);
|
||||
|
||||
// 父句柄;根/悬空/无效实体返回 entt::null
|
||||
Entity GetParent(Entity e) const;
|
||||
|
||||
// e 是否在 ancestor 的子树内(含相等)。防环检测/面板用
|
||||
bool IsDescendantOf(Entity e, Entity ancestor) const;
|
||||
|
||||
// 世界位姿写入(UE SetActorLocation 语义):world SRT → 沿父链
|
||||
// 反解 local。物理写回/Gizmo 拖拽用
|
||||
void SetWorldPose(Entity e, const glm::vec3& worldPos,
|
||||
const glm::quat& worldRot, const glm::vec3& worldScale);
|
||||
|
||||
// 沿父链自顶向下补算脏 world(层级写读 world 前调用——
|
||||
// UpdateTransforms 之外的时机读取/换算 world 前保证缓存新鲜)
|
||||
void EnsureWorldFresh(Entity e);
|
||||
|
||||
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回 entt::null
|
||||
Entity FindEntityByUUID(UUID uuid);
|
||||
|
||||
|
||||
@@ -51,8 +51,9 @@ namespace Engine {
|
||||
// 一次性对齐版)。环绕 pivot 已失效,清除待 MMB 重建
|
||||
void AlignTo(const ECS::TransformComponent& target)
|
||||
{
|
||||
m_Transform.SetPosition(target.GetPosition());
|
||||
m_Transform.SetRotationQuat(target.GetRotationQuat());
|
||||
// world 位姿复制(目标实体可能在层级子树下)
|
||||
m_Transform.SetPosition(target.GetWorldPosition());
|
||||
m_Transform.SetRotationQuat(target.GetWorldRotationQuat());
|
||||
m_Transform.Compose();
|
||||
m_Controller.ResetOrbit();
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@ namespace Engine::Assets { class AssetManager; }
|
||||
|
||||
namespace Engine::Serialization {
|
||||
|
||||
// 当前场景文件格式版本(字段/结构变更时递增)
|
||||
inline constexpr int kSceneVersion = 1;
|
||||
// 当前场景文件格式版本(字段/结构变更时递增)。
|
||||
// 2 = 层级(可选键 Parent: <uuid>,缺省 = 根实体,旧档零迁移)
|
||||
inline constexpr int kSceneVersion = 2;
|
||||
|
||||
class SceneSerializer {
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user