'Ogre3D 삽질란'에 해당되는 글 50건

  1. 2008.12.10 중급 튜토리얼 2-2
  2. 2008.12.10 중급 튜토리얼 2-3 (마지막)
  3. 2008.12.09 중급 튜토리얼 1-1
  4. 2008.12.09 중급 튜토리얼 1-2
  5. 2008.12.09 중급 튜토리얼 1-3 (마지막)
  6. 2008.12.05 기초 튜토리얼 8-1
  7. 2008.12.05 기초 튜토리얼 8-2
  8. 2008.12.05 기초 튜토리얼 8-3 (마지막)
  9. 2008.12.04 기초 튜토리얼 7-1
  10. 2008.12.04 기초 튜토리얼 7-2

중급 튜토리얼 2-2

Ogre3D 삽질란/Intermediate Tutorial 2 2008. 12. 10. 18:32

장면 설정하기

MouseQueryApplication::createScene함수로 갑시다. 다음코드는 별로 어렵지 않을겁니다. 만약 모르는 코드가 보인다면 API 레퍼런스를 참고하여 먼저 이해한다음 계속 진행하세요. createScene함수에 다음 코드를 추가합니다 :

       // Set ambient light

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

       mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);

 

       // World geometry

       mSceneMgr->setWorldGeometry("terrain.cfg");

 

       // Set camera look point

       mCamera->setPosition(40, 100, 580);

       mCamera->pitch(Degree(-30));

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


기초적인 world geometry 설정되었습니다. 이제 마우스 커서를 보이게 차례입니다. 그럴려면 CEGUI함수를 호출하여야 하므로 먼저 CEGUI 구동시켜야 합니다. 우선 OgreCEGUIRenderer 생성합니다. 다음 시스템 객체를 생성하고 방금 생성시켰던 렌더러를 전달합니다. CEGUI 자세한 설정방법은 나중에 있을 튜토리얼에서 다뤄질 계획입니다. 일단 지금은 CEGUI 어떤 SceneManager 쓰일것인지와 마지막 매개변수로 mGUIRenderer 들어간다는 것만 기억하세요.

       // CEGUI setup

       mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);

       mGUISystem = new CEGUI::System(mGUIRenderer);

이제 실제로 마우스 커서를 보이게 차례입니다. 코드에 대한 설명은 생략하겠습니다. 나중에 있을 튜토리얼에서 알려드리겠습니다.

       // Mouse

       CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");

       CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");

컴파일 실행시키면 화면 한가운데에서 커서를 발견할 것입니다. 하지만 움직이지는 않습니다(아직은).

 

FrameListener 소개

프로그램에서 필요한건 모두 끝냈습니다. FrameListener 코드에 복잡하게 관여하는 부분입니다. 그러므로 실제로 구현하기전에 뭐가 어떻게 돌아갈지에 대해서 설명을 드려야 구현하기전에 머릿속에서 대충 정리가 것입니다.

*                   첫번째, 마우스 우클릭버튼을 “mouse look”버튼으로 사용할 겁니다. 마우스로 자유롭게 주변상황을 없다는것은 귀찮거든요. 그래서 첫번재 우선과제는 마우스 컨트롤기능을 프로그램에 넣는 입니다(마우스 우클릭을 유지한 상태에서만 동작됩니다).

*                   두번째, 카메라가 지형을 뚫고 지나가지 않게끔 계획입니다. 지형에 가까이 다가갈 수 있도록 보통 흔히들 떠올리는 그런 동작을 보여줄 겁니다.

*                   세번째, 왼쪽클릭으로 지형의 어떤 공간에라도 엔티티를 추가할 있도록 계획입니다.

*                   마지막으로 엔티티를 “drag” 있도록 만들겁니다. 왼쪽버튼을 누르고 유지시키면 엔티티를 놓고 싶은 위치까지 움직일 있게 됩니다. 마우스버튼을 떼면 자리에 위치하게 됩니다.

기능구현을 위해서 몇몇 protected 변수를 사용하게 될겁니다(이미 클래스에 추가되어 있습니다) :

    RaySceneQuery *mRaySceneQuery;     // The ray scene query pointer

    bool mLMouseDown, mRMouseDown;     // True if the mouse buttons are down

    int mCount;                        // The number of robots on the screen

    SceneManager *mSceneMgr;           // A pointer to the scene manager

    SceneNode *mCurrentObject;         // The newly created object

    CEGUI::Renderer *mGUIRenderer;     // cegui renderer

mRaySceneQuery 지형의 좌표를 찾는데 쓰일 RaySceneQuery 복사본을 가집니다. mLMouseDown, mRMouseDown변수는 어떤 마우스 버튼이 눌렸는지를 체크합니다(mLMouseDown변수가 true 왼쪽버튼이 눌린상태로 유지되고 있다는 의미며 false 그렇지 않다는 의미입니다). mCount변수는 화면상에 몇개의 엔티티가 있는지를 헤아립니다. mCurrentObject변수는 가장 최근에 생성된 SceneNode(“drag”하는데 쓰일 엔티티로 사용될겁니다) 가리킵니다. 마지막으로 mGUIRenderer CEGUI 업데이트하는데 사용될 CEGUI 렌더러의 포인터를 가집니다.

Mouse listener에는 수많은 함수들이 연관되어 있습니다. 데모에서 모든 기능을 사용하지는 않지만 적어도 정의는 해둬야 컴파일시 에러를 발생시키지 않습니다.

 

FrameListener 설정

MouseQueryListener생성자로 가서 다음 초기화 코드를 추가하세요. 유심히 보셔야 부분은 지형크기가 줄어듬에 따라서 이동속도와 회전속도역시 감소했다는 입니다.

        // Setup default variables

        mCount = 0;

        mCurrentObject = NULL;

        mLMouseDown = false;

        mRMouseDown = false;

        mSceneMgr = sceneManager;

 

        // Reduce move speed

        mMoveSpeed = 50;

        mRotateSpeed /= 500;

MouseQueryListener 마우스 이벤트를 받기위해서 클래스를 MouseListener로서 등록을 해야 합니다. 무슨말인지 이해가 되지 않는다면 Basic Tutorial 5 참고하세요

        // Register this so that we get mouse events.

        mMouse->setEventCallback(this);

마지막으로 생성자에서 RaySceneQuery객체를 생성해야 합니다. SceneManager에서 해야 하는 호출은 이게 전부입니다:

        // Create RaySceneQuery

        mRaySceneQuery = mSceneMgr->createRayQuery(Ray());

생성자에서 필요한건 모두 했지만 RaySceneQuery 생성했으면 나중에는 파괴시켜줘야 합니다. MouseQueryListener 파괴자로 가서 (~MouseQueryListener) 다음코드를 추가하세요 :

        // We created the query, and we are also responsible for deleting it.

        mSceneMgr->destroyQuery(mRaySceneQuery);

다음 섹션으로 넘어가기전에 컴파일이 제대로 되는지 확인하세요.

 

Mouse Look 추가

마우스의 우클릭을 마우스룩 모드로 바인딩할 차례입니다. 다음과 같은 절차로 구현됩니다 :

*                   마우스가 움직이면 CEGUI 업데이트합니다(커서도 움직입니다)

*                   버튼이 눌려지면 mRMouseButton변수는 true값을 가집니다.

*                   버튼이 떨어지면 mRMouseButton변수는 false 값을 가집니다.

*                   드래그되면 view 바꿉니다(버튼을 누른상태로 움직인다는 의미입니다)

*                   마우스가 드래그중일 경우에는 마우스 커서를 숨깁니다

MouseQueryListener::mouseMoved함수로 가서 마우스가 움직이는 모든 프레임에서 마우스 커서위치를 바꾸는 코드를 넣을겁니다. 다음 코드를 함수로 추가하세요 :

       // Update CEGUI with the mouse motion

       CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);

MouseQueryListener::mousePressed함수로 갑니다. 다음 코드는 마우스 오른쪽클릭 드래깅의 경우 커서를 숨기고 mRMouseDown변수값을 true 만듭니다.

       // Left mouse button down

       if (id == OIS::MB_Left)

       {

           mLMouseDown = true;

       } // if

 

       // Right mouse button down

       else if (id == OIS::MB_Right)

       {

           CEGUI::MouseCursor::getSingleton().hide();

           mRMouseDown = true;

       } // else if

마우스 오른쪽 버튼이 떨어지면 mRMouseDown변수값을 토글시키고 마우스커서를 다시 보여지게 해야 합니다. mouseReleased함수로 가서 다음 코드를 추가하세요 :

       // Left mouse button up

       if (id == OIS::MB_Left)

       {

           mLMouseDown = false;

       } // if

 

       // Right mouse button up

       else if (id == OIS::MB_Right)

       {

           CEGUI::MouseCursor::getSingleton().show();

           mRMouseDown = false;

       } // else if

