OGRE/RaySceneQuery i proste użycie myszy

Rozpoczęcie

edytuj

Podobnie jak to wcześniej bywało zaczniemy od pewnego, bazowego kodu, który będziemy rozszerzać. Nazwijmy plik z tym kodem MouseQuery.cpp. Program będzie korzystać z CEGUI zatem przypadku użycia VC++ i SDK należy pamiętać o zanaczeniu opcji "CEGUI Support" w opcjach tworzonego projektu. Pamiętaj też o dostosowaniu kodu do wersji Ogre. Jeżeli używasz Ogre 1.0 odkomentuj linię po każdym komentarzu "//[Ogre 1.0]" i zakomentuj jedną linię po każdym komentarzu "//[Ogre 1.2]".

#include <CEGUI/CEGUISystem.h>
#include <CEGUI/CEGUISchemeManager.h>
#include <OgreCEGUIRenderer.h>

#include "ExampleApplication.h"
 
class MouseQueryListener : public ExampleFrameListener, public MouseListener, public MouseMotionListener
{
public:

    MouseQueryListener(RenderWindow* win, Camera* cam, SceneManager *sceneManager, CEGUI::Renderer *renderer)
        : ExampleFrameListener(win, cam, false, true), mGUIRenderer(renderer)
    {
    } // MouseQueryListener

    ~MouseQueryListener( )
    {
    }

    bool frameStarted(const FrameEvent &evt)
    {
        return ExampleFrameListener::frameStarted( evt );
    }

   /* zdarzenia MouseListenera. */
   virtual void mouseClicked(MouseEvent* e) { }
   virtual void mouseEntered(MouseEvent* e) { }
   virtual void mouseExited(MouseEvent* e)  { }

   // Te wywoływane jest przy naciśnieciu dowolnego klawisza myszy
   virtual void mousePressed(MouseEvent* e)
   {
   } // mousePressed

   // Te wywoływane jest gdy klawisz myszy jest zwalniany
α   virtual void mouseReleased(MouseEvent* e)
   {
   } // mouseReleased

   /* zdarzenia MouseMotionListenera */
   virtual void mouseMoved (MouseEvent *e)
   {
   } // mouseMoved

   // To wywoływane jest gdy nacisnąwszy klawisz trzymamy go i przesuwamy myszką.
   virtual void mouseDragged (MouseEvent *e)
   {
   } // mouseDragged

protected:
    RaySceneQuery *mRaySceneQuery;     // wskaźnik do zapytania ray scene
    bool mLMouseDown, mRMouseDown;     // prawda gdy klawisze myszki są wciśnięte
    int mCount;                        // ilość robotów na scenie
    SceneManager *mSceneMgr;           // wskaźnik do managera sceny
    SceneNode *mCurrentObject;         // nowo tworzony obiekt
    CEGUI::Renderer *mGUIRenderer;     // cegui renderer
};

class MouseQueryApplication : public ExampleApplication
{
protected:
    CEGUI::OgreCEGUIRenderer *mGUIRenderer;
    CEGUI::System *mGUISystem;         // cegui system
public:
    MouseQueryApplication()
    {
    }

    ~MouseQueryApplication() 
    {
    }
protected:
    void chooseSceneManager(void)
    {
        // Use the terrain scene manager.
        //[Ogre 1.0]
        //mSceneMgr = mRoot->getSceneManager( ST_EXTERIOR_CLOSE );
        //[Ogre 1.2]
        mSceneMgr = mRoot->createSceneManager( ST_EXTERIOR_CLOSE);
        
    }

    void createScene(void)
    {
    }

    void createFrameListener(void)
    {
        mFrameListener= new MouseQueryListener(mWindow, mCamera, mSceneMgr, mGUIRenderer);
        mFrameListener->showDebugOverlay(true);
        mRoot->addFrameListener(mFrameListener);
    }

};


