可破碎物(Fragments)、布料、破碎玻璃与绳索
本页解释 RAGE suite 层建立在刚体引擎之上的四类"会变形/会碎"的对象:fragment(可破碎、可铰接、可受损的复合物体,也是车辆与 ragdoll 的物理载体)、cloth(Verlet 布料:环境布料与角色布料)、breakableglass + phglass(程序化碎玻璃)与 grrope(绳索渲染;绳索物理复用 Verlet 内核)。重点是数据结构(类型/实例/缓存三级)、破碎与损坏流程、与 RAGE 物理/动画/渲染的接口,以及 GTA 游戏代码装配它们的回调。
背景与相关页面#
- 引擎基础(
phInst、复合 bound、关节体、求解器中的破碎循环):rage-physics-engine.md。 - 游戏层每帧何时更新 frag/布料/绳索/玻璃、ragdoll(
fragInstNM):game-physics-and-ragdoll.md。 framework/src/fwpheffects(clothManager、ropeManager、verletTaskManager)的权威页面是 ../12-vfx/decals-and-vfx-systems.md,本页只描述与 suite 层交界的部分。- 车辆 frag、车门/车轮脱落:车辆侧见 ../08-vehicles/vehicle-physics-handling-damage.md;车窗碎玻璃视效(
vehicleglass)见 ../12-vfx/decals-and-vfx-systems.md。 - 行人服装布料的游戏侧(
CPedClothCollision、变体)见 ../06-peds/ped-motion-and-rendering.md。 - 骨架/动画(
crSkeleton、crAnimation):../10-animation/rage-animation-and-move.md。
相关源码文件#
| 目录 | 关键文件 | 作用 |
|---|---|---|
rage/suite/src/fragment | type.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/cloth | clothcontroller.h/.cpp、environmentcloth.h/.cpp、charactercloth.h/.cpp、characterclothcontroller.h/.cpp、clothbridgesimgfx.h/.cpp | 布料控制器、环境/角色布料、模拟网格→渲染网格映射 |
rage/base/src/pheffects | cloth_verlet*.cpp/.h、clothdata.h、tune.h | Verlet 内核(见引擎页 §13) |
rage/suite/src/breakableglass | glassmanager.h/.cpp、breakable.h/.cpp、crackstemplate.h/.psc、geometrybuilder*、polygondicer.*、meshclipper.*、bgdrawable.* | 碎玻璃生命周期、裂纹模板、几何生成 |
rage/suite/src/phglass | glass.h/.cpp、glassinstance.h/.cpp | 玻璃的物理 bound 与 phGlassInst |
rage/suite/src/grrope | grroperender.h/.cpp、rage_curvedModel.fx | 程序化绳索网格渲染 |
rage/framework/src/fwpheffects | clothmanager.h、ropemanager.h/.cpp、ropedatamanager.cpp、taskmanager.h | 布料/绳索调度(权威页在 12-vfx) |
game/physics | Breakable.cpp、gtaInst.cpp、physics.cpp、Rope.h | 游戏回调:frag 插入/移除、玻璃初始化、破碎许可、调试 bank |
game/cloth | ClothMgr.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:
- 有空闲条目就从链表头取出、移到尾部、打序号。
- 否则若物理模拟正在运行,
Verifyf失败并返回 NULL(不能在模拟中淘汰)。 - 否则从尾部向前扫描"至少两个 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/UpdateEnvironmentRope | src/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、SwitchLOD | src/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)、控制器与自身 phInst | src/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:
- (SPU 或对比模式下的环境布料)把所有顶点减去首顶点作为局部原点,以避免远离原点时的 SPU 精度问题,结束时再加回。
IntegrationCloth_WithCompression:Verlet 积分步(参数为钉住顶点数、重力位移与缩放阻尼),并把旧位置存入临时数组。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。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 两种"玻璃"#
- frag 组玻璃:
fragTypeGroup带MADE_OF_GLASS标志的部件,健康耗尽时消失(§1.6),可附加碎玻璃窗格。 - 程序化碎玻璃窗格(
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 |
| 玻璃 BVH | BREAKABLE_GLASS_USE_BVH=1 | glassmanager.h:24 |
| 同时破碎窗格 | GTA MAX_GLASS_PANES = 10;每格约 31KB(BVH)/16KB | physics.cpp:600-617 |
| 每帧新建窗格 | kMaxCreateCount = 4 | src/dev_ng/rage/suite/src/breakableglass/glassmanager.cpp:176 |
| 碎片物理 inst 池 | InitPhysicsInstPool(maxGlassShards),默认 25 | physics.cpp:1088 |
| 最大 LOD | bgLod::LOD_MED | physics.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 做球-盒测试并ReduceGroupDamageHealthphysics.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 datasrc/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作为被引用的 drawablesrc/dev_ng/rage/framework/src/fwpheffects/ropemanager.cpp:440-462。 - 更新顺序
enRopeUpdateOrder:ROPE_UPDATE_EARLY、ROPE_UPDATE_LATE、ROPE_UPDATE_WITHSIMropemanager.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 后
SwapConstraintsrc/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把它包装成可进入渲染列表的 drawablegrroperender.h:137-168、grroperender.h:274-326。游戏加载common:/shaders/rope_default作为绳着色器并交给ropeManager::Initphysics.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 资源版本细节未展开。