미리 작성해야할 모든 코드가 완성되었습니다. 이제는 마우스우클릭 드래깅의 경우 view 바꿔야 합니다. 마지막 순간에서 지금까지 마우스가 움직인 거리를 읽어내야 합니다. Basic Tutorial 5 에서 구현했던 카메라 회전과 동일한 방법으로 구현합니다. MouseQueryListener::mouseMoved함수를 찾아서 다음 코드를 return구문 이전에 위치하도록 추가하세요 :

       // If we are dragging the left mouse button.

       if (mLMouseDown)

       {

       } // if

 

       // If we are dragging the right mouse button.

       else if (mRMouseDown)

       {

           mCamera->yaw(Degree(-arg.state.X.rel * mRotateSpeed));

           mCamera->pitch(Degree(-arg.state.Y.rel * mRotateSpeed));

       } // else if

이제 컴파일 실행시키면 마우스우클릭 드래깅으로 주변을 둘러볼 있게 됩니다.

 

지형충돌 감지

이제는 지형을 향해서 이동시 지형을 뚫고 지나가지 못하게끔 만들어 봅시다. BaseFrameListener 이미 카메라이동을 다루고 있기 때문에 그에 관련된 코드는 건드리지 않을겁니다. 대신에 BaseFrameListener 카메라를 움직이고 이후 카메라가 지형으로부터 적어도 10 unit만큼 상위에 위치하게끔 만들겁니다. 만약 충분히 높은위치의 경우에는 제약없이 이동됩니다. 코드를 유심히 읽어주세요. 튜토리얼의 남은부분을 진행하면서 RaySceneQuery 몇가지 기능을 구현할 계획이지만 이번 섹션이후로는 자세한 설명을 드리지 않을겁니다.

MouseQueryListener::frameStarted함수로 가서 모든 내용을 지우세요. 가장 먼저 추가할 코드는 ExampleFrameListener::frameStarted 기본함수를 호출하는 입니다. 만약 false 리턴하면 똑같이 false 리턴합니다.

        // Process the base frame listener code.  Since we are going to be

        // manipulating the translate vector, we need this to happen first.

        if (!ExampleFrameListener::frameStarted(evt))

            return false;

코드는 frameStarted 가장 상위부분에서 수행됩니다. 왜냐하면 ExampleFrameListener's frameStarted함수는 카메라를 이동시키고 함수의 모든 행동은 함수가 수행된 이후에 수행되어야 하기 때문입니다. 우리의 목표는 카메라의 현재 좌표를 얻고 지형을 향해서 수직하단방향으로 광선을 쏩니다. 이것이 바로 RaySceneQuery이며 지형으로부터 얼마나 높이 위치하고 있는지를 알려줍니다. 카메라의 현재위치를 얻은 이후 광선을 생성해야 합니다. 광선은 원점(광선이 시작되는 위치) 방향을 필요로 합니다. 방향에 있어서는 NEGATIVE_UNIT_Y값이 것이며 수직하단방향을 가르킬 겁니다. 광선이 생성되면 RaySceneQuery객체를 통해서 사용합니다.

       // Setup the scene query

       Vector3 camPos = mCamera->getPosition();

       Ray cameraRay(Vector3(camPos.x, 5000.0f, camPos.z), Vector3::NEGATIVE_UNIT_Y);

       mRaySceneQuery->setRay(cameraRay);

높이를 카메라의 현재위치가 아닌 5000.0f수치를 대신 사용했습니다. 만약 카메라의 실제 Y 위치를 사용한다면 카메라가 지형 아래에 위치할 경우 무조건 지형을 놓치게 됩니다. 이제 쿼리를 실행하여 결과값을 얻습니다. 결과값은 std::iterator형태로 나오게 되는데 간략하게 설명드리겠습니다.

        // Perform the scene query

        RaySceneQueryResult &result = mRaySceneQuery->execute();

        RaySceneQueryResult::iterator itr = result.begin();

간략하게( 심하게 단순화시켜서) 설명드리자면 쿼리 결과값은 worldFragment(지금의 경우는 지형)들과  움직일 있는 객체(나중에 선보일 계획)들의 리스트 입니다. STL 반복자에 대해서 익숙치 않다면 반복자의 요소를 얻는 begin함수만 알아두세요. If the result.begin() == result.end() 경우는 리턴할 결과값이 아무것도 없다는 것을 의미합니다. 다음 데모에서는 다수의 SceneQuery 대한 리턴값을 다루게 것입니다. 지금은 일단 간단한 것만 알아두고 넘어갑시다. 다음코드는 최소한 하나의 결과값(itr != result.end()) 리턴하며 결과값은 지형입니다(itr->worldFragment).

        // Get the results, set the camera height

        if (itr != result.end() && itr->worldFragment)

        {

worldFragment구조체는 광선이 지형 어느 부분에 hit 했는지를 singleIntersection변수(Vector3형식) 담아둡니다. 지형으로부터의 높이를 변수의 y변수값으로부터 얻어낼 있습니다. 높이를 알아낸 다음 만약 카메라가 일정높이 아래에 위치한다면 특정 높이로 카메라를 끌어 올리게 됩니다. 카메라는 지형으로부터 10 unit만큼 떨어지게 것입니다. 그렇게 해서 카메라가 지형으로부터 너무 가까이 위치해서 지형을 뚫고 볼수 없게끔 만들어 줍니다.

            Real terrainHeight = itr->worldFragment->singleIntersection.y;

            if ((terrainHeight + 10.0f) > camPos.y)

                mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );

        }

 

        return true;

마지막으로 계속해서 렌더링을 유지하도록 true값을 리턴합니다. 이제 프로그램을 컴파일후 테스트해볼 있습니다.

 

지형 선택

섹션에서는 마우스 왼쪽클릭시 화면상에 객체를 생성하는 기능을 구현합니다. 왼쪽클릭마다 객체가 생성되고 커서에 묶여있게 됩니다. 마우스버튼을 떼기 전까지 객체를 이동시킬 있습니다. 왼쪽클릭시 이러한 동작을 위해서 mousePressed함수의 내용을 변경해야 합니다. MouseQueryListener::mousePressed함수를 찾아서 if 구문 안에 다음 내용을 추가하세요.

       // Left mouse button down

       if (id == OIS::MB_Left)

       {

           mLMouseDown = true;

       } // if

문장은 익숙한 내용입니다. mRaySceneQuery객체에서 사용될 광선을 생성하고 설정합니다. 오우거에서 제공하는 Camera::getCameraToViewportRay함수가 있습니다; 상당히 유용한 함수입니다. 화면상에서 클릭된 좌표(x, y좌표) RaySceneQuery객체에서 사용할 있는 광선으로 변환해 줍니다.

           // Left mouse button down

           if (id == OIS::MB_Left)

           {

               // Setup the ray scene query, use CEGUI's mouse position

               CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();

               Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));

               mRaySceneQuery->setRay(mouseRay);

쿼리를 실행하고 리턴된 결과값을 확인합니다.

               // Execute query

               RaySceneQueryResult &result = mRaySceneQuery->execute();

               RaySceneQueryResult::iterator itr = result.begin( );

 

               // Get results, create a node/entity on the position

               if (itr != result.end() && itr->worldFragment)

               {

(클릭된 좌표에 대한)worldFragment 얻었고 해당 좌표에 객체를 생성해야 합니다. 첫번째 난관은 오우거에서의 모든 엔티티, SceneNode 중복되지 않는 이름을 가져야 합니다. 문제를 해결하기위해서 엔티티 각각에 대해서 Entity "Robot1", "Robot2", "Robot3"… 이름을 설정하고 SceneNode 대해서는 “Robot1Node", "Robot2Node", "Robot3Node"...이런식으로 설정할 입니다. 우선 이름을 생성합시다(C언어 문법책에서 sprintf 대한 내용을 참고하세요).

               char name[16];

               sprintf( name, "Robot%d", mCount++ );

다음 엔티티와 SceneNode 생성합니다. itr->worldFragment->singleIntersection 통해서 로봇의 최초위치를 설정합니다. 그리고 지형의 크기를 고려해서 로봇을 1/10 크기로 사이즈를 줄입니다. 지금 새롭게 생성된 객체는 mCurrentObject변수에 저장되는것을 눈여겨 봐두세요. mCurrentObject변수는 다음 섹션에서 사용됩니다.

                   Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");

                   mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);

                   mCurrentObject->attachObject(ent);

                   mCurrentObject->setScale(0.1f, 0.1f, 0.1f);

               } // if

 

               mLMouseDown = true;

           } // if