#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
{
    // tworzymy obiekt aplikacji
    MouseQueryApplication app;

    try {
        app.go();
    } catch( Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
        MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        fprintf(stderr, "An exception has occured: %s\n",
                e.getFullDescription().c_str());
#endif
    }

    return 0;
}

Upewnij się, że możesz bez błędów skompilować powyższy kod.

Ustawienie sceny

edytuj

Odszukaj metodę createScene. Kod, który w nią wprowadzimy powinien być dla Ciebie znajomy.

       // Ustawienie światła otoczenia
       mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
       mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
  
       // Wczytanie geometrii terenu
       mSceneMgr->setWorldGeometry( "terrain.cfg" );
  
       // Ustawienie pozycji kamery
       mCamera->setPosition( 40, 100, 580 );
       mCamera->pitch( Degree(-30) );
       mCamera->yaw( Degree(-45) );

Po ewentualnym przekompilowaniu i uruchomieniu zobaczymy standardowy obrazek szarego terenu pod płynącymi chmurami. Teraz potrzebujemy właczyć kursor myszki. Jednak zanim to będziemy mogli zrobić trzeba uruchomić CEGUI. Po pierwsze tworzymy obiekt OgreCEGUIRenderer a następnie obiekt System, który przekazujemy do stworzonego Rendera. Opis specyfikacji ustawieć CEGUI zostawimy do poruszenia w innym artykule. Jedyne co powinienieś wiedzieć to to, że zawsze trzeba podać CEGUI z którego SceneManagera korzystamy co uczyniono za pomocą ostatniego parametru przekazanego do mGUIRenderer. Pamiętaj o dostosowaniu kodu do uzywanej wersji CEGUI. W starszej wersji ww. wywołanie ma inne parametry.

       // start CEGUI
       //[Stare CEGUI]
       //mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, ST_EXTERIOR_CLOSE);
       //[Obecne CEGUI]
       mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
       mGUISystem = new CEGUI::System(mGUIRenderer);

Teraz wyświetlimy kursor. Tu też nie będziemy wgłębiali się w szczegóły pozostawiając to na inny artykuł.

       // Kursor myszy
       CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLook.scheme");
       CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");

Gdy teraz skompilujesz i uruchomisz kod, zobaczysz dodatkowo kursor w środku ekranu. [Uwaga: jeśli wystąpią błędy z komentarzem "can't find TaharezLook..." lub ekran będzie ciemny] popróbuj zmienić użyć nazwy "TaharezLookSkin.scheme". (Jeśli dalej występują błędy odszukaj plik definicji schematu, który znajduje się w katalogu media/gui. Sprawdź jak się naprawdę nazywa i czy NAZWA w linii "GUIScheme Name=NAZWA" jest taka sama. Sprawdź też czy umieszczone w nim wskazania na inne pliki podane są prawidłowo.)

Rozpoczynamy pracę z FrameListenerem

edytuj

Wszystko co było potrzebne wprowadziliśmy do naszej aplikacji. Teraz musimy wprowadzić kod zarządzający tymi elementami. Robiąc to będziemy modyfikować FrameListener. Spróbujmy podsumować czego bedziemy od niego wymagać:

  • Chcemy połączyć ruch myszki z wciśniętym prawym przyciskiem z trybem zmiany kierunku patrzenia kamery. W chwili obecnej najbardziej dziwaczną rzeczą jest niemożność obejrzenia całego otoczenia za pomocą poruszenia myszką i przywrócenie tego będzie naszym pierwszym zadaniem.
  • Po drugie zablokujemy możliwość przechodzenia kamery przez powierzchnię ziemi. Sytuacja znalezienia się "pod ziemią" nie jest oczekiwana w tego typu programach.
  • Po trzecie chcemy uzyskać możliwość stawiania obiektów (entities) w dowolnym miejscu na powierzchni ziemi wskazanym myszką za pomocą kliknięcia lewym przyciskiem.
  • Wreszcie chcielibyśmy uzyskać możliwość przesuwania obiektów. Powinno byc to możliwe po najechaniu na obiekt myszką, nacisnięcie lewego klawisza i bez jego puszczania przesunięcie myszką na nowe miejsce gdzie zwalniamy klawisz, czyli tak zwany "drag-drop".

