- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对), 指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis 行走基朝向与跨骨架去重、根运动通道 - 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与 字符冲量判定 - 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、 不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖 - 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增 ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target - 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板 - 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
1221 lines
56 KiB
C++
1221 lines
56 KiB
C++
//
|
||
// PhysicsWorld.cpp:Jolt 后端实现。层配置/类型注册/对账/写回。
|
||
//
|
||
|
||
#include <Engine/Physics/PhysicsWorld.h>
|
||
#include <Engine/Debug/DebugDraw.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>();
|
||
|
||
// 角色间碰撞互查表(内部 Array 走 JPH 分配器,须在 call_once 之后)
|
||
m_VsCharacterCollision =
|
||
CreateScope<JPH::CharacterVsCharacterCollisionSimple>();
|
||
|
||
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, 65536, 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_Characters.clear(); // 角色析构回 m_VsCharacterCollision 摘除,先于 Jolt 反注册
|
||
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* 后续加这里)。
|
||
// 速度箭头不交 Jolt 画——其 BodyManager::DrawBodies 无零速保
|
||
// 护,静止刚体的零向量箭头退化成 +X 方向(本次修复的突兀箭
|
||
// 头)→ 统一在下方手画并加阈值
|
||
if ((m_DebugBits & kPhyDbgColliders) != 0) {
|
||
JPH::BodyManager::DrawSettings ds;
|
||
ds.mDrawShape = true;
|
||
ds.mDrawShapeWireframe = true;
|
||
m_System->DrawBodies(ds, m_DebugDraw.get());
|
||
}
|
||
|
||
// 2) 约束(关节;软体/载具约束接入后自动生效)
|
||
if ((m_DebugBits & kPhyDbgConstraints) != 0) {
|
||
m_System->DrawConstraints(m_DebugDraw.get());
|
||
}
|
||
|
||
// 3) 手绘箭头(Jolt 无现成 API):重力 = 常量方向指示(长度固定),
|
||
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)。
|
||
// 走引擎通用 DebugDraw 服务。收集按渲染帧率跑(Step 末尾
|
||
// 无步进帧也重画,每帧全量重建)→ duration 必须 0(当帧瞬
|
||
// 时):此前沿用 step 率时代的 2×步长续期,高帧率下每根箭
|
||
// 头叠 4-5 个滞后副本,物体一动副本脱锚拖尾——表现为断断
|
||
// 续续乱跳
|
||
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;
|
||
constexpr f32 kVelScale = 0.2f; // 速度箭头比例(单位/速度)
|
||
constexpr f32 kDbgDur = 0.0f; // 渲染帧率收集 → 瞬时图元
|
||
constexpr glm::vec4 kGravityColor = Debug::Rgb(0xDC3C3C); // 红:重力
|
||
constexpr glm::vec4 kAccelColor = Debug::Rgb(0xE0640A); // 深橙:加速度
|
||
constexpr glm::vec4 kVelColor = Debug::Rgb(0x5ADC5A); // 绿:速度
|
||
constexpr glm::vec4 kContactColor = Debug::Rgb(0xD9C33C); // 黄:接触反力
|
||
auto ToG = [](const JPH::RVec3Arg v) {
|
||
return glm::vec3{static_cast<f32>(v.GetX()),
|
||
static_cast<f32>(v.GetY()),
|
||
static_cast<f32>(v.GetZ())};
|
||
};
|
||
|
||
for (auto &[bits, rec]: m_Bodies) {
|
||
if (rec.type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic)) {
|
||
continue;
|
||
}
|
||
const glm::vec3 com = ToG(bi.GetCenterOfMassPosition(rec.id));
|
||
|
||
// 速度箭头(绿):零速阈值——Jolt 原生 DrawBodies 无零速保
|
||
// 护(静止零向量箭头退化成 +X),此处阈值手画
|
||
if ((m_DebugBits & kPhyDbgVelocity) != 0) {
|
||
const JPH::Vec3 v = bi.GetLinearVelocity(rec.id);
|
||
if (v.LengthSq() > 1e-4f) { // >0.01 m/s 才画
|
||
Debug::Arrow(com, com + ToG(JPH::RVec3(v * kVelScale)),
|
||
kVelColor,
|
||
/*headSize*/ 0.12f, /*width*/ 2.0f,
|
||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||
}
|
||
}
|
||
|
||
if ((m_DebugBits & kPhyDbgGravity) != 0) {
|
||
const JPH::Vec3 g = gravity * bi.GetGravityFactor(rec.id);
|
||
const f32 len = g.Length();
|
||
if (len > 1e-4f) {
|
||
Debug::Arrow(
|
||
com,
|
||
com + ToG(JPH::RVec3(g * (kGravityLen / len))),
|
||
kGravityColor,
|
||
/*headSize*/ 0.12f, /*width*/ 2.0f,
|
||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||
}
|
||
}
|
||
|
||
if ((m_DebugBits & kPhyDbgAccel) != 0) {
|
||
// 接触合力(比力 F/m = a − g,从测量加速度反推):静置
|
||
// = +g 纯法向(与重力箭头等长反向 = 力平衡);滑动出现
|
||
// 水平分量(摩擦);自由落体 ≈ 0 不画。长度按重力箭头
|
||
// 同一比例归一 → 静置红下黄上视觉等长
|
||
const JPH::Vec3 gB = gravity * bi.GetGravityFactor(rec.id);
|
||
const glm::vec3 spec = rec.lastAccel -
|
||
glm::vec3(gB.GetX(), gB.GetY(), gB.GetZ());
|
||
const f32 smag = glm::length(spec);
|
||
if (smag > 0.2f && gravity.Length() > 1e-4f) {
|
||
const f32 slen = std::min(
|
||
smag * kGravityLen / gravity.Length(), kAccelMaxLen);
|
||
Debug::Arrow(com, com + (spec / smag) * slen,
|
||
kContactColor,
|
||
/*headSize*/ 0.12f, /*width*/ 2.0f,
|
||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||
}
|
||
|
||
const f32 mag = glm::length(rec.lastAccel);
|
||
if (mag > 0.05f) {
|
||
const f32 len = std::min(mag * kAccelScale, kAccelMaxLen);
|
||
Debug::Arrow(com, com + (rec.lastAccel / mag) * len,
|
||
kAccelColor,
|
||
/*headSize*/ 0.12f,
|
||
/*width*/ 2.0f,
|
||
/*duration*/ kDbgDur, /*depthTest*/ false);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4) 角色控制器:CharacterVirtual 非 body,DrawBodies 画不到,
|
||
// 手动画胶囊线框(绿 = 与普通 collider 区分)
|
||
if ((m_DebugBits & kPhyDbgColliders) != 0) {
|
||
for (auto &[bits, entry] : m_Characters) {
|
||
entry.cc->DebugDraw(*m_DebugDraw);
|
||
}
|
||
}
|
||
|
||
// 角色运动类线条(速度/加速度)一律归用户脚本画(Unity
|
||
// CharacterController 同语义:引擎只画 collider,移动调试归
|
||
// 移动逻辑的拥有者);物理侧不保留任何角色箭头
|
||
|
||
// 释放上一帧缓存不再引用的通用几何 batch(DebugRenderer 内部池)
|
||
m_DebugDraw->NextFrame();
|
||
}
|
||
|
||
// ---- 对账 ----
|
||
|
||
void PhysicsWorld::SyncScene() {
|
||
auto ® = m_Scene.GetRegistry();
|
||
auto &bi = m_System->GetBodyInterface();
|
||
std::unordered_set<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);
|
||
m_BodyToEntity.erase(it->second.id.GetIndexAndSequenceNumber());
|
||
invalidated.insert(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);
|
||
|
||
// 禁用 = 视为组件不存在:销毁 body(对账自然消化),重启用即重建
|
||
if (!rb.active) {
|
||
if (found != m_Bodies.end()) {
|
||
bi.RemoveBody(found->second.id);
|
||
bi.DestroyBody(found->second.id);
|
||
m_BodyToEntity.erase(found->second.id.GetIndexAndSequenceNumber());
|
||
m_Bodies.erase(found);
|
||
invalidated.insert(bits);
|
||
}
|
||
rb.bodyValid = false;
|
||
continue;
|
||
}
|
||
|
||
if (found == m_Bodies.end() || !rb.bodyValid) {
|
||
if (found != m_Bodies.end()) {
|
||
bi.RemoveBody(found->second.id);
|
||
bi.DestroyBody(found->second.id);
|
||
m_BodyToEntity.erase(found->second.id.GetIndexAndSequenceNumber());
|
||
m_Bodies.erase(found);
|
||
invalidated.insert(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.isTrigger != rb.isTrigger) {
|
||
// 类型/碰撞体/质量变更 → 重建(简单正确,重置速度可接受)
|
||
bi.RemoveBody(rec.id);
|
||
bi.DestroyBody(rec.id);
|
||
m_BodyToEntity.erase(found->second.id.GetIndexAndSequenceNumber());
|
||
m_Bodies.erase(found);
|
||
invalidated.insert(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);
|
||
// 编辑器拖动 Dynamic 体(暂停期/物理未步进的帧)→ 传送 +
|
||
// 清速度(UE 编辑器语义:编辑器操作权威于物理 body,拖动 =
|
||
// 重新放置)。物理模拟中 Writeback 每步同步 lastPos/lastRot
|
||
// → 本比对只在真正拖动帧为真,不会打断自由模拟。
|
||
// body 是世界系:读 world 缓存(层级实体取父链组合结果)
|
||
if (rec.lastPos != tr.GetWorldPosition() ||
|
||
rec.lastRot != tr.GetWorldRotationQuat()) {
|
||
bi.SetPositionAndRotation(
|
||
id,
|
||
JPH::RVec3(tr.GetWorldPosition().x, tr.GetWorldPosition().y, tr.GetWorldPosition().z),
|
||
ToJolt(tr.GetWorldRotationQuat()),
|
||
JPH::EActivation::Activate);
|
||
bi.SetLinearVelocity(id, JPH::Vec3(0.0f, 0.0f, 0.0f));
|
||
bi.SetAngularVelocity(id, JPH::Vec3(0.0f, 0.0f, 0.0f));
|
||
rec.lastPos = tr.GetWorldPosition();
|
||
rec.lastRot = tr.GetWorldRotationQuat();
|
||
}
|
||
} else if (rec.lastPos != tr.GetWorldPosition() ||
|
||
rec.lastRot != tr.GetWorldRotationQuat()) {
|
||
// 编辑器拖动静态/运动学体 → 传送 body
|
||
bi.SetPositionAndRotation(id,
|
||
JPH::RVec3(tr.GetWorldPosition().x, tr.GetWorldPosition().y, tr.GetWorldPosition().z),
|
||
ToJolt(tr.GetWorldRotationQuat()),
|
||
JPH::EActivation::DontActivate);
|
||
rec.lastPos = tr.GetWorldPosition();
|
||
rec.lastRot = tr.GetWorldRotationQuat();
|
||
}
|
||
}
|
||
|
||
// 3. 约束对账(body 循环之后:依赖新建 body 的最终 ID)
|
||
SyncConstraints(invalidated);
|
||
|
||
// 4. 角色对账(非刚体,不依赖 body 生命周期)
|
||
SyncCharacters();
|
||
}
|
||
|
||
// ---- 约束对账 ----
|
||
|
||
void PhysicsWorld::SyncConstraints(const std::unordered_set<u32> &invalidated) {
|
||
auto ® = m_Scene.GetRegistry();
|
||
const auto tainted = [&invalidated](const u32 bits) {
|
||
return invalidated.count(bits);
|
||
};
|
||
|
||
// 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 ECS::JointComponent* jc =
|
||
reg.valid(ownerEnt) ? reg.try_get<ECS::JointComponent>(ownerEnt)
|
||
: nullptr;
|
||
const bool ownerGone = jc == nullptr || !jc->active;
|
||
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()) {
|
||
if (!jc.active) {
|
||
continue;
|
||
}
|
||
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 ® = 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->GetWorldScale();
|
||
const glm::vec3 s2 = tr2->GetWorldScale();
|
||
|
||
// 双写锁读位姿/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 ® = 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);
|
||
const auto* cc = reg.valid(e)
|
||
? reg.try_get<ECS::CharacterControllerComponent>(e) : nullptr;
|
||
if (cc == nullptr || !cc->active ||
|
||
!reg.all_of<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()) {
|
||
if (!cc.active) {
|
||
continue;
|
||
}
|
||
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;
|
||
p.shapeOffset = cc.shapeOffset;
|
||
|
||
auto found = m_Characters.find(bits);
|
||
if (found == m_Characters.end()) {
|
||
CharacterEntry entry;
|
||
entry.cc = std::make_unique<CharacterController>(
|
||
*m_System, p, tr.GetWorldPosition());
|
||
entry.cc->SetCharacterCollision(*m_VsCharacterCollision);
|
||
entry.lastPos = tr.GetWorldPosition();
|
||
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.GetWorldPosition());
|
||
found->second.cc->SetCharacterCollision(*m_VsCharacterCollision);
|
||
found->second.lastPos = tr.GetWorldPosition();
|
||
continue;
|
||
}
|
||
if (found->second.lastPos != tr.GetWorldPosition()) {
|
||
// 编辑器拖动实体 → 传送角色(角色是唯一位置源,反向覆盖)
|
||
found->second.cc->Teleport(tr.GetWorldPosition());
|
||
found->second.lastPos = tr.GetWorldPosition();
|
||
}
|
||
}
|
||
}
|
||
|
||
void PhysicsWorld::UpdateCharacters(const f32 dt) {
|
||
auto ® = 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;
|
||
}
|
||
// 每角色重力倍率(组件直读:Inspector 调完下个物理步即生效,
|
||
// 不触发重建——重力只是 Update 的入参,不是形状参数)
|
||
const auto *cc = reg.try_get<ECS::CharacterControllerComponent>(
|
||
static_cast<entt::entity>(bits));
|
||
const f32 gscale = cc != nullptr ? cc->gravityScale : 1.0f;
|
||
entry.cc->Update(dt, gravity * gscale, *m_TempAllocator);
|
||
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底(世界系)
|
||
// 写回走 world→local 换算(层级实体挂父下仍正确;标脏 →
|
||
// UpdateTransforms 重算 world)
|
||
m_Scene.SetWorldPose(
|
||
static_cast<entt::entity>(bits), p,
|
||
tr->GetWorldRotationQuat(), tr->GetWorldScale());
|
||
entry.lastPos = p;
|
||
|
||
// ---- 角色碰撞事件合成(Enter/Exit)----
|
||
// CharacterVirtual 非刚体,Jolt 的 ContactListener 不会为角色
|
||
// 回调——脚本因此收不到"玩家撞墙"。这里读角色本步活跃接触,
|
||
// 与上一步集合对比出边沿,合成与刚体同款的 CollisionEvent
|
||
// (a = 角色实体,b = 被撞实体)。只处理刚体接触(mBodyB 有效):
|
||
// 角色↔角色由 CharacterVsCharacterCollision 的物理互查处理,
|
||
// 不发脚本事件(对方是角色时 mBodyB 无效,天然跳过)
|
||
std::unordered_set<u32> now;
|
||
now.reserve(entry.contacts.size() + 4);
|
||
for (const JPH::CharacterContact& c :
|
||
entry.cc->GetActiveContacts()) {
|
||
if (c.mBodyB.IsInvalid()) continue; // 对方非刚体(角色)
|
||
if (c.mWasDiscarded) continue; // 被丢弃/传感器
|
||
if (c.mDistance > 0.0f) continue; // >0 = 预测接触,未真相交
|
||
u32 other = 0;
|
||
if (!TryBitsByBodyId(c.mBodyB, other)) continue;
|
||
now.insert(other);
|
||
if (entry.contacts.count(other) == 0) {
|
||
const glm::vec3 pt(
|
||
static_cast<f32>(c.mPosition.GetX()),
|
||
static_cast<f32>(c.mPosition.GetY()),
|
||
static_cast<f32>(c.mPosition.GetZ()));
|
||
const glm::vec3 n(c.mContactNormal.GetX(),
|
||
c.mContactNormal.GetY(),
|
||
c.mContactNormal.GetZ());
|
||
QueueEvent({bits, other, CollisionEvent::Kind::Added, pt, n});
|
||
}
|
||
}
|
||
for (const u32 prev : entry.contacts) {
|
||
if (now.count(prev) == 0) {
|
||
QueueEvent(
|
||
{bits, prev, CollisionEvent::Kind::Removed, {}, {}});
|
||
}
|
||
}
|
||
entry.contacts = std::move(now);
|
||
}
|
||
}
|
||
|
||
// ---- 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 || !mc->model.Valid()) {
|
||
return nullptr; // 无效句柄(未指派)不进 Get(对无效 FATAL)
|
||
}
|
||
const Model *model = m_Assets->GetModel(mc->model);
|
||
if (model == nullptr) {
|
||
return nullptr;
|
||
}
|
||
|
||
// 缩放烘进顶点:Jolt shape 无缩放语义,body 只吃 pos/rot。
|
||
// 取 world 缩放(层级实体 = 父链累计)
|
||
const glm::vec3 s = tr.GetWorldScale();
|
||
|
||
// 动态/运动体 → ConvexHull(MeshShape 三角网格太贵,Jolt 不允许动)
|
||
if (rb.type != ECS::RigidBodyComponent::BodyType::Static) {
|
||
JPH::ConvexHullShapeSettings cs;
|
||
const auto &meshes = model->GetMeshes();
|
||
for (size_t mi = 0; mi < meshes.size(); ++mi) {
|
||
// 单 mesh 子引用:碰撞只取渲染可见的同一 mesh(语义跟随)
|
||
if (mc->meshIndex >= 0 &&
|
||
static_cast<int>(mi) != mc->meshIndex) {
|
||
continue;
|
||
}
|
||
for (const auto &p: meshes[mi]->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;
|
||
const auto &meshes = model->GetMeshes();
|
||
for (size_t mi = 0; mi < meshes.size(); ++mi) {
|
||
// 单 mesh 子引用:碰撞只取渲染可见的同一 mesh(语义跟随)
|
||
if (mc->meshIndex >= 0 &&
|
||
static_cast<int>(mi) != mc->meshIndex) {
|
||
continue;
|
||
}
|
||
const auto &pos = meshes[mi]->GetCpuPositions();
|
||
const auto &idx = meshes[mi]->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.GetWorldScale().x, 0.01f),
|
||
std::max(rb.boxHalfExtents.y * tr.GetWorldScale().y, 0.01f),
|
||
std::max(rb.boxHalfExtents.z * tr.GetWorldScale().z, 0.01f)));
|
||
}
|
||
} else {
|
||
shape = new JPH::BoxShape(JPH::Vec3(
|
||
std::max(rb.boxHalfExtents.x * tr.GetWorldScale().x, 0.01f),
|
||
std::max(rb.boxHalfExtents.y * tr.GetWorldScale().y, 0.01f),
|
||
std::max(rb.boxHalfExtents.z * tr.GetWorldScale().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.GetWorldPosition().x, tr.GetWorldPosition().y, tr.GetWorldPosition().z),
|
||
ToJolt(tr.GetWorldRotationQuat()),
|
||
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.isTrigger; // 触发器:只收 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.isTrigger = rb.isTrigger;
|
||
rec.lastPos = tr.GetWorldPosition();
|
||
rec.lastRot = tr.GetWorldRotationQuat();
|
||
m_Bodies[entityBits] = rec;
|
||
m_BodyToEntity[id.GetIndexAndSequenceNumber()] = entityBits;
|
||
|
||
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_BodyToEntity.erase(found->second.id.GetIndexAndSequenceNumber());
|
||
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 {
|
||
const auto& found = m_BodyToEntity.find(id.GetIndexAndSequenceNumber());
|
||
if (found == m_BodyToEntity.end()) return false;
|
||
outBits = found->second;
|
||
return true;
|
||
}
|
||
|
||
// ---- 射线查询 ----
|
||
|
||
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};
|
||
}
|
||
|
||
glm::vec3 PhysicsWorld::GetCharacterPosition(const u32 entityBits) const {
|
||
const auto found = m_Characters.find(entityBits);
|
||
return found != m_Characters.end() ? found->second.cc->GetPosition()
|
||
: glm::vec3{0.0f};
|
||
}
|
||
|
||
glm::vec3
|
||
PhysicsWorld::GetCharacterImpactVelocity(const u32 entityBits) const {
|
||
const auto found = m_Characters.find(entityBits);
|
||
return found != m_Characters.end()
|
||
? found->second.cc->GetSweepVelocity()
|
||
: glm::vec3{0.0f};
|
||
}
|
||
|
||
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
|
||
|
||
void PhysicsWorld::Writeback() {
|
||
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);
|
||
const glm::vec3 worldPos(pos.GetX(), pos.GetY(), pos.GetZ());
|
||
const glm::quat worldRot = ToGlm(rot);
|
||
// body 姿态是世界系:写回经 world→local 换算(挂父层级实体
|
||
// 不破坏相对位姿;根实体 = 直写)。标脏 → UpdateTransforms 重算
|
||
m_Scene.SetWorldPose(
|
||
e,
|
||
worldPos,
|
||
worldRot,
|
||
tr.GetWorldScale());
|
||
|
||
const JPH::Vec3 vel = bi.GetLinearVelocity(id);
|
||
rb.velocity = glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
|
||
|
||
// lastPos/lastRot 同步到 body 当前姿态:否则下帧 SyncScene 会把物理
|
||
// 位移误判为编辑器拖动 → teleport + 清零速度死循环。
|
||
// 注意:必须用 bi.GetPosition/GetRotation 的 fresh 值,不能读
|
||
// tr.GetWorldPosition()——SetWorldPose 只改 local+标脏,world cache
|
||
// 要等本帧末尾 Scene::UpdateTransforms 才重算,此刻仍是旧值。
|
||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||
if (auto it = m_Bodies.find(bits); it != m_Bodies.end()) {
|
||
it->second.lastPos = worldPos;
|
||
it->second.lastRot = worldRot;
|
||
}
|
||
}
|
||
}
|
||
} // namespace Engine
|