Files
HelloVulkan/Engine/src/Physics/PhysicsWorld.cpp
T
atdunbg beba491bcb 阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画: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 的拒读)
2026-10-11 22:22:19 +08:00

1221 lines
56 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/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 &reg = 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 &reg = 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 &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->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 &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);
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 &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;
}
// 每角色重力倍率(组件直读: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 &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);
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