PMDをUnityで読み込みたい(13) / UnityのC#スクリプトで構造体を使ってPMDファイルの頂点リストを読み込んでみた

PMDファイルから頂点リストを読み込むよ

何度もお世話になっている「通りすがりの記憶」さんのところの記述によれば
ヘッダ領域の後に続くのは、この頂点リストなのだそうです
頂点リスト部分は以下二つの領域から構成されてるっぽい

  1. DWORD vert_count; // 頂点数
  2. t_vertex vertex[vert_count]; // 頂点データ(38Bytes/頂点)


ははぁなるほど。
t_vertexっていうのはなんぞや?と思ったらこれも記述アリ
以下のメンバからなる構造体らしいです

float pos[3]; // x, y, z // 座標
float normal_vec[3]; // nx, ny, nz // 法線ベクトル
float uv[2]; // u, v // UV座標 // MMDは頂点UV
WORD bone_num[2]; // ボーン番号1、番号2 // モデル変形(頂点移動)時に影響
BYTE bone_weight; // ボーン1に与える影響度 // min:0 max:100
BYTE edge_flag; // 0:通常、1:エッジ無効 // エッジ(輪郭)が有効の場合

じゃあこれをC#なりの構造体にしてみよう!ってことで
以下のような構造体PmdVertexを作ったよ
(名前もt_vertexのまんまにしとけばよかったかも)

// Vertex of PMD
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct PmdVertex
{
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=3)] 
	public float[] pos; // x, y, z // 座標
	
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=3)] 
	public float[] normal_vec;// nx, ny, nz // 法線ベクトル
	
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=2)] 
	public float[] uv; // u, v // UV座標 // MMDは頂点UV

	[MarshalAs( UnmanagedType.ByValArray, SizeConst=2)] 
	public ushort[] bone_num; // ボーン番号1、番号2 // モデル変形時に影響
	// In "C#", we can't use WORD and DWORD. 
	// So, we must use Int and Uint as substitutes for them.
	
	public byte bone_weight; // ボーン1に与える影響度 // min:0 max:100
	public byte edge_flag; // 0:通常、1:エッジ無効 // エッジ(輪郭)が有効の場合

}

なんのことないですね
ただ注意すべきポイントは、WORD型の代わりにushort型を使用しているところ
C#にはWORD型は無いんだけど、ushortで代用できるそうな。
この辺の話は次の記事に説明がありました


①てあ氏:Sheltie書庫室
http://wiki.sh4e.net/?%B7%BF


つーわけで、PMDのヘッダ領域と頂点リスト領域を読み込むPMDReaderクラスは
以下の感じに書けたよ

using UnityEngine;
using System;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;

// Header of PMD
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct PmdHeader
{
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=3)] 
	public char[] magic;

	public float version; // 00 00 80 3F == 1.00

	[MarshalAs( UnmanagedType.ByValArray, SizeConst=20)] 
	public char[] model_name;

	[MarshalAs( UnmanagedType.ByValArray, SizeConst=256)] 
	public char[] comment;
}


// Vertex of PMD
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct PmdVertex
{
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=3)] 
	public float[] pos; // x, y, z // 座標
	
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=3)] 
	public float[] normal_vec;// nx, ny, nz // 法線ベクトル
	
	[MarshalAs( UnmanagedType.ByValArray, SizeConst=2)] 
	public float[] uv; // u, v // UV座標 // MMDは頂点UV

	[MarshalAs( UnmanagedType.ByValArray, SizeConst=2)] 
	public ushort[] bone_num; // ボーン番号1、番号2 // モデル変形(頂点移動)時に影響
	
	public byte bone_weight; // ボーン1に与える影響度 // min:0 max:100
	public byte edge_flag; // 0:通常、1:エッジ無効 // エッジ(輪郭)が有効の場合

}

public class PMDReader : MonoBehaviour {
		
	FileStream fs;
	
	public string inputfile; // Name of Input-Pmd-File 
	
	public PmdHeader pmdheader; // Structure of Header

	public uint vertcount;  // The number of vertcount 
	public PmdVertex[] pmdvertex; // Structure of Vertex
	
	public void Main()
	{

		using(fs = File.OpenRead(inputfile))
		{
			ReadHeader();
			ReadVertexCount();
			pmdvertex = new PmdVertex[vertcount];
			ReadVertexList();
									
		}
			
	}	

	private void ReadVertexCount()
	{
		try
		{
			byte[] varbuf = new byte[Marshal.SizeOf(typeof(uint))];

			fs.Read(varbuf,0,Marshal.SizeOf(typeof(uint)));
			vertcount = BitConverter.ToUInt32(varbuf, 0);

		}			
		catch(EndOfStreamException)
		{
					Debug.Log("end");
		}
		
        Debug.Log("vertcount = "+ vertcount);

	}
	
	
	
	private void ReadVertexList()
	{
		try
		{
							
		   byte[] buffer = new byte[Marshal.SizeOf(typeof(PmdVertex)) * vertcount];
		   for(int i=0;i<vertcount;i++)
		   {
		      fs.Read(buffer,0,Marshal.SizeOf(typeof(PmdVertex)));
			
		      IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PmdVertex)));
		      try 
		      {
		      	Marshal.Copy(buffer, 0, ptr, Marshal.SizeOf(typeof(PmdVertex)));
		      	pmdvertex[i] = (PmdVertex)Marshal.PtrToStructure(ptr, typeof(PmdVertex));
		      }
		      finally
		      {
		          if ( IntPtr.Zero != ptr ) Marshal.FreeHGlobal(ptr);
		      }
		   }
			
		}			
		catch(EndOfStreamException)
		{
		        Debug.Log("end");
		}
			
	}
	
	private void ReadHeader()
	{	
		try
		{
			byte[] buffer = new byte[Marshal.SizeOf(typeof(PmdHeader))];
			fs.Read(buffer,0,Marshal.SizeOf(typeof(PmdHeader)));
			
			IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PmdHeader)));
			try 
			{
			   Marshal.Copy(buffer, 0, ptr, Marshal.SizeOf(typeof(PmdHeader)));
			   pmdheader = (PmdHeader)Marshal.PtrToStructure(ptr, typeof(PmdHeader));
			}
			finally
			{
			  if ( IntPtr.Zero != ptr ) Marshal.FreeHGlobal(ptr);
			}
		}			
		catch(EndOfStreamException)
		{
			Debug.Log("end");
		}
	}
}

Main()の中で

  1. ヘッダ読み込み   : ReadHeader();
  2. 頂点数の読み込み  : ReadVertexCount();
  3. 頂点構造体領域確保 : pmdvertex = new PmdVertex[vertcount];
  4. 頂点データの読み込み: ReadVertexList();

の順で処理を行なっています
「頂点リストの読み込み」は「頂点数の読み込み」と「頂点データの読み込み」という
2つの処理に分けて、別々にメソッドを作成してみました


うーん。とりあえず読み込めればいいやっていうことでこんな風に書いてみたけど
変数・配列とかの生存期間?がよくわかんないな
Main()内で頂点構造体領域の確保を行ってるけど、この領域ってどのタイミングで消されるの?


ようわからんけど、そのへんはおいおいやっていきます。
まぁとにかく、これで頂点数と頂点データ(座標とか)を取得できたよ
じゃあこれらを使って、メッシュとか描けないのかしら
ていうかメッシュ描くのには何が必要なん?


いろいろ疑問が生まれるので次はそういう話。