loganw234/mercs2-mesher
GitHub: loganw234/mercs2-mesher
一个浏览器端的《雇佣兵2》角色模组工具,用于将 glTF 骨骼角色重定向到游戏骨骼并导出 WAD 补丁所需的蒙皮与位置数据。
Stars: 0 | Forks: 0
# mercs2-mesher
这是一个浏览器工具,用于将带有绑定的 glTF 角色重新绑定到 Mercenaries 2 的骨骼上,并导出 WAD 补丁所需的数据:**`skin.bin`**、**`pos.bin`** 和 **调色板范围表**。
它是角色导入流水线的*最终层* —— 也就是需要人工干预的那一部分。在此之前(将网格grafting进 donor 容器)和之后(将字节写入 WAD)的所有工作都保留在现有的 Rust/Python 工具中;该工具涵盖了必须决定哪个源骨骼对应哪个游戏骨骼的步骤,以便在花费时间启动游戏之前,确认结果是否真正有效。
```
split_glb ─► mercs2_workshop --mod-new ─► fix_char_segm ─► dump_group_verts
│
┌─────────────┘
▼
★ mercs2-mesher ★
(retarget · validate · export)
│
skin.bin, pos.bin, ranges
▼
write_skin ─► write_positions ─► merge_patches
```
## 快速开始
打开 **`dist/mercs2-mesher.html`** —— 这是一个完全独立的单文件,无需安装、无需网络、无需服务器。(在 Windows 上,`open.cmd` 会执行此操作。)
1. 拖入一个带绑定的 `.glb` 文件。选择哪个网格作为绘制组(drawing group)。
2. 检查重定向(retarget)。自动映射器能处理大多数绑定;如果有任何错误,可以通过点击骨骼并在游戏的 84 个骨骼中搜索来进行修复。
3. 查看检查结果。
4. 导出 `skin.bin` + `pos.bin`,并复制生成的命令块。
要在编辑源代码后重新构建单文件,请运行:`npm run build`。
要基于源代码进行开发,请启动一个本地服务器来托管该仓库(`python -m http.server`)并打开 `index.html` —— 因为 ES 模块无法通过 `file://` 协议加载,这就是为什么需要打包成单文件的原因。
## 两种变换模式
重新摆姿势(re-pose)需要知道模型的坐标空间是如何映射到容器中的。
| 模式 | 适用场景 | 精度 |
|---|---|---|
| **exact** | 你拖入了一个 `dump_group_verts` TSV 文件时 | 基于实际的容器顶点进行拟合,残差约为 1e-4 —— 这是已在游戏中验证可行的路径 |
| **estimated** | 还没有 TSV 文件时 | 基于骨骼对应关系进行拟合;在 50 Cent 模型上测得相比 exact 模式存在 **平均 2.9 厘米 / 最大 12.3 厘米** 的偏移,导致网格缩短了约 2% |
**在这两种模式下 `skin.bin` 是完全相同的** —— 权重从来不需要容器的数据。因此,你可以在运行任何 Rust 工具之前完成整个重定向工作,然后再拖入 TSV 文件并重新导出真实的位置数据。
## 检查
五个独立的检查,刻意不合并为一个总分。增加每一个检查都是因为曾经有构建通过了之前所有的检查,但在游戏中仍然显示异常。
| 检查项 | 捕捉的问题 | 参考标准 |
|---|---|---|
| 骨骼距离 | 权重指向了错误的骨骼 | 正常发布的 Mercs2 模型平均值为 0.136 / 中位数为 0.124 / p95 为 0.328;随机错误对应值约为 0.77 |
| 三角形面积 | LBS 塌陷 —— 网格被压扁 | 中位数约为 1.0,塌陷率 <2% |
| 肢体方向 | 肢体折叠进躯干内 | 正常约为 3°;而手臂折叠的 bug 显示为 28° |
| 绑定高度链 | 绑定位置混淆了两个不同的坐标空间 | 任何反转 = 导致 1.7 米处的胸部撕裂 |
| 角色高度 | 丢弃了根变换、单位不匹配、模型塌陷 | Mercs2 角色高度约为 1.84 米 |
此外还有静态限制:调色板 ≤ 46 个槽位,≤ 8 个运行段(runs),每组 ≤ 10,900 个三角形,并且保留了多骨骼影响。
**为什么是五个而不是一个:** 强制每个关节都对齐到髋部会使网格塌陷成一团 —— 而且*骨骼距离对这个灾难性错误的评分竟然比正常构建还要好*(0.157 对比 0.161),因为每个顶点确实都靠近它对应的那单个骨骼。角色高度和影响检查可以捕捉到这个问题。这个悖论被固定为一个测试用例(`validate.test.js`),因此这套检查组合永远不会被悄悄简化为一个单一的数字。
## 共享绑定锚点(选项,默认关闭)
重定向是一个多对一的过程:在一个包含 119 个关节的绑定中,**28 个被使用的目标骨骼中有 14 个是由多个源关节提供的**(25 个面部骨骼继承到 `Bone_Head`,17 个手指骨骼继承到每只手上,两节脊柱分别共享 `bone_spine1`)。重新摆姿势会通过 `TGT[h] − SRCP[j]` 移动每个顶点,因此如果组内成员具有不同的 `SRCP`,该组就会被挤压在一起 —— 在 50 Cent 的模型上测得有 3.2% 的三角形存在这种情况,且集中在脊柱和扭转骨骼上。
启用该选项会给组内的每个关节分配一个共享锚点,使该组能够刚性移动。在 50 Cent 模型上测得的权衡结果如下:
| | 塌陷尾部比例 | 骨骼距离 |
|---|---|---|
| 关(默认) | 3.2% | 0.161 |
| 开 | **1.8%** | 0.194 |
局部压缩更少,但对游戏骨骼的贴合度也更低。**默认关闭**,因为此前成功制作出两个已确认可用角色的流水线是以另一种方式(即关闭状态)运作的,而且这种方式尚未在游戏中实际验证过。在 1:1 映射的绑定上,这被证明是一个空操作(no-op)。
## 测试
```
npm test
```
包含 98 个断言。核心测试在于一致性校验:`src/automap.js` 和 `src/build.js` 是对 `tools/model_import_test/automap.py` 和 `build_character.py` 的移植,这两个脚本曾用于生成已被确认能在游戏中正常变形的角色,因此 **Python 脚本即是规范标准**。
- 五种不同绑定下的自动映射一致性(19 / 83 / 94 / 119 / 266 个关节)—— 完全一致
- `skin.bin` 与 `build_character.py` 的输出在**字节级别完全一致**
- `pos.bin` 误差在 9.5e-7 以内(属于 float32 舍入误差;f16 的 POSITION 存储格式甚至根本无法表示这种精度差异)
全新的克隆仓库可以运行其中的 59 个测试;其余的需要使用未提交到仓库的测试夹具(见下文),它们会自动跳过并输出相关的重新生成提示。
## 重新生成大型夹具
第三方美术资源和提取的游戏资产已被添加到 gitignore 中。要恢复完整的测试集:
```
# donor skeleton 导出(tools/bake_skeletons.py 也需要)
mercs2_workshop --export-bundle civ_hum_beachfemale_a --out wsexport
cp wsexport/civ_hum_beachfemale_a/model.gltf test/fixtures/donor_beachfemale.gltf
# 一个用于测试的 character + 其 container dump,来自 injection pipeline
python split_glb.py .glb 0 test/fixtures/50cent_mesh0.glb
dump_group_verts p2.wad 2 > test/fixtures/50cent_g2.tsv
# 预期输出,来自作为 specification 的 Python
python build_character.py test/fixtures/50cent_mesh0.glb test/fixtures/50cent_g2.tsv \
out/ test/fixtures/donor_beachfemale.gltf
cp out/skin.bin test/fixtures/50cent_skin.expected.bin
cp out/pos.bin test/fixtures/50cent_pos.expected.bin
```
`data/skeleton_npc84.json` 和 `skeleton_hero100.json` 是由 `tools/bake_skeletons.py` 烘焙生成的,并且**已经**被提交到仓库中 —— 如果没有它们,该工具将无法运行。它们仅包含骨骼名称、层级结构和静息位置。
## 布局
| 路径 | 内容说明 |
|---|---|
| `index.html` | 用户界面(开发入口;以 ES 模块方式加载 `src/`) |
| `dist/mercs2-mesher.html` | 供用户打开的单文件构建版本 |
| `src/mat.js` | 最小二乘法、极分解、Rodrigues 旋转公式 —— 无依赖 |
| `src/glb.js` | glTF/glb 读取器(POSITION / JOINTS_0 / WEIGHTS_0,蒙皮,层级结构) |
| `src/automap.js` | 绑定到 84 骨骼的自动映射器,基于角色+左右侧+片段 token 模式匹配 |
| `src/build.js` | 调色板、255 归一化权重、方向对齐的重新摆姿势 |
| `src/validate.js` | 五项检查及静态限制 |
| `src/ui/` | 应用连线及 2D 画布的骨骼视口 |
| `data/` | 烘焙的 Mercs2 骨骼及 hero↔NPC 对照转换表 |
| `tools/` | 夹具准备、骨骼烘焙、打包工具 |
## 尚未实现
- **多组注入。** 每次运行只能处理一个网格,因此如果模型的头部是一个单独的网格,它仍然会使用 donor 的头部。
- **纹理。** 导入的模型会在自身的 UV 上使用 donor 的纹理。
- **法线没有被重新摆姿势**(仅针对位置进行了处理)。
- **Hero(100 骨骼)donor。** 骨骼和一个经过验证的哈希对照表已被烘焙(`data/hero_crosswalk.json`,在 hero 绑定中包含了全部 84/84 个 NPC 骨骼),但 UI 目前仅支持映射到 84 骨骼的绑定上。
- 基于 `file://` 协议的单文件构建版本**尚未经过验证** —— 整个流水线已通过 http 在浏览器中验证过(字节精确导出、实时重定向、画布渲染),并且打包文件中不包含任何外部脚本或 fetch 请求,但目前还没有人实际双击运行过它。
标签:3D动画, glTF, Mercenaries 2, 前端工具, 可视化界面, 多模态安全, 数据可视化, 暗色界面, 游戏Modding, 自定义脚本, 逆向工具, 骨骼重定向