'Unbuffered Input'에 해당되는 글 1건

  1. 2010.03.27 기초 튜토리얼4
2010.03.27 17:22

입문자 튜토리얼 4: Frame Listeners 와 Unbuffered Input

튜토리얼 진행중 문제가 발생한다면 Help 포럼 문의하세요.

 

Contents

*                               1 미리 알아두어야

*                               2 소개

*                               3 시작하기

*                               4 FrameListeners

*                                       4.1 소개

*                                       4.2 FrameListener 등록하기  

*                               5 장면(Scene) 설정

*                                       5.1

*                                       5.2 소스코드

*                               6 TutorialFrameListener

*                                       6.1 사용되는 변수들

*                                       6.2 생성자

*                                       6.3 frameStarted 함수

 

미리 알아두어야

튜토리얼은 독자가 C++ 프로그래밍이 가능하고 오우거 어플리케이션 설정 컴파일이 가능하다는 가정하에 진행됩니다. 튜토리얼은 이전 튜토리얼을 기초로 작성되었으며 독자는 이전 튜토리얼들을 거쳐왔다고 가정합니다.

 

소개

튜토리얼에서는 오우거에서 가장 쓸만한 한가지인 FrameListener 소개합니다. 끝날 시점에서 어떻게 프레임마다 업데이트 동작을 하고 버퍼를 쓰지 않는 입력시스템을 사용하는 방법을 이해하게 것입니다.

튜토리얼을 위한 코드는 여기(http://www.ogre3d.org/wiki/index.php/BasicTutorial4Source) 구할 있습니다. 튜토리얼을 천천히 진행함에 따라 프로젝트에 코드를 직접 입력하고 중간중간 생성되는 결과를 확인하세요.

 

시작하기

지난번 튜토리얼처럼 미리 작성된 코드를 기초로 시작할 입니다. 프로젝트를 생성하고 아래의 코드를 입력하세요 :

#include "ExampleApplication.h"

 

class TutorialFrameListener : public ExampleFrameListener

{

public:

    TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)

        : ExampleFrameListener(win, cam, false, false)

    {

    }


    // Overriding the default processUnbufferedKeyInput so the key updates we define
    // later on work as intended.
    bool processUnbufferedKeyInput(const FrameEvent& evt)
    {
         return true;
    }

    // Overriding the default processUnbufferedMouseInput so the Mouse updates we define
    // later on work as intended.
    bool processUnbufferedMouseInput(const FrameEvent& evt)
    {
         return true;
    }

    bool frameStarted(const FrameEvent &evt)

    {

        return ExampleFrameListener::frameStarted(evt);

    }

protected:

    bool mMouseDown;       // Whether or not the left mouse button was down last frame

    Real mToggle;          // The time left until next toggle

    Real mRotate;          // The rotate constant

    Real mMove;            // The movement constant

    SceneManager *mSceneMgr;   // The current SceneManager

    SceneNode *mCamNode;   // The SceneNode the camera is currently attached to

};

 

class TutorialApplication : public ExampleApplication

{

public:

    TutorialApplication()

    {

    }

 

    ~TutorialApplication()

    {

    }

protected:

    void createCamera(void)

    {

    }

 

    void createScene(void)

    {

    }

 

    void createFrameListener(void)

    {

    }

};

 

#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32

#define WIN32_LEAN_AND_MEAN

#include "windows.h"

 

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)

#else

int main(int argc, char **argv)

#endif

{

    // Create application object

    TutorialApplication app;

 

    try {

        app.go();

    } catch(Exception& e) {

#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32

        MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);

#else

        fprintf(stderr, "An exception has occurred: %s\n",

                e.getFullDescription().c_str());

#endif

    }

 

    return 0;

}


만약 오우거SDK Windows 에서 사용한다면 "[OgreSDK_DIRECTORY]\samples\include" 디렉토리 (ExampleApplication.h 파일이 있는곳) include 가능하도록 프로젝트에 추가해 주세요. 만약 오우거엔진 소스를 직접 사용하신다면 [OgreSource_DIRECTORY]\Samples\Common\include" 추가해 주세요. 다음 진행을 위해서 컴파일이 에러없이 되도록 해두세요. 그러나 실행은 하지마세요! 나중에 코드에 추가할 계획입니다. 만약 컴파일에 문제가 생긴다면 Wiki 페이지에서 컴파일러별 설정 정보를 찾아보시고 문제가 지속된다면 Help 포럼에 문의하세요.

