physics: capsule/cylinder/compound collider shapes

- ColliderType + Capsule/Cylinder/Compound; CompoundChild list (Box/
  Sphere/Capsule, local pos/quat, non-recursive)
- CreateBody: CapsuleShape/CylinderShape (ctor param orders differ) +
  StaticCompoundShape; empty/failed compound falls back to Box
- BodyRecord caches radius/halfHeight/compound; SyncScene rebuilds on
  change (per-collider param extraction)
- Inspector: combo entries + shape params + compound child list editor
- Serialization: new fields with as<T> fallback, compound seq round-trip
This commit is contained in:
2026-10-03 16:17:44 +08:00
parent 6c81bfd70d
commit a744b332eb
5 changed files with 245 additions and 10 deletions
+97 -5
View File
@@ -16,6 +16,9 @@
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Renderer/Model.h>
@@ -83,6 +86,26 @@ namespace Engine {
glm::quat ToGlm(const JPH::Quat &q) {
return {q.GetW(), q.GetX(), q.GetY(), q.GetZ()};
}
// 变更检测快照取值:collider 决定哪组参数生效(collider 本身变更
// 在外层先触发重建,到这里时语义已对齐)。Box/Mesh/Compound 不用
void ExtractShapeParams(const ECS::RigidBodyComponent &rb,
f32 &radius, f32 &halfHeight) {
using CT = ECS::RigidBodyComponent::ColliderType;
switch (rb.collider) {
case CT::Sphere: radius = rb.sphereRadius; break;
case CT::Capsule: radius = rb.capsuleRadius;
halfHeight = rb.capsuleHalfHeight; break;
case CT::Cylinder: radius = rb.cylinderRadius;
halfHeight = rb.cylinderHalfHeight; break;
default: break;
}
}
// 球/胶囊/圆柱类径向形状的缩放取 max 轴(不可各向异性)
f32 UniformScale(const glm::vec3 &s) {
return std::max({s.x, s.y, s.z});
}
} // namespace
// ---- 碰撞事件监听:回调在 Update 内多线程并发,只读 body、加锁入队 ----
@@ -338,10 +361,14 @@ namespace Engine {
}
auto &rec = found->second; // 非 const:静态/运动体拖动时更新 lastPos/lastRot
f32 radius = 0.0f, halfHeight = 0.0f;
ExtractShapeParams(rb, radius, halfHeight);
if (rec.type != static_cast<u8>(rb.type) ||
rec.collider != static_cast<u8>(rb.collider) ||
rec.colliderSize != rb.boxHalfExtents ||
rec.radius != rb.sphereRadius ||
rec.radius != radius ||
rec.halfHeight != halfHeight ||
rec.compound != rb.compound ||
rec.mass != rb.mass ||
rec.isSensor != rb.isSensor) {
// 类型/碰撞体/质量变更 → 重建(简单正确,重置速度可接受)
@@ -444,16 +471,80 @@ namespace Engine {
return res.Get();
}
JPH::RefConst<JPH::Shape> PhysicsWorld::BuildCompoundShape(
ECS::RigidBodyComponent &rb, ECS::TransformComponent &tr) {
if (rb.compound.empty()) {
return nullptr;
}
// 缩放烘焙:child 局部位置按轴缩放,尺寸按 max 轴(球/胶囊径向
// 不可各向异性)。rotation 存数据与序列化(首版 Inspector 不编辑)
const glm::vec3 s = tr.GetScale();
JPH::StaticCompoundShapeSettings cs;
for (const auto &child: rb.compound) {
JPH::RefConst<JPH::Shape> sub;
const f32 us = UniformScale(s);
switch (child.shape) {
case ECS::RigidBodyComponent::CompoundChild::Shape::Sphere:
sub = new JPH::SphereShape(
std::max(child.radius * us, 0.01f));
break;
case ECS::RigidBodyComponent::CompoundChild::Shape::Capsule:
sub = new JPH::CapsuleShape(
std::max(child.radius * us, 0.01f),
std::max(child.capsuleHalfHeight * us, 0.01f));
break;
default: // Box
sub = new JPH::BoxShape(JPH::Vec3(
std::max(child.boxHalfExtents.x * s.x, 0.01f),
std::max(child.boxHalfExtents.y * s.y, 0.01f),
std::max(child.boxHalfExtents.z * s.z, 0.01f)));
break;
}
cs.AddShape(JPH::Vec3(child.position.x * s.x,
child.position.y * s.y,
child.position.z * s.z),
ToJolt(child.rotation), sub);
}
const JPH::Shape::ShapeResult res = cs.Create();
if (res.HasError()) {
ENGINE_WARN("[Physics] CompoundShape build failed: {}",
res.GetError().c_str());
return nullptr;
}
return res.Get();
}
void PhysicsWorld::CreateBody(u32 entityBits, ECS::RigidBodyComponent &rb,
ECS::TransformComponent &tr) {
auto &bi = m_System->GetBodyInterface();
// 碰撞体:组件显式尺寸 × Transform 缩放(形状按单位尺度烹饪 +
// ScaledShape 的优化留给 ShapeCache 阶段;MVP 直接带缩放建形状)
// ScaledShape 的优化留给 ShapeCache 阶段;MVP 直接带缩放建形状)。
// 注意 Jolt 两 ctor 参数序相反:CapsuleShape(半径, 圆柱段半高)、
// CylinderShape(圆柱段半高, 半径)
JPH::RefConst<JPH::Shape> shape;
const f32 uniformScale = UniformScale(tr.GetScale());
if (rb.collider == ECS::RigidBodyComponent::ColliderType::Sphere) {
const f32 scale = std::max({tr.GetScale().x, tr.GetScale().y, tr.GetScale().z});
shape = new JPH::SphereShape(std::max(rb.sphereRadius * scale, 0.01f));
shape = new JPH::SphereShape(std::max(rb.sphereRadius * uniformScale, 0.01f));
} else if (rb.collider == ECS::RigidBodyComponent::ColliderType::Capsule) {
shape = new JPH::CapsuleShape(
std::max(rb.capsuleRadius * uniformScale, 0.01f),
std::max(rb.capsuleHalfHeight * uniformScale, 0.01f));
} else if (rb.collider == ECS::RigidBodyComponent::ColliderType::Cylinder) {
shape = new JPH::CylinderShape(
std::max(rb.cylinderHalfHeight * uniformScale, 0.01f),
std::max(rb.cylinderRadius * uniformScale, 0.01f));
} else if (rb.collider == ECS::RigidBodyComponent::ColliderType::Compound) {
// 子形状列表 → StaticCompoundShape;空列表/构建失败回退 Box
shape = BuildCompoundShape(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)));
}
} else if (rb.collider == ECS::RigidBodyComponent::ColliderType::Mesh) {
// Mesh 碰撞体:实体 MeshRenderer 的视觉网格(Unity MeshCollider
// 语义)。缩放烘进顶点(Jolt shape 无缩放);Static → MeshShape
@@ -507,7 +598,8 @@ namespace Engine {
rec.type = static_cast<u8>(rb.type);
rec.collider = static_cast<u8>(rb.collider);
rec.colliderSize = rb.boxHalfExtents;
rec.radius = rb.sphereRadius;
ExtractShapeParams(rb, rec.radius, rec.halfHeight);
rec.compound = rb.compound;
rec.mass = rb.mass;
rec.isSensor = rb.isSensor;
rec.lastPos = tr.GetPosition();