마우스를 이용한 View 회전

예전에 나이스가이님이 질문한 마우스를 이용해 현재 화면을 회전하면서 화면상의 물체를 살펴보는 방법에 대해 간단하게 설명해 보려한다. 전문용어(?)로는 Arcball 기법이라고 하는데, 이 Arcball을 구현하는 방법으로 직접 회전행렬을 계산해서 OpenGL의 glMultMatrix를 이용해 적용하는 방법 하나와 x와 y축에 대한 회전각도만을 계산해서 glRotatef를 이용해 적용하는 방법이 있다. 첫번째 방법은 NeHe의 강좌에 소개되어져있으나 URL은 http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=48 이다. 하지만 필자는 두번째 방법을 이용해 Arcball를 구현해 보려고 한다. 이유인 즉, 첫번째 방법은 OpenGL에서는 기본적으로 행렬 Type와 행렬간의 연산자 함수를 제공해주지 않으므로, 회전행렬을 직접 구하기 위한 행렬 Type을 개발자가 직접 정의해줘야 하고 행렬연산을 만들어줘야하는 번거로움이 있는 반면, 두번째 방법은 단지 x와 y축에 대한 회전각도 값을 실수형으로도 충분하기 때문이다.

글자만 있으면 썰렁하므로 별 의미도 없을법한 최종 결과 스크린샷은 아래와 같다.


화면상에 OpenGL의 GLUquadricObj을 이용하여 실린더를 6개를 그렸으며, 마우스를 이용하여 실린더 6개를 회전시키면서 살펴볼 수 있다. 보다 자세한 조작법은 마우스 오른쪽 버튼을 누른 상태에서 좌우로 마우스를 이동하면 Y축으로 회전되며 상하로 마우스를 이동하면 X축으로 회전한다. 이러한 사용자 액션과 반응을 염두해 두고 코드를 살펴보면 이해하는데 큰 도움이 될 것이다.

먼저 마우스 조작에 따른 Arcball 기법을 적용하기 이전에 화면상에 6개의 실린더를 그려주는 코드는 다음과 같다.

int DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glTranslatef(0.0f, 0.0f, -20.0f);

	bool bToggle = true;
	for(float offset=-5; offset<=5; offset+=2) {
		if(bToggle) glColor3f(0.6f, 0.6f, 1.0f);
		else glColor3f(1.0f, 0.6f, 0.6f);
		bToggle = !bToggle;

		glPushMatrix();
		glTranslatef(offset, 0.0f, 0.0f);
		glTranslatef(0.0f, 0.0f, -8.0f);
		gluCylinder(obj, 1.0f, 1.0f, 16.0f, 38, 4);
		glPopMatrix();
	}

	return TRUE;			
}

코드를 보고 이미 짐작하고 계실 분도 있겠지만, 이 코드는 NeHe의 강좌에서 제공하는 코드를 기본으로 하고 있다. 혹시 필요한 분들을 위해 전체코드를 다운로드 할 수 있도록 하겠다.

이제 위의 코드에서 마우스를 이용해 상하좌우로 회전시키면서 6개의 실린더를 구석구석 관찰하는 코드를 추가해보자.

먼저 마우스의 Drag를 통해 계산될 X축과 Y축에 대한 회전값을 위한 변수는 다음과 같다.

GLfloat xAngle;
GLfloat yAngle;
POINT mouseDownPt;

언급하지 않은 mouseDownPt 변수가 있는데, 이것은 마우스 버튼을 누른 좌표 지점을 저정하기 위한 변수이다. 마우스 버튼을 누른 지점과 마우스 버튼을 누른 상태에서 마우스를 이동하였을때 X축과 Y축으로 얼마만큼 이동되었는지를 계산하고 이 계산된 값을 이용해 xAngle와 yAngle값이 계산된다.

일단 xAngle와 yAngle가 계산되었다고 가정하고 최종적으로 회전을 적용시킨 코드는 가장 앞서 언급한 코드인 DrawGLScene 함수의 수정에 있다.

int DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();							
	glTranslatef(0.0f, 0.0f, -20.0f);

	glRotatef(xAngle, 1.0f,0.0f,0.0f);
	glRotatef(yAngle, 0.0f,1.0f,0.0f);

	bool bToggle = true;
	for(float offset=-5; offset<=5; offset+=2) {
		.
		.
		.
	}

	return TRUE;			
}

새롭게 추가된 오렌지색 두줄의 코드가 전부이다. X축과 Y축의 회전에 맞게 glRotatef의 함수의 인자가 사용되었음을 주의해서 보기 바란다.

이제 마지막으로 마우스의 액션에 따른 실제 xAngle와 yAngle의 값을 계산하는 방법을 알아보도록하자. 이 계산 코드는 마우스 액션에 대한 Event에 위치하게 되는데, MouseDown, MouseMove, MouseUp 이벤트에 그 코드가 존재한다.

	case WM_LBUTTONDOWN:
	{
		mouseDownPt.x = GET_X_LPARAM(lParam);
		mouseDownPt.y = GET_Y_LPARAM(lParam);

		SetCapture(hWnd);

		return 0;
	}

위의 코드는 간단히 앞에서 정의한 mouseDownPt 변수에 가장 처음 마우스 버튼이 눌려진 위치를 저장하고 있으며 SetCapture  API를 호출해서 마우스가 Window를 벗어나도 지속적으로 Mouse Message를 받을수 있도록 하고 있다.

	case WM_MOUSEMOVE:
	{
		if(GetCapture() == hWnd) {
			int X = GET_X_LPARAM(lParam);
			int Y = GET_Y_LPARAM(lParam);

			xAngle += (Y-mouseDownPt.y) / 3.6;
			yAngle += (X-mouseDownPt.x) / 3.6;

	                InvalidateRect(hWnd, NULL, FALSE);

			mouseDownPt.x = X;
			mouseDownPt.y = Y;
		}

		return 0;
	}

위의 코드는 마우스를 이동했을때 호출되는 코드인데, GetCapture를 이용해서 현재 마우스 버튼이 눌러졌는지를 검사하고, 만약 마우스가 눌려진 상태에서 움직였다면 바로 여기서 xAngle와 yAngle를 계산한다. 계산공식을 눈여겨 보길 바란다. 3.6으로 나눠주고 있는데, 이것은 마우스를 1 픽셀 이동했을 경우 1/3.6 회전, 즉 0.27777778도 회전하게 되는데, 이 회전량이 사용자가 가장 자연스럽게 느껴지므로 3.6으로 나눴다. 좀더 회전정도를 강하게 하고 싶다면 이 값을 줄이면 회전이 팍팍!! 되도록 할 수 있다.

	case WM_LBUTTONUP:
	{
		ReleaseCapture();
		return 0;
	}

끝으로 위의 코드는 마우스 버튼을 눌러 이동하면서 물체를 이리저러 살펴보다가 마우스 버튼을 누름을 해제시킬때 발생하는 코드로써, ReleaseCapture API를 호출한다. ReleaseCapture는 SetCapture를 호출한 윈도우가 반드시 호출해줘야 하는 Couple API로써 SetCapture에 의한 마우스 메세지의 독점처리를 해제하는 함수이다.

이상으로 간단하게 마우스를 이용한 Arcball 기법에 대해 살펴보는 것을 마치도록 하며, 아래에 해당 소스를 링크한다.

너 “또라이”지?

오늘 문제의 원인을 알고 내가 나한테 던진 말이다.

“너.. 또라이지?” ㅋㅋ

문제는 MySQL에서 BLOB Type으로 데이터를 저장한 후에 읽어보니 전체 데이터가 아닌 일부만 읽혀지는 것이였다. 테이블에 저장한 그림파일의 크기는 195,881KB. 그런데 읽혀지는 데이터 길이는 65,535KB. 저장할때 잘못된 건지.. 읽어올때 잘못된 것인지 고민하다가 이것저것.. MySQL 환경설정 파일도 건드려 보고 말이다. 환경설정 파일을 보고 MySQL… 이거 제대로 쓸라면 공부할거 너무 많은것 같다.. 라는 부담스런 생각도 들고… 뭐 여튼… 아무리 인터넷을 찾아봐도 BLOB 데이터가 전부 오지 않는 이유는 찾을 수가 없었다. 구글신에게 도움을 요청해도 없다는건…. 뭔가 이유가 “얼토당토” 않은 것이라는 확율이 90%이상이란 말인데… 하지만 여전이 나는 “MySQL 이놈이 분명이 BLOB 데이터의 크기를 제한하고 있는게 분명해. 봐… 65,535KB만 보내주잔아? 즉, 64K만 보내주고 있다구.. 분명 환경설정에서 전송 크기를 늘려주는 환경변수가 있을 것이고 그것만 늘려주면 될것야..”라고 확신하며 그다지 흥미롭지 않은 MySQL 환경 변수만을 뒤지기 시작했으나.. 도대체 이놈이다 싶은 놈이 없었다. 음… 혹시 BLOB의 최대 크기가 64KB아냐? 라는 생각에 찾아보니 그냥 BLOB만 있는게 아니라 TINYBLOB, MEDIUMBLOB, LONGBLOB 라는 Type도 있는게 아닌가.. 각각의 최대 크기를 보니까

  • BLOB는 2^16-1KB
  • TINYBLOB는 2^8-1KB
  • MEDIUMBLOB는 2^24-1KB
  • LONGBLOB는 2^32-1KB

였다. 봐.. 다 “65,535KB보다 크잔아..”라고 자신있게 확신한 후에, 역시 원인은 MySQL의 환경변수에 있어 하고 있는데..  근데 2^16이 몇이지? 라는 생각에 계산기를 뚜드려보니.. 2^16 = 65536 !! 으아~~ ㅜ_ㅜ 저 또라이 맞죠? ㅠ_ㅜ 그데 쓰고보니 또라이는 좀 심했다는 생각에 또 다시 ㅠ_ㅠ

나 이제 퇴근할래.. 지하철 끊기기 전에~