MassBattlePhysicsTypes.h
Source/MassBattle/Public/Physics/MassBattlePhysicsTypes.h
概述 Overview
独立多次子步 PBD 物理引擎的共享类型(纯 C++ struct,无 USTRUCT 反射)。按「GridUpdateProcessor + MoveProcessor 两段式模型」的评审结论组织为三段:REGISTER(注册段)遍历 agent,把每个打包为恰好一条 64 字节缓存行的 FAgentPhysicsData 记录注册进 2D 平坦哈希网格——记录本身就是网格负载(位置住在网格里,不在侧边数组);SOLVE(求解段)只遍历槽位数组(绝不碰 fragment、绝不碰 TMap):每个 body 读邻居的 Pre 记录(各一条缓存行、cell 切片连续),写自己的 Current 记录,Pre/Current 双版本规则——读只见 Pre、写只进 Current,子步之间由网格更新把 Current 提升为 Pre(零拷贝 swap),成员身份每帧注册一次、遍历余量随运行最大累计位移自适应,跨格漂移绝不丢对;WRITEBACK(写回段)再遍历 agent 一次——唯一的 fragment 写。分桶保持 2D 的原因:精确 3D 胶囊对测试负责垂直判定,2D 使遍历为 (2R/256)² 次探测而非 (2R/256)³;生产 agent 网格 Z=5000cm 实际同为 2D,同理。
Shared types for the standalone multi-substep PBD physics engine (plain C++ structs, no USTRUCT reflection). Organized per the GridUpdate + Move two-pass design review into three passes: REGISTER walks agents and packs each into ONE exactly-64-byte cache-line FAgentPhysicsData record registered into the 2D flat hash grid — the record IS the grid payload (positions live in the grid, not in side arrays). SOLVE iterates SLOT ARRAYS ONLY (never fragments, never TMap): every body reads neighbors' Pre records (one cache line each, contiguous cell slices) and writes its own Current record; the Pre/Current double-version rule — reads see only Pre, writes go only to Current; between substeps the grid update promotes Current → Pre (a zero-copy swap); membership is registered once per frame and the walk margin adapts to the running max cumulative displacement so cell-boundary drift can never drop a pair. WRITEBACK walks agents once more — the ONLY fragment writes. Bucketing stays 2D because the exact 3D capsule pair test handles verticality, and 2D keeps a walk at ~(2R/256)² probes instead of (2R/256)³; the production agent grid runs Z=5000 cm — effectively 2D — for the same reason.
通道规则:本文件全部为帧内派生数据——绝不注册进网络 hash / 快照。双端从相同同步 fragment 跑相同代码,锁步安全来自计算一致。引擎宿主为 Processors/MassBattlePhysicsProcessor.h。
Channel rule: everything here is frame-internal derived data — NEVER registered into the network hash/snapshot. Both peers run the same code from the same synced fragments; lockstep safety comes from identical computation. The engine host is Processors/MassBattlePhysicsProcessor.h.
FPhysicsHandoffRecord Move → Physics 帧内交接
Move 处理器 → 物理处理器的帧内交接记录。独立引擎启用时,Move 处理器在其尾部并行(TLS 直写实体索引槽位)推送;物理处理器在注册段开头串行合并消费(超容溢出部分排空)。
The per-frame handoff record from the Move processors to the physics processor. When the standalone engine is armed, the Move processors push records (parallel, direct entity-indexed writes) at their tail; the physics processor merges + consumes them (draining capacity overflow) serially at register time.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Entity | FMassEntityHandle | — | 交接对应的实体句柄(用于实体索引直写)。Entity handle the record belongs to (drives the direct entity-indexed write). |
UniqueID | int32 | -1 | 实体 UID(-1 = 无效)。Entity UID (-1 = invalid). |
ObstaclePlanes | TArray<FPlane, TInlineAllocator<4>> | 空 | 从 RVO 场景命中转化的墙体平面(Move SECTION 8 第 1 步产物);物理求解段作为硬约束平面消费。Scene wall planes converted from RVO scene hits (Move SECTION 8 step 1); consumed by the solve pass as hard-constraint planes. |
bIsTouchingSteepSlope | bool | false | 地面采样得到的陡坡接触(留在 Move 侧判定);物理写回时 OR 进 bCachedTouchingObst。Steep-slope contact from the ground trace (the test itself stays in Move); OR-ed into bCachedTouchingObst at physics writeback. |
DetectObstDist | float | 0.f | Move 本帧算出的障碍采集半径(避障派生);物理障碍缓存原样复用,保证两条路径看到同一障碍集合。Obstacle gather reach Move computed this frame (avoidance-derived); the physics obstacle cache reuses it verbatim so both paths see the same set. |
FAgentPhysicsData 网格/槽位热记录
网格/槽位热记录——恰好一条 64 字节缓存行(static_assert(sizeof == 64) 强制)。每槽存在两个版本(Pre / Current):子步读邻居的 Pre、写自身 Current(整记录拷贝 + 新位置),因此每次邻居读取都是一条连续缓存行。求解全程单精度 float(Chaos 同款)。.cpp 中另有 static_assert(std::is_trivially_copyable_v<FAgentPhysicsData>)——以原始字节跨总线安全。
The grid/slot hot record — exactly one 64-byte cache line (enforced by static_assert(sizeof == 64)). Two versions exist per slot (Pre / Current): a substep reads neighbors' Pre and writes its own Current (whole-record copy + new position), so every neighbor read is one contiguous line. The whole solve runs single-precision float (Chaos-style). The .cpp adds static_assert(std::is_trivially_copyable_v<FAgentPhysicsData>) — concurrency-safe to ship as raw bytes.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Pos | FVector3f | ZeroVector | 子步位置(单精度)。Substep position (single precision). |
Up | FVector3f | UpVector | 碰撞体上向(世界空间,含相对旋转的总旋转)。Collider up (world space, from the total rotation including the relative rotation). |
Radius | float | 34.f | PBD 半径(已含 FPhysicsShared::ColliderMult 放大)——agent 对接触用。PBD radius (inflated by FPhysicsShared::ColliderMult) — agent-pair contacts. |
ShaftHalf | float | 0.f | 真实轴半高(绝不放大)。Real shaft half-height (never inflated). |
Mass | float | 1.f | 质量(对修正的质量加权分摊)。Mass (mass-weighted split of pair corrections). |
AngularVelocity | float | 0.f | 角速度(度/秒),供旋转扫掠膨胀计算。Angular velocity (deg/s), feeding the rotational sweep inflation. |
Bound | float | 68.f | ShaftHalf + Radius——预算候选界(包围球半径)。ShaftHalf + Radius — precomputed candidacy bound (bounding-sphere radius). |
UniqueID | int32 | -1 | 实体 UID(网格成员排序键来源)。Entity UID (source of the grid member sort key). |
bIsDying | uint8 : 1 | 0 | 死亡中(旗标发布)。Dying (flag publication). |
bImmovable | uint8 : 1 | 0 | 不可推动——发布式与 GridUpdate 一致:!bRunsMoveSolver || !bEnablePBD || !bCollideWithAgent;命中时对修正的自身份额取满。Immovable — published exactly as GridUpdate does: !bRunsMoveSolver || !bEnablePBD || !bCollideWithAgent; a hit takes the full self-share of the pair correction. |
bTouchingObst | uint8 : 1 | 0 | 帧初网格发布值(触碰障碍);对端命中时自身份额取满。Frame-start grid publication (touching an obstacle); neighbors take the full self-share against it. |
bMultiCell | uint8 : 1 | 0 | 巨型单位——多格注册;查询侧用槽位去重保证只处理一次。Giant — multi-cell registered; query-side slot dedup processes it once. |
CollisionGroup | uint8 : 3 | 0 | 碰撞组 1–8 打包为 0–7。Collision groups 1–8 packed as 0–7. |
Pad : 1 + PadToLine[3] | uint8 / float[3] | 0 | 填充位,保持记录恰好一整条缓存行。Padding keeping the record exactly one full cache line. |
方法 Methods
默认构造,按上表默认值初始化;POD 语义,可平凡拷贝。
Default constructor initializing the defaults above; POD semantics, trivially copyable.
Input
无参数。
No parameters.
Output
无返回值。
No return value.
FPhysicsObstRef 缓存障碍候选
一条缓存障碍候选。数据指针整帧稳定(障碍网格帧初重建、模拟阶段只读);子步对引用重算接触几何,不重扫障碍网格。每帧只采集一次(注册段内 BuildObstacleCacheForSlot 构建进 FPhysicsSlotSelf::ObstRefs)。
One cached obstacle candidate. The data pointer is stable for the whole frame (the obstacle grid is rebuilt at frame start and read-only during the sim stages); substeps recompute contact geometry against the ref without re-walking the obstacle grid. Gathered ONCE per frame (built into FPhysicsSlotSelf::ObstRefs by BuildObstacleCacheForSlot during the register pass).
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Src | const void* | nullptr | 障碍网格数据指针,按 Type 转译为 FCapsuleObstGridData* / FSegmentObstGridData* / FBoxObstGridData*(类型定义于 Fragments/GridData.h)。Obstacle grid data pointer, reinterpret-cast by Type to FCapsuleObstGridData* / FSegmentObstGridData* / FBoxObstGridData* (types from Fragments/GridData.h). |
CellLocation | FVector | ZeroVector | 障碍所在网格单元位置(double;与相对位置相加得世界位置)。Grid cell location of the obstacle (double; added to its relative location for the world position). |
Type | uint8 | 0 | 0 = 胶囊,1 = 线段墙,2 = 盒。0 capsule, 1 segment, 2 box. |
InvOrientation | FQuat4f | Identity | 仅盒使用——缓存构建时预算的逆朝向(变换帧内恒定,子步免重复求逆)。Box only — inverse orientation precomputed at cache build (frame-constant transform; substeps skip re-inverting). |
FPhysicsSlotSelf 仅自身求解状态
仅自身可见的求解状态(配置拷贝、门控、交接输入、累积器)。邻居永远不碰它——所以它可以「胖」,不伤对遍历的缓存表现。热记录(FAgentPhysicsData)只保留邻居需要的最小字段,自身专属的一切都在这里。
Self-only solve state (config copies, gates, handoff inputs, accumulators). Neighbors never touch it — so it can be fat without hurting the pair-walk cache profile. The hot record (FAgentPhysicsData) keeps only the minimum neighbors need; everything self-specific lives here.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Entity / UniqueID | FMassEntityHandle / int32 | — / -1 | 实体句柄与 UID。Entity handle and UID. |
RealRadius | float | 34.f | 真实(未放大)半径——障碍/场景路径使用,与旧路径口径一致。Real (un-inflated) radius — used by the obstacle/scene paths, legacy parity. |
SelfTotalHalf | float | 68.f | ShaftHalf + RealRadius——障碍包围半径。ShaftHalf + RealRadius — obstacle bounding radius. |
SweepInflation | float | 0.f | 旋转扫掠膨胀(轴 XY 投影 × |角速度| × Δt 弧度);竖直胶囊该值≈0。Rotational sweep inflation (shaft XY projection × |angular velocity| × Δt in radians); ≈0 for vertical capsules. |
bEnablePBD … bLaunching | uint8 : 1 ×9 | 0 | 自身门控位:是否启用 PBD、是否与 agent 碰撞、是否与障碍碰撞、是否与死亡单位碰撞、agent 对是否参与(死亡碰撞延迟的镜像)、Z 轴启用、可飞行、下落中、击飞中。Self gate bits: PBD on, collide-agent, collide-obstacle, collide-dying, agent-pair participation (dying-delay mirror), Z enabled, can fly, falling, launching. |
IgnoreCollMask | uint8 | 0 | 忽略碰撞组位掩码(按对端 CollisionGroup 位测试)。Ignore-collision-group bitmask (tested per neighbor CollisionGroup bit). |
RelaxAgent | float | 1.f | 每子步刚度 k′ = 1−(1−k)^(1/N)(k 为帧级 PositionRelaxation_Agent),保持 N 次应用的帧等效语义。Per-substep stiffness k′ = 1−(1−k)^(1/N) (k = the frame-level PositionRelaxation_Agent), preserving the frame-equivalent meaning across N applications. |
VelRelaxAgent | float | 1.f | agent 位移的速度反馈份额。Velocity-feedback share for agent-pair displacement. |
MassDominanceRatio | float | 1.f | 质量优势比(质量优势门控)。Mass dominance ratio (the dominance gate). |
StackStrength | float | 0.25f | 堆叠支撑强度(支撑/滑移判定)。Stacking support strength (support/slip decision). |
FrictionStaticLat / FrictionStaticLong | float | 0.f | 库仑静摩擦预算(横向/纵向,帧量——每子步按整帧预算判定)。Coulomb static friction budgets (lateral / longitudinal; frame quantities — each substep tests against the full frame budget). |
MoveAccel / MoveDecel | float | 1000.f | 速度反馈的加速/减速限幅(写回段)。Acceleration/deceleration caps for the velocity feedback (writeback). |
XYSpeedLimit | FVector2f | (0, 5000) | XY 速度钳制(X=下限,Y=上限;击飞/下落例外保留)。XY speed clamp (X = min, Y = max; launching/falling exception preserved). |
TurnSpeedDeg / TurnSpeedRangeMap | float / FVector4f | 360 / (0,1,2000,1) | 转向速度与「速度→转向倍率」映射(扫掠弧占位归一化用)。Turn speed and the speed→turn-multiplier range map (sweep-arc occupancy normalization). |
Fwd2D / DesiredVel2D / OldVel | FVector2f / FVector2f / FVector3f | (1,0) / 零 / 零(1,0) / zero / zero | 摩擦帧(求解处零保护)、纵向意图判定用的期望速度、构建时旧速度(反馈基准)。Friction frame (zero-guarded at solve), desired velocity for the longitudinal-intent gate, build-time old velocity (feedback baseline). |
ScenePlanes | TArray<FPlane, TInlineAllocator<4>> | 空 | 交接输入:场景硬约束平面(拷贝自 FPhysicsHandoffRecord)。Handoff input: hard-constraint scene planes (copied from FPhysicsHandoffRecord). |
ObstRefs | TArray<FPhysicsObstRef, TInlineAllocator<6>> | 空 | 每帧障碍缓存(采集趟构建,消费趟只读)。Per-frame obstacle cache (built at gather, read-only while consuming). |
PairSlots | TArray<int32, TInlineAllocator<16>> | 空 | 接触对缓存——采集趟按余量界准入的候选槽位列表;消费趟只迭代此表。Contact-pair cache — candidate slots admitted (margin-bound) at gather substeps; consume substeps iterate only this list. |
PairActive | TArray<uint8, TInlineAllocator<16>> | 空 | 与 PairSlots 对齐——某对产生真实接触时置位(AVBD 对偶更新的行门)。Aligned with PairSlots — set when a pair produces a real contact (the AVBD dual update's row gate). |
PairLambda | TArray<float, TInlineAllocator<16>> | 空 | AVBD(Solver 1)对偶乘子 λ 的每对镜像——两端从相同输入各自演化副本,镜像同步、力等大反向,零跨体写零原子;每 sweep clamp(ρ·C+λ, 0, ∞) 更新。AVBD (Solver 1) per-pair mirror of the dual multiplier λ — both endpoints evolve their own copy from identical inputs; mirrors stay in sync, forces equal-opposite, with zero cross-body writes and no atomics; updated per sweep as clamp(ρ·C+λ, 0, ∞). |
PairPenalty | TArray<float, TInlineAllocator<16>> | 空 | AVBD 罚量 ρ 镜像——行处于内域(λ>0)时逐 sweep 按 β·|C| 递增,封顶 ρ_max。AVBD penalty ρ mirror — ramps β·|C| per sweep while the row is interior (λ>0), capped at ρ_max. |
PairCContact | TArray<float, TInlineAllocator<16>> | 空 | sweep 起点重叠缓存——对偶更新使用的(滞后一 sweep 的)C。Sweep-start overlap cache — the (one-sweep-lagged) C consumed by the dual update. |
bSteepSlope / DetectObstDist | bool / float | false / 300.f | 交接输入:陡坡标志与障碍采集半径(兜底 = RealRadius + 300,与 Move 一致)。Handoff inputs: steep-slope flag and obstacle gather reach (fallback = RealRadius + 300, matching Move). |
StartPos | FVector3f | 零 | 帧初构建位置——AVBD 锚项拉力目标与累计漂移度量基准。Frame-start build position — the AVBD anchor target and the cumulative-drift baseline. |
TotalAgentDelta | FVector3f | 零 | agent 对修正累计(速度反馈的 agent 份额)。Accumulated agent-pair corrections (the agent share of velocity feedback). |
AccumSweepOccupancy | float | 0.f | 子步间扫掠弧占位的运行最大值 → 写回为 RotationalResistance。Running MAX of sweep-arc occupancy across substeps → written back as RotationalResistance. |
LastObservedOverlap | float | 0.f | 调试:本 body 子步开始时看到的 agent 对总重叠(收敛诊断求和用)。Debug: agent-pair overlap this body saw at substep start (summed for convergence diagnostics). |
bCollidedAgent / bCollidedObst / bSceneContact / bSupported | bool ×4 | false | 接触结果标志,写回段发布进 FMoving 的缓存字段。Contact-result flags, published into the FMoving cached fields at writeback. |
FPhysicsCellEntry 网格注册项
一条网格注册项:cell 坐标 + 槽位索引 + 偏置 UID(巨型单位发多条——多格脚印)。UID 与槽位一起打包进每格的 64 位成员字,因此逐格成员排序即得 UID 序,与槽位编号无关——槽位序本身是票号分配(依赖调度),确定性住在格子里(AgentGrid 的 SortAgentCellsByUniqueID 配方)。
One grid registration entry: cell coordinate + slot index + biased UID (giants emit several — a multi-cell footprint). The UID packs WITH the slot into each cell's 64-bit member word, so the per-cell member sort produces UID rank regardless of slot numbering — slot order itself is ticket-assigned (scheduling-dependent); determinism lives in the cells (the AgentGrid SortAgentCellsByUniqueID recipe).
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Cell | FIntPoint | ZeroValue | 2D 格坐标。2D cell coordinate. |
Slot | int32 | 0 | 槽位数组下标。Slot-array index. |
BiasedUID | uint32 | 0 | uint32(UniqueID ^ 0x80000000)——无符号序排序键。uint32(UniqueID ^ 0x80000000) — the unsigned-order sort key. |
方法 Methods
把 BiasedUID 与 Slot 打包为一个 64 位成员字(BiasedUID << 32 | Slot)。逐格成员按此字排序后,高 32 位即 UID 序、低 32 位即槽位——一次排序同时确定遍历顺序与去重键。
Packs BiasedUID and Slot into one 64-bit member word (BiasedUID << 32 | Slot). Sorting each cell's members by this word yields UID rank in the high 32 bits and the slot in the low 32 — one sort fixes both traversal order and the dedup key.
Input
无参数。
No parameters.
Output
返回打包后的 64 位成员字。无副作用。
Returns the packed 64-bit member word. No side effects.
管线角色 Pipeline Role
全部类型只服务于 UMassBattlePhysicsProcessor(及其持久化网格宿主 UMassBattlePhysicsSubsystem),以下关系在发布树 grep 验证:FPhysicsHandoffRecord 由两个 Move 处理器(MassBattleAgentMoveProcessor.cpp 与 MassBattleAgentMoveProcessorFP.cpp)在其尾部经 UMassBattlePhysicsProcessor::PushHandoff 实体索引直写;FAgentPhysicsData / FPhysicsSlotSelf 是处理器的 PreData / CurData / Selves 三条槽位数组元素;FPhysicsObstRef 由处理器的 BuildObstacleCacheForSlot 从 UMassBattleHashGridSubsystem 的障碍网格一次性采集;FPhysicsCellEntry 被 UMassBattlePhysicsSubsystem 消费——注册波内经自旋锁追加 / Pending 收集器进入,串行 flush 后逐格按打包字做 UID 成员排序(MassBattlePhysicsSubsystem.h / .cpp)。fragment 的读写全部隔离在处理器的注册段(读)与写回段(写),求解段只见这些槽位结构。网络通道零参与——绝不进 hash / 快照。
Every type here serves only UMassBattlePhysicsProcessor (and its persistent grid host UMassBattlePhysicsSubsystem); the following relations are grep-verified in the release tree: FPhysicsHandoffRecord is written at the tails of both Move processors (MassBattleAgentMoveProcessor.cpp and MassBattleAgentMoveProcessorFP.cpp) via UMassBattlePhysicsProcessor::PushHandoff with direct entity-indexed writes; FAgentPhysicsData / FPhysicsSlotSelf are the elements of the processor's PreData / CurData / Selves slot arrays; FPhysicsObstRef is gathered once per frame by the processor's BuildObstacleCacheForSlot from the obstacle grid of UMassBattleHashGridSubsystem; FPhysicsCellEntry is consumed by UMassBattlePhysicsSubsystem — spinlock appends / the Pending gatherer during the register wave, then a serial flush and the per-cell UID-packed member sort (MassBattlePhysicsSubsystem.h / .cpp). Fragment reads/writes are fully isolated into the processor's register pass (reads) and writeback pass (writes); the solve pass sees only these slot structures. Zero network-channel involvement — never hashed, never snapshotted.
补充说明 Notes
两条 static_assert 守住硬约束:sizeof(FAgentPhysicsData) == 64(恰好一条缓存行,头文件内)与 std::is_trivially_copyable_v<FAgentPhysicsData>(可平凡复制,PhysicsProcessor.cpp 内)。头文件按引擎小版本条件包含实体句年头(UE ≥ 5.8 走 Mass/EntityHandle.h,否则 MassEntityHandle.h),并包含 Fragments/GridData.h 以取三种障碍网格数据类型。AVBD 求解器状态(PairLambda / PairPenalty / PairCContact)的生命周期 = 对条目的生命周期:采集趟初始化(λ=0、ρ=ρ₀),消费趟演化,重采重置——v1 无跨帧 warmstart;完整设计见引擎源码引用的 references/avbd-solver-design.md。
Two static_asserts guard the hard constraints: sizeof(FAgentPhysicsData) == 64 (exactly one cache line, in the header) and std::is_trivially_copyable_v<FAgentPhysicsData> (trivially copyable, in PhysicsProcessor.cpp). The header conditionally includes the entity-handle header by engine minor version (UE ≥ 5.8 → Mass/EntityHandle.h, else MassEntityHandle.h) and includes Fragments/GridData.h for the three obstacle grid data types. The AVBD solver state (PairLambda / PairPenalty / PairCContact) has the lifecycle of its pair entry: gather inits (λ=0, ρ=ρ₀), consume sweeps evolve, regather resets — no cross-frame warmstart in v1; full design in references/avbd-solver-design.md cited by the engine sources.