MMD中的VMD文件格式详解
VMD是一个二进制流的文件。
Header
VMD有两个版本,你可以认为是v1跟v2。
前30个字节是版本描述。ASCII编码
v1是"Vocaloid Motion Data file"
v2是"Vocaloid Motion Data 0002"
接下来是名字,ShiftJIS编码
v1中是10个字节,v2中是20个字节
接下来每部分开头都是一个uint32_t,记录了这部分有多少关键帧。没有记录此项为0(而不是省略)。
然后跟上相应数量的记录。
类型 | 解释 |
---|
byte*30 | VersionInformation |
byte*(10/20) | ModelName |
uint32_t | BoneKeyFrameNumber |
byte * 111 * n | BoneKeyFrameRecord |
uint32_t | MorphKeyFrameNumber |
byte* 47 *n | MorphKeyFrameRecord |
uint32_t | CameraKeyFrameNumber |
byte* 61 *n | CameraKeyFrameRecord |
uint32_t | LightKeyFrameNumber |
byte* 28 *n | LightKeyFrameRecord |
KeyFrameRecord
BoneKeyFrameRecord(byte*111)
类型 | 解释 |
---|
byte*15(ShiftJIS) | BoneName |
uint32_t | FrameTime |
float*3 | Translation.xyz |
float*4 | Rotation.xyzw |
uint8_t*16 | XCurve |
uint8_t*16 | YCurve |
uint8_t*16 | ZCurve |
uint8_t*16 | RCurve |
uint8_t那里有冗余,每四个只读第一个就行。 | |
MorphKeyFrameRecord(byte*23)
类型 | 解释 |
---|
byte*15(ShiftJIS) | MorphName |
uint32_t | FrameTime |
float | Weight |
CameraKeyFrameRecord(byte*61)
类型 | 解释 |
---|
uint32_t | FrameTime |
float | Distance |
float*3 | Position.xyz |
float*3 | Rotation.xyz |
uint8_t*24 | Curve |
float | ViewAngle |
uint8_t | Orthographic |
LightKeyFrameRecord(byte*28)
类型 | 解释 |
---|
uint32_t | FrameTime |
float*3 | Color.rgb |
float*3 | Direction.xyz |
Java Code
package com.company;
import java.io.*;
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("test.vmd");
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
String headerString = readString(dis, 30);
int version = 0;
String modelName = null;
if (headerString.startsWith("Vocaloid Motion Data file")){
version = 1;
modelName = readString(dis, 10);
}else if (headerString.startsWith("Vocaloid Motion Data 0002")){
version = 2;
modelName = readString(dis, 20);
}else{
throw new IOException("VmdReader: Not A VMD File!");
}
int numOfBoneRecord = readInt(dis);
for (int i = 0; i < numOfBoneRecord; i++){
String BoneRecordNameString = readString(dis, 15);
int frameTime = readInt(dis);
float tx = readFloat(dis);
float ty = readFloat(dis);
float tz = readFloat(dis);
float rx = readFloat(dis);
float ry = readFloat(dis);
float rz = readFloat(dis);
float rw = readFloat(dis);
int txc1 = dis.readByte();
dis.skipBytes(3);
int txc2 = dis.readByte();
dis.skipBytes(3);
int txc3 = dis.readByte();
dis.skipBytes(3);
int txc4 = dis.readByte();
dis.skipBytes(3);
int tyc1 = dis.readByte();
dis.skipBytes(3);
int tyc2 = dis.readByte();
dis.skipBytes(3);
int tyc3 = dis.readByte();
dis.skipBytes(3);
int tyc4 = dis.readByte();
dis.skipBytes(3);
int tzc1 = dis.readByte();
dis.skipBytes(3);
int tzc2 = dis.readByte();
dis.skipBytes(3);
int tzc3 = dis.readByte();
dis.skipBytes(3);
int tzc4 = dis.readByte();
dis.skipBytes(3);
int rc1 = dis.readByte();
dis.skipBytes(3);
int rc2 = dis.readByte();
dis.skipBytes(3);
int rc3 = dis.readByte();
dis.skipBytes(3);
int rc4 = dis.readByte();
dis.skipBytes(3);
}
int numOfMorphRecord = readInt(dis);
for (int i = 0; i < numOfMorphRecord; i++){
String name = readString(dis, 15);
int frameTime = readInt(dis);
float weight = readFloat(dis);
}
int numOfCameraRecord = readInt(dis);
for (int i = 0; i < numOfCameraRecord; i++){
int frameTime = readInt(dis);
float distance = readFloat(dis);
float px = readFloat(dis);
float py = readFloat(dis);
float pz = readFloat(dis);
float rx = readFloat(dis);
float ry = readFloat(dis);
float rz = readFloat(dis);
int txc1 = dis.readByte();
int txc2 = dis.readByte();
int txc3 = dis.readByte();
int txc4 = dis.readByte();
int tyc1 = dis.readByte();
int tyc2 = dis.readByte();
int tyc3 = dis.readByte();
int tyc4 = dis.readByte();
int tzc1 = dis.readByte();
int tzc2 = dis.readByte();
int tzc3 = dis.readByte();
int tzc4 = dis.readByte();
int qc1 = dis.readByte();
int qc2 = dis.readByte();
int qc3 = dis.readByte();
int qc4 = dis.readByte();
int dc1 = dis.readByte();
int dc2 = dis.readByte();
int dc3 = dis.readByte();
int dc4 = dis.readByte();
int vc1 = dis.readByte();
int vc2 = dis.readByte();
int vc3 = dis.readByte();
int vc4 = dis.readByte();
float viewAngle = readFloat(dis);
byte orthographic = dis.readByte();
}
} catch (IOException e) {
System.err.println(e);
e.printStackTrace();
}
}
private static int readInt(DataInputStream stream) throws IOException {
byte intBytes[] = new byte[4];
stream.read(intBytes);
for (int j = 0; j < intBytes.length/2; j++){
byte temp = intBytes[j];
intBytes[j] = intBytes[intBytes.length -1 -j];
intBytes[intBytes.length -1 -j] = temp;
}
return ByteBuffer.wrap(intBytes).getInt();
}
private static float readFloat(DataInputStream stream) throws IOException {
byte floatBytes[] = new byte[4];
stream.read(floatBytes);
for (int j = 0; j < floatBytes.length/2; j++){
byte temp = floatBytes[j];
floatBytes[j] = floatBytes[floatBytes.length -1 -j];
floatBytes[floatBytes.length -1 -j] = temp;
}
return ByteBuffer.wrap(floatBytes).getFloat();
}
private static String readString(DataInputStream stream, int length) throws IOException {
byte[] name = new byte[length];
stream.read(name);
String nameString = new String(name, "Shift-jis");
int indexOfNull = nameString.indexOf('\0');
if (indexOfNull != -1){
nameString = nameString.substring(0, indexOfNull);
}
return nameString;
}
private static String readString(DataInputStream stream, boolean utf8) throws IOException {
int length = readInt(stream);
byte[] name = new byte[length];
stream.read(name);
String string = null;
if (utf8== false){
for (int i = 0; i < name.length; i+=2){
byte temp = name[i];
name[i] = name[i+1];
name[i+1] = temp;
}
string = new String(name, "utf-16");
}else{
string = new String(name, "utf-8");
}
return string;
}
/*
private static Vector2f readVector2f(DataInputStream stream) throws IOException {
float x = readFloat(stream);
float y = readFloat(stream);
return new Vector3f(x,y);
}
private static Vector3f readVector3f(DataInputStream stream) throws IOException {
float x = readFloat(stream);
float y = readFloat(stream);
float z = readFloat(stream);
return new Vector3f(x,y,z);
}
*/
}