DirectX12でミクさんを躍らせてみよう8－VMDの読み込み

# 前回
https://qiita.com/dpals39/items/528aa66a382c6747a35a

# VMDの読み込み

VMDはMMDのアニメーションファイル形式です。

正確には、アニメーション情報以外の情報も含んでいますが、読み込むだけで、実際に使用するのはアニメーションのみです。

今回も使用するVMDファイルは、自分でお探しください。

これもPMXファイルを読み込む時と大きく変わりません。同じように順番にバイト数だけ読み込めば大丈夫です。

VMDは以下の情報を含んでいます。

- ヘッダー
- アニメーション
- モーフ
- カメラ
- ライト
- シャドウ
- IK

それでは始めましょう。

### VMDファイルの読み込み

```cpp
struct VMDFileData
{
	VMDHeader header;
	std::vector<VMDMotion> motions;
	std::vector<VMDMorph> morphs;
	std::vector<VMDCamera> cameras;
	std::vector<VMDLight> lights;
	std::vector<VMDShadow> shadows;
	std::vector<VMDIK> iks;
};
```

VMDはVMDFileDataという構造体に保存するようにしました。

```cpp
bool LoadVMDFile(const std::wstring& filePath, VMDFileData& fileData)
```

PMXファイルを読み込む際と同じです。

ファイルパスを指定し、保存先の構造体を渡します。

```cpp
	if (filePath.empty() == true)
	{
		return false;
	}

	std::ifstream vmdFile{ filePath, (std::ios::binary | std::ios::in) };

	if (vmdFile.fail() == true)
	{
		vmdFile.close();
		return false;
	}
```

ファイルをバイナリ、読み取りモードで開きます。

### ヘッダー

```cpp
struct VMDHeader
{
	char header[30];
	char modelName[20];
};
```

ヘッダーの構造体は上記の通りです。

```cpp
bool ReadHeader(VMDFileData& data, std::ifstream& file)
{
	file.read(reinterpret_cast<char*>(&data.header.header), 30);

	std::string headerStr = data.header.header;

	if (headerStr != "Vocaloid Motion Data 0002" &&
		headerStr != "Vocaloid Motion Data")
	{
		return false;
	}

	file.read(reinterpret_cast<char*>(&data.header.modelName), 20);

	return true;
}
```

最初の30バイトはPMXのマジックナンバーに似ています。VMDにも決まった文字列が含まれています。上記の2つの文字列のいずれでもない場合、VMDファイルではないとしてメソッドを終了します。

次の20バイトはモデル名です。

### アニメーション

このデータが最も重要なデータです。

```cpp
struct VMDMotion
{
	std::wstring boneName;
	unsigned int frame;
	DirectX::XMFLOAT3 translate;
	DirectX::XMFLOAT4 quaternion;
	unsigned char interpolation[64];
};
```

各パラメータがどのようなものかは、実際にアニメーションを実装する際に見ていきましょう。

```cpp
bool ReadMotion(VMDFileData& data, std::ifstream& file)
{
	unsigned int count = 0;
	file.read(reinterpret_cast<char*>(&count), 4);

	data.motions.resize(count);
	for (auto& motion : data.motions)
	{
		UnicodeUtil::ReadJISToWString(file, motion.boneName, 15);
		file.read(reinterpret_cast<char*>(&motion.frame), 4);
		file.read(reinterpret_cast<char*>(&motion.translate), 12);
		file.read(reinterpret_cast<char*>(&motion.quaternion), 16);
		file.read(reinterpret_cast<char*>(motion.interpolation), 64);
	}

	return true;
}
```

アニメーションデータを読み込むコードは上記の通りです。

ボーンの名前を読み込む部分を見てみましょうか。

ここでも std::wstring にボーン名を保存していますが、PMX の時とは異なるメソッドを使用しています。

なぜ異なるものを使用しているのでしょうか？

```cpp

	void ReadJISToWString(std::ifstream& _file, std::wstring& output, size_t length)
	{
		char* charStr = new char[length];
		_file.read(reinterpret_cast<char*>(charStr), length);
		std::string jisString = charStr;
		output = multi_to_wide_capi(jisString);
		delete[] charStr;
	}
```

メソッドの内容は以下のようになっています。ボーンの名前も日本語であるため、コードデバッガーで内容を確認したり、後でUIに表示したりするためにwstringとして保存します。

しかし、VMDのアニメーションデータの文字列はPMXとは異なり、バイト数が固定されています。

PMXでは文字列を読み込む前に4バイトを先に読み込んで文字列の長さを取得していましたね。

VMDではこの文字列の長さが別途なく固定されているため、別のメソッドを作成しました。

### モーフ

```cpp
struct VMDMorph
{
	std::wstring blendShapeName;
	unsigned int frame;
	float weight;
};
```

今回はモーフデータです。主に表情アニメーションなどのためのデータです。

```cpp
bool ReadMorph(VMDFileData& data, std::ifstream& file)
{
	unsigned int count = 0;
	file.read(reinterpret_cast<char*>(&count), 4);

	data.morphs.resize(count);
	for (auto& morph : data.morphs)
	{
		UnicodeUtil::ReadJISToWString(file, morph.blendShapeName, 15);
		file.read(reinterpret_cast<char*>(&morph.frame), 4);
		file.read(reinterpret_cast<char*>(&morph.weight), 4);
	}

	return true;
}
```

### カメラ

VMDはカメラのデータも持っています。

おそらくMMDプログラムのカメラの位置や移動のためのデータでしょうが、ここでは使用せず、読み込むだけにします。

```cpp
struct VMDCamera
{
	unsigned int frame;
	float distance;
	DirectX::XMFLOAT3 interest;
	DirectX::XMFLOAT3 rotate;
	unsigned char interpolation[24];
	unsigned int fov;
	unsigned char isPerspective;
};
```

```cpp
bool ReadCamera(VMDFileData& data, std::ifstream& file)
{
	unsigned int count = 0;
	file.read(reinterpret_cast<char*>(&count), 4);

	data.cameras.resize(count);
	for (auto& camera : data.cameras)
	{
		file.read(reinterpret_cast<char*>(&camera.frame), 4);
		file.read(reinterpret_cast<char*>(&camera.distance), 4);
		file.read(reinterpret_cast<char*>(&camera.interest), 12);
		file.read(reinterpret_cast<char*>(&camera.rotate), 12);
		file.read(reinterpret_cast<char*>(camera.interpolation), 24);
		file.read(reinterpret_cast<char*>(&camera.fov), 4);
		file.read(reinterpret_cast<char*>(&camera.isPerspective), 1);
	}

	return true;
}
```

### ライト

照明に関するデータも含まれています。カメラと同様に、読み込むだけにします。

```cpp
struct VMDLight
{
	unsigned int frame;
	DirectX::XMFLOAT3 color;
	DirectX::XMFLOAT3 position;
};
```

```cpp
bool ReadLight(VMDFileData& data, std::ifstream& file)
{
	unsigned int count = 0;
	file.read(reinterpret_cast<char*>(&count), 4);

	data.lights.resize(count);
	for (auto& light : data.lights)
	{
		file.read(reinterpret_cast<char*>(&light.frame), 4);
		file.read(reinterpret_cast<char*>(&light.color), 12);
		file.read(reinterpret_cast<char*>(&light.position), 12);
	}

	return true;
}
```

### 影

影についても同様に読み込むだけです。

```cpp
struct VMDShadow
{
	unsigned int frame;
	unsigned char shadowType;
	float distance;
};
```

```cpp
bool ReadShadow(VMDFileData& data, std::ifstream& file)
{
	unsigned int count = 0;
	file.read(reinterpret_cast<char*>(&count), 4);

	data.shadows.resize(count);
	for (auto& shadow : data.shadows)
	{
		file.read(reinterpret_cast<char*>(&shadow.frame), 4);
		file.read(reinterpret_cast<char*>(&shadow.shadowType), 1);
		file.read(reinterpret_cast<char*>(&shadow.distance), 4);
	}

	return true;
}
```

### IK

これはIKに関するデータです。

IKとは何か、そしてデータ構造については、実際に適用する際にまた話すことにします。

```cpp
struct VMDIKInfo
{
	std::wstring name;
	unsigned char enable;
};

struct VMDIK
{
	unsigned int frame;
	unsigned char show;
	std::vector<VMDIKInfo> ikInfos;
};
```

```cpp
bool ReadIK(VMDFileData& data, std::ifstream& file)
{
	unsigned int count = 0;
	file.read(reinterpret_cast<char*>(&count), 4);

	data.iks.resize(count);
	for (auto& ik : data.iks)
	{
		file.read(reinterpret_cast<char*>(&ik.frame), 4);
		file.read(reinterpret_cast<char*>(&ik.show), 1);

		unsigned int ikInfoCount = 0;
		file.read(reinterpret_cast<char*>(&ikInfoCount), 4);

		ik.ikInfos.resize(ikInfoCount);
		for (auto& ikInfo : ik.ikInfos)
		{
			UnicodeUtil::ReadJISToWString(file, ikInfo.name, 20);
			file.read(reinterpret_cast<char*>(&ikInfo.enable), 1);
		}
	}

	return true;
}
```

### まとめ

今回もPMXファイルを読み込む時と同様に、単純にファイルを読み込むコードを追加しました。

次の記事では、読み込んだデータを使ってミクさんを踊らせてみましょう。

# 次回
https://qiita.com/dpals39/items/528aa66a382c6747a35a