CD Code Digest Hub /GTA V · v2

可破碎物(Fragments)、布料、破碎玻璃与绳索

56 分钟阅读 8 个章节 2 张图

本页解释 RAGE suite 层建立在刚体引擎之上的四类"会变形/会碎"的对象:fragment(可破碎、可铰接、可受损的复合物体,也是车辆与 ragdoll 的物理载体)、cloth(Verlet 布料:环境布料与角色布料)、breakableglass + phglass(程序化碎玻璃)与 grrope(绳索渲染;绳索物理复用 Verlet 内核)。重点是数据结构(类型/实例/缓存三级)、破碎与损坏流程、与 RAGE 物理/动画/渲染的接口,以及 GTA 游戏代码装配它们的回调。

背景与相关页面#

相关源码文件#

目录关键文件作用
rage/suite/src/fragmenttype.h/.cpp、typephysicslod.h、typegroup.h、typechild.h、typecloth.h、typexml.cpp、instance.h/.cpp、cache.h/.cpp、cacheheap.h、cachemanager.h/.cpp、manager.h/.cpp、tune.h/.psc、eventplayer.h、animation.h、drawable.h、fragmentconfig.h、fragment.dtx可破碎物类型、实例、缓存、管理器、调参、事件
rage/suite/src/clothclothcontroller.h/.cpp、environmentcloth.h/.cpp、charactercloth.h/.cpp、characterclothcontroller.h/.cpp、clothbridgesimgfx.h/.cpp布料控制器、环境/角色布料、模拟网格→渲染网格映射
rage/base/src/pheffectscloth_verlet*.cpp/.h、clothdata.h、tune.hVerlet 内核(见引擎页 §13)
rage/suite/src/breakableglassglassmanager.h/.cpp、breakable.h/.cpp、crackstemplate.h/.psc、geometrybuilder*、polygondicer.*、meshclipper.*、bgdrawable.*碎玻璃生命周期、裂纹模板、几何生成
rage/suite/src/phglassglass.h/.cpp、glassinstance.h/.cpp玻璃的物理 bound 与 phGlassInst
rage/suite/src/grropegrroperender.h/.cpp、rage_curvedModel.fx程序化绳索网格渲染
rage/framework/src/fwpheffectsclothmanager.h、ropemanager.h/.cpp、ropedatamanager.cpp、taskmanager.h布料/绳索调度(权威页在 12-vfx)
game/physicsBreakable.cpp、gtaInst.cpp、physics.cpp、Rope.h游戏回调:frag 插入/移除、玻璃初始化、破碎许可、调试 bank
game/clothClothMgr.h 等已停用(NORTH_CLOTHS 0),见游戏页 §8

1. Fragment 系统#

1.1 设计意图#

模块文档说明:fragment 模块的核心是"在可破碎实例碎成多块后管理这些块",使每块都能在正确位置、以正确的缺失部件绘制;fragType 用 fragTypeChild/fragTypeGroup 结构描述物体与断裂强度;每个可断部件有损坏区域与健康值,归零可换成损坏模型;部件可由骨架动画驱动(受控部件受力过大时可断开),也可由物理驱动(带关节限位、可被"门闩"(latch)锁住,如车门);支持游戏内调参与流式加载 src/dev_ng/rage/suite/src/fragment/fragment.dtx:1-44。缓存(cache)是"两级"内存系统:每个需要状态的实例分得一个缓存条目,条目内再按类型复杂度分配可变大小的数据 fragment.dtx:71-80。

编译开关:ENABLE_FRAGMENT_EVENTS = 1、ENABLE_FRAGMENT_MIN_BREAKING_IMPULSES = 1、FRAGMENT_STORE_ANG_INERTIA_IN_CHILD = 0 src/dev_ng/rage/suite/src/fragment/fragmentconfig.h:19-30;FRAGMENT_LAZY_ARTICULATED_CACHE = 1 src/dev_ng/rage/suite/src/fragment/cache.h:32;PHYSICS_LOD_GROUP_SHARING = 1 src/dev_ng/rage/suite/src/fragment/typephysicslod.h:23。

1.2 类型数据:fragType → 物理 LOD → 组/子#

证据:fragType 字段 src/dev_ng/rage/suite/src/fragment/type.h:585-646;fragPhysicsLODGroup(High/Medium/Low 三个 datOwner<fragPhysicsLOD>)与 fragPhysicsLOD 字段 typephysicslod.h:46-71、typephysicslod.h:340-412;fragTypeChild 字段 src/dev_ng/rage/suite/src/fragment/typechild.h:149-180;布料类型包装 src/dev_ng/rage/suite/src/fragment/typecloth.h:18-46。

