Home | Lesson | Game | Tool | Link

10.簡易3D空間

ファイルからポリゴンの頂点情報を読み込んで描画します。
簡易的な3D空間を作成し、カーソルキーで動き回れるようにします。


構造体定義
頂点情報を格納する構造体を作成します。
上は、三角形ポリゴンの頂点だけを格納する構造体です。
下は、全体のポリゴン数と、それを構成する三角形を格納する構造体です。
ここでは、数が不明なために、要素数が決まっていない点に注意してください。
typedef struct
{
	S3DVertex vertex[3];
}TRIANGLE;

typedef struct
{
	int num;
	TRIANGLE *triangle;
}POLYGON;
POLYGON polygon;

1行読み込み
ファイルを1行読み込む処理です。
読み込んだ行の先頭が以下の場合は、再度読み込むようにしています。
/ :コメントアウトされている行です。
\n:改行されている行です。
※読み込む1行の長さが255byte以上の場合は、正常に処理できないので注意が必要です。
 ワールド情報は1行が255byte以内で作成されているので問題ないですが、
 違ったデータ形式を使用する場合は注意してください。

//1行読み込み
void readstr(FILE *fp, char *str)
{
	do
	{
		fgets(str, 255, fp);
	}while((str[0] == '/') || (str[0] == '\n'));
	return;
}

ワールド情報読み込み
ポリゴンの情報が記載されたワールドファイルを読み込みます。
ワールドファイルには、以下のものが記載されています。
NUMPOLLIES:全ポリゴン数
頂点情報 :X,Y,Z,TU,TV
(ワールドファイルでは、四角形を6つの頂点で定義しています。)
全ポリゴン数がわかった時点で、newで配列の領域を確保しています。
プログラムが終了する際に、deleteで削除するのを忘れないようにしてください。
//ワールド情報読み込み
void LoadWorld()
{
	float x,y,z,u,v;
	int num;
	FILE *fp;
	char oneline[255];
	int i,j;
	if((fp = fopen("world.txt", "rt")) == NULL)
		return;

	readstr(fp, oneline);
	sscanf(oneline, "NUMPOLLIES %d\n", &num);

	polygon.triangle = new TRIANGLE[num];
	polygon.num = num;

構造体セット
ワールドファイルから読み込んだ情報を順番に構造体に格納していきます。
処理が終わったら、ファイルポインタを閉じるのを忘れないようにしてください。
	for(i=0;i<num;i++)
	{
		for(j=0;j<3;j++)
		{
			readstr(fp, oneline);
			sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
			polygon.triangle[i].vertex[j].Pos.X = x;
			polygon.triangle[i].vertex[j].Pos.Y = y;
			polygon.triangle[i].vertex[j].Pos.Z = z;
			polygon.triangle[i].vertex[j].TCoords.X = u;
			polygon.triangle[i].vertex[j].TCoords.Y = v;
			polygon.triangle[i].vertex[j].Color = 0xFFFFFFFF;
		}
	}
	fclose(fp);
	return;
}

描画
読み込んだワールド情報を元に描画します。
ワールド情報には、テクスチャの情報がないので、テクスチャも別途読み込んでおきます。
ポリゴンの数だけ描画します。
void makeScene(IVideoDriver *driver)
{
	int i;
	u16 list[] = {0,1,2};
	S3DVertex ver[3];

	//テクスチャ読み込み
	ITexture* Texture;
	Texture = driver->getTexture("wall.bmp");
	SMaterial Material;
	Material.Lighting = false;
	Material.BackfaceCulling = false;
	Material.TextureLayer[0].Texture = Texture;
	driver->setMaterial(Material);

	for(i=0;i<polygon.num;i++)
	{
		ver[0] = polygon.triangle[i].vertex[0];
		ver[1] = polygon.triangle[i].vertex[1];
		ver[2] = polygon.triangle[i].vertex[2];
		driver->drawIndexedTriangleList(ver, 3, list, 1);
	}
}

カメラ設定
通常のカメラからFPSカメラ(自由に移動可能)に変更しています。
これに伴い、マウスの移動ができなくなるので、マウスカーソルも非表示にしています。
FPSカメラには、作成時点では位置情報を設定できないので、
別途setPositionで位置情報を設定しています。
	//カメラ設定
//	smgr->addCameraSceneNode(0, vector3df(0,0,-5), vector3df(0,0,0));//通常カメラ
	ICameraSceneNode *Camera = smgr->addCameraSceneNodeFPS(0,100,5);//FPSカメラ
	Camera->setPosition(vector3df(0,0,-5));

	device->getCursorControl()->setVisible(false);


addCameraSceneNodeFPS
FPSカメラを設定します
ICameraSceneNode* addCameraSceneNodeFPS(
	ISceneNode* parent             = 0,
	f32         rotateSpeed        = 100.0f,
	f32         moveSpeed          = 500.0f,
	s32         id                 = -1,
	SKeyMap*    keyMapArray        = 0,
	s32         keyMapSize         = 0,
	bool        noVerticalMovement = false,
	f32         jumpSpeed          = 0.f
)
parent             カメラの親を設定します。親が動けばカメラも動きます。
rotateSpeed        回転速度を設定します。
moveSpeed          移動速度を設定します。
id                 IDを設定します。
keyMapArray        カーソルキー以外でもカメラを動かすための設定をします。
keyMapSize         keyMapArrayのサイズを指定します。
noVerticalMovement 水平移動だけを有効にし、垂直の移動を無効にします。
jumpSpeed          ジャンプ時の速度を設定します。

キーマップの割り当て例です。WSADで移動できるように設定してあります。
SKeyMap keyMap[8];
keyMap[0].Action  = EKA_MOVE_FORWARD;
keyMap[0].KeyCode = KEY_UP;
keyMap[1].Action  = EKA_MOVE_FORWARD;
keyMap[1].KeyCode = KEY_KEY_W;

keyMap[2].Action  = EKA_MOVE_BACKWARD;
keyMap[2].KeyCode = KEY_DOWN;
keyMap[3].Action  = EKA_MOVE_BACKWARD;
keyMap[3].KeyCode = KEY_KEY_S;

keyMap[4].Action  = EKA_STRAFE_LEFT;
keyMap[4].KeyCode = KEY_LEFT;
keyMap[5].Action  = EKA_STRAFE_LEFT;
keyMap[5].KeyCode = KEY_KEY_A;

keyMap[6].Action  = EKA_STRAFE_RIGHT;
keyMap[6].KeyCode = KEY_RIGHT;
keyMap[7].Action  = EKA_STRAFE_RIGHT;
keyMap[7].KeyCode = KEY_KEY_D;

Camera = smgr->addCameraSceneNodeFPS(0, 100, 500, -1, keyMap, 8);


読み込みと後始末
メインループ前にワールド情報の読み込みと、
メインループが終わった時点で、newで確保した領域の削除を行っています。
※newで領域確保した場合は、必ずdeleteしてください。
	LoadWorld();

	while(device->run())
	{
		driver->beginScene(true,true,0xFF6060FF);

		//図形作成
		makeScene(driver);

		//シーンの描画
		smgr->drawAll();

		driver->endScene();
	}

	delete []polygon.triangle;
	driver->drop();

ダウンロード
今回作成したファイル一式です。

宿題
1.ワールドファイルにテクスチャ情報も設定してみましょう。
2.四角形を6つの頂点で定義していますが、これを4つの頂点に変更してみましょう。
3.ワールドファイルに法線情報も追加して、光源の影響を受けるようにしてみましょう。