조작컨트롤 부분은 튜토리얼을 진행하면서 완성될 입니다.

 

FrameListeners

 

소개

이전 튜토리얼에서는 createScene 함수에 추가한 내용만 다뤘습니다. 오우거에서는 화면에 프레임이 그려지기 전과 그려진 후의 알림을 전달받을 있는 클래스를 등록할 있습니다. FrameListener 인터페이스는 2개의 함수를 정의합니다 :

bool frameStarted(const FrameEvent& evt)

bool frameEnded(const FrameEvent& evt)

오우거의 메인루프 (Root::startRendering) 다음과 같습니다 :

1.    Root 객체가 등록된 모든 FrameListener들의 frameStarted함수를 호출.

2.    Root 객체가 프레임을 출력.

3.    Root 객체가 등록된 모든 FrameListener 들의 frameEnded 함수를 호출.

반복루프는 어느 FrameListener frameStarted 또는 frameEnded함수가 false 리턴하기 전까지 계속 돕니다. 함수들에 있어서 리턴값의 의미는 간단하게 말하자면 "계속 그려라" 입니다. 이것들 하나가 거짓을 리턴하면 프로그램은 종료됩니다. FrameEvent 객체가 2개의 변수를 가지고 있지만 FrameListener에서는  timeSinceLastFrame값만 유용하게 사용됩니다. 값은 frameStarted 또는 frameEnded 함수가 마지막으로 실행되고나서 얼마나 시간이 흘렀는지를 추적합니다. 참고로 frameStarted 함수에서 FrameEvent::timeSinceLastFrame 마지막 frameStarted 호출된 시간으로부터 지금까지 지난 시간을 가집니다 (frameEnded 마지막으로 호출된 시간이 아닙니다).

오우거에서 다수의 FrameListener 있어서 중요한 속성중 하나는 어떤 순서로 호출될지의 여부는 오우거엔진에게 달려있다는 입니다. 어떤순서로 FrameListener 들이 호출될지 예측할 없습니다. 만약 다수의 FrameListener들의 호출순서를 정하고 싶다면 하나의 FrameListener 등록하고 관련된 모든 객체들의 호출들의 순서를 적절히 설정하면 됩니다.

메인루프는 세가지 단계만 실행된다는 것을 아셨을 겁니다. frameEnded frameStarted 사이에는 아무런 일이 일어나지 않으므로 코드를 바꿔쓰셔도 됩니다. 코드를 작성하는 위치를 정하는것은 전적으로 유저에게 달렸습니다. 하나의 frameStarted frameEnded 몰아서 작성하셔도 되고 두개에 나눠서 작성하셔도 됩니다.

 

FrameListener 등록하기

지금까지 작성한 코드는 컴파일이 되지만 ExampleApplication클래스의 createFrameListener createCamera함수를 재정의 했으므로 실행시킨다면 종료시킬 없게 됩니다. 계속 진행하기전에 여기서 문제점을 해결해야 합니다.

TutorialApplication::createCamera 함수를 찾아서 다음 코드를 추가하세요 :  

        // create camera, but leave at default position

        mCamera = mSceneMgr->createCamera("PlayerCam");

        mCamera->setNearClipDistance(5);

아직 이걸로는 아무것도 완성된 것이 아닙니다. 함수를 재정의한 이유는 기존의 createCamera함수에서 카메라를 움직이고 방향을 바꿔버리기 때문입니다. 그러한 기능은 튜토리얼에서는 필요없는 부분입니다.

Root 클래스가 프레임을 그릴때 FrameListener 같이 관여합니다. 첫째로 해야할 일은 TutorialFrameListener 인스턴스를 만들고 Root 객체에 등록합니다. TutorialApplication::createFrameListener 찾아서 다음 코드를 추가하세요.

        // Create the FrameListener

        mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);

        mRoot->addFrameListener(mFrameListener);

