feat: Jolt 物理系统——刚体/碰撞体/物理调试图元 + 引擎时间单源
- PhysicsWorld:Jolt 集成,Dynamic/Kinematic/Static 刚体,姿态写回 Transform(Unity FixedUpdate 语义,吃 scaled dt,暂停自动冻结) - 碰撞体:Box(默认/手动 halfExtents)+ Mesh 碰撞体(Static→精确 MeshShape / Dynamic·Kinematic→ConvexHull,数据源 = Mesh ctor 缓存 的 CPU 位置副本,退化三角剔除,失败回退 Box 并告警) - PhysicsDebugPass:物理调试图元每帧上传绘制(debug_line shader) - Time:引擎时间单源(BeginFrame 落定 FrameTime,scaled/unscaled 分离) - JoltPhysics 以 submodule 接入(官方 url)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// PhysicsDebugDraw.cpp:回调实现(图元收集)。
|
||||
//
|
||||
|
||||
#include <Engine/Physics/PhysicsDebugDraw.h>
|
||||
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
|
||||
namespace Engine {
|
||||
|
||||
void PhysicsDebugDraw::DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo,
|
||||
JPH::ColorArg inColor)
|
||||
{
|
||||
const glm::vec4 c = ToGlm(inColor);
|
||||
m_Frame.lines.push_back({ToGlm(inFrom), c});
|
||||
m_Frame.lines.push_back({ToGlm(inTo), c});
|
||||
}
|
||||
|
||||
void PhysicsDebugDraw::DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2,
|
||||
JPH::RVec3Arg inV3,
|
||||
JPH::ColorArg inColor,
|
||||
JPH::DebugRenderer::ECastShadow /*inCastShadow*/)
|
||||
{
|
||||
// 纯收集:实心面留待后续(线框模式下本回调不触发——
|
||||
// DebugRendererSimple::DrawGeometry 的 Wireframe 分支走 DrawLine)
|
||||
const glm::vec4 c = ToGlm(inColor);
|
||||
m_Frame.triangles.push_back({ToGlm(inV1), c});
|
||||
m_Frame.triangles.push_back({ToGlm(inV2), c});
|
||||
m_Frame.triangles.push_back({ToGlm(inV3), c});
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
#endif // JPH_DEBUG_RENDERER
|
||||
@@ -0,0 +1,634 @@
|
||||
//
|
||||
// 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 <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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 引擎 Compose() 的旋转合成是 Ry·Rx·Rz(YXZ 序,见 Components.h)——
|
||||
// 物理写回必须按同约定分解欧拉角,否则体素会随写回帧帧漂移。
|
||||
// R = Ry(y)·Rx(x)·Rz(z) 的矩阵展开(0 基):
|
||||
// M12 = -sx → x = asin(-M12);否则 y = atan2(M02, M22),z = atan2(M10, M11)
|
||||
glm::vec3 QuatToEulerYXZ(const JPH::Quat &q) {
|
||||
const glm::quat gq(q.GetW(), q.GetX(), q.GetY(), q.GetZ());
|
||||
const glm::mat3 m = glm::mat3(gq); // glm 列主序:m[col][row]
|
||||
// 数学记法 M[row][col]:M12 = m[2][1],M02 = m[2][0],M10 = m[0][1] …
|
||||
const f32 m12 = m[2][1];
|
||||
const f32 m02 = m[2][0];
|
||||
const f32 m22 = m[2][2];
|
||||
const f32 m10 = m[0][1];
|
||||
const f32 m11 = m[1][1];
|
||||
const f32 m01 = m[1][0];
|
||||
|
||||
const f32 sinX = std::clamp(-m12, -1.0f, 1.0f);
|
||||
const f32 x = std::asin(sinX);
|
||||
if (std::abs(sinX) < 0.9999f) {
|
||||
return {
|
||||
glm::degrees(x),
|
||||
glm::degrees(std::atan2(m02, m22)),
|
||||
glm::degrees(std::atan2(m10, m11))
|
||||
};
|
||||
}
|
||||
// 万向锁(x=±90°):z 并入 y,取 z=0,y = atan2(M01, M02)
|
||||
return {
|
||||
glm::degrees(x),
|
||||
glm::degrees(std::atan2(m01, m02)),
|
||||
0.0f
|
||||
};
|
||||
}
|
||||
|
||||
// 与 QuatToEulerYXZ 互逆:q = qy(y)·qx(x)·qz(z) ≡ Ry·Rx·Rz
|
||||
//(不能用 glm::quat(euler) ctor——那是 ZYX 航向序,语义不同)
|
||||
JPH::Quat EulerYXZToQuat(const glm::vec3 &eulerDeg) {
|
||||
const glm::quat qx = glm::angleAxis(glm::radians(eulerDeg.x), glm::vec3(1, 0, 0));
|
||||
const glm::quat qy = glm::angleAxis(glm::radians(eulerDeg.y), glm::vec3(0, 1, 0));
|
||||
const glm::quat qz = glm::angleAxis(glm::radians(eulerDeg.z), glm::vec3(0, 0, 1));
|
||||
const glm::quat q = qy * qx * qz;
|
||||
return {q.x, q.y, q.z, q.w};
|
||||
}
|
||||
} // 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();
|
||||
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 ® = m_Scene.GetRegistry();
|
||||
auto &bi = m_System->GetBodyInterface();
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
CreateBody(bits, rb, tr);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &rec = found->second; // 非 const:静态/运动体拖动时更新 lastPos/lastRot
|
||||
if (rec.type != static_cast<u8>(rb.type) ||
|
||||
rec.collider != static_cast<u8>(rb.collider) ||
|
||||
rec.colliderSize != rb.boxHalfExtents ||
|
||||
rec.radius != rb.sphereRadius ||
|
||||
rec.mass != rb.mass ||
|
||||
rec.isSensor != rb.isSensor) {
|
||||
// 类型/碰撞体/质量变更 → 重建(简单正确,重置速度可接受)
|
||||
bi.RemoveBody(rec.id);
|
||||
bi.DestroyBody(rec.id);
|
||||
m_Bodies.erase(found);
|
||||
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.GetRotation()) {
|
||||
// 编辑器拖动静态/运动学体 → 传送 body
|
||||
bi.SetPositionAndRotation(id,
|
||||
JPH::RVec3(tr.GetPosition().x, tr.GetPosition().y, tr.GetPosition().z),
|
||||
EulerYXZToQuat(tr.GetRotation()),
|
||||
JPH::EActivation::DontActivate);
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 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();
|
||||
}
|
||||
|
||||
void PhysicsWorld::CreateBody(u32 entityBits, ECS::RigidBodyComponent &rb,
|
||||
ECS::TransformComponent &tr) {
|
||||
auto &bi = m_System->GetBodyInterface();
|
||||
|
||||
// 碰撞体:组件显式尺寸 × Transform 缩放(形状按单位尺度烹饪 +
|
||||
// ScaledShape 的优化留给 ShapeCache 阶段;MVP 直接带缩放建形状)
|
||||
JPH::RefConst<JPH::Shape> shape;
|
||||
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));
|
||||
} 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),
|
||||
EulerYXZToQuat(tr.GetRotation()),
|
||||
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;
|
||||
rec.radius = rb.sphereRadius;
|
||||
rec.mass = rb.mass;
|
||||
rec.isSensor = rb.isSensor;
|
||||
rec.lastPos = tr.GetPosition();
|
||||
rec.lastRot = tr.GetRotation();
|
||||
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;
|
||||
}
|
||||
|
||||
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
|
||||
|
||||
void PhysicsWorld::Writeback() const {
|
||||
auto ® = 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.SetRotation(QuatToEulerYXZ(rot));
|
||||
|
||||
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
|
||||
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||
}
|
||||
}
|
||||
} // namespace Engine
|
||||
Reference in New Issue
Block a user