跳转至

文件格式

两种 OTS 文件都是纯粹的 UTF-8 JSON,带缩进、可读。你可以用文本编辑器打开一个,在版本控制里对比两个,或者写脚本去读它。这是刻意的 — 一个你无法检视的交换格式,就是一个你无法调试的交换格式。

当前格式是 version 4

共享的文件头

两种格式的开头是一样的:

{
  "format": "octo_anm",
  "version": 4,
  "source": "harmony",
  "fps": 24.0,
  "coordinate_system": {
    "up": "y",
    "hand": "right",
    "unit": "meters",
    "rot_order": "xyz",
    "rot_repr_anim": "euler_deg",
    "rot_repr_rest": "quat_wxyz"
  }
}
含义
format octo_pvtocto_anm
version 格式版本。必须精确匹配 — 见下文
source 是哪个应用写的:harmonyblendermaya
fps 源场景的帧率
coordinate_system 这些数值所处的空间 — 参见 坐标系

没有向后迁移

读取方 接受它自己的版本。把一个 v3 文件加载进 v4 构建版本会报告:

unsupported version 3 (this build writes/reads v4; no backward migration — re-export from the source DCC)

这是一个选择,不是疏忽。悄悄迁移一个旧文件,意味着去猜它那些数字当初是什么意思,而猜错会产生一个"细微地错着"的 rig,而不是一个明显坏掉的 rig。从源头重新导出只要几秒钟,而且永远是对的。


.pvt — pivot 文件

{
  "format": "octo_pvt",
  "version": 4,
  "source": "harmony",
  "name": "spaceship",
  "scale_factor": 1.0,
  "pivot_space": "local",
  "fps": 24.0,
  "coordinate_system": { "…": "…" },
  "bones": [
    {
      "name": "hip",
      "parent": null,
      "pivot": [0.0, 0.0, 0.0],
      "rotation": [1.0, 0.0, 0.0, 0.0],
      "path": "Armature/hip",
      "rig": "spaceship",
      "is_camera": false
    }
  ],
  "cameras": []
}
含义
name 这个 rig 的名字
scale_factor 面向用户的 Scale 选项。Pivot 在写入时乘以它,读取时除以它
pivot_space local(相对于 parent)或 armature
bones[].pivot 静置位置
bones[].rotation 静置旋转,形式为 quaternion [w, x, y, z]
bones[].path 该项在其源场景中的完整路径 — 用于匹配,以及让不同 rig 中的同名项保持区分
bones[].rig Rig 标签

pathrigis_camera 在为空时会被省略,所以一个简单的文件依然很小。


.anm — 动画文件

{
  "format": "octo_anm",
  "version": 4,
  "source": "maya",
  "frame_range": [1, 60],
  "fps": 24.0,
  "coordinate_system": { "…": "…" },
  "pegs": [
    {
      "name": "hip-P",
      "path": "Top/spaceship/hip-P",
      "parent": "root",
      "rest_pivot": [0.0, 0.0, 0.0],
      "rest_rotation": [1.0, 0.0, 0.0, 0.0],
      "rig": "spaceship",
      "is_camera": false,
      "source_modes": {
        "position": "separate",
        "rotation": "euler",
        "scale": "separate",
        "enable_3d": true
      },
      "keyframes": [
        {
          "frame": 1,
          "pos": [0.0, 0.0, 0.0],
          "rot": [0.0, 0.0, 0.0],
          "rot_quat": [1.0, 0.0, 0.0, 0.0],
          "scl": [1.0, 1.0, 1.0],
          "interp": "BEZIER",
          "handles": { "pos": [], "rot": [], "scl": [] }
        }
      ]
    }
  ],
  "cameras": []
}

自给自足的那几个字段

parentrest_pivotrest_rotationrig 就是折叠进每个 peg 里的 .pvt 内容。它们正是 Create Rig 能够只凭一个 .anm 就构建出可用骨架的原因。参见 动画文件 (.anm)

Keyframe

含义
frame 帧号
pos 位置,规范的米
rot Euler XYZ, — 人类可读的那种形式
rot_quat 同一个旋转的 quaternion 形式 [w, x, y, z],来自 world matrix bake
scl 缩放
interp BEZIERLINEARCONSTANT
handles 绝对的 bezier handle,逐轴,按通道分组

rot_quat 胜出

当两者都存在时,rot_quat 是权威的,每个导入方都优先用它。它与顺序无关,因此彻底绕开了 Euler 顺序的歧义。rot 则作为可读的备选保留。

Maya 和 Blender 的 bake 不会在 rot_quat 旁边输出 handle,因为局部坐标系的 handle 无法映射到一条烘焙出的 world 曲线上。插值类型仍然会传递。

Handle 是 (frame, value) 空间中绝对的 [[left_x, left_y], [right_x, right_y]] 数对 — 每个轴一对,分组进 posrotscl。之所以是绝对值而不是相对值,是为了在读入时不必再对着某个帧率重新推导一遍。

source_modes

一份关于这个 peg 在其源应用中是怎么创作的记录 — 位置是分离的还是基于 path、旋转是 Euler 还是 quaternion-path,以及 3D 有没有启用。

它是一条提示,而且仅仅是一条提示

source_modes 从不影响任何数值。它是给读文件的人看的诊断信息。导入方不会因为它而改变自己写曲线的方式。


摄像机

两种格式携带相同的摄像机块,通过名称与驱动它的那一项关联:

含义
namepegpath 身份,以及是哪一项在驱动它
is_default 这是不是场景的默认摄像机
fovfov_axis 视野 — 垂直,与 Harmony 的惯例一致
override_fov Harmony 的逐摄像机 FOV 覆盖标记
near_planefar_plane Clip plane,以规范距离表示
near_fieldfar_field Harmony 原本以 field 为单位的 clip 值,保留下来以便 Harmony 往返无损
angle Roll,绕 Z 的度数
offset_x/y/zpivot_x/y 相对于 peg 的摆放
rig Rig 标签

参见 摄像机


实用说明

文件很小。 一个完整角色 rig 加一个 60 帧的镜头是几百 KB。它们完全可以用邮件发送、放进版本控制,或者作为记录与镜头存放在一起。

它们可以 diff。 同一个 rig 的两次导出会产生可比较的 JSON,这让"这两个版本之间改了什么"变成一次文本 diff,而不是靠猜。

命名随你。 OTS 不在乎文件叫什么;识别它的是里面的 format 键。保留 .pvt / .anm 扩展名只是为了让文件选择器表现正常。