/*==================================================================================
 주요 전역변수
		WHEELSIZE : 자동차 바퀴 반경. 커지면 속도가 빨라진다.
		curRLRotationSpeed : 프레임 당 자동차의 좌우 회전 속도. 각도로서 커지면 회적속도가 빨라진다.
		curRLRotationCircle = 0.0  : 자동차바퀴 각도
		curRotSpeed  : 프레임 당 자동차 바퀴가 회전하는 각도. 커지면 속도가 빠라진다.
	     ** 자동차의 프레임당 이동속도 
		             curMovingSpeed = 2.0*PI*WHEELSIZE * curRotSpeed/ 360
====================================================================================*/
#include <stdio.h>
#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glaux.h>
#include <math.h>
#include <stdlib.h>
#include <gl/glut.h>

#define	 PI  3.14159265359f

void drag(int x,int y, int z);//마우스 drag

//자동차의 속도, 방향, 회전 속도 등을 정하는 파라매터
#define WHEELSIZE 5.0f   // wheel의 반경
GLfloat curRLRotationSpeed = 0.0;	// 자동차의 회전 속도  angle/frame_interval
GLfloat curRLRotationCircle = 0.0;	// 자동차바퀴 각도

GLfloat curGear = 0.0; // 기어설정

GLfloat curRotSpeed =0.0; // 바퀴의 회전속도 각도/frame_interval. 자동차의 속도를 나타냄. 
                           // curMovingSpeed = 2*3.14*WHEELSIZE * curRotSpeed / 360


GLint	CUBLIST = 0;	// OpenGL의 List pointer

GLfloat curXPos	= -100.0f;	// 자동차의 현재 위치 X
GLfloat curZPos	= 45.0f;	// 자동차의 현재 위치 Z
GLfloat curMovingSpeed;	// 자동차 속도 pixels/frame interval
double  curOrientation = 0.0;    	// 자동차의 현재 방향

double  curTireAngle	= 0.0f;	// 현재 바퀴의 회전 위치
double TireAngle = 0.0f; // 무조정 자동차 바퀴회전 위치

GLdouble g_Viewer[] = {-120.0f, 120.0f, -45.0f}; // 카메라의 초기위치
GLdouble g_Center[] = {0.0, 0.0f, 0.0};	// 카메라의 초기 초점

GLint	g_View		= 0;	// 시점의 선택용 변수

//	3D Model definition
#define MdlSz 5.0f
#define HMdlSz MdlSz/2.0f
#define GROUND_LIMIT 10000
#define GROUND_LIMITF 10000.0f

GLfloat plane[][3]	= {{MdlSz, 0.0, MdlSz}, {MdlSz, 0, -MdlSz}
						,{-MdlSz, 0.0, -MdlSz}, {-MdlSz, 0.0, MdlSz}};


GLfloat cube[][3]	= {{MdlSz, -MdlSz, MdlSz}, {MdlSz, -MdlSz, -MdlSz}
						,{-MdlSz, -MdlSz, -MdlSz}, {-MdlSz, -MdlSz, MdlSz}
						,{-MdlSz, MdlSz, MdlSz}, {MdlSz, MdlSz, MdlSz}
						,{MdlSz, MdlSz, -MdlSz}, {-MdlSz, MdlSz, -MdlSz}};

GLfloat BLUE[3] = {0.0,0.0,1.0};  
GLfloat YELLOW[3] = {1.0,1.0,0.0};  
GLfloat GREEN[3] = {0.0,1.0,0.0};  
GLfloat RED[3] = {1.0,0.0,0.0};  
GLfloat WHITE[3] = {1.0,1.0,1.0}; 
GLfloat GRAY[3] = {0.5,0.5,0.5};  
GLfloat BLACK[3] = {0.0,0.0,0.0};  
					
void regQuad(int a, int b, int c, int d)
{
	glBegin(GL_POLYGON); 
		glVertex3fv(cube[a]); // 0
		glVertex3fv(cube[b]); // 1
		glVertex3fv(cube[c]); // 2
		glVertex3fv(cube[d]); // 3
		glEnd();
	glEnd();
}

void regCube()
{	
	glNewList(CUBLIST, GL_COMPILE);
		regQuad(0,1,2,3); // 밑면
		regQuad(4,5,6,7);  // 윗면
		regQuad(0,5,4,3); // 앞면
		regQuad(0,1,6,5); //오른쪽 면
		regQuad(4,7,2,3); // 왼쪽면
		regQuad(1,2,7,6); // 뒷면
	glEndList();
	
}

void regModel()
{
	regCube();
}

/*==================================================================================
기능: plane에 정의된 정사각형을 x방향과 z 방향으로 xcale과 zScale 만큼확대하고,
	  Y 축을 중심으로 yRoate 만큼 회전한 다음,
	  xz평면에 (xPos,yPos)만큼 이동한다. 
	  즉 xz 평면 상에 원하는 형태의 면을 생성하여 원하는 장소에 배치한다.
====================================================================================*/
void drawPlane(float xScale, float zScale, float yRotate, float xPos, float zPos)
{
	
	glPushMatrix();	// 행렬 스택에 현재의 행렬을 push
	glTranslatef(xPos,0.0,zPos); // 해당 위치로 이동 
	glRotatef(yRotate,0.0,1.0,0.0); // Y축에 대한 회전
	glScalef(xScale,1.0,zScale);
	glBegin(GL_POLYGON); 
		glVertex3fv(plane[0]); // 0
		glVertex3fv(plane[1]); // 1
		glVertex3fv(plane[2]); // 2
		glVertex3fv(plane[3]); // 3
		glEnd();
	glEnd();
	glPopMatrix();	// 현재 행렬을 그 행렬 스택에서 밖으로 꺼낸다
}
/*==================================================================================
기능: CUBLIST에 정의된 정육면체를 
		x,y,z 방향으로 scaleX, scaleY, scaleZ 만큼 확대하고,
		Y 축을 중심으로 yRoate 만큼 회전하고, 
		xz평면 상의 (xPos,yPos)에 이동한다. 
		즉 정육면체를 다양한 형태의 직육면체로 변형한 다음  WC 상의 지정된 위치에 지정된 
		방위로 배치한다. 		
====================================================================================*/
void placeObject(float scaleX, float scaleY, float scaleZ, float rotateY
				 , float posX, float posY, float posZ) // 건물을 주어진 값에 따라 그리는 함수.
{
	glPushMatrix();	// 행렬 스택에 현재의 행렬을 push
	glTranslatef(posX,posY,posZ); // 해당 위치로 이동 add 월드를 일정 위치로 옮긴다.
    glRotatef(rotateY,0.0,1.0,0.0); // Y축에 대한 회전 add 회전 시킨다.
	glScalef(scaleX,scaleY,scaleZ); // 늘인다.
	glCallList(CUBLIST); // add 오브젝트를 그린다.
	glPopMatrix();	// 현재 행렬을 그 행렬 스택에서 밖으로 꺼낸다 // add 초기 월드 로 복귀.
}

/*==================================================================================
기능: 정육면체를 다양한 형태로 변형하여 건물로 WC 상에 배치한다.
====================================================================================*/
void drawWheel(float scaleX, float scaleY, float scaleZ, float rotateY, float rotateZ
				 , float posX, float posY, float posZ)
// instance transformation: 육면체를 적당히 확대한 다음 WC상에 해당 위치에 지정된 방위로 배치
{
	glPushMatrix();	// 행렬 스택에 현재의 행렬을 push
	glTranslatef(posX,posY,posZ); // 해당 위치로 이동 
    glRotatef(rotateY,0.0,1.0,0.0); // Y축에 대한 회전
	glRotatef(rotateZ,0.0,0.0,1.0); // z축에 관한 회전. tire를 rotate 시킴.
	glScalef(scaleX,scaleY,scaleZ);
	glCallList(CUBLIST);
	glPopMatrix();	// 현재 행렬을 그 행렬 스택에서 밖으로 꺼낸다
}

void modelingCar() // add 자동차 그리기
{
	// draw body : x방향 40, y방향 20, z방향 20
	glColor3f(0.8f,0.0f,0.0f); //앞 범퍼
	placeObject(2.0, 1.0, 2.0, 0.0, 10.0, 0.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	glColor3fv(RED);
    placeObject(3.0, 2.0, 2.0, 0.0, -3.0, 0.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	
	glColor3fv(BLACK); // 뒷 바퀴 - 얇은 정육면체를 일정 앵글로 돌리면서 디스플레이
    drawWheel(1.0, 1.0, 0.5, 0.0,-curTireAngle, -10.0, -5.0, 12.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)3
    drawWheel(1.0, 1.0, 0.5, 0.0, -curTireAngle, -10.0, -5.0, -12.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	glColor3fv(GRAY);
    drawWheel(0.2, 0.2, 2.5, 0.0,-curTireAngle, -10.0, -5.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)3Z)

   	curTireAngle += curRotSpeed; // 앞바퀴
	if (curTireAngle > 360.0) curTireAngle = 0.0;
	glColor3fv(BLACK);
    drawWheel(1.0, 1.0, 0.2, curRLRotationCircle, -curTireAngle, 15.0, -5.0, 12.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
    drawWheel(1.0, 1.0, 0.2, curRLRotationCircle, -curTireAngle, 15.0, -5.0, -12.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	glColor3fv(GRAY);
    drawWheel(0.2, 0.2, 2.5, 0.0,-curTireAngle, 15.0, -5.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)3Z)

	
	glColor3fv(BLUE); // 차동차 의 윗부분 
    placeObject(2.3, 1.0, 1.6, 0.0, -4.0, 10.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	glColor3f(0.0f,0.5f,1.0f); 
    placeObject(0.1, 1.0, 1.6, 0.0, 8.0, 10.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	glColor3f(0.0f,0.5f,1.0f); 
    placeObject(1.6, 0.6, 0.1, 0.0, -6.0, 10.0, 8.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)
	glColor3f(0.0f,0.5f,1.0f); 
    placeObject(1.6, 0.6, 0.1, 0.0, -6.0, 10.0, -8.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, pos
	glColor3f(0.0f,0.5f,1.0f); 
    placeObject(0.1, 1.0, 1.6, 0.0, -12.0, 10.0, 0.0); //scaleX, scaleY, scaleZ, rotateY, posX, posY, posZ)Z)
	
	glPopMatrix();	// 현재 행렬을 그 행렬 스택에서 밖으로 꺼낸다
}

void drawMovingCar() // 움직이는 자동차 
{
	float theta;

	// 이동 거리를 계산: 바퀴의 속도에 비례 
	curMovingSpeed = 2.0f * PI * WHEELSIZE * curRotSpeed/360.0f;
    
    
    // 현재 자동차의 축적된 orientation 계산
	curOrientation += curRLRotationSpeed;
	if (curOrientation > 360.0f)	curOrientation = 0.0f;
	else if (curOrientation < 0.0f) curOrientation = 360.0f;

    // 축적된 자동차의 위치 계산
	theta = -curOrientation*PI/180.0;
	curXPos	+= curMovingSpeed * cos(theta);
	curZPos	+= curMovingSpeed *  sin(theta);
	if (curXPos > GROUND_LIMITF) 		curXPos = GROUND_LIMITF -100.0f;
	else if (curXPos < -GROUND_LIMITF) 	curXPos = GROUND_LIMITF + 100.0f;

	if (curZPos > GROUND_LIMITF) 		curZPos = GROUND_LIMITF -100.0f;
	else if (curZPos < -GROUND_LIMITF) 	curZPos = GROUND_LIMITF + 100.0f;

	
	glPushMatrix();	// 현재 행렬을 그 행렬 스택에서 밖으로 꺼낸다 add 현 상태 저장
	glTranslatef(curXPos,0.0,curZPos); // 해당 위치로 이동 add 자동차 이동 위치 
    glRotatef(curOrientation, 0.0, 1.0, 0.0 ); // Y축에 대한 회전 add 회전
	modelingCar();	
	glPopMatrix();	// 현재 행렬을 그 행렬 스택에서 밖으로 꺼낸다
}

void renderScene()
{	
	glMatrixMode(GL_MODELVIEW); // Modelview 행렬 스택 선택
	glLoadIdentity();			// 현재 변환 행렬을 단위 행렬로 대치 add 월드, 카메라 일치..
	// viewing 변환 정의
	gluLookAt(g_Viewer[0], g_Viewer[1], g_Viewer[2], g_Center[0], g_Center[1], g_Center[2], 0.0, 1.0, 0.0); // add 카메라의 위치 결정! 월드와 카메라의 관계형성..
	
	// 배경 생성
	glPushMatrix();
	glPopMatrix();
	
	drawMovingCar(); // 조작용 차량 생성
}

// 마우스 컨트롤

void mouse(int button, int state, int x, int y)
{
	if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { // 왼쪽 마우스버튼 카메라 원위치
		g_Viewer[0] = -120.0f;
		g_Viewer[1] = 120.0f;
		g_Viewer[2] = -45.0f;

	}
	if(button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) { // 오른쪽 마우스버튼 카메라초점 원위치
		g_Center[0] = 0.0f;
		g_Center[1] = 0.0f;
		g_Center[2] = 0.0f;

	}
}

void drag(int x,int y,int z) // 마우스 드래그시 카메라 위치및 초점 변경
{
	g_Viewer[0] = x; 
	g_Viewer[1] = y; 
	//g_Viewer[2] = z; 
	g_Center[0] = -x; 
	g_Center[1] = -y; 
	//g_Center[2] = -z; 
}

// 키보드 컨트롤
void ConTrol1(unsigned char key, int x, int y) // 키보드 조작 설정1
{
	switch( key )
		{

		case GLUT_KEY_UP : // 엑셀레이터
			if (curGear == 0.0) curRotSpeed+=1.0; 
				else curRotSpeed-=3.0;
			break;

		case GLUT_KEY_DOWN : // 브레이크
				if(curRotSpeed > 0) curRotSpeed-=5.0;
				else if (curRotSpeed < 0) curRotSpeed+=5.0;
		break;

		case GLUT_KEY_LEFT : // 좌회전 회전속도 및 바퀴각도
			if(curRLRotationCircle < 15) { 
				curRLRotationCircle += 1.0;
				curRLRotationSpeed += 0.2;
				} 
			break;

		case GLUT_KEY_RIGHT : // 우회전 회전속도 및 바퀴각도
			if(curRLRotationCircle > -15) { 
				curRLRotationCircle -= 1.0;
				curRLRotationSpeed -= 0.2;
				}
			break;

		}
	
}

void ConTrol2(unsigned char key, int x, int y) // 키보드 조작 설정2
{
	switch( key ) {
	case 'a' : // 카메라 위치 x값 증가
		g_Viewer[0]+=1;
	break;
	
	case 'A' : // 카메라 위치 x값 감소
		g_Viewer[0]-=1;
	break;
	
	case 's' : // 카메라 위치 y값 증가
		g_Viewer[1]+=1;
	break;
	
	case 'S' : // 카메라 위치 y값 감소
		g_Viewer[1]-=1;
	break;

	case 'd' : // 카메라 위치 z값 증가
		g_Viewer[2]+=1;
	break;
	
	case 'D' : // 카메라 위치 z값 감소
		g_Viewer[2]-=1;
	break;
	
	case 'z' : // 카메라 초점 x값 증가
		g_Center[0]+=1;
	break;
	
	case 'Z' : // 카메라 초점 x값 감소
		g_Center[0]-=1;
	break;
	
	case 'x' :  // 카메라 초점 y값 증가
		g_Center[1]+=1;
	break;
	
	case 'X' : // 카메라 초점 y값 감소
		g_Center[1]-=1;
	break;

	case 'c' : // 카메라 초점 z값 증가
		g_Center[2]+=1;
	break;
	
	case 'C' : // 카메라 초점 z값 감소
		g_Center[2]-=1;
	break;

	case ' ' : // spacebar
		if (curGear == 1.0) curGear = 0.0; // 전진기어
		else curGear = 1.0; // 후진기어
	break;
	}
}
	


//		화면을 갱신한다. CALLBACK 함수로 화면의 갱신이 필요할때마다 자동으로
//		호출된다.
void display(void)
{		
	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // RGB color display
	renderScene();		// RenderScene 함수 호출 - 차가다닐 배경
	glFlush();			// 버퍼에 대기하고 있는 명령들 수행
	glutSwapBuffers();	// 앞 buffer에 있는 것을 다 그려준 뒤 뒤에 있는 buffer에 남은 것까지 모두 그려줌
	glFlush();
}

//		윈도우가 invalidate될 경우 projection 및 modelview 매트릭스를 재조정
//		한다.
void myReshape(int w, int h)
{	
	double aspect;	// front clipping plane의 너비(w)를 높이(h)로 나눈 값
	aspect = (h == 0) ? (double)w : (double)w/(double)h;
					 	
	glViewport(0, 0, w, h);	// viewport 위치, 크기 결정 
	glMatrixMode(GL_PROJECTION); // projection 행렬 스택 선택
	glLoadIdentity(); // viewing 변환 정의
	gluPerspective(100.0f, aspect, 1, 5000.0);
	glMatrixMode(GL_MODELVIEW);	 // modelview 행렬 스택 선택
	glLoadIdentity(); // viewing 변환 정의
}

//		program entry point로서 OpenGL의 구동에 필요한 환경을 구성하고 주요
//		이벤트 핸들러 및 각종 CALLBACK 함수들을 정의한다.
//		또한 윈도우를 생성하여 실제 OpenGL이 구현되어 출력할 수 있는 공간을
//		생성하게 된다.
//		발생하는 이벤트들을 잡아내기위한 스레드가 돌아간다.
void main(int argc, char **argv)
{	
	glutInit(&argc, argv);

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); // | GLUT_DEPTH); // RGB color display, duble buffer 사용
	glutInitWindowSize(700, 500);			// 출력에 사용될 윈도우 크기 700*500 
	glutCreateWindow("자동차");		// 윈도우 타이틀 결정 
	glutSpecialFunc(ConTrol1);
	glutKeyboardFunc(ConTrol2);
	glutMouseFunc(mouse);
	glutMotionFunc(drag);
	glutReshapeFunc(myReshape);				// window 설정 Func 호출 		
	glutDisplayFunc(display);				// display Func 호출, 화면 redisplay 
	glutIdleFunc(display);			//'아무 이벤트가 없을 때' 수행될 함수 지정
	CUBLIST = glGenLists(1);
	regModel();	  // register list

	glClearColor(0.0f, 0.4f, 0.9f, 0.0f);	// 윈도우 color 초기화 
	glutMainLoop();	// event 발생시 main Func 반복 
}

