마우스처리

앞 장에서 우리는 GLUT 의 키보드 입력처리 기능을 사용해서 OpenGL 어플리케이션과 대화하는(상호작용)하는 방법을 배웠습니다. 이제 키보드 외에 입력도구로 많이 사용하는 마우스에 대해서 알아봅시다. GLUT 의 마우스 인터페이스는 마우스를 사용하는데 필요한 기능들을 많이 가지고 있습니다. 이 마우스 인터페이스를 사용하면 마우스 버튼의 클릭여부와 마우스의 움직임을 감지할 수 있습니다.

마우스 클릭 감지

키보드 입력처리에서 본 것처럼 GLUT 는 마우스 관련 이벤트를 처리하는데 콜백함수를 사용합니다. 물론 콜백함수를 등록해주는 함수가 있습니다. 마우스의 클릭 처리를 위한 콜백함수는 glutMouseFunc 함수로 등록하며 대부분 어플리케이션의 초기화부분에서 호출합니다. 다음은 이 함수의 설명입니다.:

void glutMouseFunc(void (*func)(int button, int state, int x, int y)); 

인자 설명:
func - 마우스클릭 이벤트를 처리하는 함수의 이름입니다.

위의 glutMouseFunc 함수를 보면 알 수 있듯이, 마우스의 클릭 이벤트를 처리하는 함수는 네 개의 인자를 갖습니다. 첫번째 인자는 어떤 버튼이 눌렸는지 혹은 놓여졌는지에 관한 것인데 다음 세가지의 값중에 한가지가 될 수 있습니다. :

  • GLUT_LEFT_BUTTON

  • GLUT_MIDDLE_BUTTON

  • GLUT_RIGHT_BUTTON

두 번째 인자는 콜백함수가 호출됐을 때 마우스 버튼의 상태를 말해줍니다. 즉, 이 값을 통해서 버튼이 눌렸는지 아니면 놓여져있는지를 알 수 있는 것이죠. 다음 값 중에 한가지가 될 수 있습니다. :

  • GLUT_DOWN

  • GLUT_UP

마우스 버튼의 상태가 GLUT_DOWN 일 때 콜백함수가 호출되면 어플리케이션은 "다음 마우스 버튼의 상태는 GLUT_UP 이 되겠지" 라고 가정합니다. 이 것은 마우스의 움직임이 창 밖에서 일어나고 있어도 마찬가지입니다. 그러나 어플리케이션이 glutMouseFunc 의 인자에 NULL 값을 주어 다시 호출하면 GLUT 가 마우스 버튼의 상태를 변경하는 것을 막을 수 있습니다.

나머지 두 개의 인자는 마우스의 좌표인데 OpenGL 창의 클라이언트 영역에 상대적인 좌표값입니다.

마우스 모션(motion, 움직임) 감지

GLUT 로 마우스 모션을 감지할 수 있습니다. GLUT 가 처리하는 마우스 모션에는 두가지 종류가 있는데 능동모션과 수동모션입니다. 능동모션은 마우스 버튼을 누른채 마우스를 움직이는 것이고 수동모션은 마우스 버튼을 누르지 않은채 마우스를 움직이는 것입니다. 어플리케이션이 마우스 모션을 계속해서 추적하면 모션 이벤트는 마우스가 움직일동안 매 프레임마다 발생됩니다.

다른 이벤트처리와 마찬가지로 마우스 모션 이벤트를 처리하려면 관련 콜백함수를 등록해야합니다. GLUT 는 두 개의 함수를 제공하는데 하나는 수동모션을 처리하는 함수이고 다른 하나는 능동모션을 처리하는 함수입니다.

다음은 이 함수들의 설명입니다. :

void glutMotionFunc(void (*func) (int x,int y)); 
void glutPassiveMotionFunc(void (*func) (int x, int y)); 

인자 설명:
func - 모션처리를 위한 함수의 이름입니다.

모션을 처리하는 함수의 인자인 x, y 는 마우스의 좌표인데 OpenGL 창의 클라이언트 영역에 상대적인 좌표값입니다.

마우스가 OpenGL 창에 내부에 있는지 아니면 외부에 있는지 판단하기

GLUT 를 사용하면 마우스가 OpenGL 창의 내부 있는지 아니면 외부에 있는지를 쉽게 알 수 있습니다. 두 경우를 처리하는 콜백함수를 등록할 때 glutEntryFunc 함수를 사용합니다. 다음은 이 함수의 설명입니다.:

void glutEntryfunc(void (*func)(int state));

인자 설명:
func - 이벤트를 처리하는 함수의 이름입니다.

이벤트를 처리하는 함수의 state 인자로 마우스가 OpenGL 창의 영역에 들어왔는지 아닌지를 알 수 있습니다. 이 인자에 사용할 수 있는 값은 두 개의 상수인데 GLUT 에 다음과 같이 정의되어 있습니다.:

  • GLUT_LEFT

  • GLUT_ENTERED

주의 : 마이크로소프트 윈도우즈에서 이 함수는 정확하게 동작하지 않는데 그 이유는 윈도우즈 운영체제가 마우스를 클릭했을 때 포커스를 변경하기 때문입니다. 마이크로소프트의 다른 도구들를 사용해서 이 설정을 임의대로 바꿀 수는 있지만 다른 사람들이 사용하는 마이크로소프트 윈도우즈는 대부분 표준 설정을 가지고 있을 것입니다. 그래서 마이크로소프트 윈도우즈를 사용한다면 마우스가 창의 내/외부에 있는지 알기 위해서 이 함수를 사용하지 않는게 나을듯 싶습니다.

모든걸 적용해서 예제를 만들어 볼까요?

제일 처음에 해야할 것은 마우스 이벤트 처리 함수들을 등록하는 것이므로 필요한 모든 콜백함수들를 등록하기 위해서 main 함수를 고쳐봅시다. 예제를 만들면서 놓치기 쉬운 것들은 자세하게 설명하고 아직 자세하게 배우지 않은 부분은 설명하지 않겠습니다.

void main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100,100);
    glutInitWindowSize(320,320);
    glutCreateWindow("SnowMen");
    glutDisplayFunc(renderScene);
    glutIdleFunc(renderScene);
    glutReshapeFunc(changeSize);

    // 마우스 처리 콜백함수들을 등록합니다.
    glutMouseFunc(processMouse);
    glutMotionFunc(processMouseActiveMotion);
    glutPassiveMotionFunc(processMousePassiveMotion);
    glutEntryFunc(processMouseEntry);

    glutMainLoop();
}

자~ 이제 좀 즐겨볼까요! :) 위에서 등록한 콜백함수들을 차례차례 정의해봅시다. 마우스 버튼을 누르고 ALT 키도 같이 눌렀을 때 삼각형의 색깔이 바뀌겠끔 만드는데, 이때 왼쪽 버튼을 누르면 삼각형이 빨간색으로, 가운데 버튼을 누르면 녹색으로, 왼쪽 버튼을 누르면 파란색으로 변하게 합니다. 다음은 이 일을 처리하는 processMouse 함수의 코드입니다. :

void processMouse(int button, int state, int x, int y)
{
    specialKey = glutGetModifiers();

    // 마우스 버튼과 ALT 키가 같이 눌리면~
    if ((state == GLUT_DOWN) && (specialKey == GLUT_ACTIVE_ALT))
    {
        // 왼쪽 버튼을 눌렀으면 삼각형을 빨간색으로 설정합니다.
        if (button == GLUT_LEFT_BUTTON)
        {
            red = 1.0; green = 0.0; blue = 0.0;
        }
        // 가운데 버튼을 눌렀으면 삼각형을 녹색으로 설정합니다.
        else if (button == GLUT_MIDDLE_BUTTON)
        {
            red = 0.0; green = 1.0; blue = 0.0;
        }
        // 오른쪽 버튼을 눌렀으면 삼각형을 파란색으로 설정합니다.
        else
        {
            red = 0.0; green = 0.0; blue = 1.0;
        }
    }
}