ExampleApplication 클래스에는 mRoot mFrameListener 변수가 정의되어 있습니다. addFrameListener 함수는 FrameListener 추가하고 removeFrameListener 함수는 FrameListener 제거합니다( 말은 FrameListener 이상 새로운 메세지를 받지 않게 된다는 말입니다). 알아 두실점은 add|removeFrameListener 함수는 FrameListener 포인터만 받습니다 (FrameListener 제거할때 이름이 사용되지 않음을 의미합니다). 그러므로 각각의 FrameListener 포인터를 보관하고 계셔야 나중에 메모리에서 제거할 있습니다.

ExampleFrameListener(TutorialFrameListener 상속받은 클래스) 역시 showDebugOverlay (불리언 리턴) 함수를 제공합니다. 함수는 ExampleApplication 클래스에게 프레임율을 보여주는 작은 박스를 화면 왼쪽 구석에 표시할지 여부를 설정합니다. 다음과 같이 on 으로 설정합니다 :

        // Show the frame stats overlay

        mFrameListener->showDebugOverlay(true);

계속 진행하기전에 컴파일 하고 실행이 가능하도록 해주세요.

 

장면(Scene) 설정

 

소개

코드를 살펴보기 전에 이해를 돕고자 화면에 무엇을 넣을것인지에 대한 간략한 설명을 드리고자 합니다.

닌자 한명과 Point 조명하나를 배치시킬 입니다. 마우스왼쪽버튼 누르면 조명이 on/off 됩니다. 오른쪽 버튼을 누르고 있으면 "Mouse Look" 모드가 됩니다(카메라로 사방을 자유롭게 둘러볼 있습니다). 다수의 SceneNode 장면에 배치하여 서로다른 뷰포트를 가지는 카메라들이 attach 있도록 것입니다. "1", "2" 키로 보고 싶은 뷰포트를 선택합니다.

 

소스코드

TutorialApplication::createScene 함수로 갑시다. 먼저 희미한 주변광(ambient) 설정합니다. 조명이 off 되더라도 객체가 보여야 하며, on 상태와는 구별되어야 합니다 :

        mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));

원점위치에 닌자 Entity 추가합니다 :

        Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");

        SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");

        node->attachObject(ent);

이제 Point조명을 장면에 추가하고 닌자로부터 조금 떨어진 거리에 위치시킵니다 :  

        Light *light = mSceneMgr->createLight("Light1");

        light->setType(Light::LT_POINT);

        light->setPosition(Vector3(250, 150, 250));

        light->setDiffuseColour(ColourValue::White);

        light->setSpecularColour(ColourValue::White);

이제 SceneNode 카메라를 attach 하는일이 남았군요 :

        // Create the scene node

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));

        node->yaw(Degree(-45));

        node->attachObject(mCamera);

 

        // create the second camera node

        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));

TutorialApplication 클래스에서 해야 할일은 끝입니다. 이제 TutorialFrameListener 들어갑니다...

 

Trackback 0 : Comment 0

기초 튜토리얼 4-2

Ogre3D 삽질란/Basic Tutorial 4 2008/11/20 18:20

TutorialFrameListener

 

사용되는 변수들

시작하기 TutorialFrameListener 클래스에 몇개의 변수들을 정의합니다 :

    bool mMouseDown;       // Whether or not the left mouse button was down last frame

    Real mToggle;          // The time left until next toggle

    Real mRotate;          // The rotate constant

    Real mMove;            // The movement constant

    SceneManager *mSceneMgr;   // The current SceneManager

    SceneNode *mCamNode;   // The SceneNode the camera is currently attached to

mSceneMgr 현재 SceneManager 포인터를 가르키며 mCamNode 현재의 카메라가 attach SceneNode 가르킵니다. mRotate, mMove 회전과 움직임 상수입니다. 만약 움직임이나 회전속도를 바꾸고 싶으시면 값을 조절하시면 됩니다.

