Files
HelloVulkan/Engine/src/Physics/PhysicsWorld.cpp
T
atdunbg e1cef53158 physics: character controller (Jolt CharacterVirtual)
- CharacterControllerComponent: capsule params (radius/halfHeight, mass,
  max slope, step height); entity origin = feet via shape offset
- CharacterController per-entity wrapper: gravity integration + grounded
  clamp, ExtendedUpdate (sweep/slide + stick-to-floor + walk-stairs),
  SetLinearVelocity/Jump/IsGrounded facade
- PhysicsWorld: SyncCharacters reconcile (create/rebuild on param
  change, editor-drag teleport), UpdateCharacters after physics Update
  (Jolt recommended order), writes back to Transform
- Inspector: params + grounded/velocity telemetry + jump debug button
- Serialization: params only, state rebuilt by reconcile
2026-10-03 16:34:20 +08:00

1018 lines
44 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// PhysicsWorld.cpp:Jolt 后端实现。层配置/类型注册/对账/写回。
//
#include <Engine/Physics/PhysicsWorld.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Components.h>
#include <Engine/ECS/Scene.h>
#include <Jolt/RegisterTypes.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyInterface.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#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 <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>
#include <glm/gtc/quaternion.hpp>
#include <Jolt/Physics/Collision/ContactListener.h>
#include <Jolt/Physics/Collision/NarrowPhaseQuery.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Body/BodyLock.h>
#include <glm/gtc/quaternion.hpp>
#include <algorithm>
#include <mutex>
#include <thread>
namespace Engine {
namespace {
// ---- 层配置:MVP 两层(非移动/移动),扩软体/传感器时再分层 ----
constexpr JPH::ObjectLayer LAYER_NON_MOVING{0};
constexpr JPH::ObjectLayer LAYER_MOVING{1};
constexpr JPH::BroadPhaseLayer BP_NON_MOVING{0};
constexpr JPH::BroadPhaseLayer BP_MOVING{1};
// ObjectLayer → BroadPhaseLayer 映射
class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface {
public:
JPH::uint GetNumBroadPhaseLayers() const override { return 2; }
JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer layer) const override {
return layer == LAYER_NON_MOVING ? BP_NON_MOVING : BP_MOVING;
}
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
const char *GetBroadPhaseLayerName(JPH::BroadPhaseLayer) const override {
return "BP";
}
#endif
};
class ObjectVsBPLayerFilterImpl final : public JPH::ObjectVsBroadPhaseLayerFilter {
public:
bool ShouldCollide(JPH::ObjectLayer, JPH::BroadPhaseLayer) const override {
return true; // MVP 全放行
}
};
class ObjectLayerPairFilterImpl final : public JPH::ObjectLayerPairFilter {
public:
bool ShouldCollide(JPH::ObjectLayer a, JPH::ObjectLayer b) const override {
switch (a) {
case LAYER_NON_MOVING: return b == LAYER_MOVING;
case LAYER_MOVING: return true; // 动体与静/动都碰
default: return false;
}
}
};
// Jolt ↔ glm 四元数直转(TransformComponent 旋转存 glm::quat,
// UE FTransform 语义):物理同步/写回不再经欧拉往返 → 姿态
// 零漂移(分量序:Jolt ctor (x,y,z,w),glm ctor (w,x,y,z))
JPH::Quat ToJolt(const glm::quat &q) {
return {q.x, q.y, q.z, q.w};
}
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、加锁入队 ----
class PhysicsWorld::ContactListenerImpl final : public JPH::ContactListener {
public:
explicit ContactListenerImpl(PhysicsWorld &owner) : m_Owner(owner) {}
void OnContactAdded(const JPH::Body &b1, const JPH::Body &b2,
const JPH::ContactManifold &manifold,
JPH::ContactSettings &) override {
Record(CollisionEvent::Kind::Added, b1, b2, manifold);
}
void OnContactPersisted(const JPH::Body &b1, const JPH::Body &b2,
const JPH::ContactManifold &manifold,
JPH::ContactSettings &) override {
Record(CollisionEvent::Kind::Persisted, b1, b2, manifold);
}
void OnContactRemoved(const JPH::SubShapeIDPair &pair) override {
// body 可能已销毁(GetUserData 不可用)→ BodyID 反查实体;
// 主线程阻塞在 Update 内,m_Bodies 此刻稳定
u32 a = 0, b = 0;
if (!m_Owner.TryBitsByBodyId(pair.GetBody1ID(), a) ||
!m_Owner.TryBitsByBodyId(pair.GetBody2ID(), b)) {
return; // 一端已随实体移除,事件无意义
}
m_Owner.QueueEvent({a, b, CollisionEvent::Kind::Removed, {}, {}});
}
private:
void Record(CollisionEvent::Kind kind, const JPH::Body &b1,
const JPH::Body &b2, const JPH::ContactManifold &manifold) {
CollisionEvent ev;
ev.kind = kind;
ev.a = static_cast<u32>(b1.GetUserData());
ev.b = static_cast<u32>(b2.GetUserData());
if (!manifold.mRelativeContactPointsOn1.empty()) {
const JPH::RVec3 p = manifold.GetWorldSpaceContactPointOn1(0);
ev.point = {p.GetX(), p.GetY(), p.GetZ()};
}
const JPH::Vec3 n = manifold.mWorldSpaceNormal;
ev.normal = {n.GetX(), n.GetY(), n.GetZ()};
m_Owner.QueueEvent(std::move(ev));
}
PhysicsWorld &m_Owner;
};
// ---- 生命周期 ----
PhysicsWorld::PhysicsWorld(ECS::Scene &scene, Assets::AssetManager *assets)
: m_Scene(scene), m_Assets(assets) {
static std::once_flag s_JoltOnce;
std::call_once(s_JoltOnce, [] {
JPH::RegisterDefaultAllocator();
JPH::Factory::sInstance = new JPH::Factory();
JPH::RegisterTypes();
});
// 调试收集器必须在分配器注册之后构造:DebugRendererSimple 构造即
// Initialize() 预烘焙通用几何(STLAllocator 走 JPH 函数指针),若作
// 直接成员会先于 call_once 构造 → 函数指针未注册 → SIGSEGV at 0x0
m_DebugDraw = CreateScope<PhysicsDebugDraw>();
m_TempAllocator = CreateScope<JPH::TempAllocatorImpl>(16 * 1024 * 1024);
const u32 threads = std::max(1u, std::thread::hardware_concurrency() - 1);
m_JobSystem = CreateScope<JPH::JobSystemThreadPool>(
JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers, static_cast<int>(threads));
m_BPLayerInterface = CreateScope<BPLayerInterfaceImpl>();
m_ObjectVsBPLayerFilter = CreateScope<ObjectVsBPLayerFilterImpl>();
m_ObjectVsObjectLayerFilter = CreateScope<ObjectLayerPairFilterImpl>();
m_System = CreateScope<JPH::PhysicsSystem>();
m_System->Init(4096, 0, 16384, 4096,
*m_BPLayerInterface, *m_ObjectVsBPLayerFilter,
*m_ObjectVsObjectLayerFilter);
m_System->SetGravity(JPH::Vec3(0.0f, -9.81f, 0.0f));
// 碰撞事件监听(ctor 不走 JPH 分配器,仍保持"分配器后构造"纪律)
m_ContactListener = CreateScope<ContactListenerImpl>(*this);
m_System->SetContactListener(m_ContactListener.get());
#ifdef JPH_DEBUG_RENDERER
// 调试收集器安装为 Jolt 全局渲染后端(DrawBodies/约束/软体全走这里)
JPH::DebugRenderer::sInstance = m_DebugDraw.get();
#endif
ENGINE_INFO("PhysicsWorld initialized (Jolt {} threads)", threads);
}
PhysicsWorld::~PhysicsWorld() {
// 先析构调试收集器:~DebugRenderer 会断言 sInstance==this 并清空、
// 释放烘焙几何——必须先于 UnregisterTypes
m_DebugDraw.reset();
m_Bodies.clear();
m_ContactListener.reset(); // 监听器先于 system 析构(持 m_Owner 引用)
m_System.reset();
// 单世界假设:进程退出时统一反注册(多世界时改为引用计数)
JPH::UnregisterTypes();
delete JPH::Factory::sInstance;
JPH::Factory::sInstance = nullptr;
}
// ---- 步进:定步长 accumulator ----
void PhysicsWorld::Step(f32 dt) {
if (!m_Enabled) {
m_Accumulator = 0.0f;
m_DebugDraw->Frame().Clear(); // 物理关:清残留图元(防旧帧闪烁)
m_PendingEvents.clear();
return;
}
// 事件队列每渲染帧清一次 → GetEvents 语义 = 最近一次步进的事件
m_PendingEvents.clear();
SyncScene(); // 每帧对账(编辑器改参数/加删实体即时生效)
constexpr f32 kFixedDt = 1.0f / 60.0f;
constexpr int kMaxSubSteps = 4;
m_Accumulator += dt;
int steps = static_cast<int>(m_Accumulator / kFixedDt);
if (steps > 0) {
steps = std::min(steps, kMaxSubSteps);
m_Accumulator -= static_cast<f32>(steps) * kFixedDt;
// 丢弃多余积压(卡顿后不追帧,防螺旋死亡)
m_Accumulator = std::min(m_Accumulator, kFixedDt);
m_System->Update(kFixedDt, steps, m_TempAllocator.get(), m_JobSystem.get());
Writeback();
UpdateCharacters(kFixedDt); // 角色扫掠(Jolt 官方建议:物理更新后)
UpdateAccels(kFixedDt); // 速度差分 → 加速度(step 率采样一次)
}
// 调试收集按渲染帧率跑(无步进帧也重画,箭头不闪);全关时清空
if (m_DebugBits != 0) {
CollectDebugData();
} else {
m_DebugDraw->Frame().Clear();
}
}
// ---- 物理调试可视化 ----
void PhysicsWorld::UpdateAccels(f32 dt) {
auto &bi = m_System->GetBodyInterface();
for (auto &[bits, rec]: m_Bodies) {
if (rec.type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic)) {
continue;
}
const JPH::Vec3 v = bi.GetLinearVelocity(rec.id);
const glm::vec3 gv(v.GetX(), v.GetY(), v.GetZ());
const glm::vec3 instAccel = (gv - rec.prevVel) / dt;
// 轻度 EMA:积分噪声被滤掉,碰撞冲量的单步尖峰仍然保留
rec.lastAccel = glm::mix(rec.lastAccel, instAccel, 0.35f);
rec.prevVel = gv;
}
}
void PhysicsWorld::CollectDebugData() {
m_DebugDraw->BeginCollect();
// 1) Jolt 原生:collider 线框 + 速度箭头(软体 mDrawSoftBody* 后续加这里)
if ((m_DebugBits & (kPhyDbgColliders | kPhyDbgVelocity)) != 0) {
JPH::BodyManager::DrawSettings ds;
ds.mDrawShape = (m_DebugBits & kPhyDbgColliders) != 0;
ds.mDrawShapeWireframe = true;
ds.mDrawVelocity = (m_DebugBits & kPhyDbgVelocity) != 0;
m_System->DrawBodies(ds, m_DebugDraw.get());
}
// 2) 约束(关节;软体/载具约束接入后自动生效)
if ((m_DebugBits & kPhyDbgConstraints) != 0) {
m_System->DrawConstraints(m_DebugDraw.get());
}
// 3) 手绘箭头(Jolt 无现成 API):重力 = 常量方向指示(长度固定),
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)
auto &bi = m_System->GetBodyInterface();
const JPH::Vec3 gravity = m_System->GetGravity();
constexpr f32 kGravityLen = 1.0f; // 世界单位
constexpr f32 kAccelScale = 0.05f;
constexpr f32 kAccelMaxLen = 2.0f;
const JPH::Color kGravityColor(220, 60, 60); // 红:重力
const JPH::Color kAccelColor(255, 170, 40); // 橙:加速度
for (auto &[bits, rec]: m_Bodies) {
if (rec.type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic)) {
continue;
}
const JPH::RVec3 com = bi.GetCenterOfMassPosition(rec.id);
if ((m_DebugBits & kPhyDbgGravity) != 0) {
const JPH::Vec3 g = gravity * bi.GetGravityFactor(rec.id);
const f32 len = g.Length();
if (len > 1e-4f) {
const JPH::RVec3 tip = com + JPH::RVec3(g * (kGravityLen / len));
m_DebugDraw->DrawArrow(com, tip, kGravityColor, kGravityLen * 0.12f);
}
}
if ((m_DebugBits & kPhyDbgAccel) != 0) {
const f32 mag = glm::length(rec.lastAccel);
if (mag > 0.05f) {
const glm::vec3 dir = rec.lastAccel / mag;
const f32 len = std::min(mag * kAccelScale, kAccelMaxLen);
const JPH::RVec3 tip =
com + JPH::RVec3(dir.x, dir.y, dir.z) * len;
m_DebugDraw->DrawArrow(
com, tip, kAccelColor, std::max(len * 0.15f, 0.05f));
}
}
}
// 释放上一帧缓存不再引用的通用几何 batch(DebugRenderer 内部池)
m_DebugDraw->NextFrame();
}
// ---- 对账 ----
void PhysicsWorld::SyncScene() {
auto &reg = 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();) {
const auto e = static_cast<entt::entity>(it->first);
if (!reg.valid(e) ||
!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;
}
}
// 2. 遍历:缺失 → 创建;参数变更 → 重建;静态/运动学拖动 → 同步进 body
auto view = reg.view<ECS::RigidBodyComponent, ECS::TransformComponent>();
for (auto [e, rb, tr]: view.each()) {
const u32 bits = static_cast<u32>(entt::to_integral(e));
const auto found = m_Bodies.find(bits);
if (found == m_Bodies.end() || !rb.bodyValid) {
if (found != m_Bodies.end()) {
bi.RemoveBody(found->second.id);
bi.DestroyBody(found->second.id);
m_Bodies.erase(found);
invalidated.push_back(bits);
}
CreateBody(bits, rb, tr);
continue;
}
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 != radius ||
rec.halfHeight != halfHeight ||
rec.compound != rb.compound ||
rec.mass != rb.mass ||
rec.isSensor != rb.isSensor) {
// 类型/碰撞体/质量变更 → 重建(简单正确,重置速度可接受)
bi.RemoveBody(rec.id);
bi.DestroyBody(rec.id);
m_Bodies.erase(found);
invalidated.push_back(bits);
rb.bodyValid = false;
CreateBody(bits, rb, tr);
continue;
}
const JPH::BodyID id(rec.id);
bi.SetFriction(id, rb.friction);
bi.SetRestitution(id, rb.restitution);
if (rb.type == ECS::RigidBodyComponent::BodyType::Dynamic) {
bi.SetGravityFactor(id, rb.gravityScale);
} else if (rec.lastPos != tr.GetPosition() ||
rec.lastRot != tr.GetRotationQuat()) {
// 编辑器拖动静态/运动学体 → 传送 body
bi.SetPositionAndRotation(id,
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
ToJolt(tr.GetRotationQuat()),
JPH::EActivation::DontActivate);
rec.lastPos = tr.GetPosition();
rec.lastRot = tr.GetRotationQuat();
}
}
// 3. 约束对账(body 循环之后:依赖新建 body 的最终 ID)
SyncConstraints(invalidated);
// 4. 角色对账(非刚体,不依赖 body 生命周期)
SyncCharacters();
}
// ---- 约束对账 ----
void PhysicsWorld::SyncConstraints(const std::vector<u32> &invalidated) {
auto &reg = 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 &reg = 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 析构 → 引用计数释放
}
// ---- 角色控制器 ----
void PhysicsWorld::SyncCharacters() {
auto &reg = m_Scene.GetRegistry();
// 1. 孤儿清理:实体销毁 / 组件被移除(Ref 析构即清理,无 body 负担)
for (auto it = m_Characters.begin(); it != m_Characters.end();) {
const auto e = static_cast<entt::entity>(it->first);
if (!reg.valid(e) ||
!reg.all_of<ECS::CharacterControllerComponent,
ECS::TransformComponent>(e)) {
it = m_Characters.erase(it);
} else {
++it;
}
}
// 2. 遍历:缺失 → 创建;参数变更 → 重建;编辑器拖动 → 传送
auto view = reg.view<ECS::CharacterControllerComponent,
ECS::TransformComponent>();
for (auto [e, cc, tr]: view.each()) {
const u32 bits = static_cast<u32>(entt::to_integral(e));
CharacterController::Params p;
p.radius = cc.radius;
p.halfHeight = cc.capsuleHalfHeight;
p.mass = cc.mass;
p.maxSlopeAngle = cc.maxSlopeAngle;
p.stepHeight = cc.stepHeight;
auto found = m_Characters.find(bits);
if (found == m_Characters.end()) {
CharacterEntry entry;
entry.cc = std::make_unique<CharacterController>(
*m_System, p, tr.GetPosition());
entry.lastPos = tr.GetPosition();
m_Characters.emplace(bits, std::move(entry));
continue;
}
if (found->second.cc->GetParams() != p) {
// 参数变更 → 重建(位置沿用当前 Transform)
found->second.cc = std::make_unique<CharacterController>(
*m_System, p, tr.GetPosition());
found->second.lastPos = tr.GetPosition();
continue;
}
if (found->second.lastPos != tr.GetPosition()) {
// 编辑器拖动实体 → 传送角色(角色是唯一位置源,反向覆盖)
found->second.cc->Teleport(tr.GetPosition());
found->second.lastPos = tr.GetPosition();
}
}
}
void PhysicsWorld::UpdateCharacters(const f32 dt) {
auto &reg = m_Scene.GetRegistry();
const JPH::Vec3 gravity = m_System->GetGravity();
for (auto &[bits, entry]: m_Characters) {
auto *tr = reg.try_get<ECS::TransformComponent>(
static_cast<entt::entity>(bits));
if (tr == nullptr) {
continue;
}
entry.cc->Update(dt, gravity, *m_TempAllocator);
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底
tr->SetPosition(p); // 标脏 → UpdateTransforms 重算 world
entry.lastPos = p;
}
}
// ---- Mesh 碰撞体:视觉网格 → Jolt 形状 ----
JPH::RefConst<JPH::Shape> PhysicsWorld::BuildMeshShape(
u32 entityBits, ECS::RigidBodyComponent &rb, ECS::TransformComponent &tr) {
// 同实体 MeshRenderer 提供网格;缺失 → 回退(CreateBody 判 null 走 Box)
auto *mc = m_Scene.GetRegistry().try_get<ECS::MeshRendererComponent>(
static_cast<entt::entity>(entityBits));
if (m_Assets == nullptr || mc == nullptr) {
return nullptr;
}
const Model *model = m_Assets->GetModel(mc->model);
if (model == nullptr) {
return nullptr;
}
// 缩放烘进顶点:Jolt shape 无缩放语义,body 只吃 pos/rot
const glm::vec3 s = tr.GetScale();
// 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动)
if (rb.type != ECS::RigidBodyComponent::BodyType::Static) {
JPH::ConvexHullShapeSettings cs;
for (const auto &mesh: model->GetMeshes()) {
for (const auto &p: mesh->GetCpuPositions()) {
cs.mPoints.emplace_back(p.x * s.x, p.y * s.y, p.z * s.z);
}
}
if (cs.mPoints.size() < 4) {
return nullptr; // 凸包至少 4 点
}
const JPH::Shape::ShapeResult res = cs.Create();
if (res.HasError()) {
ENGINE_WARN("[Physics] ConvexHull build failed: {}",
res.GetError().c_str());
return nullptr;
}
return res.Get();
}
// 静态体 → MeshShape 精确三角网格(各 mesh 顶点拼接,索引加基址偏移)
JPH::VertexList verts;
JPH::IndexedTriangleList tris;
u32 base = 0;
for (const auto &mesh: model->GetMeshes()) {
const auto &pos = mesh->GetCpuPositions();
const auto &idx = mesh->GetCpuIndices();
verts.reserve(verts.size() + pos.size());
for (const auto &p: pos) {
verts.push_back(JPH::Float3(p.x * s.x, p.y * s.y, p.z * s.z));
}
tris.reserve(tris.size() + idx.size() / 3);
for (size_t i = 0; i + 2 < idx.size(); i += 3) {
// 退化三角(两索引相同)剔除:MeshShape 构建会因退化报错
if (idx[i] != idx[i + 1] && idx[i] != idx[i + 2] &&
idx[i + 1] != idx[i + 2]) {
tris.emplace_back(idx[i], idx[i + 1], idx[i + 2],
static_cast<u32>(0), base);
}
}
base += static_cast<u32>(pos.size());
}
if (tris.empty()) {
return nullptr;
}
JPH::MeshShapeSettings ms(std::move(verts), std::move(tris));
const JPH::Shape::ShapeResult res = ms.Create();
if (res.HasError()) {
ENGINE_WARN("[Physics] MeshShape build failed: {}",
res.GetError().c_str());
return nullptr;
}
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 直接带缩放建形状)。
// 注意 Jolt 两 ctor 参数序相反:CapsuleShape(半径, 圆柱段半高)、
// CylinderShape(圆柱段半高, 半径)
JPH::RefConst<JPH::Shape> shape;
const f32 uniformScale = UniformScale(tr.GetScale());
if (rb.collider == ECS::RigidBodyComponent::ColliderType::Sphere) {
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
// 精确三角网格,Dynamic/Kinematic → ConvexHull(MeshShape 太贵
// 不能动)。无 MeshRenderer/空网格回退 Box 并告警
shape = BuildMeshShape(entityBits, 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 {
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)));
}
const bool dynamic = rb.type == ECS::RigidBodyComponent::BodyType::Dynamic;
const bool kinematic = rb.type == ECS::RigidBodyComponent::BodyType::Kinematic;
JPH::BodyCreationSettings settings(
shape,
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
ToJolt(tr.GetRotationQuat()),
dynamic
? JPH::EMotionType::Dynamic
: (kinematic
? JPH::EMotionType::Kinematic
: JPH::EMotionType::Static),
(dynamic || kinematic) ? LAYER_MOVING : LAYER_NON_MOVING);
if (dynamic) {
settings.mAllowSleeping = true;
settings.mGravityFactor = rb.gravityScale;
settings.mLinearDamping = 0.05f;
settings.mAngularDamping = 0.05f;
settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia;
settings.mMassPropertiesOverride.mMass = std::max(rb.mass, 1e-3f);
}
settings.mFriction = rb.friction;
settings.mRestitution = rb.restitution;
settings.mUserData = entityBits; // 事件回调:Body → 实体位模式反查
settings.mIsSensor = rb.isSensor; // 传感器:只收 Added/Removed,不解算
const JPH::BodyID id = bi.CreateAndAddBody(
settings, dynamic ? JPH::EActivation::Activate : JPH::EActivation::DontActivate);
BodyRecord rec;
rec.id = id;
rec.type = static_cast<u8>(rb.type);
rec.collider = static_cast<u8>(rb.collider);
rec.colliderSize = rb.boxHalfExtents;
ExtractShapeParams(rb, rec.radius, rec.halfHeight);
rec.compound = rb.compound;
rec.mass = rb.mass;
rec.isSensor = rb.isSensor;
rec.lastPos = tr.GetPosition();
rec.lastRot = tr.GetRotationQuat();
m_Bodies[entityBits] = rec;
rb.bodyId = id.GetIndexAndSequenceNumber();
rb.bodyValid = true;
}
void PhysicsWorld::RemoveBody(const u32 entityBits) {
const auto found = m_Bodies.find(entityBits);
if (found == m_Bodies.end()) return;
auto &bi = m_System->GetBodyInterface();
bi.RemoveBody(found->second.id);
bi.DestroyBody(found->second.id);
m_Bodies.erase(found);
}
// ---- 碰撞事件队列 ----
void PhysicsWorld::QueueEvent(CollisionEvent &&ev) {
std::lock_guard lock(m_EventMutex);
m_PendingEvents.push_back(std::move(ev));
}
bool PhysicsWorld::TryBitsByBodyId(const JPH::BodyID &id, u32 &outBits) const {
for (const auto &[bits, rec]: m_Bodies) {
if (rec.id == id) {
outBits = bits;
return true;
}
}
return false;
}
// ---- 射线查询 ----
bool PhysicsWorld::RayCast(const glm::vec3 &origin, const glm::vec3 &dir,
const f32 maxDist, RayHit &outHit) const {
const f32 len = glm::length(dir);
if (len < 1e-8f) return false;
// RRayCast 的 direction 长度 = 射线长度(超出部分不报告命中)
const JPH::RRayCast ray(
JPH::RVec3(origin.x, origin.y, origin.z),
JPH::Vec3(dir.x / len, dir.y / len, dir.z / len) * maxDist);
JPH::RayCastResult hit;
if (!m_System->GetNarrowPhaseQuery().CastRay(ray, hit)) {
return false;
}
// 法线需读 body(locking 接口,主线程安全);body 已销毁则丢弃
const JPH::RVec3 point = ray.GetPointOnRay(hit.mFraction);
JPH::BodyLockRead lock(m_System->GetBodyLockInterface(), hit.mBodyID);
if (!lock.Succeeded()) return false;
const JPH::Vec3 n =
lock.GetBody().GetWorldSpaceSurfaceNormal(hit.mSubShapeID2, point);
u32 bits = 0;
if (!TryBitsByBodyId(hit.mBodyID, bits)) return false;
outHit.entityBits = bits;
outHit.point = {point.GetX(), point.GetY(), point.GetZ()};
outHit.normal = {n.GetX(), n.GetY(), n.GetZ()};
outHit.distance = hit.mFraction * maxDist;
return true;
}
// ---- 刚体驱动(游戏逻辑/编辑器调试;见头文件语义注释)----
const PhysicsWorld::BodyRecord *PhysicsWorld::FindRecord(const u32 entityBits) const {
const auto found = m_Bodies.find(entityBits);
return found != m_Bodies.end() ? &found->second : nullptr;
}
// 力/扭矩/冲量:仅动态体(Jolt 对静态/运动学体施加力无意义)
void PhysicsWorld::AddForce(const u32 entityBits, const glm::vec3 &force) {
const auto *rec = FindRecord(entityBits);
if (!rec || rec->type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic))
return;
m_System->GetBodyInterface().AddForce(
rec->id, JPH::Vec3(force.x, force.y, force.z));
}
void PhysicsWorld::AddImpulse(const u32 entityBits, const glm::vec3 &impulse) {
const auto *rec = FindRecord(entityBits);
if (!rec || rec->type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic))
return;
m_System->GetBodyInterface().AddImpulse(
rec->id, JPH::Vec3(impulse.x, impulse.y, impulse.z));
}
void PhysicsWorld::AddTorque(const u32 entityBits, const glm::vec3 &torque) {
const auto *rec = FindRecord(entityBits);
if (!rec || rec->type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic))
return;
m_System->GetBodyInterface().AddTorque(
rec->id, JPH::Vec3(torque.x, torque.y, torque.z));
}
// 速度直写:动态+运动学体都有效(运动学体速度即其运动方式)
void PhysicsWorld::SetLinearVelocity(const u32 entityBits, const glm::vec3 &velocity) {
const auto *rec = FindRecord(entityBits);
if (!rec || rec->type == static_cast<u8>(ECS::RigidBodyComponent::BodyType::Static))
return;
m_System->GetBodyInterface().SetLinearVelocity(
rec->id, JPH::Vec3(velocity.x, velocity.y, velocity.z));
}
void PhysicsWorld::SetAngularVelocity(const u32 entityBits, const glm::vec3 &angularVelocity) {
const auto *rec = FindRecord(entityBits);
if (!rec || rec->type == static_cast<u8>(ECS::RigidBodyComponent::BodyType::Static))
return;
m_System->GetBodyInterface().SetAngularVelocity(
rec->id, JPH::Vec3(angularVelocity.x, angularVelocity.y, angularVelocity.z));
}
glm::vec3 PhysicsWorld::GetLinearVelocity(const u32 entityBits) const {
const auto *rec = FindRecord(entityBits);
if (!rec || rec->type == static_cast<u8>(ECS::RigidBodyComponent::BodyType::Static))
return {0.0f, 0.0f, 0.0f};
const JPH::Vec3 v = m_System->GetBodyInterface().GetLinearVelocity(rec->id);
return {v.GetX(), v.GetY(), v.GetZ()};
}
// ---- 角色控制器门面 ----
CharacterController *PhysicsWorld::FindCharacter(const u32 entityBits) {
const auto found = m_Characters.find(entityBits);
return found != m_Characters.end() ? found->second.cc.get() : nullptr;
}
void PhysicsWorld::SetCharacterVelocity(const u32 entityBits,
const glm::vec3 &velocity) {
if (CharacterController *cc = FindCharacter(entityBits)) {
cc->SetLinearVelocity(velocity);
}
}
void PhysicsWorld::Jump(const u32 entityBits, const f32 speed) {
if (CharacterController *cc = FindCharacter(entityBits)) {
cc->Jump(speed);
}
}
bool PhysicsWorld::IsCharacterGrounded(const u32 entityBits) const {
const auto found = m_Characters.find(entityBits);
return found != m_Characters.end() && found->second.cc->IsGrounded();
}
glm::vec3 PhysicsWorld::GetCharacterVelocity(const u32 entityBits) const {
const auto found = m_Characters.find(entityBits);
return found != m_Characters.end() ? found->second.cc->GetLinearVelocity()
: glm::vec3{0.0f};
}
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
void PhysicsWorld::Writeback() const {
auto &reg = m_Scene.GetRegistry();
const auto &bi = m_System->GetBodyInterface();
const auto view = reg.view<ECS::RigidBodyComponent, ECS::TransformComponent>();
for (auto [e, rb, tr]: view.each()) {
if (!rb.bodyValid ||
rb.type != ECS::RigidBodyComponent::BodyType::Dynamic) {
continue; // 静态/运动学体的 Transform 由编辑器驱动
}
const JPH::BodyID id(rb.bodyId);
const JPH::RVec3 pos = bi.GetPosition(id);
const JPH::Quat rot = bi.GetRotation(id);
tr.SetPosition(glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ()));
tr.SetRotationQuat(ToGlm(rot));
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
}
}
} // namespace Engine