컴파일후 실행시켜보세요. 지형의 모든지점에 클릭만으로 로봇을 배치시킬 있습니다. 드래깅만 구현하면 프로그램은 완성됩니다. 이 if 구문 안에 코드를 추가하게 될 것 입니다 :

       // If we are dragging the left mouse button.

        if (mLMouseDown)

        {

        } // if

그다지 설명이 필요없는 코드입니다. 현재 마우스의 위치에서 광선을 생성하고 RaySceneQuery 이용하여 객체를 새로운 위치로 이동시킵니다. mCurrentObject변수의 내용이바른지 아닌지를 판별할 필요가 없는데 이유는 mCurrentObject변수가 mousePressed에서 설정되지 않았다면 mLMouseDown변수가 true값이 될리가 없기때문입니다.

if (mLMouseDown)

       {

           CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();

           Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width),mousePos.d_y/float(arg.state.height));

           mRaySceneQuery->setRay(mouseRay);

 

           RaySceneQueryResult &result = mRaySceneQuery->execute();

           RaySceneQueryResult::iterator itr = result.begin();

 

           if (itr != result.end() && itr->worldFragment)

               mCurrentObject->setPosition(itr->worldFragment->singleIntersection);

       } // if

컴파일 실행시켜보세요. 완성했습니다! 신경써서 클릭한다면 다음과 같은 결과물도 만들 있습니다 :

사용자 삽입 이미지


주의사항: TerrainSceneManager 이용해서 RaySceneQuery 교차지점을 얻고자 반드시 지형위에서 수행되어야 합니다.

:

중급 튜토리얼 2-3 (마지막)

Ogre3D 삽질란/Intermediate Tutorial 2 2008. 12. 10. 18:30

심화학습용 문제들

쉬운 문제

1.    지형을 바라보기위해 카메라를 지형으로부터 적어도 10 unit만큼 떨어지게끔 했습니다. 10 unit이란 거리는 그냥 적당히 정한 수치입니다. 지형을 꿰뚫지 않고 가까이 있는 수치로 설정할 있을까요? 만약 가능하다면 수치로 static class 멤버변수값으로 설정해 보세요.

2.    SceneEditor 경우 가끔씩은 지형을 통과하고 싶을때가 있습니다. 키보드로 flag값을 토글하여 충돌검사를 on/off 있도록 만들어 보세요. 충돌검사가 off 경우 frameStarted에서 SceneQuery 만들지 마세요.

 

보통 문제

1.    카메라위치가 변경되지 않아도 항상 SceneQuery 수행됩니다. 카메라가 움직인 경우에만 SceneQuery 수행되도록 문제점을 고쳐보세요. (힌트 : ExampleFrameListener translation vector 찾아서 Vector3::ZERO 비교해 보세요.)

 

어려운 문제

1.    장면에서 쿼리를 호출하는 과정에서 중복되는 코드가 너무 많습니다. SceneQuery 관련된 기능을 하나의 protected 함수로 묶으세요. 지형은 항상 선택되지 않는다는 것을 고려해야 합니다.

 

고급 문제

1.    튜토리얼에서는 객체를 지형에 위치시키는데 RaySceneQuery 사용했습니다. 기능은 다양한 목적으로 사용될 있습니다. 우선 튜토리얼1 코드를 가지고 어려운문제 1번을 해결하세요. 그리고 코드를 조합하여 로봇이 텅빈 공간이 아닌 지형위를 걷게끔 만들어 보세요.

2.    지형에서 클릭을 할때마다 로봇이 위치로 이동하게끔 만들어 보세요.

:

중급 튜토리얼 1-1

Ogre3D 삽질란/Intermediate Tutorial 1 2008. 12. 9. 20:06

중급 튜토리얼 1 (번역 : n_Sys)

애니메이션, 지점간 이동시키기, 사원수의 기초 

튜토리얼 진행에 있어서 문제가 생긴다면 Help 포럼 문의하세요.

목차

                               1 소개

                               2 사전 요구사항

                               3 시작하기

                               4 장면 설정

                               5 애니메이션

                               6 로봇 움직이기

                               7 심화학습용 문제들

                                       7.1 쉬운 문제

                                       7.2 보통 문제

                                       7.3 어려운 문제

                                       7.4 고급 문제

 

소개

튜토리얼에서는 엔티티를 어떻게 획득하고 움직이게 하며, 어떻게 미리 지정된 지점간을 걸을 있게 하는가를 다루게 것입니다. 엔티티가 걷는방향을 자연스럽게 유지시키기 위해서 사원수 회전의 기초내용도 다루게 됩니다. 내용을 스스로 천천히 진행하면서 완성되어지는 결과물을 직접 확인하세요.

튜토리얼의 최종결과물은 여기 있습니다.

 

사전 요구사항

튜토리얼은 여러분이 Ogre project 어떻게 설정하고 컴파일 하는지에 대해서 이미 알고있다고 가정합니다. 그리고 이번 튜토리얼에서는 STL deque 자료구조를 사용합니다. Deque 대한 지식은 필요 없습니다. 단지 어떻게 쓰는지만 알면 됩니다. STL 대해서 익숙치 않다면 STL포켓레퍼런스[ISBN 0-596-00556-3] 한권 구입하시길 권장합니다.

STL포켓레퍼런스의 부분을 여기 읽을 있습니다.

 

시작하기

데모를 만들기위해서 새로운 프로젝트를 생성합니다. “MoveDemo.cpp” 이름의 파일을 추가하고 다음 내용을 추가하세요 :

#include "ExampleApplication.h"

 

#include <deque>

using namespace std;

 

class MoveDemoListener : public ExampleFrameListener

{

public:

 

    MoveDemoListener(RenderWindow* win, Camera* cam, SceneNode *sn,

        Entity *ent, deque<Vector3> &walk)

        : ExampleFrameListener(win, cam, false, false), mNode(sn), mEntity(ent), mWalkList(walk)

    {

    } // MoveDemoListener

 

    /* This function is called to start the object moving to the next position

    in mWalkList.

    */

    bool nextLocation()

    {

        return true;

    } // nextLocation()

 

    bool frameStarted(const FrameEvent &evt)

    {

        return ExampleFrameListener::frameStarted(evt);

    }

protected:

    Real mDistance;                  // The distance the object has left to travel

    Vector3 mDirection;              // The direction the object is moving

    Vector3 mDestination;            // The destination the object is moving towards

 

    AnimationState *mAnimationState; // The current animation state of the object

 

    Entity *mEntity;                 // The Entity we are animating

    SceneNode *mNode;                // The SceneNode that the Entity is attached to

    std::deque<Vector3> mWalkList;   // The list of points we are walking to

 

    Real mWalkSpeed;                 // The speed at which the object is moving

};

 

 

class MoveDemoApplication : public ExampleApplication

{

protected:

public:

    MoveDemoApplication()

    {

    }

 

    ~MoveDemoApplication()

    {

    }

protected:

    Entity *mEntity;                // The entity of the object we are animating

    SceneNode *mNode;               // The SceneNode of the object we are moving

    std::deque<Vector3> mWalkList;  // A deque containing the waypoints

 

    void createScene(void)

    {

    }

 

    void createFrameListener(void)

    {

        mFrameListener= new MoveDemoListener(mWindow, mCamera, mNode, mEntity, mWalkList);

        mFrameListener->showDebugOverlay(true);

        mRoot->addFrameListener(mFrameListener);

    }

 

};

 

#if 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

    MoveDemoApplication app;

 

    try {

        app.go();

    } catch(Exception& e) {

#if 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;

}


컴파일이 제대로 되는지 확인하세요.

:

중급 튜토리얼 1-2

Ogre3D 삽질란/Intermediate Tutorial 1 2008. 12. 9. 20:05

장면 설정하기

시작하기에 앞서 MoveDemoApplication에서 설정했던 3개의 변수를 살펴봅시다. mEntity 생성시킬 엔티티를 가집니다. mNode 생성시킬 노드, mWalkList 객체가 걷게 포인트 위치정보를 포함하게 것입니다.

MoveDemoApplication::createScene함수로 가서 다음 코드를 추가하세요. 우선 밝은 주변광을 넣어서 객체들이 보이도록 만듭시다.

        // Set the default lighting.

        mSceneMgr->setAmbientLight(ColourValue(1.0f, 1.0f, 1.0f));

이제 가지고 로봇을 생성해 봅시다. 로봇 엔티티를 생성하고 SceneNode 만든다음 로봇을 매달아 놓읍시다.

       // Create the entity

       mEntity = mSceneMgr->createEntity("Robot", "robot.mesh");

 

       // Create the scene node

       mNode = mSceneMgr->getRootSceneNode()->

           createChildSceneNode("RobotNode", Vector3(0.0f, 0.0f, 25.0f));

       mNode->attachObject(mEntity);