2개의 변수(mToggle, mMouseDown) 입력을 제어합니다. "unbuffered" 방식의 마우스와 제어를 튜토리얼에서 다루게 것입니다(buffered 방식은 다음 튜토리얼 주제입니다). FrameListener 키보드와 마우스의 상태를 체크하기위해 어떠한 함수를 호출하게 입니다. 키보드를 사용하여 화면상의 객체를 제어할때 작은 문제점에 직면하게 됩니다. 만약 키를 누르면 그에 따른 행동을 하긴하는데 다음 프레임에서는 어떤일이 일어날까요? 같은키가 눌러져 있으면 똑같은 행동을 취할까요? 어떤 경우에는(화살표키로 움직이는 경우) 이렇게 되는게 맞겠죠. 그러나 예를 들어 "T" 키가 조명을 끄고 켜는것이라면 어떨까요. 프레임에서 T 키가 눌러지고 조명이 반전되겠죠. 그리고 다음 프레임에서는 여전히 눌러져 있습니다. 그래서 다시 반전되고.. 계속해서 계속.. 키가 떨어 까지 반복될겁니다. 키의 상태를 프레임별로 체크해서 이러한 상황을 방지해야 합니다. 조금 뒤에서 2가지 종류의 해결방안을 제시해 드리겠습니다.

mMouseDown 변수는 마우스버튼이 이전프레임에서도 눌러져 있었는지 체크합니다(만약 mMouseDown값이 참이면 마우스버튼을 뗄때까지 지속되는 반복행동을 방지해야 합니다). mToggle 변수는 동작이 다시 실행 까지의 최소한의 시간차를 두기위해 사용됩니다. 버튼이 눌러질때 mToggle 변수에 일정한 시간이 설정되며 시간이 지나기 전까지는 어떤 행동도 금지됩니다.

 

생성자

가장 먼저 보게 ExampleFrameListener 생성자에 대해서 알아봅시다 :

        : ExampleFrameListener(win, cam, false, false)

주의깊게 보셔야 점은 3, 4번째 변수가 false 설정된 것입니다. 3번째 변수는 buffered 방식의 입력을 받을것인지, 4번째 변수는 buffered 방식의 마우스 입력을 받을것인지 ( 튜토리얼에서는 다루지 않습니다) 설정합니다.

TutorialFrameListener 생성자에서 일단 지금은 모든 변수를 기본값으로 둡니다 :

        // key and mouse state tracking

        mMouseDown = false;

        mToggle = 0.0;

 

        // Populate the camera and scene manager containers

        mCamNode = cam->getParentSceneNode();

        mSceneMgr = sceneMgr;

 

        // set the rotation and move speed

        mRotate = 0.13;

        mMove = 250;

됐습니다. mCamNode 변수는 현재 설정되어 있는 카메라로 초기화 되어 있습니다.

Trackback 0 : Comment 0

기초 튜토리얼 4-3 (마지막)

Ogre3D 삽질란/Basic Tutorial 4 2008/11/20 16:19

frameStarted 함수

이제 튜토리얼의 진짜 내용이 시작됩니다 : 모든 프레임마다 일어나는 동작을 설정하는 . 현재 frameStarted 함수에는 다음 코드가 있습니다 :

        return ExampleFrameListener::frameStarted(evt);

한줄짜리 코드가 지금까지 프로그램이 실행될 있도록 유지시켜 줬습니다. ExampleFrameListener::frameStarted 함수는 수많은 동작을 포함합니다 (모든 키에 대한 반응, 모든 카메라에 대한 움직임 등등). 일단 지금은 TutorialFrameListener::frameStarted 함수의 모든내용을 지우세요.

"Open Input System"(OIS) 입력처리를 위해 세가지 클래스를 제공합니다 : 키보드, 마우스, 조이스틱. 튜토리얼에서는 키보드와 마우스객체처리에 대해서만 다룹니다. 오우거에서 조이스틱(또는 패드) 사용하고 싶다면 조이스틱 클래스를 참고하세요.

unbuffered 입력 방식을 사용하기위해서는 일단 키보드와 마우스의 상태를 가로채야 합니다. 마우스와 키보드 객체의 capture 멤버함수를 써서 구현이 가능합니다. 예제 프레임워크에는 이미 mMouse, mKeyboard 변수명으로 객체가 이미 생성되어 있습니다. TutorialFrameListener::frameStarted 멤버함수에 다음 코드를 추가하세요 :

        mMouse->capture();

        mKeyboard->capture();

