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:
+195
-8
@@ -8,6 +8,9 @@
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace Engine::ECS {
|
||||
|
||||
EntityHandle Scene::CreateEntity(std::string name)
|
||||
@@ -37,8 +40,19 @@ namespace Engine::ECS {
|
||||
ENGINE_FATAL("Scene::DestroyEntity: stale entity ({})",
|
||||
static_cast<u32>(e));
|
||||
}
|
||||
// 延迟到帧尾,保证遍历期间池结构不变
|
||||
m_PendingDestroy.push_back(e);
|
||||
// 层级级联(UE 语义):销毁父 → 附着子树随葬。整棵子树入队,
|
||||
// 重复入队由 FlushDestroyQueue 的 valid 检查消化
|
||||
std::vector<Entity> stack{e};
|
||||
while (!stack.empty()) {
|
||||
const Entity cur = stack.back();
|
||||
stack.pop_back();
|
||||
m_PendingDestroy.push_back(cur);
|
||||
if (const auto* h = m_Registry.try_get<HierarchyComponent>(cur)) {
|
||||
for (Entity c : h->children) {
|
||||
if (m_Registry.valid(c)) stack.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::FlushDestroyQueue()
|
||||
@@ -46,9 +60,17 @@ namespace Engine::ECS {
|
||||
if (m_PendingDestroy.empty()) return;
|
||||
|
||||
for (Entity e : m_PendingDestroy) {
|
||||
if (m_Registry.valid(e)) {
|
||||
m_Registry.destroy(e); // entt 自动回收槽位并递增版本号
|
||||
if (!m_Registry.valid(e)) continue; // 级联重复入队 / 已销毁
|
||||
// 摘链:从(仍存活的)父的 children 表移除,防悬空句柄残留
|
||||
if (const auto* h = m_Registry.try_get<HierarchyComponent>(e);
|
||||
h && h->parent != entt::null && m_Registry.valid(h->parent)) {
|
||||
auto& siblings =
|
||||
m_Registry.get<HierarchyComponent>(h->parent).children;
|
||||
siblings.erase(
|
||||
std::remove(siblings.begin(), siblings.end(), e),
|
||||
siblings.end());
|
||||
}
|
||||
m_Registry.destroy(e); // entt 自动回收槽位并递增版本号
|
||||
}
|
||||
m_PendingDestroy.clear();
|
||||
}
|
||||
@@ -69,13 +91,178 @@ namespace Engine::ECS {
|
||||
void Scene::UpdateTransforms()
|
||||
{
|
||||
ZoneScoped;
|
||||
// 只重算脏的:UE transform-update 阶段模式。
|
||||
// 静止实体(绝大多数)零成本;层级传播在此扩展
|
||||
m_Registry.view<TransformComponent>().each([](TransformComponent& t) {
|
||||
if (t.IsDirty()) t.Compose();
|
||||
// 层级级联:从根 DFS(父先于子)。"自身脏 || 祖先脏" 才重算
|
||||
// ——父 world 变化传播给整棵子树,静止子树零成本(UE
|
||||
// transform-update 阶段模式)。迭代栈防爆栈(深层链安全)
|
||||
struct Frame { Entity e; bool ancestorDirty; };
|
||||
std::vector<Frame> stack;
|
||||
|
||||
m_Registry.view<TransformComponent>().each([&](Entity e, TransformComponent&) {
|
||||
// 根 = 无有效父链接的实体
|
||||
const auto* h = m_Registry.try_get<HierarchyComponent>(e);
|
||||
if (h && h->parent != entt::null && m_Registry.valid(h->parent)) {
|
||||
return;
|
||||
}
|
||||
stack.push_back({e, false});
|
||||
while (!stack.empty()) {
|
||||
const Frame f = stack.back();
|
||||
stack.pop_back();
|
||||
auto& t = m_Registry.get<TransformComponent>(f.e);
|
||||
const bool recompose = f.ancestorDirty || t.IsDirty();
|
||||
const auto* hc = m_Registry.try_get<HierarchyComponent>(f.e);
|
||||
if (recompose) {
|
||||
const Entity p = (hc && hc->parent != entt::null &&
|
||||
m_Registry.valid(hc->parent))
|
||||
? hc->parent : entt::null;
|
||||
if (p != entt::null) {
|
||||
t.ComposeFromParent(
|
||||
m_Registry.get<TransformComponent>(p));
|
||||
} else {
|
||||
t.Compose();
|
||||
}
|
||||
}
|
||||
// 无论本节点是否重算都下探:子自身可能脏(新建实体),
|
||||
// ancestorDirty = 本节点是否重算(父 world 变了才传播)
|
||||
if (hc) {
|
||||
for (Entity c : hc->children) {
|
||||
if (m_Registry.valid(c) &&
|
||||
m_Registry.all_of<TransformComponent>(c)) {
|
||||
stack.push_back({c, recompose});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 层级(UE Actor attach 语义)----
|
||||
|
||||
Entity Scene::GetParent(Entity e) const
|
||||
{
|
||||
if (!m_Registry.valid(e)) return entt::null;
|
||||
const auto* h = m_Registry.try_get<HierarchyComponent>(e);
|
||||
return (h && h->parent != entt::null && m_Registry.valid(h->parent))
|
||||
? h->parent : entt::null;
|
||||
}
|
||||
|
||||
bool Scene::IsDescendantOf(Entity e, Entity ancestor) const
|
||||
{
|
||||
if (!m_Registry.valid(e) || !m_Registry.valid(ancestor)) return false;
|
||||
Entity cur = e;
|
||||
while (cur != entt::null) {
|
||||
if (cur == ancestor) return true;
|
||||
cur = GetParent(cur);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Scene::SetParent(Entity child, Entity parent, bool keepWorld)
|
||||
{
|
||||
if (!m_Registry.valid(child)) return false;
|
||||
if (parent != entt::null && !m_Registry.valid(parent)) return false;
|
||||
if (child == parent) return false;
|
||||
|
||||
// 防环:新父的祖先链不得包含 child(否则成环,DFS 死循环)
|
||||
if (parent != entt::null) {
|
||||
Entity a = parent;
|
||||
while (a != entt::null) {
|
||||
if (a == child) {
|
||||
ENGINE_WARN("Scene::SetParent: rejected (would create "
|
||||
"cycle: parent is descendant of child)");
|
||||
return false;
|
||||
}
|
||||
a = GetParent(a);
|
||||
}
|
||||
}
|
||||
if (GetParent(child) == parent) return true; // 幂等:已在位
|
||||
|
||||
// keepWorld 换算基准 = 改挂前 world(补算脏节点,缓存新鲜)
|
||||
EnsureWorldFresh(child);
|
||||
const glm::vec3 wp = m_Registry.get<TransformComponent>(child)
|
||||
.GetWorldPosition();
|
||||
const glm::quat wr = m_Registry.get<TransformComponent>(child)
|
||||
.GetWorldRotationQuat();
|
||||
const glm::vec3 ws = m_Registry.get<TransformComponent>(child)
|
||||
.GetWorldScale();
|
||||
|
||||
// 从旧父摘链
|
||||
auto& h = m_Registry.get_or_emplace<HierarchyComponent>(child);
|
||||
if (h.parent != entt::null && m_Registry.valid(h.parent)) {
|
||||
auto& siblings =
|
||||
m_Registry.get<HierarchyComponent>(h.parent).children;
|
||||
siblings.erase(
|
||||
std::remove(siblings.begin(), siblings.end(), child),
|
||||
siblings.end());
|
||||
}
|
||||
|
||||
// keepWorld:world SRT → 沿新父反解 local(UE Attach 语义:
|
||||
// 挂载前后世界位姿不变)
|
||||
h.parent = parent;
|
||||
if (keepWorld) {
|
||||
SetWorldPose(child, wp, wr, ws);
|
||||
} else {
|
||||
// 序列化加载:local 已从文件就位,只需标脏触发重算
|
||||
m_Registry.get<TransformComponent>(child).MarkDirty();
|
||||
}
|
||||
if (parent != entt::null) {
|
||||
m_Registry.get_or_emplace<HierarchyComponent>(parent)
|
||||
.children.push_back(child);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Scene::SetWorldPose(Entity e, const glm::vec3& worldPos,
|
||||
const glm::quat& worldRot,
|
||||
const glm::vec3& worldScale)
|
||||
{
|
||||
if (!m_Registry.valid(e)) return;
|
||||
const Entity p = GetParent(e);
|
||||
auto& t = m_Registry.get<TransformComponent>(e);
|
||||
if (p == entt::null) {
|
||||
// 根:local 即 world
|
||||
t.SetPosition(worldPos).SetRotationQuat(worldRot)
|
||||
.SetScale(worldScale);
|
||||
return;
|
||||
}
|
||||
EnsureWorldFresh(p);
|
||||
const auto& pt = m_Registry.get<TransformComponent>(p);
|
||||
// world SRT → local:除父缩放 → 反旋父旋转 → 平移差
|
||||
//(ComposeFromParent 的严格逆运算)。父缩放零保护:clamp 避免除零
|
||||
const glm::vec3 invS{
|
||||
std::abs(pt.GetWorldScale().x) > 1e-8f ? 1.0f / pt.GetWorldScale().x : 0.0f,
|
||||
std::abs(pt.GetWorldScale().y) > 1e-8f ? 1.0f / pt.GetWorldScale().y : 0.0f,
|
||||
std::abs(pt.GetWorldScale().z) > 1e-8f ? 1.0f / pt.GetWorldScale().z : 0.0f};
|
||||
const glm::quat invR = glm::conjugate(pt.GetWorldRotationQuat());
|
||||
t.SetPosition(invR * ((worldPos - pt.GetWorldPosition()) * invS))
|
||||
.SetRotationQuat(invR * worldRot)
|
||||
.SetScale(worldScale * invS);
|
||||
}
|
||||
|
||||
void Scene::EnsureWorldFresh(Entity e)
|
||||
{
|
||||
if (!m_Registry.valid(e)) return;
|
||||
// 收集父链到根,自顶向下补算。一旦某节点重算,其下全部后代
|
||||
// 的 world 缓存都基于旧父 → 必须级联重算(latch)
|
||||
std::vector<Entity> chain;
|
||||
Entity cur = e;
|
||||
while (cur != entt::null) {
|
||||
chain.push_back(cur);
|
||||
cur = GetParent(cur);
|
||||
}
|
||||
bool dirty = false;
|
||||
for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
|
||||
auto& t = m_Registry.get<TransformComponent>(*it);
|
||||
if (!dirty && !t.IsDirty()) continue; // 该节点照旧
|
||||
const Entity p = GetParent(*it);
|
||||
if (p != entt::null) {
|
||||
t.ComposeFromParent(m_Registry.get<TransformComponent>(p));
|
||||
} else {
|
||||
t.Compose();
|
||||
}
|
||||
dirty = true; // 子孙缓存已过时
|
||||
}
|
||||
}
|
||||
|
||||
CameraComponent* Scene::GetPrimaryCamera(TransformComponent** outTransform)
|
||||
{
|
||||
CameraComponent* found = nullptr;
|
||||
|
||||
@@ -417,28 +417,29 @@ namespace Engine {
|
||||
// 编辑器拖动 Dynamic 体(暂停期/物理未步进的帧)→ 传送 +
|
||||
// 清速度(UE 编辑器语义:编辑器操作权威于物理 body,拖动 =
|
||||
// 重新放置)。物理模拟中 Writeback 每步同步 lastPos/lastRot
|
||||
// → 本比对只在真正拖动帧为真,不会打断自由模拟
|
||||
if (rec.lastPos != tr.GetPosition() ||
|
||||
rec.lastRot != tr.GetRotationQuat()) {
|
||||
// → 本比对只在真正拖动帧为真,不会打断自由模拟。
|
||||
// body 是世界系:读 world 缓存(层级实体取父链组合结果)
|
||||
if (rec.lastPos != tr.GetWorldPosition() ||
|
||||
rec.lastRot != tr.GetWorldRotationQuat()) {
|
||||
bi.SetPositionAndRotation(
|
||||
id,
|
||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||
ToJolt(tr.GetRotationQuat()),
|
||||
JPH::RVec3(tr.GetWorldPosition().x, tr.GetWorldPosition().y, tr.GetWorldPosition().z),
|
||||
ToJolt(tr.GetWorldRotationQuat()),
|
||||
JPH::EActivation::Activate);
|
||||
bi.SetLinearVelocity(id, JPH::Vec3(0.0f, 0.0f, 0.0f));
|
||||
bi.SetAngularVelocity(id, JPH::Vec3(0.0f, 0.0f, 0.0f));
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotationQuat();
|
||||
rec.lastPos = tr.GetWorldPosition();
|
||||
rec.lastRot = tr.GetWorldRotationQuat();
|
||||
}
|
||||
} else if (rec.lastPos != tr.GetPosition() ||
|
||||
rec.lastRot != tr.GetRotationQuat()) {
|
||||
} else if (rec.lastPos != tr.GetWorldPosition() ||
|
||||
rec.lastRot != tr.GetWorldRotationQuat()) {
|
||||
// 编辑器拖动静态/运动学体 → 传送 body
|
||||
bi.SetPositionAndRotation(id,
|
||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||
ToJolt(tr.GetRotationQuat()),
|
||||
JPH::RVec3(tr.GetWorldPosition().x, tr.GetWorldPosition().y, tr.GetWorldPosition().z),
|
||||
ToJolt(tr.GetWorldRotationQuat()),
|
||||
JPH::EActivation::DontActivate);
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotationQuat();
|
||||
rec.lastPos = tr.GetWorldPosition();
|
||||
rec.lastRot = tr.GetWorldRotationQuat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,8 +520,8 @@ namespace Engine {
|
||||
if (tr1 == nullptr || tr2 == nullptr) {
|
||||
return;
|
||||
}
|
||||
const glm::vec3 s1 = tr1->GetScale();
|
||||
const glm::vec3 s2 = tr2->GetScale();
|
||||
const glm::vec3 s1 = tr1->GetWorldScale();
|
||||
const glm::vec3 s2 = tr2->GetWorldScale();
|
||||
|
||||
// 双写锁读位姿/COM + 创建(SyncScene 在主线程,与 Update 不并发)
|
||||
JPH::BodyLockWrite lock1(m_System->GetBodyLockInterface(), b1.id);
|
||||
@@ -642,22 +643,22 @@ namespace Engine {
|
||||
if (found == m_Characters.end()) {
|
||||
CharacterEntry entry;
|
||||
entry.cc = std::make_unique<CharacterController>(
|
||||
*m_System, p, tr.GetPosition());
|
||||
entry.lastPos = tr.GetPosition();
|
||||
*m_System, p, tr.GetWorldPosition());
|
||||
entry.lastPos = tr.GetWorldPosition();
|
||||
m_Characters.emplace(bits, std::move(entry));
|
||||
continue;
|
||||
}
|
||||
if (found->second.cc->GetParams() != p) {
|
||||
// 参数变更 → 重建(位置沿用当前 Transform)
|
||||
found->second.cc = std::make_unique<CharacterController>(
|
||||
*m_System, p, tr.GetPosition());
|
||||
found->second.lastPos = tr.GetPosition();
|
||||
*m_System, p, tr.GetWorldPosition());
|
||||
found->second.lastPos = tr.GetWorldPosition();
|
||||
continue;
|
||||
}
|
||||
if (found->second.lastPos != tr.GetPosition()) {
|
||||
if (found->second.lastPos != tr.GetWorldPosition()) {
|
||||
// 编辑器拖动实体 → 传送角色(角色是唯一位置源,反向覆盖)
|
||||
found->second.cc->Teleport(tr.GetPosition());
|
||||
found->second.lastPos = tr.GetPosition();
|
||||
found->second.cc->Teleport(tr.GetWorldPosition());
|
||||
found->second.lastPos = tr.GetWorldPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,8 +673,12 @@ namespace Engine {
|
||||
continue;
|
||||
}
|
||||
entry.cc->Update(dt, gravity, *m_TempAllocator);
|
||||
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底
|
||||
tr->SetPosition(p); // 标脏 → UpdateTransforms 重算 world
|
||||
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底(世界系)
|
||||
// 写回走 world→local 换算(层级实体挂父下仍正确;标脏 →
|
||||
// UpdateTransforms 重算 world)
|
||||
m_Scene.SetWorldPose(
|
||||
static_cast<entt::entity>(bits), p,
|
||||
tr->GetWorldRotationQuat(), tr->GetWorldScale());
|
||||
entry.lastPos = p;
|
||||
}
|
||||
}
|
||||
@@ -693,8 +698,9 @@ namespace Engine {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 缩放烘进顶点:Jolt shape 无缩放语义,body 只吃 pos/rot
|
||||
const glm::vec3 s = tr.GetScale();
|
||||
// 缩放烘进顶点:Jolt shape 无缩放语义,body 只吃 pos/rot。
|
||||
// 取 world 缩放(层级实体 = 父链累计)
|
||||
const glm::vec3 s = tr.GetWorldScale();
|
||||
|
||||
// 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动)
|
||||
if (rb.type != ECS::RigidBodyComponent::BodyType::Static) {
|
||||
@@ -833,23 +839,23 @@ namespace Engine {
|
||||
shape = BuildMeshShape(entityBits, rb, tr);
|
||||
if (shape == nullptr) {
|
||||
shape = new JPH::BoxShape(JPH::Vec3(
|
||||
std::max(rb.boxHalfExtents.x * tr.GetScale().x, 0.01f),
|
||||
std::max(rb.boxHalfExtents.y * tr.GetScale().y, 0.01f),
|
||||
std::max(rb.boxHalfExtents.z * tr.GetScale().z, 0.01f)));
|
||||
std::max(rb.boxHalfExtents.x * tr.GetWorldScale().x, 0.01f),
|
||||
std::max(rb.boxHalfExtents.y * tr.GetWorldScale().y, 0.01f),
|
||||
std::max(rb.boxHalfExtents.z * tr.GetWorldScale().z, 0.01f)));
|
||||
}
|
||||
} else {
|
||||
shape = new JPH::BoxShape(JPH::Vec3(
|
||||
std::max(rb.boxHalfExtents.x * tr.GetScale().x, 0.01f),
|
||||
std::max(rb.boxHalfExtents.y * tr.GetScale().y, 0.01f),
|
||||
std::max(rb.boxHalfExtents.z * tr.GetScale().z, 0.01f)));
|
||||
std::max(rb.boxHalfExtents.x * tr.GetWorldScale().x, 0.01f),
|
||||
std::max(rb.boxHalfExtents.y * tr.GetWorldScale().y, 0.01f),
|
||||
std::max(rb.boxHalfExtents.z * tr.GetWorldScale().z, 0.01f)));
|
||||
}
|
||||
|
||||
const bool dynamic = rb.type == ECS::RigidBodyComponent::BodyType::Dynamic;
|
||||
const bool kinematic = rb.type == ECS::RigidBodyComponent::BodyType::Kinematic;
|
||||
JPH::BodyCreationSettings settings(
|
||||
shape,
|
||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||
ToJolt(tr.GetRotationQuat()),
|
||||
JPH::RVec3(tr.GetWorldPosition().x, tr.GetWorldPosition().y, tr.GetWorldPosition().z),
|
||||
ToJolt(tr.GetWorldRotationQuat()),
|
||||
dynamic
|
||||
? JPH::EMotionType::Dynamic
|
||||
: (kinematic
|
||||
@@ -882,8 +888,8 @@ namespace Engine {
|
||||
rec.compound = rb.compound;
|
||||
rec.mass = rb.mass;
|
||||
rec.isTrigger = rb.isTrigger;
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotationQuat();
|
||||
rec.lastPos = tr.GetWorldPosition();
|
||||
rec.lastRot = tr.GetWorldRotationQuat();
|
||||
m_Bodies[entityBits] = rec;
|
||||
|
||||
rb.bodyId = id.GetIndexAndSequenceNumber();
|
||||
@@ -1053,8 +1059,13 @@ namespace Engine {
|
||||
const JPH::BodyID id(rb.bodyId);
|
||||
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.SetRotationQuat(ToGlm(rot));
|
||||
// body 姿态是世界系:写回经 world→local 换算(挂父层级实体
|
||||
// 不破坏相对位姿;根实体 = 直写)。标脏 → UpdateTransforms 重算
|
||||
m_Scene.SetWorldPose(
|
||||
e,
|
||||
glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ()),
|
||||
ToGlm(rot),
|
||||
tr.GetWorldScale());
|
||||
|
||||
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
|
||||
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||
@@ -1063,8 +1074,8 @@ namespace Engine {
|
||||
// 位移误判为编辑器拖动 → teleport 回旧位置死循环
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
if (auto it = m_Bodies.find(bits); it != m_Bodies.end()) {
|
||||
it->second.lastPos = tr.GetPosition();
|
||||
it->second.lastRot = tr.GetRotationQuat();
|
||||
it->second.lastPos = tr.GetWorldPosition();
|
||||
it->second.lastRot = tr.GetWorldRotationQuat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ namespace Engine
|
||||
: 2.0f);
|
||||
|
||||
if (light.type == LightComponent::Type::Point) {
|
||||
dst.position = glm::vec4(t.GetPosition(), 1.0f);
|
||||
dst.position = glm::vec4(t.GetWorldPosition(), 1.0f);
|
||||
dst.direction = glm::vec4(0.0f);
|
||||
} else {
|
||||
glm::vec3 dir = glm::length(light.direction) > 0.0f
|
||||
@@ -391,7 +391,7 @@ namespace Engine
|
||||
shadowDir = glm::vec3(collected[0].direction);
|
||||
}
|
||||
ComputeCascades(shadowDir);
|
||||
ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);
|
||||
ubo.cameraPos = glm::vec4(camTransform->GetWorldPosition(), 1.0f);
|
||||
|
||||
// ---- 2.5 骨骼动画推进 + 姿态采样(与可见性无关:播放头恒走,
|
||||
// 否则被剔除的实体回屏时动画跳变)----
|
||||
@@ -579,7 +579,7 @@ namespace Engine
|
||||
// LOD 选级:屏占比近似 = radius/距离(主视图按实例距离;
|
||||
// 阈值 10/20 倍半径,可调)。开关关闭 = 恒 LOD0
|
||||
const f32 dist =
|
||||
glm::length(worldCenter - camTransform->GetPosition());
|
||||
glm::length(worldCenter - camTransform->GetWorldPosition());
|
||||
u32 mainLod = 0;
|
||||
if (m_LodEnabled) {
|
||||
if (dist >= worldRadius * 20.0f) {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -176,6 +177,7 @@ namespace Engine::Serialization {
|
||||
{
|
||||
u64 uuid = 0;
|
||||
std::string name = "entity";
|
||||
u64 parentUuid = 0; // 0 = 根实体(层级键缺省)
|
||||
YAML::Node comps; // 缺省 = Null(无组件)
|
||||
};
|
||||
|
||||
@@ -190,6 +192,13 @@ namespace Engine::Serialization {
|
||||
if (const auto* name = reg.try_get<ECS::NameComponent>(e)) {
|
||||
out << YAML::Key << "Name" << YAML::Value << name->name;
|
||||
}
|
||||
// 层级(UE Actor attach):父存 UUID(跨存档稳定);
|
||||
// 悬空父(不该发生)按根实体写
|
||||
if (const auto* h = reg.try_get<ECS::HierarchyComponent>(e);
|
||||
h && h->parent != entt::null && reg.valid(h->parent)) {
|
||||
out << YAML::Key << "Parent" << YAML::Value
|
||||
<< reg.get<ECS::IdComponent>(h->parent).uuid.Value();
|
||||
}
|
||||
|
||||
out << YAML::Key << "Components" << YAML::Value << YAML::BeginMap;
|
||||
|
||||
@@ -524,6 +533,7 @@ namespace Engine::Serialization {
|
||||
ParsedEntity pe;
|
||||
pe.uuid = e["Entity"].as<u64>(0);
|
||||
pe.name = e["Name"].as<std::string>(pe.name);
|
||||
pe.parentUuid = e["Parent"].as<u64>(0);
|
||||
if (const YAML::Node comps = e["Components"];
|
||||
comps && comps.IsMap()) {
|
||||
// 仅在 rhs 有效时赋值(僵尸节点 operator= 会抛)
|
||||
@@ -543,6 +553,7 @@ namespace Engine::Serialization {
|
||||
scene.Clear();
|
||||
|
||||
std::unordered_set<u64> seenUuids;
|
||||
std::unordered_map<u64, entt::entity> byUuid; // 第二遍重链用
|
||||
for (ParsedEntity& pe : parsed) {
|
||||
auto handle = scene.CreateEntity(pe.name);
|
||||
|
||||
@@ -552,6 +563,8 @@ namespace Engine::Serialization {
|
||||
uuid = UUID::New().Value();
|
||||
}
|
||||
handle.GetComponent<ECS::IdComponent>().uuid = UUID{uuid};
|
||||
pe.uuid = uuid; // 回写最终 uuid(重生成后父引用仍可命中)
|
||||
byUuid.emplace(uuid, static_cast<entt::entity>(handle));
|
||||
|
||||
const YAML::Node comps = pe.comps; // 拷贝初始化,缺省 Null
|
||||
|
||||
@@ -734,6 +747,22 @@ namespace Engine::Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 阶段 3:重链父子(两遍解析:父可能出现在子之后)。
|
||||
// keepWorld=false:local TRS 已从文件就位(相对父语义),
|
||||
// 只建链标脏。防环由 SetParent 兜底(手编档成环 → 告警 + 挂根)
|
||||
for (const ParsedEntity& pe : parsed) {
|
||||
if (pe.parentUuid == 0) continue;
|
||||
const auto c = byUuid.find(pe.uuid);
|
||||
const auto p = byUuid.find(pe.parentUuid);
|
||||
if (c == byUuid.end() || p == byUuid.end()) {
|
||||
ENGINE_WARN("SceneSerializer: parent {} missing for entity "
|
||||
"{}, kept as root",
|
||||
pe.parentUuid, pe.uuid);
|
||||
continue;
|
||||
}
|
||||
scene.SetParent(c->second, p->second, /*keepWorld=*/false);
|
||||
}
|
||||
|
||||
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
||||
// 显式补算,否则首帧 World = 单位阵
|
||||
scene.UpdateTransforms();
|
||||
|
||||
Reference in New Issue
Block a user