docs: 新增「初次部署(从零跑起来)」;去混淆字符与真实内网信息
- README.md / README.txt 新增初次部署章节:前置条件、获取代码、首次运行、 配置服务端地址(API 3000 / 管理端 3001 分离)、与 Android 的 WebSocket 联通、 打包导出、自检清单、常见问题 - 三库同源契约:':=' 写法修回、去掉真实 MariaDB 版本串 - 全文去除易被 Gitea 判为「模棱两可 Unicode」的字符(- - ... -> 取代 — – … § →) - 真实内网地址与库名改占位符
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┘
|
||||
@@ -19,14 +19,14 @@ Web 服务端 ──GET /api/catalog/export──────┤
|
||||
VRM 驱动 + DTW 评分
|
||||
```
|
||||
|
||||
**本端是 WebSocket 的「服务端」,Android 主动连入**(契约 websocket-keypoints.md §1)。
|
||||
**本端是 WebSocket 的「服务端」,Android 主动连入**(契约 websocket-keypoints.md 1)。
|
||||
|
||||
## 2. 技术栈
|
||||
|
||||
| 组件 | 选型 | 说明 |
|
||||
|---|---|---|
|
||||
|--|--|--|
|
||||
| 引擎 | Godot 4.7.2(MIT) | 可在 Linux 上导出 Windows exe |
|
||||
| VRM 加载 | **Godot 内置 GLTFDocument** | 见 §6 的选型变更说明 |
|
||||
| VRM 加载 | **Godot 内置 GLTFDocument** | 见 6 的选型变更说明 |
|
||||
| 多格式导入 | Godot 内置 glTF 导入器 | FBX 由管理端转 BVH 后下发 |
|
||||
| 通信 | WebSocketPeer + HTTPRequest | 无第三方依赖 |
|
||||
| 评分 | DTW + 关节角度差异 | 纯 GDScript 实现 |
|
||||
@@ -52,7 +52,7 @@ Web 服务端 ──GET /api/catalog/export──────┤
|
||||
## 4. 上下文预算
|
||||
|
||||
| 项 | 建议值 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 单任务文件数 | ≤ 6 |
|
||||
| 单任务代码行数 | ≤ 1500 |
|
||||
| 单任务 Token | ≤ 12K |
|
||||
@@ -72,7 +72,7 @@ opendance-windows/
|
||||
│ ├── autoload/ # AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
│ ├── net/ # 239 字节编解码、WebSocket 服务、REST 客户端、NDJSON 解析
|
||||
│ ├── model/ # VRM 加载、humanoid 骨骼名映射
|
||||
│ ├── anim/ # 关键点 → 骨骼重定向
|
||||
│ ├── anim/ # 关键点 -> 骨骼重定向
|
||||
│ ├── score/ # 特征提取、DTW、评分规则
|
||||
│ └── ui/ # 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
|
||||
├── tests/ # 无头测试框架 + 用例
|
||||
@@ -85,7 +85,7 @@ opendance-windows/
|
||||
`.tscn` 是带 uid 与节点路径的手写格式,在没有编辑器的情况下极易出错且难以验证;
|
||||
用代码构建可随脚本一起被无头导入和单元测试覆盖。`scenes/main.tscn` 因此只保留一个根节点。
|
||||
|
||||
**VRM 加载改用 Godot 内置 glTF 导入器**(文档 §5.1 原选型为 godot-vrm GDExtension):
|
||||
**VRM 加载改用 Godot 内置 glTF 导入器**(文档 5.1 原选型为 godot-vrm GDExtension):
|
||||
|
||||
- `V-Sekai/godot-vrm` **不发布任何预编译产物**(无 GitHub Release,仅源码,
|
||||
需用 godot-cpp 本地编译原生库),这会同时破坏「Linux 上导出 Windows exe」的验收路径。
|
||||
@@ -99,7 +99,7 @@ opendance-windows/
|
||||
|
||||
1. **绝不让视频帧离开设备**:本端只接收 33 个关键点的坐标,不接收也不存储图像。
|
||||
2. **契约先行**:改交互先改 `opendance-web` 的 `contracts/`,再回本仓库同步快照。
|
||||
3. **不假设骨骼轴向约定**:重定向基于「静止朝向 → 目标朝向」的最小旋转,
|
||||
3. **不假设骨骼轴向约定**:重定向基于「静止朝向 -> 目标朝向」的最小旋转,
|
||||
`pose = parent_global_basis⁻¹ × desired` 这一合成公式由真实 VRM 实测标定,
|
||||
有回归测试守护,不得凭记忆改写。
|
||||
4. **上传/下载的模型不入库**:真实 VRM 样例仅放在 `_scratch/`(已 gitignore),
|
||||
@@ -113,13 +113,13 @@ opendance-windows/
|
||||
GODOT=/path/to/godot # Godot 4.7.x
|
||||
|
||||
# 1. 全部脚本编译通过(无输出即通过)
|
||||
$GODOT --headless --path . --import 2>&1 | grep -E "SCRIPT ERROR|Parse Error"
|
||||
$GODOT --headless --path. --import 2>&1 | grep -E "SCRIPT ERROR|Parse Error"
|
||||
|
||||
# 2. 无头单元测试 + 真实 API 集成(API 不可达时相关用例显式跳过)
|
||||
$GODOT --headless --path . --script res://tests/run_tests.gd
|
||||
$GODOT --headless --path. --script res://tests/run_tests.gd
|
||||
|
||||
# 3. 应用可启动(autoload + UI 构建无报错)
|
||||
$GODOT --headless --path . --quit-after 180
|
||||
$GODOT --headless --path. --quit-after 180
|
||||
|
||||
# 4. Windows 导出(需先安装 export templates)
|
||||
bash build/export-windows.sh release
|
||||
@@ -128,7 +128,7 @@ bash build/export-windows.sh release
|
||||
## 9. 上下文超标时的拆分策略
|
||||
|
||||
| 超标原因 | 拆分方式 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 单任务涉及多个界面 | 按界面拆(主/游戏/结算/关于) |
|
||||
| 单文件过大 | 按类拆,先生成接口再分实现 |
|
||||
| 契约全文加载 | 改用锚点引用 |
|
||||
|
||||
@@ -9,10 +9,10 @@ OpenDance Studio **阶段二**:Windows 客户端(Godot 4)。
|
||||
|
||||
## 状态
|
||||
|
||||
**阶段二已完成**:T-WIN-001 → T-WIN-011 全部落地。
|
||||
**阶段二已完成**:T-WIN-001 -> T-WIN-011 全部落地。
|
||||
|
||||
| 验收项 | 结果 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 脚本编译 | ✓ 全部通过(无 SCRIPT ERROR) |
|
||||
| 无头测试 | ✓ **580 项全部通过**(8 个测试套件) |
|
||||
| 应用启动 | ✓ 180 帧无运行时错误 |
|
||||
@@ -24,7 +24,7 @@ OpenDance Studio **阶段二**:Windows 客户端(Godot 4)。
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 验证(编译 → 测试 → 启动)
|
||||
# 验证(编译 -> 测试 -> 启动)
|
||||
GODOT_BIN=/path/to/godot bash build/verify.sh
|
||||
|
||||
# 导出 Windows 可执行文件
|
||||
@@ -36,10 +36,113 @@ bash build/package-release.sh 0.1.0
|
||||
|
||||
详细使用说明见 [`README.txt`](README.txt),工程约束见 [`DSH.md`](DSH.md)。
|
||||
|
||||
## 初次部署(从零跑起来)
|
||||
|
||||
本节面向**第一次拿到本仓库**的人:从安装引擎到跑通「Web 服务端 + 本端 + Android」
|
||||
三端链路。地址与端口一律使用占位符或产品默认值。
|
||||
|
||||
### 1. 前置条件
|
||||
|
||||
| 项 | 要求 |
|
||||
|--|--|
|
||||
| 操作系统 | 运行客户端: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`) |
|
||||
| 磁盘与网络 | Godot 编辑器 + 导出模板约数百 MB;导出产物 `OpenDance.exe` 约 104 MB、`OpenDance.pck` 约 107 KB。本端需能访问 Web 服务端 API(默认 TCP 3000),并与 Android 设备处于同一局域网、放行本端 **TCP 8765 入站** |
|
||||
|
||||
### 2. 获取代码
|
||||
|
||||
```bash
|
||||
git clone https://<Gitea 地址>/<owner>/opendance-windows
|
||||
cd opendance-windows
|
||||
```
|
||||
|
||||
### 3. 首次运行(编辑器内)
|
||||
|
||||
1. 启动 Godot 4.7.x,在项目管理器选择「导入」,指向本仓库的 `project.godot`。
|
||||
2. **首次导入会执行资源导入**(生成 `.godot/`),耗时从数十秒到数分钟不等;请等待完成,不要在导入中途关闭编辑器。
|
||||
3. 主场景已在 `project.godot` 中配置为 `res://scenes/main.tscn`,直接按 **F5**(或「运行项目」)即可。
|
||||
4. 界面全部由 GDScript 代码构建,不需要在编辑器里编辑场景,这是刻意设计(见 `DSH.md` 6)。
|
||||
5. 首次运行会在用户数据目录生成配置与日志:
|
||||
|
||||
```
|
||||
%APPDATA%\Godot\app_userdata\OpenDance Studio\opendance.cfg
|
||||
%APPDATA%\Godot\app_userdata\OpenDance Studio\logs\godot.log
|
||||
```
|
||||
|
||||
### 4. 配置服务端地址
|
||||
|
||||
- 位置:主界面顶部「**服务端地址**」输入框,填好后点「**保存并测试**」。
|
||||
- 配置字段名(`opendance.cfg`):`api_base_url`。
|
||||
- **端口区分**:客户端只访问 Web 服务端的 **API 端口(默认 3000)**;管理端页面在**管理端口(默认 3001)**,客户端不使用该端口。
|
||||
- 示例占位符:
|
||||
|
||||
```
|
||||
http://<服务器地址>:3000
|
||||
http://192.0.2.10:3000
|
||||
```
|
||||
|
||||
保存后点「**刷新目录**」;顶部应显示「服务端正常 | DB ...」,左侧列出已发布的曲目 / 动作 / 模型。
|
||||
|
||||
### 5. 与另外两端的联通
|
||||
|
||||
| 对端 | 方向 | 本端配置 | 说明 |
|
||||
|--|--|--|--|
|
||||
| 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)。
|
||||
- 接收状态可用主界面「**查看接收状态**」查看:连接数、累计帧、丢帧、畸形帧。
|
||||
|
||||
### 6. 打包导出
|
||||
|
||||
```bash
|
||||
# Linux 上直接导出 Windows 可执行文件(推荐)
|
||||
GODOT_BIN=/path/to/godot bash build/export-windows.sh release
|
||||
|
||||
# Windows 上兜底导出
|
||||
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
|
||||
|
||||
# 打成可分发的 zip(可选)
|
||||
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.console.exe` | `build/windows/` | 控制台包装器,可能生成,非必需 |
|
||||
| `opendance-windows-v0.1.0.zip` | `dist-release/` | 发布包:exe + pck + `THIRD_PARTY.md` + `README.txt` + `contracts/` |
|
||||
|
||||
导出前必须安装与编辑器同版本的 export templates,否则脚本会报「未找到导出模板」。
|
||||
|
||||
### 7. 自检清单
|
||||
|
||||
| 检查 | 命令 | 预期输出 |
|
||||
|--|--|--|
|
||||
| 一键验证(编译 + 测试 + 启动) | `GODOT_BIN=/path/to/godot bash build/verify.sh` | 末行「验证全部通过」,退出码 0 |
|
||||
| 无头单元 / 集成测试 | `$GODOT --headless --path . --script res://tests/run_tests.gd` | `== 结果:580 通过 / 0 失败 ==` 与 `全部通过`(仓库基线 580 项 / 8 个套件;缺 VRM 素材或服务端不可达时相关用例显式跳过,数字会减少) |
|
||||
| 启动冒烟(180 帧) | `$GODOT --headless --path . --quit-after 180` | 无 `SCRIPT ERROR` / `ERROR:`,进程退出码 0 |
|
||||
| 日志检查 | 打开 `%APPDATA%\Godot\app_userdata\OpenDance Studio\logs\godot.log` | 无 `SCRIPT ERROR`、无 `ERROR:` |
|
||||
| 导出产物校验 | `bash build/export-windows.sh release` | 输出「PE 头校验通过」,且 `build/windows/OpenDance.exe` 前 2 字节为 `4d5a` |
|
||||
| 服务端联通 | 主界面「服务端地址」->「保存并测试」 | 顶部显示「服务端正常 | DB ...」 |
|
||||
|
||||
### 8. 常见问题
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|--|--|--|
|
||||
| 游戏界面提示「模型加载失败:缺少骨骼」或「骨骼绑定失败,模型可能不受支持」 | 模型没有 `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\` |
|
||||
| 提示「接收服务启动失败(端口 8765 可能被占用)」 | TCP 8765 被占用或未放行 | 改 `opendance.cfg` 的 `listen_port` 后点「重新连接接收服务」,并同步修改 Android 端填写的接收地址 |
|
||||
| 手机已连接但分数一直是 0 | Android 端未开始推流,或帧内关键点置信度都低于 0.5 | 「查看接收状态」确认累计帧在增长、畸形帧为 0;确认 Android 端子协议为 `opendance.pose.v1` |
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 组件 | 选型 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 引擎 | Godot 4.7.x(MIT) |
|
||||
| VRM 加载 | Godot 内置 GLTFDocument |
|
||||
| 通信 | WebSocketPeer(服务端)+ HTTPRequest |
|
||||
@@ -48,7 +151,7 @@ bash build/package-release.sh 0.1.0
|
||||
## 架构
|
||||
|
||||
```
|
||||
Android 辅助 App ──239 字节关键点帧──▶ 本端 WebSocket 服务端 :8765
|
||||
Android 辅助 App ──239 字节关键点帧──▶ 本端 WebSocket 服务端:8765
|
||||
│
|
||||
Web 服务端 ──GET /api/catalog/export────────┤
|
||||
──GET /api/motions/:id/keypoints ┘
|
||||
@@ -61,7 +164,7 @@ src/
|
||||
├── autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
├── net/ 239 字节编解码、WebSocket 服务、REST 客户端、NDJSON 解析
|
||||
├── model/ VRM 加载、humanoid 骨骼名映射
|
||||
├── anim/ 关键点 → 骨骼重定向
|
||||
├── anim/ 关键点 -> 骨骼重定向
|
||||
├── score/ 特征提取、DTW、评分规则
|
||||
└── ui/ 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
|
||||
```
|
||||
@@ -70,14 +173,14 @@ src/
|
||||
|
||||
## 依赖的契约(opendance-web v0.1.0 冻结)
|
||||
|
||||
- [`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 优先规则
|
||||
- [`contracts/model-formats.md`](contracts/model-formats.md) — 模型与动作格式、关键点序列结构
|
||||
- [`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 优先规则
|
||||
- [`contracts/model-formats.md`](contracts/model-formats.md) - 模型与动作格式、关键点序列结构
|
||||
|
||||
`contracts/` 是**只读快照**。任何跨端契约变更必须先回到
|
||||
[`opendance-web`](https://gitea.kasugano.cn/zijunchiang/opendance-web) 修改并提升版本,
|
||||
[`opendance-web`](https://<Gitea 地址>/<owner>/opendance-web) 修改并提升版本,
|
||||
再在本仓库创建适配任务。
|
||||
|
||||
## 已知限制
|
||||
@@ -85,7 +188,7 @@ src/
|
||||
1. Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
|
||||
实现接受任意 path,仅校验子协议 `opendance.pose.v1`。
|
||||
2. 文档原选型 `godot-vrm` GDExtension 不发布预编译产物,改用 Godot 内置 glTF 导入器
|
||||
+ 自实现骨骼名映射;理由见 `DSH.md` §6。
|
||||
+ 自实现骨骼名映射;理由见 `DSH.md` 6。
|
||||
3. `build/export-windows.ps1` 未在真实 Windows 上执行过(本环境无 Windows)。
|
||||
两条路径共用同名导出预设与相同的 PE 头校验逻辑。
|
||||
4. 真实 VRM 样例仅放在 `_scratch/`(已 gitignore),版权原因不入库;
|
||||
|
||||
+138
-35
@@ -1,5 +1,5 @@
|
||||
================================================================
|
||||
OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
OpenDance Studio -- Windows 客户端 v0.1.0 使用说明
|
||||
================================================================
|
||||
|
||||
摄像头跳舞评分系统的核心体验端:接收 Android 辅助 App 通过 WebSocket
|
||||
@@ -8,9 +8,9 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
|
||||
本客户端只接收关键点坐标,不接收、不存储任何视频画面。
|
||||
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
一、快速开始
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
|
||||
1. 解压后得到:
|
||||
OpenDance.exe 主程序
|
||||
@@ -24,19 +24,122 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
3. 双击 OpenDance.exe 启动。
|
||||
|
||||
4. 在主界面确认「服务端地址」(默认 http://127.0.0.1:3000),
|
||||
点击「保存并测试」;顶部应显示「服务端正常 | DB ...」。
|
||||
点击「保存并测试」;顶部应显示「服务端正常 | DB...」。
|
||||
|
||||
5. 点击「刷新目录」,左侧会出现已发布的曲目、动作与模型。
|
||||
|
||||
6. 在 Android 手机上启动「OpenDance 辅助 App」,填入本机显示在
|
||||
主界面上的接收地址(形如 ws://192.168.x.x:8765/pose)并连接。
|
||||
主界面上的接收地址(形如 ws://<本机局域网地址>:8765/pose)并连接。
|
||||
连接成功后主界面会提示「已连接 N 台设备」。
|
||||
|
||||
7. 依次选择曲目 → 动作 → 模型,点击「开始跟练」。
|
||||
7. 依次选择曲目 -> 动作 -> 模型,点击「开始跟练」。
|
||||
|
||||
----------------------------------------------------------------
|
||||
二、界面说明
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
二、初次部署(从零跑起来)
|
||||
--
|
||||
|
||||
本节面向第一次拿到源码、要从零把客户端跑起来的人;只运行已打包好的
|
||||
OpenDance.exe 的用户看第一节即可。
|
||||
|
||||
1. 前置条件
|
||||
|
||||
- 操作系统:Windows 10/11(x86_64)。开发与导出可在 Windows 或 Linux。
|
||||
- Godot 4.7.x 标准版(GDScript)。不需要 .NET / C# 版。
|
||||
- 渲染后端为 GL Compatibility,运行需 OpenGL 3.3 级别的显卡与驱动。
|
||||
- 导出模板:仅打包导出时需要,必须与编辑器完全同版本。
|
||||
- 磁盘:编辑器 + 导出模板数百 MB;导出产物 exe 约 104 MB + pck 约 107 KB。
|
||||
- 网络:能访问 Web 服务端 API(默认 TCP 3000);与手机同一局域网,
|
||||
并放行本端 TCP 8765 入站。
|
||||
|
||||
2. 获取代码
|
||||
|
||||
git clone https://<Gitea 地址>/<owner>/opendance-windows
|
||||
cd opendance-windows
|
||||
|
||||
3. 编辑器内首次运行
|
||||
|
||||
- Godot 项目管理器 -> 导入 -> 选择 project.godot。
|
||||
- 首次导入会执行资源导入(生成 .godot/),可能等待数十秒到数分钟;
|
||||
不要在导入中途关闭编辑器。
|
||||
- 主场景已配置为 res://scenes/main.tscn,按 F5 运行。
|
||||
- 首次运行会生成:
|
||||
%APPDATA%\Godot\app_userdata\OpenDance Studio\opendance.cfg
|
||||
%APPDATA%\Godot\app_userdata\OpenDance Studio\logs\godot.log
|
||||
|
||||
4. 配置服务端地址
|
||||
|
||||
主界面顶部「服务端地址」填好后点「保存并测试」,配置字段名为 api_base_url:
|
||||
|
||||
http://<服务器地址>:3000
|
||||
http://192.0.2.10:3000
|
||||
|
||||
注意:客户端只访问 API 端口(默认 3000);管理端页面在管理端口
|
||||
(默认 3001),客户端不使用。保存后点「刷新目录」。
|
||||
|
||||
5. 与另两端的联通
|
||||
|
||||
- Android 辅助 App -> 本端 WebSocket:本端是服务端,默认监听
|
||||
0.0.0.0:8765,路径 /pose,子协议 opendance.pose.v1。Android 端填写
|
||||
本端主界面显示的「接收地址」,形如 ws://<本机局域网地址>:8765/pose。
|
||||
本端可在 opendance.cfg 改 listen_host / listen_port / ws_subprotocol,
|
||||
改后点「重新连接接收服务」生效。
|
||||
- Web 服务端:需先在服务端发布曲目 / 动作 / 模型,动作还须完成关键点
|
||||
预处理,客户端才有可选内容。
|
||||
|
||||
6. 打包导出
|
||||
|
||||
# Linux(推荐)
|
||||
GODOT_BIN=/path/to/godot bash build/export-windows.sh release
|
||||
|
||||
# Windows(兜底)
|
||||
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
|
||||
|
||||
产物在 build/windows/:OpenDance.exe(约 104 MB,PE 头 MZ 校验通过)
|
||||
+ OpenDance.pck(约 107 KB,必须与 exe 同目录);可能附带
|
||||
OpenDance.console.exe。
|
||||
|
||||
打成发布包(可选):
|
||||
|
||||
bash build/package-release.sh 0.1.0
|
||||
|
||||
得到 dist-release/opendance-windows-v0.1.0.zip,内含 OpenDance.exe、
|
||||
OpenDance.pck、THIRD_PARTY.md、README.txt、contracts/。
|
||||
|
||||
7. 自检清单
|
||||
|
||||
1) 一键验证:GODOT_BIN=/path/to/godot bash build/verify.sh
|
||||
预期末行「验证全部通过」。
|
||||
2) 无头测试:$GODOT --headless --path . --script res://tests/run_tests.gd
|
||||
预期「== 结果:580 通过 / 0 失败 ==」与「全部通过」(缺 VRM 素材或
|
||||
服务端不可达时相关用例跳过,数字会减少)。
|
||||
3) 启动冒烟:$GODOT --headless --path . --quit-after 180
|
||||
预期无 SCRIPT ERROR / ERROR:,退出码 0。
|
||||
4) 日志:%APPDATA%\Godot\app_userdata\OpenDance Studio\logs\godot.log
|
||||
预期无 SCRIPT ERROR。
|
||||
5) 服务端联通:主界面点「保存并测试」后顶部显示「服务端正常 | DB ...」。
|
||||
|
||||
8. 部署相关常见问题
|
||||
|
||||
Q: 模型加载失败:缺少骨骼
|
||||
A: 模型无 Skeleton3D,或骨骼命名不在映射表内;换用标准 VRM(VRoid
|
||||
Studio 导出)。人形覆盖率低于 60% 会提示「骨骼覆盖偏低」。
|
||||
|
||||
Q: 连不上服务端 / 显示「服务端不可达」
|
||||
A: 确认 Web 服务端已启动,api_base_url 指向 http://<服务器地址>:3000
|
||||
(不要填管理端口 3001);访问 http://<服务器地址>:3000/health 应返回 200。
|
||||
|
||||
Q: 导出失败:未找到导出模板
|
||||
A: 安装与编辑器同版本的 export templates。Linux 放
|
||||
~/.local/share/godot/export_templates/<版本>.stable/;
|
||||
Windows 放 %APPDATA%\Godot\export_templates\<版本>.stable\。
|
||||
|
||||
Q: 接收服务启动失败(端口 8765 可能被占用)
|
||||
A: 改 opendance.cfg 的 listen_port 后点「重新连接接收服务」,
|
||||
并同步修改 Android 端填写的接收地址。
|
||||
|
||||
--
|
||||
三、界面说明
|
||||
--
|
||||
|
||||
主界面
|
||||
- 三栏选择:曲目 / 动作(仅列出已完成预处理的动作)/ 模型(VRM 优先)
|
||||
@@ -56,9 +159,9 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
关于页面
|
||||
- 版本与契约版本、隐私说明、完整可滚动的第三方开源项目声明
|
||||
|
||||
----------------------------------------------------------------
|
||||
三、配置文件
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
四、配置文件
|
||||
--
|
||||
|
||||
首次运行会在以下位置生成配置文件,可直接用文本编辑器修改:
|
||||
|
||||
@@ -74,9 +177,9 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
score_tolerance 评分容差:平均每步代价达到该值即 0 分(默认 0.25)
|
||||
mirror_preview 镜像预览
|
||||
|
||||
----------------------------------------------------------------
|
||||
四、防火墙与网络
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
五、防火墙与网络
|
||||
--
|
||||
|
||||
本客户端需要接受 Android 设备的入站连接:
|
||||
|
||||
@@ -86,9 +189,9 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
|
||||
若端口被占用,可修改 opendance.cfg 中的 listen_port 后重启。
|
||||
|
||||
----------------------------------------------------------------
|
||||
五、评分说明
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
六、评分说明
|
||||
--
|
||||
|
||||
- 每帧特征 = 33 点 × (x,y,z) + 9 个关节角余弦 = 108 维
|
||||
(与 Web 服务端预处理产物、Android 端推流结构完全一致)
|
||||
@@ -97,9 +200,9 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
- 得分 = clamp(1 − 平均代价 / 容差, 0, 1) × 100,并做指数平滑
|
||||
- 等级:S ≥ 95,A ≥ 85,B ≥ 70,C ≥ 55,D 其余
|
||||
|
||||
----------------------------------------------------------------
|
||||
六、从源码构建
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
七、从源码构建
|
||||
--
|
||||
|
||||
前置:Godot 4.7.x + 对应版本的 export templates
|
||||
|
||||
@@ -115,13 +218,13 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
运行测试:
|
||||
|
||||
GODOT=/path/to/godot
|
||||
$GODOT --headless --path . --import
|
||||
$GODOT --headless --path . --script res://tests/run_tests.gd
|
||||
$GODOT --headless --path . --quit-after 180
|
||||
$GODOT --headless --path. --import
|
||||
$GODOT --headless --path. --script res://tests/run_tests.gd
|
||||
$GODOT --headless --path. --quit-after 180
|
||||
|
||||
----------------------------------------------------------------
|
||||
七、目录结构
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
八、目录结构
|
||||
--
|
||||
|
||||
project.godot 工程配置(autoload / 渲染 / 窗口)
|
||||
export_presets.cfg 导出预设(必须在项目根目录)
|
||||
@@ -133,21 +236,21 @@ OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
src/autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
src/net/ 239 字节编解码、WebSocket 服务、REST 客户端
|
||||
src/model/ VRM 加载、humanoid 骨骼映射
|
||||
src/anim/ 关键点 → 骨骼重定向
|
||||
src/anim/ 关键点 -> 骨骼重定向
|
||||
src/score/ 特征提取、DTW、评分规则
|
||||
src/ui/ 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
|
||||
tests/ 无头测试框架与用例
|
||||
build/ 导出脚本
|
||||
|
||||
----------------------------------------------------------------
|
||||
八、常见问题
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
九、常见问题
|
||||
--
|
||||
|
||||
Q: 主界面显示「服务端不可达」
|
||||
A: 检查 Web 服务端是否已启动,以及「服务端地址」是否正确(含端口)。
|
||||
|
||||
Q: 目录里没有可用的动作
|
||||
A: 动作必须先在管理端完成「预处理」(BVH → 关键点序列)并「审核通过」,
|
||||
A: 动作必须先在管理端完成「预处理」(BVH -> 关键点序列)并「审核通过」,
|
||||
才会出现在客户端。
|
||||
|
||||
Q: 模型加载失败 / 动作不完整
|
||||
@@ -160,9 +263,9 @@ A: 确认手机端已开始推流;主界面的「查看接收状态」会显
|
||||
Q: 关于页面的开源声明
|
||||
A: 与 THIRD_PARTY.md、contracts/open-source-credits.md 三处保持一致。
|
||||
|
||||
----------------------------------------------------------------
|
||||
九、许可
|
||||
----------------------------------------------------------------
|
||||
--
|
||||
十、许可
|
||||
--
|
||||
|
||||
本客户端代码以 MIT 许可发布。第三方开源项目声明见 THIRD_PARTY.md。
|
||||
未使用 SMPL 模型及其衍生拓扑,统一采用 MediaPipe 33 点拓扑。
|
||||
|
||||
+13
-13
@@ -1,4 +1,4 @@
|
||||
# contracts/data-models.md — 数据模型契约 v0.4.0
|
||||
# contracts/data-models.md - 数据模型契约 v0.4.0
|
||||
|
||||
Web 服务端是唯一权威定义,本文件是另两端消费的**稳定视图**。
|
||||
字段名在 JSON 中统一 `camelCase`;数据库列名 `snake_case`(见 `database.md`)。
|
||||
@@ -16,13 +16,13 @@ Web 服务端是唯一权威定义,本文件是另两端消费的**稳定视
|
||||
- 错误响应统一:
|
||||
|
||||
```json
|
||||
{ "statusCode": 400, "message": "…", "error": "Bad Request", "details": {} }
|
||||
{ "statusCode": 400, "message": "...", "error": "Bad Request", "details": {} }
|
||||
```
|
||||
|
||||
- **来源(出处)五元组**是必填项,出现在 `songs` / `motions` / `models` 三处:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
|--|--|--|
|
||||
| `sourceType` | enum | 见 1.1 |
|
||||
| `sourceName` | string | 来源名称,如「官方曲库」「作者名」 |
|
||||
| `sourceUrl` | string | 来源链接,必须为 http(s) |
|
||||
@@ -52,7 +52,7 @@ rejected 已驳回
|
||||
## 2. `Song`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
|--|--|--|--|
|
||||
| `id` | number | 读 | |
|
||||
| `title` | string | ✅ | 曲名,≤200 |
|
||||
| `artist` | string | ✅ | 演唱者,≤200 |
|
||||
@@ -71,7 +71,7 @@ rejected 已驳回
|
||||
## 3. `Motion`(动作数据)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
|--|--|--|--|
|
||||
| `id` | number | 读 | |
|
||||
| `songId` | number\|null | | 关联曲目 |
|
||||
| `name` | string | ✅ | 动作名称,≤200 |
|
||||
@@ -82,7 +82,7 @@ rejected 已驳回
|
||||
| `frameCount` | number\|null | | 原始帧数 |
|
||||
| `durationMs` | number\|null | | |
|
||||
| `keypointFormat` | string\|null | 读 | 预处理后固定为 `mediapipe33@0.1.0` |
|
||||
| `keypointUrl` | string\|null | 读 | 预处理产物(NDJSON 或 .npz 的 JSON 封装) |
|
||||
| `keypointUrl` | string\|null | 读 | 预处理产物(NDJSON 或.npz 的 JSON 封装) |
|
||||
| `keypointFrames` | number\|null | 读 | 关键点帧数 |
|
||||
| `preprocessState` | enum | 读 | `none` / `running` / `done` / `failed` |
|
||||
| `preprocessError` | string\|null | 读 | |
|
||||
@@ -93,19 +93,19 @@ rejected 已驳回
|
||||
## 4. `Model`(角色模型,VRM 优先)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
|--|--|--|--|
|
||||
| `id` | number | 读 | |
|
||||
| `name` | string | ✅ | 模型名称,≤200 |
|
||||
| `format` | enum | ✅ | `glb` / `vrm`(主力)/ `gltf` / `fbx`(历史数据)。上传的 PMX/FBX 会被服务端转换成 glb 或 vrm 再入库,见 model-formats.md §1.1 |
|
||||
| `format` | enum | ✅ | `glb` / `vrm`(主力)/ `gltf` / `fbx`(历史数据)。上传的 PMX/FBX 会被服务端转换成 glb 或 vrm 再入库,见 model-formats.md 1.1 |
|
||||
| `vrmVersion` | string\|null | 读 | `0.x` / `1.0`,非 VRM 为 null |
|
||||
| `isPrimary` | boolean | | 全局默认模型;**同一时刻全库至多一个 `true`** |
|
||||
| `fileUrl` | string | 读 | |
|
||||
| `fileSize` | number | 读 | |
|
||||
| `thumbnailUrl` | string\|null | 读 | |
|
||||
| `conversionNote` | string\|null | 读 | 由压缩包转换而来时的说明(`Hatsune Miku.pmx → GLB,已内嵌 2 张贴图`);直传模型为 null |
|
||||
| `fileSha256` | string\|null | 读 | 模型文件(转换产物)的 sha256 —— **每次转换都会变**,仅作记录 |
|
||||
| `conversionNote` | string\|null | 读 | 由压缩包转换而来时的说明(`Hatsune Miku.pmx -> GLB,已内嵌 2 张贴图`);直传模型为 null |
|
||||
| `fileSha256` | string\|null | 读 | 模型文件(转换产物)的 sha256 -- **每次转换都会变**,仅作记录 |
|
||||
| `sourceSha256` | string\|null | 读 | 上传压缩包的 sha256(稳定);默认模型匹配以它为准,非压缩包上传为 null |
|
||||
| `stats` | object\|null | 读 | 模型分析结果:面数 / 顶点数 / 贴图 / **骨骼树** / humanoid 覆盖 / 可驱动性,见 model-formats.md §1.1 |
|
||||
| `stats` | object\|null | 读 | 模型分析结果:面数 / 顶点数 / 贴图 / **骨骼树** / humanoid 覆盖 / 可驱动性,见 model-formats.md 1.1 |
|
||||
| `source*` | | ✅ | 来源五元组 |
|
||||
| `status` | `PublishStatus` | | |
|
||||
| `createdAt` / `updatedAt` | string | 读 | |
|
||||
@@ -124,7 +124,7 @@ rejected 已驳回
|
||||
2. models 中 format = 'vrm' 且 status = 'published' 且 is_primary = 1
|
||||
3. models 中 format = 'vrm' 且 status = 'published',按 id 升序取第一个
|
||||
4. models 中 status = 'published',按 (format='vrm' DESC, id ASC) 取第一个
|
||||
5. 无可用模型 → 客户端使用内置占位模型
|
||||
5. 无可用模型 -> 客户端使用内置占位模型
|
||||
```
|
||||
|
||||
第 0/1 步是 v0.3.0 新增:上传 MMD 模型压缩包并转换成功后,服务端会把该模型
|
||||
@@ -135,7 +135,7 @@ rejected 已驳回
|
||||
## 5. `ReviewRecord`(审核流水)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
|--|--|--|
|
||||
| `id` | number | |
|
||||
| `targetType` | enum | `song` / `motion` / `model` |
|
||||
| `targetId` | number | |
|
||||
|
||||
+11
-11
@@ -1,11 +1,11 @@
|
||||
# contracts/database.md — 数据库契约 v0.1.0
|
||||
# contracts/database.md - 数据库契约 v0.1.0
|
||||
|
||||
## 环境
|
||||
|
||||
- 类型:MySQL 兼容实例(外部提供)。**当前部署实际为 MariaDB 10.11.19**,TypeORM
|
||||
- 类型:MySQL 兼容实例(外部提供,含 MariaDB 10.5+),TypeORM
|
||||
使用 `mysql` driver(`mysql2`)连接,SQL 语法限制在 MySQL 5.7 / MariaDB 10.5+
|
||||
共同子集内,不使用 MySQL 8 专有语法(窗口函数 CTE 递归除外)。
|
||||
- 数据库名:`dpendance`(由部署方提供,通过 `DB_NAME` 注入;文档默认值 `opendance` 可覆盖)
|
||||
- 数据库名:由部署方提供,通过 `DB_NAME` 注入(本文档示例统一用 `opendance`)
|
||||
- 字符集:`utf8mb4` / `utf8mb4_general_ci`(沿用现有实例默认排序规则)
|
||||
- 迁移工具:TypeORM migration,**不使用 `DB_SYNCHRONIZE`**
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
host / port / user / password 一律通过环境变量注入,**不入库、不入仓**。
|
||||
|
||||
```bash
|
||||
DB_HOST=192.168.150.10
|
||||
DB_HOST=<数据库主机>
|
||||
DB_PORT=3306
|
||||
DB_USER=dpendance
|
||||
DB_PASS=<部署方提供>
|
||||
DB_NAME=dpendance
|
||||
DB_USER=opendance_app
|
||||
DB_PASS=<数据库口令>
|
||||
DB_NAME=opendance
|
||||
DB_SYNCHRONIZE=false
|
||||
DB_LOGGING=false
|
||||
```
|
||||
@@ -39,17 +39,17 @@ DB_LOGGING=false
|
||||
## 增量迁移(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)
|
||||
|
||||
| 表 | 用途 | 任务 |
|
||||
|---|---|---|
|
||||
|--|--|--|
|
||||
| `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 |
|
||||
|
||||
|
||||
+34
-34
@@ -1,15 +1,15 @@
|
||||
# contracts/model-formats.md — 模型与动作格式契约 v0.4.0
|
||||
# contracts/model-formats.md - 模型与动作格式契约 v0.4.0
|
||||
|
||||
## 1. 角色模型(models)
|
||||
|
||||
**系统以 GLB / VRM 为主力格式**(两个客户端都原生加载),其余格式一律在服务端转换。
|
||||
|
||||
| 格式 | 扩展名 | 支持级别 | 服务端行为 |
|
||||
|---|---|---|---|
|
||||
|--|--|--|--|
|
||||
| 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 |
|
||||
| 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) |
|
||||
|
||||
@@ -21,18 +21,18 @@
|
||||
#### 单文件上传
|
||||
|
||||
| 输入 | 行为 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| `.glb` / `.vrm` | 直接导入;`.glb` 若含 `VRMC_vrm`/`VRM` 扩展会被识别为 VRM |
|
||||
| `.gltf` | 直接导入(外部 `.bin` / 贴图不在同一文件里,会缺资源) |
|
||||
| `.pmx` / `.fbx` | 转换:`ASSIMP_BIN` 导出 → 回读校验(见下) |
|
||||
| `.pmx` / `.fbx` | 转换:`ASSIMP_BIN` 导出 -> 回读校验(见下) |
|
||||
|
||||
#### 压缩包上传(自动搜索)
|
||||
|
||||
不再"只找 `.pmx`",而是**按格式优先级自动搜索包内可用模型**:
|
||||
|
||||
```
|
||||
.vrm → .glb → .gltf (直接导入,优先)
|
||||
.pmx → .fbx (需要转换)
|
||||
.vrm ->.glb ->.gltf (直接导入,优先)
|
||||
.pmx ->.fbx (需要转换)
|
||||
```
|
||||
|
||||
同一优先级内:`MODEL_CONVERT_PMX` 指定的文件名优先,其次目录最浅,最后文件最大。
|
||||
@@ -41,31 +41,31 @@
|
||||
#### 转换与校验(PMX / FBX)
|
||||
|
||||
| 步骤 | 规则 |
|
||||
|---|---|
|
||||
| 1. GLB 直出 | `assimp export → .glb`,成功后把 Assimp 写成外部 URI 的贴图**内嵌**进 BIN chunk |
|
||||
| 2. glTF 打包 | 上一步失败时 `assimp export → .gltf`,由 `gltf-packer.ts` 把 `.gltf` + `.bin` + 贴图自行合成 GLB |
|
||||
|--|--|
|
||||
| 1. GLB 直出 | `assimp export ->.glb`,成功后把 Assimp 写成外部 URI 的贴图**内嵌**进 BIN chunk |
|
||||
| 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 张贴图`) |
|
||||
| 失败 | 包内没有可用模型、Assimp 不可用、两条路径都失败 → **HTTP 422**,不产生模型记录 |
|
||||
| 说明 | 每次尝试都写进 `models.conversion_note`(例如 `Hatsune Miku.pmx -> GLB,已内嵌 2 张贴图`) |
|
||||
| 失败 | 包内没有可用模型、Assimp 不可用、两条路径都失败 -> **HTTP 422**,不产生模型记录 |
|
||||
|
||||
#### 入库后(自动)
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 状态 | **一律 `pending`(待审核)**:模型必须审核通过后才可能被客户端消费,见 `database.md` 状态机 |
|
||||
| 分析 | `model-inspector.ts` 计算面数 / 顶点数 / 贴图 / **骨骼树** / humanoid 覆盖 / 可驱动性,写入 `models.stats`,供审核界面展示 |
|
||||
| 缩略图 | `model-thumbnail.ts` 纯 TS 光栅化生成 PNG,写入 `models.thumbnail_asset_id`(失败不影响入库) |
|
||||
| 默认模型 | 只在**审核通过并勾选"同时设为默认"**(或 `POST /api/models/:id/primary`)时设置,并把身份写进源码 `src/config/default-model.ts`,见 `data-models.md` §4.1 |
|
||||
| 默认模型 | 只在**审核通过并勾选"同时设为默认"**(或 `POST /api/models/:id/primary`)时设置,并把身份写进源码 `src/config/default-model.ts`,见 `data-models.md` 4.1 |
|
||||
|
||||
#### 其它约定
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换 3 次得到 3 个不同 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**上传压缩包的 sha256** 为匹配键,产物 sha256 只作记录 |
|
||||
| 安全 | 解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE |
|
||||
|
||||
**VRM 优先**的选取顺序见 `data-models.md` §4.1。
|
||||
**VRM 优先**的选取顺序见 `data-models.md` 4.1。
|
||||
|
||||
VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)或
|
||||
`extensions.VRMC_vrm`(1.0),写入 `models.vrm_version`。
|
||||
@@ -73,9 +73,9 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
## 2. 动作数据(motions)
|
||||
|
||||
| 格式 | 扩展名 | 预处理支持 | 说明 |
|
||||
|---|---|---|---|
|
||||
| BVH | `.bvh` | ✅ 内置纯 TS 解析器 | 文本格式,服务端直接解析 → FK → MediaPipe33 |
|
||||
| VMD | `.vmd` | ✅ 内置纯 TS 解析器 | MikuMikuDance 动作数据(二进制,Shift-JIS 骨骼名 + 四元数关键帧);服务端按标准 MMD 骨架做 FK + 腿部 IK 求解 → MediaPipe33,见 §2.3 |
|
||||
|--|--|--|--|
|
||||
| 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` 结构,仅做校验与归一化 |
|
||||
@@ -86,11 +86,11 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
- 一行结构:
|
||||
|
||||
```json
|
||||
{"frame":0,"timeMs":0.0,"points":[[0.5,0.5,0.0,1.0],"…33 项"],"angles":[1.0,"…9 项"]}
|
||||
{"frame":0,"timeMs":0.0,"points":[[0.5,0.5,0.0,1.0],"...33 项"],"angles":[1.0,"...9 项"]}
|
||||
```
|
||||
|
||||
- `points[i] = [x, y, z, confidence]`,语义与 `websocket-keypoints.md` 解码后完全一致
|
||||
- `angles` 为 `skeleton-topology.md` §5 定义的 9 个关节角余弦,顺序固定:
|
||||
- `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 轴向上、右手系、骨盆为原点**,
|
||||
@@ -102,7 +102,7 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
### 2.2 BVH 关节名映射(默认表,可在 `web-server/src/preprocess/joint-map.ts` 扩展)
|
||||
|
||||
| MediaPipe 点 | 常见 BVH 关节名(不区分大小写,按序匹配) |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 0 nose | `Head`, `Neck1`, `Head_Nub` |
|
||||
| 11 left_shoulder | `LeftShoulder`, `LeftArm`, `LShoulder` |
|
||||
| 12 right_shoulder | `RightShoulder`, `RightArm`, `RShoulder` |
|
||||
@@ -119,7 +119,7 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
|
||||
| 31 left_foot_index | `LeftToeBase`, `LeftToe`, `LToe` |
|
||||
| 32 right_foot_index | `RightToeBase`, `RightToe`, `RToe` |
|
||||
|
||||
未映射到的点按 `skeleton-topology.md` §5 的层级由父节点推算,
|
||||
未映射到的点按 `skeleton-topology.md` 5 的层级由父节点推算,
|
||||
并置 `confidence` 降低(`0.5`),表示估算而非实测。
|
||||
|
||||
### 2.3 VMD(MikuMikuDance)解析约定
|
||||
@@ -130,13 +130,13 @@ float32×3 位移、float32×4 旋转四元数、64 字节贝塞尔插值参数
|
||||
灯光 / 自阴影 / IK / 可见性段落。**VMD 不含骨骼长度**,只有旋转(少数骨骼带位移),
|
||||
因此服务端必须自带一套标准 MMD 骨架才能做 FK。
|
||||
|
||||
处理链路:`vmd-parser.ts`(二进制 + Shift-JIS + 贝塞尔插值)→ `mmd-skeleton.ts`
|
||||
(标准骨架)→ `mmd-pose.ts`(FK + 腿部 IK)→ `retarget.ts` 的
|
||||
处理链路:`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 | `左ひじ` / `右ひじ` | |
|
||||
@@ -149,12 +149,12 @@ float32×3 位移、float32×4 旋转四元数、64 字节贝塞尔插值参数
|
||||
骨骼名按 `mmd-skeleton.ts` 的别名表归一(含全角 `IK` / 半角 `IK` 两种写法);
|
||||
未纳入标准骨架的骨骼(裙摆、物理、表情等)被忽略但会计数并写入预处理日志。
|
||||
|
||||
**腿部驱动判定**:`足IK` 轨道存在非零位移 → 走 IK(用两骨骼解析 IK 把踝拉到 IK
|
||||
**腿部驱动判定**:`足IK` 轨道存在非零位移 -> 走 IK(用两骨骼解析 IK 把踝拉到 IK
|
||||
目标点,膝盖朝角色正面弯,不可达时收敛为伸直);否则走 FK(部分动作数据把腿烘焙成
|
||||
FK 旋转)。两份示例文件分别命中两种路径。
|
||||
|
||||
**坐标系**:MMD 原生是左手系(正面为 -Z),本项目通路是右手系(正面为 +Z、+X 为角色
|
||||
左侧)。VMD 的四元数与位移统一做 Z 镜像(`(x,y,z,w) → (-x,-y,z,w)`、位移 z 取反)后
|
||||
左侧)。VMD 的四元数与位移统一做 Z 镜像(`(x,y,z,w) -> (-x,-y,z,w)`、位移 z 取反)后
|
||||
送入标准骨架。该选择由真实数据对照实验确定:腿部烘焙为 FK 的样本中,`zMirrored` 下
|
||||
膝盖朝前占比 94.1%,不转换时仅 5.9%(见 `test/mmd-vmd.spec.ts`)。
|
||||
|
||||
@@ -166,7 +166,7 @@ FK 旋转)。两份示例文件分别命中两种路径。
|
||||
## 3. 上传限制
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 单文件上限 | 512 MiB(`UPLOAD_MAX_BYTES` 可覆盖) |
|
||||
| 允许扩展名 | 上表全部;其他一律 422 |
|
||||
| 存储路径 | `${STORAGE_DIR}/{kind}/{yyyy}/{mm}/{sha256前2位}/{sha256}.{ext}` |
|
||||
@@ -175,7 +175,7 @@ FK 旋转)。两份示例文件分别命中两种路径。
|
||||
|
||||
## 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 预览
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# contracts/open-source-credits.md — 开源声明锚点 v0.1.0
|
||||
# contracts/open-source-credits.md - 开源声明锚点 v0.1.0
|
||||
|
||||
三端发布产物中的 `THIRD_PARTY.md` 与各自「关于」页面必须包含下表全部条目。
|
||||
本文件是**唯一来源**,任一端新增依赖须回到 `opendance-web` 更新此表。
|
||||
|
||||
| # | 项目 | 许可证 | 源链接 | 使用端 |
|
||||
|---|---|---|---|---|
|
||||
|--|--|--|--|--|
|
||||
| 1 | MediaPipe | Apache License 2.0 | https://github.com/google-ai-edge/mediapipe | Android |
|
||||
| 2 | Three.js | MIT | https://github.com/mrdoob/three.js | Web 管理端 |
|
||||
| 3 | @pixiv/three-vrm | MIT | https://github.com/pixiv/three-vrm | Web 管理端 |
|
||||
@@ -21,7 +21,7 @@
|
||||
| 14 | Jetpack Compose | Apache License 2.0 | https://developer.android.com/jetpack/compose | Android |
|
||||
| 15 | mysql2 | MIT | https://github.com/sidorares/node-mysql2 | Web 服务端 |
|
||||
|
||||
> 注意:文档模板中列出的 10 项为**最低要求**;第 11–15 项为实际引入的构建期/运行时依赖,
|
||||
> 注意:文档模板中列出的 10 项为**最低要求**;第 11-15 项为实际引入的构建期/运行时依赖,
|
||||
> 一并声明更完整。发布时必须保留各许可证副本(见 `web-server/LICENSE-THIRD-PARTY/`)。
|
||||
|
||||
## 版权声明补充
|
||||
|
||||
+12
-12
@@ -262,7 +262,7 @@ paths:
|
||||
parameters: [{ $ref: '#/components/parameters/Id' }]
|
||||
post:
|
||||
tags: [motions]
|
||||
summary: 触发 BVH/FBX → MediaPipe33 关键点序列预处理(同步执行)
|
||||
summary: 触发 BVH/FBX -> MediaPipe33 关键点序列预处理(同步执行)
|
||||
description: |
|
||||
同步执行并返回最终状态:成功时 preprocessState=done;
|
||||
失败时返回 422,且 Motion.preprocessError 记录可读原因。
|
||||
@@ -285,7 +285,7 @@ paths:
|
||||
'200':
|
||||
description: |
|
||||
application/x-ndjson,每行一个帧对象:
|
||||
{"frame":0,"timeMs":0,"points":[[x,y,z,conf], …33],"angles":[9 个余弦]}
|
||||
{"frame":0,"timeMs":0,"points":[[x,y,z,conf], ...33],"angles":[9 个余弦]}
|
||||
content:
|
||||
application/x-ndjson:
|
||||
schema: { type: string }
|
||||
@@ -323,11 +323,11 @@ paths:
|
||||
|
||||
- `.glb` / `.vrm` / `.gltf`:**直接导入**,不重编码
|
||||
- `.pmx` / `.fbx`:服务端转换,**优先 GLB、其次 VRM**
|
||||
- `.zip`:**自动搜索包内可用模型** —— `.vrm` → `.glb` → `.gltf`(直接导入)
|
||||
优先,其次 `.pmx` → `.fbx`(转换)。压缩包原件作为 `model-archive` 资产留存。
|
||||
- `.zip`:**自动搜索包内可用模型** -- `.vrm` -> `.glb` -> `.gltf`(直接导入)
|
||||
优先,其次 `.pmx` -> `.fbx`(转换)。压缩包原件作为 `model-archive` 资产留存。
|
||||
|
||||
转换链路(`model-convert.ts`):`assimp export → .glb` →(失败)`→ .gltf` 后
|
||||
由服务端自行打包成 GLB →(`MODEL_CONVERT_FORMAT=vrm` 时)在 GLB 之上补
|
||||
转换链路(`model-convert.ts`):`assimp export -> .glb` ->(失败)`-> .gltf` 后
|
||||
由服务端自行打包成 GLB ->(`MODEL_CONVERT_FORMAT=vrm` 时)在 GLB 之上补
|
||||
`VRMC_vrm` 扩展产出 VRM 1.0。每一步都 `assimp info` **回读校验**,
|
||||
并把过程写进 `conversionNote`;贴图会内嵌进 GLB,保证单文件可用。
|
||||
|
||||
@@ -488,7 +488,7 @@ paths:
|
||||
- $ref: '#/components/parameters/Id'
|
||||
post:
|
||||
tags: [reviews]
|
||||
summary: 审核通过 → published
|
||||
summary: 审核通过 -> published
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
@@ -510,7 +510,7 @@ paths:
|
||||
- $ref: '#/components/parameters/Id'
|
||||
post:
|
||||
tags: [reviews]
|
||||
summary: 撤回审核 → draft
|
||||
summary: 撤回审核 -> draft
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
@@ -552,7 +552,7 @@ paths:
|
||||
- $ref: '#/components/parameters/Id'
|
||||
post:
|
||||
tags: [reviews]
|
||||
summary: 审核驳回 → rejected
|
||||
summary: 审核驳回 -> rejected
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -664,7 +664,7 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
connected: { type: boolean }
|
||||
server: { type: string, description: '如 MariaDB 10.11.19' }
|
||||
server: { type: string, description: '如 MariaDB 10.5+' }
|
||||
database: { type: string }
|
||||
migrationsPending: { type: integer }
|
||||
|
||||
@@ -795,13 +795,13 @@ components:
|
||||
fileSha256:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 模型文件(转换产物)的 sha256 —— 每次转换都会变(Assimp 导出非字节级确定),仅作记录
|
||||
description: 模型文件(转换产物)的 sha256 -- 每次转换都会变(Assimp 导出非字节级确定),仅作记录
|
||||
stats:
|
||||
type: object
|
||||
nullable: true
|
||||
description: >-
|
||||
模型分析结果(面数 / 顶点数 / 贴图 / 骨骼树 / humanoid 覆盖 / 可驱动性),
|
||||
审核界面据此判断"能不能驱动与评分"。结构见 contracts/model-formats.md §1.1
|
||||
审核界面据此判断"能不能驱动与评分"。结构见 contracts/model-formats.md 1.1
|
||||
sourceSha256:
|
||||
type: string
|
||||
nullable: true
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# contracts/skeleton-topology.md — 骨骼拓扑契约 v0.1.0
|
||||
# contracts/skeleton-topology.md - 骨骼拓扑契约 v0.1.0
|
||||
|
||||
**唯一拓扑:MediaPipe Pose 33 点。禁止引入 SMPL 及其衍生拓扑(许可风险)。**
|
||||
|
||||
## 1. 33 点定义与索引
|
||||
|
||||
| idx | name | 中文 | 说明 |
|
||||
|---|---|---|---|
|
||||
|--|--|--|--|
|
||||
| 0 | nose | 鼻 | |
|
||||
| 1 | left_eye_inner | 左眼内 | |
|
||||
| 2 | left_eye | 左眼 | |
|
||||
@@ -43,7 +43,7 @@
|
||||
## 2. 虚拟关节(由 33 点派生,索引 33/34,仅用于层级与角度特征)
|
||||
|
||||
| idx | name | 计算 |
|
||||
|---|---|---|
|
||||
|--|--|--|
|
||||
| 33 | neck | `midpoint(left_shoulder(11), right_shoulder(12))` |
|
||||
| 34 | pelvis | `midpoint(left_hip(23), right_hip(24))` |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# contracts/websocket-keypoints.md — 关键点传输契约 v0.1.0
|
||||
# contracts/websocket-keypoints.md - 关键点传输契约 v0.1.0
|
||||
|
||||
Android 辅助 App(发送端)→ Windows 客户端(接收端)。
|
||||
Android 辅助 App(发送端)-> Windows 客户端(接收端)。
|
||||
服务端不参与实时链路,但 T-WEB-006 预处理产出的序列必须与此结构同构。
|
||||
|
||||
## 1. 端点
|
||||
@@ -28,7 +28,7 @@ ws://<windows-host>:8765/pose
|
||||
bit3-7 保留,必须为 0
|
||||
4 4 frame_id u32 LE 单调递增,从 0 开始
|
||||
8 231 keypoints 33 × 7B 见下
|
||||
--------------------------------------------------------------
|
||||
--
|
||||
合计 239
|
||||
```
|
||||
|
||||
@@ -70,7 +70,7 @@ func decode(buf: PackedByteArray) -> Dictionary:
|
||||
|
||||
## 3. 文本控制帧(JSON,UTF-8)
|
||||
|
||||
### 3.1 `hello` — Android → Windows,连接后立即发送
|
||||
### 3.1 `hello` - Android -> Windows,连接后立即发送
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -84,25 +84,25 @@ func decode(buf: PackedByteArray) -> Dictionary:
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 `welcome` — Windows → Android
|
||||
### 3.2 `welcome` - Windows -> Android
|
||||
|
||||
```json
|
||||
{ "type": "welcome", "protocol": "opendance.pose.v1", "sessionId": "uuid", "expectedFps": 30 }
|
||||
```
|
||||
|
||||
### 3.3 `config` — Windows → Android,运行中调整
|
||||
### 3.3 `config` - Windows -> Android,运行中调整
|
||||
|
||||
```json
|
||||
{ "type": "config", "mirror": true, "targetFps": 30, "upperBodyOnly": false, "resolution": "640x480" }
|
||||
```
|
||||
|
||||
### 3.4 `stats` — Android → Windows,每秒一次
|
||||
### 3.4 `stats` - Android -> Windows,每秒一次
|
||||
|
||||
```json
|
||||
{ "type": "stats", "fps": 29.6, "dropped": 3, "latencyMs": 42, "confidenceAvg": 0.93 }
|
||||
```
|
||||
|
||||
### 3.5 `bye` — 双向
|
||||
### 3.5 `bye` - 双向
|
||||
|
||||
```json
|
||||
{ "type": "bye", "reason": "user_stop" }
|
||||
@@ -117,7 +117,7 @@ func decode(buf: PackedByteArray) -> Dictionary:
|
||||
"frameId": 1024,
|
||||
"timestampMs": 1730000000000,
|
||||
"flags": { "mirrored": false, "upperBodyOnly": false },
|
||||
"points": [[0.51, 0.22, -0.03, 0.99], "…共 33 项,顺序固定 [x, y, z, conf]"]
|
||||
"points": [[0.51, 0.22, -0.03, 0.99], "...共 33 项,顺序固定 [x, y, z, conf]"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ default_branch: main
|
||||
engine: Godot 4.7.2
|
||||
contracts_version: 0.1.0
|
||||
|
||||
# 契约变更流程(文档 §7.2):
|
||||
# 契约变更流程(文档 7.2):
|
||||
# 任何跨端契约变更必须先在 opendance-web 修改 contracts/ 并提升版本,
|
||||
# 再在本项目创建适配任务。本项目内的 contracts/ 只是只读快照。
|
||||
|
||||
@@ -42,7 +42,7 @@ tasks:
|
||||
status: done
|
||||
- id: T-WIN-006
|
||||
file: tasks/006-skeleton-retarget.md
|
||||
scope: 关键点 → VRM 骨骼重定向
|
||||
scope: 关键点 -> VRM 骨骼重定向
|
||||
status: done
|
||||
- id: T-WIN-007
|
||||
file: tasks/007-scoring-dtw.md
|
||||
|
||||
@@ -20,9 +20,9 @@ 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` | 目录缓存 + 模型下载缓存 + 关键点序列缓存 |
|
||||
@@ -32,8 +32,8 @@ scenes/main.tscn
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
GODOT --headless --path . --import # 无 SCRIPT ERROR
|
||||
GODOT --headless --path . --quit-after 180 # 应用可启动,UI 构建无报错
|
||||
GODOT --headless --path. --import # 无 SCRIPT ERROR
|
||||
GODOT --headless --path. --quit-after 180 # 应用可启动,UI 构建无报错
|
||||
```
|
||||
|
||||
## 结果
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
```
|
||||
DSH.md
|
||||
contracts/rest-api.yaml # 锚点:#/paths/~1api~1catalog
|
||||
contracts/data-models.md # 锚点:§2 Song、§3 Motion、§4 Model、§6 CatalogBundle
|
||||
contracts/data-models.md # 锚点:2 Song、3 Motion、4 Model、6 CatalogBundle
|
||||
src/net/api_client.gd
|
||||
src/net/catalog_types.gd
|
||||
src/autoload/catalog_store.gd
|
||||
@@ -21,10 +21,10 @@ src/autoload/catalog_store.gd
|
||||
## 交付物
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| `src/net/api_client.gd` | HTTPRequest 封装;统一返回 `{ok,status,data,error}`,不抛异常 |
|
||||
| `src/net/catalog_types.gd` | Song / Motion / Model / CatalogBundle / SourceInfo 类型化解析 |
|
||||
| `src/net/keypoint_sequence.gd` | NDJSON 关键点序列 → 特征向量序列 |
|
||||
| `src/net/keypoint_sequence.gd` | NDJSON 关键点序列 -> 特征向量序列 |
|
||||
| `src/autoload/catalog_store.gd` | 目录刷新、模型文件下载缓存、序列缓存 |
|
||||
|
||||
## 关键实现
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/websocket-keypoints.md # 锚点:§1 端点与子协议、§3 文本控制帧
|
||||
contracts/websocket-keypoints.md # 锚点:1 端点与子协议、3 文本控制帧
|
||||
src/net/pose_server.gd
|
||||
src/autoload/pose_receiver.gd
|
||||
```
|
||||
@@ -24,9 +24,9 @@ src/autoload/pose_receiver.gd
|
||||
## 能力
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 子协议 | 协商 `opendance.pose.v1`,客户端选择不符时告警 |
|
||||
| 控制帧 | `hello` → 回 `welcome`(含 sessionId);`bye` → 关闭 |
|
||||
| 控制帧 | `hello` -> 回 `welcome`(含 sessionId);`bye` -> 关闭 |
|
||||
| 协议不匹配 | 回 `bye{reason: protocol_mismatch}` 并以 1002 关闭 |
|
||||
| 二进制帧 | 严格校验 239 字节与 magic,畸形帧计数但不影响连接 |
|
||||
| 丢包统计 | 按 `frame_id` 跳号计数(含 u32 回绕保护) |
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/websocket-keypoints.md # 锚点:§2 二进制帧(冻结)
|
||||
contracts/skeleton-topology.md # 锚点:§4 坐标系
|
||||
contracts/websocket-keypoints.md # 锚点:2 二进制帧(冻结)
|
||||
contracts/skeleton-topology.md # 锚点:4 坐标系
|
||||
src/net/keypoint_frame.gd
|
||||
```
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/model-formats.md # 锚点:§1 角色模型
|
||||
contracts/data-models.md # 锚点:§4.1 VRM 优先规则
|
||||
contracts/model-formats.md # 锚点:1 角色模型
|
||||
contracts/data-models.md # 锚点:4.1 VRM 优先规则
|
||||
src/model/vrm_loader.gd
|
||||
src/model/humanoid_map.gd
|
||||
```
|
||||
@@ -29,7 +29,7 @@ src/model/humanoid_map.gd
|
||||
`HumanoidMap` 覆盖 VRoid / VRM 0.x / Mixamo 三类命名,归一化后精确比较:
|
||||
|
||||
- 剥离前缀 `jbip` / `mixamorig` / `bip01`(**归一化之后**的形态)
|
||||
- 生成 compact(left→l)与 expanded(l→left)两种变体比较
|
||||
- 生成 compact(left->l)与 expanded(l->left)两种变体比较
|
||||
- **故意不剥离 `J_Aim_` / `J_Roll_` / `J_Sec_`**:VRoid 用它们标记扭转辅助骨,
|
||||
剥离后 `J_Aim_L_UpperLeg` 会与真正的 `J_Bip_L_UpperLeg` 撞名
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# T-WIN-006 关键点 → VRM 骨骼重定向
|
||||
# T-WIN-006 关键点 -> VRM 骨骼重定向
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-005、T-WIN-004
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/skeleton-topology.md # 锚点:§1 33 点、§2 虚拟关节、§4 坐标系
|
||||
contracts/skeleton-topology.md # 锚点:1 33 点、2 虚拟关节、4 坐标系
|
||||
src/anim/skeleton_retarget.gd
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ src/anim/skeleton_retarget.gd
|
||||
1. 每根骨骼指定一条骨链(起点关键点,终点关键点)
|
||||
2. 取骨骼静止朝向 `rest_dir`(由该骨与首个子骨的位置差得出,**不假设模型骨骼轴向**)
|
||||
3. 目标朝向由两个关键点在世界空间的方向得出
|
||||
4. `delta` = rest_dir → target_dir 的最小旋转四元数(保留扭转/roll)
|
||||
4. `delta` = rest_dir -> target_dir 的最小旋转四元数(保留扭转/roll)
|
||||
5. `desired = Basis(delta) × rest_global_basis`
|
||||
6. `pose_local = parent_global_basis⁻¹ × desired`
|
||||
|
||||
@@ -30,15 +30,15 @@ src/anim/skeleton_retarget.gd
|
||||
|
||||
## 坐标转换
|
||||
|
||||
契约 §4 的 MediaPipe 归一化坐标 → Godot 世界方向:
|
||||
契约 4 的 MediaPipe 归一化坐标 -> Godot 世界方向:
|
||||
`Vector3(x × aspect, −y, −z × aspect)`(x/z 以图像宽为基准故乘宽高比)。
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_retarget.gd` —— 最关键的是**功能性**断言:
|
||||
`tests/test_retarget.gd` -- 最关键的是**功能性**断言:
|
||||
|
||||
```
|
||||
→ 左上臂方向点积 1.0000,左前臂方向点积 1.0000
|
||||
-> 左上臂方向点积 1.0000,左前臂方向点积 1.0000
|
||||
```
|
||||
|
||||
即:给定一组关键点,重定向后骨骼在世界空间的**实际指向**与关键点方向一致;
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/skeleton-topology.md # 锚点:§5 派生特征
|
||||
contracts/model-formats.md # 锚点:§2.1 预处理产物
|
||||
contracts/skeleton-topology.md # 锚点:5 派生特征
|
||||
contracts/model-formats.md # 锚点:2.1 预处理产物
|
||||
src/score/pose_features.gd
|
||||
src/score/dtw.gd
|
||||
src/score/scoring.gd
|
||||
```
|
||||
|
||||
## 特征(契约 §5 冻结)
|
||||
## 特征(契约 5 冻结)
|
||||
|
||||
每帧 = `[33 × (x,y,z), 9 个关节角余弦]` = **108 维**。
|
||||
低置信度点(conf < 0.5)对应维度**权重置 0**,不参与代价计算。
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## 目标
|
||||
|
||||
三页式体验:主菜单(选曲/选动作/选模型)→ 游戏页(3D 角色 + 实时评分 HUD)→ 结算页。
|
||||
三页式体验:主菜单(选曲/选动作/选模型)-> 游戏页(3D 角色 + 实时评分 HUD)-> 结算页。
|
||||
|
||||
## load
|
||||
|
||||
@@ -21,14 +21,14 @@ src/ui/ui_kit.gd
|
||||
## 页面
|
||||
|
||||
| 页面 | 能力 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| 主菜单 | 三栏选择(曲目 / 可评分动作 / 模型)、服务端地址编辑与测试、目录刷新、接收服务状态与重启 |
|
||||
| 游戏页 | SubViewport 3D 角色、自动定机位、实时分数/等级/连击、进度条、连接状态提示、来源展示 |
|
||||
| 结算 | 等级大字、平均/最终分、完成度、最高单帧、最长连击、评语、来源展示 |
|
||||
|
||||
## 关键实现
|
||||
|
||||
- 界面全部代码构建(见 DSH.md §6),主菜单只列出 `preprocessState == "done"` 的动作
|
||||
- 界面全部代码构建(见 DSH.md 6),主菜单只列出 `preprocessState == "done"` 的动作
|
||||
- 游戏页订阅 `PoseReceiver.frame_received` 驱动骨骼,`ScoreService` 独立订阅做评分
|
||||
- 未连接设备时给出明确提示而非静默失败;模型/序列拉取失败时显示可读原因并允许重试
|
||||
- 参考序列走完自动结算
|
||||
@@ -36,5 +36,5 @@ src/ui/ui_kit.gd
|
||||
|
||||
## 验收
|
||||
|
||||
`GODOT --headless --path . --quit-after 180` 启动无运行时错误;
|
||||
`GODOT --headless --path. --quit-after 180` 启动无运行时错误;
|
||||
主菜单、游戏页、结算页的构造路径均被该启动流程覆盖。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/data-models.md # 锚点:§1 来源五元组
|
||||
contracts/data-models.md # 锚点:1 来源五元组
|
||||
src/ui/source_panel.gd
|
||||
src/net/catalog_types.gd
|
||||
```
|
||||
|
||||
@@ -19,9 +19,9 @@ export_presets.cfg
|
||||
## 交付物
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
|--|--|
|
||||
| `export_presets.cfg` | 导出预设(**必须在项目根目录**,Godot 硬性要求) |
|
||||
| `build/export-windows.sh` | Linux 导出:定位 Godot → 检查模板 → 导入 → 导出 → **校验 PE 头** |
|
||||
| `build/export-windows.sh` | Linux 导出:定位 Godot -> 检查模板 -> 导入 -> 导出 -> **校验 PE 头** |
|
||||
| `build/export-windows.ps1` | Windows 兜底:同上,产物路径与校验逻辑一致 |
|
||||
|
||||
## 脚本健壮性
|
||||
@@ -31,10 +31,10 @@ export_presets.cfg
|
||||
- 导出命令返回非 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,未实机执行 |
|
||||
| 生成的 exe 在 Windows 10/11 可运行 | ⚠️ 本环境无 Windows;已校验为合法 PE32+ x86_64 可执行文件 |
|
||||
|
||||
Reference in New Issue
Block a user