- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
101 lines
12 KiB
Markdown
101 lines
12 KiB
Markdown
# render.model_attachment —— Granny 模型、LOD、骨骼、武器、盾牌和发型挂点
|
||
|
||
状态:`PARTIAL`
|
||
|
||
本合同检查 40250 的模型实例生命周期、Granny 骨骼姿态、LOD 控制器、装备/发型挂点、资源换装、材质替换、碰撞/包围盒和清理语义。判断标准是模型数据来源、LOD 分档、姿态传播、子模型重新挂点、装备更新顺序、缺失资源回退和销毁顺序一致;“能加载模型”或“测试没有断言失败”不能单独视为与 40250 等价。
|
||
|
||
## 40250 参考调用链
|
||
|
||
- `EterGrnLib/ThingInstance.cpp` / `ThingInstance.h`
|
||
- `CGraphicThingInstance` 为每个 Granny LOD 持有 `CGrannyLODController`,模型实例和附着子模型在每个 LOD 上保持关联。
|
||
- `AttachModelInstance` 将子模型挂到父模型骨骼;切换活动 LOD 时要同步活动模型、动作和附着模型,而不是只替换一张渲染网格。
|
||
- `EterGrnLib/LODController.cpp` / `LODController.h`
|
||
- Actor LOD 使用 500cm 高度/5000cm 距离范围,距离分档的关键阈值为 500cm、1500cm、2500cm;选中模型后复制动作、刷新附着模型,并按距离调整动作 FPS。
|
||
- 控制器同时参与更新、变形、渲染、碰撞和包围盒计算;LOD 资源缺失时按 40250 的模型队列回退。
|
||
- `EterGrnLib/ModelInstance*.cpp`
|
||
- `CGrannyModelInstance::UpdateWorldPose` 采样骨骼姿态;`GetBoneMatrixPointer` / `GetCompositeBoneMatrixPointer` 为挂点提供骨骼矩阵;`SetParentModelInstance` 维护子模型父骨骼关系。
|
||
- `GameLib/ActorInstanceAttach.cpp`
|
||
- `PART_WEAPON`、`PART_WEAPON_LEFT`、`PART_HAIR`、`PART_ARMOR` 通过 RaceData 注册的 attaching bone 连接;缺少左手挂点时不伪造挂接。
|
||
- `GameLib/RaceData.cpp` / `RaceData.h` / `GameLib/ItemData.cpp`
|
||
- RaceData 解析基础模型、LOD 模型、挂点骨骼和 HairSkin;ItemData 提供装备模型、LOD 模型列表、材质与皮肤数据,换装时对所有 LOD 控制器生效。
|
||
|
||
## 当前端实现映射
|
||
|
||
- `extension/src/metin2_model.cpp` / `.h`
|
||
- 已加载 GR2/MSM、构造 Skeleton3D 和 CPU/GPU skin mesh,解析兄弟文件 `_lod_01/_02/_03.gr2`,提供发型、武器、盾牌和材质替换入口。
|
||
- 当前默认 LOD 距离为 `[18, 42, 90]` 米;`_set_lod` 只在一个基础骨骼上替换网格,并增加 0.18 秒 `LodGhost` 淡入淡出。它不是 40250 的多 `CGrannyModelInstance` 控制器切换,且未证明切换时动作、子模型、碰撞和所有挂点同步。
|
||
- 刚性武器/盾牌通过 `slot_pre * base_world_pose[bone]` 更新;该路径已有实际 03150 双手武器姿态失败,说明矩阵语义尚未达到参考实现。
|
||
- `extension/src/metin2_anim.cpp`
|
||
- 每个采样动作姿态后调用 `Metin2Model::update_weapon_pose`,当前能驱动本模型的武器/盾牌,但尚无完整 LOD 控制器级动作复制和附着子模型矩阵证据。
|
||
- `extension/src/gr2_bridge.cpp`
|
||
- 提供 GR2 文件、骨骼、材质、网格和姿态桥接;资源解析及单位/坐标转换是 Godot 适配层,不等同于 40250 的 Granny Thing/LOD 生命周期。
|
||
- `project/ui/player_view.gd` / `remote_player_view.gd` / `mob_view.gd`
|
||
- 已按角色、远端角色和怪物创建 `Metin2Model`,设置动作、武器/左手骨骼、发型和基础模型;远端与怪物共享部分 PlayerView 逻辑,但不同 Actor 的完整装备/LOD/事件消费矩阵还没有 40250 级别的逐分支证据。
|
||
- `project/ui/equip_model.gd`
|
||
- 已实现身体、发型/头部、武器的刷新顺序、武器双手判定、MSM HairData 与皮肤选择以及部分精炼效果;当前装备模型主要解析单个 GR2,`_is_poly()` 仍为固定 `false`,变身/遮挡/资源列表与 40250 的 ItemData/RaceData 语义不完全一致。
|
||
|
||
## 分支等价矩阵
|
||
|
||
| 40250 判据/副作用 | 当前实现 | 结论 |
|
||
|---|---|---|
|
||
| GR2/MSM、骨骼绑定姿态、CPU/GPU 变形 | `gr2_bridge` + `Metin2Model` + `Metin2AnimPlayer` 已能加载并采样真实 GR2 | 基础路径存在;GPU/CPU、材质边界和骨骼矩阵仍缺逐资产等价证明 |
|
||
| Actor LOD 阈值 500/1500/2500cm、模型队列和距离范围 | 默认 `[18,42,90]m`,即约 1800/4200/9000cm,并带自定义 15% hysteresis | 不等价;默认常量已与 40250 Actor 分档不同 |
|
||
| LOD 切换时复制动作、刷新装备/发型/盾牌挂点、碰撞和包围盒 | 只替换当前 `MeshInstance3D` 网格,保留一个骨骼并创建 `LodGhost` 淡入淡出 | 不等价;没有控制器级子模型和完整副作用同步 |
|
||
| 右手/左手骨骼解析及武器/盾牌挂接 | `player_view`/`equip_model` 注册骨骼,`update_weapon_pose` 更新刚性附件 | 部分等价;`weapon_attach_test.gd` 的 warrior/run/03150 左手握持距离为 0.75m,超过 0.12m 阈值 |
|
||
| 发型骨骼按名称重映射、HairSkin/SourceSkin→TargetSkin | `_load_hair` 重映射基座骨骼并合并 CPU/GPU mesh,支持 skin override | 部分等价;缺少对所有 LOD、材质数组和缺失骨骼回退的参考级证据 |
|
||
| Armor/weapon/hair 资源更新及多 LOD 生命周期 | `EquipModel` 按身体→头发→武器重建 Godot 子树 | 部分等价;重建顺序存在,但不是 RaceData/ItemData 驱动的全 LOD `Change*`/`SetMaterialImagePointer` 链 |
|
||
| 缺模型、缺骨骼、无左手挂点、缺 LOD 的回退 | 有 GR2/MSM 加载失败日志、骨骼索引 `-1` 和连续 `_lod_0N` 查找 | 部分等价;资源列表、非连续 LOD、子模型清理和缺失挂点的所有边界尚未对齐 |
|
||
| visual/collision/fly target bounds 与地面贴合 | 提供 visual AABB、骨骼 OBB、ground offset 和飞行目标 bounds | 部分等价;当前测试覆盖基础包围盒,但未证明 40250 LOD/碰撞控制器语义和动作切换后的 bounds 一致 |
|
||
| 删除、换图、死亡和重复换装时释放模型/材质/纹理 | `reload` 清理子节点、GR2、材质和缓存引用 | 部分等价;真实模型测试普遍出现 ObjectDB 泄漏,进程有 exit 139,清理不能作为已等价 |
|
||
|
||
## 已执行测试
|
||
|
||
- `project/weapon_attach_test.gd`:失败 1 项;warrior race 0 的 `run` 动作中 `03150.gr2` 左手到武器可见 AABB 距离为 0.750m,期望小于 0.12m;进程另有 exit 139 和 52 个 ObjectDB 泄漏。
|
||
- `project/gpu_lod_attachment_test.gd`:`failures=0`、`ghosts=48`;但有 48 个 ObjectDB 泄漏,且测试证明的是当前自定义网格/ghost 路径,不证明 40250 LOD 控制器等价。
|
||
- `project/gpu_pose_bounds_test.gd`:8 个 race × 6 个 motion × 5 个 sample,`failures=0`;有 48 个 ObjectDB 泄漏。
|
||
- `project/race_motion_assembly_test.gd`:8 个 race × 6 个 motion × 3 个 sample,`failures=0`;有 82 个 ObjectDB 泄漏。
|
||
- `project/equip_model_test.gd`、`project/remote_player_test.gd`、`project/mob_view_test.gd`:基础装备/远端角色/怪物模型断言通过;分别观察到 0、8、8 个 ObjectDB 泄漏,其中远端样本的 03150 武器和 00010 发型出现骨骼匹配告警。
|
||
- `project/rendering_scenario_test.gd`:GPU 0/1 两种场景 `failures=0`;有 54 个 ObjectDB 泄漏。
|
||
- `project/fly_target_bounds_test.gd`、`project/mob_winding_test.gd`:断言通过,但仍存在模型资源/RID 清理告警。
|
||
- `project/animation_cache_test.gd`:`hot motion switches reuse decoded clips` 和 `hot switches reuse parsed MSA metadata` 失败各 1 项;统计为 hits=3/misses=3、msa_hits=3/msa_misses=3,进程 exit 139。该结果是模型动作资源生命周期的相关回归证据,不能视为挂接已通过。
|
||
|
||
## Deep audit round 2026-09-21
|
||
|
||
### Fresh regression results
|
||
|
||
- `weapon_attach_test.gd` 仍失败 1 项:warrior/run/03150 双手姿态左手挂点距离 0.750m,超过 0.12m 阈值;进程 exit 139,并泄漏 52 个 ObjectDB。
|
||
- `gpu_lod_attachment_test.gd`、`gpu_pose_bounds_test.gd`、`race_motion_assembly_test.gd` 的断言分别为 0 failures,但均 exit 139,分别泄漏 48、48、82 个 ObjectDB。
|
||
- `equip_model_test.gd` 断言通过;`remote_player_test.gd`、`mob_view_test.gd` 断言通过但各有 8 个 ObjectDB 泄漏并 exit 139;`animation_cache_test.gd` 仍有 decoded clip/MSA metadata 热切换各 1 项失败,exit 139。
|
||
- `formats_map_test`、`formats_msa_test`、`formats_msm_test`、`net_bounds_test`、`net_entity_test`、`net_classic_session_test`、`net_classic_wire_test` 均退出码 0;formats live A1 断言因未设置 `M2_ASSETS` 跳过。
|
||
|
||
### Static parity conclusion
|
||
|
||
- 40250 的 500/1500/2500cm Actor LOD 控制器、动作复制和每个 LOD 的附着模型生命周期仍未被当前端的 `[18,42,90]m` 单骨骼网格替换等价实现。
|
||
- 03150 左手姿态失败和 animation cache 失败是可重复的实现差异;不能用 GPU/CPU bounds 或基础装备测试通过掩盖矩阵和资源缓存问题。
|
||
- 当前端真实模型路径仍有大量 exit 139/ObjectDB 泄漏,换图、死亡、重复换装、LOD 切换和远端模型删除的清理语义尚不能视为等价。
|
||
|
||
### Active source group review
|
||
|
||
- 对 Visual Studio `EterGrnLib` 活跃编译单元逐组核对后,参考链补充确认:`CGraphicThing` 维护模型/动作资源,`CGraphicThingInstance` 管理多模型实例、LOD 注册、Attach/Detach、材质/高光重绑、motion、bounds 和清理;`CGrannyLODController` 在每个 LOD 上同步模型、动作、材质、骨骼、shadow/bounds;`CGrannyModelInstance::UpdateWorldPose/UpdateWorldMatrices` 生成骨骼世界姿态;`CGrannyMaterial/Palette` 管理材质与 sphere map。
|
||
- 当前 `Metin2Model` 的单骨骼/网格替换和 `slot_pre` 附件路径没有覆盖上述每个控制器的状态传播;这组核心源码已静态核对,但 03150 挂点、LOD、动作缓存和资源清理的已知差异仍保持 `PARTIAL`。
|
||
|
||
### Active GameLib attachment review
|
||
|
||
- `GameLib/ActorInstanceData.cpp` 的 `SetRace`/`SetShape`/`SetPart`、`AttachWeapon` 和 `UpdateAttachingInstances` 将 RaceData/ItemData 的 base、LOD、发型、武器/盾牌和挂接数据注册到多模型实例;`ActorInstance.h` 还暴露按 part 的 bone name、collision piece 和 effect attach 入口。
|
||
- 当前 `EquipModel`/`PlayerView` 能重建身体、头发、武器和 `slot_pre` 附件,但没有同等的多 `CGraphicThingInstance` 控制器传播;03150 左手姿态失败、LOD ghost 泄漏和动作缓存失败仍是可重复差异。
|
||
- 本轮完成活跃 `GameLib` actor 数据/挂接文件的静态核对;该证据进一步确认“单骨骼网格替换”不是 40250 的 1:1 实现,合同保持 `PARTIAL`。
|
||
|
||
## 已确认差异和未验证项
|
||
|
||
1. 必须先统一 Actor LOD 常量、距离单位、分档边界和缺失 LOD 回退,再决定 Godot 网格替换是否能保留 40250 的可观察副作用。
|
||
2. 必须把 LOD 切换时的动作复制、武器/盾牌/发型/盔甲挂点、材质和 bounds 更新做成可观测状态,并用同一组真实 race/item/motion 资产与 40250 快照对照。
|
||
3. 必须定位 `03150.gr2` 在 `run` 姿态的根/局部变换差异,建立右手、左手、双手武器、盾牌、弓和远端角色的逐动作挂点矩阵测试;当前不能以固定 `slot_pre` 通过一组静态样本代替。
|
||
4. 必须覆盖非连续/缺失 LOD、缺骨骼、重复换装、死亡、换图和节点释放,并清除真实模型测试的 ObjectDB 泄漏与 exit 139。
|
||
5. 必须补齐 `_is_poly`、变身装备遮挡和 ItemData/RaceData 多模型资源列表的等价实现或明确其平台适配边界。
|
||
|
||
## 下一轮验证入口
|
||
|
||
- 先用参考常量和真实 `_lod_0N.gr2` 资产建立 LOD 分档快照,分别验证 0/500/1500/2500/5000cm 边界。
|
||
- 再对 03150、盾牌、弓、发型和远端 Actor 采集每个动作采样点的父骨骼/子模型矩阵与可见 bounds,定位坐标系或 authored root offset 差异。
|
||
- 最后修复模型测试清理/崩溃,再把装备换装、LOD 切换、跨图删除和资源复用纳入稳定回归门禁。
|