RAGE 物理引擎(phcore / phbound / phsolver / physics / pharticulated / phbullet / pheffects / phframework)
本页解释 RAGE 底层刚体物理库如何组织:对象如何进入"物理关卡"(phLevelNew),每个物理子步里 phSimulator 按什么顺序做积分、宽相、窄相、接触求解、推离(push)与休眠,碰撞形状(phBound* 族与 BVH)如何编码,材质 ID 如何把游戏语义打包进 64 位整数,以及这些阶段如何被拆成 worker 线程 / SPU 任务。GTA V 的所有车辆、行人胶囊、ragdoll、可破碎物体、布料碰撞和射线检测都建立在这一层之上。
背景与相关页面#
- 游戏层如何创建/配置这套引擎、每帧调度几次子步、碰撞事件如何回到
CEntity:见 game-physics-and-ragdoll.md。 - 可破碎物(
fragInst/fragType/缓存)、布料、玻璃、绳索:见 fragments-cloth-glass-rope.md。 - 车辆专用物理(轮子、悬挂、操控)只在 ../08-vehicles/vehicle-physics-handling-damage.md 展开。
sysTaskManager、线程与渲染线程交接:见 ../01-runtime/threading-and-render-thread.md。- 资源(
pgBase/datResource,bound 与 archetype 都是可资源化对象):见 ../02-rage-core/paging-and-resources.md。 vectormath(Vec3V/ScalarV/Mat34V):见 ../02-rage-core/math-and-containers.md。
术语速查:
| 术语 | 代码实体 | 含义 |
|---|---|---|
| 物理关卡 / level | phLevelNew = phLevelNodeTree<phLooseOctreeNode> | 所有物理实例的注册表 + 宽相 + 松散八叉树空间索引 |
| 实例 | phInst | 一个放进关卡的物理对象:矩阵 + archetype 引用 + level index |
| 原型 | phArchetype / phArchetypePhys / phArchetypeDamp | 共享的碰撞形状(bound)、类型/包含标志、质量/惯量/阻尼 |
| 碰撞体(运动态) | phCollider / phArticulatedCollider | 只有"激活"的对象才有:速度、角速度、push/turn、休眠计数 |
| 流形 | phManifold | 一对(子)形状间的接触点缓存,也是约束的载体 |
| 接触 | phContact | 单个接触点:两侧局部点、法线、深度、累计冲量、材质 |
| 约束 | phConstraintBase 派生类 | 关节/附着/距离等,内部转化为带约束类型的 manifold |
相关源码文件#
| 目录 | 关键文件 | 作用 |
|---|---|---|
rage/base/src/phcore | config.h、constants.h、material*.h/.cpp、workerthread.*、pool.h | 编译期开关、线程/任务配置、材质管理器基类、通用池 |
rage/base/src/phbound | bound.h、boundtypes.inc、boundpolyhedron.h、boundgeom.h、boundbvh.h、boundcomposite.h、OptimizedBvh.h、primitives.h | 碰撞形状族、量化 BVH |
rage/base/src/physics | levelbase.h、levelnew.h/.cpp、simulator.h/.cpp、inst.h、archetype.h、collider.h、manifold.h、contact.h、constraint*.h、collision.cpp、shapetest.h、asyncshapetestmgr.h、sleep.*、lockconfig.h、iterator.h、btAxisSweep3.* | 关卡、模拟器、宽相、中相、约束、形状测试、锁 |
rage/base/src/phsolver | contactmgr.h/.cpp、forcesolver.h/.cpp、forcesolvertables*、applyimpulse*.cpp、preresponse*.cpp、updatecontacts*.cpp | 顺序冲量求解器、分岛/分相并行、破碎 |
rage/base/src/phbullet | ContinuousConvexCollision.*、ConvexIntersector.*、GjkPairDetector.*、MinkowskiPenetrationDepthSolver.*、TrianglePenetrationDepthSolver.*、GjkSimplexCache.*、SpursGjk/ | 从 Bullet 改造的窄相(GJK、穿透深度、CCD) |
rage/base/src/pharticulated | articulatedbody.h、articulatedcollider.h、joint*.h、pharticulated.dtx | Featherstone 关节体(ragdoll、车门等) |
rage/base/src/pheffects | cloth_verlet*.cpp/.h、wind*.h、instbehaviorexplosion.h、instbehaviorliquid.h、spring.h、mouseinput.h | Verlet 布料/绳索内核、风场、实例行为 |
rage/framework/src/phframework | inst.h/.cpp、fraginst.h/.cpp | phfwInst/phfwFragInst:把 fwEntity* 放进 phInst 的 user data |
1. 编译期配置:哪些代码路径真正生效#
phcore/constants.h 决定了 GTA V 用的是哪条物理管线,后文所有流程都以这些取值为前提:
| 宏 | 值 | 影响 | 证据 |
|---|---|---|---|
EARLY_FORCE_SOLVE | 1 | 先用上一子步的接触做速度求解,再做碰撞检测与 push(见 §4) | src/dev_ng/rage/base/src/phcore/constants.h:47-57 |
USE_GEOMETRY_CURVED / USE_GRIDS / USE_RIBBONS / USE_TAPERED_CAPSULE / USE_SURFACES | 全为 0 | 对应 bound 类型被替换为 phRemovedBound 占位 | src/dev_ng/rage/base/src/phcore/constants.h:77-110 |
ENABLE_UNUSED_PHYSICS_CODE | 0 | 关闭旧代码;也导致只有 btAxisSweep3 宽相被编译使用 | src/dev_ng/rage/base/src/phcore/constants.h:119-131 |
USE_NEW_MID_PHASE | 1 | 中相走 phMidphase::ProcessCollisionNew | src/dev_ng/rage/base/src/phcore/constants.h:139 |
USE_STATIC_BVH_REJECTION / USE_PRUNE_NEW_PAIRS | 1 / 1 | 宽相对与静态 BVH 的配对做额外剔除 | src/dev_ng/rage/base/src/phcore/constants.h:170-173 |
PH_MATERIAL_ID_64BIT | 1 && HACK_GTA4 | 材质 ID 为 u64,可打包大量标志(§7) | src/dev_ng/rage/base/src/phcore/constants.h:203-205 |
OCTANT_MAP_SUPPORT_ACCEL | 1 && !__TOOL | 多面体按八分区分类顶点以加速 support 函数;改变资源格式 | src/dev_ng/rage/base/src/phcore/constants.h:184-187 |
PH_SLEEP_DEBUG | __BANK && !__SPU && (!__XENON || !__DEV) | 休眠调试 | src/dev_ng/rage/base/src/phcore/constants.h:209-216 |
GRAVITY | -9.8f | 引擎默认重力常数(游戏会用 SetGravity 覆盖) | src/dev_ng/rage/base/src/phcore/constants.h:34 |
ENABLE_PHYSICS_LOCK | 1 | 关卡/模拟器的多读单写锁开启 | src/dev_ng/rage/base/src/physics/lockconfig.h:12 |
PHLEVEL_NODE_TYPE | 0 | phLevelNew 的节点类型是 phLooseOctreeNode(而非四叉树) | src/dev_ng/rage/base/src/physics/leveldefs.h:10-24 |
phConfig 只保存"每个阶段用几个 worker、主线程是否参与"一类的运行期配置(force solver、contact、integrate velocities/positions、apply pushes、collision 各一组),并提醒:如果设定的 worker 数超过调度器实际能给的数量,求解器会在 Sync 中挂起 src/dev_ng/rage/base/src/phcore/config.h:32-165。VALIDATE_PHYSICS* 只在 __ASSERT && !RSG_ORBIS 时由 phConfig::sm_ValidatePhysics 开关控制 src/dev_ng/rage/base/src/phcore/config.h:216-228。
2. 实例、原型与碰撞体#
2.1 phInst#
phInst(继承 pgBase,可资源化)只有很少的数据:pgRef<phArchetype> m_Archetype、u16 m_LevelIndex(关卡句柄,INVALID_INDEX=0xFFFF)、u16 m_Flags 和 Mat34V m_Matrix;user data 在非"矩阵 W 分量存 user data"配置下是单独的 void* src/dev_ng/rage/base/src/physics/inst.h:569-580。RAGE 级的类型号止于 PH_INST_GLASS,上层从 PH_INST_RAGE_LAST 之后继续编号 src/dev_ng/rage/base/src/physics/inst.h:75-81。
实例标志(14 位留给用户):
| 标志 | 含义 | 证据 |
|---|---|---|
FLAG_OPTIONAL_ITERATIONS | 请求求解器额外迭代(ragdoll 用) | inst.h:376 |
FLAG_DONT_WAKE_DUE_TO_ISLAND / FLAG_SLEEPS_ALONE | 不随休眠岛唤醒 / 不参与岛休眠 | inst.h:377-388 |
FLAG_HIGH_PRIORITY | manifold 从高优先级池分配 | inst.h:378 |
FLAG_NEVER_ACTIVATE | PrepareForActivation 应返回 NULL | inst.h:379 |
FLAG_NO_GRAVITY / FLAG_NO_GRAVITY_ON_ROOT_LINK | 不受重力 / 关节体根链接不受重力(GTA 特有) | inst.h:380-385 |
FLAG_QUERY_EXTERN_VEL | 求解器查询"外部控制速度"(动画驱动物体) | inst.h:386 |
FLAG_INTERNAL_USE_ONLY_LAST_MTX | 关卡为其分配了 last matrix | inst.h:387 |
FLAG_USER_0 | 用户标志起点(1<<9) | inst.h:389 |
phInst 的扩展点全部是(非 SPU 下的)虚函数:ShouldFindImpacts(是否做窄相)、PreComputeImpacts(修改/禁用接触)、PrepareForActivation/OnActivate/PrepareForDeactivation/OnDeactivate、GetExternallyControlledVelocity、ApplyImpulse/NotifyImpulse、ModifyImpulseMag、IsBreakable、GetNMAgentID、ReportMovedBySim 等 src/dev_ng/rage/base/src/physics/inst.h:226-499。游戏的 phInstGta / fragInstGta / fragInstNMGta 正是通过覆写它们实现游戏规则(见 game-physics-and-ragdoll.md)。
phframework 是这一层与 framework 实体之间唯一的胶水:phfwInst 和 phfwFragInst 在构造时 SetEntity(NULL),SetEntity 把 fwEntity* 写进 user data 并设置 FLAG_USERDATA_ENTITY(等于 phInst::FLAG_USER_0,编译期断言保证),phfwInst 还把类型号存成成员 m_type 由 GetClassType 返回 src/dev_ng/rage/framework/src/phframework/inst.h:19-46、src/dev_ng/rage/framework/src/phframework/inst.cpp:11-26、src/dev_ng/rage/framework/src/phframework/fraginst.cpp:15-34。两者析构时断言已不在关卡中。
2.2 phArchetype 家族#
phArchetype(ARCHETYPE)→ phArchetypePhys(ARCHETYPE_PHYS,加质量/逆质量、重力系数、最大线/角速度、浮力系数、角惯量)→ phArchetypeDamp(ARCHETYPE_DAMP,6 组阻尼常数:线/角 × 常数/速度/速度平方)src/dev_ng/rage/base/src/physics/archetype.h:110-117、archetype.h:683-710、archetype.h:783-837。基类持有 pgOwner<phBound> m_Bound、u32 m_TypeFlags、u32 m_IncludeFlags、u16 m_PropertyFlags(PROPERTY_PRIORITY_PUSHER 等)和 mutable u16 m_RefCount archetype.h:376-405。
碰撞过滤的双向规则:两个对象只有当 (typeA & includeB) != 0 且 (typeB & includeA) != 0 时才会碰撞/被查询命中 src/dev_ng/rage/base/src/physics/levelnew.h:2435-2442。关卡在 phObjectData 中按 level index 缓存每实例的 type/include 标志,可与 archetype 上的值不同 levelnew.h:817-819,并提供 SetInstanceTypeAndIncludeFlags 等修改接口 levelnew.h:1129-1140。GTA 的标志位定义(ArchetypeFlags::GTA_*)见 game-physics-and-ragdoll.md("碰撞层"一节)。
2.3 phCollider#
phCollider 是"激活对象的运动控制器":持有实例指针、运动参数与可选的 phSleep;不传 phSleep 的碰撞体永远保持激活 src/dev_ng/rage/base/src/physics/collider.h:60-90。类型只有三种:TYPE_RIGID_BODY、TYPE_ARTICULATED_BODY、TYPE_ARTICULATED_LARGE_ROOT collider.h:73-78。主要状态字段包括 m_Matrix、m_Velocity/m_AngVelocity、位置修正量 m_Push/m_Turn、上一帧速度、m_LastInstanceMatrix/m_LastSafeInstanceMatrix、累计力/扭矩/冲量、m_ApproximateRadius、m_ExtraAllowedPenetration 等 collider.h:980-1157;EARLY_FORCE_SOLVE 下还有 m_NeedsCollision 位,驱动 push 迭代是否需要再做一次碰撞 collider.h:1137。
模拟器自己管理一个固定大小的碰撞体池(m_ManagedColliders + 并行的 m_ManagedSleeps 与可用索引栈),phInst::PrepareForActivation 也可以返回自带的碰撞体(例如 fragInst 的 phArticulatedCollider)src/dev_ng/rage/base/src/physics/simulator.cpp:696-722。
3. 物理关卡 phLevelNew#
3.1 对象状态与存储布局#
phLevelBase 定义四种对象状态:OBJECTSTATE_ACTIVE、OBJECTSTATE_INACTIVE、OBJECTSTATE_FIXED、OBJECTSTATE_NONEXISTENT,以及两个"非激活也参与碰撞"的覆盖位 COLLISIONSTATE_VS_INACTIVE、COLLISIONSTATE_VS_FIXED src/dev_ng/rage/base/src/physics/levelbase.h:71-103。计数器全部是 u16(m_MaxObjects、m_MaxActive、m_NumObjects/Fixed/Inactive/Active)levelbase.h:248-257。
证据:添加接口 levelnew.h:1408-1446(注释写明游戏代码应调用 PHSIM->AddActiveObject,关卡的 AddActiveObjectFromSimulator 只供模拟器用);删除接口 levelnew.h:1451-1478;激活(需 PrepareForActivation 同意并分配 collider)/去激活(休眠或显式,需 PrepareForDeactivation 同意)见 §4.4。
每个 level index 对应一个 phObjectData,布局非常紧凑 src/dev_ng/rage/base/src/physics/levelnew.h:811-933:
| 字段 | 编码 |
|---|---|
Vec4V m_CachedCenterAndRadius | 包围球(XYZ 中心、W 半径) |
u32 m_CachedArchIncludeFlags/TypeFlags | 每实例过滤标志 |
size_t m_uPackedInstAndState | 低 2 位 = 状态;bit2/bit3 = vs-inactive / vs-fixed 覆盖;& ~15 = phInst*(实例必须 16 字节对齐) |
u16 m_uOctreeNodeIndex | 低 13 位 = 所在八叉树节点(8191 保留表示"在关卡范围外");高 3 位 = 作为"访客对象"时的子节点号 |
u16 m_uNextObjectInNodeIndex | 节点内单链表 |
Init 一次性分配所有并行数组:m_paObjectData[m_MaxObjects]、generation ID(同一 index 每次复用 +1,溢出回绕)、休眠岛 ID、空闲 index 栈、user data 指针(激活对象的 user data 就是其 phCollider*,见 DeactivateObject 中 (phCollider*)m_Level->GetUserData(...))、激活列表 m_pauActiveObjectLevelIndex[m_MaxActive]、八叉树节点池、last matrix 表 src/dev_ng/rage/base/src/physics/levelnew.cpp:716-795、levelnew.h:2199-2216、src/dev_ng/rage/base/src/physics/simulator.cpp:6516。
Last matrix:只有一部分实例拥有"上一帧矩阵"(用于 CCD 扫掠与速度推导)。PC/Durango/Orbis 上最多 MAX_INST_LAST_MATRICES = 264 个,索引表用 u16;其它平台为 0xFE、索引表用 u8 levelnew.h:1076-1081、levelnew.cpp:751-766。
3.2 宽相:SAP 为主,松散八叉树为查询服务#
关卡同时维护两种结构:
- 宽相
phBroadPhase:BroadPhaseType枚举有AXISSWEEP3/AXISSWEEP1/LEVEL/SPATIALHASH/NXNlevelnew.h:953,但在ENABLE_UNUSED_PHYSICS_CODE=0时InitBroadphase无条件创建btAxisSweep3(三轴 sweep-and-prune),其它分支不编译levelnew.cpp:683-707。因此游戏命令行-phbroadphase的取值在当前配置下实际无效(推断:SetBroadPhaseType只改成员,InitBroadphase不读取它)。宽相维护"pruned pairs"数组,每对带一个phManifold*。 - 松散八叉树(
phLooseOctreeNode,分支因子 8,节点外扩边界 = 2 × 半宽levelnew.h:405-469),最大深度knMaxOctreeDepth = 18levelnew.h:955,节点数受 13 位编码限制不得超过 8191levelnew.h:1032-1035。节点在"待入对象数 ≥ create 阈值"时分裂、"≤ collapse 阈值"时合并,默认 5/3,可用-createchild/-collapsechild覆盖levelnew.cpp:593-605、levelnew.h:1037-1050。它服务于形状测试/剔除迭代(phIterator、CullObjects、TestProbe等)以及激活时的初始配对查找(FindAndAddOverlappingPairs)levelnew.cpp:2214、levelnew.cpp:4388-4558。LEVELNEW_USE_EVERYWHERE_NODE(GTA 打开)为越界对象提供一个"处处节点"levelnew.h:50、levelnew.cpp:3456-3464。
SetExtents 取最大分量作为立方体关卡;注释说明约 200km 以内碰撞精度可接受,再大就要改成局部坐标系 levelnew.h:1010-1030。
状态过滤:宽相配对只有在"两者都存在,且至少一方 ACTIVE,或某方的覆盖位与另一方状态匹配"时才保留。GTA 编译的是位运算版本(#if 1),其语义与下方 #else 的可读版本等价 levelnew.h:2581-2644。MATCH_FLAGS_FOR_PAIRS 为 0,所以 type/include 匹配不在这里,而是推迟到 ProcessOverlaps(§4.3)levelnew.h:2575。
3.3 延迟八叉树更新与锁#
移动物体时 UpdateObjectLocation 先更新宽相 AABB;若开启了延迟八叉树更新,就把 level index 放入去重位集 + 列表,而不是立刻重排八叉树;否则直接在 g_GlobalPhysicsLock 写锁下 UpdateObjectPositionInOctree src/dev_ng/rage/base/src/physics/levelnew.cpp:1756-1798。列表满(容量由 SetMaxDeferredInstanceCount 决定)时会就地 CommitDeferredOctreeUpdates——注释承认这可能造成停顿 levelnew.cpp:1663-1709。SetOnlyMainThreadIsUpdatingPhysics(true) 时跳过列表的临界区(只允许创建线程写),这是游戏在 pre/post-physics 回调期间打开的优化开关 levelnew.h:1520-1521。模拟器 Init 默认打开延迟八叉树与延迟复合 BVH 更新 simulator.cpp:643-652。延迟复合 BVH 句柄数组预留 PHLEVELNEW_MAX_DEFERRED_BVH_UPDATE_HANDLES = 300 levelnew.cpp:62。
全局锁 g_GlobalPhysicsLock 是 phMultiReaderLockToken(多读单写)src/dev_ng/rage/base/src/physics/levelbase.cpp:20;phIterator 构造时可选择读锁/写锁/不加锁 src/dev_ng/rage/base/src/physics/iterator.h:37-43;LOCKCONFIG_WRITERS_ONLY_WAIT_FOR_CURRENT_READERS 防止写者饥饿 lockconfig.h:34-36。ActivateObject/DeactivateObject 都以 PHLOCK_SCOPEDWRITELOCK 开头 simulator.cpp:6375、simulator.cpp:6510。
4. 模拟器 phSimulator#
4.1 初始化与容量#
phSimulator::Init(level, InitParams) 先允许命令行(-maxmanagedcolliders、-maxmanifolds、-maxcontacts、-maxconstraints、-scratchpadsizekb、-maxsleepislands 等)覆盖 InitParams,然后分配 src/dev_ng/rage/base/src/physics/simulator.cpp:654-778:
| 资源 | 分配 | 上限/备注 |
|---|---|---|
| 托管碰撞体 | phCollider[maxManagedColliders] + phSleep[] | 断言 ≤ level->GetMaxActive() |
| manifold 池 | phPool<phManifold>(maxManifolds),带 high-priority 配额 | 断言 < 65535 |
| contact 池 | phPool<phContact>(min(65534, maxContacts)) | |
| 复合指针池 | phPool<phCompositePointers>(maxCompositeManifolds) | 每个复合 manifold 最多 84 个子对 compositepointers.h:24 |
| 重叠对数组 | phOverlappingPairArray(maxManifolds, ...) | 注释:"pair 永远不会多于 manifold" |
| push 对数组 | 两个 DEFAULT_MAX_PUSH_PAIRS = 256 的数组(A/B 交替) | simulator.cpp:632 |
| 接触管理器 | phContactMgr(scratchpadSize, maxExternVelManifolds, pairArray) | scratchpad 也可作为全局 scratch allocator |
| 约束管理器 / 休眠管理器 | phConstraintMgr、phSleepMgr(sleepBufferSize, maxSleepIslands) | |
| 延迟 SAP 队列 | 预留 DELAYED_SAP_MAX = 512 | simulator.cpp:137 |
GTA 实际传入的数值与计算公式见 game-physics-and-ragdoll.md("CPhysics::InitCore:容量与配置"一节)。
InitClass 在未指定时以 sysTaskManager::GetNumThreads() 作为 worker 数;-nomtphys=<stage> / -mtphys=<stage> 可以单独关闭/只开启某一阶段(forcesolver、contact、integratevelocities、integratepositions、applypushes、collision);非 __OPTIMIZED 构建默认不用 worker simulator.cpp:829-945。
4.2 单个子步:Update → CoreUpdate#
phSimulator::Update(timeStep, finalUpdate) 先兜底"某阶段 worker 数为 0 且主线程不参与"的配置(强制主线程参与,避免无人干活),清空 phArticulatedCollider 激活计数,然后把复合 BVH 更新切换为"立即"模式,调用 CoreUpdate,最后再发起一次延迟八叉树提交任务 src/dev_ng/rage/base/src/physics/simulator.cpp:7416-7560。finalUpdate 由游戏决定(默认只在最后一个时间片做 push 与休眠,见游戏页)。
EARLY_FORCE_SOLVE 版 CoreUpdate 的顺序 simulator.cpp:7199-7350:
图中每个节点对应 CoreUpdate 中的调用:simulator.cpp:7213-7241(A–F),simulator.cpp:7261-7317(G–L,循环在 !sm_EnablePushes || !finalUpdate 时于首次碰撞后 break),simulator.cpp:7324-7341(K–O);g_MaxPushCollisionIterations = 2 simulator.cpp:7197。延迟 SAP 提交被刻意放在速度求解之后、位置积分之前:注释说明这样刚破碎出的物体不会被立即剪掉,且 SAP AABB 不会缺少首帧运动扩展 simulator.cpp:7216-7233。
要点(与教科书顺序不同):GTA 的速度约束求解在碰撞检测之前,使用的是上一子步留下的 manifold;本子步检测到的新接触,仅用于(finalUpdate 时的)位置推离(push/turn)、激活与事件回调,然后在下一子步的 VelocitySolve 中参与速度求解。IntegrateVelocities/IntegratePositions 分别是 UpdateCollidersTask 的两种模式,通过 m_UpdateCollidersTasks 分发到 worker,主线程可参与 simulator.cpp:6583-6599、simulator.cpp:6838-6875;VelocitySolve 与 PushSolve 都调用 phContactMgr::Update,区别是前者临时关闭 push、后者把 push 对中的碰撞体标记并保存/清零关节体速度 simulator.cpp:6827-6905。IntegratePositions 结束后对每个激活对象调用 UpdateObjectLocationAndRadius,并在 sm_ReportMovedBySim 时回调 ReportMovedBySim。
4.3 碰撞检测:CollideActive 与 ProcessOverlaps#
CollideActive src/dev_ng/rage/base/src/physics/simulator.cpp:4267-4480:
- 发起延迟八叉树提交任务(注释:碰撞引发的激活要用关卡剔除寻找初始配对,所以关卡必须最新)。
PruneNewOverlappingPairs,取宽相 pruned pairs,sm_SortPairsByCost时按代价排序。InitiateCollisionJobs启动碰撞 worker(它们阻塞在g_PairConsumeToken上;作为 B*2078547 挂起问题的绕行方案,先加锁再创建任务)simulator.cpp:4670-4772。- 主线程
ProcessOverlaps逐对过滤并把合格对追加到phOverlappingPairArray;worker 一边从数组消费、一边做中相/窄相(生产者-消费者,allPairsReady标志宣告结束)。 - 同时主线程做"额外工作":
m_ConstraintMgr->UpdateConstraintContacts(1/dt, addToSolver)——约束在此处被转换为 constraint 型 manifold;push 迭代中不再加入求解器。 FlushCollisionJobs:主线程也跑ProcessPairListTask后等待全部 worker。DoPreComputeImpacts(调用phInst::PreComputeImpacts/phInstBehavior::PreComputeImpacts,允许游戏禁用接触),然后CollisionActivations(被激活物体撞到的非激活物体在此被激活)。注释特别说明 PreComputeImpacts 必须在 FindPushPairs 与激活之前,以免被禁用的接触仍导致 push/激活。
ProcessOverlaps 对每个宽相对依次执行 simulator.cpp:3071-3500:
| 步骤 | 拒绝条件 | 证据 |
|---|---|---|
| 读取状态与 type/include 缓存 | 任一方 NONEXISTENT | simulator.cpp:3119-3139 |
ShouldCollideByState | 状态组合不允许 | simulator.cpp:3142-3148 |
MatchFlags | 双向 type/include 不匹配 | simulator.cpp:3173-3178 |
| 静态 BVH 剔除 | 动态物体包围盒与静态 BVH 的缓存球/OBB 测试不重叠 | simulator.cpp:3185-3345 |
| 自碰撞 | 同一实例但不是关节体 | simulator.cpp:3457-3463 |
ShouldFindImpacts | 任一方返回 false(游戏规则) | simulator.cpp:3466-3481 |
phInstBehavior::CollideObjects | 任一方的实例行为返回 false | simulator.cpp:3491-3499 |
被拒绝的对会释放其 manifold(DELETE_BP_MANIFOLD),因此 manifold 生命周期与"宽相对 + 这几层过滤"绑定。
4.4 激活、去激活与休眠#
ActivateObject(levelIndex, inst, collider, otherInst, constraint) src/dev_ng/rage/base/src/physics/simulator.cpp:6373-6489:
- 仅对 INACTIVE 对象生效;关卡激活数已满(
CheckAddActiveObjects)则失败;实例行为ActivateWhenHit()为 false 则失败。 - 防重入:若在某实例的
PrepareForActivation内部再次激活同一实例,直接返回(防止激活列表出现重复 index)。 - 调用
inst->PrepareForActivation(&collider, otherInst, constraint);返回 NULL 表示拒绝;返回值可以是另一个实例。 - 若实例未提供碰撞体则从托管池取一个,池空时断言"可忽略"并失败。
- 写入 last instance matrix,
m_Level->ActivateObject,OnActivate;关节体额外加入自配对(i,i);若启用休眠岛则唤醒整个岛。
DeactivateObject 先问 PrepareForDeactivation(或 forceDeactivate),再把托管碰撞体归还池 simulator.cpp:6508-6534。
phSleep 的状态机为 ASLEEP / AWAKE / DONE_SLEEPING src/dev_ng/rage/base/src/physics/sleep.h:33,默认阈值:连续 15 个"静止 tick"、睡满 20 tick;速度平方阈值 0.005、角速度平方 0.01、内部运动 0.2 src/dev_ng/rage/base/src/physics/sleep.cpp:38-47。游戏会按当前重力与时间片重新缩放这些阈值(见游戏页)。phContactMgr::sm_UseSleepIslands = true:接触相连的一组物体作为"休眠岛"一起睡、一起醒 src/dev_ng/rage/base/src/phsolver/contactmgr.cpp:102。
4.5 实例行为 phInstBehavior#
phInstBehavior 是挂在某个 level index 上的插件:Reset、IsActive、PreUpdate、Update、CollideObjects(可否决碰撞)、PreComputeImpacts、ActivateWhenHit、IsForceField src/dev_ng/rage/base/src/physics/instbehavior.h:41-91。模拟器按 level index 维护查找表(PHSIM_OPT_INSTBEHAVIOR_LEVELINDEX_ARRAY)simulator.cpp:768-774。用例:爆炸(phInstBehaviorExplosion,游戏派生 phInstBehaviorExplosionGta)、液体、弹簧、环境布料(phEnvClothVerletBehavior)。
5. 接触管理与求解器(phsolver)#
5.1 phContactMgr::Update#
src/dev_ng/rage/base/src/phsolver/contactmgr.cpp:334-381:设置求解器全局量(invTimeStep、允许穿透及其一半)→(非 push 时)UpdateContactsExternVelocity → UpdateContacts(刷新接触世界坐标、深度、bounce/切向)→ SolveConstraints。
SolveConstraints contactmgr.cpp:1663-1736:
SolveImpulsesPhase1And2:若存在"世界约束",先sm_LCPSolverIterations次迭代 →EnforceWorldConstraints→ 重新UpdateContacts→ 再sm_LCPSolverIterationsFinal次;否则一次性跑两者之和;最后PerformExtraConstraintIterations(sm_LCPSolverIterationsExtra)只迭代标记了额外迭代的约束contactmgr.cpp:1738-1763。- 破碎(仅速度求解阶段):最多
MAX_NUM_BREAKS = 8轮BreakObjects→UpdateContacts→ 重解;超过则Warningf;之后BreakConstraints处理可断约束。 - 可选迭代:收集包含
FLAG_OPTIONAL_ITERATIONS的岛(ragdoll),SplitPairs后再跑sm_LCPSolverIterationsOptional次。
默认调优常量(游戏会覆盖迭代数,见游戏页)contactmgr.cpp:88-106:
| 常量 | 默认 | 含义 |
|---|---|---|
sm_LCPSolverIterations / Final | 4 / 4 | 主迭代数 |
sm_LCPSolverIterationsExtra / Optional | 6 / 4 | 额外约束迭代 / ragdoll 可选迭代 |
sm_SeparationBias | 0.2 | 非 push 时的分离偏置 |
sm_MinBounce | 1.0 | 低于此相对速度不反弹 |
sm_MaxTurn | 0.1(EARLY_FORCE_SOLVE) | 每步最大 turn 修正 |
sm_BreakImpulseScale | 0.4 | 破碎冲量缩放 |
sm_MaxPairsPerPhase / sm_MaxArticulatedPerPhase | phForceSolver::MAX_BATCHES_NUM(20) / 2 | 每个并行 phase 的批大小 |
5.2 并行顺序冲量:phForceSolver#
求解前重叠对被划分为若干"并行组"(推断:同一 phase 内的 manifold 不共享碰撞体,依据 ParallelGroup/phase 结构与 SplitPairs 命名,算法未逐行阅读)(ParallelGroup,每组最多 MAX_BATCHES_NUM = 20 个 manifold;任务数上限 MAX_TASKS_NUM = 32)src/dev_ng/rage/base/src/phsolver/forcesolver.h:241-318。worker 用原子 AllocatePhase/ReleasePhase 领取 phase 并在信号量上同步 contactmgr.cpp:1766-1790。游戏把最大 phase 数设为 1024。
每个 manifold 的冲量计算由一张函数表分派:行 = 3 × NUM_CONSTRAINT_TYPES,列 = 3(注释:fixed / rigid / articulated)forcesolver.h:38-42、forcesolver.h:322-323。phsolver/ 下大量 applyimpulse*.cpp、applyimpulseandpush*.cpp、preresponse*.cpp、updatecontacts*.cpp(以及 PS3 的 .frag 代码片段)就是这张表按"阶段 × 约束变体(无/FixedPoint/FixedRotation/SlideRotation/PivotRotation)× A 类型 × B 类型(Fix/Mov/Art)"的展开。manifold 的约束类型枚举为 NO_CONSTRAINT_CONTACT / FIXED_POINT / SLIDING_POINT / FIXED_ROTATION / SLIDING_ROTATION / PIVOTING_ROTATION src/dev_ng/rage/base/src/physics/manifold.h:70-77。
5.3 Manifold 与 Contact 数据#
phManifold 最多缓存 MANIFOLD_CACHE_SIZE = 6 个接触(注释称 7–8 对堆叠更稳)manifold.h:79,字段包括 m_Contacts[6]、复合子 manifold 指针、两侧 phInst*/phCollider*/phBound*、m_MassInvScale[2]、level index 与 generation id、component、约束类型、m_UsePushes 及若干计时 manifold.h:730-786。复合 bound 的配对,根 manifold 通过 phCompositePointers 持有每个"子组件对"的子 manifold manifold.h:106-200。分离阈值:竖直 0.04m、水平 0.02m 以上的已分离接触被移除 manifold.h:49-53。
phContact 大量把标量塞进向量 W 分量:如 m_LocalPointAXYZfrictionW、m_LocalPointBXYZelasticityW、m_WorldNormalXYZmaterialWA、m_DinvCol0materialWB、m_PrevPushXYZdepthW、m_TargetRelVelocityXYZsepBiasW,外加 m_Element[2]、m_Lifetime、m_ActiveContact、m_PositiveDepth src/dev_ng/rage/base/src/physics/contact.h:301-333。这表明 64 位材质 ID 被拆成两半存进两个 W 分量随接触携带(推断:依据字段命名 materialWA/WB,未逐行验证打包代码)。
5.4 破碎流程(引擎侧)#
BreakObjects 在 scratchpad 后半区分配"每对象每组件冲量/位置"数组和新的 breaking pairs 数组,空间不足直接放弃并 Warningf contactmgr.cpp:2227-2289;然后按岛调用 BreakObjectsInIsland:FindWeakestInst 通过 phInstBreakable::FindBreakStrength 找出最弱的实例 → BreakApart(breakData) → RevertImpulses 回退该岛速度 → 对新实例 ApplyPostBreakingImpulses contactmgr.cpp:2161-2218。phInstBreakable 的虚接口见 src/dev_ng/rage/base/src/physics/instbreakable.h:52-95;fragInst 对它的实现见 fragments-cloth-glass-rope.md。游戏通过 SetAllowBreakingCallback 可整体禁止破碎。
6. 碰撞形状 phBound*#
6.1 类型族#
bound 类型由 boundtypes.inc 的 X-macro 生成,数值被资源文件依赖(注释要求新类型只能追加编号)src/dev_ng/rage/base/src/phbound/bound.h:125-141、src/dev_ng/rage/base/src/phbound/boundtypes.inc:1-35:
| 值 | 类 | GTA 构建中 |
|---|---|---|
0 SPHERE | phBoundSphere | 启用 |
1 CAPSULE | phBoundCapsule | 启用 |
| 2 | phBoundTaperedCapsule | 移除(USE_TAPERED_CAPSULE=0) |
3 BOX | phBoundBox | 启用 |
4 GEOMETRY | phBoundGeometry : phBoundPolyhedron | 启用(凸多面体/网格) |
| 5 | phBoundCurvedGeometry | 移除 |
| 6 / 7 | phBoundGrid / phBoundRibbon | 移除 |
8 BVH | phBoundBVH : phBoundGeometry | 启用(地图碰撞、复杂静态网格) |
| 9 | phBoundSurface | 移除 |
10 COMPOSITE | phBoundComposite | 启用(车辆、frag、ragdoll) |
11 TRIANGLE | TriangleShape | 窄相内部使用 |
12 DISC / 13 CYLINDER | phBoundDisc / phBoundCylinder | 启用 |
| 14 | 已移除的 probe | 占位 |
15 PLANE | phBoundPlane | 启用 |
NUM_BOUND_TYPES = 15(注释说明 pheffects/morphgeometry 借用这个值)。所有 bound 的公共数据:u8 m_Type、u8 m_Flags(含 FORCE_CCD_THIS_BOUND、USE_CURRENT_INSTANCE_MATRIX_ONLY 等)、u16 m_PartIndex、m_RadiusAroundCentroid、AABB(max 的 W 存 margin、min 的 W 存引用计数)、质心偏移与重心偏移(W 分量分别存材质 ID 的两半)bound.h:674-720。项目可以通过 sm_CustomConstructor / sm_CustomResourceConstructor 注入自定义 bound 加载 bound.h:665-670。
6.2 多面体、几何体与压缩顶点#
游戏构建(!__TOOL)使用 COMPRESSED_VERTEX_METHOD = 2:顶点以 3×s16 量化存储,用 m_UnQuantizeFactor 与 m_BoundingBoxCenter 解码;工具构建一律不压缩 src/dev_ng/rage/base/src/phbound/boundpolyhedron.h:54-69、boundpolyhedron.h:533-581。还保存"按 margin 内缩的顶点"(m_CompressedShrunkVertices)供带 margin 的 GJK 使用,以及八分区顶点表 m_OctantVertCounts/m_OctantVerts 加速 support 映射 boundpolyhedron.h:541-615。多边形索引为 u16、每个多边形 3 个顶点(POLY_MAX_VERTICES=3),并存邻接多边形用于边缘法线过滤 src/dev_ng/rage/base/src/phcore/constants.h:43-45、src/dev_ng/rage/base/src/phbound/primitives.h:509-519。phBoundGeometry 额外持有材质表:m_MaterialIds(64 位 ID)、每多边形 u8 材质索引 m_PolyMatIndexList、材质颜色表(HACK_GTA4_64BIT_MATERIAL_ID_COLORS)以及"第二表面"顶点位移数据 src/dev_ng/rage/base/src/phbound/boundgeom.h:301-399。
6.3 BVH 结构#
phBoundBVH 派生自 phBoundGeometry,额外拥有 datOwner<phOptimizedBvh> m_BVH src/dev_ng/rage/base/src/phbound/boundbvh.h:33-135。BVH 里的"多边形"实际是统一的 phPrimitive,类型可为 POLYGON / SPHERE / CAPSULE / BOX / CYLINDER primitives.h:32-39——这使地图静态碰撞能混合三角形与解析体(例如植被碰撞用球/胶囊,见游戏页 GetFoliageRadius)。
phOptimizedBvh 是从 Bullet 改写的量化 AABB 树 src/dev_ng/rage/base/src/phbound/OptimizedBvh.h:141-312:
- 节点
phOptimizedBvhNode:s16 m_AABBMin[3]/m_AABBMax[3](量化坐标,由整体m_AABBCenter/m_Quantize换算),m_NodeData在叶子上是首个图元索引、在内部节点上是"escape index"(无栈遍历的跳转),u8 m_PolygonCount为 0 表示内部节点。 - 节点连续存储于
m_contiguousNodes,构建时按MAX_SUBTREE_SIZE = 2048字节切出子树并生成 16 字节的phBVHSubtreeInfo头(根索引/尾后索引/量化 AABB)OptimizedBvh.h:64、src/dev_ng/rage/base/src/phbound/OptimizedBvh.cpp:719-735。子树切分的用途从结构看是便于按块 DMA/缓存(推断:注释在此处被截断,未见明确说明)。
6.4 复合 bound#
phBoundComposite 保存子 bound 指针数组、每部件当前/上一帧矩阵(Mat34V*,CCD 需要)、部件局部 AABB、可选的每部件 type/include 标志,以及(PHBOUNDCOMPOSITE_BUILD_BVH=1)一棵以部件为图元的 BVH src/dev_ng/rage/base/src/phbound/boundcomposite.h:21、boundcomposite.h:390-420。复合 BVH 在部件移动后需要更新/重建:关卡提供 UpdateCompositeBvh / RebuildCompositeBvh 及其延迟版本和线程安全的 CompositeBoundSet*ThreadSafe 包装 src/dev_ng/rage/base/src/physics/levelnew.cpp:1850-2028。
7. 材质系统#
phMaterialMgr 是抽象单例:Id 在 PH_MATERIAL_ID_64BIT 下为 u64,DEFAULT_MATERIAL_ID = 0,MATERIAL_NOT_FOUND = Id(-1) src/dev_ng/rage/base/src/phcore/materialmgr.h:52-68。GetMaterial(id) 先用 m_MaterialIndexMask 去掉高位标志再按 m_MaterialSize 步长索引材质数组——所以材质数组元素可以是派生类(phMaterialGta)materialmgr.h:250-256。
两种材质的合成规则 src/dev_ng/rage/base/src/phcore/materialmgr.cpp:40-69、materialmgr.h:22-23、materialmgr.h:258-265:
- 用掩码取出两个材质索引并排序;线性扫描
m_MaterialOverridePairs,命中则直接使用覆盖值。 - 否则摩擦 =
fA * fB,弹性 =clamp(eA * eB, 0, 0.9)。
RAGE 还提供带标志位的 phMaterialMgrFlag(在 ID 高位存标志)src/dev_ng/rage/base/src/phcore/materialmgrflag.h:45-115,但 GTA 用的是直接派生自 phMaterialMgr 的 phMaterialMgrGta,自定义 64 位打包(见 game-physics-and-ragdoll.md"材质"一节)。材质数组和数量通过 FillInSpuWorkUnit 传给碰撞任务 simulator.cpp:4734。
8. 中相与窄相#
8.1 中相 phMidphase#
碰撞 worker 对每个 pair 调用 phMidphase::ProcessCollision(USE_NEW_MID_PHASE 下走 ProcessCollisionNew),关节体自碰撞走 ProcessSelfCollision,复合 bound 的部件对通过 ComponentInfo / MidPhaseIntersector 做 OBB 测试后进入叶子级 ProcessLeafCollision;BVH 对的叶子以 PrimitiveTriangle/Sphere/Capsule/Box/Cylinder 的 Setup/TestOverlap/Collide/ProcessResult 处理 src/dev_ng/rage/base/src/physics/collision.cpp:2887-3809、collision.cpp:4060-4252、collision.cpp:5693-6120。三角形接触还会利用邻接多边形计算邻居法线以过滤内部边(PrefetchNeighborsAndComputeNeighborFlags)collision.cpp:2156-2200。SPU 版本在同一文件中通过 __SPU 分支 DMA 压缩顶点(cellDmaLargeGet)。
8.2 窄相 phPairwiseCollisionProcessor#
单对凸体的入口 collision.cpp:2121-2152:
- 若
UseSpecialCaseCollision(boundA, boundB)为真(box-box、capsule-capsule、capsule-triangle、disc-triangle、box-triangle 等解析距离算法,__BANK下可逐个关闭),调用ContinuousConvexCollision::ComputeTimeOfImpactsrc/dev_ng/rage/base/src/phbullet/ConvexIntersector.h:145-215。 - 否则调用
ComputeTimeOfImpactGJK。ContinuousConvexCollision注释写明它实现基于 Mirtich 保守前进(conservative advancement)的线性+角运动 TOI,在世界空间迭代、内部使用 GJKsrc/dev_ng/rage/base/src/phbullet/ContinuousConvexCollision.h:81-98。
穿透深度:GJK 发现相交(m_needsPenetrationSolve)时,三角形情形可用 TrianglePenetrationDepthSolver,其余用 MinkowskiPenetrationDepthSolver——注释说明它是"用 support 映射采样估计深度、再用 GJK 求见证点"的暴力法;帧持久 GJK 缓存命中时可直接复用分离方向 src/dev_ng/rage/base/src/phbullet/ConvexIntersector.cpp:180-202、src/dev_ng/rage/base/src/phbullet/MinkowskiPenetrationDepthSolver.h:21-23。btGjkEpa*(EPA)代码虽被编译(ENABLE_EPA_PENETRATION_SOLVER_CODE=1),但在检索到的调用点中只出现在 btGjkEpaPenetrationDepthSolver 自身与 btConvexConvexAlgorithm 的注释里 src/dev_ng/rage/base/src/phbullet/btConvexConvexAlgorithm.cpp:137;同样,btCollisionDispatcher/bt*CollisionAlgorithm 只被 phbullet 内部引用,未见 physics/ 调用。这表明 Bullet 的 dispatcher/EPA 是遗留代码、主路径不用(推断:基于引用检索,未逐个构建配置验证)。
GJK 单纯形缓存 USE_GJK_CACHE = 1 src/dev_ng/rage/base/src/phbullet/GjkSimplexCache.h:15;游戏每帧所有时间片结束后调用 GJKCacheSystemPostCollisionUpdate。phbullet/SpursGjk/ 是 PS3 SPU 版本。
9. 约束(phConstraint*)#
phConstraintBase::Type:基本型 HALFSPACE(去 1 自由度,单侧)、DISTANCE(1,单侧)、SPHERICAL(3)、ROTATION(1,单侧)、FIXEDROTATION(3),组合型 ATTACHMENT、PRISMATIC、HINGE、FIXED、CYLINDRICAL(2)src/dev_ng/rage/base/src/physics/constraintbase.h:36-49。参数基类 Params 统一描述两侧实例/组件(instanceB=NULL 即钉在世界上)、质量逆缩放(设 0 模拟"对方无限重")、可断裂及强度(默认 10000)、是否用 push、额外迭代、分离偏置 constraintbase.h:51-120。具体类分别位于 constraintattachment.h、constraintcylindrical.h、constraintdistance.h、constraintfixed.h、constraintfixedrotation.h、constrainthalfspace.h、constrainthinge.h、constraintprismatic.h、constraintrotation.h、constraintspherical.h。
phConstraintMgr::Insert(params) 返回 phConstraintHandle{u16 index; u16 generation;},generation 0 永远无效,用于检测悬空句柄 src/dev_ng/rage/base/src/physics/constraintmgr.h:52-76、src/dev_ng/rage/base/src/physics/constrainthandle.h:13-19。约束在每次 CollideActive 中由 UpdateConstraintContacts 生成约束型 manifold/contact,交给同一个求解器处理(§4.3),可断约束在 SolveConstraints 末尾 BreakConstraints。
10. 关节体(pharticulated)#
模块文档说明这是 Featherstone 方法的改编版,与接触力集成,支持部分肢体动画、部分物理,也支持固定根的关节体(如柔性桥)src/dev_ng/rage/base/src/pharticulated/pharticulated.dtx:1-40。类关系:phPhaseSpaceVector(6 维空间向量)→ phArticulatedBodyInertia → phArticulatedBodyPart(单个刚体链接)→ phJoint(JNT_1DOF 肘/膝、JNT_3DOF 肩/髋/颈/腰、PRISM_JNT 平移)→ phArticulatedBody → phArticulatedCollider : phCollider。
| 常量 | 值 | 证据 |
|---|---|---|
phArticulatedBodyType::MAX_NUM_LINKS | 23(关节 22,关节自由度 66) | src/dev_ng/rage/base/src/pharticulated/articulatedbody.h:68-77 |
phJoint::JointType | JNT_1DOF/JNT_3DOF/PRISM_JNT | src/dev_ng/rage/base/src/pharticulated/joint.h:98-105 |
| 默认肌肉强度 | 角度 200、速度 10 | joint.h:21-22 |
phArticulatedCollider::MAX_NUM_ACTIVE | 256 | src/dev_ng/rage/base/src/pharticulated/articulatedcollider.h:590 |
phArticulatedBodyType 是可资源化的"关节体类型",由 fragType 的物理 LOD 持有。phArticulatedCollider 维护复合 bound 组件到链接的映射 m_ComponentToLinkIndex、自碰撞组件对数组(GetSelfCollisionPairsA/B)、临时禁用自碰撞帧数,以及"拒绝简化为刚体碰撞体"的帧计数 m_DenyColliderSimplificationFrames articulatedcollider.h:34-123、articulatedcollider.h:160-169、articulatedcollider.h:420-560。车辆这类 frag 在所有关节锁住时,碰撞体可以退化为 TYPE_RIGID_BODY(游戏在 CheckSimpleColliderMode 前后比较类型,见游戏页)。SetArticulatedLargeRoot 对应 TYPE_ARTICULATED_LARGE_ROOT。求解器在 push 求解前 SaveAndZeroAllVelocitiesForPushes、在速度求解前 InitAllAccumJointImpulse,并通过函数表的 articulated 列处理关节体接触(§5.2)。
11. 形状测试(Shape Tests)#
physics/shapetest.h 定义同步形状测试框架:形状类 phShapeProbe(有向线段,返回最近命中)、phShapeEdge(无向,返回进出两点)、phShapeSphere、phShapeCapsule、phShapeSweptSphere、phShapeTaperedSweptSphere、phShapeScalingSweptQuad、phShapeObject(用任意 bound 测试)、phShapeBatch(批量组合),统一由模板 phShapeTest<ShapeType> 驱动 src/dev_ng/rage/base/src/physics/shapetest.h:69-1098、shapetest.h:2063-2110。phShapeTestCullResults 可把"关卡剔除得到的实例 + BVH 剔除得到的图元"保存下来供后续测试复用,模式为 READ / WRITE / DEFAULT shapetest.h:2053-2061。
| 限制 | 值 | 证据 |
|---|---|---|
MAX_BATCHED_SHAPES | 128 | shapetest.h:44 |
MAX_EXCLUDE_INSTANCES | 64 | shapetest.h:47 |
MAX_SHAPE_TEST_ISECTS | 64 | shapetest.h:50 |
MAX_SAVED_CULLED_INSTANCES | 128 | shapetest.h:53 |
MAX_SPU_NUM_CULLED_POLYS | 8192 | shapetest.h:55 |
phShapeTestTaskData / phShapeTestTaskManager 把测试包成 sysTaskManager 任务(PS3 上有 shapetest*spu.job 系列 SPU job)shapetest.h:2433-2504。phAsyncShapeTestMgr 提供带工作线程的异步管理(每任务最多 5 个 probe、请求/结果各 90、超时 4000ms、4 个任务)src/dev_ng/rage/base/src/physics/asyncshapetestmgr.h:186-238;GTA 的 WorldProbe 只借用它的 worker 线程与回调机制,自己维护队列(见游戏页 WorldProbe 节)。关卡还提供旧式便捷接口 TestProbe/TestEdge/TestCapsule/TestSweptSphere/TestPoint/TestSphere/TestObject src/dev_ng/rage/base/src/physics/levelnew.cpp:4407-4558,levelbase.h 中批量 probe 上限 MAX_BATCHED_PROBES = 64 levelbase.h:37。
12. 多线程与 SPU#
phWorkerThread 是对一组同类任务的封装:Initiate(params, n) 通过 sysTaskManager::Create 在指定调度器上创建 n 个任务 src/dev_ng/rage/base/src/phcore/workerthread.cpp:94-166,PS3 上则是 SPURS 任务。模拟器构造时创建三组 src/dev_ng/rage/base/src/physics/simulator.cpp:577-590:
| 任务组 | 非 PS3 入口 | PS3 | 阶段 |
|---|---|---|---|
m_CollisionTasks | TASK_INTERFACE(ProcessPairListTask) | SPURS_TASK_START(collision) | 中/窄相 |
m_UpdateCollidersTasks | TASK_INTERFACE(UpdateCollidersTask) | SPURS_TASK_START(updatecolliders) | 速度/位置积分、apply pushes |
m_CommitDeferredOctreeUpdatesTask | TASK_INTERFACE(CommitDeferredOctreeUpdatesTask) | SPURS_TASK_START(commitdeferredoctreeupdates) | 八叉树提交 |
force solver 与 contact update 由 phContactMgr 发起(目录中有 forcesolver.task、forcesolverpush.task;PS3 上求解函数被编成 .frag 代码片段,makefrags.bat、funcs.txt 是生成脚本)。PS3 的 manifold/collider 还生成 DMA plan(m_ManifoldDmaPlanPool、GenerateDmaPlan)simulator.cpp:701-720。phLevelBase 在 SPU 构建上用 m_MissingVTablePad 保持与 PPU 端(有虚表)相同的数据布局,以便直接 DMA levelbase.h:55-67。WIN32 上每个 worker 还有 64KB 的 btStackAlloc 供窄相使用 simulator.cpp:949-957。
13. pheffects 模块概览#
| 子系统 | 类 | 说明 | 详见 |
|---|---|---|---|
| Verlet 布料/绳索内核 | phVerletCloth、phClothData、phClothVerletBehavior | 位置-边约束迭代(m_nIterations 次)、钉住顶点、与实例/自定义 bound 碰撞;UpdateCharacterCloth/UpdateEnvironmentCloth;PS3 上有 UpdateCharacterCloth.job 等 SPU job | fragments-cloth-glass-rope.md |
| 风场 | phWind、phWindDisturbance*(Box/Cyclone/Downwash/Explosion/DirExplosion/Hemisphere/Sphere/Thermal) | 供布料、植被等采样;spuwindeval 为 SPU 求值 | 天气侧见 ../20-gameplay/world-rules-clock-weather.md |
| 爆炸 | phExplosionMgr、phInstBehaviorExplosion | 实例行为形式的扩张球碰撞 | 游戏爆炸见 ../21-weapons-camera/weapons.md |
| 液体/弹簧/形变几何 | phInstBehaviorLiquid、phSpring、phSpringAninmatedAttachment、phMorphGeometry | 通用实例行为 | — |
| 调试 | phMouseInput | BANK 下鼠标拖拽/抓取物体 | — |
证据:phVerletCloth 接口与字段 src/dev_ng/rage/base/src/pheffects/cloth_verlet.h:440-470、cloth_verlet.h:703-785;风场类 src/dev_ng/rage/base/src/pheffects/wind.h:95;爆炸实例行为 src/dev_ng/rage/base/src/pheffects/instbehaviorexplosion.h:42;液体 src/dev_ng/rage/base/src/pheffects/instbehaviorliquid.h:93;鼠标输入 src/dev_ng/rage/base/src/pheffects/mouseinput.h:42。
14. 约束与边界行为汇总#
- u16 句柄空间:level index、计数器、八叉树节点索引都是
u16/13 位;manifold 与 contact 池断言 < 65535。游戏把对象上限钳到 32766(见游戏页)。 - 激活失败是静默的:关卡激活数满、托管碰撞体用尽、实例拒绝激活时
ActivateObject返回 NULL,碰撞体用尽时只有"可忽略"断言。 - 删除时机:碰撞任务运行期间
SetSafeToDelete(false)/SetSafeToModifyManifolds(false),回调中不能删实例或改 manifoldsimulator.cpp:4384-4386、simulator.cpp:4444-4446。 - 破碎受 scratchpad 限制:空间不够时本轮放弃破碎(只打警告),每次速度求解最多 8 轮。
- 延迟八叉树:形状测试读到的八叉树可能落后于实例矩阵,直到下一次
CommitDeferredOctreeUpdates(模拟器在CollideActive开头与Update结尾各提交一次;游戏还会显式调用)。 - 数值范围:关卡是立方体,物体半径超过根节点半宽会在延迟更新时断言"too big for the physics level"
levelnew.cpp:1683。
未决问题 / 证据不足#
phForceSolver::Solve与各applyimpulse*.cpp的具体冲量公式(摩擦锥、warm start、Baumgarte 项)未逐个阅读;本页只描述分派结构。phContactMgr::SplitPairs的分岛/分相算法细节、phSleepMgr/phSleepIsland的岛构建细节未展开。collision.cpp(约 270KB)中MidPhaseIntersector的遍历策略只看了入口与结构,未逐行验证。- EPA/Bullet dispatcher "未使用"、
phContact材质 W 分量打包等结论属于推断。