0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DirectX12でミクさんを躍らせてみよう5-PMX ファイル読み込み

Last updated at Posted at 2024-10-01

前回

PMX ファイル読み込み

PMXモデルファイルをロードして、ミクさんを描画できるようにしましょう。

PMXフォーマットは元々MMD(MikuMikuDance)で使用されるフォーマットです。

以前はPMDを使用していましたが、PMDファイルフォーマットから改良されたファイル形式がPMXです。
使用するPMXファイルは自分で探して使用してください。

PMXファイルの構造

PMXファイルは以下のようなデータ構造を持っています。

  • ヘッダー情報
  • モデル情報(名前、コメント)
  • 頂点情報
  • フェイス情報(インデックスと考えてください)
  • テクスチャ情報
  • マテリアル情報
  • ボーン情報
  • モーフ情報
  • ディスプレイフレーム情報(使用しません)
  • リジッドボディ情報
  • ジョイント情報
  • 剛体情報

上記に列挙した順序で情報が並んでいます。

PMXファイルの読み込み

PMXフォーマットはバイナリデータです。
C++のifstreamを使用してPMXファイルを開き、readを通じて必要な情報をバイト単位で読み込むようにします。

	std::ifstream pmxFile{ filePath, (std::ios::binary | std::ios::in) };
	if (pmxFile.fail())
	{
		pmxFile.close();
		return false;
	}

filePathはpmxファイルのパスとファイル名の文字列です。ifstreamを使用してバイナリモード、読み取り専用モードでファイルを開きます。

PMXFileData 構造体を次のように定義しました。

struct PMXFileData
{
	PMXHeader header;
	PMXModelInfo modelInfo;

	std::vector<PMXVertex> vertices;
	std::vector<PMXFace> faces;
	std::vector<PMXTexture> textures;
	std::vector<PMXMaterial> materials;
	std::vector<PMXBone> bones;
	std::vector<PMXMorph> morphs;
	std::vector<PMXDisplayFrame> displayFrames;
	std::vector<PMXRigidBody> rigidBodies;
	std::vector<PMXJoint> joints;
	std::vector<PMXSoftBody> softBodies;
};

ヘッダーの読み込み

PMXファイルのヘッダー情報に関する構造体を次のように定義しました。

struct PMXHeader
{
	std::array<unsigned char, 4> magic;
	float version;
	unsigned char dataLength;
	unsigned char textEncoding;      // 0 = utf-16, 1= utf - 8
	unsigned char addUVNum;          // 0 - 4
	unsigned char vertexIndexSize;   // 1 = byte, 2 = short, 4 = int
	unsigned char textureIndexSize;  // 1 = byte, 2 = short, 4 = int
	unsigned char materialIndexSize;
	unsigned char boneIndexSize;
	unsigned char morphIndexSize;
	unsigned char rigidBodyIndexSize;
};

ヘッダー情報は各パラメーターの型のサイズだけ読み込めば良いです。

ヘッダー情報を読み込むメソッドを私はこのように作成しました。

bool ReadHeader(PMXFileData& data, std::ifstream& file)

最初の4バイトはPMXファイルのマジックナンバーです。PMXファイルであれば、必ず決まった値が最初の4バイトに含まれています。

constexpr std::array<unsigned char, 4> PMX_MAGIC_NUMBER{ 0x50, 0x4d, 0x58, 0x20 };

この値と最初の4バイトの値を比較したとき、一致しない場合はPMXファイル形式ではないため、ファイルの読み込みを中止します。

file.read(reinterpret_cast<char*>(data.header.magic.data()), data.header.magic.size());
	if (data.header.magic != PMX_MAGIC_NUMBER)
	{
		return false;
	}
file.read(reinterpret_cast<char*>(&data.header.version), sizeof(data.header.version));
file.read(reinterpret_cast<char*>(&data.header.dataLength), sizeof(data.header.dataLength));
file.read(reinterpret_cast<char*>(&data.header.textEncoding), sizeof(data.header.textEncoding));

次の4バイトはファイルのバージョン
次の1バイトはデータのサイズ
次の1バイトはテキストエンコーディング

これらのデータは私たちにとって重要な情報ではありません。

file.read(reinterpret_cast<char*>(&data.header.addUVNum), sizeof(data.header.addUVNum));

次の1バイトは追加UVの数です。基本のUV値以外に追加で使用するUVの数です。
後で頂点情報を読み込む際に使用されます。

file.read(reinterpret_cast<char*>(&data.header.vertexIndexSize), sizeof(data.header.vertexIndexSize));
file.read(reinterpret_cast<char*>(&data.header.textureIndexSize), sizeof(data.header.textureIndexSize));
file.read(reinterpret_cast<char*>(&data.header.materialIndexSize), sizeof(data.header.materialIndexSize));
file.read(reinterpret_cast<char*>(&data.header.boneIndexSize), sizeof(data.header.boneIndexSize));
file.read(reinterpret_cast<char*>(&data.header.morphIndexSize), sizeof(data.header.morphIndexSize));
file.read(reinterpret_cast<char*>(&data.header.rigidBodyIndexSize), sizeof(data.header.rigidBodyIndexSize));

その後も順番に1バイトずつ読み込んでいきますが、これらはすべて後で読み込む情報のバイト数です。

