Home | Lesson | Game | Tool | Link

A09.お絵かきツール

ユーザーが好きなように画面にポリゴンを描画します。
ポリゴンで描くお絵かきツールを作成します。
指定した3点(または2点)を結んでポリゴンを描画します。
また、右クリックを押しながらマウスを動かすことで、回転させることもできます。


画面設定
画面サイズを320*240に設定しておきます。(変更可能)
また、様々な場所でこの値を使用するので、defineで定義しておきます。

#define SCREENW 320
#define SCREENH 240

:
:

#ifdef WIN32
	int WINAPI WinMain(HINSTANCE hInst, 
		HINSTANCE hPrevInst, LPSTR strCmdLine, int nShowCmd)
#else
	int main()
#endif
{
	MyEventReceiver Receiver;

	IrrlichtDevice *device = createDevice(EDT_OPENGL,
		dimension2d(SCREENW,SCREENH),16,false,false,false,&Receiver);
	driver = device->getVideoDriver();
	ISceneManager* smgr  = device->getSceneManager();

	device->setWindowCaption(L"Irrlicht");//ウインドウタイトル設定

	//ライト
	smgr->addLightSceneNode(0, vector3df(0,0,-1000), SColorf(0xFFFFFFFF), 1000.0f);

	//カメラ用ターゲット
	ISceneNode *TargetNode = smgr->addEmptySceneNode(0);
	TargetNode->setPosition(vector3df(0,0,0));

	//カメラ設定
	ICameraSceneNode* camera;
	camera = smgr->addCameraSceneNode(TargetNode, 
		vector3df(0,0,-SCREENW/2), vector3df(0,0,0));
	//並行投影設定
	{
		matrix4 matrix;
		matrix.buildProjectionMatrixOrthoLH(
			SCREENW,SCREENH,-SCREENW/2,SCREENW);
		camera->setProjectionMatrix(matrix);
	}
	//draw系初期化
	drawInit(device, driver);

	//リストスタート
	listStart();

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

		TargetNode->setRotation(vector3df(rx,ry,0));

		//シーン作成
		makeScene(driver);

		FillRect(driver,SCREENW/2-40,0,80,10, gColor[gNum]);

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

		driver->endScene();
	}

	//リスト全消去
	listDeleteAll();

	driver->drop();

	return 0;
}

変数/構造体定義
ポリゴンを作成するための変数などを定義しておきます。
構造体はリストで作成しておきます。
s32 gCNT = 0;		//何個目の頂点か
s32 gID = 1;		//ポリゴンのID
S3DVertex vertex[3];	//頂点情報
position2d mouse;		//マウス位置
s32 gNum = 0;		//カラー番号
bool gRect;		//矩形かどうか
SColor gColor[8] = 	//カラー配列
{
	0xFF000000, 0xFF0000FF, 
	0xFF00FF00, 0xFF00FFFF, 
	0xFFFF0000, 0xFFFF00FF, 
	0xFFFFFF00, 0xFFFFFFFF
};
bool cameraMove;		//カメラを動かすかどうか
f32 rx = 0, ry = 0;	//カメラの回転角度

//リスト用構造体
typedef struct _POLYGON
{
	s32 id;
	S3DVertex vertex[3];
	struct _POLYGON *prev;
	struct _POLYGON *next;
}POLYGON;
POLYGON *poly;

LIST操作用関数
ポリゴン情報を操作するLISTの関数を用意します。
LISTの初期化/追加/削除などの基本的なものを用意しておきます。
使い方は、以下の通りです。
1.listStartを呼び出して、初期化します。
2.頂点情報が確定したらlistAddを呼び出して追加します。
 その際、listGetLastを呼び出して、必ず最後に追加するようにします。
3.追加した情報が間違っていたり、削除したい場合は、listDeleteLastを呼び出します。
4.プログラムを終了させる場合は、listDeleteAllを呼び出して全て削除します。
これは、mallocでメモリを割り当てているため、最後に必ずfreeを呼び出すためです。
//項目の初期化
POLYGON* listInit()
{
	POLYGON* polygon;
	polygon = (POLYGON*)malloc(sizeof(POLYGON));
	memset(polygon, 0x00, sizeof(POLYGON));

	return polygon;
}

//リスト開始
void listStart()
{
	poly = listInit();
}

//リストの最後を取得
POLYGON* listGetLast()
{
	POLYGON *p = poly;
	while(p->next != NULL)
	{
		p = p->next;
	}
	return p;
}

//リストの最後に追加
void listAdd(s32 id, S3DVertex vertex[3])
{
	POLYGON *last = listGetLast();

	//項目作成/追加
	POLYGON* newPoly = listInit();
	newPoly->id = id;
	newPoly->vertex[0] = vertex[0];
	newPoly->vertex[1] = vertex[1];
	newPoly->vertex[2] = vertex[2];
	newPoly->prev = last;
	newPoly->next = NULL;

	last->next = newPoly;
}

//指定したリスト項目を削除
void listDelete(POLYGON *polygon)
{
	POLYGON *prev = polygon->prev;
	POLYGON *next = polygon->next;
	//前後関係を調整
	if(prev)prev->next = polygon->next;
	if(next)next->prev = polygon->prev;

	//解放
	if(polygon != poly)
		free(polygon);
}

//リストを全て削除
void listDeleteAll()
{
	POLYGON *p = poly, *next;
	while(p != NULL)
	{
		next = p->next;
		free(p);
		p = next;
	}
	poly = NULL;
}

//リストの最後を削除
void listDeleteLast()
{
	POLYGON *last = listGetLast();
	listDelete(last);
}

シーン作成
LISTに追加されている頂点情報の数だけ、三角形ポリゴンを描画します。
また、現在作成しようとしている図形に応じて、頂点を結ぶヘルプを表示しています。

四角形の場合
最初の頂点とマウスカーソルの位置を結ぶ矩形。

三角形の場合
頂点が1つなら、頂点とマウスカーソルの位置を結ぶ線。
頂点が2つなら、頂点同士を結ぶ線と、頂点からマウスカーソル位置を結ぶ線。

※座標を操作する際、画面の中央が(0,0)となってるので注意してください。
//シーン作成
void makeScene(IVideoDriver *driver)
{
    //属性設定
    SMaterial Material;
    Material.Lighting = false;            //ライト設定
    Material.BackfaceCulling = false;
    driver->setMaterial(Material);

    //三角形作成
    u16 triList[] = {0,1,2};
    POLYGON *p = poly;
    while(p != NULL)
    {
        p = p->next;
        if(p)driver->drawIndexedTriangleList(p->vertex, 3, triList, 1);
    }

    //作成しようとしているのが矩形の場合
    if(gRect)
    {
        if(gCNT == 1)
        {
            DrawRect(driver, vertex[0].Pos.X+SCREENW/2, SCREENH/2-vertex[0].Pos.Y,
                mouse.X-vertex[0].Pos.X-SCREENW/2, mouse.Y-SCREENH/2+vertex[0].Pos.Y,
                0xFFFFFFFF);
        }
    }
    //作成しようとしているのが三角形の場合
    else
    {
        //選択した点とカーソルを線でつなぐ
        if(gCNT == 1)
        {
            DrawLine(driver, vertex[0].Pos.X+SCREENW/2, SCREENH/2-vertex[0].Pos.Y,
                mouse.X, mouse.Y, 0xFFFFFFFF);
        }
        if(gCNT == 2)
        {
            DrawLine(driver, vertex[0].Pos.X+SCREENW/2, SCREENH/2-vertex[0].Pos.Y,
                mouse.X, mouse.Y, 0xFFFFFFFF);
            DrawLine(driver, vertex[1].Pos.X+SCREENW/2, SCREENH/2-vertex[1].Pos.Y,
                mouse.X, mouse.Y, 0xFFFFFFFF);
            DrawLine(driver, vertex[0].Pos.X+SCREENW/2, SCREENH/2-vertex[0].Pos.Y,
                vertex[1].Pos.X+SCREENW/2, SCREENH/2-vertex[1].Pos.Y, 0xFFFFFFFF);
        }
    }
}

頂点作成/補間
クリックした座標を頂点情報にセットします。
四角形を作成する場合は、2つの頂点が決まったらLISTに追加します。
(自動的に、残り2つの頂点を作成し、2回追加を行います。)
三角形を作成する場合は、3つの頂点が決まったらLISTに追加します。
checkVertexは、近くに以前作成した図形の頂点があれば、その頂点を取得します。
//頂点を作成する
void makeVertex(S3DVertex &vertex, f32 x, f32 y, f32 z, SColor col)
{
	//頂点情報セット
	vertex.Pos.X = x;
	vertex.Pos.Y = y;
	vertex.Pos.Z = z;
	vertex.Normal.X = 0.0f;
	vertex.Normal.Y = 0.0f;
	vertex.Normal.Z = -1.0f;
	vertex.Color = col;
}

//頂点を設定する
void setVertex(f32 x, f32 y, f32 z, SColor col)
{
	makeVertex(vertex[gCNT],x,y,z,col);
	gCNT++;

	//矩形の頂点をセットして追加
	if(gRect && gCNT == 2)
	{
		makeVertex(vertex[2],vertex[1].Pos.X,vertex[0].Pos.Y,z,col);
		listAdd(++gID, vertex);

		makeVertex(vertex[2],vertex[0].Pos.X,vertex[1].Pos.Y,z,col);
		listAdd(++gID, vertex);

		gCNT = 0;
	}

	//3頂点が決まったらリストに追加
	if(gCNT == 3)
	{
		listAdd(++gID, vertex);
		gCNT = 0;
	}
}

//近くに頂点があるかチェック
bool checkVertex(s32 mx, s32 my, S3DVertex &ver)
{
	s32 i,x,y;
	s32 range = 10;

	POLYGON *p = poly;
	while(p->next != NULL)
	{
		p = p->next;

		for(i=0;i<3;i++)
		{
			x = p->vertex[i].Pos.X;
			if(x-range < mx && mx < x+range)
			{
				y = p->vertex[i].Pos.Y;
				if(y-range < my && my < y+range)
				{
					ver = p->vertex[i];
					return true;
				}
			}
		}
	}

	return false;
}


レシーバーの設定
キーボード操作
SHIFTキーを押している時は、矩形を作成するようにします。
ESC/DELETEキーが押されたら、現在作成中の頂点を削除します。
作成中の頂点がない場合は、最後に作成した図形を削除します。

マウス操作
左クリックした場所を頂点として扱います。
もし、近くに以前作成した図形の頂点が存在するなら、その頂点を使用するようにします。
右クリックを押しながらマウスを動かすと、カメラが動くようにします。
ホイールを上下に回すと、設定する頂点の色を変更できます。
//レシーバー設定
class MyEventReceiver : public IEventReceiver
{
public:
    virtual bool OnEvent(const SEvent& event)
    {
        if(event.EventType == EET_KEY_INPUT_EVENT)
        {
            if(event.KeyInput.Shift)
            {
                gRect = true;
            }
            else gRect = false;

            if(event.KeyInput.PressedDown)
            {
                switch(event.KeyInput.Key)
                {
                    case KEY_ESCAPE:
                    case KEY_DELETE:
                        //描画途中ならラインを削除
                        if(gCNT > 0)
                            gCNT--;
                        //描画後ならポリゴンを削除
                        else 
                            listDeleteLast();
                        //device->closeDevice();
                        return true;
                    default:
                        return false;
                }
            }
            return true;
        }
        if(event.EventType == EET_MOUSE_INPUT_EVENT)
        {
            switch(event.MouseInput.Event)
            {
                case EMIE_LMOUSE_PRESSED_DOWN:
                    //近くに頂点があれば使用
                    if(bCheck)
                        setVertex(ver.Pos.X, ver.Pos.Y, 0, gColor[gNum]);
                    //なければ自由に設定
                    else
                        setVertex(event.MouseInput.X-SCREENW/2, 
                            SCREENH/2-event.MouseInput.Y, 0, gColor[gNum]);
                    return true;
                case EMIE_LMOUSE_LEFT_UP:
                    return true;

                case EMIE_RMOUSE_PRESSED_DOWN:
                    cameraMove = true;
                    return true;
                case EMIE_RMOUSE_LEFT_UP:
                    rx = 0;
                    ry = 0;
                    cameraMove = false;
                    return true;

                case EMIE_MOUSE_WHEEL:
                    //上回転
                    if(event.MouseInput.Wheel == 1)
                    {
                        gNum++;
                        if(gNum == 8)
                            gNum = 0;
                    }
                    //下回転
                    else if(event.MouseInput.Wheel == -1)
                    {
                        gNum--;
                        if(gNum == -1)
                            gNum = 7;

                    }
                    return true;
                case EMIE_MOUSE_MOVED:
                    mouse.X = event.MouseInput.X;
                    mouse.Y = event.MouseInput.Y;

                    //カメラの回転角度を設定
                    if(cameraMove)
                    {
                        rx = SCREENH/2-mouse.Y;
                        if(rx < -90.0f)rx = -90.0f;
                        if(rx > 90.0f)rx  = 90.0f;

                        ry = -(mouse.X-SCREENW/2);
                        if(ry < -90.0f)ry = -90.0f;
                        if(ry > 90.0f)ry  = 90.0f;
                    }

                    //頂点補完
                    bCheck = checkVertex(mouse.X-SCREENW/2, SCREENH/2-mouse.Y, ver);

                    return true;
                default:
                    return true;
            }
        }
        return false;
    }
private:
    S3DVertex ver;
    bool bCheck;
};


メモ
現在は、XY座標を元に平面に図形を作成しています。
これにZ座標も組み込めば、立体的なポリゴンを作成可能です。
また、LIST構造で作成してあるので、LISTの数だけ頂点情報をファイルに出力すれば
Xファイルを作成することもできるでしょう。

その際の注意点として、隣り合ったポリゴン同士はなるべく順番で
出力するようにすると良いでしょう。
もし順番がバラバラだと、ポリゴンを1個ずつ描画しなければなりません。
しかし、順番になっていたら、drawIndexedTriangleListやdrawIndexedTriangleFanに
順番になっているデータを一気に代入して、一度に描画できる場合もあります。
結果として処理速度の向上にもつながります。

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

宿題
1.キーボードで特定のポリゴンを選択できるようにしましょう。
2.マウスで特定のポリゴンを選択できるようにしましょう。
3.指定したポリゴンを削除できるようにしましょう。