이제 다음으로 Esc 키를 누르면 종료되게끔 만들어 봅시다. 만약 키가 눌러지게 되면 InputReader isKeyDown함수에 키코드를 매개변수로 전달해 해당 키가 눌러졌는지를 검사하게 됩니다. 만약 Esc키가 눌러졌으면 false 리턴해 프로그램을 종료하게 것입니다 :

        if(mKeyboard->isKeyDown(OIS::KC_ESCAPE))

           return false;

계속해서 렌더링하기 위해서는 frameStarted 함수가 true 값을 리턴해야 합니다. 그러기 위해서 함수의 마지막 부분에 다음 코드를 추가하세요 :

        return true;

함수 내의 모든 코드는 바로 "return true" 라인에 도달해야 합니다.

FrameListener 에서 왼쪽 마우스 버튼을 누르면 조명이 on/off 되는 기능을 구현해 봅시다. 마우스 버튼이 눌러졌는지는 InputReader getMouseButton 함수로 쿼리하여 알아 있습니다. 보통 0 왼쪽버튼, 1 오른쪽, 2 가운데 버튼입니다. 어떤 시스템에서는 1 가운데, 2 오른쪽인 경우도 있습니다. 예상과 다르게 동작된다면 그렇게도 설정해 보세요.

        bool currMouse = mMouse->getMouseState().buttonDown(OIS::MB_Left);

마우스 버튼이 눌러지면 currMouse 변수가 true 값을 가집니다. 조명이 토글될지 여부는 마우스가 이전프레임에서는 눌려지지 않았고 currMouse 값이 true 여야 합니다(마우스가 눌려졌을때 한번만 토글되게끔 하고 싶을때). 조건이 만족될 조명의 setVisible 멤버함수로 on/off 토글하게 됩니다 :

        if (currMouse && ! mMouseDown)

        {

            Light *light = mSceneMgr->getLight("Light1");

            light->setVisible(! light->isVisible());

        } // if

mMouseDown 내용과 currMouse 내용을 똑같이 맞춰줘야 합니다. 다음 프레임때 이전 프레임에서 마우스가 눌려졌었는지 떼어졌었는지를 검사할 있어야 합니다.

        mMouseDown = currMouse;

실행시켜보세요. 왼쪽클릭으로 조명 on/off 토글이 가능합니다! ExampleFrameListener frameStarted 함수호출이 일어나지 않았으므로 카메라이동은 아직 안됩니다.

마지막에 수행된 행동에 연관된 마우스의 이전상태를 저장했습니다. 한가지 단점으로는 누를때마다 반응한다는 것입니다. 그러므로 불리언 변수를 써서 보완해야 합니다. 한가지 방법으로는 마지막에 눌려졌던 시간을 체크하고 일정시간이 지난 다음에야 허용하는 방식입니다. mToggle변수로 이러한 기능을 관리합니다. mToggle값이 0보다 크면 아무런 동작도 실행되지 않으며 0보다 작아야 입력에 따른 동작이 실행됩니다. 2개의 키에 방법을 적용시킬 입니다.

먼저 해야 것은 시간이 지남에 따라 mToggle값을 줄이는 입니다 :

        mToggle -= evt.timeSinceLastFrame;

동작실행에 관한 mToggle 갱신되었습니다. 다음 바인딩 키인 "1" 첫번째 SceneNode 카메라를 attach 시킵니다. 전에 mToggle 변수값이 0보다 작은지를 확인해야 합니다 :

        if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1))

        {

mToggle 변수값을 1 후에 다시 동작이 실행될 있도록 설정합니다 :

            mToggle = 0.5f;

다음 카메라가 마지막으로 attach 되었던 것을 해제하고 mCamNode 값을 CamNode1으로 설정하고attach 시킵니다.

           mCamera->getParentSceneNode()->detachObject(mCamera);

           mCamNode = mSceneMgr->getSceneNode("CamNode1");

           mCamNode->attachObject(mCamera);

       }