이제는 미묘한 색을 선택하는 함수를 만들어봅시다. ALT 키는 누르지 않고 마우스 버튼만 눌렀을 때 파란색은 0.0 으로 설정하고 빨간색과 녹색은 마우스 위치(OpenGL 창의 클라이언트 영역 내)에 따라 다르게 설정합니다. 아래에 이 함수의 코드가 있습니다. :

void processMouseActiveMotion(int x, int y)
{
    // ALT 키는 위 함수에서 검사합니다.
    if (specialKey != GLUT_ACTIVE_ALT)
    {
        // 마우스가 창의 내부에 있을 때 
        // 마우스 위치에 따라 red 변수를 설정합니다.
        if (x < 0)
            red = 0.0;
        else if (x > width)
            red = 1.0;
        else
            red = ((float) x)/height;

        // 마우스가 창의 내부에 있을 때 
        // 마우스 위치에 따라 green 변수를 설정합니다.
        if (y < 0)
            green = 0.0;
        else if (y > width)
            green = 1.0;
        else
            green = ((float) y)/height;

        // blue 변수(파란색)은 0.0 으로 설정해서 파란색 요소를 제거합니다.
        blue = 0.0;
    }
}

이제 수동모션을 처리할 때군요. SHIFT 키가 눌렸을 때 마우스 위치에 따라서 X 축 중심으로 회전하게 만들어봅시다. 이렇게 하기 위해서 renderScene 함수를 고쳐야합니다. 다음은 새로운 renderScene 함수입니다. :

float angleX = 0.0;
...
void renderScene(void) 
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();
        glRotatef(angle,0.0,1.0,0.0);

        // X 축 회전을 위해 새로 추가한 코드입니다.
        glRotatef(angleX,1.0,0.0,0.0);

        glColor3f(red,green,blue);

        glBegin(GL_TRIANGLES);
            glVertex3f(-0.5,-0.5,0.0);
            glVertex3f(0.5,0.0,0.0);
            glVertex3f(0.0,0.5,0.0);
        glEnd();
    glPopMatrix();
    angle++;
    glutSwapBuffers();
}

수동모션 이벤트를 처리하는 함수는 마우스의 x 좌표에 따라서 angleX 의 값을 설정합니다.

void processMousePassiveMotion(int x, int y)
{
    // X 축 중심으로 회전하게 만들려면 SHIFT 키를 눌러야합니다. 
    if (specialKey != GLUT_ACTIVE_SHIFT)
    {
        // 마우스 위치에 따라서 회전 각도를 설정합니다.
        if (x < 0)
            angleX = 0.0;
        else if (x > width)
            angleX = 180.0;
        else
            angleX = 180.0 * ((float) x)/height;
    }
}

마지막으로 마우스가 창을 떠났을 때 에니메이션을 멈추게 만듭니다. 이를 위해서 renderScene 함수를 다시 조금만 수정합니다. :

// angle(각도)변수의 증가값으로 사용하는데 1.0 으로 초기화합니다.
float deltaAngle = 1.0;
...
void renderScene(void) 
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
        glRotatef(angle,0.0,1.0,0.0);
        glRotatef(angleX,1.0,0.0,0.0);
        glColor3f(red,green,blue);

        glBegin(GL_TRIANGLES);
            glVertex3f(-0.5,-0.5,0.0);
            glVertex3f(0.5,0.0,0.0);
            glVertex3f(0.0,0.5,0.0);
        glEnd();
    glPopMatrix();
    // 새로운 코드입니다.
    // 이전 함수에서는 angle++ 이었습니다.
    angle+=deltaAngle;
    glutSwapBuffers();
}

마지막으로 precessMouseEntry 함수입니다. 위에서 말했듯이, 이 함수는 마이크로소프트 윈도우즈에서 정확하게 작동하지 않습니다. 리눅스에서는 잘 동작하죠.

void processMouseEntry(int state) 
{
    if (state == GLUT_LEFT)
        deltaAngle = 0.0;
    else
        deltaAngle = 1.0;
}

Visual C 프로젝트 파일을 받아서 자세하게 살펴보세요 :)

Last updated