约束拓扑:Attachment 定义帧,Constraint 连接物理宿主
自动关联目录:约束拓扑:Attachment 定义帧,Constraint 连接物理宿主
核心判断:Attachment 是约束帧的来源,不一定是刚体节点;Constraint 真正连接的是两个最终物理宿主。只有先把“内容父子树”解析为“物理拓扑图”,才能正确处理局部帧、骨骼或中间节点、运行时改父级、连通岛、所有权和销毁清理。
一、业务冲突:创作者连接的是对象层级,求解器需要的是刚体与局部帧
创作者希望把 Attachment 放在物体、模型层级或动画驱动节点下,再用刚性、铰链、滑轨、球铰等 Constraint 连接两端。编辑语义强调层级和可见 Transform;UE 求解器却只接受明确的 UPrimitiveComponent、Body Instance 与相对到 Body 的 Constraint Frame。
如果把 Attachment 的直接 Parent 当成刚体,会在中间 Folder、Model 或骨骼节点出现错误:这些对象可能只有 WorldCFrame,没有 Body;两个 Attachment 也可能最终落到同一个刚体,建立自约束;异步构建时引用存在但目标尚未物化;改 Parent、销毁 Attachment 或流式移出后,旧 Joint 仍持有失效端点。
更大的业务冲突来自连通性。两个物体之间只有一个 Constraint 时逐条管理似乎足够,但一条桥边可以把两个独立结构合并,移除后又会拆成两个岛。只更新被编辑的 Joint,无法同步更新模拟所有权、内部碰撞、复制成员与执行域。Constraint 是局部配置,连通岛才是运行时一致性单元。
二、实际实现:先解析结构,再采样帧,最后物化求解器
端点解析被拆成两个阶段。ResolveTopology 从 Attachment 沿父链向上,检测循环与失效引用,直到找到可解析为规范 UPrimitiveComponent 的物理宿主。若中间节点只具备世界 Transform 能力,端点模式标记为 EvaluatedWorld;Attachment 直接位于刚体层级时则为 RigidLocal。拓扑观察无副作用,只返回本地对象身份、宿主、World 和模式。
只有当前执行 Owner 才调用 SampleExecutionFrame。它会重新解析拓扑,确认宿主、World、Primitive 与模式未变化,再读取 Attachment 的 Local 或 World Transform;失败时不复用上一帧,以免旧骨骼姿势悄悄驱动新拓扑。局部模式直接得到 Body Frame,世界模式用当前 Host Transform 反算 Local Frame,并暴露帧就绪的 Tick 前置关系。
World 级子系统监听节点新增、删除、父级变化和属性变化,用反向依赖索引只把受影响 Constraint 放入 Dirty Set,在 PrePhysics 统一 Reconcile。引用缺失与引用尚未解析是不同 Pending Reason;跨 World、相同宿主、无效 Host 和帧未就绪分别停止物化。已有 Joint 只有在宿主、引用模式或配置发生实质变化时才重建。
权威图使用稳定 UniqueId,而不是每端不同的 VM ObjectId。每条边记录 Constraint、Attachment 与最终 Host;增删边只重建种子所在的旧连通分量。拓扑完全相同时保留 IslandId 与 Generation,合并或拆分则分配新拓扑代际。映射到本地 ObjectId 时任何成员缺失都会整岛失败,不产生半份本地描述。
flowchart LR
A0[Attachment A] --> R0[解析最终 Host A]
A1[Attachment B] --> R1[解析最终 Host B]
R0 --> E[Constraint Edge]
R1 --> E
E --> G[稳定身份拓扑图]
G --> I[Connected Island]
I --> L[本地身份映射]
L --> J[UE Constraint Solver]三、值得保留:拓扑身份与执行对象没有混为一谈
稳定身份图与本地 ObjectId 映射分离是最重要的设计。不同 VM 可以为同一内容分配不同整数 ID,但只要 UniqueId 一致,就能得到同一个岛;映射失败又是全有或全无,避免一端按三成员求解、另一端只看到两个成员。
端点的结构解析与每帧姿势采样分离也非常正确。普通刚体 Attachment 的 Local Frame 不需要每帧追踪;位于动画或其它世界变换节点下的 Attachment 才需要 Evaluated World Frame 和 Tick Prerequisite。查询能力不会顺便修改角色 Owner、CMC Mode 或速度,说明 Domain Provider 与结构解析基本保持了只读性质。
增量重建按受影响连通分量展开,而不是每次扫描并重建所有岛。桥边合并、移除后拆分、环和并行边都有测试,未受影响岛保持原身份与拓扑代际。这既是性能优化,也是网络正确性:无关岛不应因别处编辑而被迫换 epoch、清空快照和切权。
四、结构性缺陷:通用 WorldCFrame 暂时掩盖了骨骼语义
当前解析器刻意不读取 Bone、Socket 或 Skeleton 拓扑,只要中间祖先具备 World Transform 就走 EvaluatedWorld。这让结构能先工作,但丢失了三个重要契约:骨骼身份是否稳定、Pose 在哪个 Tick 阶段完成、骨架重建后 Attachment 应如何重绑。对普通层级足够,对动画骨骼附着仍只是“每帧得到一个世界帧”,不是完整的 Bone Attachment 方案。
World 子系统承担了引用解析、Dirty 调度、Joint 物化、Domain Provider、岛图、内部碰撞和切权协调,职责过重。反向索引虽能限制重建范围,但生命周期逻辑仍散在 NodeAdded、Destroying、Destroyed、Reload 与 Reconcile 多条入口中;若缺少统一的拓扑事务,很容易出现旧 Edge 已删、旧 Solver 未销毁或岛已换代但快照状态未退休。
岛身份在拓扑改变时重新分配,语义清晰但会放大频繁编辑的网络成本。对于仅 Constraint 参数变化、Attachment Frame 变化和真正成员变化,应分别推进 ConfigRevision、FrameRevision 与 TopologyGeneration;若都归为拓扑重建,会造成不必要的所有权屏障和快照缓存清空。
五、目标设计:以 Topology Transaction 驱动物理、切权和复制
每次内容变化先在纯数据层生成 ConstraintTopologyCandidate:稳定节点、端点绑定、帧提供者、Constraint 配置与连通分量。Validator 检查引用完整性、World、同体约束、环语义和执行域能力;Diff 明确区分 Frame、Config 与 Membership 变化。只有 Candidate 完整时才一次替换图,并向 Solver、Ownership 与 Replication 发布同一 Revision Set。
ReconcileResult ReconcileTopology(ChangeBatch changes)
{
Candidate next = graph.BuildCandidate(changes);
Validation v = ValidateEndpointsAndProviders(next);
if (!v.ready) return KeepOldTopology(v.pendingReason);
TopologyDiff diff = Diff(graph.committed, next);
SolverPlan solvers = PlanAffectedSolvers(diff);
IslandPlan islands = PlanAffectedComponents(diff);
Prepare(solvers, islands);
if (!AllReady()) return KeepOldTopology(PendingDependency);
AtomicSwap(graph.committed, next);
PublishRevisions(diff); // frame/config/topology 各自推进
RetireOldSolversAndIslands(diff);
return Committed;
}骨骼 Attachment 不应由通用祖先遍历猜测,而应接入 IFrameProvider:Rigid Body、Scene Transform、Skeletal Socket 各自声明稳定身份、采样阶段、Generation 和降级策略。DS 没有动画 Pose 时,内容必须声明使用参考姿势、逻辑 Socket 轨迹、简化刚体或不允许该 Constraint,不能静默得到不同帧。
六、决策规则:区分端点、宿主、帧提供者与岛
| 变化或场景 | 影响范围 | 正确处理 |
|---|---|---|
| 修改 Constraint 限位/电机 | ConfigRevision | 原地更新或仅重建该 Solver,不改岛成员 |
| 移动刚体下 Attachment | FrameRevision | 更新局部 Frame,保留拓扑与所有权 |
| 动画 Socket Attachment | FrameProvider Generation | 在声明 Tick 后采样;DS 使用明确降级 |
| Attachment 改父级到另一刚体 | TopologyGeneration | 重建受影响连通分量并重新协调所有权 |
| 引用尚未物化 | PendingReference | 保留旧提交或不激活新 Constraint,禁止半连接 |
| 两端解析到同一 Body | InvalidTopology | 拒绝 Solver,输出结构化原因 |
| 桥边移除导致拆岛 | 新岛身份/epoch | 原子退休旧岛,分别建立新岛 |
| Host/Attachment 销毁或流出 | 反向依赖清理 | 先摘图和 Solver,再释放对象引用 |
这部分实践已经超越了“封装几个 UPhysicsConstraintComponent”:它把内容树、物理宿主、本地帧和连通岛区分开了。下一步要继续收紧的是事务边界和 Frame Provider,尤其是动画骨骼与 DS 降级;否则普通 Attachment 很稳,骨骼 Attachment 仍会把时序与身份问题带回求解器。