モデル情報の読み込み

モデル情報を読み込みましょう。
モデル情報はモデルの名前とコメントの文字列です。

bool ReadModelInfo(PMXFileData& data, std::ifstream& file)
{
	UnicodeUtil::GetPMXStringUTF16(file, data.modelInfo.modelName);
	UnicodeUtil::GetPMXStringUTF8(file, data.modelInfo.englishModelName);
	UnicodeUtil::GetPMXStringUTF16(file, data.modelInfo.comment);
	UnicodeUtil::GetPMXStringUTF8(file, data.modelInfo.englishComment);

	return true;
}

PMXファイルは名前とコメントのUTF16文字列とUTF8文字列を持っているため、それぞれ別々に読み込む必要があります。

UTF16の文字列をstd::wstringに変換するメソッドを作成する必要があります。

bool GetPMXStringUTF16(std::ifstream& _file, std::wstring& output)
{
	std::array<wchar_t, 512> wBuffer{};
	int textSize;

	_file.read(reinterpret_cast<char*>(&textSize), 4);

	_file.read(reinterpret_cast<char*>(&wBuffer), textSize);
	output = std::wstring(&wBuffer[0], &wBuffer[0] + textSize / 2);

	return true;
}

最初の4バイトは、その文字列のバイト数です。
文字列のバイト数を読み込んだ後、その分だけ読み込んでwbufferに保存しましょう。
wstring コンストラクタに開始と終了のポインタを渡して、読み込んだバイトをwstringに変換します。
texSizeを2で割る理由は、UTF16の文字が2バイトだからです。
例えば、texSizeが16の場合、文字数は8個ということになります。

UTF8の文字列はstd::stringに保存します。

bool GetPMXStringUTF8(std::ifstream& _file, std::string& output)
{
    std::array<wchar_t, 512> wBuffer{};
	int textSize;

	_file.read(reinterpret_cast<char*>(&textSize), 4);
	_file.read(reinterpret_cast<char*>(&wBuffer), textSize);

	output = std::string(&wBuffer[0], &wBuffer[0] + textSize);

	return true;
}

頂点情報の読み込み

今回は頂点情報を読み込みましょう。
PMXの頂点構造体は次のように構成されています。

struct PMXVertex
{
	DirectX::XMFLOAT3 position;        
	DirectX::XMFLOAT3 normal;          
	DirectX::XMFLOAT2 uv;              
	DirectX::XMFLOAT4 additionalUV[4]; 

	PMXVertexWeight weightType;
	int boneIndices[4];
	float boneWeights[4];
	DirectX::XMFLOAT3 sdefC;
	DirectX::XMFLOAT3 sdefR0;
	DirectX::XMFLOAT3 sdefR1;

	float edgeMag;
};

頂点情報を読み込むメソッドは次のとおりです。

bool ReadVertex(PMXFileData& data, std::ifstream& file)

最初の4バイトはこのモデルの頂点数です。

unsigned int vertexCount;
file.read(reinterpret_cast<char*>(&vertexCount), 4);
data.vertices.resize(vertexCount);

この頂点数で頂点Vectorをリサイズし、forループで頂点を読み込んでいきます。

for (auto& vertex : data.vertices)
{
}

最初の32バイトは位置、法線、UV値です。

file.read(reinterpret_cast<char*>(&vertex.position), 12);
file.read(reinterpret_cast<char*>(&vertex.normal), 12);
file.read(reinterpret_cast<char*>(&vertex.uv), 8);

次は追加UVですが、先ほどヘッダーを読み込む際にこの追加UVの数を読み込みました。
この数だけ16バイトずつ読み込みます。

for (int i = 0; i < data.header.addUVNum; i++)
{
	file.read(reinterpret_cast<char*>(&vertex.additionalUV[i]), 16);
}

次にボーンのウェイトタイプと現在の頂点にリギングされたボーンのインデックスを読み込みます。

file.read(reinterpret_cast<char*>(&vertex.weightType), 1);

	const unsigned char boneIndexSize = data.header.boneIndexSize;

	switch (vertex.weightType)
	{
		case PMXVertexWeight::BDEF1:
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[0]), boneIndexSize);
			break;
		case PMXVertexWeight::BDEF2:
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[0]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[1]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneWeights[0]), 4);
			break;
		case PMXVertexWeight::BDEF4:
		case PMXVertexWeight::QDEF:
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[0]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[1]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[2]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[3]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneWeights[0]), 4);
			file.read(reinterpret_cast<char*>(&vertex.boneWeights[1]), 4);
			file.read(reinterpret_cast<char*>(&vertex.boneWeights[2]), 4);
			file.read(reinterpret_cast<char*>(&vertex.boneWeights[3]), 4);
			break;
		case PMXVertexWeight::SDEF:
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[0]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneIndices[1]), boneIndexSize);
			file.read(reinterpret_cast<char*>(&vertex.boneWeights[0]), 4);
			file.read(reinterpret_cast<char*>(&vertex.sdefC), 12);
			file.read(reinterpret_cast<char*>(&vertex.sdefR0), 12);
			file.read(reinterpret_cast<char*>(&vertex.sdefR1), 12);
			break;
		default:
			return false;
	}

