This tutorial will not use the Ogre Wiki Tutorial Framework. Instead, we are going to show you how to get an application up and running without the help of the additional class. After this tutorial, you should have a basic understanding of most of the code that was hidden in the BaseApplication class. The full source for this tutorial is here. |
Any problems you encounter during working with this tutorial should be posted in the Help Forum.
Prerequisites
This tutorial assumes that you already know how to set up an Ogre project and compile it successfully. If you need help with this, then read Setting Up An Application. This tutorial is also part of the Basic Tutorials series and knowledge from the previous tutorials will be assumed.
If you have been using a project created for the tutorial framework, then make sure to remove the BaseApplication files. We will not be using them for this tutorial. For instance, if you're building with CMake, then you will need to remove the lines that include those files in your CMakeLists.txt. If you're using an IDE, then you should be able to remove the files from the project just by deleting them.
Table of contents
- Prerequisites
- Getting Started
- The Ogre Startup Process
- Creating the Root Object
- Setting Up Resources
- Configuring the RenderSystem
- Creating a RenderWindow
- Initialising Resources
- Creating a SceneManager
- Creating the Camera
- Adding a Viewport
- Setting Up the Scene
- An Initial Rendering Loop
- The Object-Oriented Input System (OIS)
- Initialising OIS
- Shutting down OIS
- Setting Up the Framelistener
- Registering Our FrameListener
- Replacing Our Custom Render Loop
- Notes About Mac OS X
- Conclusion
- Full Source
- Next
Getting Started
We will start from scratch with this tutorial. This code should compile successfully and exit immediately.
class TutorialApplication { public: TutorialApplication(); virtual ~TutorialApplication(); bool go(); };
#include "TutorialApplication.h" #include <OgreException.h> TutorialApplication::TutorialApplication() { } TutorialApplication::~TutorialApplication() { } bool TutorialApplication::go() { return true; } // MAIN FUNCTION OMITTED
The Ogre Startup Process
We are going to give a brief overview of the Ogre startup process. In the previous tutorials, we have used the Ogre Wiki Tutorial Framework to take care of a large part of this process. In this tutorial, we will build an Ogre application from scratch. This will give you greater control over how your application runs. Remember that the tutorial framework is for teaching purposes only. Your own applications should not be organized like the framework.
The basic Ogre life cycle looks like this:
- Create the Ogre::Root object
- Define the resources that Ogre will use
- Choose and set up the RenderSystem (DirectX, OpenGL, etc)
- Create the RenderWindow
- Set up any third party libraries and plugins.
- Initialise resources
- Register listener classes
- Build a scene
- Start the render loop
This tutorial will cover each of these steps in some depth. This order is not absolutely necessary. The first four steps should be done in this order, but you can experiment with the rest if it suits your application's design better. Just make sure you understand the basics of the process before you begin making any drastic changes. For instance, you don't want to try and use a resource before it is initialised.
Creating the Root Object
In some ways, the Ogre::Root object will be our new BaseApplication framework. Everything that is done by the root object can be done manually. It allows us to initialize the core Ogre system with very little effort.
The first thing we'll do is add an Ogre::Root member to our class along with two Strings that will be used during setup. Add the following to the private section of the TutorialApplication header:
Ogre::Root* mRoot; Ogre::String mResourcesCfg; Ogre::String mPluginsCfg;
We also need to include the header that provides Ogre::Root.
#include <OgreRoot.h>
After that add the initialization and cleanup to the implementation file.
TutorialApplication::TutorialApplication() : mRoot(0), mResourcesCfg(Ogre::StringUtil::BLANK), mPluginsCfg(Ogre::StringUtil::BLANK) { } TutorialApplication::~TutorialApplication() { delete mRoot; }
Now we will construct an instance of the root object, but first we need to define the strings that identify the resource and plugin configuration files. We use the preprocessor flag _DEBUG to make sure we are using the appropriate files for each build. The root object takes the plugin filename as its first parameter. Add the following to go:
#ifdef _DEBUG mResourcesCfg = "resources_d.cfg"; mPluginsCfg = "plugins_d.cfg"; #else mResourcesCfg = "resources.cfg"; mPluginsCfg = "plugins.cfg"; #endif mRoot = new Ogre::Root(mPluginsCfg);
The constructor for Ogre::Root takes 3 parameters:
Parameter: | Default Value: |
Ogre::String pluginFileName | "plugins.cfg" |
Ogre::String configFileName | "ogre.cfg" |
Ogre::String logFileName | "Ogre.log" |
We have overriden the default value for the plugin config file so that it will switch to the debug version when built in debug mode using a precompiled Ogre SDK.
Compile and run your application. It still looks like it does nothing, but if you look in your 'bin' directory, you'll find a new 'Ogre.log' file that should have recorded a large amount of information about Ogre starting up and loading plugins. We haven't gone anywhere yet, but the motor is humming...
Setting Up Resources
We are now going to set up our applications resources. You should open up your project's 'resources.cfg' file. If you're using the SDK, then this will be in your 'bin/release' or 'bin/debug' directory. If you built Ogre from source, then most likely this file will be in the 'bin' directory right next to your executable.
The 'resources.cfg' file lets Ogre know where it should look for potential resources. Ogre does not initialize all of these resources during this step. We will do that later. Looking at the file, we can see it is essentially a list of directories. Here is the file that is being used to write these tutorials:
# Resources required by the sample browser and most samples. [Essential] Zip=../media/packs/SdkTrays.zip # Resource locations to be added to the default path [General] FileSystem=../media FileSystem=../media/materials/scripts FileSystem=../media/materials/textures FileSystem=../media/models
One important thing to note is that Ogre will not automatically search sub-directories. From the example, you can see that including '../media' did not prevent us from having to include '../media/materials/scripts'.
For the next step, we need to include Ogre::ConfigFile.
#include <OgreConfigFile.h>
We already set up the resources string when we created the root object. Now we will create a Ogre::ConfigFile object and use it to parse our cfg file. Add the following to go after we create the root object:
Ogre::ConfigFile cf; cf.load(mResourcesCfg);
You are free to use your own config file formats and parser. To do this, simply replace Ogre's ConfigFile parser with your own.
If you look back to your 'resources.cfg' file, you will see that they are divided into sections like [Essential]. Now that we have the information loaded from the cfg file, we have to add these sections and their list of locations to the ResourceGroupManager.
The first thing we will do is define two strings we will use to gather information from the parsed config file.
Ogre::String name, locType;
The name parameter is the path to the resources (i.e. "../media"). The locType parameter defines what kind of location this is (i.e. Filesystem, Zip, etc.)
To get started, we will ask for a SectionIterator. This will allow us to iterate through all of the sections discovered by the parser.
Ogre::ConfigFile::SectionIterator secIt = cf.getSectionIterator();
We will now iterate through all of the results.
while (secIt.hasMoreElements()) {
Now, inside of this loop we are going to ask for another iterator that will let us iterate through the items in each section.
^ Ogre::ConfigFile::SettingsMultiMap* settings = secIt.getNext(); Ogre::ConfigFile::SettingsMultiMap::iterator it;
Each section is returned as a SettingsMultiMap. This is Ogre's implementation of a multimap which contains pairs of settings. The "multi" part means it can contain multiple elements all with the same key. This is what we want here, because the key is the location type, and many of our locations will have the same type. We also set up an iterator to read through these pairs.
Now we will start another loop to scan through each item with this iterator.
^ for (it = settings->begin(); it != settings->end(); ++it) {
We will now unpack each pair. The first object will be a string representing the location type of this resource, and the second argument will be the path.
^ locType = it->first; name = it->second;
Finally, we will use these two strings to add this location to our ResourceGroupManager using the addResourceLocation method.
^ Ogre::ResourceGroupManager::getSingleton().addResourceLocation(name, locType); } }
Keep in mind that this only lets Ogre know where to look for the resources. We still have to initialize the particular resources we actually need later in the tutorial.
Compile and run your application. It still isn't doing much, but now your 'Ogre.log' should contain information about adding the resource locations.
Configuring the RenderSystem
The next thing we will do is choose which RenderSystem to use. DirectX or OpenGL will be the most popular options, but they are not the only choices and you can even add additional systems. The demo applications that come with Ogre all use the config dialog to allow the user to make choices about their render settings. We will use this same approach. In your own applications, you might want to incorporate this into a settings menu that can also handle other things like keybindings.
The first thing we will do is create a RenderWindow instance. Add the following to go right after our resource code:
Ogre::RenderWindow* mWindow;
The BaseApplication class from the tutorial framework always shows the config dialog. We can do a little better than this. We can make it so that the dialog will only be shown if there is no 'ogre.cfg' file present. This way we can set the options once and speed up our startup time. In your own applications, you would want to add a little to this method, because you want your user to be able to reset these settings if they need to without manually deleting 'ogre.cfg'.
if(!(mRoot->restoreConfig() || mRoot->showConfigDialog())) return false;
The first call in the if statement tries to restore our configuration from an existing cfg file. If that fails, then we show the config dialog. The || statement will do what is called "short-circuit evaluation". The showConfigDialog call will only be executed if the restoreConfig call fails. The if statement will only return false if both of these calls return false.
You could also choose to throw an exception instead of just returning false and exiting the application. If you do throw an exception, then you should try to delete any 'ogre.cfg' file that exists in the catch block, because it is possible the settings the user chose are causing the problem.
It is also possible to manually set up the RenderSystem. This would be helpful if you were designing your own settings menu. Here is a simple example of setting up a Direct3D9 RenderSystem:
// Do not add this to your project RenderSystem* rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem"); mRoot->setRenderSystem(rs); rs->setConfigOption("Full Screen", "No"); rs->setConfigOption("Video Mode", "800 x 600 @ 32-bit colour");
The method Root::getAvailableRenderers will let your know what is currently available on your system. Once you have a pointer to the RenderSystem, you can use RenderSystem::getConfigOptions to see what options it provides.
Creating a RenderWindow
Now we will initialise a window for our application to be rendered in. This is another thing that we can ask the Root object to take care of. First, add the RenderWindow include to your project.
#include "OgreRenderWindow.h"
Then add the following to go right after our configuration code:
mWindow = mRoot->initialise(true, "TutorialApplication Render Window");
This call initialises the RenderSystem we chose in the previous step. The first parameter determines whether or not Ogre will create a RenderWindow for us.
We could also create a window using the win32 API, wxWidgets, or any other windowing system. Here is a quick example of how to do this under Windows:
// Do not add this to your project mRoot->initialise(false); HWND hWnd = 0; // Retrieve the HWND for the window we want to render in. // This step depends entirely on the windowing system you are using. NameValuePairList misc; misc["externalWindowHandle"] = StringConverter::toString((int)hWnd); RenderWindow* win = mRoot->createRenderWindow("Main RenderWindow", 800, 600, false, &misc);
You can see that we still need to call Root::initialise, but the first parameter is set to false. We then call Root::createRenderWindow to directly assign our render window using the information we've gathered.
Initialising Resources
In a very large application, we may have thousands of resources. This can include meshes, textures, and scripts. However, at any given time, we will probably only be using a small subset of those resources. To help manage memory use, we will try to only initialise resources when we need them. For further reading on this topic, you can read Resources and ResourceManagers.
Before we initialise our resources, let's set the default number of mipmaps to be used. These are the lower resolution versions of textures used to produce different "level of detail" (LOD) for a texture, depending on how far it is away from the Camera. Continue adding to go.
Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);
Now we'll call a single method from the ResourceGroupManager singleton that will initialise all of the resources found by Ogre.
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
That's it.
Creating a SceneManager
As we described in the early tutorials, we need a SceneManager to...manage the scene. First, add this include to your class:
#include <OgreSceneManager.h>
Then we need a SceneManager data member.
Ogre::SceneManager* mSceneMgr;
Now we ask the Root object to create the manager for us in go.
mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);
The Ogre namespace defines a series of SceneManager types that all start with "ST_". We've chosen the default, generic manager.
Creating the Camera
Now we will add a Camera to our project. Include the Camera class.
#include <OgreCamera.h>
Add a Camera member.
Ogre::Camera* mCamera;
And set it up in the go method.
mCamera = mSceneMgr->createCamera("MainCam"); mCamera->setPosition(0, 0, 80); mCamera->lookAt(0, 0, -300)); mCamera->setNearClipDistance(5);
Other than needing to ask the SceneManager to create the Camera, this should all be familiar from past tutorials.
Adding a Viewport
The last thing we need before setting up our scene is a viewport. Include the class.
#include "OgreViewport.h"
Now we will declare the viewport and create it in the go method.
Ogre::Viewport* vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); mCamera->setAspectRatio( Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));
This should also be familiar from past tutorials. We need a viewport to actually see a camera's viewpoint rendered on the screen.
Setting Up the Scene
We're ready to add something to our scene! None of this should be new to you by now. Make sure to include the Entity class.
#include <OgreEntity.h>
Now we'll add an Ogre head, some ambient light, and a point light to our scene.
Ogre::Entity* ogreEntity = mSceneMgr->createEntity("ogrehead.mesh"); Ogre::SceneNode* ogreNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); ogreNode->attachObject(ogreEntity); mSceneMgr->setAmbientLight(Ogre::ColourValue(.5, .5, .5)); Ogre::Light* light = mSceneMgr->createLight("MainLight"); light->setPosition(20, 80, 50);
That does it for our basic scene.
An Initial Rendering Loop
We still have to get the rendering started somehow. The first thing we will try is calling Root::renderOneFrame in a while loop. We briefly looked at this function in Basic Tutorial 4.
bool Root::renderOneFrame(void) { if(!_fireFrameStarted()) return false; if(!_updateAllRenderTargets()) return false; return _fireFrameEnded(); }
Now we need to set up our render loop. First, include this header:
#include <OgreWindowEventUtilities.h>
Now we will add the following to the end of go:
while(true) { Ogre::WindowEventUtilities::messagePump(); if(mWindow->isClosed()) return false; if(!mRoot->renderOneFrame()) return false; }
This will loop infinitely until the window is closed or the renderOneFrame method returns false. The messagePump call basically updates the RenderWindow.
Compile and run your application. You should see the familiar Ogre head rendered in your window. You have to exit by pressing the close button on the window. We will add back input in the next section.
The Object-Oriented Input System (OIS)
One good option for adding input to Ogre is the Object-Oriented Input System. We will now provide an introduction to initialising OIS to work with Ogre. For more information, reference the OIS documentation.
Let's include the OIS headers in TutorialApplication.h.
#include <OISEvents.h> #include <OISInputManager.h> #include <OISKeyboard.h> #include <OISMouse.h>
Now add the following data members to your class.
OIS::InputManager* mInputManager; OIS::Mouse* mMouse; OIS::Keyboard* mKeyboard;
Also, remember to include the OIS library in your project. If you're using the CMake script from the tutorial framework, then it is already attempting to find OIS and including everything. If you're working with an IDE, then you'll need to add these settings to your project:
Include Directory | $(OGRE_HOME)/include/OIS |
Input Library | OIS_d.lib/OIS.lib |
Compile and run your application. Make sure adding OIS hasn't caused any problems.
Initialising OIS
OIS uses a general InputManager which is a touch difficult to set up, but easy to use once you have created it properly. OIS does not integrate into Ogre; it's a standalone library, which means that you will need to provide it with some information at the beginning for it to work properly. In practice it really only needs the window handle in which Ogre is rendering. Thankfully, since we have used the automatically created window, Ogre makes this easy for us.
Go to the BasicTutorial6::go function and add this code to it, before our render loop:
Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***"); OIS::ParamList pl; size_t windowHnd = 0; std::ostringstream windowHndStr; mWindow->getCustomAttribute("WINDOW", &windowHnd); windowHndStr << windowHnd; pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); mInputManager = OIS::InputManager::createInputSystem( pl );
This sets up the InputManager for use, but to actually use OIS to get input for the Keyboard, Mouse, or Joystick of your choice, you'll need to create those objects:
mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, false )); mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, false ));
We are passing 'false' to the createInputObject function because we want mouse and keyboard unbuffered.
Note: If you wish to use buffered input (meaning you get event callbacks to mouseMoved, mousePressed, keyReleased, and so on), then you must set the second parameter to createInputObject to be true.
Shutting down OIS
Note: This is very important when building on linux.
OIS is a bit tricky to shut down properly.
The most failsafe way to do this is by using a WindowEventListener.
First, put this line in your list of includes in BasicTutorial6.h:
#include <OgreWindowEventUtilities.h>
Then we need to change our class declaration to derive from WindowEventListener:
class BasicTutorial6 : public Ogre::WindowEventListener
We want to override WindowEventListener::windowResized and WindowEventListener::windowClosed, so put this in the protected section of the BasicTutorial6 class declaration:
// Ogre::WindowEventListener virtual void windowResized(Ogre::RenderWindow* rw); virtual void windowClosed(Ogre::RenderWindow* rw);
Now, open BasicTutorial6.cpp and add the following to it:
//Adjust mouse clipping area void BasicTutorial6::windowResized(Ogre::RenderWindow* rw) { unsigned int width, height, depth; int left, top; rw->getMetrics(width, height, depth, left, top); const OIS::MouseState &ms = mMouse->getMouseState(); ms.width = width; ms.height = height; } //Unattach OIS before window shutdown (very important under Linux) void BasicTutorial6::windowClosed(Ogre::RenderWindow* rw) { //Only close for window that created OIS (the main window in these demos) if(rw == mWindow) { if(mInputManager) { mInputManager->destroyInputObject( mMouse ); mInputManager->destroyInputObject( mKeyboard ); OIS::InputManager::destroyInputSystem(mInputManager); mInputManager = 0; } } }
windowResized is called whenever the window is resized, and makes sure that the OIS mouse state is synchronised with the actual size of the window.
windowClosed destroys OIS when the window is closed.
To make our application act as a WindowEventListener, we need to register it as one.
So add these lines to BasicTutorial6::go:
//Set initial mouse clipping size windowResized(mWindow); //Register as a Window listener Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);
There's one more thing we need to do.
Find the BasicTutorial6 destructor and make it look like this:
//Remove ourself as a Window listener Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this); windowClosed(mWindow); delete mRoot;
Setting Up the Framelistener
No matter if you are using buffered or unbuffered input, every frame you must call the capture method on all Keyboard, Mouse, and Joystick objects you use.
For unbuffered input, this is all you need to do.
Every frame you can call the various Keyboard and Mouse functions to query for the state of these objects.
So, we need to make our BasicTutorial6 class become a FrameListener. ๐
Change the class declaration to look like this:
class BasicTutorial6 : public Ogre::WindowEventListener, public Ogre::FrameListener
Then add this function declaration to the protected section of the class declaration:
// Ogre::FrameListener virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
And then, in BasicTutorial6.cpp, add this function definition:
bool BasicTutorial6::frameRenderingQueued(const Ogre::FrameEvent& evt) { if(mWindow->isClosed()) return false; //Need to capture/update each device mKeyboard->capture(); mMouse->capture(); if(mKeyboard->isKeyDown(OIS::KC_ESCAPE)) return false; return true; }
Registering Our FrameListener
Before we're ready to compile and run our application, we need to register our application as a FrameListener.
We don't need a custom render loop either. Find BasicTutorial6::go and remove the while loop. Then add this to it:
mRoot->addFrameListener(this);
This line adds the BasicTutorial6 instance to mRoot as a FrameListener, meaning that it will receive frame events.
If we don't register our class as a FrameListener with the Root object, the frameRenderingQueued() function will never be called.
Replacing Our Custom Render Loop
This line of code starts the rendering loop. We don't really need any special handling of the loop since we can perform our per-frame tasks in the frameRenderingQueued() function.
mRoot->startRendering();
Compile and run! ๐
You should see the familiar Ogre head in an Ogre RenderWindow, and you should be able to exit the application by pressing the Escape key.
Notes About Mac OS X
Cocoa Version
The new way to set up the current wiki tutorial framework for xcode in Mac OS X is much shorter and easy to understand.
Make sure your project is created as a cocoa application and changed the extension to .mm from .cpp and copy both resources and plugins cfg to your copy resource phase in your xcode target
Now change the following from
#ifdef _DEBUG mResourcesCfg = "resources_d.cfg"; mPluginsCfg = "plugins_d.cfg"; #else mResourcesCfg = "resources.cfg"; mPluginsCfg = "plugins.cfg"; #endif
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE std::string mResourcePath = [[[NSBundle mainBundle] resourcePath] cStringUsingEncoding:NSUTF8StringEncoding]; #endif #ifdef _DEBUG #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE mResourcesCfg = mResourcePath + "/resources_d.cfg"; mPluginsCfg = mResourcePath + "/plugins_d.cfg"; #else mResourcesCfg = "resources_d.cfg"; mPluginsCfg = "plugins_d.cfg"; #endif #else #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE mResourcesCfg = mResourcePath + "/resources.cfg"; mPluginsCfg = mResourcePath + "/plugins.cfg"; #else mResourcesCfg = "resources.cfg"; mPluginsCfg = "plugins.cfg"; #endif
Carbon Version
Since Mac OS X uses app bundles, a concept radically different from what is used on Windows and Linux, the code described above is going to crash on Mac OS X.
- Add the following function:
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include <CoreFoundation/CoreFoundation.h> // This function will locate the path to our application on OS X, // unlike windows you cannot rely on the current working directory // for locating your configuration files and resources. std::string macBundlePath() { char path[1024]; CFBundleRef mainBundle = CFBundleGetMainBundle(); assert(mainBundle); CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle); assert(mainBundleURL); CFStringRef cfStringRef = CFURLCopyFileSystemPath( mainBundleURL, kCFURLPOSIXPathStyle); assert(cfStringRef); CFStringGetCString(cfStringRef, path, 1024, kCFStringEncodingASCII); CFRelease(mainBundleURL); CFRelease(cfStringRef); return std::string(path); } #endif
- In createRoot(), change
mRoot = new Root();
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE mRoot = new Root(macBundlePath() + "/Contents/Resources/plugins.cfg"); #else mRoot = new Root(); #endif
- In defineResources(), change
cf.load("resources.cfg");
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE cf.load(macBundlePath() + "/Contents/Resources/resources.cfg"); #else cf.load("resources.cfg"); #endif
- Also in defineResources(), change
Ogre::ResourceGroupManager::getSingleton().addResourceLocation( archName, typeName, secName);
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE Ogre::ResourceGroupManager::getSingleton().addResourceLocation( String(macBundlePath() + "/" + archName), typeName, secName); #else Ogre::ResourceGroupManager::getSingleton().addResourceLocation( archName, typeName, secName); #endif
Conclusion
Now you should have a basic understanding of how to create your own simple Ogre application.
Full Source
The full source for this tutorial is here.
Next
Alias: Basic_Tutorial_6