CD Code Digest Hub /GTA V · v2

RAGE 物理引擎(phcore / phbound / phsolver / physics / pharticulated / phbullet / pheffects / phframework)

85 分钟阅读 17 个章节 2 张图

本页解释 RAGE 底层刚体物理库如何组织:对象如何进入"物理关卡"(phLevelNew),每个物理子步里 phSimulator 按什么顺序做积分、宽相、窄相、接触求解、推离(push)与休眠,碰撞形状(phBound* 族与 BVH)如何编码,材质 ID 如何把游戏语义打包进 64 位整数,以及这些阶段如何被拆成 worker 线程 / SPU 任务。GTA V 的所有车辆、行人胶囊、ragdoll、可破碎物体、布料碰撞和射线检测都建立在这一层之上。

背景与相关页面#

术语速查:

术语代码实体含义
物理关卡 / levelphLevelNew = phLevelNodeTree<phLooseOctreeNode>所有物理实例的注册表 + 宽相 + 松散八叉树空间索引
实例phInst一个放进关卡的物理对象:矩阵 + archetype 引用 + level index
原型phArchetype / phArchetypePhys / phArchetypeDamp共享的碰撞形状(bound)、类型/包含标志、质量/惯量/阻尼
碰撞体(运动态)phCollider / phArticulatedCollider只有"激活"的对象才有:速度、角速度、push/turn、休眠计数
流形phManifold一对(子)形状间的接触点缓存,也是约束的载体
接触phContact单个接触点:两侧局部点、法线、深度、累计冲量、材质
约束phConstraintBase 派生类关节/附着/距离等,内部转化为带约束类型的 manifold

相关源码文件#

目录关键文件作用
rage/base/src/phcoreconfig.h、constants.h、material*.h/.cpp、workerthread.*、pool.h编译期开关、线程/任务配置、材质管理器基类、通用池
rage/base/src/phboundbound.h、boundtypes.inc、boundpolyhedron.h、boundgeom.h、boundbvh.h、boundcomposite.h、OptimizedBvh.h、primitives.h碰撞形状族、量化 BVH
rage/base/src/physicslevelbase.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/phsolvercontactmgr.h/.cpp、forcesolver.h/.cpp、forcesolvertables*、applyimpulse*.cpp、preresponse*.cpp、updatecontacts*.cpp顺序冲量求解器、分岛/分相并行、破碎
rage/base/src/phbulletContinuousConvexCollision.*、ConvexIntersector.*、GjkPairDetector.*、MinkowskiPenetrationDepthSolver.*、TrianglePenetrationDepthSolver.*、GjkSimplexCache.*、SpursGjk/从 Bullet 改造的窄相(GJK、穿透深度、CCD)
rage/base/src/pharticulatedarticulatedbody.h、articulatedcollider.h、joint*.h、pharticulated.dtxFeatherstone 关节体(ragdoll、车门等)
rage/base/src/pheffectscloth_verlet*.cpp/.h、wind*.h、instbehaviorexplosion.h、instbehaviorliquid.h、spring.h、mouseinput.hVerlet 布料/绳索内核、风场、实例行为
rage/framework/src/phframeworkinst.h/.cpp、fraginst.h/.cppphfwInst/phfwFragInst:把 fwEntity* 放进 phInst 的 user data

1. 编译期配置:哪些代码路径真正生效#

phcore/constants.h 决定了 GTA V 用的是哪条物理管线,后文所有流程都以这些取值为前提:

宏值影响证据
EARLY_FORCE_SOLVE1先用上一子步的接触做速度求解,再做碰撞检测与 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_CODE0关闭旧代码;也导致只有 btAxisSweep3 宽相被编译使用src/dev_ng/rage/base/src/phcore/constants.h:119-131
USE_NEW_MID_PHASE1中相走 phMidphase::ProcessCollisionNewsrc/dev_ng/rage/base/src/phcore/constants.h:139
USE_STATIC_BVH_REJECTION / USE_PRUNE_NEW_PAIRS1 / 1宽相对与静态 BVH 的配对做额外剔除src/dev_ng/rage/base/src/phcore/constants.h:170-173
PH_MATERIAL_ID_64BIT1 && HACK_GTA4材质 ID 为 u64,可打包大量标志(§7)src/dev_ng/rage/base/src/phcore/constants.h:203-205
OCTANT_MAP_SUPPORT_ACCEL1 && !__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_LOCK1关卡/模拟器的多读单写锁开启src/dev_ng/rage/base/src/physics/lockconfig.h:12
PHLEVEL_NODE_TYPE0phLevelNew 的节点类型是 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_PRIORITYmanifold 从高优先级池分配inst.h:378
FLAG_NEVER_ACTIVATEPrepareForActivation 应返回 NULLinst.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 matrixinst.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 为主,松散八叉树为查询服务#

关卡同时维护两种结构:

  1. 宽相 phBroadPhase:BroadPhaseType 枚举有 AXISSWEEP3/AXISSWEEP1/LEVEL/SPATIALHASH/NXN levelnew.h:953,但在 ENABLE_UNUSED_PHYSICS_CODE=0 时 InitBroadphase 无条件创建 btAxisSweep3(三轴 sweep-and-prune),其它分支不编译 levelnew.cpp:683-707。因此游戏命令行 -phbroadphase 的取值在当前配置下实际无效(推断:SetBroadPhaseType 只改成员,InitBroadphase 不读取它)。宽相维护"pruned pairs"数组,每对带一个 phManifold*。
  2. 松散八叉树(phLooseOctreeNode,分支因子 8,节点外扩边界 = 2 × 半宽 levelnew.h:405-469),最大深度 knMaxOctreeDepth = 18 levelnew.h:955,节点数受 13 位编码限制不得超过 8191 levelnew.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 = 512simulator.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:

  1. 发起延迟八叉树提交任务(注释:碰撞引发的激活要用关卡剔除寻找初始配对,所以关卡必须最新)。
  2. PruneNewOverlappingPairs,取宽相 pruned pairs,sm_SortPairsByCost 时按代价排序。
  3. InitiateCollisionJobs 启动碰撞 worker(它们阻塞在 g_PairConsumeToken 上;作为 B*2078547 挂起问题的绕行方案,先加锁再创建任务)simulator.cpp:4670-4772。
  4. 主线程 ProcessOverlaps 逐对过滤并把合格对追加到 phOverlappingPairArray;worker 一边从数组消费、一边做中相/窄相(生产者-消费者,allPairsReady 标志宣告结束)。
  5. 同时主线程做"额外工作":m_ConstraintMgr->UpdateConstraintContacts(1/dt, addToSolver)——约束在此处被转换为 constraint 型 manifold;push 迭代中不再加入求解器。
  6. FlushCollisionJobs:主线程也跑 ProcessPairListTask 后等待全部 worker。
  7. DoPreComputeImpacts(调用 phInst::PreComputeImpacts / phInstBehavior::PreComputeImpacts,允许游戏禁用接触),然后 CollisionActivations(被激活物体撞到的非激活物体在此被激活)。注释特别说明 PreComputeImpacts 必须在 FindPushPairs 与激活之前,以免被禁用的接触仍导致 push/激活。

ProcessOverlaps 对每个宽相对依次执行 simulator.cpp:3071-3500:

步骤拒绝条件证据
读取状态与 type/include 缓存任一方 NONEXISTENTsimulator.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任一方的实例行为返回 falsesimulator.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:

  1. 仅对 INACTIVE 对象生效;关卡激活数已满(CheckAddActiveObjects)则失败;实例行为 ActivateWhenHit() 为 false 则失败。
  2. 防重入:若在某实例的 PrepareForActivation 内部再次激活同一实例,直接返回(防止激活列表出现重复 index)。
  3. 调用 inst->PrepareForActivation(&collider, otherInst, constraint);返回 NULL 表示拒绝;返回值可以是另一个实例。
  4. 若实例未提供碰撞体则从托管池取一个,池空时断言"可忽略"并失败。
  5. 写入 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:

  1. SolveImpulsesPhase1And2:若存在"世界约束",先 sm_LCPSolverIterations 次迭代 → EnforceWorldConstraints → 重新 UpdateContacts → 再 sm_LCPSolverIterationsFinal 次;否则一次性跑两者之和;最后 PerformExtraConstraintIterations(sm_LCPSolverIterationsExtra)只迭代标记了额外迭代的约束 contactmgr.cpp:1738-1763。
  2. 破碎(仅速度求解阶段):最多 MAX_NUM_BREAKS = 8 轮 BreakObjects → UpdateContacts → 重解;超过则 Warningf;之后 BreakConstraints 处理可断约束。
  3. 可选迭代:收集包含 FLAG_OPTIONAL_ITERATIONS 的岛(ragdoll),SplitPairs 后再跑 sm_LCPSolverIterationsOptional 次。