W tym celu użyjemy kilku chronionych zmiennych (które już są dodane do klasy):

    RaySceneQuery *mRaySceneQuery;     // wskaźnik do zapytania ray scene
    bool mLMouseDown, mRMouseDown;     // prawda gdy klawisz myszki jest wciśnięty
    int mCount;                        // ilość robotów na ekranie
    SceneManager *mSceneMgr;           // wskaźnik do managera sceny
    SceneNode *mCurrentObject;         // nowo tworzony obiekt
    CEGUI::Renderer *mGUIRenderer;     // cegui renderer

Zmienna mRaySceneQuery utrzymuje kopię RaySceneQuery którą będziemy używali do odszukania współrzędnych w terenie. Zmienne mLMouseDown i mRMouseDown będą nas informować czy i który klawisz myszki jest wciśnięty (np. mLMouseDown zawierać będzie prawdę gdy użytkownik wciśnie lewy klawisz myszki, w przeciwnym przypadku będzie tam fałsz). mCount zlicza ilość obiektów wstawianych na podłoże. mCurrentObject zawiera wskaźnik do najświerzszej utworzonej SceneNode (będziemy jej używać do "przeciągania" obiektów). Wreszcie, mGUIRenderer zawiera wskaźnik do Rendera CEGUI, który będzie używany do uaktualniania środowiska CEGUI.

Zauważy, że mamy też wiele funkcji związanych z listenerami myszki. Nie będziemy ich wszystkich uzywać w tym programie, ale muszą zostać w nim zdefiniowane gdyż w przeciwnym razie nie zdołamy prawidłowo skompilować naszego programu.

Konfigurujemy FrameListenera

edytuj

Odszukaj konstruktor MouseQueryListener i dodaj następujący kod inicjalizacyjny. Proszę również zauważyć, że zmniejszamy szybkość przesuwania kamery (w przód i w tył) tworząc złudzenie powiększenia przestrzeni a zarazem zwiększyliśmy szybkość jej obrotu.

        // Ustawienie domyślnych wartości zmiennych
        mCount = 0;
        mCurrentObject = NULL;
        mLMouseDown = false;
        mRMouseDown = false;
        mSceneMgr = sceneManager;

        // Zmiana szybkości kamery
        mMoveSpeed = 50;
        mRotateSpeed *= 2;

By umożliwić MouseQueryListenerowi odbieranie zdarzeń myszki, musimy zarejestrować go jako MouseListener i jako MouseMotionListener. Sprawdź w Ogre API reference jakie metody są zdefiniowane dla MouseListener i dla MouseMotionListener.

        // Rejestrujemy tak by odbierać zdarzenia myszki.
        mEventProcessor->addMouseListener( this );
        mEventProcessor->addMouseMotionListener( this );

Wreszcie w konstruktorze musimy utworzyć obiekt RaySceneQuery. Robimy to wywołując funkcję managera sceny.

        // Tworzymy RaySceneQuery
        mRaySceneQuery = mSceneMgr->createRayQuery( Ray() );

To wszystko co musimy zrobić w konstruktorze, jednak ponieważ utworzyliśmy nowy obiekt RaySceneQuery, musimy także zadbać o jego zniszczenie. Odszukaj destruktor MouseQueryListener (~MouseQueryListener) i dodaj do niego poniższą linię:

        // Wcześniej stworzylismy zapytanie, teraz musimy zadbać o jego usunięcie.
        delete mRaySceneQuery;

Przed przejściem do następnej sekcji sprawdź czy obecny kod da się prawidłowo skompilować.

Dodajemy "Rozglądanie się" za pomocą myszy

edytuj

