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:
@@ -19,6 +19,9 @@
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CylinderShape.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/Renderer/Model.h>
|
||||
@@ -331,6 +334,7 @@ namespace Engine {
|
||||
void PhysicsWorld::SyncScene() {
|
||||
auto ® = m_Scene.GetRegistry();
|
||||
auto &bi = m_System->GetBodyInterface();
|
||||
std::vector<u32> invalidated; // 本帧销毁/重建 body → 约束失效传染
|
||||
|
||||
// 1. 孤儿清理:实体销毁 / 组件被移除 → 移除 body
|
||||
for (auto it = m_Bodies.begin(); it != m_Bodies.end();) {
|
||||
@@ -339,6 +343,7 @@ namespace Engine {
|
||||
!reg.all_of<ECS::RigidBodyComponent, ECS::TransformComponent>(e)) {
|
||||
bi.RemoveBody(it->second.id);
|
||||
bi.DestroyBody(it->second.id);
|
||||
invalidated.push_back(it->first);
|
||||
it = m_Bodies.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
@@ -355,6 +360,7 @@ namespace Engine {
|
||||
bi.RemoveBody(found->second.id);
|
||||
bi.DestroyBody(found->second.id);
|
||||
m_Bodies.erase(found);
|
||||
invalidated.push_back(bits);
|
||||
}
|
||||
CreateBody(bits, rb, tr);
|
||||
continue;
|
||||
@@ -375,6 +381,7 @@ namespace Engine {
|
||||
bi.RemoveBody(rec.id);
|
||||
bi.DestroyBody(rec.id);
|
||||
m_Bodies.erase(found);
|
||||
invalidated.push_back(bits);
|
||||
rb.bodyValid = false;
|
||||
CreateBody(bits, rb, tr);
|
||||
continue;
|
||||
@@ -396,6 +403,160 @@ namespace Engine {
|
||||
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 形状 ----
|
||||
|
||||
Reference in New Issue
Block a user