매우 쉬운수준의 코드이므로 자세한 설명은 생략합니다. 다음코드는 로봇이 어디로 이동해야 할지를 설정합니다. STL 모르는 분들을 위해서 간략하게 설명하자면 deque객체는 double ended queue 효과적으로 구성한 형태 입니다. 여기서는 deque 일부분만 사용할겁니다. push_front push_back함수는 각각 deque 앞과 뒤에서 item 삽입하는 역할을 합니다. Front back함수는 각각 deque 앞과 뒤의 값을 리턴하는 역할을 합니다. The pop_front pop_back함수는 각각 deque 앞과 뒤의 item 삭제하는 역할을 합니다. 마지막으로 empty함수는 deque 비었는지에 대한 여부를 리턴합니다. 코드는 로봇이 움직일 목표점인 2개의 벡터를 deque 삽입합니다.

       // Create the walking list

       mWalkList.push_back(Vector3(550.0f,  0.0f,  50.0f ));

       mWalkList.push_back(Vector3(-100.0f,  0.0f, -200.0f));

이제는 로봇이 이동하고자 하는 목표점에 뭔가의 객체를 놓도록 합니다. 그렇게 하면 로봇이 어떠한 객체를 향해서 움직이는것 처럼 보일겁니다. Y 좌표가 음수인 것을 감안하세요. , 로봇이 움직이는 목표점의 하단에 놓이게 됩니다. 로봇이 객체에 도달하면 객체를 밟고 서있는 모습이 것입니다.

       // Create objects so we can see movement

       Entity *ent;

       SceneNode *node;

 

       ent = mSceneMgr->createEntity("Knot1", "knot.mesh");

       node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot1Node",

           Vector3(0.0f, -10.0f,  25.0f));

       node->attachObject(ent);

       node->setScale(0.1f, 0.1f, 0.1f);

 

       ent = mSceneMgr->createEntity("Knot2", "knot.mesh");

       node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot2Node",

           Vector3(550.0f, -10.0f,  50.0f));

       node->attachObject(ent);

       node->setScale(0.1f, 0.1f, 0.1f);

 

       ent = mSceneMgr->createEntity("Knot3", "knot.mesh");

       node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot3Node",

           Vector3(-100.0f, -10.0f,-200.0f));

       node->attachObject(ent);

       node->setScale(0.1f, 0.1f, 0.1f);

마지막으로 장면들이 보이게 하기 위해 카메라를 적당하게 설정합니다. 장면이 보이도록 카메라를 좋은 위치로 옮깁니다.

       // Set the camera to look at our handiwork

       mCamera->setPosition(90.0f, 280.0f, 535.0f);

       mCamera->pitch(Degree(-30.0f));

       mCamera->yaw(Degree(-15.0f));

컴파일 실행시키면 다음과 같은 화면이 보일겁니다 :


다음섹션으로 넘어가기전에 MoveDemoApplication::createFrameListener 에서 호출되는 MoveDemoListener 생성자를 확인하세요. BaseFrameListener 기본 매개변수와 함께 SceneNode, Entity, deque 전달됩니다.

 

애니메이션

이제 기본적인 애니메이션을 다뤄봅시다. 오우거에 있어서 애니메이션은 무척 단순한 부분입니다. 일단 엔티티객체로부터 AnimationState 구한다음 옵션을 설정하고 enable시키면 됩니다. 이렇게 애니메이션을 활성화 시킵니다. 그리고 애니메이션이 실행되기 위해서 프레임별 시간도 설정해 줘야 합니다. 과정들을 한방에 끝내봅시다. MoveDemoListener 생성자로 가서 다음 코드를 추가하세요 :

       // Set idle animation

       mAnimationState = ent->getAnimationState("Idle");

       mAnimationState->setLoop(true);

       mAnimationState->setEnabled(true);

두번째 라인에서 엔티티로부터 AnimationState 얻습니다. 세번째 라인에서 setLoop(true) 호출하는데 이건 애니메이션을 계속해서 반복시켜줍니다. 어떤 애니메이션(죽은상황같은) 경우는 true대신 false 설정해줘야 할지도 모릅니다. 네번째 라인에서는 애니메이션을 활성화 시켜줍니다. 그런데 여기서 잠깐.. “Idle”상태를 어디서 구한걸까요? 신기한 상태값은 어디서부터 걸까요? 모든 메쉬는 그것들만의 애니메이션상태정의값 셋트를 가지고 있습니다. 작업하려는 각각의 모든 메쉬들이 어떤 애니메이션들을 가지고 있는지 확인하려면 OgreMeshViewer 다운로드받고 직접 해당 메쉬를 확인해야 합니다.

이제 컴파일 실행시키면.. 아무것도 바뀐게 없습니다. 왜냐하면 시간마다 프레임을 갱신시켜줘야 합니다. MoveDemoListener::frameStarted함수의 시작부분에 다음코드를 추가하세요 :

        mAnimationState->addTime(evt.timeSinceLastFrame);

다시 빌드하고 실행시켜보세요. 로봇이 서서 idle 애니메이션을 수행하고 있음을 있습니다.

 

로봇 움직이기

이제 로봇이 멋지게 point to point 움직이게 해봅시다. 시작하기에 앞서 MoveDemoListener클래스에 저장된 몇가지 변수들을 설명하고자 합니다. 로봇이 움직이는데 4가지 변수가 사용될 것입니다. 로봇이 움직이는 방향을 mDirection 저장합니다. 현재의 로봇 도착지점은 mDestination 저장되며 도착지점까지 남은 거리는 mDistance 저장됩니다. 마지막으로 로봇의 이동속도는 mWalkSpeed 저장됩니다.

우선 MoveDemoListener 생성자 내용을 모두 지워야 합니다. 지금 작성된 코드와는 약간 다른 코드로 교체해야 합니다. 첫째로 해야 일은 클래스의 변수들을 셋업해야 합니다. 걷는속도는 35 units/sec입니다. 여기서 중요한 사항이 하나 있습니다. mDirection 영벡터로 설정하게 될텐데 이유는 나중에 변수로 로봇이 움직이고 있는지 아닌지를 판별하게 것이기 때문입니다.

        // Set default values for variables

        mWalkSpeed = 35.0f;

        mDirection = Vector3::ZERO;

일단 로봇이 움직이는데 필요한 작업은 끝입니다. 로봇을 움직이려면 단순히 로봇의 애니메이션만 바꿔주면 됩니다. 하지만 우리는 로봇이 이동할 도착지점이 있는 경우에만 움직이게 하고 싶습니다. 이런 이유로 nextLocation함수를 호출하게 됩니다. MoveDemoListener::frameStarted함수의 윗부분, AnimationState::addTime호출 이전에 다음 코드를 추가하세요 :

       if (mDirection == Vector3::ZERO)

       {

           if (nextLocation())

           {

               // Set walking animation

               mAnimationState = mEntity->getAnimationState("Walk");

               mAnimationState->setLoop(true);

               mAnimationState->setEnabled(true);

           }

       }

지금 실행시켜보면 로봇이 제자리에서 걷게 겁니다. 왜냐하면 영벡터인 상태에서 로봇이 시작되었고 MoveDemoListener::nextLocation함수는 항상 true값만 리턴하기 때문입니다. 나중 단계에서 MoveDemoListener::nextLocation함수에 지능적인 동작을 추가할 계획입니다.

이제 로봇을 장면에서 제대로 움직이게끔 만들어 봅시다. 그럴려면 프레임마다 조금씩 로봇을 이동시켜야 합니다. MoveDemoListener::frameStarted함수로 가서 AnimationState::addTime호출 이전에 다음 코드를 추가하세요. 코드가 로봇을 실제로 움직여 줄겁니다; mDirection != Vector3::ZERO. mWalkspeed evt.timeSinceLastFrame변수와 곱해주는 이유는 framerate 변하더라도 walkspeed 일정하게 유지시켜 주기 위함입니다. 만약 Real move = mWalkspeed 움직인다면 느린컴퓨터에서는 느리게 움직이고 빠른컴퓨터에서는 빠르게 움직일 겁니다.


        else

        {

            Real move = mWalkSpeed * evt.timeSinceLastFrame;

            mDistance -= move;

이제 로봇이 목표위치를 지나치는지감시할 필요가 있습니다. 말은 mDistance값이 현재 0보다 낮다면 이동할 위치를 건너뛰고이동할 위치를 다음지점으로 설정해야 합니다. mDirection값을 영벡터로 설정했습니다. 만약 nextLocation함수가 mDirection값을 바꾸지 않는다면(예를들면 갈곳이 없는경우) 이상 움직이지 않게 됩니다.

            if (mDistance <= 0.0f)

            {

                mNode->setPosition(mDestination);

                mDirection = Vector3::ZERO;

이제 다음지점으로 옮길 있게 되었습니다. 이제 다음지점으로 모션을 설정해야 합니다. 다음지점으로 이동해야 시점에서 적절한 애니메이션을 설정할 있습니다; 이동해야 또다른 다음지점이 있다면 걷고 더이상 도착지점이 없다면 idle입니다. 더이상 곳이 없을경우 idle 애니메이션을 설정하는것은 간단한 일입니다.

               // Set animation based on if the robot has another point to walk to.

               if (! nextLocation())

               {

                   // Set Idle animation                    

                   mAnimationState = mEntity->getAnimationState("Idle");

                   mAnimationState->setLoop(true);

                   mAnimationState->setEnabled(true);

               }

               else

               {

                   // Rotation Code will go here later

               }

           }

Queue 이동해야 지점이 있다고 해서 다시 걷기애니메이션을 설정할 필요는 없습니다. 이미 걷고 있는 상태일것이기 때문에 다시 하라고 알릴필요는 없습니다. 하지만 다른 포인트로 이동해야 한다면 지점을 바라볼 있도록 회전시켜야 합니다. else구문안에 주석표시를 달아놓읍시다; 나중에 여기로 다시 돌아와야 하거든요.

코드는 목표지점에 거의 도달했을때를 다룹니다. 지금은 한참 움직이고 있을 상태인 일반적인 경우를 다룰때 입니다. 이동방향으로 로봇을 이동변수값 만큼 이동시킵니다. 다음 코드를 추가함으로써 완성됩니다 :

            else

            {

                mNode->translate(mDirection * move);

            } // else

        } // if