ここでもヘッダーにあったboneIndexSizeの分だけリギングされたボーンのインデックス値を読み込みます。

スキニングアニメーションについて知らない方であれば、リギング、ウェイト、ボーンが何なのか分からないでしょうが、モデルのアニメーションのためのデータだと考えてください。
スキニングアニメーションについては後ほど扱うことにします。

最後にedgeMagを読み込むと、1つの頂点情報を全て読み込んだことになります。
forループを閉じてください。

  file.read(reinterpret_cast<char*>(&vertex.edgeMag), 4);
}

フェイス情報の読み込み

フェイスは三角形1つのインデックスリストだと考えてください。

struct PMXFace
{
	int vertices[3];
};

読み込むメソッドは次のとおりです。

bool ReadFace(PMXFileData& data, std::ifstream& file)
int faceCount = 0;
file.read(reinterpret_cast<char*>(&faceCount), 4);

faceCount /= 3;
data.faces.resize(faceCount);

最初の4バイトはインデックスの数です。
インデックス3つで1つのフェイスを構成するので、3で割るとフェイスの数になります。

switch (data.header.vertexIndexSize)
{
	case 1:
	{
		std::vector<uint8_t> vertices(faceCount * 3);
		file.read(reinterpret_cast<char*>(vertices.data()), sizeof(uint8_t) * vertices.size());
		for (int32_t faceIdx = 0; faceIdx < faceCount; faceIdx++)
		{
			data.faces[faceIdx].vertices[0] = vertices[faceIdx * 3 + 0];
			data.faces[faceIdx].vertices[1] = vertices[faceIdx * 3 + 1];
			data.faces[faceIdx].vertices[2] = vertices[faceIdx * 3 + 2];
		}
	}
		break;
	case 2:
	{
		std::vector<uint16_t> vertices(faceCount * 3);
		file.read(reinterpret_cast<char*>(vertices.data()), sizeof(uint16_t) * vertices.size());
		for (int32_t faceIdx = 0; faceIdx < faceCount; faceIdx++)
		{
			data.faces[faceIdx].vertices[0] = vertices[faceIdx * 3 + 0];
			data.faces[faceIdx].vertices[1] = vertices[faceIdx * 3 + 1];
			data.faces[faceIdx].vertices[2] = vertices[faceIdx * 3 + 2];
		}
	}
		break;
	case 4:
	{
		std::vector<uint32_t> vertices(faceCount * 3);
		file.read(reinterpret_cast<char*>(vertices.data()), sizeof(uint32_t) * vertices.size());
		for (int32_t faceIdx = 0; faceIdx < faceCount; faceIdx++)
		{
			data.faces[faceIdx].vertices[0] = vertices[faceIdx * 3 + 0];
			data.faces[faceIdx].vertices[1] = vertices[faceIdx * 3 + 1];
			data.faces[faceIdx].vertices[2] = vertices[faceIdx * 3 + 2];
		}
	}
		break;
	default:
			return false;
}

return true;

インデックス値のバイト数もヘッダーに含まれていました。そのサイズだけ読み込めるようにします。
PMXのインデックス値のバイト数は1、2、4バイトのいずれかです。

テクスチャ情報の読み込み

メソッドは次のとおりです。

bool ReadTextures(PMXFileData& data, std::ifstream& file)
{
    unsigned int numOfTexture = 0;
	file.read(reinterpret_cast<char*>(&numOfTexture), 4);

	data.textures.resize(numOfTexture);

	for (auto& texture : data.textures)
	{
		UnicodeUtil::GetPMXStringUTF16(file, texture.textureName);
	}

	return true;
}

最初の4バイトはテクスチャの数です。
テクスチャの数だけテクスチャの名前をstd::wstringとして読み込めるようにします。

マテリアル情報の読み込み

UnityやUnreal Engineを扱ったことがある方なら、マテリアルという言葉は馴染みがあるでしょう。
マテリアル構造体は次のとおりです。

struct PMXMaterial
{
	std::wstring name;
	std::string englishName;

	DirectX::XMFLOAT4 diffuse;
	DirectX::XMFLOAT3 specular;
	float specularPower;
	DirectX::XMFLOAT3 ambient;

	PMXDrawModeFlags drawMode;

	DirectX::XMFLOAT4 edgeColor;
	float edgeSize;

	unsigned int textureIndex;
	unsigned int sphereTextureIndex;
	PMXSphereMode sphereMode;

	PMXToonMode toonMode;
	unsigned int toonTextureIndex;

	std::wstring memo;

	unsigned int numFaceVertices;
};

メソッドは次のとおりです。