"2" 누르면 CamNode2 동작할 수도 있습니다. 언급한 코드에서 숫자를 1에서 2로만 바꾸면 되기때문에 쉽게 이해 하실수 있으리라 생각하고 설명은 생략합니다. (같은 설명은 지루하기만 하니까요) :

        else if ((mToggle < 0.0f) && mKeyboard->isKeyDown(OIS::KC_2))

        {

           mToggle = 0.5f;

           mCamera->getParentSceneNode()->detachObject(mCamera);

           mCamNode = mSceneMgr->getSceneNode("CamNode2");

           mCamNode->attachObject(mCamera);

       }

실행시켜보세요. "1", "2" 누르면 카메라의 뷰포인트가 바뀔겁니다.

다음으로 해야 일은 WASD 또는 화살표키를 누르면 mCamNode 변환시키는 작업입니다. 지금까지와는 다르게 마지막으로 움직였던 카메라를 추적할 필요가 없습니다. 키가 눌러진 상태라면 계속해서 움직여야 하기 때문이죠. 그래서 간단하게 구현이 됩니다. 움직이고 싶은 방향을 설정할 Vector3 객체를 만듭니다 :

        Vector3 transVector = Vector3::ZERO;

W키와 위쪽 화살표를 누르면 전진합니다. (모니터를 바라보는 방향인 -z 방향입니다) :

       if (mKeyboard->isKeyDown(OIS::KC_UP) || mKeyboard->isKeyDown(OIS::KC_W))

           transVector.z -= mMove;

S 아랫쪽 화살표도 비슷하게 처리합니다. 대신에 이번에는 +z 방향입니다 :

       if (mKeyboard->isKeyDown(OIS::KC_DOWN) || mKeyboard->isKeyDown(OIS::KC_S))

           transVector.z += mMove;

왼쪽과 오른쪽 방향을 위해서 +x -x 설정합니다 :

       if (mKeyboard->isKeyDown(OIS::KC_LEFT) || mKeyboard->isKeyDown(OIS::KC_A))

           transVector.x -= mMove;

       if (mKeyboard->isKeyDown(OIS::KC_RIGHT) || mKeyboard->isKeyDown(OIS::KC_D))

           transVector.x += mMove;

마지막으로 y축을 따라서 위아래로도 움직이게 봅시다. 개인적으로 E/PageDown 아래로, Q/PageUp 키를 위로 사용합니다 :

       if (mKeyboard->isKeyDown(OIS::KC_PGUP) || mKeyboard->isKeyDown(OIS::KC_Q))

           transVector.y += mMove;

       if (mKeyboard->isKeyDown(OIS::KC_PGDOWN) || mKeyboard->isKeyDown(OIS::KC_E))

           transVector.y -= mMove;

이동할 방향을 transVector 저장했고 카메라의 SceneNode 적용할 준비가 되었습니다. 첫번째 주의사항으로는 SceneNode 회전시킨다음에 움직이면 x, y, z 방향이 꼬입니다. 이런 현상을 고치려면 지금까지 회전시킨 방향을 이동시키는데 사용되는 변환노드에도 적용시켜야 한다는 입니다. 어렵게 들릴지 몰라도 생각보다 쉽습니다.

공간좌표변환을 위해서 오우거는 다른 몇몇 그래픽 엔진들 처럼 행렬계산을 사용하지 않습니다. 대신에 사원수(Quaternions) 공간좌표변환을 수행합니다. 사원수계산에는 이해하기 어려운 4차원 선형대수학 계산식이 필요합니다. 고맙게도 오우거를 사용함에 있어서는 이러한 계산을 위해 복잡한 수학 배경지식을 몰라도 됩니다. 아주 간단하게도 벡터회전을 위해 사원수를 쓰기위해선 2개의 벡터를 곱하는게 전부입니다. 경우 매번 회전결과값을 이동변환좌표에다 적용시켜야 합니다. 사원수 회전값을 구하려면 SceneNode::getOrientation() 함수를 사용하시면 됩니다. 결과값으로 이동변환 노드에 곱하면 됩니다.

두번째 주의사항은 시간이 흐른만큼 이동수치가 조절되어야 합니다. 안그러면 프레임율에 따라서 움직이는 속도가 달라질 것입니다. 절대로 그렇게 되서는 안됩니다. 이러한 문제점을 해결하기위한 변환과정 호출부분입니다 :

        mCamNode->translate(transVector * evt.timeSinceLastFrame, Node::TS_LOCAL);