层关键数据
fragType公共 drawable、额外/损坏 drawable、根 child、调参名、环境/角色布料数组、user data(GTA 的 gtaFragType 存 model id)、碰撞事件集、共享矩阵集、估算缓存大小、m_ARTAssetID(NM 资产,-1 表示非 NM)、类型标志、重力/浮力系数、玻璃窗模型信息、物理 LOD 组
fragPhysicsLOD最小/最大角惯量、m_MinMoveForce、phArticulatedBodyType、每组最小破碎冲量、根 CG 偏移、6 组阻尼、组/子数组、未损坏/损坏 phArchetypeDamp、复合 bound、link attachment 矩阵、自碰撞对(A/B 数组)、组数/根组数/骨驱动子数/子数(均为 u8)
fragTypeGroup强度 m_Strength、上/下传力系数、关节刚度与软限位角、旋转速度/强度、复位强度/最大扭矩、门闩强度 m_LatchStrength、未损坏/损坏总质量、父子组索引、m_MinDamageForce、m_DamageHealth、m_WeaponHealth、按来源的伤害倍率(武器/车辆/行人/ragdoll/爆炸/物体/近战)、m_PedInvMassScale src/dev_ng/rage/suite/src/fragment/typegroup.h:242-341
fragTypeChild未损坏/损坏质量、所属组、骨骼 ID、未损坏/损坏 fragDrawable、持续/碰撞/断裂/从根断裂事件集与播放器

组标志:DISAPPEARS_WHEN_DEAD、MADE_OF_GLASS、DAMAGE_WHEN_BROKEN、DOESNT_AFFECT_VEHICLES(与车辆碰撞时把车视为无限重)、DOESNT_PUSH_VEHICLES_DOWN、HAS_CLOTH(每个 fragment 最多一块布)typegroup.h:328-335。类型标志:NEEDS_CACHE_ENTRY_TO_ACTIVATE、HAS_ANY_ARTICULATED_PARTS、CLONE_BOUND_PARTS_IN_CACHE、GTA 新增的 FORCE_ARTICULATED_DAMPING/FORCE_LOAD_COMMON_DRAWABLE/FORCE_ALLOCATE_LINK_ATTACHMENTS、DISABLE_ACTIVATION、DISABLE_BREAKING 等 type.h:565-583。上限:每个 LOD 最多 256 个子与 256 个组(fragChildBits/fragGroupBits 为定长位集)typephysicslod.h:31-35。

fragType 通常从流式资源(g_FragmentStore,见 ../04-rage-framework/streaming-engine.md)加载;开发期也可用 LoadWithXML(entity.type, rig) 从工具输出与 XML 物理骨架构建(游戏只在 __DEV 的 -physicsrigsatload 下这么做,见游戏页 §9.4)。

1.3 实例:fragInst#

fragInst : phInstBreakable,注释称其只有 128 字节(phInst 64 字节的两倍),绝大多数工作数据在共享的 fragType 中;可用 fragType(自己负责加载)或 fragTypeStub(类型页入后自动生效)构造;Insert() 把实例加入物理关卡与"可移动世界",类型未就绪时等待 src/dev_ng/rage/suite/src/fragment/instance.h:44-74。类型号 PH_FRAG_INST = PH_INST_RAGE_LAST+1;物理 LOD 枚举 RAGDOLL_LOD_HIGH/MEDIUM/LOW(对应 LOD 组的三个 LOD);Collision 结构携带位置、法线、相对速度、伤害、两侧组件/部件 instance.h:80-116。实例状态位包括 INSERTED、BROKEN(已不与地面相连)、NO_REVIVE、MANUAL_UPDATE、NO_ARTIC_BODY、DEAD、BROKE_AND_REMOVED、各类 disable 及其 override、ACTIVATE_ON_HIT 等 instance.h:667-686。

关键操作:BreakOffAbove(component)/BreakOffAboveGroup/BreakOffRoot、DeleteAbove、DamageAllGroups/DamageGroupByComponent、骨架与 bound/关节体的三向同步 PoseBoundsFromSkeleton、PoseArticulatedBodyFromBounds、SyncSkeletonToArticulatedBody,以及供子类覆写的 PartsBrokeOff/PartsRestored/CollisionDamage/ReportMovedBySim instance.h:348-584。

1.4 缓存:fragCacheManager / fragCacheEntry / fragHierarchyInst#

