Table of contents
Introduction
Once upon a time a young programmer started discovering the great world of Ogre. He went through the first tutorials and was happy that everything worked without lots of code as there was the ExampleApplication.h that made the Ogre beginner's life rather easy. But, suddenly he read somewhere that the way it is done in this example application, isn't the way it is meant to be done (at least not in more advanced applications). So he started searching on the wiki and in the forums and piece-by-piece, he put together his own BasicOgreFramework.
This magic piece of code was capable of starting Ogre, loading the basic resources, reacting to standard keyboard and mouse input by moving the camera, making screen shots as well as showing the standard Ogre overlays with the logo and the FPS counter. From this day on, he always used this Framework when he needed a simple base for a test case or whatever.
As many people have the same problem (as he recognized by following most of the forum threads), he finally decided to put his solution in this great wiki, as his solution was much more complete than the other versions already available. However, he decided not to explain the code that much as it is self-explanatory for everyone that read the first Ogre tutorials.
An example that runs out-of-the-box (without any compiling) can be downloaded from my BasicOgreFramework BitBucket Hg repository. Of course it comes with the source, the needed media as well as with a Microsoft VisualStudio solution file.
Architecture
Well, the architecture is rather simple: There is one class, called OgreFramework, that does most of the magic. Just add it to your project. Then you need a second class that is the real application. It calls several functions from the OgreFramework class and also implements the main loop. If you want, you can also make it handle additional input to extend the input handling already implemented.
Note: Don't be scared by the code that looks probably quite a lot to a beginner. However, it is quite easy to understand.
OgreFramework.hpp
That is the header for our OgreFramework class. It contains all the Ogre related variables:
- Ogre Root
- Camera
- RenderWindow
- Viewport
- SceneManager
- Log
- Timer
- Input stuff
- ...
It also has the input handler function (e.g. keyPressed() or mouseMoved() ), that are used to capture some keys for basic camera movement. Apart from that, there are some other members such as a counter for the number of screenshots done while running the application or a variable indicating whether the application is to be shut down.
//||||||||||||||||||||||||||||||||||||||||||||||| #ifndef OGRE_FRAMEWORK_HPP #define OGRE_FRAMEWORK_HPP //||||||||||||||||||||||||||||||||||||||||||||||| #include <OgreCamera.h> #include <OgreEntity.h> #include <OgreLogManager.h> #include <OgreOverlay.h> #include <OgreOverlayElement.h> #include <OgreOverlayManager.h> #include <OgreRoot.h> #include <OgreViewport.h> #include <OgreSceneManager.h> #include <OgreRenderWindow.h> #include <OgreConfigFile.h> #include <OgreFrameListener.h> #include <OISEvents.h> #include <OISInputManager.h> #include <OISKeyboard.h> #include <OISMouse.h> #include <SdkTrays.h> //||||||||||||||||||||||||||||||||||||||||||||||| class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MouseListener, OgreBites::SdkTrayListener { public: OgreFramework(); ~OgreFramework(); bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MouseListener *pMouseListener = 0); void updateOgre(double timeSinceLastFrame); void moveCamera(); void getInput(); bool isOgreToBeShutDown()const{return m_bShutDownOgre;} bool keyPressed(const OIS::KeyEvent &keyEventRef); bool keyReleased(const OIS::KeyEvent &keyEventRef); bool mouseMoved(const OIS::MouseEvent &evt); bool mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id); bool mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id); Ogre::Root* m_pRoot; Ogre::SceneManager* m_pSceneMgr; Ogre::RenderWindow* m_pRenderWnd; Ogre::Camera* m_pCamera; Ogre::Viewport* m_pViewport; Ogre::Log* m_pLog; Ogre::Timer* m_pTimer; OIS::InputManager* m_pInputMgr; OIS::Keyboard* m_pKeyboard; OIS::Mouse* m_pMouse; private: OgreFramework(const OgreFramework&); OgreFramework& operator= (const OgreFramework&); OgreBites::SdkTrayManager* m_pTrayMgr; Ogre::FrameEvent m_FrameEvent; int m_iNumScreenShots; bool m_bShutDownOgre; Ogre::Vector3 m_TranslateVector; Ogre::Real m_MoveSpeed; Ogre::Degree m_RotateSpeed; float m_MoveScale; Ogre::Degree m_RotScale; }; //||||||||||||||||||||||||||||||||||||||||||||||| #endif //|||||||||||||||||||||||||||||||||||||||||||||||
OgreFramework.cpp
The following code parts form together the OgreFramework.cpp. I'll give some information on the functions:
- First line: Needed for the [[Singleton]]
- OgreFramework(): It just sets some default values for the variables
#include "OgreFramework.hpp" #include <OgreTextureManager.h> using namespace Ogre; template<> OgreFramework* Ogre::Singleton<OgreFramework>::msSingleton = 0; OgreFramework::OgreFramework() { m_MoveSpeed = 0.1f; m_RotateSpeed = 0.3f; m_bShutDownOgre = false; m_iNumScreenShots = 0; m_pRoot = 0; m_pSceneMgr = 0; m_pRenderWnd = 0; m_pCamera = 0; m_pViewport = 0; m_pLog = 0; m_pTimer = 0; m_pInputMgr = 0; m_pKeyboard = 0; m_pMouse = 0; m_pTrayMgr = 0; m_FrameEvent = Ogre::FrameEvent(); }
- initOgre():
- instantiates the log manager class
- creates a new Ogre root
- creates the scene manager and set some ambient light
- creates the camera and sets its position and clip planes
- creates the viewport and sets the background color
- creates the input devices
- loads the resources
- creates the timer
- creates the debug overlay
- sets the render window active
bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MouseListener *pMouseListener) { Ogre::LogManager* logMgr = new Ogre::LogManager(); m_pLog = Ogre::LogManager::getSingleton().createLog("OgreLogfile.log", true, true, false); m_pLog->setDebugOutputEnabled(true); m_pRoot = new Ogre::Root(); if(!m_pRoot->showConfigDialog()) return false; m_pRenderWnd = m_pRoot->initialise(true, wndTitle); m_pSceneMgr = m_pRoot->createSceneManager(ST_GENERIC, "SceneManager"); m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.7f, 0.7f, 0.7f)); m_pCamera = m_pSceneMgr->createCamera("Camera"); m_pCamera->setPosition(Vector3(0, 60, 60)); m_pCamera->lookAt(Vector3(0, 0, 0)); m_pCamera->setNearClipDistance(1); m_pViewport = m_pRenderWnd->addViewport(m_pCamera); m_pViewport->setBackgroundColour(ColourValue(0.8f, 0.7f, 0.6f, 1.0f)); m_pCamera->setAspectRatio(Real(m_pViewport->getActualWidth()) / Real(m_pViewport->getActualHeight())); m_pViewport->setCamera(m_pCamera); size_t hWnd = 0; OIS::ParamList paramList; m_pRenderWnd->getCustomAttribute("WINDOW", &hWnd); paramList.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(hWnd))); m_pInputMgr = OIS::InputManager::createInputSystem(paramList); m_pKeyboard = static_cast<OIS::Keyboard*>(m_pInputMgr->createInputObject(OIS::OISKeyboard, true)); m_pMouse = static_cast<OIS::Mouse*>(m_pInputMgr->createInputObject(OIS::OISMouse, true)); m_pMouse->getMouseState().height = m_pRenderWnd->getHeight(); m_pMouse->getMouseState().width = m_pRenderWnd->getWidth(); if(pKeyListener == 0) m_pKeyboard->setEventCallback(this); else m_pKeyboard->setEventCallback(pKeyListener); if(pMouseListener == 0) m_pMouse->setEventCallback(this); else m_pMouse->setEventCallback(pMouseListener); Ogre::String secName, typeName, archName; Ogre::ConfigFile cf; cf.load("resources.cfg"); Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); while (seci.hasMoreElements()) { secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iterator i; for (i = settings->begin(); i != settings->end(); ++i) { typeName = i->first; archName = i->second; Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName); } } Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5); Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); m_pTimer = new Ogre::Timer(); m_pTimer->reset(); m_pTrayMgr = new OgreBites::SdkTrayManager("TrayMgr", m_pRenderWnd, m_pMouse, this); m_pTrayMgr->showFrameStats(OgreBites::TL_BOTTOMLEFT); m_pTrayMgr->showLogo(OgreBites::TL_BOTTOMRIGHT); m_pTrayMgr->hideCursor(); m_pRenderWnd->setActive(true); return true; }
- ~OgreFramework(): Just clears up the whole thing
OgreFramework::~OgreFramework() { if(m_pInputMgr) OIS::InputManager::destroyInputSystem(m_pInputMgr); if(m_pTrayMgr) delete m_pTrayMgr; if(m_pRoot) delete m_pRoot; }
- keyPressed(): Implements the basic keyboard input handling
- leave the application when escape is pressed
- make a screenshot when print is hit
- change polygon mode when 'M' is hit
- toggle the frame stats and logo when 'O' is pressed
bool OgreFramework::keyPressed(const OIS::KeyEvent &keyEventRef) { if(m_pKeyboard->isKeyDown(OIS::KC_ESCAPE)) { m_bShutDownOgre = true; return true; } if(m_pKeyboard->isKeyDown(OIS::KC_SYSRQ)) { m_pRenderWnd->writeContentsToTimestampedFile("BOF_Screenshot_", ".png"); return true; } if(m_pKeyboard->isKeyDown(OIS::KC_M)) { static int mode = 0; if(mode == 2) { m_pCamera->setPolygonMode(PM_SOLID); mode = 0; } else if(mode == 0) { m_pCamera->setPolygonMode(PM_WIREFRAME); mode = 1; } else if(mode == 1) { m_pCamera->setPolygonMode(PM_POINTS); mode = 2; } } if(m_pKeyboard->isKeyDown(OIS::KC_O)) { if(m_pTrayMgr->isLogoVisible()) { m_pTrayMgr->hideLogo(); m_pTrayMgr->hideFrameStats(); } else { m_pTrayMgr->showLogo(OgreBites::TL_BOTTOMRIGHT); m_pTrayMgr->showFrameStats(OgreBites::TL_BOTTOMLEFT); } } return true; }
- keyReleased(): Doesn't do anything. It just has to be in there due to the class being inherited from the OIS::MouseListener and OIS::KeyboardListener.
bool OgreFramework::keyReleased(const OIS::KeyEvent &keyEventRef) { return true; }
- mouseMoved(): This function gets called when the mouse is move and then changes the camera's orientation.
bool OgreFramework::mouseMoved(const OIS::MouseEvent &evt) { m_pCamera->yaw(Degree(evt.state.X.rel * -0.1f)); m_pCamera->pitch(Degree(evt.state.Y.rel * -0.1f)); return true; }
- mousePressed(): Doesn't do anything. It just has to be in there due to the class being inherited from the OIS::MouseListener and OIS::KeyboardListener
- mouseReleased(): Same here as above.
bool OgreFramework::mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id) { return true; } bool OgreFramework::mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id) { return true; }
- updateOgre(): The function will be called by our own demo class in the main loop (so for every frame). It calculates the move and rotation scale in order to take into account the time passed since the last frame. It also calls getInput() and moveCamera() as well as inject an update event into the SdkTrayManager.
void OgreFramework::updateOgre(double timeSinceLastFrame) { m_MoveScale = m_MoveSpeed * (float)timeSinceLastFrame; m_RotScale = m_RotateSpeed * (float)timeSinceLastFrame; m_TranslateVector = Vector3::ZERO; getInput(); moveCamera(); m_FrameEvent.timeSinceLastFrame = timeSinceLastFrame; m_pTrayMgr->frameRenderingQueued(m_FrameEvent); }
- moveCamera(): This function changes the position of the camera accordingly to the vector m_TranslateVector that is changed by the keyboard input. If shift is pressed, it will move ten times faster than without.
void OgreFramework::moveCamera() { if(m_pKeyboard->isKeyDown(OIS::KC_LSHIFT)) m_pCamera->moveRelative(m_TranslateVector); else m_pCamera->moveRelative(m_TranslateVector / 10); }
- getInput(): This function changes the camera translation vector m_TranslateVector accordingly to the keyboard input.
void OgreFramework::getInput() { if(m_pKeyboard->isKeyDown(OIS::KC_A)) m_TranslateVector.x = -m_MoveScale; if(m_pKeyboard->isKeyDown(OIS::KC_D)) m_TranslateVector.x = m_MoveScale; if(m_pKeyboard->isKeyDown(OIS::KC_W)) m_TranslateVector.z = -m_MoveScale; if(m_pKeyboard->isKeyDown(OIS::KC_S)) m_TranslateVector.z = m_MoveScale; }
...page... Wiki page pagination has not been enabled.
Creating your demo class
All that is missing now is your own demo class that only needs some content. It might look like this:
- some functions to start, setup and run the Demo
- a pointer to an OgreFramework instance
- an entity and a scene node
- a boolean variable to show whether the demo should be shut down
Note: By inheriting from OIS::KeyListener and passing our demo class as a parameter to the initOgre() method, we can extend the input handling from the OgreFramework class by reacting to the inputs events in the keyPressed() and keyReleased() functions. The keyPressed() and keyReleased() function of OgreFramework are not called by OIS in this case, so we must call them from keyPressed() and keyReleased() in the DemoApp instead.
//||||||||||||||||||||||||||||||||||||||||||||||| #ifndef OGRE_DEMO_HPP #define OGRE_DEMO_HPP //||||||||||||||||||||||||||||||||||||||||||||||| #include "OgreFramework.hpp" //||||||||||||||||||||||||||||||||||||||||||||||| class DemoApp : public OIS::KeyListener { public: DemoApp(); ~DemoApp(); void startDemo(); bool keyPressed(const OIS::KeyEvent &keyEventRef); bool keyReleased(const OIS::KeyEvent &keyEventRef); private: void setupDemoScene(); void runDemo(); Ogre::SceneNode* m_pOgreHeadNode; Ogre::Entity* m_pOgreHeadEntity; bool m_bShutdown; }; //||||||||||||||||||||||||||||||||||||||||||||||| #endif //|||||||||||||||||||||||||||||||||||||||||||||||
Implementing your demo class
This is what your demo class implementation could look like:
- DempApp(): Constructor
- ~DemoApp(): Destructor
- startDemo():
- creates a new OgreFramework
- set the shutdown indicator to false (why would we want to shut it down right now?)
- calls setupDemo() and runDemo()
- setupDemoScene(): puts some content in our scene
- adds a skyBox
- creates a light
- adds a simple cube mesh
- runDemo(): the main loop of our application
- leaves the application if m_Shutdown is false or if the OgreFramework wants us to shut down for some reason
- calculates the time passed since the last frame
- orders the input devices for the keyboard and the mouse to look for new input events
- calls the updateOgre() function
- renders one frame
- if the render window is not active (does not have the focus), the application sleeps for a second
- keyPressed(): you can define here own input handling stuff, as e.g. move the cube in our scene
- keyReleased(): same as above
DemoApp::DemoApp() { m_pOgreHeadNode = 0; m_pOgreHeadEntity = 0; } //||||||||||||||||||||||||||||||||||||||||||||||| DemoApp::~DemoApp() { delete OgreFramework::getSingletonPtr(); } //||||||||||||||||||||||||||||||||||||||||||||||| void DemoApp::startDemo() { new OgreFramework(); if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", this, 0)) return; m_bShutdown = false; OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!"); setupDemoScene(); runDemo(); } //||||||||||||||||||||||||||||||||||||||||||||||| void DemoApp::setupDemoScene() { OgreFramework::getSingletonPtr()->m_pSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox"); OgreFramework::getSingletonPtr()->m_pSceneMgr->createLight("Light")->setPosition(75, 75, 75); m_pOgreHeadEntity = OgreFramework::getSingletonPtr()->m_pSceneMgr->createEntity("OgreHeadEntity", "ogrehead.mesh"); m_pOgreHeadNode = OgreFramework::getSingletonPtr()->m_pSceneMgr->getRootSceneNode()->createChildSceneNode("OgreHeadNode"); m_pOgreHeadNode->attachObject(m_pOgreHeadEntity); } //||||||||||||||||||||||||||||||||||||||||||||||| void DemoApp::runDemo() { OgreFramework::getSingletonPtr()->m_pLog->logMessage("Start main loop..."); double timeSinceLastFrame = 0; double startTime = 0; OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics(); while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown()) { if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isClosed())m_bShutdown = true; Ogre::WindowEventUtilities::messagePump(); if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive()) { startTime = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU(); OgreFramework::getSingletonPtr()->m_pKeyboard->capture(); OgreFramework::getSingletonPtr()->m_pMouse->capture(); OgreFramework::getSingletonPtr()->updateOgre(timeSinceLastFrame); OgreFramework::getSingletonPtr()->m_pRoot->renderOneFrame(); timeSinceLastFrame = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU() - startTime; } else { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 Sleep(1000); #else sleep(1); #endif } } OgreFramework::getSingletonPtr()->m_pLog->logMessage("Main loop quit"); OgreFramework::getSingletonPtr()->m_pLog->logMessage("Shutdown OGRE..."); } //||||||||||||||||||||||||||||||||||||||||||||||| bool DemoApp::keyPressed(const OIS::KeyEvent &keyEventRef) { OgreFramework::getSingletonPtr()->keyPressed(keyEventRef); if(OgreFramework::getSingletonPtr()->m_pKeyboard->isKeyDown(OIS::KC_F)) { //do something } return true; } //||||||||||||||||||||||||||||||||||||||||||||||| bool DemoApp::keyReleased(const OIS::KeyEvent &keyEventRef) { OgreFramework::getSingletonPtr()->keyReleased(keyEventRef); return true; } //|||||||||||||||||||||||||||||||||||||||||||||||
The very last thing: main.cpp
- first of all: you don't have to understand this!
- the only important parts are the two lines where an instance of our DemoApp is created and the startDemo() function is called
//||||||||||||||||||||||||||||||||||||||||||||||| #include "DemoApp.hpp" //||||||||||||||||||||||||||||||||||||||||||||||| #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 { try { DemoApp demo; demo.startDemo(); } catch(std::exception& e) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBoxA(NULL, e.what(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s\n", e.what()); #endif } return 0; } //|||||||||||||||||||||||||||||||||||||||||||||||
Notes
1. If you want to, you can declare all the Ogre members in the OgreFramework class (Root, Camera, RenderWindow, SceneManager, ...) private and implement getter methods for them. This is mostly a matter of code style and one's preferences. You could also totally remove most of them as they are also singletons (e.g. Ogre::Root, Ogre::Log) and retrieve them via the singleton functions. It's up to you!
2. If Ogre::WindowEventUtilities::messagePump() doesn't work for you, try this instead:
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) m_bShutdown = true; else { TranslateMessage(&msg); DispatchMessage(&msg); } } } #endif
3. In this forum post you will find a list of all needed changes in order to get this running with XCode on Mac.
Conclusion
So that's it! It's not that much magic as it looks like. In fact it's no magic at all... just one possible way to have a simple and clean framework to use as a starting point.
Of course, you may alter and modify it to your needs. The young Ogre programmer from the top of the page wishes you good luck...
Alias: Example Framework Demystified
Alias: Basic_Ogre_Framework