【MMD】PMX文件格式解析

一、前言

  PMX是Polygon Model eXtended的简写,是动画软件MikuMikuDance中的模型文件,是.PMD格式(Polygon Model Data)的继承者。
  上次解析的是MMD中的动作文件VMD,相比于VMD,模型文件需要了解的东西更多,为此我还简单学了下建模工具Blender。
  同样的,这次也有参考的网站。

国外Github上,看样子是老外翻译日文到英语的文档
https://gist.github.com/felixjones/f8a06bd48f9da9a4539f
日文Wiki
https://www6.atwiki.jp/vpvpwiki/pages/284.html
参考PmxEditor说明
【PE教学】超超超初心者向PE入门Part1 - 基本原理
古剑二MMD&绑骨教程
mmd教程/pe教程-oeasy
VPVP:https://www6.atwiki.jp/vpvpwiki/pages/118.html

借物表:

名称
MikuMikuDance十周年汉化版
PmxEditor_0254e
八重樱、q版樱火轮舞、女仆丽塔-洛丝薇瑟2.0-神帝宇改模
MMD-ray渲染

使用工具

  相较之前用MMD演示动作数据构成,PMX文件的构成用PmxEditor研究更好,Pmx编辑窗口很好的把文件结构展示出来。


二、PMX文件解析

1.类型说明

  相对于上次VMD的解析,PMX的类型更为复杂,用简单的C数据类型解释很麻烦,因此向国外的文档学习,将其中某些元素定义为一些类型。可以先大致看一眼。

(1)C类型

  C语言风格基础类型,复杂类型也是由许多基础类型定义。

类型名称 C类型 标准类型 大小(字节) 范围
byte char uint8_t 1 [-128, 127]
ubyte unsigned char uint8_t 1 [0, 255]
short short int16_t 2 [-2^{15}, 2^{15}-1]
ushort unsigned short uint16_t 2 [0, 2^{16}-1]
int int int32_t 4 [-2^{31}, 2^{31}-1]
uint unsigned int uint32_t 4 [0, 2^{32}-1]
float float float 4 IEEE754标准定义float范围

(2)PMX类型

  PMX格式所定义类型,不过这些类型在大多数图形库中都有定义

类型名称 类型说明 类型结构 大小(字节)
vec2 XY 向量 float, float 8
vec3 XYZ 向量 float, float, float 12
vec4 XYZW 向量 float, float, float, float 16
text 前四个字节说明字符串长度,编码定义
在后面的Globas全局定义中
int, byte[] 4+字符长度
flag 标志类型,每个Byte可含有8个标志
0为false,1为true
byte 1
index 索引类型,每个要素的索引不同,
后面详细说明
byte/ubyte/short/ushort/int 1/2/4

(3)索引类型

  有时候我们需要记住元素的索引,例如某个面是由哪几个点组成的,而表示一个点,就需要索引。
  索引的大小不是确定的,有些元素需要很多索引,因此索引类型也很大;而有些元素很少,索引节点的大小很小。
  同样的元素,例如顶点,有时一个模型只有几个顶点,有的有几万个顶点,因此同样是节点的索引,大小也可能不一样。PMX将索引类型分为3种:Type1、Type2、Type4,分别占有1、2、4个字节,模型的索引的大小储存在Globas全局定义中,后面会提到。

类型名称 说明 类型1 类型2 类型4 类型1最大值 类型2最大值 类型4最大值 空值
Vertex 顶点 ubyte ushort int 255 65535 2147483647 N/A
Bone 骨骼 byte short int 127 32767 2147483647 -1
Texture 纹理 byte short int 127 32767 2147483647 -1
Material 材质 byte short int 127 32767 2147483647 -1
Morph 变形(表情) byte short int 127 32767 2147483647 -1
Rigidbody 刚体 byte short int 127 32767 2147483647 -1

2.文件总体结构概览

  和VMD文件类似,都是全局信息-[元素总数-元素信息]。

Header 版本 说明
Model information Globals 2.0 模型全局信息
Vertex count Vertices 2.0 顶点
Surface count Surfaces 2.0
Texture count Textures 2.0 纹理
Material count Materials 2.0 材质
Bone count Bones 2.0 骨骼
Morph count Morphs 2.0 变形(表情)
Displayframe count Displayframes 2.0 表示枠(huà)
Rigidbody count Rigidbodies 2.0 刚体
Joint count Joints 2.0 关节点
SoftBody count SoftBodies 2.0 软体

  软体应该是作为Pmx独有的,很明显的,PmdEditor没有软体按钮。
  下面是真正开始解析文件每一部分格式。


3.头部、全局信息

Header


  模型头部,描述模型的基本信息

数据含义 结构 说明
签名 byte[4] "PMX " [0x50, 0x4D, 0x58, 0x20] 值为"PMX空格"
版本号 float 2.0、2.1
全局信息数量 byte 8(PMX2.0) PMX2.0有8条全局信息
全局信息 byte[全局信息数量] 用于描述各索引的数量 见后面的全局信息说明表
本地模型名 text 本地语言模型名,例如中文、日文
通用模型名 text 英文名
本地语言模型描述 text 打开MMD弹出的注意事项,本地语言
通用语言模型描述 text 英文注意事项

全局信息


用于描述索引类型的类型,就是上面索引类型的[1, 2, 4]

位置 含义 说明
0 字符串编码 {0, 1} 0为UTF16LE编码, 1为UTF8编码
1 额外的vec4数量 {0, 1, 2, 3, 4} 在这里可以定义为每个顶点增加vec4的数量,详见后面的顶点说明
2 顶点索引大小 {1, 2, 4} 这个大小用于表示索引类型的类型
3 纹理索引大小 {1, 2, 4} 也可以理解为所占的字节有多少
4 材质索引大小 {1, 2, 4} 间接能知道模型的该元素数量大致为多少
5 骨骼索引大小 {1, 2, 4}
6 变形索引大小 {1, 2, 4}
7 刚体索引大小 {1, 2, 4}

4.顶点

  首先我们打开PmxEditor并读入一个模型,然后进入编辑器的顶点页面。因为顶点有复杂的数据,这次同样先说明一些其他的东西。如果感觉迷惑了不要紧,对照着顶点页面研究就能很好的明白各处的含义。


额外的vec4

  方才在全局定义中,有一个额外的vec4选项,定义为每个顶点多出几个vec4,每个顶点可以最多获得4个额外的vec4值,文件文身也不知道这是什么含义,多数情况会直接传递给vertex shader决定使用情况。例如,vec4用于以下方面:

  • 额外的纹理UV
  • 高光贴图UV
    详细参考这个教程
      右侧的贴图就是左侧的高光贴图,因为黑色部分的RGB值为0,不会参与高光的计算(参与了但值为0),而金属边框会参与计算,做出的结果是:木头部分不会反射高光,而金属部分会出现高光
  • 法线贴图UV
      通过改变法向量,使得平面图计算出阴暗交替的感觉。
  • 顶点颜色(PMX2.1拓展)

  忽略这些额外的vec4是最安全的,但同时也会导致很多东西无法显示。


这里就是追加UV选项的地方

顶点绑骨

  顶点要随着骨骼运动、变形,每个骨骼对顶点有相应的权重,在MMD中,一个顶点最多和4个骨骼相关联;随着绑定骨骼数量的不同,顶点绑骨的类型也不同。以下是绑骨类型:

无绑骨

  骨骼索引-1是零值,骨骼应该被忽略。

BDEF 1(Bone Deform)(2.0)
类型 名称 说明
索引-骨骼 骨骼1 权重==1

注意:索引类型(index)对每个元素不同,骨骼索引的定义看上面的索引类型说明。

BDEF 2(2.0)
类型 名称 说明
索引-骨骼 骨骼1
索引-骨骼 骨骼2
foat 骨骼1权重 骨骼2权重 = 1-骨骼1权重
BDEF 4(2.0)
类型 名称 说明
索引-骨骼 骨骼1
索引-骨骼 骨骼2
索引-骨骼 骨骼3
索引-骨骼 骨骼4
foat 骨骼1权重
foat 骨骼2权重
foat 骨骼3权重
foat 骨骼4权重 \sum_{i=1}^4Bone_i不保证=1
SDEF(2.0)

  一种SBS(Spherical Blend Skinning)球面混合蒙皮方式,能使皮肤的扭动更自然。

类型 名称 说明
索引-骨骼 骨骼1
索引-骨骼 骨骼2
foat 骨骼1权重 骨骼2权重 = 1-骨骼1权重
vec3 C
vec3 R_0
vec3 R_1
QDEF(2.1)

  双四元数变型混合

类型 名称 说明
索引-骨骼 骨骼1
索引-骨骼 骨骼2
索引-骨骼 骨骼3
索引-骨骼 骨骼4
foat 骨骼1权重
foat 骨骼2权重
foat 骨骼3权重
foat 骨骼4权重 \sum_{i=1}^4Bone_i不保证=1

顶点数据

  顶点部分以一个int开始,定义了有多少个顶点(注意它是一个带符号的int),后面跟着每个顶点的定义
每个顶点的格式如下:

含义 类型 说明
位置 vec3 XYZ
法向量 vec3 XYZ
UV坐标 vec2 XY
额外的vec4 vec4[N] N的大小见上面的全局定义
变型权重类型 byte 0=BDEF1, 1=BDEF2, 2=BDEF4, 3=SDEF, 4=QDEF
变型权重 顶点绑骨类型 见上面的顶点绑骨结构
边缘放大率 float [0, 1]

5.面

  面以三个顶点为一组,定义单个三角形面;三角形面向按顺时针缠绕顺序定义,顺时针为正面,逆时针为背面(与DirectX一样)。
  同样别忘了先读取一个int来确定有多少个面,不过要注意,面的定义是按照索引数量定义的,因此要将这个数字整除3。

含义 类型 说明
索引-顶点数组 index-vertex[3] 参考上面定义的索引类型
附加面类型(PMX2.1)

  材质数据在2.1版本有扩充,能包含点和线的绘制,详细需要查阅资料。


6.纹理

  此处是纹理路径表,路径本身是字符串,它的格式和操作系统相关,可能是绝对路径也可能是相对路径。
  某些实现程序会基于文件名修改纹理,例如使用"_s"作为球体贴图或使用"_n"作为法线贴图。
  纹理贴图没有首选格式,最常见个纹理格式可能是bmp、png、tga,有时候也可能是jpg或dds格式。

保留的纹理名称

纹理名称toon01.bmp"到"toon10.bmp"是保留的,模型不应该通过纹理数据引用这些纹理。如果不巧使用了,会发生什么取决于实现程序,某些实现将忽略这些,某些实现会使用内部的纹理。
  同样,纹理数据以一个int开头,定义总共有多少纹理。

含义 类型 说明
路径 text 纹理路径通常是相对的

  PmxEditor编辑没有独立的纹理页面,但在材质页面能看到材质引用的纹理。


7.材质

  PMX用基于材质的渲染,而不是基于物理的渲染,
鉴于后面的材质数据会使用标志位,先放出材质的标志类型定义(1byte=8flag):

位置 含义 效果 版本
0 no-cull 禁用背面剔除 双面描绘 2.0
1 Ground shadow 地面阴影 将阴影投射到几何体上 2.0
2 Draw shadow 描绘阴影 渲染到阴影贴图(本影标示) 2.0
3 Receive shadow 受到阴影渲染 从阴影贴图接受阴影(本影) 2.0
4 Has edge 有边缘 有边缘描绘(轮廓线有效) 2.0
5 Vertex colour 顶点颜色 使用额外的vec4作为顶点的颜色 2.1
6 Point drawing 画点 三个顶点都被画出 2.1
7 Line drawing 画线 三个边被画出 2.1
点线绘图(PMX2.1)

  如果绘点和绘线标志同时存在,绘点标志将覆盖绘线标志。

  • 普通面通过顶点 [A -> B -> C]渲染
  • 通过点绘制,每个顶点被渲染为单独的点[A,B,C],从而产生3个点。
  • 线条图将呈现3条线[A-> B,B-> C,C-> A],产生3条线。

  启用点渲染时,材质(不是顶点)的边尺度值将控制点的大小。
  如果设置了边缘标志,则点或线将具有边缘,这是未定义的行为。

材质数据

  与之前相同,材质也有一个int记录材质的数量,材质的数据结构定义如下:

含义 类型 说明
本地材质名称 text 本地的材质名称,日语、中文等等
通用材质名称 text 通用的材质名称,一般是英文
漫反射颜色Diffuse vec4 RGBA
镜面光(高光)颜色Specular vec3 RGB
镜面光强度Specular strength float 镜面高光的大小
环境色ambient vec3 当光线不足时的阴影色(既基础色,让阴影不那么黑)
绘制标记 flag 见上面的材质标志
边缘颜色 vec4 RGBA
边缘比例 float [0, 1]
索引-纹理 index-texture 参考索引类型
环境(高光贴图)索引 index-texture 与纹理索引相同,但用于环境映射
环境(高光贴图)混合模式 byte 0=禁用, 1=乘, 2=加, 3=额外的vec4[注1]
贴图引用 byte 0=引用纹理, 1=内部引用
贴图值 索引-纹理/byte 取决于贴图引用[注2]
元数据 text 用于脚本或其他数据
面数量 int 表示当前材质影响了多少个面[注3]

注1:环境混合模式3将使用第一个额外的vec4来映射环境纹理,仅使用X和Y值作为纹理UV。它被映射为附加纹理图层。这可能与第一个额外vec4的其他用途冲突。
注2:Toon值将是一个非常类似于标准纹理和环境纹理索引的纹理索引,除非Toon引用字节等于1,在这种情况下,Toon值将是一个引用一组10个内部toon纹理的字节(大多数实现将使用“toon01.bmp”到“toon10.bmp”作为内部纹理,请参阅上面纹理的保留名称。
注3:表面计数总是3的倍数。它基于先前材质的偏移量与当前材质的尺寸。如果将所有材质的所有表面计数相加,则应最终得到表面总数。
人话3:图上的“脸红”为第一个材质,有81个面,[0, 81)面都属于本材质,而“首”由38个面组成,索引[81, 81+38]都属于“首”材质,以此类推,这样将材质、纹理、面组成的网格,组成面的顶点都结合起来了。


  环境色中,扩散色其实是diffuse漫反射,而反射色是specular高光反射色,反射强度是指光泽(Shininess),反射光强度是根据出射光与人眼矢量夹角的cos值确定的(处于0到1之间),shininess会将得到的数字再进行平方选择,得到的值为specular^{(2^{shininess})},比如shininess为5,则要进行32次的pow。
  因此反射强度越小,光泽就越大。不过在MME自发光特效中,反射强度大概是不光代表光泽,同时代表光强,往往模型的反射强度值会设为120左右,这样物体的光泽很小,但光强特别大,看起来很闪耀。


8.骨骼

  用于骨骼动画。

骨骼标志

位置 含义 效果 版本
0 骨骼尾部(尖端)位置 0为连接相对位置,1为连接子骨骼 2.0
1 可旋转 启用旋转 2.0
2 可移动 启用移动 2.0
3 可见 显示 2.0
4 启用 操作 2.0
5 IK 是IK骨 2.0
8 付予旋转 继承亲骨的旋转 2.0
9 付予移动 继承亲骨的移动 2.0
10 固定轴 轴限制 2.0
11 本地轴 Local轴 2.0
12 后算物理 先变形 2.0
13 外部亲骨骼变形 外部亲 2.0

  变形阶层在这里有讨论,大致意思是:变形阶层的作用是物理的计算顺序,值越大越靠后计算,一般IK骨的值才是1,其他是0,而原文提到TDA式MIKU的眼睛是2,用于特殊的需求,具体可参照原文。
  (继承)付予旋转和移动的原意好像不单单是继承旋转和移动的作用,毕竟没有IK的骨骼都会跟着亲骨走,我发现有付予旋转的骨骼多是隐藏骨骼,而作用大概是可以增加或减少亲骨对当前骨骼的影响,应用可以看这里: 【MMD教程相关】不追加捩骨的情况下解决模型手肘旋转时的扭曲

骨骼继承

名称 类型 作用
亲骨索引 索引-骨骼 参阅骨骼索引类型
影响权重 float 亲骨的影响力

  假如上图的付予栏位中的旋转、移动有一个被选中,这个类型有效

骨骼固定轴

名称 类型 作用
轴方向 vec3 骨骼指向的方向

  这个用在手臂中那个圆形打着叉的捩(liè)骨中:

骨骼Local坐标

名称 类型 作用
X矢量 vec3
Z矢量 vec3

  PmxEditor最下面的local轴选项,这个见于左腕、右腕(左右胳膊)、以及往下延伸的子骨骼中,其他用法可以看看这个
  MMD左下角有操作手柄:

  GLOBAL是可以点击的,会变成LOCAL。
  默认情况(模型选项Local轴无效),LOCAL轴依旧可以使用,不旋转模型的情况下,两个坐标没有区别,如果将模型旋转,GLOBAL轴会根据世界坐标旋转,而LOCAL轴会根据模型坐标旋转。
  当Local轴选项有效时,GLOBAL模式没有改变,而LOCAL的坐标会根据骨骼自身坐标系进行旋转:
Global,Local无效,Local有效

  这个骨骼坐标系的定义就是由三个基向量(上图左3)确定。
  这个设定不会改变骨骼动画的行为;解释一下,我们现在已知坐标系有三种:世界坐标系、模型坐标系、骨骼坐标系,为了区分,如上图绕世界坐标系Y轴旋转一个角度做对比。
  如果我们用Global手柄轴做旋转,永远是在世界坐标系下旋转,此时你拖动X轴手柄,会发现XYZ轴一起被改变。
  如果用Local轴旋转,需要看骨骼本身是否有骨骼坐标系(既上边的Local轴选项是否有效),如果像手臂等有效骨骼,旋转Local一样会发生XYZ轴同时改变,但如果无效,就不会发生,可以确定,这个轴旋转是依照模型坐标系来确定的。
  Local轴只会影响用户对模型的部分旋转操作,而动作数据记录的是模型坐标系,因此这里只会影响用户的操作,即使这里随意设定,也不会影响动画的展示。
  通常情况下,LocalX轴会设定为normalize(childBone-parentBone)(和上图不同),LocalZ=cross(LocalX, [0, 0, 1])LocalY=cross(LocalZ, LocalX),这个可以去试试,和相机旋转的计算方式很像。

外亲骨

名称 类型 作用
骨骼索引 索引-骨骼

  MMD即时绑定两个模型,为什么会出现在模型文件中,真是个迷。

IK 角度限制

名称 类型 作用
下限 vec3 最小角度(弧度制)
上限 vec3 最大角度(弧度制)

IK链

名称 类型 作用
骨骼索引 索引-骨骼 参阅索引类型
角度限制 byte 值为1时,使用角度限制
IK 角度限制 ~ 如果角度限制值为1,那么此处参照上面的IK 角度限制

~是参考其他类型,也可能没有的意思。

IK骨

名称 类型 作用
目标骨骼索引 索引-骨骼 参阅索引类型
循环计数 int IK解算 CCD(循环坐标下降法)循环次数
限制角度 float IK骨旋转角度限制
IK链计数 int IK链接的骨骼数量
IK链接 IK链[N] N是链接数,可以为0,类型参阅上面的IK链类型

  这里IK骨的数据看起来很迷

  IK是反向动力学骨骼,可以根据骨骼自身状态,反向影响亲骨,而非其他骨骼那样由亲骨带动子骨。
  图中可见到足IK有三处连接,分别非亲骨+IK链的上方,指向相对位置的左方,以及作为IK目标且不可见的右足先IK连接在右下方。
  原文中没有给出单位角和loop的作用,我在谷歌查了下:这里,文章表示,Loop和模型本身无关,而是MMD内部计算某个最佳方案的算法有关,如果为0可能导致IK不会工作,因此很多模型都直接给个40作为值,角度也是这样,并且如果不给单位角值,它会自动生成一个能适应性良好的值。
(更新:IK解算用了循环坐标下降法,Loop是指循环次数,而角度限制是应对特殊的关节,如膝盖只让模型坐标系下的X轴旋转,并且关节角度不得大于180度,相对来说头发如果有IK,限制会小很多)
  上图的那个单位角114.5916,在文件中存储是2.0,也就是弧度制:。

骨骼数据

  骨骼数据以有符号int开头,定义了有多少个骨骼

名称 类型 说明
骨骼本地名称 text 通常是日语
骨骼通用名称 text 一般是英文
位置 vec3
亲骨索引 索引-骨骼 特殊的:操作中心的亲骨为-1
变形阶层 int 计算物理的顺序
标志 flag[2] 见Bone标志
尾部位置 vec3/索引-骨骼 见上面的材质标志
骨骼继承 ~ 如果Flag中此处为True,就参阅上面的骨骼继承
固定轴 ~ 同上
Local轴 ~ 同上
外部亲 ~ 同上
IK ~ 同上

9.变形

  变形就是我们常说的表情

变型类型

  标识变形的类型,长度只有1byte。
  对于使用者,常认为变型有三种或五种等等,从文件存储来说似乎不止这些:

变型模式 说明 版本
组合 0 Group 2.0
顶点 1 2.0
骨骼 2 2.0
UV 3 2.0
额外UV1 4 2.0
额外UV2 5 2.0
额外UV3 6 2.0
额外UV4 7 2.0
材质 8 2.0
切换 9 Flip 2.1
脉冲 10 2.1

偏移与偏移值

  变形对每一个持有的作用元素(顶点、骨骼、UV这些)称为偏移(Offset),我感觉不是十分准确,不过PmxEditor都是这么翻译的,那就入乡随俗了。
  针对不同的作用元素,会拥有不用的偏移值(偏移量):

组合(Group)
名称 类型 说明
变形索引 索引-变形 参阅索引类型
影响 float 对被索引变形的权重

  PmxEditor中操作方法是:右键表情->归纳到表情组合,或者直接右键->新建制作表情->组合(G)
顶点(Vertex)
名称 类型 说明
顶点索引 索引-顶点 参阅索引类型
移动 vec3 变化的相对位置
骨骼(Bone)
名称 类型 说明
骨骼索引 索引-骨骼 参阅索引类型
移动 vec3 变化的相对位置
移动 vec4 相对旋转四元数
UV(及拓展UV)
名称 类型 说明
顶点索引 索引-顶点 参阅索引类型
~ vec4 做什么取决于UV拓展

  没太读明白,UV的值需要4个,我用的时候只有两个:
材质(Material)
名称 类型 说明
材质索引 索引-材质 参阅索引类型,-1代表所有材质
混合方法 byte 0是乘法,1是加法
漫反射(扩散色) vec4
镜面光(反射色) vec3
镜面光强度 float
环境光(环境色) vec3
边缘颜色 vec4
边缘大小 float
纹理色调 vec4
环境色调 vec4
贴图色调 vec4

不是很清楚环境色调(Environment tint)的意义,毕竟前面材质中光照贴图都给翻译成环境贴图了。

切换(Flip)
名称 类型 说明
变形索引 索引-变形 参阅索引类型
影响 float 对模型的影响

  和分组很像,这里大致有应用。

脉冲(Impulse)
名称 类型 说明
刚体索引 索引-刚体 参阅索引类型
本地标志 byte
移动速度 vec3
转动扭矩 vec3

变形数据

  依旧先以一个有符号的int开始

名称 类型 说明
本地变形名称 text
通用变形名称 text
面板位置 byte {1,2,3,4},表情在MMD面板中处于的位置
变形类型 byte 参阅上面的变形类型说明
偏移量个数 int 元素的个数
偏移量数据 ~[N] N是偏移量个数,可以为0,具体参照上面的偏移量说明

10.表示枠

  一开始看着英文还很蒙(Display Frame),寻思展示帧是什么。表示枠其实就是在MMD中,模型可以注册的物件的分组(只有骨骼和变形,相机、
光照不属于模型),在MMD左侧栏,那些可以展开的就是表示枠:


帧类型

类型 说明 版本
0 索引-骨骼 2.0
1 索引-变形 2.0

骨骼帧数据

名称 类型 说明
骨骼索引 索引-骨骼 参阅索引类型

变形帧数据

名称 类型 说明
变形索引 索引-变形 参阅索引类型

帧数据

名称 类型 说明
帧类型 byte 参阅帧类型
帧数据 ~ 参阅帧数据类型

表示枠数据

  以一个有符号int开始作为表示枠个数

名称 类型 说明
表示枠本地名称 text
表示枠通用名称 text
特殊标识 byte 0表示普通帧,1表示特殊帧
帧数量 int 记录有多少个帧
帧数据 ~[N] N是帧数据个数,类型参照帧数据说明

11.刚体

刚体形状类型

类型 说明 版本
0 2.0
1 盒子 2.0
2 胶囊 2.0

物理模式

名称 类型 说明 版本
0 追踪骨骼 刚体黏在骨骼上 2.0
1 物理演算 刚体使用重力 2.0
2 物理+骨骼 刚体使用重力摆动骨骼 2.0

刚体数据

  刚体部分以一个int开始,用于定义有多少刚体

名称 类型 说明
刚体本地名称 text
刚体通用名称 text
关联骨骼索引 索引-骨骼 参阅索引类型
群组ID byte
非碰撞组 short 非碰撞组的掩码
形状 byte 参阅刚体形状类型
形状大小 vec3 XYZ边界
形状位置 vec3 XYZ位置
形状旋转 vec3 弧度制
质量 float
移动衰减 float
旋转衰减 float
反应力 float
摩擦力 float
物理模式 byte 参阅刚体物理模式

  形状大小固定vec3不变,不过不同的刚体形状,对应的有效字节数不同,球是1字节有效(半径),箱体是3字节(长高宽),胶囊是2字节有效(半径、高)

12.关节点

  关节点(Joint)是用来连接刚体的“钉子”。

关节点类型

类型 说明 版本
0 Spring 6DOF 2.0
1 6DOF 2.1
2 P2P 点结合 2.1
3 ConeTwist 轴旋转 2.1
4 Slider 轴移动 2.1
5 Hinge 轴旋转 2.1

关节点数据

关节点数据以一个有符号的int开头,标识关节点数量

名称 类型 说明
关节点本地名称 text
关节点通用名称 text
关节点类型 byte 参阅关节点类型
刚体索引A 索引-刚体 参阅索引类型
刚体索引B 索引-刚体
位置 vec3
旋转 vec3 弧度制
位置最小值 vec3
位置最大值 vec3
旋转最小值 vec3
旋转最大值 vec3
定位弹簧 vec3 弹力
旋转弹簧 vec3

13.软体

  软体基于Bullet Physics,随PMX 2.1一起推出。
  我暂时还不会物理方面的知识,暂时机翻,这部分等学一学Bullet后再来补全。

形状类型

类型 说明 版本
0 TriMesh 三角网格 2.1
1 Rope 2.1

标识

类型 说明 版本
0 B-Link 2.1
1 Cluster creation 2.1
2 Link crossing 2.1

空气动力学模型

类型 说明 版本
0 V-Point 2.1
1 V-TwoSided 2.1
2 V-OneSided 2.1
3 F-TwoSided 2.1
4 F-OneSided 2.1

锚固刚体

名称 类型 说明
刚体索引 索引-刚体 参照索引类型
顶点索引 索引-顶点
Near mode byte

顶点针脚

名称 类型 说明
顶点索引 索引-顶点 参照索引类型

软体数据

名称 类型 说明
软体本地名称 text
软体通用名称 text
形状 byte 参阅软体形状类型
材质索引 索引-材质 参阅索引类型
byte 组ID
碰撞体掩码 short
标识 flag 见软体标识
B-link create distance int
Number of clusters int
总质量 float
Collision margin float
空气动力学模型 int 参照空气动力学模型
VCF配置 float 速度修正系数
DP配置 float 阻尼系数
DG配置 float 阻力系数
LF配置 float 提升系数
PR配置 float 压力系数
VC配置 float 音量对话系数
DF配置 float 动摩擦系数
MT配置 float 姿势匹配系数
CHR配置 float 刚性接触硬度
KHR配置 float 动力学接触硬度
SHR配置 float 软接触硬度
AHR配置 float 锚固硬度
Cluster SRHR_CL float 软硬度与刚硬度
Cluster SKHR_CL float 柔软与动力学硬度
Cluster SSHR_CL float 柔软与柔软的硬度
Cluster SR_SPLT_CL float 软与刚冲动分裂
Cluster SK_SPLT_CL float 软与动能冲动分裂
Cluster SS_SPLT_CL float 软与软冲动分裂
Interation V_IT int 速度求解器迭代
Interation P_IT int 定位求解器迭代
Interation D_IT int 漂移求解器迭代
Interation C_IT int 群集解算器迭代
Material LST int 线性刚度系数
Material AST int 面积/角度刚度系数
Material VST int 体积刚度系数
Anchor rigid body count int 锚固刚体个数
Anchor rigid bodies ~[N] N是锚固刚体计数。参见锚固刚体说明
Vertex pin count int 顶点引脚个数
Vertex pins ~[N] N是顶点引脚计数。参见顶点引脚说明

14.样例代码

  因为不清楚软体方面,所以没有读取软体数据,不过数据组织套路都一样,有需求可以自己写。代码只考虑2.1版本,2.0版本根据各处版本描述自行更改适应程序。
  因为需要先实验一次,所以用Python,写起来更快一些;C++需要考虑组织程序以及数据类型,Opengl还没学到家,现在只读出了顶点数据,等写出差不多的模型读取器后,再把C++程序放上来。或者直接上github搜索已有现成的mmd读取器saba,参考src/Saba/Model/MMD/PMXFile.h文件,我的C++pmx文件读取程序也是参考这个写法。

class PMX:
    def __init__(self):
        pass

    #Version
    #Index
    #Model_Name
    #Model_Name_Universal
    #Comments_Local
    #Comments_Universal
    #Vertices
    #Surfaces
    #Textures
    #Materials
    #Bones
    #Morphs
    #DisplayFrames
    #Rigid_Bodies
    #Joints

    @staticmethod
    def from_file(filename):
        res = PMX()

        f = open(filename, 'rb')
        Signature = f.read(4)

        if Signature.decode() != "PMX ":
            raise Exception("model type is not PMX")
        else:
            print("Loading PMX")

        res.Version = struct.unpack('<f', f.read(4))[0]

        Global_Count = int.from_bytes(f.read(1), byteorder='little', signed=False)

        Globals = list(f.read(Global_Count))

        Index = res.Index = {'Text Encoding' : "UTF16" if Globals[0] == 0 else "UTF8",
                 'Appendix UV' : Globals[1],
                'Vertex Index Size' : Globals[2],
                'Texture Index Size' : Globals[3],
                'Material Index Size' :Globals[4],
                'Bone Index Size' : Globals[5],
                'Morph Index Size' : Globals[6],
                'Rigid Body Index Size' : Globals[7],
                 }

        res.Model_Name = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(res.Index['Text Encoding'])
        res.Model_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(res.Index['Text Encoding'])

        res.Comments_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])
        res.Comments_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])

        #索引类型对应unpack的大小
        bone_index_type = {1: 'b', 2: 'h', 4:'i'}[Index['Bone Index Size']]
        vertex_index_type = {1:'B', 2:'H', 4:'i'}[Index['Vertex Index Size']]
        texture_index_type = {1: 'b', 2: 'h', 4:'i'}[Index['Texture Index Size']]
        morph_index_type = {1: 'b', 2: 'h', 4:'i'}[Index['Morph Index Size']]
        material_index_type = {1: 'b', 2: 'h', 4:'i'}[Index['Material Index Size']]
        rigid_body_index_type = {1: 'b', 2: 'h', 4:'i'}[Index['Rigid Body Index Size']]

        Vertex_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f"Vertex Count: {Vertex_Count}")
        res.Vertices = []

        for i in range(Vertex_Count):
            Position = struct.unpack("<fff", f.read(12))

            Normal = struct.unpack("<fff", f.read(12))

            UV_Texture_Coordinate = struct.unpack("<ff", f.read(8))

            Appendix_UV = []
            for j in range(Index['Appendix UV']):
                Appendix_UV.append(struct.unpack("<ffff", f.read(16)))

            Weight_Type = int.from_bytes(f.read(1), byteorder='little', signed=True)

            # struct: [(bone, weight),...]
            Weight_Deform = []
            if Weight_Type == 0:# BDEF1
                BDEF1 = struct.unpack('<'+bone_index_type, f.read(Index['Bone Index Size']))[0]
                Weight_Deform.append((BDEF1, 1))
            elif Weight_Type == 1:# BDEF2
                BDEF2 = struct.unpack('<'+bone_index_type*2+'f', f.read(Index['Bone Index Size']*2+4))
                Weight_Deform.extend([(BDEF2[0], BDEF2[2]), (BDEF2[1], 1-BDEF2[2])])
            elif Weight_Type == 2:# BDEF4
                BDEF4 = struct.unpack('<'+bone_index_type*4+'ffff', f.read(Index['Bone Index Size']*4+16))
                Weight_Deform.extend([(BDEF4[0], BDEF4[4]),
                                      (BDEF4[1], BDEF4[5]),
                                      (BDEF4[2], BDEF4[6]),
                                      (BDEF4[3], BDEF4[7])])
            elif Weight_Type == 3:# SDEF
                SDEF = struct.unpack('<'+bone_index_type*2+'f'+'f'*9, f.read(Index['Bone Index Size']*2+40))
                Weight_Deform.extend([(SDEF[0], SDEF[2]), (SDEF[1], 1 - SDEF[2]), {'C': SDEF[3:6], 'R0':SDEF[6:9], 'R1':SDEF[9:12]}])
            elif Weight_Type == 4:#QDEF
                QDEF = struct.unpack('<' + bone_index_type * 4 + 'ffff', f.read(Index['Bone Index Size'] * 4 + 16))
                Weight_Deform.extend([(QDEF[0], QDEF[4]),
                                      (QDEF[1], QDEF[5]),
                                      (QDEF[2], QDEF[6]),
                                      (QDEF[3], QDEF[7])])
            elif Weight_Type == -1:
                pass
            else:
                raise Exception(f'Weight Type {Weight_Type} not found')

            Edge_Scale = struct.unpack('<f', f.read(4))[0]

            res.Vertices.append({
                'Position':Position,
                'Normal':Normal,
                'UV Texture Coordinate':UV_Texture_Coordinate,
                'Appendix UV':Appendix_UV,
                'Weight Type':Weight_Type,
                'Weight Deform':Weight_Deform,
                'Edge Scale':Edge_Scale
            })

        Surface_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Surface count: {Surface_Count}')

        res.Surfaces = []
        for i in range(Surface_Count//3):
            res.Surfaces.append(struct.unpack('<'+vertex_index_type*3,f.read(3*Index['Vertex Index Size'])))

        Texture_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Texture_Count: {Texture_Count}')
        res.Textures = []
        for i in range(Texture_Count):
            res.Textures.append(f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding']))


        Material_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Material Count: {Material_Count}')
        res.Materials = []
        for i in range(Material_Count):
            Material_Name_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])
            Material_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])
            Diffuser_Color = struct.unpack('<ffff', f.read(16))
            Specular_Color=struct.unpack('<fff', f.read(12))
            Speculat_Strength=struct.unpack('<f', f.read(4))[0]
            Ambient_Color=struct.unpack('<fff',f.read(12))
            Drawing_Flags=f.read(1)[0]
            Drawing_Flags = {
                'No-Cull': Drawing_Flags & 0b00000001 == 0b00000001,
                'Ground Shadow':Drawing_Flags & 0b00000010 == 0b00000010,
                'Draw shadow':Drawing_Flags & 0b00000100 == 0b00000100,
                'Receive Shadow':Drawing_Flags & 0b00001000 == 0b00001000,
                'Has Edge':Drawing_Flags & 0b00010000 == 0b00010000,
                'Vertex Color':Drawing_Flags & 0b00100000 == 0b00100000,
                'Point Drawing':Drawing_Flags & 0b01000000 == 0b01000000,
                'Line Drawing': Drawing_Flags & 0b10000000 == 0b10000000
            }
            Edge_Color = struct.unpack('<ffff', f.read(16))
            Edge_Scale = struct.unpack('<f', f.read(4))[0]
            Texture_Index = struct.unpack('<'+texture_index_type, f.read(Index['Texture Index Size']))[0]
            Environment_Index = struct.unpack('<' + texture_index_type, f.read(Index['Texture Index Size']))[0]
            Environment_Blend_Mode = f.read(1)[0]
            Toon_Reference = f.read(1)[0]
            if Toon_Reference == 0:#reference texture
                Toon_Value = struct.unpack('<'+texture_index_type, f.read(Index['Texture Index Size']))[0]
            elif Toon_Reference == 1:#reference internal
                Toon_Value = f.read(1)[0]
            else:
                raise Exception(f'Toon Reference {Toon_Reference} not found')
            Meta_Data = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode('UTF16')
            Material_Surface_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
            res.Materials.append({
                'Material Name Local':Material_Name_Local,
                'Material Name Universal': Material_Name_Universal,
                'Diffuser Color': Diffuser_Color,
                'Specular Color': Specular_Color,
                'Speculat Strength': Speculat_Strength,
                'Ambient Color': Ambient_Color,
                'Drawing Flags': Drawing_Flags,
                'Edge Color': Edge_Color,
                'Texture Index': Texture_Index,
                'Environment Index': Environment_Index,
                'Environment Blend Mode': Environment_Blend_Mode,
                'Toon Reference': Toon_Reference,
                'Toon Value': Toon_Value,
                'Meta Data': Meta_Data,
                'Surface Count': Material_Surface_Count
            })

        Bone_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Bone Count: {Bone_Count}')
        res.Bones = []
        for i in range(Bone_Count):
            Bone_Name_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])
            Bone_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])
            Position = struct.unpack('<fff', f.read(12))
            Parent_Bone_Index = struct.unpack('<'+bone_index_type,f.read(Index['Bone Index Size']))[0]
            Layer = int.from_bytes(f.read(4), byteorder='little', signed=True)
            Bone_Flags = f.read(2)
            Bone_Flags = {
                'Indexed Tail Position': Bone_Flags[0] & 0b00000001 == 0b00000001,
                'Rotatable': Bone_Flags[0] & 0b00000010 == 0b00000010,
                'Translatable': Bone_Flags[0] & 0b00000100 == 0b00000100,
                'Is Visible': Bone_Flags[0] & 0b00001000 == 0b00001000,
                'Enabled': Bone_Flags[0] & 0b00010000 == 0b00010000,
                'IK': Bone_Flags[0] & 0b00100000 == 0b00100000,
                'Inherit Rotation': Bone_Flags[1] & 0b00000001 == 0b00000001,
                'Inherit Translation': Bone_Flags[1] & 0b00000010 == 0b00000010,
                'Fixed Axis': Bone_Flags[1] & 0b00000100 == 0b00000100,
                'Local Coordinate': Bone_Flags[1] & 0b00001000 == 0b00001000,
                'Physics After Deform': Bone_Flags[1] & 0b00010000 == 0b00010000,
                'External Parent Deform': Bone_Flags[1] & 0b00100000 == 0b00100000,
            }

            if Bone_Flags['Indexed Tail Position'] is True:
                Tail_Position = struct.unpack('<'+bone_index_type,f.read(Index['Bone Index Size']))[0]
            else:
                Tail_Position = struct.unpack('<fff', f.read(12))

            if Bone_Flags['Inherit Rotation'] or Bone_Flags['Inherit Translation']:
                Inherit_Bone = {
                    'Parent Index': struct.unpack('<'+bone_index_type,f.read(Index['Bone Index Size']))[0],
                    'Parent Influence': struct.unpack('<f', f.read(4))[0]
                }
            else:
                Inherit_Bone = None

            if Bone_Flags['Fixed Axis']:
                Fixed_Axis = struct.unpack('<fff', f.read(12))
            else:
                Fixed_Axis = None

            if Bone_Flags['Local Coordinate']:
                Local_Coordinate = [struct.unpack('<fff', f.read(12)), struct.unpack('<fff', f.read(12))]
            else:
                Local_Coordinate = None

            if Bone_Flags['External Parent Deform']:
                External_Parent = struct.unpack('<'+bone_index_type,f.read(Index['Bone Index Size']))[0]
            else:
                External_Parent = None

            if Bone_Flags['IK']:
                Target = struct.unpack('<'+bone_index_type,f.read(Index['Bone Index Size']))[0]
                Loop = int.from_bytes(f.read(4), byteorder='little', signed=False)
                Limit_Radian = struct.unpack('<f', f.read(4))[0]
                Link_Count = int.from_bytes(f.read(4), byteorder='little', signed=False)
                IK_Links = []
                for j in range(Link_Count):
                    Bone_Index = struct.unpack('<'+bone_index_type,f.read(Index['Bone Index Size']))[0]
                    Has_Limit = bool(f.read(1)[0])
                    if Has_Limit:
                        Limit_Min = struct.unpack('<fff', f.read(12))
                        Limit_Max = struct.unpack('<fff', f.read(12))
                    IK_Links.append({
                        'Bone Index': Bone_Index,
                        'Has Limit': Has_Limit,
                        'Limit Min': Limit_Min if Has_Limit else None,
                        'Limit Max': Limit_Max if Has_Limit else None,
                    })
                IK = {
                    'Target': Target,
                    'Loop': Loop,
                    'Limit Radian': Limit_Radian,
                    'Link Count': Link_Count,
                    'IK Links': IK_Links
                }
            else:
                IK = None

            res.Bones.append({
                'Bone Name Local': Bone_Name_Local,
                'Bone Name Universal': Bone_Name_Universal,
                'Position': Position,
                'Parent Bone Index': Parent_Bone_Index,
                'Layer': Layer,
                'Bone Flags': Bone_Flags,
                'Tail Position': Tail_Position,
                'Inherit Bone': Inherit_Bone,
                'Fixed Axis': Fixed_Axis,
                'Local Coordinate': Local_Coordinate,
                'IK': IK
            })

        Morph_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Morph Count: {Morph_Count}')
        res.Morphs = []
        for i in range(Morph_Count):
            Morph_Name_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(Index['Text Encoding'])
            Morph_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            Panel_Type = f.read(1)[0]
            Morph_Type = f.read(1)[0]
            Offset_Size = int.from_bytes(f.read(4), byteorder='little', signed=False)
            Offset_Data = []
            for j in range(Offset_Size):
                #注意这里面的索引类型多不一样
                if Morph_Type in [0, 9]:#Group or Flip
                    Offset_Data.append({
                        'Morph Index': struct.unpack('<' + morph_index_type, f.read(Index['Morph Index Size']))[0],
                        'Influence': struct.unpack('<f', f.read(4))[0]
                     })
                elif Morph_Type == 1:#Vertex
                    Offset_Data.append({
                        'Vertex Index': struct.unpack('<' + vertex_index_type, f.read(Index['Vertex Index Size']))[0],
                        'Translation': struct.unpack('<fff', f.read(12))
                    })
                elif Morph_Type == 2:#Bone
                    Offset_Data.append({
                        'Bone Index': struct.unpack('<' + bone_index_type, f.read(Index['Bone Index Size']))[0],
                        'Translation': struct.unpack('<fff', f.read(12)),
                        'Rotation': struct.unpack('<ffff', f.read(16)),
                    })
                elif Morph_Type in [3, 4, 5, 6, 7]:#UV
                    Offset_Data.append({
                        'Vertex Index': struct.unpack('<' + vertex_index_type, f.read(Index['Vertex Index Size']))[0],
                        'Data': struct.unpack('<ffff', f.read(16))
                    })
                elif Morph_Type == 8:#Material
                    Offset_Data.append({
                        'Material Index': struct.unpack('<' + material_index_type, f.read(Index['Material Index Size']))[0],
                        'Mix Method': f.read(1)[0],
                        'Diffuse': struct.unpack('<ffff', f.read(16)),
                        'Specular': struct.unpack('<fff', f.read(12)),
                        'Specularity': struct.unpack('<f', f.read(4))[0],
                        'Ambient': struct.unpack('<fff', f.read(12)),
                        'Edge Color': struct.unpack('<ffff', f.read(16)),
                        'Edge Size': struct.unpack('<f', f.read(4))[0],
                        'Texture Tint': struct.unpack('<ffff', f.read(16)),
                        'Environment Tint': struct.unpack('<ffff', f.read(16)),
                        'Toon Tint': struct.unpack('<ffff', f.read(16))
                    })
                elif Morph_Type == 10: #Impulse
                    Offset_Data.append({
                        'Rigid Body Index': struct.unpack('<' + rigid_body_index_type, f.read(Index['Rigid Body Index Size'])),
                        'Local Flag': struct.unpack('<f', f.read(4))[0],
                        'Movement Speed': struct.unpack('<fff', f.read(12)),
                        'Rotation torque': struct.unpack('<fff', f.read(12)),
                    })
                else:
                    raise Exception('morph type is not exist')

            res.Morphs.append({
                'Morph Name Local': Morph_Name_Local,
                'Morph Name Universal': Morph_Name_Universal,
                'Panel Type': Panel_Type,
                'Morph Type': Morph_Type,
                'Offset Size': Offset_Size,
                'Offset Data': Offset_Data
            })

        DisplayFrame_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'DisplayFrame Count: {DisplayFrame_Count}')
        res.DisplayFrames = []
        for i in range(DisplayFrame_Count):
            DisplayFrame_Name_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            DisplayFrame_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            Special_Flag = f.read(1)[0]
            Frame_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
            Frames = []
            for j in range(Frame_Count):
                Frame_Type = f.read(1)[0]
                if Frame_Type == 1:
                    Frame_Data = struct.unpack('<' + morph_index_type, f.read(Index['Morph Index Size']))[0]
                elif Frame_Type == 0:
                    Frame_Data = struct.unpack('<' + bone_index_type, f.read(Index['Bone Index Size']))[0]
                else:
                    raise Exception('frame type is not exist')
                Frames.append({
                    'Frame Type': Frame_Type,
                    'Frame Data': Frame_Data
                })
            res.DisplayFrames.append({
                'DisplayFrame Name Local': DisplayFrame_Name_Local,
                'DisplayFrame Name Universal': DisplayFrame_Name_Universal,
                'Special Flag': Special_Flag,
                'Frame Count': Frame_Count,
                'Frames': Frames
            })

        Rigid_Body_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Rigid Body Count: {Rigid_Body_Count}')
        res.Rigid_Bodies = []
        for i in range(Rigid_Body_Count):
            Rigid_Body_Name_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            Rigid_Body_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            Related_Bone_Index = struct.unpack('<' + bone_index_type, f.read(Index['Bone Index Size']))[0]
            Group_Id = f.read(1)[0]
            Non_Collision_Group = struct.unpack('<h', f.read(2))[0]
            Non_Collision_Group = [((1<<j) & Non_Collision_Group) != (1<<j) for j in range(14)]
            Shape = f.read(1)[0]
            Shape_Size = struct.unpack('<fff', f.read(12))
            Shape_Position = struct.unpack('<fff', f.read(12))
            Shape_Rotation = struct.unpack('<fff', f.read(12))
            Mass = struct.unpack('<f', f.read(4))[0]
            Move_Attenuation = struct.unpack('<f', f.read(4))[0]
            Rotation_Damping = struct.unpack('<f', f.read(4))[0]
            Repulsion = struct.unpack('<f', f.read(4))[0]
            Friction_Force = struct.unpack('<f', f.read(4))[0]
            Physics_Mode = f.read(1)[0]
            res.Rigid_Bodies.append({
                'Rigid Body Name Local': Rigid_Body_Name_Local,
                'Rigid Body Name Universal': Rigid_Body_Name_Universal,
                'Related Bone Index': Related_Bone_Index,
                'Group Id': Group_Id,
                'Non Collision Group': Non_Collision_Group,
                'Shape': Shape,
                'Shape Size': Shape_Size,
                'Shape Position': Shape_Position,
                'Shape Rotation': Shape_Rotation,
                'Mass': Mass,
                'Move Attenuation': Move_Attenuation,
                'Rotation Damping': Rotation_Damping,
                'Repulsion': Repulsion,
                'Friction Force': Friction_Force,
                'Physics Mode': Physics_Mode
            })

        Joint_Count = int.from_bytes(f.read(4), byteorder='little', signed=True)
        print(f'Joint_Count: {Joint_Count}')
        res.Joints = []
        for i in range(Joint_Count):
            Joint_Name_Local = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            Joint_Name_Universal = f.read(int.from_bytes(f.read(4), byteorder='little', signed=False)).decode(
                Index['Text Encoding'])
            Joint_Type = f.read(1)[0]
            Rigid_body_index_A = struct.unpack('<' + rigid_body_index_type, f.read(Index['Rigid Body Index Size']))[0]
            Rigid_body_index_B = struct.unpack('<' + rigid_body_index_type, f.read(Index['Rigid Body Index Size']))[0]
            Position = struct.unpack('<fff', f.read(12))
            Rotation = struct.unpack('<fff', f.read(12))
            Position_Limit = (struct.unpack('<fff', f.read(12)), struct.unpack('<fff', f.read(12)))
            Rotation_Limit = (struct.unpack('<fff', f.read(12)), struct.unpack('<fff', f.read(12)))
            Position_Spring = struct.unpack('<fff', f.read(12))
            Rotation_Spring = struct.unpack('<fff', f.read(12))
            res.Joints.append({
                'Joint Name Local': Joint_Name_Local,
                'Joint Name Universal': Joint_Name_Universal,
                'Joint Type': Joint_Type,
                'Rigid body index A': Rigid_body_index_A,
                'Rigid body index B': Rigid_body_index_B,
                'Position': Position,
                'Rotation': Rotation,
                'Position Limit': Position_Limit,
                'Rotation Limit': Rotation_Limit,
                'Position Spring': Position_Spring,
                'Rotation Spring': Rotation_Spring,
            })
            f.close()
            return res

初始化:

if __name__ == '__main__':
    pmx = PMX.from_file('model/model_test.pmx')

  PMX对象的成员参考staticmethod上面的注释。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容