简单 vs 复杂 fragment:没有 NEEDS_CACHE_ENTRY_TO_ACTIVATE 的类型是"简单 fragment"——破碎即"把自己从世界上断开",不需要缓存;复杂类型在被激活、破碎或受损前必须先进入缓存。fragInst::PrepareForActivation src/dev_ng/rage/suite/src/fragment/instance.cpp:3781-3884:未 BROKEN 的实例拒绝激活(仍与世界相连);禁用激活则拒绝;复杂类型调用 PutIntoCache(),拿不到条目就不激活也不破碎;FRAGMENT_LAZY_ARTICULATED_CACHE 下按需 LazyArticulatedInit;实例行为可否决激活;把碰撞体指向缓存条目里的 phArticulatedCollider,Freeze 旧速度(注释:为 GTA 加入)并重置休眠,按需 PoseArticulatedBodyFromBounds。

fragHierarchyInst(缓存条目内的"活体"状态)字段:各组运行时健康 groupInsts、组断开位集、复合 bound 副本、骨架与损坏骨架、phArticulatedBody 与 phArticulatedCollider、持续事件播放器、动画与相位、年龄、门闩关节位集、玻璃信息数组、实例行为指针 src/dev_ng/rage/suite/src/fragment/cache.h:62-156。fragCacheEntry 还保存序号、标志、backing inst、骨骼↔组件映射等 cache.h:453-494。

条目初始化 InitInternal:所有动态数据都通过"缓存堆"(fragMemStartCacheHeap,在条目自有内存块中的只增堆)分配;顺序为 InitSkeleton → InitBitsets → InitBound → InitSkeleton2 → 组件/骨骼映射 → InitPhysics →(若可简化且调参允许)创建 TYPE_RIGID_BODY 的简化碰撞体,否则 InitArticulated + InitSelfCollisions → InitConstrained → InitEvents → 环境布料;然后上报缓存大小、把实例行为加入模拟器、在缓存哈希中按 key 增加引用 src/dev_ng/rage/suite/src/fragment/cache.cpp:3516-3642、src/dev_ng/rage/suite/src/fragment/cacheheap.h:1-40。

分配与淘汰 fragCacheManager::GetNewCacheEntry src/dev_ng/rage/suite/src/fragment/cachemanager.cpp:158-268:

  1. 有空闲条目就从链表头取出、移到尾部、打序号。
  2. 否则若物理模拟正在运行,Verifyf 失败并返回 NULL(不能在模拟中淘汰)。
  3. 否则从尾部向前扫描"至少两个 sim 更新内未使用(序号 < 当前-1)且不需要缓存才能绘制"的条目:设置了回收球时优先淘汰与回收球不相交的、否则选与球重叠最少的;未设回收球时优先淘汰兴趣视锥外的,再否则按 GetImpact(距离/屏幕占比)选影响最小的。被选中者 UnInit 后复用。

SimUpdate 每个子步把序号 +1 并把视口数据传给玻璃系统;Update 每帧遍历所有活条目调用 fragCacheEntry::Update cachemanager.cpp:281-323。兴趣视锥由游戏每帧 FRAGMGR->SetInterestFrustum 设置(CPhysics::Update 中)src/dev_ng/rage/suite/src/fragment/manager.cpp:276-287。

GTA 的装配:fragManager 构造参数中 numCacheEntries = numTypeStubs = maxPeds + maxVehicles + 128(PC)/+64,回调为 CBreakable::Insert/Remove/InitFragInst/AddToDrawBucketFragCallback,并跳过默认 cache manager 初始化,改为提供游戏自己的 fragInstGta[n](类型 PH_INST_FRAG_CACHE_OBJECT)作为 backing inst src/dev_ng/game/physics/physics.cpp:1012-1041、src/dev_ng/rage/suite/src/fragment/manager.h:58-128。

1.5 破碎与分离#

破碎由求解器触发(引擎页 §5.4):phContactMgr 把一岛内的冲量按组件累计,调用各实例的 FindBreakStrength,选最弱者 BreakApart。

证据:FindBreakStrength 的简单 fragment 分支与注释(根断裂优先,其余按组修改)instance.cpp:1506-1560;BreakApart instance.cpp:4174-4303;BreakEffects 为断开的组创建新缓存条目并插入、迁移 manifold instance.cpp:4328-4560。BreakEffects 前半部分还处理"解开门闩但未断"的部件:把它们作为新的关节链接加入原关节体并重算质量。

游戏侧:

  • CBreakable::Insert(fragManager 的 insert 回调)对缓存对象类型的新 inst 创建一个 ENTITY_OWNEDBY_FRAGMENT_CACHE 的 CObject(模型同父实体),设置物理 inst、drawable、骨架与 type/include 标志;无法分配 CObject 时只打警告 src/dev_ng/game/physics/Breakable.cpp:88-180。
  • 因此 CBreakable::AllowBreaking 仅在 CObject 池仍有空位时允许破碎 Breakable.cpp:458-461。
  • fragInstGta::PartsBrokeOff 处理游戏语义:车门标记为脱落、车轮隐藏刹车卡钳、更新断开组件的碰撞标志、通知玻璃等 src/dev_ng/game/physics/gtaInst.cpp:1660-1760。
  • fragManager::SimUpdate 在 fragTune 的 BreakingFrameRateLimit ≥ 1/dt 时置 m_PreventBreakingBecauseOfFramerate(低帧率保护)src/dev_ng/rage/suite/src/fragment/manager.cpp:288-327。