默认调优常量(游戏会覆盖迭代数,见游戏页)contactmgr.cpp:88-106:

常量默认含义
sm_LCPSolverIterations / Final4 / 4主迭代数
sm_LCPSolverIterationsExtra / Optional6 / 4额外约束迭代 / ragdoll 可选迭代
sm_SeparationBias0.2非 push 时的分离偏置
sm_MinBounce1.0低于此相对速度不反弹
sm_MaxTurn0.1(EARLY_FORCE_SOLVE)每步最大 turn 修正
sm_BreakImpulseScale0.4破碎冲量缩放
sm_MaxPairsPerPhase / sm_MaxArticulatedPerPhasephForceSolver::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 SPHEREphBoundSphere启用
1 CAPSULEphBoundCapsule启用
2phBoundTaperedCapsule移除(USE_TAPERED_CAPSULE=0)
3 BOXphBoundBox启用
4 GEOMETRYphBoundGeometry : phBoundPolyhedron启用(凸多面体/网格)
5phBoundCurvedGeometry移除
6 / 7phBoundGrid / phBoundRibbon移除
8 BVHphBoundBVH : phBoundGeometry启用(地图碰撞、复杂静态网格)
9phBoundSurface移除
10 COMPOSITEphBoundComposite启用(车辆、frag、ragdoll)
11 TRIANGLETriangleShape窄相内部使用
12 DISC / 13 CYLINDERphBoundDisc / phBoundCylinder启用
14已移除的 probe占位
15 PLANEphBoundPlane启用

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:

  1. 用掩码取出两个材质索引并排序;线性扫描 m_MaterialOverridePairs,命中则直接使用覆盖值。
  2. 否则摩擦 = 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::ComputeTimeOfImpact src/dev_ng/rage/base/src/phbullet/ConvexIntersector.h:145-215。
  • 否则调用 ComputeTimeOfImpactGJK。ContinuousConvexCollision 注释写明它实现基于 Mirtich 保守前进(conservative advancement)的线性+角运动 TOI,在世界空间迭代、内部使用 GJK src/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_LINKS23(关节 22,关节自由度 66)src/dev_ng/rage/base/src/pharticulated/articulatedbody.h:68-77
phJoint::JointTypeJNT_1DOF/JNT_3DOF/PRISM_JNTsrc/dev_ng/rage/base/src/pharticulated/joint.h:98-105
默认肌肉强度角度 200、速度 10joint.h:21-22
phArticulatedCollider::MAX_NUM_ACTIVE256src/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_SHAPES128shapetest.h:44
MAX_EXCLUDE_INSTANCES64shapetest.h:47
MAX_SHAPE_TEST_ISECTS64shapetest.h:50
MAX_SAVED_CULLED_INSTANCES128shapetest.h:53
MAX_SPU_NUM_CULLED_POLYS8192shapetest.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_CollisionTasksTASK_INTERFACE(ProcessPairListTask)SPURS_TASK_START(collision)中/窄相
m_UpdateCollidersTasksTASK_INTERFACE(UpdateCollidersTask)SPURS_TASK_START(updatecolliders)速度/位置积分、apply pushes
m_CommitDeferredOctreeUpdatesTaskTASK_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 jobfragments-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通用实例行为—
调试phMouseInputBANK 下鼠标拖拽/抓取物体—

证据: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),回调中不能删实例或改 manifold simulator.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 分量打包等结论属于推断。