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
@@ -307,6 +307,27 @@ namespace Engine::Serialization {
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; // Entity
}
@@ -562,6 +583,24 @@ namespace Engine::Serialization {
rb.isSensor = n["IsSensor"].as<bool>(rb.isSensor);
// 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: