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:
2026-10-03 16:26:20 +08:00
parent a744b332eb
commit 82910c41e8
5 changed files with 296 additions and 0 deletions
+23
View File
@@ -236,6 +236,29 @@ namespace Engine::ECS {
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
#endif //HELLOVULKAN_COMPONENTS_H
@@ -30,6 +30,7 @@
#include <Jolt/Jolt.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Body/BodyID.h>
#include <Jolt/Physics/Constraints/Constraint.h> // JPH::Ref<Constraint>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Core/TempAllocator.h>
@@ -114,6 +115,12 @@ namespace Engine {
void CollectDebugData(); // 按 m_DebugBits 收集调试图元(渲染帧率)
void CreateBody(u32 entityBits, ECS::RigidBodyComponent& rb,
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 /
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
JPH::RefConst<JPH::Shape> BuildMeshShape(u32 entityBits,
@@ -128,6 +135,15 @@ namespace Engine {
void QueueEvent(CollisionEvent&& ev); // 回调线程 append(多线程并发,加锁)
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;
Assets::AssetManager* m_Assets = nullptr; // Mesh 碰撞体解析 modelRef
bool m_Enabled = true;