Postaramy się teraz dodać do naszego programu możliwość zmiany kierunku patrzenia kamery przy przesuwaniu myszki z naciśniętym prawym klawiszem. By to dokonać musimy:

  • uaktualnić środowisko CEGUI gdy mysz się porusza (przecież kursor też się porusza);
  • ustawić mRMouseButton tak by wskazywał prawdę gdy prawy klawisz zostanie wciśnięty;
  • ustawić mRMouseButton tak by wskazywał fałsz gdy klawisz zostanie puszczony;
  • zmienić widok gdy będziemy "przeciągać" myszką (klawisz naciśnięty i trzymany podczas ruchu)
  • ukryć kursor gdy "przeciągamy"

Odszukaj metodę MouseQueryListener::mouseMoved. Dodamy do niej poniższy kod powodujący przesuwanie kursora za każdym razem gdy poruszymy myszką.

       // Uaktualnij CEGUI przy ruchu myszki
       CEGUI::System::getSingleton().injectMouseMove(e->getRelX() * mGUIRenderer->getWidth(), e->getRelY() * mGUIRenderer->getHeight());

Ponieważ w momencie rozpoczęcia "przeciągania" ruchy myszki przesyłane są już do innego zdarzenia (a przecież pozycja kursora musi i wtedy się zmieniać) musimy dodać uaktualnienie także na poczatku metody mouseDragged:

       // Uaktualnij CEGUI przy ruchu myszki
       mouseMoved(e);

Teraz odszukaj metodę MouseQueryListener::mousePressed. Ten kawałek kodu będzie zapewniał ukrywanie kursora gdy naciśniemy prawy klawisz myszki a zarazem będzie zmieniał stan mLMouseDown i mRMouseDown na prawdę.

       // naciśnięty lewy klawisz myszki
       if (e->getButtonID() & InputEvent::BUTTON0_MASK)
       {
           mLMouseDown = true;
       } // if
  
       // naciśnięty prawy klawisz myszki
       else if (e->getButtonID() & InputEvent::BUTTON1_MASK)
       {
           CEGUI::MouseCursor::getSingleton().hide( );
           mRMouseDown = true;
       } // else if

Teraz musimy pokazać na nowo kursor a także zmienić mLMouseDown i mRMouseDown na fałsz gdy puścimy odpowiedni klawisz. Odszukaj metodę mouseReleased i dodaj poniższy kod:

       // lewy klawisz myszki został puszczony
       if (e->getButtonID() & InputEvent::BUTTON0_MASK)
       {
           mLMouseDown = false;
       } // if
  
       // prawy klawisz myszki został puszczony
       else if (e->getButtonID() & InputEvent::BUTTON1_MASK)
       {
           CEGUI::MouseCursor::getSingleton().show( );
           mRMouseDown = false;
       } // else if

Teraz chcielibyśmy jeszcze zmienić widok gdy przeciągamy myszką z wciśniętym prawym klawiszem. By to zrobić musimy odczytać odległość o jaką przesunęła się myszka od ostatniego wywołania tej funkcji. Zrealizujemy to za pomocą wywołania funkcji getRel. Kod ten należy dodać do metody mouseDragged. Kiedy poruszamy myszką z wciśniętym lewym klawiszem nic się nie dzieje ale gdy "przeciągamy" nią z prawym klawiszem następuje zmiana ustawienia kamery.

       // kiedy przeciągamy z wciśniętym lewym klawiszem
       if ( mLMouseDown )
       {
       } // if
  
       // kiedy przeciągamy z wciśniętym prawym klawiszem
       else if ( mRMouseDown )
       {
           mCamera->yaw( -e->getRelX() * mRotateSpeed );
           mCamera->pitch( -e->getRelY() * mRotateSpeed );
       } // else if

Teraz skompilujmy kod i uruchommy go a zobaczymy, że możemy kontrolować ruchy kamery za pomocą myszki z wciśnietym prawym klawiszem.