거의 다했습니다. 이동에 필요한 변수값을 설정하는 부분을 제외하고는 모두 제대로 동작합니다. 만약 적절하게 이동변수값을 설정할 있게된다면 로봇은 스스로 움직일 것입니다. MoveDemoListener::nextLocation함수를 찾아가세요. 이상 움직일 지점이 없다면 false 리턴해야 합니다. 구현은 함수의 첫줄이 겁니다. (함수의 하단부분에 true 리턴하는 구문은 남겨둬야 합니다.)

        if (mWalkList.empty())

            return false;

이제 변수값을 설정해 봅시다. 먼저 deque에서 목표지점을 하나 건너뜁니다. 방향벡터는 목표지점으로부터 SceneNode 현재위치를 뺄셈연산으로 구해집니다. 여기서 작은 문제가 있습니다. 어떻게 mDirection값을 frameStarted에서 곱하는지 기억하십니까? 그렇게 하려면 방향벡터는 단위벡터(1 기준이 되는 길이) 되어야 합니다. Normalise함수가 문제를 해결해 주고 예전 벡터길이를 리턴합니다. 리턴된 값은 목표지점까지의 거리를 설정하는데 있어서도 유용합니다.

       mDestination = mWalkList.front();  // this gets the front of the deque

       mWalkList.pop_front();             // this removes the front of the deque

 

       mDirection = mDestination - mNode->getPosition();

       mDistance = mDirection.normalise();

컴파일하고 실행시켜보세요. 돌아갑니다! Sotra. 로봇이 모든포인트를 거치기는 하는데 항상 Vector3::UNIT_X방향(기본값) 바라보는군요. 로봇이 움직이는 방향쪽으로 바라보게 바꿔줘야 합니다.

로봇이 바라보고 있는 방향을 구하고 옳바른 방향으로 rotate함수를 이용해 회전시켜줘야 합니다. 이전과정에서 주석표시를 해둔 지점으로 가서 다음 코드를 추가하세요. 첫번째 줄은 로봇이 바라보는 방향을 구합니다. 두번째 라인은 현재방향에서 목표방향까지의 회전을 표현하는 사원수를 생성합니다. 세번째 라인은 실제로 로봇을 회전시킵니다.

        Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;

        Ogre::Quaternion quat = src.getRotationTo(mDirection);

        mNode->rotate(quat);

기초 튜토리얼 4에서 간단하게 사원수를 다루었습니다. 하지만 여기서 처음으로 사용되었습니다. 기본적으로 사원수는 3차원 공간에서의 회전을 표현합니다. 오우거에 있어서 객체가 공간의 어디에 위치되고 어떻게 회전되어야 할지에 대해서 쓰입니다. 함수의 첫번째 줄에서 호출된 getOrientation함수는 공간속의 로봇에 대한 사원수를 리턴합니다. 오우거가 어느면이 로봇의 정면인지 없으므로 위치정보를 UNIT_X 벡터(원래 바라보고 있던 로봇의 정면방향) 곱하여 로봇이 현재 바라보고 있는 방향을 구해야 합니다. 방향을 src변수에 저장하였습니다. 두번째 라인의 getRotationTo함수는 로봇이 바라보고 있는 방향으로부터 우리가 원하는 방향까지의 회전을 표현하는 사원수를 제공합니다. 세번째 라인에서는 새로운 방향으로 노드를 회전시킵니다.

지금까지 작성한 코드에는 한가지 문제점이 있습니다. SceneNode::rotate함수가 실패하는 특별한 경우입니다. 만약에 로봇을 정확하게 180도로 회전시키면 회전코드는 divide by zero 에러를 발생시킬 입니다. 경우 우리는 180 회전을 rotate함수를 안쓰고 yaw 처리할 입니다. 그러기 위해서 방금전의 3 라인들을 모두 지우고 다음코드로 교체합니다 :

       Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;

       if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)

       {

           mNode->yaw(Degree(180));

       }

       else

       {

           Ogre::Quaternion quat = src.getRotationTo(mDirection);

           mNode->rotate(quat);

       } // else

if문으로 둘러쌓인 부분을 제외하고는 설명이 필요없는 부분들 입니다. 만약 2개의 벡터들이 서로 마주보고 있는 상황(2개의 각도가 180도인 경우)이라면 그들의 dot product값은 -1 됩니다. 그래서 만약 2개의 벡터를 서로 dotProduct함수로 처리 한다면결과값은 -1.0f 되고 180도로 yaw시켜야 합니다. 이외의 상황에서는 rotate 이용합니다. 1.0f 더한뒤 0.0001f보다 작은지를 검사할까요? floating point rounding error 잊지마세요. 2개의 부동소숫점 숫자를 바로 비교해서는 안됩니다. 마지막으로 알아두실점은 2개의 벡터들에 대한 dot product값은 [-1, 1] 사이로 떨어진다는점 입니다. 간결하게 설명되지는 않지만 그래픽 프로그래밍을 하려면 적어도 선형대수학에 대한 기본지식은 필요합니다! 최소한 Quaternion and Rotation Primer부분정도는 복습하시고 벡터와 행렬연산에 대한 기본서를 참고하시길 바랍니다.

코드가 모두 완성되었습니다! 컴파일후 데모를 실행하여 로봇이 주어진 점들 사이를 걷는것을 확인하세요.


:

중급 튜토리얼 1-3 (마지막)

Ogre3D 삽질란/Intermediate Tutorial 1 2008. 12. 9. 20:00

심화학습용 문제들

쉬운 문제

1.    로봇의 경로에 많은 점들을 추가하세요. 하단부분에 매듭도 추가해서 로봇이 어디로 움직이고 있는지를 추적할 있도록 하세요.

2.    필요 이상으로 오랫동안 살아남는 로봇은 더이상 필요 없습니다! 로봇이 걷기를 끝마치면 idle애니메이션 대신에 death 애니메이션을 수행하도록 해보세요. Death 애니메이션은 “Die”입니다.

 

보통 문제

1.    mWalkSpeed값은 조금 잘못되었습니다. 튜토리얼을 진행하면서 눈치채지 못하셨나요? 값은 한번만 설정하고 전혀 바뀌지 않았습니다. 상수형 정적 클래스변수(constant static class variable)로 되어야 합니다. 그렇게 변수를 바꿔주세요.

2.    코드가 부분적으로 약간 엉성합니다. 로봇이 걷는상태와는 무관하게  항상 mDirection벡터를 참조하고 Vector3::ZERO 비교합니다. 로봇이 움직이고 있는지를 검사하는 방법을 mWalking 불리언변수를 사용하면 나아질 것입니다. 구현해보세요.

 

어려운 문제

1.    지금 작성된 클래스는 한계가 있습니다. 객체가 생성되고 나면 더이상 로봇이움직이는 경로에 포인트를 추가할 없습니다. 문제를 고치기 위해서 Vector3 mWalkList deque 응용하는 새로운 함수를 구현함으로써 가능해 집니다. (힌트를 드리자면 로봇이 계속 움직이는중이라면 deque 끝에 포인트를 추가하기만 하면 됩니다. 만약 움직임이 끝났다면 다시 움직이게 해야하며 nextLocation함수를 호출해서 다시 걷게끔 해야합니다.)

 

고급 문제