새로운 매개변수가 보이는군요. 노드를 이동시키거나 축에대한 회전을 수행할때 어떠한 공간을 기준으로 수행될지를 설정할 있습니다. 평상시 객체이동을 할때 이러한 매개변수는 설정하지 않습니다. 기본값은 TS_PARENT이며 부모노드의 공간좌표를 중심으로 이동된다는 의미입니다. 경우 부모노드는 root scene 노드 입니다. W 키를 누르면 (전진) z 방향값에 뺄셈을 수행하며 -z 방향으로 움직인다는 의미 입니다. 만약 방금전의 코드에서 TS_LOCAL 값을 매개변수로 넣지 않았다면 global -Z 방향으로 카메라가 이동할 것입니다. 하지만 W 누르면 앞으로 나아가게 하고 싶으므로 노드가 실제로 바라보는 방향으로 가게해야 합니다. 그래서 "Local" 좌표변환공간을 쓰는 입니다.

다른방법으로 구현할 수도 있습니다(약간 간접적인 방법으로). 사원수로 노드의 방향을 방향벡터에 곱하면 동일한 결과를 얻을 있습니다. 결과값은 완전 동일합니다 :

       // Do not add this to the program

       mCamNode->translate(mCamNode->getOrientation() * transVector * evt.timeSinceLastFrame, Node::TS_WORLD);

이것역시 로컬공간에서 카메라노드위치를 변환시킵니다. 경우 특별한 이유는 없습니다. 오우거는 3가지의 변환공간을 제공합니다 : TS_LOCAL, TS_PARENT, TS_WORLD. 3가지 공간을 제외한 또다른 벡터공간에서의 변환이 필요할지도 모릅니다. 이런경우 바로 앞에서 코드와 비슷하게 처리하시면 됩니다. 벡터공간을 표현하는 사원수를 구하시고 (또는 매치시키려는 객체의 방향), 올바른 변환벡터를 구하기 위해 곱셈과정을 거친후, TS_WORLD 공간 기준으로 움직이면 됩니다. 당분간 그럴일은 별로 없을겁니다. 그리고 나중에 있을 어떤 튜토리얼에서도 나오지 않을 입니다.

지금까지 키보드를 눌렀을때를 처리했습니다. 이제는 마우스 오른쪽 버튼을 누르고 있으면 자유자재로 사방을 둘러볼 있는 기능을 추가해 봅시다. 먼저 마우스 버튼이 눌러졌을때의 상태를 체크해야 합니다 :

        if (mMouse->getMouseState().buttonDown(OIS::MB_Right))

        {

시간이 지난만큼 마우스가 움직인거리를 고려해서 카메라의 yaw pitch 동작을 처리합니다. 구현하기 위해서 X, Y 상대적인 변화량을 구한 다음 pitch yaw 함수로 넘깁니다 :

           mCamNode->yaw(Degree(-mRotate * mMouse->getMouseState().X.rel), Node::TS_WORLD);

           mCamNode->pitch(Degree(-mRotate * mMouse->getMouseState().Y.rel), Node::TS_LOCAL);

        }

yaw 동작에 있어서는 TS_WORLD 벡터공간을 사용했습니다(따로 설정하지 않는이상 회전함수는 TS_LOCAL 값이 기본치입니다). pitch동작이 yaw 영향받지 않도록 고려한 것입니다. yaw동작은 항상 같은축으로 회전하도록 했습니다. 이것이 바로 세번째 주의사항입니다. 회전간의 상호작용을 까먹을 있습니다. yaw 동작을 TS_LOCAL에서 한다면 다음과 같은 상황이 발행될 있습니다.

 

사용자 삽입 이미지

컴파일 실행시켜 보세요.

튜토리얼에서 회전과 사원수에 대한 모든것을 다룬건 아닙니다(튜토리얼 관련내용만 다루기에 충분한 내용입니다). 다음 튜토리얼에서는 키가 눌려졌는지 프레임마다 검사하는방식 대신에 쓰이는 버퍼방식 마우스입력에 대한 내용이 이어질 것입니다

신고
Posted by 우엉 여왕님!! ghostkyow

티스토리 툴바