
// ------------------------------------------------------------------
// xi.cpp - OpenGLの基礎を学ぶために作ったプログラム
// コンパイルにはOpenGLライブラリ必須
// WindowsXP SP2  Visual C++.NET  にて動作確認済み
// written by kenji (2005/09/01)
// http://ruffnex.oc.to/kenji/ 
// ------------------------------------------------------------------

#include <GL/glut.h>
#include <math.h>
#include <time.h>
#include <stdio.h>

// 数学関連ステータス
#define MATH_PI                      (3.14159265358979)
#define MATH_ROOT2                   (1.41421356237309)

// ウィンドウ関連ステータス
#define GL_WINDOW_TITLE              "XI ver 0.1"
#define GL_POS_X                     (100)
#define GL_POS_Y                     (100)
#define GL_SIZE_X                    (320)
#define GL_SIZE_Y                    (240)

// インターバルステータス
#define MY_INTERVAL_TIME             (0.01)

// キューブの初期位置
#define DEFAULT_CUBE_CENTER_POS_X    (0.0)
#define DEFAULT_CUBE_CENTER_POS_Y    (0.0)
#define DEFAULT_CUBE_CENTER_POS_Z    (0.0)

#define DEFAULT_CUBE_CORNER_POS_X    (-0.5)
#define DEFAULT_CUBE_CORNER_POS_Y    (-0.5)
#define DEFAULT_CUBE_CORNER_POS_Z    (-0.5)

#define DEFAULT_CUBE_THETA_X         (0.0)
#define DEFAULT_CUBE_THETA_Y         (0.0)
#define DEFAULT_CUBE_THETA_Z         (0.0)

// 視点位置
#define LOOK_POS_X                   (3.0)
#define LOOK_POS_Y                   (4.0)
#define LOOK_POS_Z                   (5.0)

// 見る方向の位置
#define LOOKTO_POS_X                 (0.0)
#define LOOKTO_POS_Y                 (0.0)
#define LOOKTO_POS_Z                 (0.0)


// ------------------------------------------------------------------
// 入力情報ルーチン
// ------------------------------------------------------------------

#define LOCK_INPUTDATA      (-3)
#define UNLOCK_INPUTDATA    (-2)
#define GET_INPUTDATA       (-1)
#define INPUT_INIT            0
#define INPUT_LEFT            1
#define INPUT_RIGHT           2
#define INPUT_UP              3
#define INPUT_DOWN            4

// 入力情報保持関数
int inputdata(int in)
{
	static int out = INPUT_INIT;
	static int lock = false;

	switch(in)
	{
	case LOCK_INPUTDATA:   // データにロックをかける
		lock = true;
		break;
	case UNLOCK_INPUTDATA: // データのロックを解除する
		lock = false;
		break;
	case GET_INPUTDATA:   // 現在のデータを取得する
		break;
	default:              // データを入力する
		if(lock == false) // ただしロックが解除されている状態に限る
			out = in;
		break;
	}
	
	return out;
}

// スペシャルキー受け取り
void spkeyboard(int key, int x, int y)
{
	switch(key){
		case GLUT_KEY_LEFT:  inputdata(INPUT_LEFT);  break;
		case GLUT_KEY_RIGHT: inputdata(INPUT_RIGHT); break;
		case GLUT_KEY_UP:    inputdata(INPUT_UP);    break;
		case GLUT_KEY_DOWN:  inputdata(INPUT_DOWN);  break;
	}
}

// ------------------------------------------------------------------
// 角度変換ルーチン
// ------------------------------------------------------------------

// 角度からラジアンへ
GLdouble DegToRad(GLdouble Deg)
{
	return ((MATH_PI / 180) * Deg);
}

// ラジアンから角度へ
GLdouble RadToDeg(GLdouble Rad)
{
	return ((180 / MATH_PI) * Rad);
}


// ------------------------------------------------------------------
// 3次元座標に対する演算ルーチン
// ------------------------------------------------------------------

// 代入処理
void Copy3d(GLdouble *dest, GLdouble *src)
{
	for(int i=0; i < 3; i++)
		dest[i] = src[i];
}

// 加算処理
GLdouble *Add3d(GLdouble *a, GLdouble *b)
{
	static GLdouble c[3];
	for(int i=0; i < 3; i++)
		c[i] = a[i] + b[i];
	return c;
}

// 減算処理
GLdouble *Sub3d(GLdouble *a, GLdouble *b)
{
	static GLdouble c[3];
	for(int i=0; i < 3; i++)
		c[i] = a[i] - b[i];
	return c;
}


// ------------------------------------------------------------------
// キューブ生成ルーチン
// ------------------------------------------------------------------

// 回転時の軸設定
#define MAKECUBE_DEF       0
#define MAKECUBE_LINE_X    1
#define MAKECUBE_LINE_Z    2

// Add3dの変形型関数
GLdouble *Add3da(GLdouble *a, GLdouble x, GLdouble y, GLdouble z)
{
	GLdouble b[3] = {x, y, z};
	return Add3d(a, b);
}

// キューブの描画
void DrawCube(GLdouble **vertex)
{
	int face[][4] = {
		{ 0, 1, 2, 3 }, { 1, 5, 6, 2 }, { 5, 4, 7, 6 },
		{ 4, 0, 3, 7 }, { 4, 5, 1, 0 }, { 3, 2, 6, 7 },
	};

	GLdouble color[][3] = {
		{ 1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0 },
		{ 1.0, 1.0, 0.0 }, { 1.0, 0.0, 1.0 }, { 0.0, 1.0, 1.0 },
	};

	glBegin(GL_QUADS);
	for(int i=0; i < 6; i++){
		glColor3dv(color[i]);
		for (int j=0; j < 4; j++)
			glVertex3dv(vertex[face[i][j]]);
	}
	glEnd();
}

// 頂点回転ルーチン
GLdouble *RotatePoint(GLdouble *o, GLdouble x, GLdouble r, int flag)
{
	static GLdouble newpos[3];

	// 移動後の位置を計算
	switch(flag)
	{
	case MAKECUBE_LINE_X:
		newpos[0] = o[0];
		newpos[1] = o[1] + ((MATH_ROOT2 * x) * sin(DegToRad(r)));
		newpos[2] = o[2] + ((MATH_ROOT2 * x) * cos(DegToRad(r)));
		break;
	case MAKECUBE_LINE_Z:
		newpos[0] = o[0] + ((MATH_ROOT2 * x) * cos(DegToRad(r)));
		newpos[1] = o[1] + ((MATH_ROOT2 * x) * sin(DegToRad(r)));
		newpos[2] = o[2];
		break;
	}
	return newpos;
}

// キューブのすべての頂点を回転する関数
void RotatePoints(GLdouble **vertex, int *num,
				  GLdouble *o1, GLdouble *o2, GLdouble x, GLdouble r, int flag)
{
	Copy3d(vertex[num[0]], RotatePoint(o1, x, r+45,  flag));
	Copy3d(vertex[num[1]], RotatePoint(o1, x, r+135, flag));
	Copy3d(vertex[num[2]], RotatePoint(o1, x, r+225, flag));
	Copy3d(vertex[num[3]], RotatePoint(o1, x, r+315, flag));
	Copy3d(vertex[num[4]], RotatePoint(o2, x, r+45,  flag));
	Copy3d(vertex[num[5]], RotatePoint(o2, x, r+135, flag));
	Copy3d(vertex[num[6]], RotatePoint(o2, x, r+225, flag));
	Copy3d(vertex[num[7]], RotatePoint(o2, x, r+315, flag));
}

// キューブ生成関数
void MakeCube(GLdouble **vertex, GLdouble *center, GLdouble *corner)
{

	// 最初に、中心点と角の点から角度のないキューブを生成
	GLdouble v[3];
	Copy3d(v, Sub3d(center, corner));
	Copy3d(vertex[0], Add3da(center, v[0]*(-1), v[1]*(-1), v[2]*(-1)));
	Copy3d(vertex[1], Add3da(center, v[0],      v[1]*(-1), v[2]*(-1)));
	Copy3d(vertex[2], Add3da(center, v[0],      v[1],      v[2]*(-1)));
	Copy3d(vertex[3], Add3da(center, v[0]*(-1), v[1],      v[2]*(-1)));
	Copy3d(vertex[4], Add3da(center, v[0]*(-1), v[1]*(-1), v[2]));
	Copy3d(vertex[5], Add3da(center, v[0],      v[1]*(-1), v[2]));
	Copy3d(vertex[6], Add3da(center, v[0],      v[1],      v[2]));
	Copy3d(vertex[7], Add3da(center, v[0]*(-1), v[1],      v[2]));
}

// キューブ回転関数
void RotateCube(GLdouble **vertex, 
				GLdouble *center, GLdouble *corner, GLdouble *r, int flag)
{
	// vertexに各頂点の座標が入っているので
	// それらの頂点に角度変化を与える

	int num[8];
	GLdouble o1[3], o2[3];

	GLdouble v[3];
	Copy3d(v, Sub3d(center, corner));

	switch(flag)
	{
	case MAKECUBE_LINE_X:
		Copy3d(o1, center);  o1[0] = o1[0] + fabs(v[0]) * (-1);
		Copy3d(o2, center);  o2[0] = o2[0] + fabs(v[0]);
		num[0] = 7; num[1] = 3; num[2] = 0; num[3] = 4;
		num[4] = 6; num[5] = 2; num[6] = 1; num[7] = 5;
		RotatePoints(vertex, num, o1, o2, fabs(v[0]), r[0], MAKECUBE_LINE_X);
		break;
	case MAKECUBE_LINE_Z:
		Copy3d(o1, center);  o1[2] = o1[2] + fabs(v[2]) * (-1);
		Copy3d(o2, center);  o2[2] = o2[2] + fabs(v[2]);
		num[0] = 2; num[1] = 3; num[2] = 0; num[3] = 1;
		num[4] = 6; num[5] = 7; num[6] = 4; num[7] = 5;
		RotatePoints(vertex, num, o1, o2, fabs(v[0]), r[2], MAKECUBE_LINE_Z);
		break;
	}
}


// ------------------------------------------------------------------
// サイコロ移動演算ルーチン
// ------------------------------------------------------------------

#define DIRECT_X_AHEAD    1
#define DIRECT_X_BACK     2
#define DIRECT_Z_AHEAD    3
#define DIRECT_Z_BACK     4

// サイコロ移動後の、中心点への距離を求める関数
GLdouble *OperationXi(int direct, GLdouble *center, GLdouble *corner, GLdouble r)
{
	static GLdouble newcenter[3];

	GLdouble o[3]; // 中心点
	GLdouble *v = Sub3d(center, corner);

	switch(direct)
	{
	case DIRECT_X_AHEAD: // X加算方向へ進む
		Copy3d(o, Add3da(center, v[0], v[1]*(-1), 0.0));
		Copy3d(newcenter, RotatePoint(o, fabs(v[0]), r+135, MAKECUBE_LINE_Z));
		break;

	case DIRECT_X_BACK: // X減算方向へ進む
		Copy3d(o, Add3da(center, v[0]*(-1), v[1]*(-1), 0.0));
		Copy3d(newcenter, RotatePoint(o, fabs(v[0]), r+45, MAKECUBE_LINE_Z));
		break;

	case DIRECT_Z_AHEAD: // Z加算方向へ進む
		Copy3d(o, Add3da(center, 0.0, v[1]*(-1), v[2]));
		Copy3d(newcenter, RotatePoint(o, fabs(v[0]), r+135, MAKECUBE_LINE_X));
		break;

	case DIRECT_Z_BACK: // Z減算方向へ進む
		Copy3d(o, Add3da(center, 0.0, v[1]*(-1), v[2]*(-1)));
		Copy3d(newcenter, RotatePoint(o, fabs(v[0]), r+45, MAKECUBE_LINE_X));
		break;
	}

	// 「現在の中心点」から「移動後の中心点」への距離をnewcenerへ入れる
	Copy3d(newcenter, Sub3d(newcenter, center));
	return newcenter;
}


// ------------------------------------------------------------------
// 描画ルーチン
// ------------------------------------------------------------------

// ディスプレイ描画処理
void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// キューブの初期位置の設定

	static GLdouble center[3] = { 
		DEFAULT_CUBE_CENTER_POS_X,  
		DEFAULT_CUBE_CENTER_POS_Y, 
		DEFAULT_CUBE_CENTER_POS_Z
	};

	static GLdouble corner[3] = {
		DEFAULT_CUBE_CORNER_POS_X, 
		DEFAULT_CUBE_CORNER_POS_Y,
		DEFAULT_CUBE_CORNER_POS_Z
	};

	static GLdouble theta[3]  = { 
		DEFAULT_CUBE_THETA_X,
		DEFAULT_CUBE_THETA_Y,
		DEFAULT_CUBE_THETA_Z
	};

	// キューブの各頂点の座標を入れる配列
	GLdouble vertex[8][3];
	GLdouble *vertexptr[8];
	for(int i=0; i < 8; i++)
		vertexptr[i] = vertex[i];

	// 移動フラグ（移動中なら1、停止中なら0）
	static int moveflag = 0;

	// 入力データを取得
	int key = inputdata(GET_INPUTDATA);

	// キューブ停止中
	if(moveflag == 0){

		// 移動処理の発動
		if(key == INPUT_LEFT || key == INPUT_RIGHT || key == INPUT_UP || key == INPUT_DOWN){
			inputdata(LOCK_INPUTDATA); // 入力データのロック
			moveflag = 1;              // 移動中フラグをON
		}
		
		// 描画処理の発動（停止時）
		else{
			// キューブの描画
			MakeCube(vertexptr, center, corner);
			RotateCube(vertexptr, center, corner, theta, MAKECUBE_LINE_Z);
			DrawCube(vertexptr);
		}
	}
	
	// キューブ移動中
	if(moveflag == 1){

		static GLdouble r = 0; // キューブの角度
		
		// キューブの角度を変化
		r += ((key == INPUT_LEFT || key == INPUT_UP) ? 1 : -1);

		GLdouble *move;       // 移動量
		GLdouble dmytheta[3]; // 回転量

		// 現在地までの回転量を取得
		Copy3d(dmytheta, theta);

		switch(key)
		{ // 入力されたキーに対して、処理を実行
		case INPUT_LEFT:
			move = OperationXi(DIRECT_X_BACK, center, corner, r);
			dmytheta[2] += r;
			break;
		case INPUT_RIGHT:
			move = OperationXi(DIRECT_X_AHEAD, center, corner, r);
			dmytheta[2] += r;
			break;
		case INPUT_UP:
			move = OperationXi(DIRECT_Z_BACK, center, corner, r);
			dmytheta[0] += r;
			break;
		case INPUT_DOWN:
			move = OperationXi(DIRECT_Z_AHEAD, center, corner, r);
			dmytheta[0] += r;
			break;
		}

		// 移動時のデータを保存
		GLdouble dmycenter[3], dmycorner[3];
		Copy3d(dmycenter, Add3d(center, move)); // 移動後の中心点
		Copy3d(dmycorner, Add3d(corner, move)); // 移動後の角点

		// キューブ描画
		MakeCube(vertexptr, dmycenter, dmycorner);
		RotateCube(vertexptr, dmycenter, dmycorner, dmytheta, MAKECUBE_LINE_Z);
		DrawCube(vertexptr);

		// 90度回転したら、移動処理を終了
		if(r <= -90.0 || 90.0 <= r){
			Copy3d(center, dmycenter);   // 移動後の中心点を保存
			Copy3d(corner, dmycorner);   // 移動後の角点を保存
			Copy3d(theta, dmytheta);     // 移動後の角度を保存
			inputdata(UNLOCK_INPUTDATA); // 入力データのロック解除
			inputdata(INPUT_INIT);       // 入力データの初期化
			r = 0;                       // 角度を初期化
			moveflag = 0;                // 移動フラグをOFF
		}
	}

	glutSwapBuffers();
}


// ------------------------------------------------------------------
// インターバルルーチン
// ------------------------------------------------------------------

// ウェイト関数
void sleepf(float intv)
{
	clock_t time1, time2, interval;
	interval = CLOCKS_PER_SEC * intv;
	time1 = time2 = clock();
	while(time2 - time1 < interval)
		time2 = clock();
}

// アイドル関数
void idle(void)
{
	sleepf(MY_INTERVAL_TIME);
	glutPostRedisplay();
}


// ------------------------------------------------------------------
// ウィンドウリサイズルーチン
// ------------------------------------------------------------------

// リサイズ処理
void resize(int w, int h)
{
	// ウィンドウ全体をビューポートにする
	// ビューボードとは、開いたウィンドウの中で実際に描画が
	// 行われる領域のこと
	// wとhにはそれぞれウィンドウの横幅と縦幅が入っているので、
	// (0, 0)から(w, h)をビューボードとすることで
	// ウィンドウ全体がビューボードとなる
	glViewport(0, 0, w, h);

	// 視点の設定
	// 第1引数が、視野（角度）
	// 第2引数が、縦横比
	// 第3、第4引数は、図形を表示する手前の限界値と奥の限界値
	// 以下の設定だと、
	// 視野30度で、1.0先から100.0先までに存在する図形を表示する
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(30.0, (double)w / (double)h, 1.0, 100.0);

	// 視点の位置を、
	// (x, y, z) = (LOOK_POS_X, LOOK_POS_Y, LOOK_POS_Z) へ移動して
	// Y軸を上と考えて、(LOOKTO_POS_X, LOOKTO_POS_Y, LOOKTO_POS_Z)を
	// 見る視点とする
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(LOOK_POS_X, LOOK_POS_Y, LOOK_POS_Z, 
		LOOKTO_POS_X, LOOKTO_POS_Y, LOOKTO_POS_Z, 0.0, 1.0, 0.0);
}


// ------------------------------------------------------------------
// スタートアップルーチン
// ------------------------------------------------------------------

// 初期化処理
void init(void)
{
	// ウィンドウを塗りつぶす際の色を指定（RGBで指定）
	// (1.0, 1.0, 1.0)は白
	glClearColor(1.0, 1.0, 1.0, 1.0);

	// 隠面消去処理を行う
	// 立方体の面は描画処理が行われた順に表示されるため
	// 視点からみて奥の面よりも手前の面が先に描画された場合
	// 次の奥の面が描画されるときに、手前の面が上書きされてしまう
	// これを防ぐために隠面消去処理を行う
	glEnable(GL_DEPTH_TEST);

	// 立方体の内側の面は
	// 陰面消去処理をする必要がないので外側だけに設定する
	glEnable(GL_CULL_FACE);
	glCullFace(GL_FRONT);
}

// メイン関数
int main(int argc, char *argv[])
{
	// ウィンドウ位置とサイズの設定
	glutInitWindowPosition(GL_POS_X, GL_POS_Y);
	glutInitWindowSize(GL_SIZE_X, GL_SIZE_Y);

	// GLUTおよびOpenGL環境を初期化
	glutInit(&argc, argv);

	// ディスプレイの表示モード指定
	// GLUT_RGBAを指定すると色のRGBで指定できる
	// GLUT_DOUBLEはダブルバッファリングを行うために指定
	// GLUT_DEPTHは隠面消去処理
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);

	// ウィンドウを開く
	// 引数にウィンドウタイトルを渡す
	glutCreateWindow(GL_WINDOW_TITLE);

	// 関数呼び出しポインタ設定処理
	glutDisplayFunc(display);    // ウィンドウ描画
	glutReshapeFunc(resize);     // ウィンドウリサイズ
	glutSpecialFunc(spkeyboard); // スペシャルキー入力
	glutIdleFunc(idle);          // アイドル状態

	// 初期化処理（init関数参照）
	init();

	// 処理ループ
	glutMainLoop();
	return 0;
}

// ----------------------------------------------------------- EOF --