bool ReadMaterial(PMXFileData& data, std::ifstream& file)
{
	int numOfMaterial = 0;
	file.read(reinterpret_cast<char*>(&numOfMaterial), 4);

	data.materials.resize(numOfMaterial);

	for (auto& mat : data.materials)
	{
		UnicodeUtil::GetPMXStringUTF16(file, mat.name);
		UnicodeUtil::GetPMXStringUTF8(file, mat.englishName);

		file.read(reinterpret_cast<char*>(&mat.diffuse), 16);
		file.read(reinterpret_cast<char*>(&mat.specular), 12);
		file.read(reinterpret_cast<char*>(&mat.specularPower), 4);
		file.read(reinterpret_cast<char*>(&mat.ambient), 12);

		file.read(reinterpret_cast<char*>(&mat.drawMode), 1);

		file.read(reinterpret_cast<char*>(&mat.edgeColor), 16);
		file.read(reinterpret_cast<char*>(&mat.edgeSize), 4);

		file.read(reinterpret_cast<char*>(&mat.textureIndex), data.header.textureIndexSize);
		file.read(reinterpret_cast<char*>(&mat.sphereTextureIndex), data.header.textureIndexSize);
		file.read(reinterpret_cast<char*>(&mat.sphereMode), 1);

		file.read(reinterpret_cast<char*>(&mat.toonMode), 1);

		if (mat.toonMode == PMXToonMode::Separate)
		{
			file.read(reinterpret_cast<char*>(&mat.toonTextureIndex), data.header.textureIndexSize);
		}
		else if (mat.toonMode == PMXToonMode::Common)
		{
			file.read(reinterpret_cast<char*>(&mat.toonTextureIndex), 1);
		}
		else
		{
			return false;
		}

		UnicodeUtil::GetPMXStringUTF16(file, mat.memo);

		file.read(reinterpret_cast<char*>(&mat.numFaceVertices), 4);
	}

	return true;
}

この部分も実際に使用する際に各パラメータについて話します。

ボーン情報の読み込み

ボーンもかなり複雑です。
ボーンはさまざまなオプションを持つことができます。
PMXモデルのボーンのオプションは以下の通りです。

enum class PMXBoneFlags : uint16_t
{
	TargetShowMode = 0x0001,
	AllowRotate = 0x0002,
	AllowTranslate = 0x0004,
	Visible = 0x0008,
	AllowControl = 0x0010,
	IK = 0x0020,
	AppendLocal = 0x0080,
	AppendRotate = 0x0100,
	AppendTranslate = 0x0200,
	FixedAxis = 0x0400,
	LocalAxis = 0x800,
	DeformAfterPhysics = 0x1000,
	DeformOuterParent = 0x2000,
};

そして、IKで使用するPMXIKLinkも定義します。

struct PMXIKLink
{
	unsigned int ikBoneIndex;
	unsigned char enableLimit;

	DirectX::XMFLOAT3 limitMin;
	DirectX::XMFLOAT3 limitMax;
};

ボーンの構造体は次のとおりです。

struct PMXBone
{
	std::wstring name;
	std::string englishName;

	DirectX::XMFLOAT3 position;
	unsigned int parentBoneIndex;
	unsigned int deformDepth;

	PMXBoneFlags boneFlag;

	DirectX::XMFLOAT3 positionOffset;
	unsigned int linkBoneIndex;

	unsigned int appendBoneIndex;
	float appendWeight;

	DirectX::XMFLOAT3 fixedAxis;
	DirectX::XMFLOAT3 localXAxis;
	DirectX::XMFLOAT3 localZAxis;

	unsigned int keyValue;

	unsigned int ikTargetBoneIndex;
	unsigned int ikIterationCount;
	float ikLimit;

	std::vector<PMXIKLink> ikLinks;
};

メソッド全体は次のとおりです。

bool ReadBone(PMXFileData& data, std::ifstream& file)
{
	unsigned int numOfBone;
	file.read(reinterpret_cast<char*>(&numOfBone), 4);

	data.bones.resize(numOfBone);

	for (auto& bone : data.bones)
	{
		UnicodeUtil::GetPMXStringUTF16(file, bone.name);
		UnicodeUtil::GetPMXStringUTF8(file, bone.englishName);

		file.read(reinterpret_cast<char*>(&bone.position), 12);
		file.read(reinterpret_cast<char*>(&bone.parentBoneIndex), data.header.boneIndexSize);
		file.read(reinterpret_cast<char*>(&bone.deformDepth), 4);

		file.read(reinterpret_cast<char*>(&bone.boneFlag), 2);

		if (((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::TargetShowMode) == 0)
		{
			file.read(reinterpret_cast<char*>(&bone.positionOffset), 12);
		}
		else
		{
			file.read(reinterpret_cast<char*>(&bone.linkBoneIndex), data.header.boneIndexSize);
		}

		if (((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::AppendRotate) ||
			((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::AppendTranslate))
		{
			file.read(reinterpret_cast<char*>(&bone.appendBoneIndex), data.header.boneIndexSize);
			file.read(reinterpret_cast<char*>(&bone.appendWeight), 4);
		}

		if ((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::FixedAxis)
		{
			file.read(reinterpret_cast<char*>(&bone.fixedAxis), 12);
		}

		if ((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::LocalAxis)
		{
			file.read(reinterpret_cast<char*>(&bone.localXAxis), 12);
			file.read(reinterpret_cast<char*>(&bone.localZAxis), 12);
		}

		if ((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::DeformOuterParent)
		{
			file.read(reinterpret_cast<char*>(&bone.keyValue), 4);
		}

		if ((uint16_t)bone.boneFlag & (uint16_t)PMXBoneFlags::IK)
		{
			file.read(reinterpret_cast<char*>(&bone.ikTargetBoneIndex), data.header.boneIndexSize);
			file.read(reinterpret_cast<char*>(&bone.ikIterationCount), 4);
			file.read(reinterpret_cast<char*>(&bone.ikLimit), 4);

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

			bone.ikLinks.resize(linkCount);
			for (auto& ikLink : bone.ikLinks)
			{
				file.read(reinterpret_cast<char*>(&ikLink.ikBoneIndex), data.header.boneIndexSize);
				file.read(reinterpret_cast<char*>(&ikLink.enableLimit), 1);

				if (ikLink.enableLimit != 0)
				{
					file.read(reinterpret_cast<char*>(&ikLink.limitMin), 12);
					file.read(reinterpret_cast<char*>(&ikLink.limitMax), 12);
				}
			}
		}
	}

	return true;
}

ボーンは設定されているオプションに応じて、読み込むか読み込まない情報が異なります。
オプション値を読み込み、ビット演算でオプションに該当するかどうかを検査します。

モーフ情報の読み込み

モーフは顔の表情などをコントロールするための情報です。アニメーションに関連していますね。
モーフには複数のタイプが存在します。

enum class PMXMorphType : uint8_t
{
	Group,
	Position,
	Bone,
	UV,
	AddUV1,
	AddUV2,
	AddUV3,
	AddUV4,
	Material,
	Flip,
	Impluse,
};

そして、モーフはタイプによって使用される情報の構造が異なります。

struct PMXMorph
{
	std::wstring name;
	std::string englishName;

	unsigned char controlPanel;
	PMXMorphType morphType;

	struct PositionMorph
	{
		unsigned int vertexIndex;
		DirectX::XMFLOAT3 position;
	};

	struct UVMorph
	{
		unsigned int vertexIndex;
		DirectX::XMFLOAT4 uv;
	};

	struct BoneMorph
	{
		unsigned int boneIndex;
		DirectX::XMFLOAT3 position;
		DirectX::XMFLOAT4 quaternion;
	};

	struct MaterialMorph
	{
		enum class OpType : uint8_t
		{
			Mul,
			Add,
		};

		unsigned int	materialIndex;
		OpType	opType;
		DirectX::XMFLOAT4 diffuse;
		DirectX::XMFLOAT3 specular;
		float specularPower;
		DirectX::XMFLOAT3 ambient;
		DirectX::XMFLOAT4 edgeColor;
		float edgeSize;
		DirectX::XMFLOAT4 textureFactor;
		DirectX::XMFLOAT4 sphereTextureFactor;
		DirectX::XMFLOAT4 toonTextureFactor;
	};

	struct GroupMorph
	{
		unsigned int morphIndex;
		float weight;
	};

	struct FlipMorph
	{
		unsigned int morphIndex;
		float weight;
	};

	struct ImpulseMorph
	{
		unsigned int rigidBodyIndex;
		unsigned char localFlag;	//0:OFF 1:ON
		DirectX::XMFLOAT3 translateVelocity;
		DirectX::XMFLOAT3 rotateTorque;
	};

	std::vector<PositionMorph> positionMorph;
	std::vector<UVMorph> uvMorph;
	std::vector<BoneMorph> boneMorph;
	std::vector<MaterialMorph> materialMorph;
	std::vector<GroupMorph> groupMorph;
	std::vector<FlipMorph> flipMorph;
	std::vector<ImpulseMorph> impulseMorph;
};

読み込むメソッドは次のとおりです。

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

	data.morphs.resize(numOfMorph);

	for (auto& morph : data.morphs)
	{
		UnicodeUtil::GetPMXStringUTF16(file, morph.name);
		UnicodeUtil::GetPMXStringUTF8(file, morph.englishName);

		file.read(reinterpret_cast<char*>(&morph.controlPanel), 1);
		file.read(reinterpret_cast<char*>(&morph.morphType), 1);

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

		if (morph.morphType == PMXMorphType::Position)
		{
			morph.positionMorph.resize(dataCount);
			for (auto& morphData : morph.positionMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.vertexIndex), data.header.vertexIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.position), 12);
			}
		}
		else if (morph.morphType == PMXMorphType::UV ||
			     morph.morphType == PMXMorphType::AddUV1 || 
			     morph.morphType == PMXMorphType::AddUV2 || 
			     morph.morphType == PMXMorphType::AddUV3 || 
			     morph.morphType == PMXMorphType::AddUV4)
		{
			morph.uvMorph.resize(dataCount);
			for (auto& morphData : morph.uvMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.vertexIndex), data.header.vertexIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.uv), 16);
			}
		}
		else if (morph.morphType == PMXMorphType::Bone)
		{
			morph.boneMorph.resize(dataCount);
			for (auto& morphData : morph.boneMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.boneIndex), data.header.boneIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.position), 12);
				file.read(reinterpret_cast<char*>(&morphData.quaternion), 16);
			}
		}
		else if (morph.morphType == PMXMorphType::Material)
		{
			morph.materialMorph.resize(dataCount);
			for (auto& morphData : morph.materialMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.materialIndex), data.header.materialIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.opType), 1);
				file.read(reinterpret_cast<char*>(&morphData.diffuse), 16);
				file.read(reinterpret_cast<char*>(&morphData.specular), 12);
				file.read(reinterpret_cast<char*>(&morphData.specularPower), 4);
				file.read(reinterpret_cast<char*>(&morphData.ambient), 12);
				file.read(reinterpret_cast<char*>(&morphData.edgeColor), 16);
				file.read(reinterpret_cast<char*>(&morphData.edgeSize), 4);
				file.read(reinterpret_cast<char*>(&morphData.textureFactor), 16);
				file.read(reinterpret_cast<char*>(&morphData.sphereTextureFactor), 16);
				file.read(reinterpret_cast<char*>(&morphData.toonTextureFactor), 16);
			}
		}
		else if (morph.morphType == PMXMorphType::Group)
		{
			morph.groupMorph.resize(dataCount);
			for (auto& morphData : morph.groupMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.morphIndex), data.header.morphIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.weight), 4);
			}
		}
		else if (morph.morphType == PMXMorphType::Flip)
		{
			morph.flipMorph.resize(dataCount);
			for (auto& morphData : morph.flipMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.morphIndex), data.header.morphIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.weight), 4);
			}
		}
		else if (morph.morphType == PMXMorphType::Impluse)
		{
			morph.impulseMorph.resize(dataCount);
			for (auto& morphData : morph.impulseMorph)
			{
				file.read(reinterpret_cast<char*>(&morphData.rigidBodyIndex), data.header.rigidBodyIndexSize);
				file.read(reinterpret_cast<char*>(&morphData.localFlag), 1);
				file.read(reinterpret_cast<char*>(&morphData.translateVelocity), 12);
				file.read(reinterpret_cast<char*>(&morphData.rotateTorque), 12);
			}
		}
		else
		{
			return false;
		}
	}

	return true;
}

ディスプレイフレーム情報の読み込み

この情報は使用しないため、特に説明しません。

struct PMXDisplayFrame
{
	std::wstring name;
	std::string englishName;

	enum class TargetType : uint8_t
	{
		BoneIndex,
		MorphIndex,
	};
	struct Target
	{
		TargetType type;
		unsigned int index;
	};

	enum class FrameType : uint8_t
	{
		DefaultFrame,
		SpecialFrame
	};

	FrameType flag;
	std::vector<Target> targets;
};
bool ReadDisplayFrame(PMXFileData& data, std::ifstream& file)
{
	unsigned int numOfDisplayFrame = 0;
	file.read(reinterpret_cast<char*>(&numOfDisplayFrame), 4);

	data.displayFrames.resize(numOfDisplayFrame);

	for (auto& displayFrame : data.displayFrames)
	{
		UnicodeUtil::GetPMXStringUTF16(file, displayFrame.name);
		UnicodeUtil::GetPMXStringUTF8(file, displayFrame.englishName);

		file.read(reinterpret_cast<char*>(&displayFrame.flag), 1);

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

		displayFrame.targets.resize(targetCount);
		for (auto& target : displayFrame.targets)
		{
			file.read(reinterpret_cast<char*>(&target.type), 1);
			if (target.type == PMXDisplayFrame::TargetType::BoneIndex)
			{
				file.read(reinterpret_cast<char*>(&target.index), data.header.boneIndexSize);
			}
			else if (target.type == PMXDisplayFrame::TargetType::MorphIndex)
			{
				file.read(reinterpret_cast<char*>(&target.index), data.header.morphIndexSize);
			}
			else
			{
				return false;
			}
		}
	}

	return true;
}

剛体、ジョイント、ソフトボディ情報の読み込み

これらはすべて物理シミュレーションに関連する情報です。

今回はこれらが何であるかを説明すると長くなりすぎるため、後で物理エンジンを追加する際に詳しく見ていくことにします。今回は情報をどのように読み込むかだけを見ていきましょう。

剛体の構造体は次のとおりです。

struct PMXRigidBody
{
	std::wstring name;
	std::string englishName;

	unsigned int boneIndex;
	unsigned char group;
	unsigned short collisionGroup;

	enum class Shape : uint8_t
	{
		Sphere,
		Box,
		Capsule
	};

	Shape shape;
	DirectX::XMFLOAT3 shapeSize;
	DirectX::XMFLOAT3 translate;
	DirectX::XMFLOAT3 rotate;

	float mass;
	float translateDimmer;
	float rotateDimmer;
	float repulsion;
	float friction;

	enum class Operation : uint8_t
	{
		Static,
		Dynamic,
		DynamicAndBoneMerge,
	};
	Operation op;
};

読み込むメソッドは次のとおりです。