1.6 损坏、调参与事件#

CollisionDamage(msg) → ApplyDamage → ReduceGroupDamageHealth:必要时先 PutIntoCache;禁用损坏则返回;健康首次跌破 0 且有损坏 archetype 时切换为损坏 bound;健康到下限时,DISAPPEARS_WHEN_DEAD 或玻璃组会消失并播放死亡事件 instance.cpp:920-1000、instance.cpp:2234-2300。CollisionDamage 中还处理"简化刚体碰撞体 ↔ 关节碰撞体"的切换(切换时清接触,因为两者接触格式不同)。

fragTune(USE_FRAG_TUNE=1,游戏从 common:/data/fragment.xml 加载)提供全局倍率(最大绘制距离、上/下传力、根断裂/断裂/损坏健康缩放、BreakingFrameRateLimit、是否允许关节化/简化碰撞体、碰撞伤害缩放与阈值、默认调参参考质量)以及命名的 fragTuneBreakPreset(强度、传力、最小伤害力、各类健康与倍率)src/dev_ng/rage/suite/src/fragment/tune.psc:4-50、src/dev_ng/game/physics/physics.h:33。

事件:fragTypeChild/fragTypeGroup 上的 evtSet 由 fragCollisionEventPlayer、fragBreakEventPlayer、fragContinuousEventPlayer 播放(碰撞、断裂、从根断裂、持续、死亡)src/dev_ng/rage/suite/src/fragment/eventplayer.h:14-90。

1.7 骨架、动画与关节体的同步#

  • fragAnimation 持有一个 crAnimation 与受控骨骼索引;注释:动画部件可以控制 bound 部件,若受力超限该部件可被打断 src/dev_ng/rage/suite/src/fragment/animation.h:16-54。
  • 物理 → 骨架:模拟器移动实例后 ReportMovedBySim;若未设手动更新且碰撞体是关节体,则 PoseSkeletonFromArticulatedBody,更新变换后的包围球,并为没有 bound 部件的骨骼补算矩阵 instance.cpp:2646-2700。
  • 动画 → 物理:PoseBoundsFromSkeleton 用骨架姿态写复合 bound 部件矩阵(可选同时写 last 矩阵、全量/增量重建复合 BVH);PoseArticulatedBodyFromBounds 再从 bound 姿态设置关节体 instance.h:448-454。ragdoll 的 fragInstNMGta 覆写 PoseBoundsFromSkeleton。

1.8 Ragdoll 用的 fragType#

行人的 fragType 同时是 ragdoll 的定义:m_ARTAssetID ≥ 0 表示该类型对应某个 NM 资产,其物理 LOD 组被替换为五套共享组之一;三个物理 LOD 即 ragdoll 的高/中/低 LOD(21/16/12 个部件);fragInstNM/fragInstNMGta 派生自 fragInst,通过同样的缓存/关节体机制激活。详见 game-physics-and-ragdoll.md("共享 ragdoll 物理数据"与"fragInstNM"两节)。

1.9 GTA 的破碎冲量修正(fragInstGta::ModifyImpulseMag)#

RAGE 求解器在累计"破碎冲量"时会调用 phInst::ModifyImpulseMag。GTA 用它把物理冲量换算成美术更易调的"破碎冲量",再乘以组上按撞击来源区分的倍率 src/dev_ng/game/physics/gtaInst.cpp:864-1043:

撞击方破碎冲量倍率
动画状态行人的主胶囊不用接触冲量,而按移动状态估算动量(注释:50kg,走 1.5646、跑 2.2352 m/s,冲刺另算),再除以参与接触数GetPedScale()
车辆(PH_INST_VEH/PH_INST_FRAG_VEH),且本 frag 仍连在世界上(或根固定)用 VehicleFragImpulseFunction 分段线性映射:按 m_InputThreshold 分段,在 [m_OutputMin, m_OutputMax] 间 Ramp;超出最后一段或车辆设置 bActAsIfHighSpeedForFragSmashing 时取最大输出GetVehicleScale()
ragdoll(PH_INST_FRAG_PED)接触冲量GetRagdollScale()
抛射物接触冲量GetWeaponScale()
其它接触冲量GetObjectScale()