1.    작성된 클래스의 또다른 한계점은 오직 하나의 객체만 추적할 있다는 입니다. 객체의 수에 제한없이 각각 따로 움직이게끔 만드세요. (힌트를 드리자면 하나의 객체가 필요한 모든 정보를 담은 또다른 클래스를 작성해야합니다. 객체를 key값으로 나중에 다시 참조할 있게끔 STL map 저장합니다.) 추가적인 frame listener등록없이 구현할 있다면 더욱 좋습니다.

2.    이전 단계를 구현했다면 로봇들이 서로 충돌하지 않는다는걸 확인할 있을겁니다. 똑똑한 경로찾기 함수를 만들거나 로봇이 부딛히게 된다면 다른하나가 지나갈때까지 기다리게 하여 문제를 수정하세요.


:

기초 튜토리얼 8-1

Ogre3D 삽질란/Basic Tutorial 8 2008. 12. 5. 10:37

기초 튜토리얼 8 (번역 : n_Sys)

입문자 튜토리얼 8: 다중 Scene Manager 다루기

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

Contents

                               1 소개

                               2 미리 준비해야할

                               3 프로그램 설정

                                       3.1 SceneManager 생성

                                       3.2 Camera 생성

                                       3.3 Viewport 생성

                                       3.4 Scene 생성

                               4 기능 추가하기

                                       4.1 듀얼 SceneManager

                                       4.2 SceneManager 서로 바꾸기

                               5 결론

                                       5.1 Overlays

                                       5.2 One Last Note

 

소개

이번 간략한 튜토리얼에서는 어떻게 다수의 scene manager 들을 서로 바꾸는지에 대한 내용을 다루게 됩니다.

튜토리얼에 대한 코드는 여기(http://www.ogre3d.org/wiki/index.php/BasicTutorial8Source) 찾을 있습니다. 코드를 천천히 입력하면서 나오는 결과물을 직접 눈으로 확인하세요

 

미리 준비해야할

cpp파일을생성하고 다음 코드를 추가하세요 :

#include "ExampleApplication.h"

#define CAMERA_NAME "SceneCamera"

 

void setupViewport(RenderWindow *win, SceneManager *curr)

{

}

 

void dualViewport(RenderWindow *win, SceneManager *primary, SceneManager *secondary)

{

}

 

class SMTutorialListener : public ExampleFrameListener, public OIS::KeyListener

{

public:

    SMTutorialListener(RenderWindow* win, SceneManager *primary, SceneManager *secondary)

        : ExampleFrameListener(win, primary->getCamera(CAMERA_NAME), true, false),

          mPrimary(primary), mSecondary(secondary), mDual(false), mContinue(true)

    {

        mKeyboard->setEventCallback(this);

    }

 

    bool frameStarted(const FrameEvent& evt)

    {

        mKeyboard->capture();

        return mContinue;

    }

 

    bool keyPressed(const OIS::KeyEvent &arg)

    {

       switch (arg.key)

       {

       case OIS::KC_ESCAPE:

           mContinue = false;

           break;

 

       default:

           break;

       }

 

       return true;

    }

 

    bool keyReleased(const OIS::KeyEvent &) {return true;}

 

private:

    SceneManager *mPrimary, *mSecondary;

    bool mDual, mContinue;

 

    static void swap(SceneManager *&first, SceneManager *&second)

    {

        SceneManager *tmp = first;

        first = second;

        second = tmp;

    }

};

 

class SMTutorialApplication : public ExampleApplication

{

public:

    SMTutorialApplication()

    {

    }

 

    ~SMTutorialApplication()

    {

    }

protected:

    SceneManager *mPrimary, *mSecondary;

 

    void chooseSceneManager(void)

    {

    }

 

    void createCamera()

    {

    }

 

    void createViewports()

    {

    }

 

    void createScene(void)

    {

    }

 

    void createFrameListener(void)

    {

        mFrameListener = new SMTutorialListener(mWindow, mPrimary, mSecondary);

        mFrameListener->showDebugOverlay(true);

        mRoot->addFrameListener(mFrameListener);

    }

};

 

#if 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

    SMTutorialApplication app;

 

    try {

        app.go();

    } catch(Exception& e) {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

        MessageBoxA(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;

}

컴파일이 제대로 되는지 확인은 하시되 지금시점에서 실행은 되지않으므로 에러가 없다면 넘어갑니다.

 

:

기초 튜토리얼 8-2

Ogre3D 삽질란/Basic Tutorial 8 2008. 12. 5. 10:36

프로그램 설정

SceneManager 생성

SceneManager 선택하는방법에 대해서는 앞서 다룬적이 있습니다. 그러므로 부분에 대해서는 생략합니다. 여기서 다루는 내용과의 차이점은 2개의 SceneManager 생성한다는 입니다. chooseSceneManager함수에 다음 코드를 추가하세요 :

       mPrimary = mRoot->createSceneManager(ST_GENERIC, "primary");

       mSecondary = mRoot->createSceneManager(ST_GENERIC, "secondary");

 

Camera 생성

다음으로 해야 일은 각각의 SceneManager 대한 카메라 객체를 생성하는 입니다. 이전 튜토리얼과의 차이점은 같은 이름으로 2개의 카메라를 생성한다는 뿐입니다. createCamera함수에 다음 코드를 추가하세요 :

       mPrimary->createCamera(CAMERA_NAME);

       mSecondary->createCamera(CAMERA_NAME);

 

Viewport 생성

뷰포트 생성에 있어서 잠시 이전 튜토리얼에서 다룬 내용을 집고 넘어가겠습니다. 뷰포트생성시 2가지 사항을 지켜야 합니다 : 뷰포트 자체에대한 설정을 다음에 실제로 쓰일 카메라의 화면좌우비율을 설정해야 합니다. 다음 코드를 createViewports 함수에 추가하고 계속 진행합니다 :

       setupViewport(mWindow, mPrimary);

뷰포트를 설정하는 코드를 프로그램코드 어디에서나 사용할 있도록 setupViewport함수에 작성합니다. 첫번째로 해야 일은 지금까지 만들어 졌던 모든 뷰포트들을 제거하는 입니다. 비록 지금 아무것도 만들어진게 없다 하더라도 나중에 함수를 호출할때를 대비해서 새로만들 뷰포트 이전에 모든 뷰포트들을 제거하는 작업을 해야합니다. 다음 지금까지 해온 처럼 뷰포트를 설정하게 것입니다. 다음 코드를 파일의 최상위에 있는 setupViewport 함수에 추가하세요 :

   win->removeAllViewports();

 

   Camera *cam = curr->getCamera(CAMERA_NAME);

   Viewport *vp = win->addViewport(cam);

 

   vp->setBackgroundColour(ColourValue(0,0,0));

   cam->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));

 

Scene 생성

이제 각각의 SceneManager 표시할 장면을 생성할 차례입니다. 복잡하게 코드를 작성할 필요없이 그냥 2개의 SceneManager 바뀌었다는 것을 알아챌 있을 정도로만 만들겁니다. createScene함수에 다음 코드를 추가하세요 :

       // Setup the TerrainSceneManager

       mPrimary->setSkyBox(true, "Examples/SpaceSkyBox");

 

       // Setup the Generic SceneManager

       mSecondary->setSkyDome(true, "Examples/CloudySky", 5, 8);

이제 컴파일 해보세요. 실행시켜봐도 좋습니다. 다만 ESC키를 누르면 프로그램이 종료된다는 기능밖에는 없습니다.

 

기능 추가하기

듀얼 SceneManager

첫번째로 넣을 기능중 하나는 2개의 SceneManager 나란히 옆으로 위치하면서 화면에 출력하는 기능입니다. V키를 누르면 듀얼 뷰포트모드를 토글할 있습니다. 기본적인 설계는 간단합니다. 듀얼 뷰포트모드를 끄려면 그냥 setupViewport(앞서 작성한 내용)함수에 primary SceneManager 넘겨주며 뷰포트를 싱글모드로 다시 생성합니다. 켜기 위해서는 새로운 함수인 dualViewport함수를 호출하게 됩니다. 뷰포트모드상태를 감시하기위해 mDual 변수가 사용됩니다. 다음 코드를 keyPressed 함수의 switch구문 내부에 추가하세요 :

       case OIS::KC_V:

           mDual = !mDual;

 

           if (mDual)

               dualViewport(mWindow, mPrimary, mSecondary);

           else

               setupViewport(mWindow, mPrimary);

 

           break;

이제 mDual변수내용을 반전시키고 그에 따른 모드에 맞는 적절한 함수를 호출하게 됩니다. 이제 2개의 뷰포트들을 동시에 다루는 dualViewport함수를 정의합니다.

