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:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user