Wykrywanie kolizji z terenem

edytuj

Kolejnym krokiem jest spowodowanie by podczas poruszania się w terenie nie przechodzić przez niego. Nie będziemy nic zmieniać w kodzie wprowadzonym już do BaseFrameListenera, który umożliwia poruszanie kamerą. Natomiast dodamy po poruszeniu kamery przez BaseFrameListenera sprawdzenie czy kamera jest w odległości nie mniejszej niż 10 jednostek od powierzchni terenu. Jeżeli nie, odległość ta będzie poprawiana na 10 jednostek. Przepisz dokładnie kod. Będziemy używać jeszcze RaySceneQuery do wielu innych rzeczy i nie chciałbym teraz zbytnio w jego użycie się wgłębiać. Usuń cały dodychczasowy kod z metody MouseQueryListener::frameStarted. Pierwszym krokiem który powinniśmy dodać jest wywołanie metody ExampleFrameListener::frameStarted tak by wykonała wszystkie swoje standardowe czynności. Jeśli zwróci fałsz to i nasz kod zwróci fałsz.

        // Wykonujemy standardowy kod frame listenera. Musi to zostać zrobione
        // zanim zaczniemy manipulować wektorem translacji.
        if ( !ExampleFrameListener::frameStarted( evt ) )
            return false;

Nastąpiło teraz przemieszczenie kamery. Mamy zamiar teraz ustalić bieżącą pozycję kamery i wypuścić "promień" (ray) testujący odległość pionowo w dół (w kierunku terenu). Promień ten jest parametrem wywołania RaySceneQuery, które zwróci nam wysokość na jakiej znajdujemy się względem terenu. Można to sobie wyobrazić jako oświetlenie powierzchni za pomocą dalmierza laserowego (oczywiście w naszym przypadku całkowicie niewidocznego). Po pobraniu aktualnej pozycji musimy utworzyć promień. Promień zawiera współrzędne początkowe (tam gdzie się zaczyna) i kierunek. W tym wypadku kierunek musi być ustawiony na UNIT_NEGATIVE_Y, bowiem mam kierować się dokładnie w dół. Gdy już utworzymy promień przekażemy go do użycia zapytaniu RaySceneQuery.

        // Tworzymy zapytanie sceny
        Vector3 camPos = mCamera->getPosition( );
        Ray cameraRay( Vector3(camPos.x, 5000.0f, camPos.z), Vector3::NEGATIVE_UNIT_Y );
        mRaySceneQuery->setRay( cameraRay );

Teraz wykonujemy zapytanie i pobieramy wynik. Wynik zapytania zwracany jest w formie std::iterator, którą pokrótce wyjaśnię.

        // Wykonanie zapytania sceny
        RaySceneQueryResult &result = mRaySceneQuery->execute();
        RaySceneQueryResult::iterator itr = result.begin( );

Rezultat jest w zasadzie (zastosowano tu oversimplifikację) listą elementów worldFragments (w tym przypadku Terrain) oraz elementów ruchomych (które wyeliminujemy w dalszej części). Jeśli nie jesteś zbyt zaznajomiony z iteratorami STL, wystarczy byś wiedział że pobranie pierwszego elementu iteratora zapewnia metoda 'begin'. Jeżeli result.begin() == result.end(), oznacza to że nie ma żadnych wyników do pobrania. W następnych artykułach będziemy mieli do czynienia z wieloma zwracanymi wartościami z zapytania SceneQuery. Na razie możemy sobie takie problemy pominąć. Poniższy kod sprawdza tylko czy mamy co najmniej jedną zwróconą wartość ( itr != result.end( ) ), i czy jest ona terenem (itr->worldFragment).

        // Pobierz wyniki, ustaw wysokość kamery
        if ( itr != result.end() && itr->worldFragment )
        {

Struktura worldFragment zawiera lokalizację punktu gdzie nasz promień dotknął gruntu w postaci zmiennej singleIntersection (która jest wektorem trójwymiarowym). Możemy zatem ustalić wysokość powierzchni gruntu przez przekazanie wartości y do naszej zmiennej lokalnej terrainHeight. Kiedy już będziemy mieli tę wysokość możemy sprawdzić czy nasza kamera jest poniżej założonej wysokości i jeżeli tak z powrotem podnieść ją w górę. Zauważ, że w takim przypadku przesuwamy kamerę na wysokość o 10 większą od poziomu gruntu. To zapewni, że nie będziemy widzieć przez grunt gdybyśmy byli zbyt blisko niego.

            Real terrainHeight = itr->worldFragment->singleIntersection.y;
            if ((terrainHeight + 10.0f) > camPos.y)
                mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );
        }
       return true;

Na końcu kodu zwracamy prawdę by nasz rendering mógł być kontynuowany. Teraz powinienieś przekompilować i przetestować program.

Wstawianie obiektu

edytuj

W tej części utworzymy możliwość tworzenia i dodawania obiektów w terenie za każdym razem gdy klikniesz lewym klawiszem myszki. Obiekt zostanie wstawiony w miejscu, które pokazuje kursor myszki i do momentu puszczenia klawisza będzie go jeszcze mozna przesunąć w odpowiednie miejsce. Musimy zmienić funkcję mousePressed tak by wykonywała coś innego gdy klika się lewym klawiszem. Oszukaj funkcję MouseQueryListener::mousePressed function a w niej następujący fragment kodu. Nowy kod wstawimy wewnątrz procedury 'if'.

       // Naciśniety lewy klawisz myszy
       if (e->getButtonID() & MouseEvent::BUTTON0_MASK)
       {
           mLMouseDown = true;
       } // if

Pierwszy kawałek kodu wygląda znajomo. Tworzymy i konfigurujemy promień do zadania pytania przez mRaySceneQuery. Ogre daje nam pomoc w postaci Camera::getCameraToViewpointRay; miłej funkcji przekształcającej punkt w którym klikneliśmy na ekranie (wpółrzędne x i y) na promień, który można bezpośrednio użyć z zapytaniem RaySceneQuery.

       // Naciśnięty lewy klawisz myszy
       if (e->getButtonID() & MouseEvent::BUTTON0_MASK)
       {
           // Ustawienie zapytania z promieniem
           Ray mouseRay = mCamera->getCameraToViewportRay( e->getX(), e->getY() );
           mRaySceneQuery->setRay( mouseRay );

Teraz wykonujemy zapytanie i upewniamy się czy zwróciło jakąś wartość.

           // Wykonanie zapytania
           RaySceneQueryResult &result = mRaySceneQuery->execute();
           RaySceneQueryResult::iterator itr = result.begin( );

           // Pobranie wyników, tworzenie node/entity w punkcie kliknięcia
           if ( itr != result.end() && itr->worldFragment )
           {

Mamy teraz worldFragment (a zatem pozycję gdzie klikneliśmy), i będziemy tworzyć obiekt a następnie umieścimy go w tej pozycji. Pierwszym problemem jest to, że każde Entity i SceneNode w Ogre wymaga unikalnej nazwy. By to zapewnić będziemy każde Entity nazywać: "Robot1", "Robot2", "Robot3"... a każde SceneNode "Robot1Node", "Robot2Node", "Robot3Node"... i tak dalej. A zatem najpierw będziemy tworzyć nazwę.

               char name[16];
               sprintf( name, "Robot%d", mCount++ );

Następnie utworzymy Entity i SceneNode. Zauważ, że używamy use itr->worldFragment->singleIntersection do ustalenia domyślnej pozycji robota. Dodatkowo zmniejszyliśmy go do 1/10 wielkości ze względu na niewielkość terenu, którym się posługujemy. Zauważ także że przypisujemy nowo utworzony obiekt do zmiennej mCurrentObject. Będziemy tego potrzebować w dalszej części.

               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

Teraz przekompiluj i uruchom program. Możemy już za pomocą kliknięcia wstawiać roboty w dowolnym miejscu na powierzchni terenu w miejscu gdzie pokazuje kursor.

Program mamy prawie ukończony. Pozostało nam wprowadzić możliwość przeciągania obiektów przed wstawieniem ich na grunt. Odszukajmy funkcję MouseQueryListener::mouseDragged. Dodamy kod wewnątrz procedury 'if':

       // Przeciągamy trzymając lewy klawisz
       if ( mLMouseDown )
       {
       } // if

Ten kawałek kodu powinien być już w pełni zrozumiały. Tworzymy promień na podstawie pozycji myszki, zadajemy pytanie RaySceneQuery i przesuwamy obiekt na nową pozycję. Proszę zwrócić uwagę, że nie musimy sprawdzać mCurrentObject czy jest właściwy, gdyż mLMouseDown nie może być prawdą jeżeli mCurrentObject nie był ustawiony przez mousePressed.

       // Przeciągamy myszką z wciśniętym lewym klawiszem i mamy obiekt w miejscu kursora
       if ( mLMouseDown )
       {
           Ray mouseRay = mCamera->getCameraToViewportRay( e->getX(), e->getY() );
           mRaySceneQuery->setRay( mouseRay );

           RaySceneQueryResult &result = mRaySceneQuery->execute();
           RaySceneQueryResult::iterator itr = result.begin( );

           if ( itr != result.end() && itr->worldFragment )
           {
               mCurrentObject->setPosition( itr->worldFragment->singleIntersection );
           } // if
       } // if

Zakończyliśmy naszą pracę. Przekompiluj i wykonaj program. Uwaga: Kamera (pozycja wyjściowa promienia) musi być nad terenem by zapytanie RaySceneQuery zwróciło właściwą odpowiedź.

Zadania do ćwiczeń

edytuj
  • Proste zadania
  1. By utrzymać kamerę ponad gruntem wybraliśmy wartość 10 jednostek minimalnej wysokości. Czy można zmienić tę wartość na mniejszą bez przechodzenia przez grunt? Jeśli tak, utwórz tą zmienną jako statycznego członka klasy i przypisz ją właściwie.
  2. Niekiedy chcemy przechodzić przez grunt, na przykład w edytorze sceny. Utwórz flagę która będzie przełączać wykrywanie kolizji i powiąż ją z jednym z klawiszy na klawiaturze. Upewnij się że nie będziesz wykonywał SceneQuery w frameStarted jeżeli wykrywanie kolizji będzie wyłączone.
  • Ćwiczenia średnio zaawansowane
  1. Obecnie zapytanie SceneQuery wykonujemy w czasie każdej ramki, bez względu na to czy kamera się poruszyła. To oczywiście niepotrzebnie obciąża procesor. Napraw ten problem i wykonuj zapytanie tylko po ruchu kamery. (Podpowiedź: znajdż wektor translacji w ExampleFrameListener, po wywołaniu funkcji przetestuj gą względem Vector3::ZERO.)
  • Ćwiczenia zaawansowane
  1. Zauważ jak wiele kodu jest powielonego przy każdym wywołaniu zapytania. Przenieś całą funkcjonalność zapytania do chronionej funkcji. Upewnij się, że będzie działać także w przypadku gdy teren nie jest wcale podzielony.
  • Ćwiczenia do dalszych studiów
  1. W tym artykule do umieszczania obiektów wykorzystujemy zapytania RaySceneQueries. Możemy go użyć także do wielu innych zastosowań. Weź kod z artykułu "Animacja" i spróbuj rozwiązać trudne zadanie nr 1 i zadanie eksperta nr 1. Potem połącz oba kody tak by robot chodził po powierzchni gruntu a nie w pustej przestrzeni.
  2. Dodaj kod który spowoduje, że robot będzie ruszał się w stronę punktu gdzie klikniesz.