physics: joint constraints (fixed/hinge/ball)
- JointComponent: type, target UUID, per-body local anchors, hinge axis + angle limits (data only, zero Jolt deps) - PhysicsWorld::SyncConstraints: create/remove/rebuild on snapshot change; body rebuild contagion via invalidated-bits list; target resolved lazily by UUID each sync (no two-pass load needed) - Jolt FixedConstraint/HingeConstraint/PointConstraint in LocalToBodyCOM space (anchors minus shape COM); fixed frames lock creation-time relative pose; hinge axis aligned across bodies - Inspector: Joint section with target entity name dropdown - Serialization: Joint map with UUID target, as<T> fallbacks
This commit is contained in:
@@ -116,6 +116,11 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
{
|
{
|
||||||
reg.emplace<Engine::ECS::RigidBodyComponent>(selected);
|
reg.emplace<Engine::ECS::RigidBodyComponent>(selected);
|
||||||
}
|
}
|
||||||
|
if (!reg.all_of<Engine::ECS::JointComponent>(selected) &&
|
||||||
|
ImGui::MenuItem("Joint"))
|
||||||
|
{
|
||||||
|
reg.emplace<Engine::ECS::JointComponent>(selected);
|
||||||
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -334,6 +339,58 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Joint(约束:两端 body 就绪后由 PhysicsWorld 对账创建)----
|
||||||
|
if (auto* jc = reg.try_get<Engine::ECS::JointComponent>(selected))
|
||||||
|
{
|
||||||
|
if (ImGui::CollapsingHeader("Joint", ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
|
{
|
||||||
|
int type = static_cast<int>(jc->type);
|
||||||
|
if (ImGui::Combo("type", &type, "Fixed\0Hinge\0Ball\0"))
|
||||||
|
{
|
||||||
|
jc->type = static_cast<Engine::ECS::JointComponent::Type>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// target 实体下拉:实体名列表,选中态按 UUID 匹配(变更写回)
|
||||||
|
std::string items;
|
||||||
|
std::vector<Engine::UUID> uuids;
|
||||||
|
int current = -1;
|
||||||
|
reg.view<Engine::ECS::IdComponent, Engine::ECS::NameComponent>().each(
|
||||||
|
[&](entt::entity e, const Engine::ECS::IdComponent& id,
|
||||||
|
const Engine::ECS::NameComponent& n)
|
||||||
|
{
|
||||||
|
if (e == selected)
|
||||||
|
{
|
||||||
|
return; // 自连无意义
|
||||||
|
}
|
||||||
|
if (id.uuid == jc->target)
|
||||||
|
{
|
||||||
|
current = static_cast<int>(uuids.size());
|
||||||
|
}
|
||||||
|
uuids.push_back(id.uuid);
|
||||||
|
items.append(n.name);
|
||||||
|
items.push_back('\0');
|
||||||
|
});
|
||||||
|
items.push_back('\0'); // ImGui Combo 双 \0 结尾
|
||||||
|
if (ImGui::Combo("target", ¤t, items.c_str()) && current >= 0)
|
||||||
|
{
|
||||||
|
jc->target = uuids[static_cast<size_t>(current)];
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::DragFloat3("anchor A", &jc->anchorA.x, 0.05f);
|
||||||
|
ImGui::DragFloat3("anchor B", &jc->anchorB.x, 0.05f);
|
||||||
|
if (jc->type == Engine::ECS::JointComponent::Type::Hinge)
|
||||||
|
{
|
||||||
|
ImGui::DragFloat3("hinge axis", &jc->hingeAxis.x, 0.05f);
|
||||||
|
ImGui::Checkbox("enable limit", &jc->enableLimit);
|
||||||
|
if (jc->enableLimit)
|
||||||
|
{
|
||||||
|
ImGui::DragFloat("min deg", &jc->limitMin, 1.0f, -180.0f, 0.0f);
|
||||||
|
ImGui::DragFloat("max deg", &jc->limitMax, 1.0f, 0.0f, 180.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Animation ----
|
// ---- Animation ----
|
||||||
if (auto* anim = reg.try_get<Engine::ECS::AnimationComponent>(selected))
|
if (auto* anim = reg.try_get<Engine::ECS::AnimationComponent>(selected))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -236,6 +236,29 @@ namespace Engine::ECS {
|
|||||||
glm::vec3 velocity{0.0f}; // 只读调试视图:线速度(m/s)
|
glm::vec3 velocity{0.0f}; // 只读调试视图:线速度(m/s)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 约束/关节(Jolt TwoBodyConstraint 家族)。纯数据 POD 零 Jolt 依赖;
|
||||||
|
// 创建/对账在 PhysicsWorld::SyncConstraints(kPhyDbgConstraints 出图元)。
|
||||||
|
// target 存 UUID(跨存档稳定),惰性解析——SyncScene 每帧按 UUID 反查
|
||||||
|
// 实体:序列化无需两遍解析,UUID 再生后自然失联,加载顺序无关
|
||||||
|
struct JointComponent {
|
||||||
|
enum class Type : u8 { Fixed = 0, Hinge = 1, Ball = 2 };
|
||||||
|
Type type = Type::Fixed;
|
||||||
|
UUID target{}; // 目标实体(0 = 未设置,不建约束)
|
||||||
|
|
||||||
|
// 锚点:各体 Transform 局部系(Unity connectedAnchor 语义,
|
||||||
|
// × 本体缩放烘焙)。Fixed/Hinge/Ball 都连这两点
|
||||||
|
glm::vec3 anchorA{0.0f}; // 本体(挂组件的实体)
|
||||||
|
glm::vec3 anchorB{0.0f}; // 目标体
|
||||||
|
|
||||||
|
// Hinge 专用:转轴(本体局部系,方向量不吃缩放)+ 角度限位(度)
|
||||||
|
glm::vec3 hingeAxis{1.0f, 0.0f, 0.0f};
|
||||||
|
bool enableLimit = false;
|
||||||
|
f32 limitMin = -90.0f; // Jolt 语义 min∈[-180,0] max∈[0,180]
|
||||||
|
f32 limitMax = 90.0f;
|
||||||
|
|
||||||
|
bool operator==(const JointComponent&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Engine::ECS
|
} // namespace Engine::ECS
|
||||||
|
|
||||||
#endif //HELLOVULKAN_COMPONENTS_H
|
#endif //HELLOVULKAN_COMPONENTS_H
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <Jolt/Jolt.h>
|
#include <Jolt/Jolt.h>
|
||||||
#include <Jolt/Physics/PhysicsSystem.h>
|
#include <Jolt/Physics/PhysicsSystem.h>
|
||||||
#include <Jolt/Physics/Body/BodyID.h>
|
#include <Jolt/Physics/Body/BodyID.h>
|
||||||
|
#include <Jolt/Physics/Constraints/Constraint.h> // JPH::Ref<Constraint>
|
||||||
#include <Jolt/Core/JobSystemThreadPool.h>
|
#include <Jolt/Core/JobSystemThreadPool.h>
|
||||||
#include <Jolt/Core/TempAllocator.h>
|
#include <Jolt/Core/TempAllocator.h>
|
||||||
|
|
||||||
@@ -114,6 +115,12 @@ namespace Engine {
|
|||||||
void CollectDebugData(); // 按 m_DebugBits 收集调试图元(渲染帧率)
|
void CollectDebugData(); // 按 m_DebugBits 收集调试图元(渲染帧率)
|
||||||
void CreateBody(u32 entityBits, ECS::RigidBodyComponent& rb,
|
void CreateBody(u32 entityBits, ECS::RigidBodyComponent& rb,
|
||||||
ECS::TransformComponent& tr);
|
ECS::TransformComponent& tr);
|
||||||
|
// 约束对账:owner 孤儿清理 / body 重建传染失效 / 建缺失与变更重建
|
||||||
|
// invalidated = 本帧 SyncScene 中被销毁或重建 body 的实体位模式
|
||||||
|
void SyncConstraints(const std::vector<u32>& invalidated);
|
||||||
|
void CreateConstraint(u32 ownerBits, u32 targetBits,
|
||||||
|
const ECS::JointComponent& jc);
|
||||||
|
void RemoveConstraint(u32 ownerBits);
|
||||||
// Mesh 碰撞体:实体视觉网格 → Jolt 形状(Static=MeshShape /
|
// Mesh 碰撞体:实体视觉网格 → Jolt 形状(Static=MeshShape /
|
||||||
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
|
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
|
||||||
JPH::RefConst<JPH::Shape> BuildMeshShape(u32 entityBits,
|
JPH::RefConst<JPH::Shape> BuildMeshShape(u32 entityBits,
|
||||||
@@ -128,6 +135,15 @@ namespace Engine {
|
|||||||
void QueueEvent(CollisionEvent&& ev); // 回调线程 append(多线程并发,加锁)
|
void QueueEvent(CollisionEvent&& ev); // 回调线程 append(多线程并发,加锁)
|
||||||
bool TryBitsByBodyId(const JPH::BodyID& id, u32& outBits) const;
|
bool TryBitsByBodyId(const JPH::BodyID& id, u32& outBits) const;
|
||||||
|
|
||||||
|
// ---- 约束(JointComponent → Jolt TwoBodyConstraint;owner 实体寻址)----
|
||||||
|
// target 惰性解析 UUID;body 重建/销毁 → 引用它的约束失效重建
|
||||||
|
struct ConstraintRecord {
|
||||||
|
JPH::Ref<JPH::Constraint> constraint;
|
||||||
|
ECS::JointComponent snapshot; // 变更检测 → 移除重建
|
||||||
|
u32 targetBits = 0;
|
||||||
|
};
|
||||||
|
std::unordered_map<u32, ConstraintRecord> m_Constraints;
|
||||||
|
|
||||||
ECS::Scene& m_Scene;
|
ECS::Scene& m_Scene;
|
||||||
Assets::AssetManager* m_Assets = nullptr; // Mesh 碰撞体解析 modelRef
|
Assets::AssetManager* m_Assets = nullptr; // Mesh 碰撞体解析 modelRef
|
||||||
bool m_Enabled = true;
|
bool m_Enabled = true;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||||
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
|
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
|
||||||
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
|
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
|
||||||
|
#include <Jolt/Physics/Constraints/FixedConstraint.h>
|
||||||
|
#include <Jolt/Physics/Constraints/HingeConstraint.h>
|
||||||
|
#include <Jolt/Physics/Constraints/PointConstraint.h>
|
||||||
|
|
||||||
#include <Engine/Assets/AssetManager.h>
|
#include <Engine/Assets/AssetManager.h>
|
||||||
#include <Engine/Renderer/Model.h>
|
#include <Engine/Renderer/Model.h>
|
||||||
@@ -331,6 +334,7 @@ namespace Engine {
|
|||||||
void PhysicsWorld::SyncScene() {
|
void PhysicsWorld::SyncScene() {
|
||||||
auto ® = m_Scene.GetRegistry();
|
auto ® = m_Scene.GetRegistry();
|
||||||
auto &bi = m_System->GetBodyInterface();
|
auto &bi = m_System->GetBodyInterface();
|
||||||
|
std::vector<u32> invalidated; // 本帧销毁/重建 body → 约束失效传染
|
||||||
|
|
||||||
// 1. 孤儿清理:实体销毁 / 组件被移除 → 移除 body
|
// 1. 孤儿清理:实体销毁 / 组件被移除 → 移除 body
|
||||||
for (auto it = m_Bodies.begin(); it != m_Bodies.end();) {
|
for (auto it = m_Bodies.begin(); it != m_Bodies.end();) {
|
||||||
@@ -339,6 +343,7 @@ namespace Engine {
|
|||||||
!reg.all_of<ECS::RigidBodyComponent, ECS::TransformComponent>(e)) {
|
!reg.all_of<ECS::RigidBodyComponent, ECS::TransformComponent>(e)) {
|
||||||
bi.RemoveBody(it->second.id);
|
bi.RemoveBody(it->second.id);
|
||||||
bi.DestroyBody(it->second.id);
|
bi.DestroyBody(it->second.id);
|
||||||
|
invalidated.push_back(it->first);
|
||||||
it = m_Bodies.erase(it);
|
it = m_Bodies.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
@@ -355,6 +360,7 @@ namespace Engine {
|
|||||||
bi.RemoveBody(found->second.id);
|
bi.RemoveBody(found->second.id);
|
||||||
bi.DestroyBody(found->second.id);
|
bi.DestroyBody(found->second.id);
|
||||||
m_Bodies.erase(found);
|
m_Bodies.erase(found);
|
||||||
|
invalidated.push_back(bits);
|
||||||
}
|
}
|
||||||
CreateBody(bits, rb, tr);
|
CreateBody(bits, rb, tr);
|
||||||
continue;
|
continue;
|
||||||
@@ -375,6 +381,7 @@ namespace Engine {
|
|||||||
bi.RemoveBody(rec.id);
|
bi.RemoveBody(rec.id);
|
||||||
bi.DestroyBody(rec.id);
|
bi.DestroyBody(rec.id);
|
||||||
m_Bodies.erase(found);
|
m_Bodies.erase(found);
|
||||||
|
invalidated.push_back(bits);
|
||||||
rb.bodyValid = false;
|
rb.bodyValid = false;
|
||||||
CreateBody(bits, rb, tr);
|
CreateBody(bits, rb, tr);
|
||||||
continue;
|
continue;
|
||||||
@@ -396,6 +403,160 @@ namespace Engine {
|
|||||||
rec.lastRot = tr.GetRotationQuat();
|
rec.lastRot = tr.GetRotationQuat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 约束对账(body 循环之后:依赖新建 body 的最终 ID)
|
||||||
|
SyncConstraints(invalidated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 约束对账 ----
|
||||||
|
|
||||||
|
void PhysicsWorld::SyncConstraints(const std::vector<u32> &invalidated) {
|
||||||
|
auto ® = m_Scene.GetRegistry();
|
||||||
|
const auto tainted = [&invalidated](u32 bits) {
|
||||||
|
return std::find(invalidated.begin(), invalidated.end(), bits)
|
||||||
|
!= invalidated.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. owner 实体/组件没了,或任一端 body 本帧被销毁/重建 → 移除
|
||||||
|
for (auto it = m_Constraints.begin(); it != m_Constraints.end();) {
|
||||||
|
const u32 owner = it->first;
|
||||||
|
const auto ownerEnt = static_cast<entt::entity>(owner);
|
||||||
|
const bool ownerGone = !reg.valid(ownerEnt) ||
|
||||||
|
!reg.all_of<ECS::JointComponent>(ownerEnt);
|
||||||
|
if (ownerGone || tainted(owner) || tainted(it->second.targetBits)) {
|
||||||
|
m_System->RemoveConstraint(it->second.constraint.GetPtr());
|
||||||
|
it = m_Constraints.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 遍历组件:快照变更 → 重建;缺失 → 创建。
|
||||||
|
// target 惰性按 UUID 反查(UUID 失联/body 未就绪 → 下帧再试)
|
||||||
|
auto view = reg.view<ECS::JointComponent>();
|
||||||
|
for (auto [e, jc]: view.each()) {
|
||||||
|
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||||
|
const auto found = m_Constraints.find(bits);
|
||||||
|
if (found != m_Constraints.end()) {
|
||||||
|
if (found->second.snapshot == jc) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_System->RemoveConstraint(found->second.constraint.GetPtr());
|
||||||
|
m_Constraints.erase(found);
|
||||||
|
}
|
||||||
|
const ECS::Entity target = m_Scene.FindEntityByUUID(jc.target);
|
||||||
|
if (target == entt::null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const u32 targetBits = static_cast<u32>(entt::to_integral(target));
|
||||||
|
if (bits == targetBits || !m_Bodies.contains(bits) ||
|
||||||
|
!m_Bodies.contains(targetBits)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CreateConstraint(bits, targetBits, jc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsWorld::CreateConstraint(u32 ownerBits, u32 targetBits,
|
||||||
|
const ECS::JointComponent &jc) {
|
||||||
|
auto ® = m_Scene.GetRegistry();
|
||||||
|
const auto &b1 = m_Bodies.find(ownerBits)->second;
|
||||||
|
const auto &b2 = m_Bodies.find(targetBits)->second;
|
||||||
|
|
||||||
|
// 锚点(Transform 局部系)× 本体缩放 → COM 局部系
|
||||||
|
//(EConstraintSpace::LocalToBodyCOM 要求减 Shape::GetCenterOfMass,
|
||||||
|
// Compound 等形状 COM 会偏离原点;Box/Sphere/Capsule 为零)
|
||||||
|
const auto *tr1 = reg.try_get<ECS::TransformComponent>(
|
||||||
|
static_cast<entt::entity>(ownerBits));
|
||||||
|
const auto *tr2 = reg.try_get<ECS::TransformComponent>(
|
||||||
|
static_cast<entt::entity>(targetBits));
|
||||||
|
if (tr1 == nullptr || tr2 == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const glm::vec3 s1 = tr1->GetScale();
|
||||||
|
const glm::vec3 s2 = tr2->GetScale();
|
||||||
|
|
||||||
|
// 双写锁读位姿/COM + 创建(SyncScene 在主线程,与 Update 不并发)
|
||||||
|
JPH::BodyLockWrite lock1(m_System->GetBodyLockInterface(), b1.id);
|
||||||
|
JPH::BodyLockWrite lock2(m_System->GetBodyLockInterface(), b2.id);
|
||||||
|
if (!lock1.Succeeded() || !lock2.Succeeded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JPH::Body &body1 = lock1.GetBody();
|
||||||
|
JPH::Body &body2 = lock2.GetBody();
|
||||||
|
const JPH::Quat r1 = body1.GetRotation();
|
||||||
|
const JPH::Quat r2 = body2.GetRotation();
|
||||||
|
const JPH::Vec3 com1 = body1.GetShape()->GetCenterOfMass();
|
||||||
|
const JPH::Vec3 com2 = body2.GetShape()->GetCenterOfMass();
|
||||||
|
const JPH::RVec3 p1(jc.anchorA.x * s1.x - com1.GetX(),
|
||||||
|
jc.anchorA.y * s1.y - com1.GetY(),
|
||||||
|
jc.anchorA.z * s1.z - com1.GetZ());
|
||||||
|
const JPH::RVec3 p2(jc.anchorB.x * s2.x - com2.GetX(),
|
||||||
|
jc.anchorB.y * s2.y - com2.GetY(),
|
||||||
|
jc.anchorB.z * s2.z - com2.GetZ());
|
||||||
|
|
||||||
|
JPH::Ref<JPH::Constraint> constraint;
|
||||||
|
if (jc.type == ECS::JointComponent::Type::Fixed) {
|
||||||
|
// 轴框架 = 同一世界姿态在各体局部系的表达 → 锁定创建时相对姿态
|
||||||
|
JPH::FixedConstraintSettings s;
|
||||||
|
s.mSpace = JPH::EConstraintSpace::LocalToBodyCOM;
|
||||||
|
s.mPoint1 = p1;
|
||||||
|
s.mPoint2 = p2;
|
||||||
|
s.mAxisX1 = r1.Conjugated() * JPH::Vec3::sAxisX();
|
||||||
|
s.mAxisY1 = r1.Conjugated() * JPH::Vec3::sAxisY();
|
||||||
|
s.mAxisX2 = r2.Conjugated() * JPH::Vec3::sAxisX();
|
||||||
|
s.mAxisY2 = r2.Conjugated() * JPH::Vec3::sAxisY();
|
||||||
|
constraint = s.Create(body1, body2);
|
||||||
|
} else if (jc.type == ECS::JointComponent::Type::Hinge) {
|
||||||
|
// 转轴:本体局部轴 → 世界(不吃缩放,归一化),再投到两体局部系;
|
||||||
|
// 法线任取与轴垂直的世界方向,保证两体框架共轴共面
|
||||||
|
JPH::HingeConstraintSettings s;
|
||||||
|
s.mSpace = JPH::EConstraintSpace::LocalToBodyCOM;
|
||||||
|
s.mPoint1 = p1;
|
||||||
|
s.mPoint2 = p2;
|
||||||
|
const JPH::Vec3 localAxis = JPH::Vec3(
|
||||||
|
jc.hingeAxis.x, jc.hingeAxis.y, jc.hingeAxis.z).Normalized();
|
||||||
|
const JPH::Vec3 worldAxis = r1 * localAxis;
|
||||||
|
JPH::Vec3 helper = std::abs(worldAxis.GetY()) < 0.99f
|
||||||
|
? JPH::Vec3::sAxisY()
|
||||||
|
: JPH::Vec3::sAxisX();
|
||||||
|
const JPH::Vec3 worldNormal = worldAxis.Cross(helper).Normalized();
|
||||||
|
s.mHingeAxis1 = r1.Conjugated() * worldAxis;
|
||||||
|
s.mNormalAxis1 = r1.Conjugated() * worldNormal;
|
||||||
|
s.mHingeAxis2 = r2.Conjugated() * worldAxis;
|
||||||
|
s.mNormalAxis2 = r2.Conjugated() * worldNormal;
|
||||||
|
if (jc.enableLimit) {
|
||||||
|
s.mLimitsMin = std::clamp(glm::radians(jc.limitMin),
|
||||||
|
-JPH::JPH_PI, 0.0f);
|
||||||
|
s.mLimitsMax = std::clamp(glm::radians(jc.limitMax),
|
||||||
|
0.0f, JPH::JPH_PI);
|
||||||
|
}
|
||||||
|
constraint = s.Create(body1, body2);
|
||||||
|
} else {
|
||||||
|
// Ball = PointConstraint(球窝:三旋转自由度全放开)
|
||||||
|
JPH::PointConstraintSettings s;
|
||||||
|
s.mSpace = JPH::EConstraintSpace::LocalToBodyCOM;
|
||||||
|
s.mPoint1 = p1;
|
||||||
|
s.mPoint2 = p2;
|
||||||
|
constraint = s.Create(body1, body2);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_System->AddConstraint(constraint.GetPtr());
|
||||||
|
|
||||||
|
ConstraintRecord rec;
|
||||||
|
rec.constraint = std::move(constraint);
|
||||||
|
rec.snapshot = jc;
|
||||||
|
rec.targetBits = targetBits;
|
||||||
|
m_Constraints[ownerBits] = rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsWorld::RemoveConstraint(const u32 ownerBits) {
|
||||||
|
const auto found = m_Constraints.find(ownerBits);
|
||||||
|
if (found == m_Constraints.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_System->RemoveConstraint(found->second.constraint.GetPtr());
|
||||||
|
m_Constraints.erase(found); // Ref 析构 → 引用计数释放
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Mesh 碰撞体:视觉网格 → Jolt 形状 ----
|
// ---- Mesh 碰撞体:视觉网格 → Jolt 形状 ----
|
||||||
|
|||||||
@@ -307,6 +307,27 @@ namespace Engine::Serialization {
|
|||||||
out << YAML::EndMap;
|
out << YAML::EndMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto* jc = reg.try_get<ECS::JointComponent>(e)) {
|
||||||
|
out << YAML::Key << "Joint" << YAML::Value
|
||||||
|
<< YAML::BeginMap;
|
||||||
|
out << YAML::Key << "Type" << YAML::Value
|
||||||
|
<< static_cast<int>(jc->type);
|
||||||
|
// 实体引用存 UUID(跨存档稳定;target=0 未设置)
|
||||||
|
out << YAML::Key << "Target" << YAML::Value
|
||||||
|
<< jc->target.Value();
|
||||||
|
out << YAML::Key << "AnchorA" << YAML::Value;
|
||||||
|
EmitVec3(out, jc->anchorA);
|
||||||
|
out << YAML::Key << "AnchorB" << YAML::Value;
|
||||||
|
EmitVec3(out, jc->anchorB);
|
||||||
|
out << YAML::Key << "HingeAxis" << YAML::Value;
|
||||||
|
EmitVec3(out, jc->hingeAxis);
|
||||||
|
out << YAML::Key << "EnableLimit" << YAML::Value
|
||||||
|
<< jc->enableLimit;
|
||||||
|
out << YAML::Key << "LimitMin" << YAML::Value << jc->limitMin;
|
||||||
|
out << YAML::Key << "LimitMax" << YAML::Value << jc->limitMax;
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
out << YAML::EndMap; // Components
|
out << YAML::EndMap; // Components
|
||||||
out << YAML::EndMap; // Entity
|
out << YAML::EndMap; // Entity
|
||||||
}
|
}
|
||||||
@@ -562,6 +583,24 @@ namespace Engine::Serialization {
|
|||||||
rb.isSensor = n["IsSensor"].as<bool>(rb.isSensor);
|
rb.isSensor = n["IsSensor"].as<bool>(rb.isSensor);
|
||||||
// bodyId/bodyValid/velocity 保持默认(对账重建)
|
// bodyId/bodyValid/velocity 保持默认(对账重建)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Joint:target 只存 UUID,加载后由 PhysicsWorld 惰性反查
|
||||||
|
//(两遍解析不需要——UUID 再生/加载顺序均由逐帧解析消化)
|
||||||
|
if (const YAML::Node n = comps["Joint"]) {
|
||||||
|
auto& jc = handle.AddComponent<ECS::JointComponent>();
|
||||||
|
if (const int type = n["Type"].as<int>(0);
|
||||||
|
type >= 0 && type <= 2) {
|
||||||
|
jc.type =
|
||||||
|
static_cast<ECS::JointComponent::Type>(type);
|
||||||
|
}
|
||||||
|
jc.target = UUID{n["Target"].as<u64>(0)};
|
||||||
|
jc.anchorA = ReadVec3(n["AnchorA"], jc.anchorA);
|
||||||
|
jc.anchorB = ReadVec3(n["AnchorB"], jc.anchorB);
|
||||||
|
jc.hingeAxis = ReadVec3(n["HingeAxis"], jc.hingeAxis);
|
||||||
|
jc.enableLimit = n["EnableLimit"].as<bool>(jc.enableLimit);
|
||||||
|
jc.limitMin = n["LimitMin"].as<f32>(jc.limitMin);
|
||||||
|
jc.limitMax = n["LimitMax"].as<f32>(jc.limitMax);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
||||||
|
|||||||
Reference in New Issue
Block a user