`model-archive.spec.ts` / `mmd-vmd.spec.ts` 用的都是合成夹具与假 Assimp,
证明得了边界,证明不了「真素材能出片」。新增 opt-in 的
`test/real-samples.spec.ts`(6 项),样例或 Assimp 缺失时整体跳过,
CI 与无素材的开发机不受影响:
① 模型 → VRM(两个真实 PMX 压缩包)
- 初音未来 1.0 MB → 1.10 MB VRM,humanoid 19/22,2 张贴图全内嵌;
- 奥黛塔 28.1 MB → 26.08 MB VRM,humanoid 21/22,18 张贴图全内嵌,
39 材质(36 项双面已补回);两者 drivable = true;
- 断言 VRMC_vrm 1.0、所有 images 带 bufferView 且无 uri、无 MMD 专属
材质扩展泄漏、蒙皮下标不越界,并**逐字节复算 POSITION 访问器的
min/max**(glTF 唯一强制填写的边界,客户端据此做视锥剔除与 AABB;
读访问器必须按 byteStride 走,否则交错缓冲里的 u16 会被读成乱码)。
② 动作内部标准 + 骨骼重绑定(真实 VMD,0.8 MB / 96.7 s)
- maxFrame 2900 → 2901 帧、标准骨架 31/31 命中、双侧腿部 IK 链齐备;
- MMD 骨骼名 → joint-map 候选名的重绑定结果逐一比对(15 个键);
- 15 个直接关节的置信度恒为 1.0(绝不退化成 0.5 的推算值),
其余 18 点为 0.5,缺失为 0;骨盆归零后投影 max|xy| = 0.85 ≤ 1;
- NDJSON 往返一致、缩略图 PNG 有效、手腕摆幅非退化。
顺带修 `assimp-locate.spec.ts` 的非密闭性:其用例断言的是 PATH/常见系统
目录查找,但会继承外部 ASSIMP_BIN(assimp-lib.sh 中 ASSIMP_BIN 优先级高于
PATH),开发者按本项目文档设过 ASSIMP_BIN 时 npm test 会假失败。
规模 24 文件 / 289 项 → 25 文件 / 295 项。产物与已发布的 v0.7.4 无关
(发布包不含 web-server/test/),故不新发版本。
3.6 KiB
3.6 KiB
T-WEB-012 模型主力格式:全面转为 VRM
- 状态: done
- 依赖: T-WEB-005 / T-WEB-007
- 文档: v1.6 §4.9
- 契约:
model-formats 0.5.0、data-models 0.7.0
目标
所有入库模型统一转成 VRM(glTF 2.0 容器 + humanoid 扩展), 客户端只消费 VRM,避免三种格式各自的骨骼命名/坐标系差异在客户端叠加。
方案
| 项 | 做法 |
|---|---|
| 转换默认值 | MODEL_CONVERT_FORMAT 默认 vrm(.env.example 同步) |
| 直传 | .vrm / .glb 已经是 glTF 容器:直接入库、不重编码 |
| 需转换 | .pmx / .fbx / .zip 由 Assimp 转 GLB,再补齐 VRM 扩展 |
| 可用性判定 | isUsableModel():format === 'vrm' 才可用 |
| 不可用模型 | 入库但标记为「不可用」,unusableReason() 说明原因;不进已发布目录 |
| 目录过滤 | catalog/export 只下发 format = vrm 的模型;catalog/version 指纹同样只算可用的 |
| 默认模型 | resolveDefaultModelId() 只在可用模型里挑 |
验收
| 验收项 | 结果 |
|---|---|
| 上传 PMX/FBX/zip 后产出 VRM | ✅ Assimp → GLB → VRM 扩展补齐;实测 MMD 模型(初音未来 / 奥黛塔)转换成功 |
| 非 VRM 模型不出现在客户端目录 | ✅ catalog/export 过滤 + 指纹过滤;test/model-usability.spec.ts 覆盖 |
| 不可用模型有明确原因 | ✅ DTO 增 usable / unusableReason 字段 |
| 三端契约一致 | ✅ data-models 0.7.0 / model-formats 0.5.0 快照三端逐字节一致 |
真实样例回归(T-WEB-003 / T-WEB-011)
test/real-samples.spec.ts 是 opt-in 的真实素材端到端用例:合成夹具证明不了
「真的 MMD 素材能出片」,而这个文件直接用仓库根 示例文件/ 下的真模型跑完整转换管线。
样例与 Assimp 任一缺失时整个 describe 跳过,因此无素材的开发机与 CI 依旧全绿。
# 跑真实素材(Assimp 不在 PATH 时用 ASSIMP_BIN 指绝对路径)
ASSIMP_BIN=/usr/bin/assimp npm test -- test/real-samples.spec.ts
# 样例放在别处时
OPENDANCE_SAMPLES_DIR=/path/to/示例文件 ASSIMP_BIN=/usr/bin/assimp npm test
实测(Assimp 6.0,2024 版素材):
| 样例 | 输入 | 产物 | humanoid | 贴图 | 材质 |
|---|---|---|---|---|---|
| 初音未来(PMX in zip) | 1.0 MB | 1.10 MB VRM | 19/22,drivable |
2 张全内嵌 | 3 |
| 奥黛塔(PMX in zip) | 28.1 MB | 26.08 MB VRM | 21/22,drivable |
18 张全内嵌 | 39(36 双面已补回) |
用例断言的不变量(与具体模型无关):
extensions.VRMC_vrm.specVersion === "1.0",asset.version === "2.0"- 所有
images都带bufferView、无uri(外链贴图离开原目录即坏图) - 无
KHR_materials_specular/volume/transmission/ior/emissive_strength泄漏(MMD 专属参数不能进标准 PBR 产物) - POSITION 访问器的
min/max与逐元素读出的真实包围盒一致 —— 这是 glTF 规范唯一强制填写的边界,客户端拿它做视锥剔除与 AABB,填错会导致模型被整体剔除。读访问器时必须按byteStride走,否则交错缓冲里的 u16 数据会被读成乱码 - 蒙皮下标
< skin.joints.length(越界会让客户端取到错误矩阵)
已知的上游瑕疵(不影响可用性):Assimp 6.0 的 glTF2 导出器会给
JOINTS_0与inverseBindMatrices写偏保守的min(前者把真实最小值 6 写成 0, 后者对角元 1 写成2.2e-308)。两者都是合法下界(min ≤ 真实值 ≤ max), 没有任何运行时会据此做剔除,因此不改产物、只在用例里锁定真正要紧的 POSITION 边界。