diff --git a/contracts/VERSION b/contracts/VERSION index 66b5603..4044fac 100644 --- a/contracts/VERSION +++ b/contracts/VERSION @@ -1,8 +1,8 @@ # OpenDance Studio 契约版本 # 规则:任何字段级变更必须提升对应条目版本,并回到 opendance-web 修改后再向另两端建迁移任务。 -rest-api: 0.1.0 +rest-api: 0.2.0 websocket-keypoints: 0.1.0 -data-models: 0.1.0 +data-models: 0.2.0 skeleton-topology: 0.1.0 -model-formats: 0.1.0 +model-formats: 0.2.0 database: 0.1.0 diff --git a/contracts/data-models.md b/contracts/data-models.md index 9acd409..5037968 100644 --- a/contracts/data-models.md +++ b/contracts/data-models.md @@ -1,4 +1,4 @@ -# contracts/data-models.md — 数据模型契约 v0.1.0 +# contracts/data-models.md — 数据模型契约 v0.2.0 Web 服务端是唯一权威定义,本文件是另两端消费的**稳定视图**。 字段名在 JSON 中统一 `camelCase`;数据库列名 `snake_case`(见 `database.md`)。 @@ -75,7 +75,7 @@ rejected 已驳回 | `id` | number | 读 | | | `songId` | number\|null | | 关联曲目 | | `name` | string | ✅ | 动作名称,≤200 | -| `format` | enum | ✅ | `bvh` / `fbx` / `gltf` / `glb` / `keypoints-json` | +| `format` | enum | ✅ | `bvh` / `vmd` / `fbx` / `gltf` / `glb` / `keypoints-json` | | `fileUrl` | string | 读 | 原始动作文件 | | `fileSize` | number | 读 | 字节 | | `fps` | number\|null | | 原始帧率 | diff --git a/contracts/model-formats.md b/contracts/model-formats.md index c4af693..b31f67a 100644 --- a/contracts/model-formats.md +++ b/contracts/model-formats.md @@ -1,4 +1,4 @@ -# contracts/model-formats.md — 模型与动作格式契约 v0.1.0 +# contracts/model-formats.md — 模型与动作格式契约 v0.2.0 ## 1. 角色模型(models) @@ -19,6 +19,7 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x) | 格式 | 扩展名 | 预处理支持 | 说明 | |---|---|---|---| | BVH | `.bvh` | ✅ 内置纯 TS 解析器 | 文本格式,服务端直接解析 → FK → MediaPipe33 | +| VMD | `.vmd` | ✅ 内置纯 TS 解析器 | MikuMikuDance 动作数据(二进制,Shift-JIS 骨骼名 + 四元数关键帧);服务端按标准 MMD 骨架做 FK + 腿部 IK 求解 → MediaPipe33,见 §2.3 | | FBX | `.fbx` | ⚠️ 需 Assimp | 服务端调用 `assimp` 可执行文件导出为 BVH/glTF 后再解析;未安装则 `preprocessState=failed` | | glTF/GLB | `.gltf` / `.glb` | ⚠️ 需 Assimp | 同上 | | 关键点 JSON | `.json` | ✅ 直通校验 | 已是 `mediapipe33@0.1.0` 结构,仅做校验与归一化 | @@ -65,6 +66,47 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x) 未映射到的点按 `skeleton-topology.md` §5 的层级由父节点推算, 并置 `confidence` 降低(`0.5`),表示估算而非实测。 +### 2.3 VMD(MikuMikuDance)解析约定 + +VMD 是二进制格式:文件头 `Vocaloid Motion Data 0002`(30 字节)+ 模型名(20 字节, +Shift-JIS)+ 骨骼关键帧数组(每条 **111 字节**:15 字节骨骼名、uint32 帧号、 +float32×3 位移、float32×4 旋转四元数、64 字节贝塞尔插值参数)+ morph / 相机 / +灯光 / 自阴影 / IK / 可见性段落。**VMD 不含骨骼长度**,只有旋转(少数骨骼带位移), +因此服务端必须自带一套标准 MMD 骨架才能做 FK。 + +处理链路:`vmd-parser.ts`(二进制 + Shift-JIS + 贝塞尔插值)→ `mmd-skeleton.ts` +(标准骨架)→ `mmd-pose.ts`(FK + 腿部 IK)→ `retarget.ts` 的 +`worldFramesToKeypointFrames()`(与 BVH 完全共用骨盆归零 / 自适应缩放 / 图像空间投影 / +9 个关节角)。固定 **30 fps**(MMD 帧率恒定,上传时填写的 `fps` 被忽略)。 + +| MediaPipe 点 | MMD 标准骨骼 | 说明 | +|---|---|---| +| 0 nose | `頭` | 取头骨根部再上移,近似面部中心 | +| 11 / 12 left/right_shoulder | `左腕` / `右腕` | 肩关节(不是 `左肩` 骨骼) | +| 13 / 14 left/right_elbow | `左ひじ` / `右ひじ` | | +| 15 / 16 left/right_wrist | `左手首` / `右手首` | | +| 23 / 24 left/right_hip | `左足` / `右足` | 大腿根部 | +| 25 / 26 left/right_knee | `左ひざ` / `右ひざ` | | +| 27 / 28 left/right_ankle | `左足首` / `右足首` | | +| 31 / 32 left/right_foot_index | `左つま先` / `右つま先` | | + +骨骼名按 `mmd-skeleton.ts` 的别名表归一(含全角 `IK` / 半角 `IK` 两种写法); +未纳入标准骨架的骨骼(裙摆、物理、表情等)被忽略但会计数并写入预处理日志。 + +**腿部驱动判定**:`足IK` 轨道存在非零位移 → 走 IK(用两骨骼解析 IK 把踝拉到 IK +目标点,膝盖朝角色正面弯,不可达时收敛为伸直);否则走 FK(部分动作数据把腿烘焙成 +FK 旋转)。两份示例文件分别命中两种路径。 + +**坐标系**:MMD 原生是左手系(正面为 -Z),本项目通路是右手系(正面为 +Z、+X 为角色 +左侧)。VMD 的四元数与位移统一做 Z 镜像(`(x,y,z,w) → (-x,-y,z,w)`、位移 z 取反)后 +送入标准骨架。该选择由真实数据对照实验确定:腿部烘焙为 FK 的样本中,`zMirrored` 下 +膝盖朝前占比 94.1%,不转换时仅 5.9%(见 `test/mmd-vmd.spec.ts`)。 + +**标准骨架数值来源**:解析参考模型的 PMX 骨骼段实测得到(而非凭经验估计), +再去掉模型自身原点偏移并抹平左右不对称。关键比例:足首 1.43 / ひざ 6.57 / +髋 11.50 / センター 10.88 / 首 16.21 / 頭 17.16(总高约 19.4,髋高比约 59%); +大腿 4.94 + 小腿 5.15 ≈ 腿长 10.07。 + ## 3. 上传限制 | 项 | 值 | diff --git a/contracts/rest-api.yaml b/contracts/rest-api.yaml index 972cd76..ed89909 100644 --- a/contracts/rest-api.yaml +++ b/contracts/rest-api.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: OpenDance Studio Web 管理服务端 API - version: 0.1.0 + version: 0.2.0 description: | OpenDance Studio 的唯一数据源与契约权威。 契约版本见 contracts/VERSION。字段语义见 contracts/data-models.md。 @@ -11,11 +11,14 @@ info: - 来源五元组(sourceType/sourceName/sourceUrl/license/author)在三类资产上均为必填 servers: - url: http://localhost:3000 - description: 本地开发 + description: 本地开发(软件 API 端口) - url: http://:3000 description: >- - Linux 业务服务部署(不使用 Docker)。服务自带管理端页面,反向代理 - (外部提供,非本项目交付物)只需将请求整体转发到该地址即可。 + Linux 业务服务部署的**软件 API 端口**(`PORT`,默认 3000)。Windows/Godot 客户端与 + Android App 调用本端口的 /api、/health、/static、/contracts。 + 管理端页面在**管理端口**(`ADMIN_PORT`,默认 3001),该端口同时提供全部接口; + API 端口不提供管理页面,未命中的路径一律 404。 + 反向代理由另外的服务提供(非本项目交付物),按用途分别转发到对应端口即可。 tags: - name: health @@ -180,7 +183,7 @@ paths: schema: { type: integer, format: int64 } - name: format in: query - schema: { type: string, enum: [bvh, fbx, gltf, glb, keypoints-json] } + schema: { type: string, enum: [bvh, vmd, fbx, gltf, glb, keypoints-json] } - name: status in: query schema: { $ref: '#/components/schemas/PublishStatus' } @@ -589,7 +592,7 @@ components: enum: [draft, pending, published, rejected] MotionFormat: type: string - enum: [bvh, fbx, gltf, glb, keypoints-json] + enum: [bvh, vmd, fbx, gltf, glb, keypoints-json] ModelFormat: type: string enum: [vrm, glb, gltf, fbx]