docs: 消除 Gitea 模棱两可 Unicode 告警(全角标点紧邻 ASCII 时改为半角)
依据 Gitea v1.27.3 modules/charset/escape_stream.go 的判定规则 (analyzeDetectResults:仅当左右相邻字符都不是非 ASCII 时才标记, 且 _common 表恒定生效),把说明文档中会被标记的全角标点 (,:;?!() 等)改为半角,并保证成对括号宽度一致。 中文语境中的全角标点(左右均为中文)保持原样,不动代码与契约语义。
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# DSH.md -- opendance-windows(OpenDance Studio 项目二:Windows 客户端)
|
||||
# DSH.md -- opendance-windows(OpenDance Studio 项目二:Windows 客户端)
|
||||
|
||||
## 0. 本文件的作用
|
||||
|
||||
@@ -11,7 +11,7 @@ Windows 客户端是核心体验载体:接收 Android 辅助 App 推送的 33
|
||||
驱动 VRM 角色,并与 Web 服务端下发的参考动作做实时 DTW 评分。
|
||||
|
||||
```
|
||||
Android 辅助 App ──239 字节关键点帧──▶ 本端(WebSocket 服务端:8765)
|
||||
Android 辅助 App ──239 字节关键点帧──▶ 本端(WebSocket 服务端:8765)
|
||||
│
|
||||
Web 服务端 ──GET /api/catalog/export──────┤
|
||||
──GET /api/motions/:id/keypoints┘
|
||||
@@ -25,7 +25,7 @@ Web 服务端 ──GET /api/catalog/export──────┤
|
||||
|
||||
| 组件 | 选型 | 说明 |
|
||||
|--|--|--|
|
||||
| 引擎 | Godot 4.7.2(MIT) | 可在 Linux 上导出 Windows exe |
|
||||
| 引擎 | Godot 4.7.2(MIT) | 可在 Linux 上导出 Windows exe |
|
||||
| VRM 加载 | **Godot 内置 GLTFDocument** | 见 6 的选型变更说明 |
|
||||
| 多格式导入 | Godot 内置 glTF 导入器 | FBX 由管理端转 BVH 后下发 |
|
||||
| 通信 | WebSocketPeer + HTTPRequest | 无第三方依赖 |
|
||||
@@ -66,7 +66,7 @@ opendance-windows/
|
||||
├── export_presets.cfg # ★ 必须位于项目根目录(Godot 硬性要求)
|
||||
├── project.yaml / DSH.md
|
||||
├── tasks/ # 001~011 单任务说明书
|
||||
├── contracts/ # 只读契约快照(来源 opendance-web v0.1.0)
|
||||
├── contracts/ # 只读契约快照(来源 opendance-web v0.1.0)
|
||||
├── scenes/main.tscn # 仅一个挂载 main.gd 的根节点
|
||||
├── src/
|
||||
│ ├── autoload/ # AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
@@ -76,7 +76,7 @@ opendance-windows/
|
||||
│ ├── score/ # 特征提取、DTW、评分规则
|
||||
│ └── ui/ # 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
|
||||
├── tests/ # 无头测试框架 + 用例
|
||||
└── build/ # 导出脚本(sh / ps1)
|
||||
└── build/ # 导出脚本(sh / ps1)
|
||||
```
|
||||
|
||||
## 6. 界面构建方式与选型变更(重要)
|
||||
@@ -121,7 +121,7 @@ $GODOT --headless --path. --script res://tests/run_tests.gd
|
||||
# 3. 应用可启动(autoload + UI 构建无报错)
|
||||
$GODOT --headless --path. --quit-after 180
|
||||
|
||||
# 4. Windows 导出(需先安装 export templates)
|
||||
# 4. Windows 导出(需先安装 export templates)
|
||||
bash build/export-windows.sh release
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# opendance-windows
|
||||
|
||||
OpenDance Studio **阶段二**:Windows 客户端(Godot 4)。
|
||||
OpenDance Studio **阶段二**: Windows 客户端(Godot 4)。
|
||||
|
||||
接收 Android 辅助 App 通过 WebSocket 推送的 33 点骨骼关键点,驱动 VRM 角色,
|
||||
并与 Web 管理服务端下发的参考动作做实时 DTW 评分。
|
||||
@@ -9,14 +9,14 @@ OpenDance Studio **阶段二**:Windows 客户端(Godot 4)。
|
||||
|
||||
## 状态
|
||||
|
||||
**阶段二已完成**:T-WIN-001 -> T-WIN-011 全部落地。
|
||||
**阶段二已完成**: T-WIN-001 -> T-WIN-011 全部落地。
|
||||
|
||||
| 验收项 | 结果 |
|
||||
|--|--|
|
||||
| 脚本编译 | OK 全部通过(无 SCRIPT ERROR) |
|
||||
| 无头测试 | OK **580 项全部通过**(8 个测试套件) |
|
||||
| 脚本编译 | OK 全部通过(无 SCRIPT ERROR) |
|
||||
| 无头测试 | OK **580 项全部通过**(8 个测试套件) |
|
||||
| 应用启动 | OK 180 帧无运行时错误 |
|
||||
| Windows 导出 | OK 生成 `OpenDance.exe`(104 MB,PE 头校验通过)+ `OpenDance.pck` |
|
||||
| Windows 导出 | OK 生成 `OpenDance.exe`(104 MB, PE 头校验通过)+ `OpenDance.pck` |
|
||||
| 真实 VRM 验证 | OK 154 骨骼 VRoid 模型,humanoid 覆盖率 **100%** |
|
||||
| 骨骼重定向 | OK 关键点方向与骨骼实际指向点积 **1.0000** |
|
||||
| 跨实现交叉校验 | OK 关节角与 Web 服务端 TypeScript 实现**逐帧一致** |
|
||||
@@ -45,10 +45,10 @@ bash build/package-release.sh 0.1.0
|
||||
|
||||
| 项 | 要求 |
|
||||
|--|--|
|
||||
| 操作系统 | 运行客户端:Windows 10/11(x86_64)。开发与导出:Windows 或 Linux 均可 |
|
||||
| Godot | **4.7.x**(`project.godot` 声明特性 `4.7`,本仓库按 4.7.2 验证)。使用**标准版**(GDScript),**不需要 .NET / C# 版** |
|
||||
| 操作系统 | 运行客户端:Windows 10/11(x86_64)。开发与导出:Windows 或 Linux 均可 |
|
||||
| Godot | **4.7.x**(`project.godot` 声明特性 `4.7`,本仓库按 4.7.2 验证)。使用**标准版**(GDScript), **不需要 .NET / C# 版** |
|
||||
| 显卡与图形界面 | 渲染后端为 **GL Compatibility**,运行需支持 OpenGL 3.3 的显卡与驱动。仅无头测试与导出可无 GPU、可无图形界面 |
|
||||
| 导出模板 | **仅打包导出时需要**:必须安装与编辑器**完全同版本**的 export templates(`.tpz`) |
|
||||
| 导出模板 | **仅打包导出时需要**:必须安装与编辑器**完全同版本**的 export templates(`.tpz`) |
|
||||
| 磁盘与网络 | Godot 编辑器 + 导出模板约数百 MB;导出产物 `OpenDance.exe` 约 104 MB、`OpenDance.pck` 约 107 KB。本端需能访问 Web 服务端 API(默认 TCP 3000),并与 Android 设备处于同一局域网、放行本端 **TCP 8765 入站** |
|
||||
|
||||
### 2. 获取代码
|
||||
@@ -75,7 +75,7 @@ cd opendance-windows
|
||||
|
||||
- 位置:主界面顶部「**服务端地址**」输入框,填好后点「**保存并测试**」。
|
||||
- 配置字段名(`opendance.cfg`):`api_base_url`。
|
||||
- **端口区分**:客户端只访问 Web 服务端的 **API 端口(默认 3000)**;管理端页面在**管理端口(默认 3001)**,客户端不使用该端口。
|
||||
- **端口区分**:客户端只访问 Web 服务端的 **API 端口(默认 3000)**;管理端页面在**管理端口(默认 3001)**,客户端不使用该端口。
|
||||
- 示例占位符:
|
||||
|
||||
```
|
||||
@@ -89,8 +89,8 @@ http://192.0.2.10:3000
|
||||
|
||||
| 对端 | 方向 | 本端配置 | 说明 |
|
||||
|--|--|--|--|
|
||||
| Android 辅助 App | Android -> 本端(WebSocket) | `listen_host`(默认 `0.0.0.0`)、`listen_port`(默认 **8765**)、`ws_subprotocol`(默认 `opendance.pose.v1`) | 本端是 WebSocket **服务端**;Android 端填写本端主界面显示的「接收地址」,形如 `ws://<本机局域网地址>:8765/pose` |
|
||||
| Web 服务端 | 本端 -> Web(HTTP) | `api_base_url`(默认 `http://127.0.0.1:3000`) | 需先在服务端**发布**曲目 / 动作 / 模型,动作还须**完成关键点预处理**,否则目录为空或动作不可选 |
|
||||
| Android 辅助 App | Android -> 本端(WebSocket) | `listen_host`(默认 `0.0.0.0`)、`listen_port`(默认 **8765**)、`ws_subprotocol`(默认 `opendance.pose.v1`) | 本端是 WebSocket **服务端**; Android 端填写本端主界面显示的「接收地址」,形如 `ws://<本机局域网地址>:8765/pose` |
|
||||
| Web 服务端 | 本端 -> Web(HTTP) | `api_base_url`(默认 `http://127.0.0.1:3000`) | 需先在服务端**发布**曲目 / 动作 / 模型,动作还须**完成关键点预处理**,否则目录为空或动作不可选 |
|
||||
|
||||
- **Android 侧无本端可配置项**:接收地址由 Android App 自己填写;本端只监听固定端口 8765(可在 `opendance.cfg` 改 `listen_port` 后点「重新连接接收服务」生效)。
|
||||
- 已知限制:Godot 的 `WebSocketPeer` 不暴露握手请求路径,本端**接受任意 path**,只校验子协议 `opendance.pose.v1`(见「已知限制」1)。
|
||||
@@ -111,8 +111,8 @@ bash build/package-release.sh 0.1.0
|
||||
|
||||
| 产物 | 位置 | 说明 |
|
||||
|--|--|--|
|
||||
| `OpenDance.exe` | `build/windows/` | 主程序,约 104 MB;脚本校验前 2 字节为 `MZ`(合法 PE) |
|
||||
| `OpenDance.pck` | `build/windows/` | 资源包,约 107 KB;**必须与 exe 同目录** |
|
||||
| `OpenDance.exe` | `build/windows/` | 主程序,约 104 MB;脚本校验前 2 字节为 `MZ`(合法 PE) |
|
||||
| `OpenDance.pck` | `build/windows/` | 资源包,约 107 KB; **必须与 exe 同目录** |
|
||||
| `OpenDance.console.exe` | `build/windows/` | 控制台包装器,可能生成,非必需 |
|
||||
| `opendance-windows-v0.1.0.zip` | `dist-release/` | 发布包:exe + pck + `THIRD_PARTY.md` + `README.txt` + `contracts/` |
|
||||
|
||||
@@ -133,9 +133,9 @@ bash build/package-release.sh 0.1.0
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|--|--|--|
|
||||
| 游戏界面提示「模型加载失败:缺少骨骼」或「骨骼绑定失败,模型可能不受支持」 | 模型没有 `Skeleton3D`,或骨骼命名不在 humanoid 映射表内;驱动至少需要 hips / spine / neck / head + 四肢共 16 根核心骨骼 | 换用标准 VRM(VRoid Studio 导出);看「人形覆盖率」,低于 60% 会提示「骨骼覆盖偏低,动作可能不完整」;必要时在 `src/model/humanoid_map.gd` 增加命名映射 |
|
||||
| 游戏界面提示「模型加载失败:缺少骨骼」或「骨骼绑定失败,模型可能不受支持」 | 模型没有 `Skeleton3D`,或骨骼命名不在 humanoid 映射表内;驱动至少需要 hips / spine / neck / head + 四肢共 16 根核心骨骼 | 换用标准 VRM(VRoid Studio 导出);看「人形覆盖率」,低于 60% 会提示「骨骼覆盖偏低,动作可能不完整」;必要时在 `src/model/humanoid_map.gd` 增加命名映射 |
|
||||
| 顶部「服务端不可达」或「目录拉取失败」 | Web 服务端未启动、地址或端口写错(误填管理端口 3001)、或网络不通 | 确认 `api_base_url` 为 `http://<服务器地址>:3000`;浏览器访问 `http://<服务器地址>:3000/health` 应返回 200;再点「保存并测试」 |
|
||||
| 导出失败:「未找到导出模板」 | 未安装 export templates,或版本 / 路径与编辑器不一致 | 编辑器「编辑器 -> 管理导出模板 -> 下载并安装」;Linux 放 `~/.local/share/godot/export_templates/<版本>.stable/`,Windows 放 `%APPDATA%\Godot\export_templates\<版本>.stable\` |
|
||||
| 导出失败:「未找到导出模板」 | 未安装 export templates,或版本 / 路径与编辑器不一致 | 编辑器「编辑器 -> 管理导出模板 -> 下载并安装」;Linux 放 `~/.local/share/godot/export_templates/<版本>.stable/`, Windows 放 `%APPDATA%\Godot\export_templates\<版本>.stable\` |
|
||||
| 提示「接收服务启动失败(端口 8765 可能被占用)」 | TCP 8765 被占用或未放行 | 改 `opendance.cfg` 的 `listen_port` 后点「重新连接接收服务」,并同步修改 Android 端填写的接收地址 |
|
||||
| 手机已连接但分数一直是 0 | Android 端未开始推流,或帧内关键点置信度都低于 0.5 | 「查看接收状态」确认累计帧在增长、畸形帧为 0;确认 Android 端子协议为 `opendance.pose.v1` |
|
||||
|
||||
@@ -143,10 +143,10 @@ bash build/package-release.sh 0.1.0
|
||||
|
||||
| 组件 | 选型 |
|
||||
|--|--|
|
||||
| 引擎 | Godot 4.7.x(MIT) |
|
||||
| 引擎 | Godot 4.7.x(MIT) |
|
||||
| VRM 加载 | Godot 内置 GLTFDocument |
|
||||
| 通信 | WebSocketPeer(服务端)+ HTTPRequest |
|
||||
| 评分 | DTW + 关节角度差异(纯 GDScript) |
|
||||
| 评分 | DTW + 关节角度差异(纯 GDScript) |
|
||||
|
||||
## 架构
|
||||
|
||||
@@ -173,7 +173,7 @@ src/
|
||||
|
||||
## 依赖的契约(opendance-web v0.1.0 冻结)
|
||||
|
||||
- [`contracts/websocket-keypoints.md`](contracts/websocket-keypoints.md) - 239 字节关键点帧与文本控制消息(本端为**接收方**)
|
||||
- [`contracts/websocket-keypoints.md`](contracts/websocket-keypoints.md) - 239 字节关键点帧与文本控制消息(本端为**接收方**)
|
||||
- [`contracts/skeleton-topology.md`](contracts/skeleton-topology.md) - MediaPipe 33 点拓扑、父节点表、9 个关节角特征
|
||||
- [`contracts/rest-api.yaml`](contracts/rest-api.yaml) - 目录与关键点序列 API
|
||||
- [`contracts/data-models.md`](contracts/data-models.md) - 资源字段与 VRM 优先规则
|
||||
@@ -185,7 +185,7 @@ src/
|
||||
|
||||
## 已知限制
|
||||
|
||||
1. Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
|
||||
1. Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
|
||||
实现接受任意 path,仅校验子协议 `opendance.pose.v1`。
|
||||
2. 文档原选型 `godot-vrm` GDExtension 不发布预编译产物,改用 Godot 内置 glTF 导入器
|
||||
+ 自实现骨骼名映射;理由见 `DSH.md` 6。
|
||||
|
||||
+17
-17
@@ -43,7 +43,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
|
||||
1. 前置条件
|
||||
|
||||
- 操作系统:Windows 10/11(x86_64)。开发与导出可在 Windows 或 Linux。
|
||||
- 操作系统:Windows 10/11(x86_64)。开发与导出可在 Windows 或 Linux。
|
||||
- Godot 4.7.x 标准版(GDScript)。不需要 .NET / C# 版。
|
||||
- 渲染后端为 GL Compatibility,运行需 OpenGL 3.3 级别的显卡与驱动。
|
||||
- 导出模板:仅打包导出时需要,必须与编辑器完全同版本。
|
||||
@@ -68,7 +68,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
|
||||
4. 配置服务端地址
|
||||
|
||||
主界面顶部「服务端地址」填好后点「保存并测试」,配置字段名为 api_base_url:
|
||||
主界面顶部「服务端地址」填好后点「保存并测试」,配置字段名为 api_base_url:
|
||||
|
||||
http://<服务器地址>:3000
|
||||
http://192.0.2.10:3000
|
||||
@@ -81,7 +81,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
- Android 辅助 App -> 本端 WebSocket:本端是服务端,默认监听
|
||||
0.0.0.0:8765,路径 /pose,子协议 opendance.pose.v1。Android 端填写
|
||||
本端主界面显示的「接收地址」,形如 ws://<本机局域网地址>:8765/pose。
|
||||
本端可在 opendance.cfg 改 listen_host / listen_port / ws_subprotocol,
|
||||
本端可在 opendance.cfg 改 listen_host / listen_port / ws_subprotocol,
|
||||
改后点「重新连接接收服务」生效。
|
||||
- Web 服务端:需先在服务端发布曲目 / 动作 / 模型,动作还须完成关键点
|
||||
预处理,客户端才有可选内容。
|
||||
@@ -94,7 +94,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
# Windows(兜底)
|
||||
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
|
||||
|
||||
产物在 build/windows/:OpenDance.exe(约 104 MB,PE 头 MZ 校验通过)
|
||||
产物在 build/windows/: OpenDance.exe(约 104 MB, PE 头 MZ 校验通过)
|
||||
+ OpenDance.pck(约 107 KB,必须与 exe 同目录);可能附带
|
||||
OpenDance.console.exe。
|
||||
|
||||
@@ -121,7 +121,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
8. 部署相关常见问题
|
||||
|
||||
Q: 模型加载失败:缺少骨骼
|
||||
A: 模型无 Skeleton3D,或骨骼命名不在映射表内;换用标准 VRM(VRoid
|
||||
A: 模型无 Skeleton3D,或骨骼命名不在映射表内;换用标准 VRM(VRoid
|
||||
Studio 导出)。人形覆盖率低于 60% 会提示「骨骼覆盖偏低」。
|
||||
|
||||
Q: 连不上服务端 / 显示「服务端不可达」
|
||||
@@ -130,7 +130,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
|
||||
Q: 导出失败:未找到导出模板
|
||||
A: 安装与编辑器同版本的 export templates。Linux 放
|
||||
~/.local/share/godot/export_templates/<版本>.stable/;
|
||||
~/.local/share/godot/export_templates/<版本>.stable/;
|
||||
Windows 放 %APPDATA%\Godot\export_templates\<版本>.stable\。
|
||||
|
||||
Q: 接收服务启动失败(端口 8765 可能被占用)
|
||||
@@ -169,12 +169,12 @@ OpenDance.exe 的用户看第一节即可。
|
||||
|
||||
可配置项:
|
||||
|
||||
api_base_url Web 服务端地址(默认 http://127.0.0.1:3000)
|
||||
listen_host WebSocket 监听地址(默认 0.0.0.0)
|
||||
listen_port WebSocket 监听端口(默认 8765)
|
||||
ws_subprotocol 子协议(默认 opendance.pose.v1)
|
||||
api_base_url Web 服务端地址(默认 http://127.0.0.1:3000)
|
||||
listen_host WebSocket 监听地址(默认 0.0.0.0)
|
||||
listen_port WebSocket 监听端口(默认 8765)
|
||||
ws_subprotocol 子协议(默认 opendance.pose.v1)
|
||||
dtw_band DTW 带宽,单位帧(默认 15 ~ 0.5 秒抖动容忍)
|
||||
score_tolerance 评分容差:平均每步代价达到该值即 0 分(默认 0.25)
|
||||
score_tolerance 评分容差:平均每步代价达到该值即 0 分(默认 0.25)
|
||||
mirror_preview 镜像预览
|
||||
|
||||
--
|
||||
@@ -196,9 +196,9 @@ OpenDance.exe 的用户看第一节即可。
|
||||
- 每帧特征 = 33 点 x (x,y,z) + 9 个关节角余弦 = 108 维
|
||||
(与 Web 服务端预处理产物、Android 端推流结构完全一致)
|
||||
- 置信度低于 0.5 的关键点不参与代价计算,避免遮挡导致误判
|
||||
- 采用带约束 DTW(|i-j| <= band)对齐,能吸收一定的节奏快慢差异
|
||||
- 采用带约束 DTW(|i-j| <= band)对齐,能吸收一定的节奏快慢差异
|
||||
- 得分 = clamp(1 - 平均代价 / 容差, 0, 1) x 100,并做指数平滑
|
||||
- 等级:S >= 95,A >= 85,B >= 70,C >= 55,D 其余
|
||||
- 等级:S >= 95, A >= 85, B >= 70, C >= 55, D 其余
|
||||
|
||||
--
|
||||
七、从源码构建
|
||||
@@ -212,7 +212,7 @@ OpenDance.exe 的用户看第一节即可。
|
||||
# Windows 上兜底导出
|
||||
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
|
||||
|
||||
产物位于 build/windows/:OpenDance.exe + OpenDance.pck
|
||||
产物位于 build/windows/: OpenDance.exe + OpenDance.pck
|
||||
(脚本会校验产物存在且为合法 PE 文件)
|
||||
|
||||
运行测试:
|
||||
@@ -230,8 +230,8 @@ OpenDance.exe 的用户看第一节即可。
|
||||
export_presets.cfg 导出预设(必须在项目根目录)
|
||||
project.yaml 任务清单与状态
|
||||
DSH.md 本项目 DSH 执行约束
|
||||
tasks/ 单任务说明书(T-WIN-001 ~ 011)
|
||||
contracts/ 只读契约快照(来源 opendance-web v0.1.0)
|
||||
tasks/ 单任务说明书(T-WIN-001 ~ 011)
|
||||
contracts/ 只读契约快照(来源 opendance-web v0.1.0)
|
||||
scenes/main.tscn 根节点
|
||||
src/autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
src/net/ 239 字节编解码、WebSocket 服务、REST 客户端
|
||||
@@ -255,7 +255,7 @@ A: 动作必须先在管理端完成「预处理」(BVH -> 关键点序列)
|
||||
|
||||
Q: 模型加载失败 / 动作不完整
|
||||
A: 游戏界面会显示「人形覆盖率」。低于 60% 说明模型的骨骼命名不被识别,
|
||||
请换用标准 VRM(VRoid Studio 导出)模型。
|
||||
请换用标准 VRM(VRoid Studio 导出)模型。
|
||||
|
||||
Q: 连上了手机但分数一直是 0
|
||||
A: 确认手机端已开始推流;主界面的「查看接收状态」会显示累计帧数与丢帧数。
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
- 版权:Copyright 2023 Google LLC
|
||||
- 许可证:Apache License 2.0
|
||||
- 源链接:https://github.com/google-ai-edge/mediapipe
|
||||
- 用途:33 点骨骼拓扑定义(契约来源,**本端不包含其代码**)
|
||||
- 用途:33 点骨骼拓扑定义(契约来源,**本端不包含其代码**)
|
||||
|
||||
## 2. Three.js
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
## 许可合规说明
|
||||
|
||||
1. 本客户端自身代码以 MIT 许可发布。
|
||||
2. 运行时依赖仅为 **Godot Engine**(MIT);其余条目为同项目其他组件或服务端依赖,
|
||||
2. 运行时依赖仅为 **Godot Engine**(MIT);其余条目为同项目其他组件或服务端依赖,
|
||||
按契约要求一并声明。
|
||||
3. 本客户端**未使用 SMPL 模型及其衍生拓扑**,统一采用 MediaPipe 33 点拓扑,
|
||||
以规避 SMPL 的非商用许可限制。
|
||||
|
||||
@@ -5,7 +5,7 @@ Web 服务端是唯一权威定义,本文件是另两端消费的**稳定视
|
||||
|
||||
## 1. 通用约定
|
||||
|
||||
- 所有资源返回 `id: number`(BIGINT,超出 2^53 前不会发生)。
|
||||
- 所有资源返回 `id: number`(BIGINT,超出 2^53 前不会发生)。
|
||||
- 所有时间为 ISO 8601 UTC 字符串,例:`2026-09-14T10:00:00.000Z`。
|
||||
- 列表接口统一分页响应:
|
||||
|
||||
@@ -117,7 +117,7 @@ rejected 已驳回
|
||||
```
|
||||
0. 环境变量 DEFAULT_MODEL_ID 指定,且该模型存在且已发布
|
||||
1. 源码内写死的默认模型种子(web-server/src/config/default-model.ts 的
|
||||
DEFAULT_MODEL_SEED):按 **sourceSha256(上传压缩包的 sha256)** 匹配已发布模型,
|
||||
DEFAULT_MODEL_SEED):按 **sourceSha256(上传压缩包的 sha256)** 匹配已发布模型,
|
||||
换库/重建库后仍然有效;没有压缩包时退回按产物 sha256,最后按名称兜底。
|
||||
⚠ 不能用产物 sha256 当主键:实测 Assimp 的 GLB 导出不是字节级确定的,
|
||||
同一份 zip 每次转换得到的文件 sha256 都不同(BIN chunk 有十几字节浮动)
|
||||
@@ -145,7 +145,7 @@ rejected 已驳回
|
||||
| `comment` | string\|null | 审核意见 |
|
||||
| `createdAt` | string | |
|
||||
|
||||
## 6. `CatalogBundle`(客户端一次性拉取,`GET /api/catalog/export`)
|
||||
## 6. `CatalogBundle`(客户端一次性拉取,`GET /api/catalog/export`)
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
+11
-11
@@ -3,11 +3,11 @@
|
||||
## 环境
|
||||
|
||||
- 类型:MySQL 兼容实例(外部提供,含 MariaDB 10.5+),TypeORM
|
||||
使用 `mysql` driver(`mysql2`)连接,SQL 语法限制在 MySQL 5.7 / MariaDB 10.5+
|
||||
使用 `mysql` driver(`mysql2`)连接,SQL 语法限制在 MySQL 5.7 / MariaDB 10.5+
|
||||
共同子集内,不使用 MySQL 8 专有语法(窗口函数 CTE 递归除外)。
|
||||
- 数据库名:由部署方提供,通过 `DB_NAME` 注入(本文档示例统一用 `opendance`)
|
||||
- 数据库名:由部署方提供,通过 `DB_NAME` 注入(本文档示例统一用 `opendance`)
|
||||
- 字符集:`utf8mb4` / `utf8mb4_general_ci`(沿用现有实例默认排序规则)
|
||||
- 迁移工具:TypeORM migration,**不使用 `DB_SYNCHRONIZE`**
|
||||
- 迁移工具:TypeORM migration, **不使用 `DB_SYNCHRONIZE`**
|
||||
|
||||
## 连接信息(由部署方提供)
|
||||
|
||||
@@ -25,10 +25,10 @@ DB_LOGGING=false
|
||||
|
||||
## 约定
|
||||
|
||||
- 所有表名使用 `snake_case`,复数形式(`songs` / `motions` / `models`)
|
||||
- 所有表名使用 `snake_case`,复数形式(`songs` / `motions` / `models`)
|
||||
- 主键统一为 `BIGINT AUTO_INCREMENT`,字段名 `id`
|
||||
- 时间字段统一 `DATETIME`,`created_at` / `updated_at`,`updated_at` 由应用层维护
|
||||
- 软删除统一使用 `deleted_at DATETIME NULL`(`@DeleteDateColumn`)
|
||||
- 时间字段统一 `DATETIME`, `created_at` / `updated_at`, `updated_at` 由应用层维护
|
||||
- 软删除统一使用 `deleted_at DATETIME NULL`(`@DeleteDateColumn`)
|
||||
- 布尔字段使用 `TINYINT(1)`
|
||||
- 来源(出处)字段为 5 元组,任何资产入库必须齐备:
|
||||
`source_type` / `source_name` / `source_url` / `license` / `author`
|
||||
@@ -36,24 +36,24 @@ DB_LOGGING=false
|
||||
- 每个 migration 必须可逆(提供 `down` 方法)
|
||||
- 迁移文件名格式:`<timestamp>-<kebab-name>.ts`
|
||||
|
||||
## 增量迁移(v0.2.0 / v0.3.0)
|
||||
## 增量迁移(v0.2.0 / v0.3.0)
|
||||
|
||||
| 迁移 | 变更 |
|
||||
|--|--|
|
||||
| `AddModelConversionFields` | `models` 增加 `source_asset_id BIGINT NULL`(上传的压缩包,用于溯源)与 `conversion_note VARCHAR(500) NULL`(转换说明,见 model-formats.md 1.1) |
|
||||
| `AddModelConversionFields` | `models` 增加 `source_asset_id BIGINT NULL`(上传的压缩包,用于溯源)与 `conversion_note VARCHAR(500) NULL`(转换说明,见 model-formats.md 1.1) |
|
||||
| `AddModelStats` | `models` 增加 `stats TEXT NULL`(模型分析结果:面数、骨骼树、humanoid 覆盖、可驱动性),供审核界面展示 |
|
||||
|
||||
## 表清单(v0.1.0)
|
||||
## 表清单(v0.1.0)
|
||||
|
||||
| 表 | 用途 | 任务 |
|
||||
|--|--|--|
|
||||
| `songs` | 曲目元数据 | T-WEB-003 |
|
||||
| `assets` | 上传文件元数据(去重、sha256、物理路径) | T-WEB-004 |
|
||||
| `motions` | 动作数据(BVH/FBX...)+ 来源 + 预处理产物 | T-WEB-004 / 006 |
|
||||
| `motions` | 动作数据(BVH/FBX...)+ 来源 + 预处理产物 | T-WEB-004 / 006 |
|
||||
| `models` | 角色模型(VRM 优先)+ 来源 + 压缩包转换溯源 | T-WEB-005 |
|
||||
| `review_records` | 上传审核流水 | T-WEB-007 |
|
||||
|
||||
## 状态机(`status` 列,v0.1.0)
|
||||
## 状态机(`status` 列,v0.1.0)
|
||||
|
||||
```
|
||||
draft ──submit──▶ pending ──approve──▶ published
|
||||
|
||||
+29
-29
@@ -1,21 +1,21 @@
|
||||
# contracts/model-formats.md - 模型与动作格式契约 v0.4.0
|
||||
|
||||
## 1. 角色模型(models)
|
||||
## 1. 角色模型(models)
|
||||
|
||||
**系统以 GLB / VRM 为主力格式**(两个客户端都原生加载),其余格式一律在服务端转换。
|
||||
|
||||
| 格式 | 扩展名 | 支持级别 | 服务端行为 |
|
||||
|--|--|--|--|
|
||||
| glTF Binary | `.glb` | **主力(P0)** | **直接导入**,不重编码 |
|
||||
| VRM | `.vrm` | **主力(P0)** | **直接导入**;= GLB + VRM 扩展(humanoid / 表情 / springBone / meta) |
|
||||
| glTF | `.gltf` (+ `.bin`, 贴图) | P1 | 直接导入(分离文件在 zip 内可用;单文件上传会缺.bin) |
|
||||
| glTF Binary | `.glb` | **主力(P0)** | **直接导入**,不重编码 |
|
||||
| VRM | `.vrm` | **主力(P0)** | **直接导入**; = GLB + VRM 扩展(humanoid / 表情 / springBone / meta) |
|
||||
| glTF | `.gltf` (+ `.bin`, 贴图) | P1 | 直接导入(分离文件在 zip 内可用;单文件上传会缺.bin) |
|
||||
| MMD 压缩包 | `.zip` | P1 | **自动搜索包内可用模型**:优先直接导入 `.vrm`/`.glb`/`.gltf`,否则转换 `.pmx`/`.fbx`,见 1.1 |
|
||||
| MMD 模型 | `.pmx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM);单文件上传无贴图 |
|
||||
| FBX | `.fbx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM) |
|
||||
| FBX | `.fbx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM) |
|
||||
|
||||
### 1.1 上传与转换策略
|
||||
|
||||
**`.glb` / `.vrm` / `.gltf` 直接导入**(不重编码,零损失);**`.pmx` / `.fbx` 由服务端转换**:
|
||||
**`.glb` / `.vrm` / `.gltf` 直接导入**(不重编码,零损失);**`.pmx` / `.fbx` 由服务端转换**:
|
||||
**优先 GLB,其次 VRM**。
|
||||
|
||||
#### 单文件上传
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
#### 压缩包上传(自动搜索)
|
||||
|
||||
不再"只找 `.pmx`",而是**按格式优先级自动搜索包内可用模型**:
|
||||
不再"只找 `.pmx`",而是**按格式优先级自动搜索包内可用模型**:
|
||||
|
||||
```
|
||||
.vrm ->.glb ->.gltf (直接导入,优先)
|
||||
@@ -38,7 +38,7 @@
|
||||
同一优先级内:`MODEL_CONVERT_PMX` 指定的文件名优先,其次目录最浅,最后文件最大。
|
||||
压缩包原件以 `model-archive` 资产留存并写入 `models.source_asset_id`。
|
||||
|
||||
#### 转换与校验(PMX / FBX)
|
||||
#### 转换与校验(PMX / FBX)
|
||||
|
||||
| 步骤 | 规则 |
|
||||
|--|--|
|
||||
@@ -46,7 +46,7 @@
|
||||
| 2. glTF 打包 | 上一步失败时 `assimp export ->.gltf`,由 `gltf-packer.ts` 把 `.gltf` + `.bin` + 贴图自行合成 GLB |
|
||||
| 3. VRM 封装 | `MODEL_CONVERT_FORMAT=vrm` 时,在 GLB 之上补 `VRMC_vrm` 扩展(humanoid 骨骼来自骨骼名映射,见 `model-inspector.ts` 的候选表,与 Godot 客户端一致);**humanoid 不全则回退交付 GLB**,缺失骨骼写进 `conversion_note` |
|
||||
| 回读校验 | 每一步产物都要 `assimp info` 回读。Assimp 会"写出损坏文件"(实测 FBX 导出退出码 11、产物回读报 `FBX-DOM no Objects dictionary found`),只看退出码会把坏文件入库 |
|
||||
| 说明 | 每次尝试都写进 `models.conversion_note`(例如 `Hatsune Miku.pmx -> GLB,已内嵌 2 张贴图`) |
|
||||
| 说明 | 每次尝试都写进 `models.conversion_note`(例如 `Hatsune Miku.pmx -> GLB,已内嵌 2 张贴图`) |
|
||||
| 失败 | 包内没有可用模型、Assimp 不可用、两条路径都失败 -> **HTTP 422**,不产生模型记录 |
|
||||
|
||||
#### 入库后(自动)
|
||||
@@ -63,14 +63,14 @@
|
||||
| 项 | 规则 |
|
||||
|--|--|
|
||||
| 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换 3 次得到 3 个不同 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**上传压缩包的 sha256** 为匹配键,产物 sha256 只作记录 |
|
||||
| 安全 | 解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE |
|
||||
| 安全 | 解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE |
|
||||
|
||||
**VRM 优先**的选取顺序见 `data-models.md` 4.1。
|
||||
|
||||
VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)或
|
||||
`extensions.VRMC_vrm`(1.0),写入 `models.vrm_version`。
|
||||
VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)或
|
||||
`extensions.VRMC_vrm`(1.0),写入 `models.vrm_version`。
|
||||
|
||||
## 2. 动作数据(motions)
|
||||
## 2. 动作数据(motions)
|
||||
|
||||
| 格式 | 扩展名 | 预处理支持 | 说明 |
|
||||
|--|--|--|--|
|
||||
@@ -82,7 +82,7 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
|
||||
### 2.1 预处理产物(冻结)
|
||||
|
||||
- 格式:NDJSON(`application/x-ndjson`),每行一帧,UTF-8 无 BOM
|
||||
- 格式:NDJSON(`application/x-ndjson`),每行一帧,UTF-8 无 BOM
|
||||
- 一行结构:
|
||||
|
||||
```json
|
||||
@@ -93,9 +93,9 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
- `angles` 为 `skeleton-topology.md` 5 定义的 9 个关节角余弦,顺序固定:
|
||||
`[shoulder_L, shoulder_R, elbow_L, elbow_R, hip_L, hip_R, knee_L, knee_R, spine]`
|
||||
- 帧率:保留源帧率;`fps` 写入 `motions.fps`,缺省 30
|
||||
- 坐标系换算:所有 3D 动作数据统一转到 **Y 轴向上、右手系、骨盆为原点**,
|
||||
- 坐标系换算:所有 3D 动作数据统一转到 **Y 轴向上、右手系、骨盆为原点**,
|
||||
再投影到 MediaPipe 归一化图像空间:
|
||||
`x_img = 0.5 + x_world * scale`,`y_img = 0.5 - y_world * scale`,`z_img = -z_world * scale`,
|
||||
`x_img = 0.5 + x_world * scale`, `y_img = 0.5 - y_world * scale`, `z_img = -z_world * scale`,
|
||||
`scale = 0.35 / max(|x|,|y|,|z|)`(按整段动作的最大范围自适应,保证不出画)
|
||||
- 缺失关节(BVH 中不存在对应骨骼):`confidence = 0.0`,坐标沿用最近可用父节点
|
||||
|
||||
@@ -122,18 +122,18 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
未映射到的点按 `skeleton-topology.md` 5 的层级由父节点推算,
|
||||
并置 `confidence` 降低(`0.5`),表示估算而非实测。
|
||||
|
||||
### 2.3 VMD(MikuMikuDance)解析约定
|
||||
### 2.3 VMD(MikuMikuDance)解析约定
|
||||
|
||||
VMD 是二进制格式:文件头 `Vocaloid Motion Data 0002`(30 字节)+ 模型名(20 字节,
|
||||
Shift-JIS)+ 骨骼关键帧数组(每条 **111 字节**:15 字节骨骼名、uint32 帧号、
|
||||
VMD 是二进制格式:文件头 `Vocaloid Motion Data 0002`(30 字节)+ 模型名(20 字节,
|
||||
Shift-JIS)+ 骨骼关键帧数组(每条 **111 字节**: 15 字节骨骼名、uint32 帧号、
|
||||
float32x3 位移、float32x4 旋转四元数、64 字节贝塞尔插值参数)+ morph / 相机 /
|
||||
灯光 / 自阴影 / IK / 可见性段落。**VMD 不含骨骼长度**,只有旋转(少数骨骼带位移),
|
||||
因此服务端必须自带一套标准 MMD 骨架才能做 FK。
|
||||
|
||||
处理链路:`vmd-parser.ts`(二进制 + Shift-JIS + 贝塞尔插值)-> `mmd-skeleton.ts`
|
||||
(标准骨架)-> `mmd-pose.ts`(FK + 腿部 IK)-> `retarget.ts` 的
|
||||
(标准骨架)-> `mmd-pose.ts`(FK + 腿部 IK)-> `retarget.ts` 的
|
||||
`worldFramesToKeypointFrames()`(与 BVH 完全共用骨盆归零 / 自适应缩放 / 图像空间投影 /
|
||||
9 个关节角)。固定 **30 fps**(MMD 帧率恒定,上传时填写的 `fps` 被忽略)。
|
||||
9 个关节角)。固定 **30 fps**(MMD 帧率恒定,上传时填写的 `fps` 被忽略)。
|
||||
|
||||
| MediaPipe 点 | MMD 标准骨骼 | 说明 |
|
||||
|--|--|--|
|
||||
@@ -149,11 +149,11 @@ float32x3 位移、float32x4 旋转四元数、64 字节贝塞尔插值参数)
|
||||
骨骼名按 `mmd-skeleton.ts` 的别名表归一(含全角 `IK` / 半角 `IK` 两种写法);
|
||||
未纳入标准骨架的骨骼(裙摆、物理、表情等)被忽略但会计数并写入预处理日志。
|
||||
|
||||
**腿部驱动判定**:`足IK` 轨道存在非零位移 -> 走 IK(用两骨骼解析 IK 把踝拉到 IK
|
||||
**腿部驱动判定**: `足IK` 轨道存在非零位移 -> 走 IK(用两骨骼解析 IK 把踝拉到 IK
|
||||
目标点,膝盖朝角色正面弯,不可达时收敛为伸直);否则走 FK(部分动作数据把腿烘焙成
|
||||
FK 旋转)。两份示例文件分别命中两种路径。
|
||||
|
||||
**坐标系**:MMD 原生是左手系(正面为 -Z),本项目通路是右手系(正面为 +Z、+X 为角色
|
||||
**坐标系**: 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`)。
|
||||
@@ -167,15 +167,15 @@ FK 旋转)。两份示例文件分别命中两种路径。
|
||||
|
||||
| 项 | 值 |
|
||||
|--|--|
|
||||
| 单文件上限 | 512 MiB(`UPLOAD_MAX_BYTES` 可覆盖) |
|
||||
| 单文件上限 | 512 MiB(`UPLOAD_MAX_BYTES` 可覆盖) |
|
||||
| 允许扩展名 | 上表全部;其他一律 422 |
|
||||
| 存储路径 | `${STORAGE_DIR}/{kind}/{yyyy}/{mm}/{sha256前2位}/{sha256}.{ext}` |
|
||||
| 去重 | 相同 sha256 复用同一 `assets` 记录,不重复落盘 |
|
||||
| 文件名 | 服务端不使用原始文件名做路径,只存 `assets.original_name` |
|
||||
|
||||
## 4. 开源依赖与许可证(引用 `open-source-credits.md`)
|
||||
## 4. 开源依赖与许可证(引用 `open-source-credits.md`)
|
||||
|
||||
- `bvh`(MIT)- BVH 解析参考实现
|
||||
- `Assimp`(BSD-3-Clause)- FBX/glTF 转换,**可选外部依赖**
|
||||
- `@pixiv/three-vrm`(MIT)- 管理端 VRM 预览
|
||||
- `three`(MIT)- 管理端 3D 预览
|
||||
- `bvh`(MIT)- BVH 解析参考实现
|
||||
- `Assimp`(BSD-3-Clause)- FBX/glTF 转换,**可选外部依赖**
|
||||
- `@pixiv/three-vrm`(MIT)- 管理端 VRM 预览
|
||||
- `three`(MIT)- 管理端 3D 预览
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
| 33 | neck | `midpoint(left_shoulder(11), right_shoulder(12))` |
|
||||
| 34 | pelvis | `midpoint(left_hip(23), right_hip(24))` |
|
||||
|
||||
## 3. 父节点表(`parent[i]`,-1 表示世界根)
|
||||
## 3. 父节点表(`parent[i]`, -1 表示世界根)
|
||||
|
||||
对称树,便于骨骼重定向与关节角计算:
|
||||
|
||||
@@ -91,16 +91,16 @@
|
||||
|
||||
## 4. 坐标系
|
||||
|
||||
- **归一化图像坐标**:`x, y ∈ [0, 1]`,原点左上角,`y` 向下;`x` 相对图像宽,`y` 相对图像高。
|
||||
- **深度**:`z` 与 `x` 同尺度,原点位于髋中心,**负值朝向相机**。
|
||||
- **镜像**:Android 前置摄像头默认输出镜像画面;是否镜像由帧头 `flags.bit0` 声明,
|
||||
接收端据此决定是否翻转 `x`(`x' = 1 - x`)。
|
||||
- **置信度**:`visibility ∈ [0, 1]`,落盘/传输为 `uint8`(`round(v * 255)`)。
|
||||
- **归一化图像坐标**: `x, y ∈ [0, 1]`,原点左上角,`y` 向下;`x` 相对图像宽,`y` 相对图像高。
|
||||
- **深度**: `z` 与 `x` 同尺度,原点位于髋中心,**负值朝向相机**。
|
||||
- **镜像**: Android 前置摄像头默认输出镜像画面;是否镜像由帧头 `flags.bit0` 声明,
|
||||
接收端据此决定是否翻转 `x`(`x' = 1 - x`)。
|
||||
- **置信度**: `visibility ∈ [0, 1]`,落盘/传输为 `uint8`(`round(v * 255)`)。
|
||||
归一化比例统一使用 `32767`(见 `websocket-keypoints.md`)。
|
||||
|
||||
## 5. 派生特征(DTW 输入,Windows 端实现,服务端预处理产物同构)
|
||||
|
||||
服务端 T-WEB-006 预处理产出的关键点序列与 Android 端输出的帧**结构完全一致**,
|
||||
服务端 T-WEB-006 预处理产出的关键点序列与 Android 端输出的帧**结构完全一致**,
|
||||
因此 Windows 端 DTW 引擎只有一条输入通路。
|
||||
|
||||
关节角特征(弧度,余弦值便于插值):
|
||||
|
||||
@@ -10,17 +10,17 @@ ws://<windows-host>:8765/pose
|
||||
```
|
||||
|
||||
- Windows 客户端监听,Android App 主动连接。
|
||||
- 子协议:`opendance.pose.v1`(`Sec-WebSocket-Protocol`)
|
||||
- 子协议:`opendance.pose.v1`(`Sec-WebSocket-Protocol`)
|
||||
- 二进制帧传输关键点,文本帧传输控制消息(JSON)。
|
||||
- 默认 30 FPS;发送端允许丢帧,接收端必须按 `frame_id` 检测丢包并插值补齐。
|
||||
|
||||
## 2. 二进制帧:239 字节(冻结)
|
||||
|
||||
**小端序(little-endian)**。
|
||||
**小端序(little-endian)**。
|
||||
|
||||
```
|
||||
偏移 长度 字段 类型 说明
|
||||
0 2 magic u16 LE 固定 0x4F44(ASCII "OD")
|
||||
0 2 magic u16 LE 固定 0x4F44(ASCII "OD")
|
||||
2 1 version u8 固定 0x01
|
||||
3 1 flags u8 bit0 = 镜像画面(mirrored)
|
||||
bit1 = 本帧含低置信度点(<0.5)
|
||||
@@ -32,7 +32,7 @@ ws://<windows-host>:8765/pose
|
||||
合计 239
|
||||
```
|
||||
|
||||
### 单个关键点(7 字节,`i` 为 0..32)
|
||||
### 单个关键点(7 字节,`i` 为 0..32)
|
||||
|
||||
```
|
||||
偏移 长度 字段 类型 换算
|
||||
@@ -43,7 +43,7 @@ ws://<windows-host>:8765/pose
|
||||
```
|
||||
|
||||
- 点索引与坐标语义见 `skeleton-topology.md`。
|
||||
- 编码:`x = clamp(round(x_norm * 32767), -32768, 32767)`,`y`、`z` 同理。
|
||||
- 编码:`x = clamp(round(x_norm * 32767), -32768, 32767)`, `y`、`z` 同理。
|
||||
- 低置信度点(`conf < 128`)坐标无意义,接收端必须插值,不得直接使用。
|
||||
- `i16` 为有符号数:`0x8000` 表示 `-32768`。
|
||||
|
||||
@@ -68,7 +68,7 @@ func decode(buf: PackedByteArray) -> Dictionary:
|
||||
return {"flags": flags, "frame_id": frame_id, "points": pts}
|
||||
```
|
||||
|
||||
## 3. 文本控制帧(JSON,UTF-8)
|
||||
## 3. 文本控制帧(JSON, UTF-8)
|
||||
|
||||
### 3.1 `hello` - Android -> Windows,连接后立即发送
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# T-WIN-001 Godot 项目骨架
|
||||
|
||||
- **状态**:done
|
||||
- **状态**: done
|
||||
- **依赖**:无
|
||||
|
||||
## 目标
|
||||
@@ -22,7 +22,7 @@ scenes/main.tscn
|
||||
| 文件 | 说明 |
|
||||
|--|--|
|
||||
| `project.godot` | 工程配置;autoload = AppConfig / CatalogStore / PoseReceiver / ScoreService |
|
||||
| `scenes/main.tscn` | 只含一个挂载 `src/ui/main.gd` 的 Control 根节点(界面用代码构建,见 DSH.md 6) |
|
||||
| `scenes/main.tscn` | 只含一个挂载 `src/ui/main.gd` 的 Control 根节点(界面用代码构建,见 DSH.md 6) |
|
||||
| `src/ui/main.gd` | 应用外壳:构建布局、切换页面、启动接收服务 |
|
||||
| `src/autoload/app_config.gd` | 客户端配置,持久化到 `user://opendance.cfg` |
|
||||
| `src/autoload/catalog_store.gd` | 目录缓存 + 模型下载缓存 + 关键点序列缓存 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-002 调用 Web 服务端 API
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-003 接收 Android 关键点帧
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -18,7 +18,7 @@ src/autoload/pose_receiver.gd
|
||||
|
||||
## 交付物
|
||||
|
||||
- `src/net/pose_server.gd`:TCPServer + `WebSocketPeer.accept_stream()` 实现服务端
|
||||
- `src/net/pose_server.gd`: TCPServer + `WebSocketPeer.accept_stream()` 实现服务端
|
||||
- `src/autoload/pose_receiver.gd`:绑定 AppConfig,暴露 `latest_frame` 与统计
|
||||
|
||||
## 能力
|
||||
@@ -36,7 +36,7 @@ src/autoload/pose_receiver.gd
|
||||
|
||||
## 已知限制
|
||||
|
||||
Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
|
||||
Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
|
||||
实现接受任意 path,仅校验子协议。已记录于 DSH.md 与任务文档。
|
||||
|
||||
## 验收
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-004 239 字节帧反序列化
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -24,20 +24,20 @@ src/net/keypoint_frame.gd
|
||||
2 1 version u8 = 1
|
||||
3 1 flags u8 bit0 镜像 bit1 低置信度 bit2 上半身
|
||||
4 4 frame_id u32 LE
|
||||
8 231 keypoints 33 x 7B:s16 x, s16 y, s16 z, u8 conf
|
||||
8 231 keypoints 33 x 7B:s16 x, s16 y, s16 z, u8 conf
|
||||
合计 239
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
`src/net/keypoint_frame.gd`:`PoseFrame` 内部类 + `decode` / `encode` / `validate` / `from_json_dict`。
|
||||
`src/net/keypoint_frame.gd`: `PoseFrame` 内部类 + `decode` / `encode` / `validate` / `from_json_dict`。
|
||||
|
||||
## 关键实现
|
||||
|
||||
- 归一化系数统一 32767,与 opendance-web 的 TypeScript 参考实现逐位对齐
|
||||
- 编解码往返误差 < 1e-4(定点精度 1/32767 ~ 3.05e-5)
|
||||
- 编解码往返误差 < 1e-4(定点精度 1/32767 ~ 3.05e-5)
|
||||
- `unmirrored_points()` 按 flags.bit0 翻转 x
|
||||
- 低置信度阈值 0.5:`is_reliable()` 与 `compute_low_confidence()`
|
||||
- 低置信度阈值 0.5: `is_reliable()` 与 `compute_low_confidence()`
|
||||
- 极值(±1.0)不溢出 int16
|
||||
|
||||
## 验收
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-005 VRM 模型加载
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -19,23 +19,23 @@ src/model/humanoid_map.gd
|
||||
|
||||
## 选型变更
|
||||
|
||||
文档原选型 `godot-vrm`(V-Sekai)GDExtension **不发布任何预编译产物**,
|
||||
文档原选型 `godot-vrm`(V-Sekai)GDExtension **不发布任何预编译产物**,
|
||||
需用 godot-cpp 本地编译原生库,会破坏「Linux 导出 Windows exe」的验收路径。
|
||||
改用 **Godot 内置 GLTFDocument**:VRM 即 glTF 2.0 容器,实测可完整解析
|
||||
改用 **Godot 内置 GLTFDocument**: VRM 即 glTF 2.0 容器,实测可完整解析
|
||||
154 骨骼的 VRoid 模型,且 `GLTFState.json` 可直接读取 VRM 扩展判定版本。
|
||||
|
||||
## 骨骼名映射
|
||||
|
||||
`HumanoidMap` 覆盖 VRoid / VRM 0.x / Mixamo 三类命名,归一化后精确比较:
|
||||
|
||||
- 剥离前缀 `jbip` / `mixamorig` / `bip01`(**归一化之后**的形态)
|
||||
- 生成 compact(left->l)与 expanded(l->left)两种变体比较
|
||||
- **故意不剥离 `J_Aim_` / `J_Roll_` / `J_Sec_`**:VRoid 用它们标记扭转辅助骨,
|
||||
- 剥离前缀 `jbip` / `mixamorig` / `bip01`(**归一化之后**的形态)
|
||||
- 生成 compact(left->l)与 expanded(l->left)两种变体比较
|
||||
- **故意不剥离 `J_Aim_` / `J_Roll_` / `J_Sec_`**: VRoid 用它们标记扭转辅助骨,
|
||||
剥离后 `J_Aim_L_UpperLeg` 会与真正的 `J_Bip_L_UpperLeg` 撞名
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_vrm_loader.gd`:
|
||||
`tests/test_vrm_loader.gd`:
|
||||
- 三类命名规范的识别(含辅助骨不遮蔽真骨的回归用例)
|
||||
- 真实 VRM:154 骨骼 / 3 网格 / VRM 1.0,**humanoid 覆盖率 100%**
|
||||
- 真实 VRM: 154 骨骼 / 3 网格 / VRM 1.0, **humanoid 覆盖率 100%**
|
||||
- 缺失文件、普通 glTF 不误判
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-006 关键点 -> VRM 骨骼重定向
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-005、T-WIN-004
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-005、T-WIN-004
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -18,20 +18,20 @@ src/anim/skeleton_retarget.gd
|
||||
## 算法
|
||||
|
||||
1. 每根骨骼指定一条骨链(起点关键点,终点关键点)
|
||||
2. 取骨骼静止朝向 `rest_dir`(由该骨与首个子骨的位置差得出,**不假设模型骨骼轴向**)
|
||||
2. 取骨骼静止朝向 `rest_dir`(由该骨与首个子骨的位置差得出,**不假设模型骨骼轴向**)
|
||||
3. 目标朝向由两个关键点在世界空间的方向得出
|
||||
4. `delta` = rest_dir -> target_dir 的最小旋转四元数(保留扭转/roll)
|
||||
4. `delta` = rest_dir -> target_dir 的最小旋转四元数(保留扭转/roll)
|
||||
5. `desired = Basis(delta) x rest_global_basis`
|
||||
6. `pose_local = parent_global_basis⁻¹ x desired`
|
||||
|
||||
**第 6 步的合成公式是实测标定的**:Godot 文档未写明 rest/pose 的合成顺序,
|
||||
**第 6 步的合成公式是实测标定的**: Godot 文档未写明 rest/pose 的合成顺序,
|
||||
在真实 VRoid 模型上暴力枚举 5 种候选公式,只有该式误差为 1.9e-8(其余 ~0.14)。
|
||||
已有回归测试守护,不得凭记忆改写。
|
||||
|
||||
## 坐标转换
|
||||
|
||||
契约 4 的 MediaPipe 归一化坐标 -> Godot 世界方向:
|
||||
`Vector3(x x aspect, -y, -z x aspect)`(x/z 以图像宽为基准故乘宽高比)。
|
||||
`Vector3(x x aspect, -y, -z x aspect)`(x/z 以图像宽为基准故乘宽高比)。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-007 DTW 评分引擎
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-004、T-WIN-002
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-004、T-WIN-002
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -30,9 +30,9 @@ src/score/scoring.gd
|
||||
每帧只计算带内一行,总体复杂度 O(n m band) 但摊到每帧 O(m band)
|
||||
- 归一化:代价 / max(i,j)+1,得到「平均每步代价」
|
||||
|
||||
## 评分规则(`scoring.gd`)
|
||||
## 评分规则(`scoring.gd`)
|
||||
|
||||
`score = clamp(1 - cost/tolerance, 0, 1) x 100`,EMA 平滑抑制抖动,
|
||||
`score = clamp(1 - cost/tolerance, 0, 1) x 100`, EMA 平滑抑制抖动,
|
||||
等级 S(>=95) / A(>=85) / B(>=70) / C(>=55) / D,连击阈值 70。
|
||||
|
||||
## 验收
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# T-WIN-008 主界面 + 游戏界面 + 结算
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-002、T-WIN-003、T-WIN-006、T-WIN-007
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-002、T-WIN-003、T-WIN-006、T-WIN-007
|
||||
|
||||
## 目标
|
||||
|
||||
三页式体验:主菜单(选曲/选动作/选模型)-> 游戏页(3D 角色 + 实时评分 HUD)-> 结算页。
|
||||
三页式体验:主菜单(选曲/选动作/选模型)-> 游戏页(3D 角色 + 实时评分 HUD)-> 结算页。
|
||||
|
||||
## load
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-009 来源信息展示
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-008
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-008
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -26,7 +26,7 @@ src/net/catalog_types.gd
|
||||
- 资产名 | 来源名称
|
||||
- 作者
|
||||
- 许可 + **可点击外链**跳转到 `sourceUrl`
|
||||
- **五元组不齐备时显式告警**("⚠ 来源信息不完整,请联系管理员补全后再使用"),不静默展示
|
||||
- **五元组不齐备时显式告警**("⚠ 来源信息不完整,请联系管理员补全后再使用"),不静默展示
|
||||
|
||||
## 使用位置
|
||||
|
||||
@@ -37,4 +37,4 @@ src/net/catalog_types.gd
|
||||
## 验收
|
||||
|
||||
`tests/test_api_integration.gd` 对真实服务端断言曲目与模型的来源五元组齐备
|
||||
(`song.source.is_complete()`),保证展示组件拿到的数据完整。
|
||||
(`song.source.is_complete()`),保证展示组件拿到的数据完整。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# T-WIN-010 关于页面 + 开源声明
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-008
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-008
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -19,7 +19,7 @@ src/ui/credits.gd
|
||||
## 交付物
|
||||
|
||||
- `src/ui/credits.gd`:与 `contracts/open-source-credits.md` 逐条对应的数据表,渲染为 BBCode
|
||||
- `src/ui/about_screen.gd`:应用信息 + 契约版本 + 隐私说明 + 可滚动声明(RichTextLabel)
|
||||
- `src/ui/about_screen.gd`:应用信息 + 契约版本 + 隐私说明 + 可滚动声明(RichTextLabel)
|
||||
|
||||
## 要点
|
||||
|
||||
|
||||
+10
-10
@@ -1,7 +1,7 @@
|
||||
# T-WIN-011 Windows 打包脚本
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001 ~ 010
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-001 ~ 010
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -20,7 +20,7 @@ export_presets.cfg
|
||||
|
||||
| 文件 | 说明 |
|
||||
|--|--|
|
||||
| `export_presets.cfg` | 导出预设(**必须在项目根目录**,Godot 硬性要求) |
|
||||
| `export_presets.cfg` | 导出预设(**必须在项目根目录**, Godot 硬性要求) |
|
||||
| `build/export-windows.sh` | Linux 导出:定位 Godot -> 检查模板 -> 导入 -> 导出 -> **校验 PE 头** |
|
||||
| `build/export-windows.ps1` | Windows 兜底:同上,产物路径与校验逻辑一致 |
|
||||
|
||||
@@ -28,15 +28,15 @@ export_presets.cfg
|
||||
|
||||
- `GODOT_BIN` 环境变量或 PATH 自动定位 Godot,缺失时给出可执行的安装指引
|
||||
- 导出模板缺失时明确报错并给出下载与安装路径
|
||||
- 导出命令返回非 0 时**仍检查产物是否存在**(Godot 有时非 0 返回但产物有效)
|
||||
- 读取产物前 2 字节校验 `MZ`(PE 头),防止导出静默产出空壳
|
||||
- 导出命令返回非 0 时**仍检查产物是否存在**(Godot 有时非 0 返回但产物有效)
|
||||
- 读取产物前 2 字节校验 `MZ`(PE 头),防止导出静默产出空壳
|
||||
|
||||
## 验收(文档 5.4)
|
||||
## 验收(文档 5.4)
|
||||
|
||||
| 验收项 | 结果 |
|
||||
|--|--|
|
||||
| Linux:`bash build/export-windows.sh` 成功生成 exe | ✅ 生成 `OpenDance.exe` 104 MB + `OpenDance.pck` 107 KB,PE 头校验通过 |
|
||||
| Windows:`powershell build/export-windows.ps1` 成功生成 exe | ⚠️ 脚本就绪,本环境无 Windows,未实机执行 |
|
||||
| Linux: `bash build/export-windows.sh` 成功生成 exe | ✅ 生成 `OpenDance.exe` 104 MB + `OpenDance.pck` 107 KB, PE 头校验通过 |
|
||||
| Windows: `powershell build/export-windows.ps1` 成功生成 exe | ⚠️ 脚本就绪,本环境无 Windows,未实机执行 |
|
||||
| 生成的 exe 在 Windows 10/11 可运行 | ⚠️ 本环境无 Windows;已校验为合法 PE32+ x86_64 可执行文件 |
|
||||
| `THIRD_PARTY.md` 包含所有开源项目与源链接 | ✅ 15 项,与契约表逐条一致 |
|
||||
|
||||
@@ -53,6 +53,6 @@ opendance-windows-v0.1.0.zip
|
||||
|
||||
## 环境记录
|
||||
|
||||
- Godot 4.7.2.stable(`ed1daf0bf`)
|
||||
- 导出模板 `4.7.2.stable`(``windows_release_x86_64.exe``)
|
||||
- Godot 4.7.2.stable(`ed1daf0bf`)
|
||||
- 导出模板 `4.7.2.stable`(``windows_release_x86_64.exe``)
|
||||
- 首次导出失败原因:`export_presets.cfg` 放在 `build/` 下,Godot 只在**项目根目录**查找
|
||||
|
||||
Reference in New Issue
Block a user