docs: 消除 Gitea 模棱两可 Unicode 告警(全角标点紧邻 ASCII 时改为半角)
依据 Gitea v1.27.3 modules/charset/escape_stream.go 的判定规则 (analyzeDetectResults:仅当左右相邻字符都不是非 ASCII 时才标记, 且 _common 表恒定生效),把说明文档中会被标记的全角标点 (,:;?!() 等)改为半角,并保证成对括号宽度一致。 中文语境中的全角标点(左右均为中文)保持原样,不动代码与契约语义。 同时删除临时探针目录 _probe/(仅用于确认 Gitea 判定规则)。
This commit is contained in:
@@ -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,连接后立即发送
|
||||
|
||||
|
||||
Reference in New Issue
Block a user