bool ReadRigidBody(PMXFileData& data, std::ifstream& file)
{
	unsigned int numOfRigidBody = 0;
	file.read(reinterpret_cast<char*>(&numOfRigidBody), 4);

	data.rigidBodies.resize(numOfRigidBody);

	for (auto& rigidBody : data.rigidBodies)
	{
		UnicodeUtil::GetPMXStringUTF16(file, rigidBody.name);
		UnicodeUtil::GetPMXStringUTF8(file, rigidBody.englishName);

		file.read(reinterpret_cast<char*>(&rigidBody.boneIndex), data.header.boneIndexSize);
		file.read(reinterpret_cast<char*>(&rigidBody.group), 1);
		file.read(reinterpret_cast<char*>(&rigidBody.collisionGroup), 2);
		file.read(reinterpret_cast<char*>(&rigidBody.shape), 1);
		file.read(reinterpret_cast<char*>(&rigidBody.shapeSize), 12);
		file.read(reinterpret_cast<char*>(&rigidBody.translate), 12);
		file.read(reinterpret_cast<char*>(&rigidBody.rotate), 12);
		file.read(reinterpret_cast<char*>(&rigidBody.mass), 4);
		file.read(reinterpret_cast<char*>(&rigidBody.translateDimmer), 4);
		file.read(reinterpret_cast<char*>(&rigidBody.rotateDimmer), 4);
		file.read(reinterpret_cast<char*>(&rigidBody.repulsion), 4);
		file.read(reinterpret_cast<char*>(&rigidBody.friction), 4);
		file.read(reinterpret_cast<char*>(&rigidBody.op), 1);
	}

	return true;
}

ジョイントの構造体は次のとおりです。

struct PMXJoint
{
	std::wstring name;
	std::string englishName;

	enum class JointType : uint8_t
	{
		SpringDOF6,
		DOF6,
		P2P,
		ConeTwist,
		Slider,
		Hinge,
	};
	JointType type;
	unsigned int rigidBodyAIndex;
	unsigned int rigidBodyBIndex;

	DirectX::XMFLOAT3 translate;
	DirectX::XMFLOAT3 rotate;

	DirectX::XMFLOAT3 translateLowerLimit;
	DirectX::XMFLOAT3 translateUpperLimit;
	DirectX::XMFLOAT3 rotateLowerLimit;
	DirectX::XMFLOAT3 rotateUpperLimit;

	DirectX::XMFLOAT3 springTranslateFactor;
	DirectX::XMFLOAT3 springRotateFactor;
};

読み込むメソッドは次のとおりです。

bool ReadJoint(PMXFileData& data, std::ifstream& file)
{
	unsigned int numOfJoint = 0;
	file.read(reinterpret_cast<char*>(&numOfJoint), 4);

	data.joints.resize(numOfJoint);

	for (auto& joint : data.joints)
	{
		UnicodeUtil::GetPMXStringUTF16(file, joint.name);
		UnicodeUtil::GetPMXStringUTF8(file, joint.englishName);

		file.read(reinterpret_cast<char*>(&joint.type), 1);
		file.read(reinterpret_cast<char*>(&joint.rigidBodyAIndex), data.header.rigidBodyIndexSize);
		file.read(reinterpret_cast<char*>(&joint.rigidBodyBIndex), data.header.rigidBodyIndexSize);

		file.read(reinterpret_cast<char*>(&joint.translate), 12);
		file.read(reinterpret_cast<char*>(&joint.rotate), 12);

		file.read(reinterpret_cast<char*>(&joint.translateLowerLimit), 12);
		file.read(reinterpret_cast<char*>(&joint.translateUpperLimit), 12);
		file.read(reinterpret_cast<char*>(&joint.rotateLowerLimit), 12);
		file.read(reinterpret_cast<char*>(&joint.rotateUpperLimit), 12);

		file.read(reinterpret_cast<char*>(&joint.springTranslateFactor), 12);
		file.read(reinterpret_cast<char*>(&joint.springRotateFactor), 12);
	}

	return true;
}

ソフトボディの構造体は次のとおりです。

struct PMXSoftBody
{
	std::wstring name;
	std::string englishName;

	enum class SoftBodyType : uint8_t
	{
		TriMesh,
		Rope
	};
	SoftBodyType type;

	unsigned int materialIndex;
	unsigned char group;
	unsigned short collisionGroup;

	enum class SoftBodyMask : uint8_t
	{
		BLink = 0x01,
		Cluster = 0x02,
		HybridLink = 0x04,
	};
	SoftBodyMask flag;

	unsigned int bLinkLength;
	unsigned int numClusters;

	float totalMass;
	float collisionMargin;

	enum class AeroModel : int32_t
	{
		kAeroModelV_TwoSided,
		kAeroModelV_OneSided,
		kAeroModelF_TwoSided,
		kAeroModelF_OneSided,
	};
	unsigned int areoModel;

	float vcf;
	float dp;
	float dg;
	float lf;
	float pr;
	float vc;
	float df;
	float mt;
	float chr;
	float khr;
	float shr;
	float ahr;

	float srhr_cl;
	float skhr_cl;
	float sshr_cl;
	float sr_splt_cl;
	float sk_splt_cl;
	float ss_splt_cl;

	unsigned int v_it;
	unsigned int p_it;
	unsigned int d_it;
	unsigned int c_it;

	float lst;
	float ast;
	float vst;

	struct AnchorRigidBody
	{
		unsigned int rigidBodyIndex;
		unsigned int vertexIndex;
		unsigned char nearMode;
	};
	std::vector<AnchorRigidBody> anchorRigidBodies;

	std::vector<unsigned int> pinVertexIndices;
};

読み込むメソッドは次のとおりです。

bool ReadSoftBody(PMXFileData& data, std::ifstream& file)
{
	unsigned int numOfSoftBody = 0;
	file.read(reinterpret_cast<char*>(&numOfSoftBody), 4);

	data.softBodies.resize(numOfSoftBody);

	for (auto& softBody : data.softBodies)
	{
		UnicodeUtil::GetPMXStringUTF16(file, softBody.name);
		UnicodeUtil::GetPMXStringUTF8(file, softBody.englishName);

		file.read(reinterpret_cast<char*>(&softBody.type), 1);

		file.read(reinterpret_cast<char*>(&softBody.materialIndex), data.header.materialIndexSize);
		file.read(reinterpret_cast<char*>(&softBody.group), 1);
		file.read(reinterpret_cast<char*>(&softBody.collisionGroup), 2);

		file.read(reinterpret_cast<char*>(&softBody.flag), 1);

		file.read(reinterpret_cast<char*>(&softBody.bLinkLength), 4);
		file.read(reinterpret_cast<char*>(&softBody.numClusters), 4);

		file.read(reinterpret_cast<char*>(&softBody.totalMass), 4);
		file.read(reinterpret_cast<char*>(&softBody.collisionMargin), 4);

		file.read(reinterpret_cast<char*>(&softBody.areoModel), 4);

		file.read(reinterpret_cast<char*>(&softBody.vcf), 4);
		file.read(reinterpret_cast<char*>(&softBody.dp), 4);
		file.read(reinterpret_cast<char*>(&softBody.dg), 4);
		file.read(reinterpret_cast<char*>(&softBody.lf) , 4);
		file.read(reinterpret_cast<char*>(&softBody.pr), 4);
		file.read(reinterpret_cast<char*>(&softBody.vc), 4);
		file.read(reinterpret_cast<char*>(&softBody.df), 4);
		file.read(reinterpret_cast<char*>(&softBody.mt), 4);
		file.read(reinterpret_cast<char*>(&softBody.chr), 4);
		file.read(reinterpret_cast<char*>(&softBody.khr), 4);
		file.read(reinterpret_cast<char*>(&softBody.shr), 4);
		file.read(reinterpret_cast<char*>(&softBody.ahr), 4);

		file.read(reinterpret_cast<char*>(&softBody.srhr_cl), 4);
		file.read(reinterpret_cast<char*>(&softBody.skhr_cl), 4);
		file.read(reinterpret_cast<char*>(&softBody.sshr_cl), 4);
		file.read(reinterpret_cast<char*>(&softBody.sr_splt_cl), 4);
		file.read(reinterpret_cast<char*>(&softBody.sk_splt_cl), 4);
		file.read(reinterpret_cast<char*>(&softBody.ss_splt_cl), 4);

		file.read(reinterpret_cast<char*>(&softBody.v_it), 4);
		file.read(reinterpret_cast<char*>(&softBody.p_it), 4);
		file.read(reinterpret_cast<char*>(&softBody.d_it), 4);
		file.read(reinterpret_cast<char*>(&softBody.c_it), 4);

		file.read(reinterpret_cast<char*>(&softBody.lst), 4);
		file.read(reinterpret_cast<char*>(&softBody.ast), 4);
		file.read(reinterpret_cast<char*>(&softBody.vst), 4);

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

		softBody.anchorRigidBodies.resize(anchorCount);
		for (auto& anchor : softBody.anchorRigidBodies)
		{
			file.read(reinterpret_cast<char*>(&anchor.rigidBodyIndex), data.header.rigidBodyIndexSize);
			file.read(reinterpret_cast<char*>(&anchor.vertexIndex), data.header.vertexIndexSize);
			file.read(reinterpret_cast<char*>(&anchor.nearMode), 1);
		}

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

		softBody.pinVertexIndices.resize(pinVertexCount);
		for (auto& pinVertex : softBody.pinVertexIndices)
		{
			file.read(reinterpret_cast<char*>(&pinVertex), data.header.vertexIndexSize);
		}
	}

	return true;
}

データの確認

ここまで準備ができたら、実際にファイルを読み込んでデータが正しく読み取れているか確認してください。

コード自体が長いため、見落としている箇所があるかもしれませんが、問題がなければデータが正しく読み込んでいることを確認できるはずです。

bool LoadPMXFile(const std::wstring& filePath, PMXFileData& fileData)
{
	if (filePath.empty() == true)
	{
		return false;
	}

	std::ifstream pmxFile{ filePath, (std::ios::binary | std::ios::in) };
	if (pmxFile.fail())
	{
		pmxFile.close();
		return false;
	}

	bool result = ReadHeader(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadModelInfo(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadVertex(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadFace(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadTextures(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadMaterial(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadBone(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadMorph(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadDisplayFrame(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadRigidBody(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadJoint(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	result = ReadSoftBody(fileData, pmxFile);
	if (result == false)
	{
		return false;
	}

	pmxFile.close();

	return true;
}

まとめ

今回は、PMXモデルファイルがどのように構成されているか、そしてどのように読み込むべきかを学びました。
特に考えるべき点はなく、既存のフォーマットを読み込むだけなので、退屈な作業です。

後半に出てくる物理シミュレーションに関連するデータについては、特に説明しませんでした。内容が非常に多く、実際に適用するまでどのような役割を果たすデータなのか理解するのが難しいです。そのため、後で実際に使用する際にまた話すことにします。

次の記事では、読み込んだPMXデータを使ってミクさんを画面に描画してみましょう。

次回

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?