2개의 SceneManager 나란히 출력되기 위해서 해야 일은 setupViewport함수에 작성했던 내용과 기본적으로 동일합니다. 다른점이 하나 있다면 2개의 뷰포트를 생성한다는 입니다. 그리고 생성했던 2개의 SceneManager들에는 각각 하나의 카메라만 존재합니다. 다음 코드를 dualViewport함수에 추가하세요 :

   win->removeAllViewports();

 

   Viewport *vp = 0;

   Camera *cam = primary->getCamera(CAMERA_NAME);

   vp = win->addViewport(cam, 0, 0, 0, 0.5, 1);

   vp->setBackgroundColour(ColourValue(0,0,0));

   cam->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));

 

   cam = secondary->getCamera(CAMERA_NAME);

   vp = win->addViewport(cam, 1, 0.5, 0, 0.5, 1);

   vp->setBackgroundColour(ColourValue(0,0,0));

   cam->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));

addViewport함수에 새롭게 추가된 매개변수를 제외하고는 익숙한 코드입니다. 첫번째 매개변수는 계속해서 쓰일 카메라입니다. 두번째 매개변수는 뷰포트의 z 위치입니다. z 높이값이 높을수록 위에 위치하게 됩니다. 유의점으로는 같은 z 높이값에 2 이상의 뷰포트가 존재할 없다는 입니다. 겹치지 않더라도 예외는 없습니다. 이어지는 2개의 매개변수들은 뷰포트의 좌측 상단위치 입니다. 값들은 0~1 사이의 값을 가집니다. 마지막으로 남은 2개의 매개변수들은 width height값의 화면상 %비율 입니다( 값들 역시 0~1사이의 값을 가집니다). 그러므로 경우에는 첫번째 뷰포트는 좌표(0, 0)부터 수평으로는 절반크기에 수직으로는 꽉차게 위치하게 됩니다. 두번째 뷰포트는 좌표(0.5, 0)부터 남아있는 절반 수평넓이에 꽉찬 수직크기를 가집니다.

컴파일 실행시켜 보세요. V키를 누르면 2개의 SceneManager 동시에 출력시킬 있게 됩니다.

 

SceneManager 서로 바꾸기

마지막으로 추가할 기능은 C키를 누르면 2개의 SceneManager 언제라도 바꿀 있게끔 하는 기능을 추가하는 것입니다. 구현하는 방법은 setupViewport 또는 dualViewport 호출될때 mPrimary mSecondary 변수값을 바꾸는 입니다. 어떤 SceneManager 어떤 변수에 저장되어 있는지는 걱정하지 마세요. 첫번째 SceneManager 항상 싱글모드에서 출력되고 듀얼모드에서는 항상 좌측에 위치할 입니다. 다음 코드를 keyPressed함수의 switch구문 안에 추가하세요 :

       case OIS::KC_C:

           swap(mPrimary, mSecondary);

변수값이 바뀐 바뀐값을 프로그램에 적용시켜야 합니다. 듀얼모드 또는 싱글모드에 따라서 적절한 뷰포트 셋업함수를 호출하면 됩니다 :

           if (mDual)

               dualViewport(mWindow, mPrimary, mSecondary);

           else

               setupViewport(mWindow, mPrimary);

           break;

완성입니다! 컴파일 실행시켜보세요. C키를 누르면 SceneManager 바꾸고 V키를 누르면 싱글, 듀얼모드로 토글됩니다.

:

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

Ogre3D 삽질란/Basic Tutorial 8 2008. 12. 5. 10:35

결론

Overlays