分段函数的数据结构由 VehicleFragImpulseFunction.psc 生成(VehicleFragImpulseRange{m_OutputMin, m_OutputMax, m_InputThreshold} 数组),在 InitCore 末尾从 common:/data/VehicleFragImpulseFunction.xml 加载 src/dev_ng/game/physics/VehicleFragImpulseFunction.psc:1-16、gtaInst.cpp:832-839、src/dev_ng/game/physics/physics.cpp:1157。

2. 布料(cloth)#

2.1 分层#

层类型职责证据
内核phVerletCloth(pheffects)顶点/边数据、迭代次数 m_nIterations、钉住顶点数、自定义 bound、行人碰撞 bound、碰撞实例列表;UpdateCharacterCloth/UpdateEnvironmentCloth/UpdateEnvironmentRopesrc/dev_ng/rage/base/src/pheffects/cloth_verlet.h:440-470、cloth_verlet.h:703-785
控制器clothController每个模拟 LOD 一份 phVerletCloth(m_Cloth[LOD_COUNT],LOD_COUNT=4)、模拟→渲染桥 clothBridgeSimGfx、LOD 间形变 phMorphController;ApplySimulationToMesh、ApplyPinning、SwitchLODsrc/dev_ng/rage/suite/src/cloth/clothcontroller.h:27-60、clothcontroller.h:168-180、src/dev_ng/rage/base/src/rmcore/lodgroup.h:30
环境布料environmentCloth : clothBase旗帜、帘子等世界物件;持有调参 clothInstanceTuning、被引用的 drawable、phEnvClothVerletBehavior(phInstBehavior)、控制器与自身 phInstsrc/dev_ng/rage/suite/src/cloth/environmentcloth.h:47-154、environmentcloth.h:186、environmentcloth.h:363-369
角色布料characterCloth : clothBase + characterClothController服装布料:姿态集、骨骼绑定、附在骨骼上的复合碰撞 bound、外力、父矩阵src/dev_ng/rage/suite/src/cloth/charactercloth.h:46、charactercloth.h:151-170、src/dev_ng/rage/suite/src/cloth/characterclothcontroller.h:45-75
模拟→渲染clothBridgeSimGfx每 LOD 的网格顶点数、显示映射(渲染顶点→模拟顶点 u16)、钉住半径、顶点权重、膨胀系数、可钉住位集src/dev_ng/rage/suite/src/cloth/clothbridgesimgfx.h:241-314
调度clothManager / clothInstance(fwpheffects)按类型(Environment/Character/Vehicle/ReplayControlled)更新、任务分发、缓冲轮换、自定义 bound;最多 600 个布料src/dev_ng/rage/framework/src/fwpheffects/clothmanager.h:106-170、clothmanager.h:329-410、clothmanager.h:493

fragType 通过 m_EnvCloth/m_CharCloth 携带布料模板(fragTypeEnvCloth/fragTypeCharCloth 只是包装),缓存条目初始化时 InitEnvCloth(无源条目、类型有环境布料且未加 -nocloth)调用 FRAGMGR->InstanceEnvironmentCloth,它直接转发给 ClothInterface::m_InstanceEnvironmentCloth 委托,由游戏通过 clothManager::InitInterfaceCallbacks 绑定;实例化得到的布料行为被设为该 frag 的实例行为 src/dev_ng/rage/suite/src/fragment/manager.h:85-113、manager.h:482-485、src/dev_ng/rage/suite/src/fragment/cache.cpp:1404-1436、src/dev_ng/game/physics/physics.cpp:1007。游戏在 InitCore 中创建 verletTaskManager、clothInstanceTaskManager、clothManager,把 cloth 回调装进 ClothInterface,并加载全局自定义 bound src/dev_ng/game/physics/physics.cpp:964-1009。

2.2 Verlet 算法#

phVerletCloth::UpdateCloth src/dev_ng/rage/base/src/pheffects/cloth_verlet_update.cpp:1568-1612:

  1. (SPU 或对比模式下的环境布料)把所有顶点减去首顶点作为局部原点,以避免远离原点时的 SPU 精度问题,结束时再加回。
  2. IntegrationCloth_WithCompression:Verlet 积分步(参数为钉住顶点数、重力位移与缩放阻尼),并把旧位置存入临时数组。
  3. IterationCloth:迭代 m_nIterations 次,每次先处理自定义边(实例的优先于类型的)再处理全部边——以 8 条为一组的 SoA 向量化边长约束松弛(超松弛系数 DEFAULT_VERLET_OVERRELAXATION_CONSTANT = 1.55),然后 DetectAndEnforceList 对碰撞实例与自定义/虚拟/行人 bound 做检测与投影 cloth_verlet_update.cpp:1797-1853、src/dev_ng/rage/base/src/pheffects/tune.h:31。
  4. CalculateDeltaPositions_WithCompression 用新旧位置计算本步位移(用途未逐行确认)。

绳索走专门的 UpdateRope/IterationRope/IntegrationRope(同一文件)。空气阻力按顶点法线与风向量(ApplyAirResistance*)施加。PS3 上环境布料/角色布料/绳索各有 SPU job(UpdateEnvironmentCloth.job 等)。

2.3 环境布料#

environmentCloth::UpdateCloth:取当前模拟 LOD 的 phVerletCloth,由 phEnvClothVerletBehavior::UpdateClothSim 推进模拟,然后 ApplySimulationToMesh 把模拟顶点写回渲染几何,并用布料包围球更新 drawable 的剔除球;最后清空碰撞实例列表(每次更新重新收集)src/dev_ng/rage/suite/src/cloth/environmentcloth.cpp:1657-1705。clothInstanceTuning 提供旋转速率、角度阈值、额外力、权重、距离阈值(CLOTH_TUNE_FORCE_VERTEX_RESISTANCE 时作为顶点阻力)与钉住顶点索引 environmentcloth.h:110-154。环境布料与车辆布料在 CPhysics::Update 的所有时间片之后、与 EARLY 绳索一起提交更新(见游戏页 §2.1)。

2.4 角色布料#

characterCloth::Update 调用控制器 Update(gravity, timeScale, skeleton, dt, boundComposite, boneIndices),再把模拟顶点拷到实体顶点缓冲(PC/Durango/Orbis 上清零 W 以免着色器 NaN)src/dev_ng/rage/suite/src/cloth/charactercloth.cpp:443-466。characterClothController 用骨架蒙皮(SkinMesh,基于 m_PoseMatrixSet 与骨骼映射)得到钉住顶点目标,并有"钉住半径集"与 m_PinningRadiusScale 调节钉住范围(推断:bridge 中每顶点的 m_PinRadius 以 GetVertexBlends 暴露,用作蒙皮与模拟间的混合量);注释说明角色布料尚不支持布料 LOD src/dev_ng/rage/suite/src/cloth/characterclothcontroller.cpp:263-266、characterclothcontroller.h:222-252。角色布料必须在动画表达式完成后更新,因此游戏把它放在 CRenderer::PreRender 中、紧跟 clothManager::SwapBuffers(游戏页 §2.1);缓冲数 NUM_CLOTH_BUFFERS 在 Xenon/PC 为 4、其它平台为 3,(推断用于把模拟写入与渲染线程读取解耦)clothmanager.h:17-30。

3. 破碎玻璃(breakableglass + phglass)#

3.1 两种"玻璃"#

  1. frag 组玻璃:fragTypeGroup 带 MADE_OF_GLASS 标志的部件,健康耗尽时消失(§1.6),可附加碎玻璃窗格。
  2. 程序化碎玻璃窗格(bgGlassManager):fragType 的 bgPaneModelInfoBase 描述窗格;缓存条目在骨骼上创建窗格并保存到 fragHierarchyInst::glassInfos(CreateBreakableGlassOnBone/ShatterGlassOnBone/PurgeBreakableGlassInfos)src/dev_ng/rage/suite/src/fragment/cache.cpp:3698-3731。

3.2 bgGlassManager#

公共 API:CreateBreakableGlass(创建 bgBreakable + bgDrawable + phGlassInst)、TransferBreakableGlassOwner、DestroyBreakableGlass(物理与图形对象延迟删除)、UpdateBreakableGlass(模拟线程)、SyncBreakableGlass(把 breakable 的矩阵同步到 drawable)、ProcessDeferredDeletes、HitGlass、BreakFrame、RecycleGlass、UpdateBuffers、SetMaxLod、SetViewportData,以及游戏回调 SetInitGlassBound/InitGlassInst/InitShardInst...ForGameFunctor src/dev_ng/rage/suite/src/breakableglass/glassmanager.h:95-220。

配置与容量:

项值证据
堆模式RAGE_GLASS_USE_PRIVATE_HEAP=0;Xenon/PS3 用用户堆(RAGE_GLASS_USE_USER_HEAP),其它平台用普通 Malloc(maxPanes, heapMem)glassmanager.h:17-24、src/dev_ng/game/physics/physics.cpp:1282-1291
玻璃 BVHBREAKABLE_GLASS_USE_BVH=1glassmanager.h:24
同时破碎窗格GTA MAX_GLASS_PANES = 10;每格约 31KB(BVH)/16KBphysics.cpp:600-617
每帧新建窗格kMaxCreateCount = 4src/dev_ng/rage/suite/src/breakableglass/glassmanager.cpp:176
碎片物理 inst 池InitPhysicsInstPool(maxGlassShards),默认 25physics.cpp:1088
最大 LODbgLod::LOD_MEDphysics.cpp:1069

生命周期要点 glassmanager.cpp:1433-1490、glassmanager.cpp:1730-1815、glassmanager.cpp:1872-1910:

  • 创建只能在主线程、受互斥锁保护;超过每帧上限或无空闲句柄即失败并返回无效句柄。
  • HitGlass 不立即碎裂,而是把冲击排入窗格冲击队列,并跳过与上一个冲击距离过近的重复冲击。
  • UpdateBuffers(主线程)先确保下一帧有足够空闲窗格("不允许在帧中途回收"),再处理排队的冲击、生成 GPU 缓冲,最后重置冲击队列与每帧创建计数。
  • 内存不足时 RecycleGlass 尝试释放旧窗格。

裂纹模板 bgCracksTemplate::InitClass 从当前 ASSET 目录(游戏设为 common:/data/glass)读取 glassconfig.xml,断言至少有一种玻璃类型和裂纹类型;LoadResources 按"玻璃类型 × 尺寸(形如 <n>x<m>)× 裂纹类型"构建模板数组 src/dev_ng/rage/suite/src/breakableglass/crackstemplate.cpp:311-362、physics.cpp:1057-1066。几何生成(geometrybuilder、polygondicer、meshclipper)在 PS3 且 BREAKABLE_GLASS_ON_SPU 时走 SPU job src/dev_ng/rage/suite/src/breakableglass/geometrybuilder.cpp:30-75。

3.3 bgBreakable 与物理#

bgBreakable 表示一块窗格:Hit(crackType, 世界命中点, 冲量, 伤害) 触发几何重建;BreakFrame 处理窗框(kFF_Top/Bottom/Left/Right)断裂;Update(dt) 对已脱落的碎片施加物理与碰撞;碎片的物理数据保存在 bgPhysicsData(bgFallingPhysics 数组),加固玻璃最多 8 块下落碎片 src/dev_ng/rage/suite/src/breakableglass/breakable.h:124-200、breakable.h:370-402。

phGlass 为窗格提供物理 bound:一个复合 bound,含 BVH(BoundIndex_BVH,按元素表示仍存在的玻璃片)与盒子(BoundIndex_Box);HitElement 处理对某元素的命中,RegenerateBound 在碎裂后重建 src/dev_ng/rage/suite/src/phglass/glass.h:21-75。phGlassInst : phInstBreakable 的类型号为 PH_INST_GLASS,覆写 ShouldFindImpacts、PreComputeImpacts、NotifyImpulse src/dev_ng/rage/suite/src/phglass/glassinstance.h:16-34。

3.4 游戏侧#

  • CPhysics::BreakGlass(entity, pos, radius, impulse, damage, crackType):先确保 frag 实例在缓存中;对每个有效窗格把命中点转到窗格空间,与窗格三角形做球-三角形测试,命中则 HitGlass(forceStoreHit 时跳过测试);没有窗格命中时,再对 MADE_OF_GLASS 组的子 bound 做球-盒测试并 ReduceGroupDamageHealth physics.cpp:1594-1708。裂纹类型通过 GlassSelectCrack 回调交给 fragManager(每次使用后重置为 0)physics.cpp:644-656。
  • CBreakable::InitGlassInst/InitGlassBound/InitShardInst 为玻璃设置碰撞层:窗格 inst 为 GLASS|UNSMASHED 类型、物体 include;复合 bound 的 BVH 部件为 GLASS、盒子部件为 UNSMASHED 且只接受形状测试;碎片为 GLASS 类型并去掉对玻璃与相机测试的 include;窗格 inst 继承被忽略 inst 的实体 user data src/dev_ng/game/physics/Breakable.cpp:393-457。
  • 帧内时序:CPhysics::UpdateBreakableGlass(UpdateBuffers + SyncBreakableGlass,BANK 下还有鼠标打碎调试)在 CGameWorld::ProcessAfterPreRender 中调用 physics.cpp:1952-1996、src/dev_ng/game/scene/world/GameWorld.cpp:2633;ProcessDeferredDeletes 与 bgDrawable::EndFrame 在渲染线程的 PerformSafeModeOperations 中执行 src/dev_ng/game/renderer/renderThread.cpp:472-478。
  • 窗格内存在 INIT_BEFORE_MAP_LOADED 分配、SHUTDOWN_WITH_MAP_LOADED 释放(游戏页 §1)。

4. 绳索(rope)#

绳索没有独立的物理引擎,而是"Verlet 绳 + 端点距离约束"的组合,调度在 fwpheffects::ropeManager(权威页 12-vfx),渲染在 grrope:

  • 组成:ropeInstance 持有 phInst* m_PhysInst、environmentCloth* m_EnvCloth(其 phVerletCloth 以绳模式更新)、端点 phConstraintHandle m_Constraint(距离约束)、两端组件/顶点/骨骼索引、附着对象,以及长度、约束长度缓冲、最小/最大长度、变化速率、最大边长、重力缩放等 src/dev_ng/rage/framework/src/fwpheffects/ropemanager.h:183-200、ropemanager.h:543-599。单根绳最多附着 5 个对象、8 对附着对,XML 中有 8 种绳类型(common:/data/ropedata.xml)ropemanager.h:28-33、src/dev_ng/rage/framework/src/fwpheffects/ropedatamanager.cpp:72。
  • 创建:绳的 phInst 以 inactive 加入关卡,环境布料行为作为实例行为加入模拟器,并创建 rmcRopeDrawable 作为被引用的 drawable src/dev_ng/rage/framework/src/fwpheffects/ropemanager.cpp:440-462。
  • 更新顺序 enRopeUpdateOrder:ROPE_UPDATE_EARLY、ROPE_UPDATE_LATE、ROPE_UPDATE_WITHSIM ropemanager.h:152-157。游戏在每个物理时间片开头更新 WITHSIM 绳、所有时间片后更新 EARLY 绳、CRenderer::PreRender 中更新 LATE 绳;回放跳转时禁用绳索更新(游戏页 §2.1)。RopeSimUpdate 先 UpdatePhysics(dt, order) 再 UpdateCloth(order) src/dev_ng/game/physics/physics.cpp:2424-2436。
  • 与 ragdoll 交互:ped 切换到 ragdoll 时,若当前物理 inst 挂在某根绳上,复制该绳的距离约束参数,把端点换成 ragdoll inst 后 SwapConstraint src/dev_ng/game/Peds/Ped.cpp:4820-4831。
  • 渲染(grrope):RopeModel 是单例程序化管状网格,按深度添加 LOD(参数:最大 Z、沿长度段数、沿宽度段数、UV 缩放);ropeManager::Init 添加 4m/100/12 与 8m/45/7 两级(USE_3LODS 时再加 30m/20/5)src/dev_ng/rage/suite/src/grrope/grroperender.h:32-91、ropemanager.cpp:1947-1956。RopeVertices 以 phVerletCloth 的边生成曲线顶点,rmcRopeDrawable 把它包装成可进入渲染列表的 drawable grroperender.h:137-168、grroperender.h:274-326。游戏加载 common:/shaders/rope_default 作为绳着色器并交给 ropeManager::Init physics.cpp:979-990;rage_curvedModel.fx 是 grrope 自带的曲线模型着色器源。
  • 调试:game/physics/Rope.h 中的 ropeBankManager/clothBankManager 只提供 BANK 下的 RAG 控件(拾取车辆/行人、附着布料变体、重载节点等)src/dev_ng/game/physics/Rope.h:24-80。

5. 约束与边界行为#

  • 缓存是硬资源:缓存条目数固定(PC 为 ped+车辆池+128);模拟运行中不能淘汰条目,拿不到条目的复杂 fragment 既不能激活也不能破碎或记录损坏(静默降级)。
  • 破碎受多重闸门:CObject 池空位(AllowBreaking)、scratchpad 空间、每次速度求解最多 8 轮、低帧率保护、实例/类型的 disable 标志。
  • 玻璃:每帧最多新建 4 个窗格,同时最多 10 个破碎窗格(GTA 设置),冲击被排队到 UpdateBuffers 才生效;删除延迟到渲染线程安全区。
  • 布料:角色布料无 LOD;环境布料每次更新后清空碰撞实例列表;布料任务可能跨线程运行,需在 ProcessAfterAllMovement 与 PreRender 之后等待。
  • 绳索:ragdoll 切换会改写绳约束端点;回放跳转期间不更新。

未决问题 / 证据不足#

  • fragInst::FindBreakStrength 复杂分支(组间传力 ForceTransmissionScaleUp/Down 的具体公式)、BreakEffects 的关节重建细节、fragCacheEntry::InitArticulated(约 570 行)未逐行分析。
  • phEnvClothVerletBehavior::UpdateClothSim、clothManager::Update 的任务拆分与 verletTaskManager 细节属于 fwpheffects(12-vfx 范围),本页未展开。
  • bgBreakable::Hit 的裂纹生成算法(模板选择、多边形切分)未分析。
  • typexml.cpp(XML 物理骨架格式)与 fragType 资源版本细节未展开。