Hatena::Groupgamehell

ゲームプログラムめも日記 このページをアンテナに追加 RSSフィード

けんもほろろ  / オススメゲームプログラム本  / 作ったもの置き場  
ゲーム開発のデザパタまとめ  / めも日記まとめ  

2007-07-17

立方体

[]DでXファイル14:00 DでXファイル4 - ゲームプログラムめも日記 を含むブックマーク はてなブックマーク - DでXファイル4 - ゲームプログラムめも日記 DでXファイル4 - ゲームプログラムめも日記 のブックマークコメント

マテリアルと、そのセット最適化するようにしました。

3D格闘ゲームプログラミングによると、

サブセット」という仕組みを使って最適化するみたい。

 

サブセット

サブセットとは、以下の3つのデータのかたまりです。

  • マテリアル番号
  • 適用する面の開始番号
  • 適用する面の数

MaterialListを読み出すときに、

例えば、

こんなMaterialListである場合、

こんなような組み合わせを作ることによって、

マテリアルの設定の回数を減らして、描画処理を最適化させる方法が、

サブセットということらしいです。

 

ようはランレングス圧縮ですね。

 

DirectXメッシュ読み込みのヘルパーでも同じことをやっているそうです。

それはさておき、今回のデータ構造。

Mesh
├Vertex
├Face
├Material
└ModelSubset

とりあえず、マテリアルまで完了。

 

あとは、

 

うーん、まだまだ先は長い、、。

 

private import std.string;
private import std.file;

template TVector(T) {
	/**
	 * 2次元ベクトル
	 */
	struct Vec2D {
	    union {
	        struct { T x=0, y=0; };
	        T[2]    e;
	    }
	}
	/**
	 * 3次元ベクトル
	 */
	struct Vec3D {
	    union {
	        struct { T x=0, y=0, z=0; };
	        T[3]  e;
	        Vec2D v2;
		}
		void set(T x, T y, T z) { this.x = x;   this.y = y;   this.z = z;   }
		void copy(Vec3D* v)     { this.x = v.x; this.y = v.y; this.z = v.z; }
	}
	/**
	 * 4次元ベクトル
	 */
	struct Vec4D {
	    union {
	        struct { T x=0, y=0, z=0, w=1; };
	        struct { T r, g, b, a; };
	        T[4]  e;
	        Vec2D v2;
	        Vec3D v3;
	    }
	}
	/**
	 * 2x2行列
	 */
	struct Mat2x2 {
	    union {
	        struct { T a00=1, a01=0, a10=0, a11=1; };
	        T[4]     e;
	        Vec2D[2] v;
	    }
	}
	/**
	 * 4x4行列
	 */
	struct Mat4x4 {
	    union {
	        struct { T a00=1, a01=0, a02=0, a03=0,
	                   a10=0, a11=1, a12=0, a13=0,
	                   a20=0, a21=0, a22=1, a23=0,
	                   a30=0, a31=0, a32=0, a33=1; };
	        T[16]    e;
	        Vec4D[4] v;
	    }
	}
}
mixin TVector!(float);

/**
 * 字句解析クラス
 */
class Lexer
{
	/**
	 * 区切り文字
	 */
	private const char[][] _DELIMITERS = [
		"{",
		"}",
		",",
		";",
		];
	private bool     _eof;    // EOFかどうか
	private char[]   _path;   // ファイルパス
	private int      _idx;    // 実行ポインタ
	private char[][] _tokens; // 改行・スペース・デリミタで分割した文字列
	/**
	 * コンストラクタ
	 * @param path ファイルパス
	 */
	public this(char[] path)
	{
		_eof           = false;
		_path          = path;
		_idx           = 0;
		_tokens.length = 1;
		auto lines = splitlines(cast(char[])read(path));
		foreach(i, line; lines)
		{
			foreach(delim; _DELIMITERS)
			{
				if(find(line, delim) != -1)
				{
					// 区切り文字の前後にスペースを入れる
					auto delim2 = format(" %s ", delim);
					line = replace(line, delim, delim2);
				}
			}
			auto ts = split(line);
			foreach(t; ts)
			{
				_tokens ~= t;
			}
		}
	}
	public bool isEof()
	{
		return _eof;
	}
	public char[] getToken()
	{
		_idx++;
		if(_idx >= _tokens.length)
		{
			_eof = true;
			return null;
		}
		return _tokens[_idx];
	}
	public char[] token()
	{
		if(_eof) return null;
		return _tokens[_idx];
	}
	public bool nextToken(char[] str)
	{
		int index = _idx + 1;
		if(index >= _tokens.length) return false;
		return cmp(_tokens[index], str) == 0;
	}
}