듀얼 뷰포트모드로 프로그램을 실행하면 오우거 디버그오버레이가 양쪽에 모두 출력되는걸 보게 될겁니다. 뷰포트 1개별로도 오버레이를 그리지 않게끔 해줄 있어야 합니다. 그럴경우 Viewport::setOverlaysEnabled함수로 켜고 끄도록 합니다. 작은 수정을 적용한 코드를 여기(http://www.ogre3d.org/wiki/index.php/BasicTutorial8Source) 작성해 두었습니다. 어떻게 적용해야 모르겠다면 참고하세요.

 

One Last Note

뷰포트클래스자체는 기능이 별로없지만 오우거렌더링에 있어서 아주 중요한 부분입니다. SceneManager 얼마나 많이 생성하는지, 카메라를 SceneManager마다 몇개를 생성하는지는 중요하지 않습니다. 장면을 비추는 카메라에 뷰포트가 설정되지 않는다면 결국 그려지는건 아무것도 없습니다. 그리고 뷰포트를 추가로 생성하기전에 쓰지않는 뷰포트들을 지워줘야 하는것도 잊지 마세요.

:

기초 튜토리얼 7-1

Ogre3D 삽질란/Basic Tutorial 7 2008. 12. 4. 17:24

기초 튜토리얼 7 (번역 : n_Sys)

입문자 튜토리얼  7: CEGUI and Ogre

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

Contents

*                               1 미리 알아두어야

*                               2 소개

*                               3 시작하기

*                                       3.1 초기 코드

*                                       3.2 코드 컴파일

*                                       3.3 간략한 소개

*                               4 오우거에 통합시키기

*                                       4.1 CEGUI 초기화

*                                       4.2 이벤트 넣기

*                                       4.3 마우스 이벤트 넣기&변환

*                               5 , 시트, 위젯

*                                       5.1 소개

*                                       5.2 시트 읽어들이기

*                                       5.3 수동으로 객체 생성하기

*                               6 이벤트

*                               7 텍스쳐 렌더링하기

*                               8 결론

*                                       8.1 대안으로 쓸만 것들

*                                       8.2 자세한 정보

 

미리 알아두어야

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

소개

이번 튜토리얼에서는 오우거에서 어떻게 CEGUI(내장형 GUI 시스템) 있는지 살펴봅니다. 튜토리얼이 끝날때 여러분은 CEGUI 기초적인 기능을 프로그램에 추가할 있게 것입니다. NOTE: 튜토리얼에서는 CEGUI 모든 기능을 가르쳐 주지 않습니다. 단지 시작할 있게 도와줄 뿐입니다. CEGUI 대한 질문이나 도움요청은 공식 CEGUI홈피(http://www.cegui.org.uk/wiki/index.php/Main_Page) 이용해 주세요.

튜토리얼에 대한 코드는 여기(http://www.ogre3d.org/wiki/index.php/BasicTutorial7Source) 찾을 있습니다. 코드를 천천히 입력하면서 나오는 결과물을 직접 눈으로 확인하세요.

 

시작하기

 

초기 코드

튜토리얼에서는 미리 작성된 코드로 부터 시작 것입니다. 지금까지 튜토리얼을 진행해 처럼 이러한 방식은 익숙하리라 생각합니다. 프로젝트를 생성하고 아래 소스코드를 입력하세요 :

#include "ExampleApplication.h"

#include <CEGUI/CEGUI.h>

#include <OIS/OIS.h>

#include <OgreCEGUIRenderer.h>

 

class TutorialListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener

{

public:

    TutorialListener(RenderWindow* win, Camera* cam)

        : ExampleFrameListener(win, cam, true, true)

    {

       mContinue=true;

       mMouse->setEventCallback(this);

       mKeyboard->setEventCallback(this);

    } // CEGUIDemoListener

 

    bool frameStarted(const FrameEvent &evt)

    {

        mKeyboard->capture();

        mMouse->capture();

 

        return mContinue && !mKeyboard->isKeyDown(OIS::KC_ESCAPE);

    }

 

    bool quit(const CEGUI::EventArgs &e)

    {

        mContinue = false;

        return true;

    }

 

    // MouseListener

    bool mouseMoved(const OIS::MouseEvent &arg)

    {

        return true;

    }

 

    bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)

    {

        return true;

    }

 

    bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)

    {

        return true;

    }

 

    // KeyListener

    bool keyPressed(const OIS::KeyEvent &arg)

    {

        return true;

    }

 

    bool keyReleased(const OIS::KeyEvent &arg)

    {

        return true;

    }

 

private:

    bool mContinue;

};

 

 

class CEGUIDemoApplication : public ExampleApplication

{

public:

    CEGUIDemoApplication()

        : mSystem(0), mRenderer(0)

    {

    }

 

    ~CEGUIDemoApplication()

    {

        if (mSystem)

            delete mSystem;

 

        if (mRenderer)

            delete mRenderer;

    }

protected:

   CEGUI::System *mSystem;

   CEGUI::OgreCEGUIRenderer *mRenderer;

 

    void createScene(void)

    {

    }

 

    void createFrameListener(void)

    {

        mFrameListener= new TutorialListener(mWindow, mCamera);

        mFrameListener->showDebugOverlay(true);

        mRoot->addFrameListener(mFrameListener);

    }

 

};

 

#if 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

    CEGUIDemoApplication app;

 

    try {

        app.go();

    } catch(Exception& e) {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

        MessageBoxA(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;

}

 

 

코드 컴파일

지금 시점에서 컴파일하고 실행이 되는지 확인하세요. 검은화면밖에 보이지 않을 입니다(ESC키를 누르면 종료합니다). 컴파일시 Linker에러가 발생하면 CEGUIBase_d.lib OgreGUIRenderer_d.lib 파일을 Linker 추가하세요(디버그 모드의 경우임. 릴리즈 모드의 경우에는 _d 부분을 제거).

 

간략한 소개

CEGUI 오우거와 같은 3D 어플리케이션(물론 순수 DirectX OpenGL 완벽하게 지원합니다) 내장될 있는 강력한 GUI 전용 라이브러리 입니다. 오우거가 순수 그래픽 라이브러리인 점에서 많이 닮아 있습니다(사운드나 물리연산같은 다른 기능들을 포함하지 않습니다). CEGUI GUI 라이브러리일 입니다. 무슨 의미냐면 자기 스스로 출력하는 기능도 없을 뿐더러 마우스나 키보드 이벤트를 인지하지도 못합니다. CEGUI 출력되기 위해서는 Renderer 제공되어야 합니다(오우거에서는 OgreGUIRenderer 의미하며 SDK 포함되어 있습니다). 그리고 마우스와 키보드이벤트를 인식시키려면 해당시스템에 임의적으로 삽입시켜야 합니다. 처음부터 무척 힘들게 느껴질지도 모르겠군요. 하지만 실제로는 정말 약간의 코드만 필요로 뿐입니다. 일단 통합되기만 하면 GUI 출력과 입력에 대해서 완벽한 제어를 제공합니다. CEGUI자체만으로는 절대 그렇게 하지 못할 입니다.

CEGUI 다루게 되면 처음보는 희안한 코드들(다른 GUI시스템들을 접해봤다 하더라도) 접할 입니다. 진행하면서 나오는 부분마다 차근차근 설명해 드리겠습니다.

:

기초 튜토리얼 7-2

Ogre3D 삽질란/Basic Tutorial 7 2008. 12. 4. 17:23

오우거에 통합시키기

CEGUI 초기화

지난 튜토리얼에서 CEGUI 구동시키는 법을 간략하게나마 배웠으므로 첫부분에 대한 내용은 넘어가도록 하겠습니다. createScene함수를 찾아서 다음 코드를 추가하세요 :

       mRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);

       mSystem = new CEGUI::System(mRenderer);

CEGUI 초기화 되었습니다. 이제 사용할 스킨을 선택해야 합니다. GEGUI 높은 수준의 커스터마이징을 지원합니다. 프로그램의 스킨을 바꿔주면서 look and feel 설정할 있도록 지원해 줍니다. 스킨에 대한 내용은 다루지 않을 계획입니다. 많은 사용법을 배우고 싶다면 CEGUI 홈페이지를 방문하세요. 다음 코드는 스킨을 선택합니다 :

CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");

오우거설치시 기본값으로는 아무런 스킨도 설치되지 않습니다. 하지만 CEGUI CEGUI 공식홈페이지를 통해서 설치하면 몇가지 선택가능한 스킨이 포함됩니다(직접 만들 있습니다). 다음작업은 기본 마우스커서와 기본 폰트를 설정 하는 입니다 :

 

mSystem->setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow");

       mSystem->setDefaultFont((CEGUI::utf8*)"BlueHighway-12");

튜토리얼 시리즈들에서는 GUI 라이브러리를 쓸일이 없더라도 모두 CEGUI 커서를 사용합니다. 다른 GUI 라이브러리를 사용해서 마우스커서를 그리거나 오우거만 이용해서 바로 그릴수 있습니다(후자 경우 복잡한 선택사항이 수도 있습니다). 만약 마우스커서 하나 때문에 CEGUI 써야 하고 메모리 사용량과 개발중인 게임의 용량이 커질까봐 걱정된다면 앞서 설명한 선택사항들을 CEGUI 바꾸는걸 고려해 있습니다.

마지막에서 작성된 코드는 마우스커서를 설정했습니다. 그러나 나중에 있을 튜토리얼들과는 다르게 MouseCursor::setImage함수를 써서 직접적으로 마우스 커서를 설정하지 않았습니다. 튜토리얼에서는 CEGUI 윈도우의 종류(보이진 않습니다) 항상 화면상에 상주할 것이기 때문입니다. 결과적으로 디폴트 커서로 설정한 이미지로 마우스 커서가 만들어 것입니다. 만약 마우스커서를 직접 설정하고 디폴트값은 설정하지 않는다면 CEGUI 윈도우를 시간 통과하면서 보이지 않는 마우스커서가 것입니다( 튜토리얼에서는 절대 보이지 않게 겁니다). 반면에 아무런 CEGUI 윈도우를 출력하지 않는다면 디폴트 마우스 커서는 아무런 효과가 없습니다. 추후 이런 상황을 보게 것입니다. 이런 상황에서는 MouseCursor::setImage 함수를 써서 화면에 어플리케이션용 커서를 출력할 있습니다. 예제 입니다 :

CEGUI::MouseCursor::getSingleton().setImage(CEGUI::System::getSingleton().getDefaultMouseCursor());

 

이벤트 넣기

CEGUI 입력기능이 전혀 없습니다. 마우스의 움직임이나 키보드의 입력을 읽어내지 못합니다. 대신에 사용자로 하여금 입력과 마우스 이벤트를 시스템에 입력시키는 것에 의존합니다. 이제 이벤트를 다룰 차례입니다. CEGUI 쓰려면 버퍼방식 입력을 사용해야 합니다. 그래야만 이벤트가 발생하는 즉시 입력할 있습니다. KeyPressed 함수를 찾아서 다음 코드를 추가하세요 :

       CEGUI::System *sys = CEGUI::System::getSingletonPtr();

       sys->injectKeyDown(arg.key);

       sys->injectChar(arg.text);

시스템 객체를 얻은 이후 2가지 일들이 있습니다. 첫번째는 키가 눌려졌다는 이벤트를 CEGUI 입력하는것이고 두번째는 실제로 눌려진 키를 입력하는 입니다. Non-English 키보드일 경우 key down 이벤트 자체만으로는 항상 올바른 결과값을 가져다 주지 않으므로 눌려진 실제 키값을 입력시키는건 필수적 입니다. injectChar 유니코드를 고려해서 디자인 되었습니다.

이제는 키가 떨어졌을때의 상황처리를 해야 합니다. keyReleased함수를 찾아서 다음 코드를 추가하세요 :

CEGUI::System::getSingleton().injectKeyUp(arg.key);

키가 떨어졌을때는 키값을 입력해 필요가 없습니다. 키가 떨어졌다는 이벤트만 필요할 입니다.

 

마우스 이벤트 넣기&변환

키보드로 입력받는 작업은 모두 끝냈습니다. 이제 마우스 입력차례 입니다. 여기서 작은 문제점이 있습니다. 키가 눌려지고 떨어질때 CEGUI 입력시 변환작업은 없었습니다. OIS CEGUI 키보드에 대해서 같은 코드를 사용합니다. 하지만 마우스의 경우는 경우가 다릅니다. CEGUI 마우스버튼이 눌려졌다는 이벤트를 입력하기 전에 OIS 버튼ID로부터 CEGUI 버튼ID 변환시켜주는 함수를 작성해야 합니다. 다음 코드를 소스코드의 거의 최상단, TutorialListener클래스이전 부분에 추가하세요 :

CEGUI::MouseButton convertButton(OIS::MouseButtonID buttonID)

{

    switch (buttonID)

    {

    case OIS::MB_Left:

        return CEGUI::LeftButton;

 

    case OIS::MB_Right:

        return CEGUI::RightButton;

 

    case OIS::MB_Middle:

        return CEGUI::MiddleButton;

 

    default:

        return CEGUI::LeftButton;

    }

}

이제 마우스 이벤트를 입력시킬 준비가 되었습니다. mousePressed함수에 다음 코드를 추가하세요 :

CEGUI::System::getSingleton().injectMouseButtonDown(convertButton(id));

설명이 필요 없는 코드입니다. 입력받은 버튼ID 변환시켜서 CEGUI 전달합니다. MouseReleased함수에 다음 코드를 추가하세요 :

CEGUI::System::getSingleton().injectMouseButtonUp(convertButton(id));

마지막으로 마우스가 움직였을때 CEGUI 입력하기 입니다. CEGUI::System객체는 injectMouseMove함수를 가지고 있는데 마우스의 상대적 움직임거리를 입력받습니다. OIS::mouseMoved핸들러가 그에 연관된 움직임거리 state.X.rel state.Y.rel변수들을 제공합니다. MouseMoved함수에 다음 코드를 추가하세요 :

CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);

완성입니다. 이제 CEGUI 마우스와 키보드 입력에대한 모든 설정을 끝마쳤습니다.

: