一、前言
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 | |
ubyte | unsigned char | uint8_t | 1 | |
short | short | int16_t | 2 | |
ushort | unsigned short | uint16_t | 2 | |
int | int | int32_t | 4 | |
uint | unsigned int | uint32_t | 4 | |
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
详细参考这个教程 -
法线贴图UV
通过改变法向量,使得平面图计算出阴暗交替的感觉。 - 顶点颜色(PMX2.1拓展)
忽略这些额外的vec4是最安全的,但同时也会导致很多东西无法显示。
顶点绑骨
顶点要随着骨骼运动、变形,每个骨骼对顶点有相应的权重,在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权重 | 不保证=1 |
SDEF(2.0)
一种SBS(Spherical Blend Skinning)球面混合蒙皮方式,能使皮肤的扭动更自然。
类型 | 名称 | 说明 |
---|---|---|
索引-骨骼 | 骨骼1 | |
索引-骨骼 | 骨骼2 | |
foat | 骨骼1权重 | 骨骼2权重 = 1-骨骼1权重 |
vec3 | ||
vec3 | ||
vec3 |
QDEF(2.1)
双四元数变型混合
类型 | 名称 | 说明 |
---|---|---|
索引-骨骼 | 骨骼1 | |
索引-骨骼 | 骨骼2 | |
索引-骨骼 | 骨骼3 | |
索引-骨骼 | 骨骼4 | |
foat | 骨骼1权重 | |
foat | 骨骼2权重 | |
foat | 骨骼3权重 | |
foat | 骨骼4权重 | 不保证=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的骨骼都会跟着亲骨走,我发现有付予旋转的骨骼多是隐藏骨骼,而作用大概是可以增加或减少亲骨对当前骨骼的影响,应用可以看这里: 【MMD教程相关】不追加捩骨的情况下解决模型手肘旋转时的扭曲。
骨骼继承
名称 | 类型 | 作用 |
---|---|---|
亲骨索引 | 索引-骨骼 | 参阅骨骼索引类型 |
影响权重 | float | 亲骨的影响力 |
假如上图的付予栏位中的旋转、移动有一个被选中,这个类型有效
骨骼固定轴
名称 | 类型 | 作用 |
---|---|---|
轴方向 | vec3 | 骨骼指向的方向 |
骨骼Local坐标
名称 | 类型 | 作用 |
---|---|---|
X矢量 | vec3 | |
Z矢量 | vec3 |
PmxEditor最下面的local轴选项,这个见于左腕、右腕(左右胳膊)、以及往下延伸的子骨骼中,其他用法可以看看这个。
MMD左下角有操作手柄:
默认情况(模型选项Local轴无效),LOCAL轴依旧可以使用,不旋转模型的情况下,两个坐标没有区别,如果将模型旋转,GLOBAL轴会根据世界坐标旋转,而LOCAL轴会根据模型坐标旋转。
当Local轴选项有效时,GLOBAL模式没有改变,而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连接在右下方。
原文中没有给出单位角和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 | 对被索引变形的权重 |
顶点(Vertex)
名称 | 类型 | 说明 |
---|---|---|
顶点索引 | 索引-顶点 | 参阅索引类型 |
移动 | vec3 | 变化的相对位置 |
骨骼(Bone)
名称 | 类型 | 说明 |
---|---|---|
骨骼索引 | 索引-骨骼 | 参阅索引类型 |
移动 | vec3 | 变化的相对位置 |
移动 | vec4 | 相对旋转四元数 |
UV(及拓展UV)
名称 | 类型 | 说明 |
---|---|---|
顶点索引 | 索引-顶点 | 参阅索引类型 |
~ | vec4 | 做什么取决于UV拓展 |
材质(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上面的注释。