Files
HelloVulkan/Engine/include/Engine/Physics/PhysicsWorld.h
T
atdunbg 36b1fc0566 新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线
通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
2026-10-08 17:02:27 +08:00

211 lines
11 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:物理世界门面(Jolt 后端)。
//
// 镜像 SceneRenderer 的单向数据流:
// SyncScene(读 ECS 对账 body)→ PhysicsSystem::Update → Writeback(动态体 → Transform 标脏)
// 定步长 accumulator(1/60,最多 4 子步,积压丢弃防螺旋死亡)。
//
// 分层约束:
// - 组件层(Components.h)零 Jolt 依赖:bodyId 存 JPH::BodyID 裸位模式
// - App/编辑器只经 Application::GetPhysics() 访问开关,不接触 Jolt 头
//
// 可扩展路线(本门面是唯一入口,feature 各自分文件演进,禁止互相耦合):
// 软体/布料 → Jolt SoftBodySharedSettings/SoftBodyCreationSettings(原生支持),
// 扩展为 SyncSoftBodies/WritebackSoft(网格引用走 AssetRef)
// 角色控制器 → CharacterVirtual(运动学扫掠,非刚体,独立 Controller 类持有)
// 载具控制器 → VehicleConstraint(挂动态刚体 chassis 上,独立 Controller 类)
// 碰撞事件 → ContactListenerImpl(本类嵌套,已实现)+ GetEvents 帧尾队列
// 射线查询 → BodyInterface::RayCast 门面(编辑器拾取/游戏查询)
//
#ifndef HELLOVULKAN_PHYSICSWORLD_H
#define HELLOVULKAN_PHYSICSWORLD_H
#include <Engine/Core/Types.h>
#include <Engine/Physics/PhysicsDebugDraw.h>
#include <Engine/Physics/PhysicsEvents.h>
#include <Engine/Physics/PhysicsRay.h>
#include <Engine/Physics/CharacterController.h>
#include <Engine/ECS/Components.h> // BodyRecord 快照存 CompoundChild(完整类型)
#include <Jolt/Jolt.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Body/BodyID.h>
#include <Jolt/Physics/Constraints/Constraint.h> // JPH::Ref<Constraint>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Core/TempAllocator.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace Engine {
namespace ECS { class Scene; struct RigidBodyComponent; struct TransformComponent; }
namespace Assets { class AssetManager; }
class PhysicsWorld {
public:
// assets:Mesh 碰撞体解析 modelRef(MeshShape/ConvexHull 数据源)
explicit PhysicsWorld(ECS::Scene& scene, Assets::AssetManager* assets);
~PhysicsWorld();
PhysicsWorld(const PhysicsWorld&) = delete;
PhysicsWorld& operator=(const PhysicsWorld&) = delete;
// 帧循环调用(OnUpdate 之前,Unity FixedUpdate→Update 语义)
void Step(f32 dt);
void SetEnabled(bool enabled) { m_Enabled = enabled; }
bool IsEnabled() const { return m_Enabled; }
// ---- 物理调试可视化(ImGui 开关;位掩码见 PhysicsDebugBits)----
// Step 尾部按位收集图元(渲染帧率),Renderer 每帧上传绘制
void SetDebugDrawBits(u32 bits) { m_DebugBits = bits; }
u32 GetDebugDrawBits() const { return m_DebugBits; }
PhysicsDebugFrame& GetDebugFrame() { return m_DebugDraw->Frame(); }
// ---- 碰撞事件(语义 = 最近一次步进;Step 开头清空)----
// 主线程在 Step 之外读取,与多线程回调互不重叠,无需加锁
const std::vector<CollisionEvent>& GetEvents() const {
return m_PendingEvents;
}
// ---- 射线查询(游戏查询/编辑器拾取;主线程调用,暂停时也可用)----
// dir 会被内部归一化;maxDist 限制射程。命中返回 true 并填 outHit
bool RayCast(const glm::vec3& origin, const glm::vec3& dir,
f32 maxDist, RayHit& outHit) const;
// ---- 刚体驱动(游戏逻辑/编辑器调试;按实体位模式寻址)----
// Jolt 力在每步模拟后清零:游戏逻辑每帧持续施加 = 正确用法;
// 冲量一次性改变动量(自动唤醒休眠体);速度直写会打断自然
// 模拟(Unity 同语义)。body 不存在或类型不符时静默忽略:
// 力/扭矩/冲量仅动态体,速度直写动态+运动学体
void AddForce(u32 entityBits, const glm::vec3& force);
void AddImpulse(u32 entityBits, const glm::vec3& impulse);
void AddTorque(u32 entityBits, const glm::vec3& torque);
void SetLinearVelocity(u32 entityBits, const glm::vec3& velocity);
void SetAngularVelocity(u32 entityBits, const glm::vec3& angularVelocity);
// 无 body / 静态体返回零向量
glm::vec3 GetLinearVelocity(u32 entityBits) const;
// ---- 角色控制器(游戏逻辑门面;实体无 CharacterControllerComponent
// 或未就绪时静默忽略)----
// 速度直写喂意图(水平移动);垂直分量由 Update 自动积分重力
void SetCharacterVelocity(u32 entityBits, const glm::vec3& velocity);
void Jump(u32 entityBits, f32 speed); // 垂直速度置 speed
bool IsCharacterGrounded(u32 entityBits) const;
glm::vec3 GetCharacterVelocity(u32 entityBits) const;
// 角色脚底位置(物理权威世界坐标):调试图元/特效锚点用它,
// 而不是 Transform().GetPosition()——后者在有父级时是本地坐标
glm::vec3 GetCharacterPosition(u32 entityBits) const;
private:
// 创建时缓存的参数(变更检测 → 重建 body)
struct BodyRecord {
JPH::BodyID id{};
u8 type = 0; // RigidBodyComponent::BodyType
u8 collider = 0; // RigidBodyComponent::ColliderType
glm::vec3 colliderSize{0.0f}; // 碰撞体尺寸缓存(box 半尺寸)
f32 radius = 0.0f; // 球/Capsule/Cylinder 半径(collider 决定语义)
f32 halfHeight = 0.0f; // Capsule/Cylinder 圆柱段半高
std::vector<ECS::RigidBodyComponent::CompoundChild> compound; // 子形状快照
f32 mass = 0.0f;
bool isTrigger = false; // 触发器标记(变更 → 重建 body)
glm::vec3 lastPos{0.0f}; // 静态/运动体编辑器拖动检测
glm::quat lastRot{1.0f, 0.0f, 0.0f, 0.0f}; // 同上(quat 直比,无欧拉往返)
// 加速度箭头用:上一步速度(定步长差分)与平滑后的加速度
glm::vec3 prevVel{0.0f};
glm::vec3 lastAccel{0.0f};
};
void SyncScene(); // 对账:孤儿清理 / 创建 / 参数同步
void Writeback(); // 动态体 → TransformComponent(标脏)+ lastPos/lastRot 对账基线
void UpdateAccels(f32 dt); // 速度差分 → BodyRecord.lastAccel(step 率)
void CollectDebugData(); // 按 m_DebugBits 收集调试图元(渲染帧率)
void CreateBody(u32 entityBits, ECS::RigidBodyComponent& rb,
ECS::TransformComponent& tr);
// 约束对账:owner 孤儿清理 / body 重建传染失效 / 建缺失与变更重建
// invalidated = 本帧 SyncScene 中被销毁或重建 body 的实体位模式
void SyncConstraints(const std::unordered_set<u32>& invalidated);
void CreateConstraint(u32 ownerBits, u32 targetBits,
const ECS::JointComponent& jc);
void RemoveConstraint(u32 ownerBits);
// 角色对账:孤儿清理 / 参数变更重建 / 编辑器拖动传送。
// 角色非刚体(不进 PhysicsSystem),无 body 清理负担
void SyncCharacters();
// 每物理步(PhysicsSystem::Update 之后,Jolt 官方建议序):
// 重力积分 + 扫掠 → 写回 TransformComponent(标脏)
void UpdateCharacters(f32 dt);
// Mesh 碰撞体:实体视觉网格 → Jolt 形状(Static=MeshShape /
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
JPH::RefConst<JPH::Shape> BuildMeshShape(u32 entityBits,
ECS::RigidBodyComponent& rb,
ECS::TransformComponent& tr);
// Compound 碰撞体:子形状列表 → Jolt StaticCompoundShape。
// 空 children 或构建失败返回 null(回退 Box)
JPH::RefConst<JPH::Shape> BuildCompoundShape(ECS::RigidBodyComponent& rb,
ECS::TransformComponent& tr);
void RemoveBody(u32 entityBits);
const BodyRecord* FindRecord(u32 entityBits) const; // 刚体驱动寻址
CharacterController* FindCharacter(u32 entityBits); // 角色门面寻址
void QueueEvent(CollisionEvent&& ev); // 回调线程 append(多线程并发,加锁)
bool TryBitsByBodyId(const JPH::BodyID& id, u32& outBits) const;
// ---- 约束(JointComponent → Jolt TwoBodyConstraint;owner 实体寻址)----
// target 惰性解析 UUID;body 重建/销毁 → 引用它的约束失效重建
struct ConstraintRecord {
JPH::Ref<JPH::Constraint> constraint;
ECS::JointComponent snapshot; // 变更检测 → 移除重建
u32 targetBits = 0;
};
std::unordered_map<u32, ConstraintRecord> m_Constraints;
// ---- 角色(entityBits → CharacterVirtual 封装)----
struct CharacterEntry {
std::unique_ptr<CharacterController> cc;
glm::vec3 lastPos{0.0f}; // 编辑器拖动检测
};
std::unordered_map<u32, CharacterEntry> m_Characters;
ECS::Scene& m_Scene;
Assets::AssetManager* m_Assets = nullptr; // Mesh 碰撞体解析 modelRef
bool m_Enabled = true;
f32 m_Accumulator = 0.0f;
// 层配置实现类在 cpp;接口必须比 PhysicsSystem 活得久
Scope<JPH::BroadPhaseLayerInterface> m_BPLayerInterface;
Scope<JPH::ObjectVsBroadPhaseLayerFilter> m_ObjectVsBPLayerFilter;
Scope<JPH::ObjectLayerPairFilter> m_ObjectVsObjectLayerFilter;
Scope<JPH::TempAllocator> m_TempAllocator;
Scope<JPH::JobSystem> m_JobSystem;
Scope<JPH::PhysicsSystem> m_System;
std::unordered_map<u32, BodyRecord> m_Bodies; // entity 位模式 → body
// BodyID 序号 → entity 位模式(O(1) 反查;ContactListener 回调用)
// 与 m_Bodies 同生共死:CreateBody 同插,RemoveBody/孤儿清理同 erase。
// 主线程在 SyncScene 写、Step 内 Update 阶段不写 → 回调线程只读,无需加锁
std::unordered_map<u32, u32> m_BodyToEntity;
// ---- 碰撞事件收集(多线程回调入队,主线程消费)----
class ContactListenerImpl; // cpp 定义(JPH::ContactListener 派生)
Scope<ContactListenerImpl> m_ContactListener;
std::mutex m_EventMutex; // 保护 m_PendingEvents(回调并发)
std::vector<CollisionEvent> m_PendingEvents; // 最近一次步进的事件
// ---- 物理调试可视化 ----
// 堆上延迟构造:DebugRendererSimple 构造即预烘焙通用几何(走 JPH
// 分配器),必须等 ctor 内 call_once 注册完分配器,见 PhysicsWorld.cpp
Scope<PhysicsDebugDraw> m_DebugDraw; // Jolt 全局调试渲染后端(收集器)
u32 m_DebugBits = 0; // PhysicsDebugBits(默认关,StatsPanel 开关)
};
} // namespace Engine
#endif //HELLOVULKAN_PHYSICSWORLD_H