/**
 * 頂点クラス
 */
class Vertex
{
public:
	// 定数
	const int MODEL_BLEND_COUNT = 4; // 1つの頂点に適用できるボーンの最大数
public:
	Vec3D pos;                       // 座標
	float weight[MODEL_BLEND_COUNT]; // ボーンウェイト
	ubyte index[MODEL_BLEND_COUNT];  // ボーン番号
	Vec3D normal;                    // 法線
	Vec2D uv;                        // テクスチャ座標(UV)
public:
	this() {}
	void dump()
	{
		printf("Vertex dump:\n");
		printf("  pos=(%f,%f,%f)\n", pos.x, pos.y, pos.z);
	}
}
/**
 * 面クラス
 */
class Face
{
public:
	int[] indices; // 面頂点番号
public:
	this() {}
	void add(int index)    { indices ~= index;      } // インデックスの追加
	int  count()           { return indices.length; } // インデックスの数
	int  getIndex(int idx) { return indices[idx];   }
	void dump()
	{
		printf("Face dump:\n");
		printf("  count:%d ", count());
		printf("(");
		foreach(i; indices)
		{
			printf("%d,", i);
		}
		printf(")\n");
	}
}
/**
 * アニメーション用頂点クラス
 */
class VertexAnimated
{
public:
	Vec3D pos;    // 座標
	Vec3D normal; // 法線
	Vec3D uv;     // テクスチャ座標(UV)
}

/**
 * マテリアルクラス
 */
class Material
{
public:
	float[4] color;    // 基本色(RGBA)
	float    power;    // ハイライト強度
	float[3] specular; // ハイライト色(RGB)
	float[3] emissive; // 発行色(RGB)
	char[]   texture;  // テクスチャ名
	this() {}
	void setColor   (float r, float g, float b, float a) { color[0] = r; color[1] = g; color[2] = b; color[3] = a; }
	void setPower   (float power)                        { this.power = power; }
	void setSpecular(float r, float g, float b)          { specular[0] = r; specular[1] = g; specular[2] = b; }
	void setEmissive(float r, float g, float b)          { emissive[0] = r; emissive[1] = g; emissive[2] = b; }
	void setTexture (char[] texture)                     { this.texture = texture.dup; }
	void dump()
	{
		printf("Material dump:\n");
		printf("  color    = (%f,%f,%f,%f)\n", color[0], color[1], color[2], color[3]);
		printf("  power    = %f\n",            power);
		printf("  specular = (%f,%f,%f)\n",    specular[0], specular[1], specular[2]);
		printf("  emissive = (%f,%f,%f)\n",    emissive[0], emissive[1], emissive[2]);
		printf("  texture  = %.*s\n",          texture);
	}
}

/**
 * サブセット
 */
class ModelSubset
{
public:
	uint materialIndex; // サブセットに適用するマテリアル番号
	uint faceIndex;     // サブセットを開始する面番号
	uint faceCount;     // サブセットに含まれる面数
	/**
	 * コンストラクタ
	 */
	this(uint materialIndex, uint faceIndex)
	{
		this.materialIndex = materialIndex;
		this.faceIndex     = faceIndex;
		this.faceCount     = 1;
	}
	/**
	 * 適用する面をカウントアップ
	 */
	void addFaceCount() { faceCount++; }
	void dump()
	{
		printf("ModelSubset \n");
		printf("  materialIndex = %d\n", materialIndex);
		printf("  faceIndex     = %d\n", faceIndex);
		printf("  faceCount     = %d\n", faceCount);
	}
}

/**
 * メッシュクラス
 */
class Mesh
{
private:
	Vertex[]      _vertices;        // 頂点配列
	Face[]        _faces;           // 面配列
	Material[]    _materials;       // マテリアル配列
	ModelSubset[] _subsets;         // サブセット配列
	int           _subsetIndexPrev; // 1つ前のサブセット番号
public:
	this() { _subsetIndexPrev = -1; }
	Vertex        getVertex(int idx)   { return _vertices[idx];  }
	Face          getFace(int idx)     { return _faces[idx];     }
	Material      getMaterial(int idx) { return _materials[idx]; }
	ModelSubset[] getSubsets()         { return _subsets;        }
	/**
	 * 頂点の生成 TODO:頂点バッファでやる
	 */
	void createVertex(int size)
	{
		_vertices = new Vertex[size];
		foreach(inout v; _vertices)
		{
			v = new Vertex();
		}
	}
	/**
	 * 頂点座標の追加 TODO:頂点バッファでやる
	 */
	void setVertexPos(int idx, float x, float y, float z)
	{
		if(idx < 0 || idx >= _vertices.length) return;
		_vertices[idx].pos.set(x, y, z);
	}
	/**
	 * 面の生成 TODO:インデックスバッファでやる
	 */
	void createFace(int size)
	{
		_faces = new Face[size];
		foreach(inout f; _faces)
		{
			f = new Face();
		}
	}
	/**
	 * 面頂点の追加 TODO:インデックスバッファでやる
	 */
	void setFaceIndices(int idx, int[] indices)
	{
		if(idx < 0 || idx >= _faces.length) return;
		foreach(i; indices)
		{
			_faces[idx].add(i);
		}
	}
	/**
	 * マテリアルの追加
	 */
	void addMaterial(Material material)
	{
		_materials ~= material;
	}
	/**
	 * サブセットの追加
	 */
	void addSubset(uint mIndex, uint fIndex)
	{
		bool bSamePrev   = mIndex == _subsetIndexPrev; // 1つ前と同じかどうか
		_subsetIndexPrev = mIndex;
		if(bSamePrev)
		{
			_subsets[_subsets.length - 1].addFaceCount();
			return;
		}
		// 要素を追加
		ModelSubset subset = new ModelSubset(mIndex, fIndex);
		_subsets ~= subset;
	}
	void dump()
	{
		printf("Mesh dump:\n");
		printf(" nVertex=%d\n", _vertices.length);
		foreach(v; _vertices)
		{
			v.dump();
		}
		printf(" nFace=%d\n", _faces.length);
		foreach(f; _faces)
		{
			f.dump();
		}
		foreach(m; _materials)
		{
			m.dump();
		}
		foreach(s; _subsets)
		{
			s.dump();
		}
	}
}

/**
 * Xファイル読み込みクラス
 */
class XFileLoader
{
private:
	enum {
		_STATE_NONE,                // なし
		_STATE_TEMPLATE,            // テンプレート
		_STATE_HEADER,              // ヘッダ
		_STATE_MESH,                // メッシュ
		_STATE_MESH_NORMALS,        // メッシュ(法線)
		_STATE_MESH_TEXTURE_COORDS, // メッシュ(テクスチャ座標)
		_STATE_MESH_MATERIAL_LIST,  // メッシュ(マテリアル)
		
	};
private:
	Lexer  _lexer; // 字句解析
	int    _state; // 状態
	Mesh[] _meshs; // メッシュ配列
public:
	this(char[] xfile)
	{
		_state  = _STATE_NONE;
		_lexer  = new Lexer(xfile);
		while(!_lexer.isEof())
		{
			switch(_state)
			{
			case _STATE_NONE:
				switch(getToken())
				{
				case "xof":
					printf("xof -");                     // おまじない
					printf(" version:%.*s", getToken()); // バージョン フォーマット
					printf(" bit:%.*s\n", getToken());   // ビット数
					break;
				case "template":
					_state = _STATE_TEMPLATE;
					skipName();
					break;
				case "Header":
					printf("* Header\n");
					_state = _STATE_HEADER;
					break;
				case "Mesh":
					printf("* Mesh\n");
					skipName();
					_meshs ~= new Mesh();
					_state = _STATE_MESH;
					break;
				case "MeshNormals":
					printf("* MeshNormals\n");
					_state = _STATE_MESH_NORMALS;
					break;
				case "MeshTextureCoords":
					printf("* MeshTextureCoords\n");
					_state = _STATE_MESH_TEXTURE_COORDS;
					break;
				case "MeshMaterialList":
					printf("* MeshMaterialList\n");
					_state = _STATE_MESH_MATERIAL_LIST;
					break;
				default:
					break;
				}
				break;
			case _STATE_TEMPLATE:
				loadTemplate();
				_state = _STATE_NONE;
				break;
			case _STATE_HEADER:
				loadHeader();
				_state = _STATE_NONE;
				break;
			case _STATE_MESH:
				loadMesh();
				_state = _STATE_NONE;
				break;
			case _STATE_MESH_NORMALS:
				loadMeshNormals();
				_state = _STATE_NONE;
				break;
			case _STATE_MESH_TEXTURE_COORDS:
				loadMeshTextureCoords();
				break;
			case _STATE_MESH_MATERIAL_LIST:
				loadMeshMaterialList();
				_state = _STATE_NONE;
				break;
			default:
				break;
			}
		}
		_meshs[0].dump();
	}
	Mesh getMesh(int idx) { return _meshs[idx]; }
	Mesh getCurrentMesh() { return _meshs[_meshs.length - 1]; }
	/**
	 * 名前を読み飛ばし
	 */
	void skipName()
	{
		while(!_lexer.isEof() && !_lexer.nextToken("{")) getToken(); 
	}
	void skipNode()
	{
		int cnt = 0;
		if(!_lexer.nextToken("{")) return;
		
		getToken();
		cnt++;
		while(!_lexer.isEof() && cnt > 0)
		{
			if(_lexer.nextToken("{")) cnt++;
			if(_lexer.nextToken("}")) cnt--;
			getToken();
		}
		if(cnt > 0)
		{
			throw new Error("short of '}'"); // '}'の対応があっていない
		}
	}
	void loadTemplate()
	{
		// ノードを読み飛ばし
		skipNode();
	}
	void loadHeader()
	{
		// ノードを読み飛ばし
		skipNode();
	}
	void loadMesh()
	{
		getToken(); // "{" を読み飛ばし
		// 頂点データ読み込み
		auto nIndices = getInt();
		printf(" * nIndices:%d\n", nIndices);
		// メッシュオブジェクト取り出し
		Mesh mesh = getCurrentMesh();
		// 頂点生成
		mesh.createVertex(nIndices);
		loadMeshIndices(nIndices, mesh);

		// 面データ読み込み
		auto nFace = getInt();
		printf(" * nFace:%d\n", nFace);
		// 面生成
		mesh.createFace(nFace);
		loadMeshFaces(nFace, mesh);
	}
	void loadMeshIndices(int nIndices, Mesh mesh)
	{
		for(int i = 0; i < nIndices; i++)
		{
			float x = getFloat();
			float y = getFloat();
			float z = getFloat();
			printf("  - (x, y, z) = (%f, %f, %f)\n", x, y, z);
			mesh.setVertexPos(i, x, y, z);
			switch(getToken())
			{
			case ";": return; // 終わり
			case ",": break;
			}
		}
	}
	void loadMeshFaces(int nFace, Mesh mesh)
	{
		for(int i = 0; i < nFace; i++)
		{
			int count = getInt(); // 面の頂点数
			printf(" - %d:", count);
			int[] nums = new int[count];
			for(int j = 0; j < nums.length; j++)
			{
				nums[j] = getInt();
				printf(" %d", nums[j]);
			}
			mesh.setFaceIndices(i, nums);
			printf("\n");
			switch(getToken())
			{
			case ";": return; // 終わり
			case ",": break;
			}
		}
	}
	void loadMeshMaterialList()
	{
		getToken();                     // "{" 読み飛ばし
		int nMaterial = getInt();       // マテリアルの数
		printf(" - nMaterial = %d\n", nMaterial);
		int nFace     = getInt();       // 面の数(メッシュと一致)
		printf(" - nFace = %d\n", nFace);
		int[] faces   = new int[nFace]; // 面の適用マテリアル番号
		foreach(i, inout f; faces)
		{
			f = getInt();
			printf("  - %d\n", f);
			getCurrentMesh().addSubset(f, i);
		}
		if(_lexer.nextToken(";")) getToken(); // ";" を読み飛ばし
		
		for(int i = 0; i < nMaterial; i++)
		{
			if(!_lexer.nextToken("Material")) return;
			printf("  *Material\n");
			getToken(); // "Material" 読み飛ばし
			loadMaterial();
		}
	}
	void loadMeshNormals()
	{
		getToken();                     // "{" 読み飛ばし
		int nNormals = getInt();        // 法線の数
		printf(" - nNormals = %d\n", nNormals);
		// 頂点法線
		for(int i = 0; i < nNormals; i++)
		{
			float x = getFloat();
			float y = getFloat();
			float z = getFloat();
			printf("  - (x, y, z) = (%f, %f, %f)\n", x, y, z);
			switch(getToken())
			{
			case ";": break; //return; // 終わり
			case ",": break;
			}
		}
		int nFaceNormals = getInt(); // 面法線の数
		printf(" - nFaceNormals = %d\n", nFaceNormals);
		// 面法線
		for(int i = 0; i < nFaceNormals; i++)
		{
			int count = getInt(); // 面の頂点数
			printf(" - %d:", count);
			scope int[] nums = new int[count];
			for(int j = 0; j < nums.length; j++)
			{
				nums[j] = getInt();
				printf(" %d", nums[j]);
			}
			printf("\n");
			switch(getToken())
			{
			case ";": return; // 終わり
			case ",": break;
			}
		}
	}
	void loadMeshTextureCoords()
	{
		getToken();                    // "{" 読み飛ばし
		int nTextureCoords = getInt(); // テクスチャ頂点の数
		// UV座標
		for(int i = 0; i < nTextureCoords; i++)
		{
			float u = getFloat();
			float v = getFloat();
			printf(" - (u, v)=(%f, %f)\n", u, v);
		}
		if(_lexer.nextToken(";")) getToken(); // ";" を読み飛ばし
	}
	void loadMaterial()
	{
		Material m = new Material();
		getCurrentMesh().addMaterial(m);
		getToken(); // "{" 読み飛ばし
		// 基本色
		{
			float r = getFloat();
			float g = getFloat();
			float b = getFloat();
			float a = getFloat();
			printf("   - color = (%f, %f, %f, %f)\n", r, g, b, a);
			m.setColor(r, g, b, a);
			getToken(); // ";" を読み飛ばし
		}
		// ハイライト強度
		{
			float power = getFloat();
			m.setPower(power);
		}
		// ハイライト色
		{
			float r = getFloat();
			float g = getFloat();
			float b = getFloat();
			m.setSpecular(r, g, b);
			getToken(); // ";" を読み飛ばし
		}
		// 発光色
		{
			float r = getFloat();
			float g = getFloat();
			float b = getFloat();
			m.setEmissive(r, g, b);
			getToken(); // ";" を読み飛ばし
		}
		// テクスチャファイル名
		{
			if(!_lexer.nextToken("TextureFilename"))
			{
				getToken(); // "}" 読み飛ばし
				return;
			}
			printf("   * TextureFilename");
			char[] filename = getToken();
			m.setTexture(filename);
			printf("    - Filename:%.*s", filename);
			getToken(); // "}" 読み飛ばし
		}
		getToken(); // "}" 読み飛ばし
	}
	char[] getToken()          { return _lexer.getToken(); }
	int    getInt()            { int   ret = atoi(_lexer.getToken()); getToken(); return ret; }
	float  getFloat()          { float ret = atof(_lexer.getToken()); getToken(); return ret; }
	int    toInt(char[] str)   { return atoi(str); }
	float  toFloat(char[] str) { return atof(str); }
}

void main()
{
	new XFileLoader("model2.x");
}

 

 

あとOpenGLで動くサンプル。

http://www5f.biglobe.ne.jp/~kenmo/dest/mesh/002.zip

Zバッファをとりあえず入れてみました。

 

しかし、頂点バッファはまだまだです。はい。

DK_alphaDK_alpha2007/07/17 16:26調べてみたら、どうもdojaでそのまま読み込めるモデルデータは特殊な形式らしくて、高価なモデルツールでないと作れないっぽいので、自分で解析する必要があるかなと思い始めてます。

kenmokenmo2007/07/17 16:49Blenderで、マスコットカプセルを出力できるみたいですよ。
http://www.blender.org/
ただ、マスコットカプセルver.3までみたい。

メタセコでもレジストすれば、プラグインで出力できそう。
http://dearna.dhs.org/exporth3t/

まー、独自に解析するのも勉強になると思いますがー。