Table of contents
Introduction
This tutorial is designed to start at the very basics of using Ogre. It will begin with setting up a project and by the end of it we will have a space ship that can be moved around in a 3D scene.
This tutorial was originally written by AgentGreen
Creating the Project Files
In these tutorials I will assume that the projects being created are located in their own folder in ogrenew\Samples. I will use APPNAME as a place holder for where you type in the name of the application. So in the example of where the project files should be stored it would be OgreNew\Samples\APPNAME.
NB: In a moments time we will embark on creating a small application called "Space", based on some of the examples supplied with Ogre. I suggest that if you plan on doing that tutorial that you use Space as the APPNAME.
Visual C++ 7 (also part of Visual Studio .NET 2003)
First, create a blank Win32 Project (in the New Project options box, it's 'Visual C++ Projects' / 'Win32' / 'Win32 Project'). After entering the project name, hit OK, and when the next window pops up, go to the 'Application Settings' tab, leave the 'Windows application' radiobutton highlighted and tick the 'Empty Project' checkbox, then hit Finish.
Now add a new file to the project's Source Files - APPNAME.cpp. A .cpp file must already be present for the IDE to display the 'C/C++' properties menu for the next step, and this file will soon contain our code. Now right click on the project name in the solution explorer, click on Properties, and change the following options in the Property Pages:
Debugging / Working Directory = "..\Common\Bin\Debug" <br /> C/C++ / General / Additional Include Directories = "..\Common\Include";"..\..\OgreMain\include" <br /> C/C++ / Code Generation / Runtime Library = Multithreaded Debug DLL <br /> C/C++ / Preprocessor / Preprocessor Definitions = _WINDOWS,_STLP_USE_DYNAMIC_LIB, <br /> OGRE_LIBRARY_IMPORTS,_DEBUG,WIN32,_STLP_DEBUG <br /> Linker / General / Output File = ..\Common\Bin\Debug\APPNAME.EXE <br /> Linker / General / Additional Library Directories = "..\..\OgreMain\Lib\Debug" <br /> Linker / Input / Additional Dependencies = OgreMain_d.lib
If you subsequently wish to compile your code in Release Mode: change the 'Configuration' listbox at the top of the Properties Pages to 'Release'; input the above settings again in the Properties Pages, except for: in C/C++'s 'Code Generation' section, where you have put 'Multithreaded Debug DLL', change 'Debug' to 'Release'; in C/C++'s 'Preprocessor' section, remove the preprocessor definitions '_DEBUG' and '_STLP_DEBUG'; in Linker's 'Input' section, change OgreMain_d.lib to OgreMain.lib.
Now copy Samples\Skyplane\Src\SkyPlane.CPP and Samples\Skyplane\Include\SkyPlane.H into the directory with your project. Right click on the project in the solution explorer and select "Add Existing Item". Highlight the two files you just copied to the directory, and add them to the project. Now rebuild your solution, and run it in debug mode. Everything should work.
Now remove those two files from your project and delete them from your project's directory.
To finish setting up the project, we need to create the main program loop. This is simpler than it sounds, because if we are following the Ogre Example Framework then we only need to create an instance of our Application object and call its go() method. In Visual C++ the code, in our APPNAME.cpp file, will look like this:
/* APPNAME.CPP */ #include "Ogre.h" #include "APPNAMEApplication.h" #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) { // Create application object APPNAMEApplication app; try { app.go(); } catch( Ogre::Exception& e ) { MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL ); } return 0; }
This doesn't compile, does it? No, because we have not written APPNAMEApplication.h yet. That will be discussed in the next section.
Visual C++ 6
This is almost identical to the instructions for Visual C++ 7.
The first thing you need to do is create a blank Win32 Project. Make sure that you create a C++ project and that you have ticked the "Empty Project" tick box.
Now select "Project" from the menu and then select "Settings". We'll change some properties from here (legend is T=Tab, C="Category"):
(T) Debug - (C) General - Working Directory = ..\Common\Bin\Debug <br /> (T) C/C++ - (C) Preprocessor - Preprocessor Definitions = _WINDOWS,_STLP_USE_DYNAMIC_LIB,OGRE_LIBRARY_IMPORTS,_DEBUG,WIN32,_STLP_DEBUG <br /> (T) C/C++ - (C) Preprocessor - Additional Include Directories = ..\Common\Include,..\..\OgreMain\include <br /> (T) Link - (C) General - Output Filename = ..\Common\Bin\Debug\APPNAME.EXE <br /> (T) Link - (C) Input - Additional Library Path = ..\..\OgreMain\Lib\Debug <br /> (T) Link - (C) General - Object/Library Modules = OgreMain_d.lib (add to whatever is there)
NB: Check that your application is set to use the 'Multithreaded Debug Dll' runtime library (under the code generation options in the C++ section).
In release mode you should change 'Debug' to 'Release', and remove the preprocessor defines '_DEBUG' and '_STLP_DEBUG', and chaneg OgreMain_d.lib to OgreMain.lib.
Set up the source files exactly as in the VC7 section.
Linux
To begin, make the Samples/Space directory for your project and copy these files into it:
- Samples/SkyPlane/src/SkyPlane.cpp
- Samples/SkyPlane/include/SkyPlane.h
- Samples/Common/include/ExampleApplication.h
- Samples/Common/include/ExampleFrameListener.h
Next we will set up a basic build system, for which we are going to use GNU automake and autoconf. If you want to learn more about these tools, I suggest reading the excellent tutorial at the autotools website. First we will write Samples/Space/configure.ac, which tells autoconf which programs and libraries we are going to use to compile our application:
AC_INIT([Ogre Space Tutorial], [0.0.1], [Mark Ivey zovirl@zovirl.com], [Space]) AM_CONFIG_HEADER(config.h) AM_INIT_AUTOMAKE([dist-bzip2]) AC_PROG_CC AC_PROG_CXX AC_PROG_LIBTOOL PKG_CHECK_MODULES(OGRE, OGRE >= 1.0.0,,AC_MSG_ERROR("OGRE not found!")) AM_CXXFLAGS="$AM_CXXFLAGS $OGRE_CFLAGS" AM_LDFLAGS="$AM_LDFLAGS $OGRE_LIBS" AC_SUBST(AM_CXXFLAGS, "$AM_CXXFLAGS") AC_SUBST(AM_LDFLAGS, "$AM_LDFLAGS") AC_SUBST(PKGDATADIR, "${datadir}/${PACKAGE}") AC_CONFIG_FILES([ Makefile ]) AC_OUTPUT
Most of that is boiler-plate. The two important parts are "PKG_CHECK_MODULES(OGRE...)" which tells autoconf we will be using OGRE (and it should go find the correct compiler flags for us) library and "AC_CONFIG_FILES(...)" which tells autoconf which files we want it to generate.
Next we will write Samples/Space/Makefile.am, which tells automake how to compile our program:
bin_PROGRAMS = Space noinst_HEADERs= ExampleApplication.h ExampleFrameListener.h SkyPlane.h Space_SOURCES= SkyPlane.cpp EXTRA_DIST = bootstrap configure
Ok, the last part of the build system is a short script to set everything up for us, Samples/Space/bootstrap:
#!/bin/sh set -x rm -f config.cache && libtoolize --force && aclocal && autoconf && autoheader && automake --foreign --add-missing
Make that executable with "chmod +x bootstrap"
You are ready to compile (from the Samples/Space/ directory):
./bootstrap ./configure make
Finally, you should be able to go into the data directory (Samples/Common/bin) and run ../../Space/Space.
Ok, the build system is done, now it is time to add the real source code. Remove SkyPlane.cpp and SkyPlane.h and edit Makefile.am to reflect the new files you are about to add:
bin_PROGRAMS = Space noinst_HEADERs= ExampleApplication.h ExampleFrameListener.h SpaceApplication.h Space_SOURCES= SpaceApplication.cpp EXTRA_DIST = bootstrap configure
Finally, add the code for the application's main loop in SpaceApplication.cpp:
#include "Ogre.h" #include "SpaceApplication.h" int main(int argc, char *argv[]) { // Create application object SpaceApplication app; try { app.go(); } catch( Ogre::Exception& e ) { std::cerr << "An exception has occured: " << e.getFullDescription().c_str() << std::endl; } return 0; }
Setting up the scene
A scene in outer space
When we derive our own application class from the ExampleApplication class we overrride the protected method createScene whose prototype is:
virtual void createScene(void) = 0; // pure virtual - this has to be overridden
As you can see this is a purely virtual function, so if we do not override it then we will get a nasty error. This method is responsible for setting up the scene within the application. This involves creating entities, lights, particle systems, etc.
It's time to get into the code, so if you have not done so already create an application following the previous steps and call it Space. To begin with we are going to create a scene from outer space, with planets visible in the distance based on the Skybox example.
Create a new header file called SpaceApplication.h. This is the file that we included in Space.cpp and is where we are going to define our own class SpaceApplication. The first thing we need to do is
#include "ExampleApplication.h"
This gives us access to the ExampleApplication class that our application class is derived from. Now to create our class definition
class SpaceApplication : public ExampleApplication { protected: void createScene(void); };
Now to override that createScene method with our own. There is only one thing we are interested in doing right now, create a skybox that shows us a scene in outer space. We do this by using the Scene Manager (mSceneMgr).
void SpaceApplication::createScene(void) { // Create a skybox mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox"); }
Compile and run the program. After the Ogre configuration box is gone you should be thrust into our space scene. The mouse will let you look left, right, up, and down. Time for a quick look at "Examples/SpaceSkyBox".
Resources
When we set the sky box we told the Scene Manager to use the material "Examples/SpaceSkyBox". But where does this come from? When an ExampleApplication class is created it tells the Resource Manager to add the following places to search for resources
../../../Media/dragon.zip ../../../Media/knot.zip ../../../Media/skybox.zip ../../../Media/
The Resource Manager searches those paths for a files ending in ".material" for definitions of materials. This material, Examples/SpaceSkyBox, happens to be defined in Example.material
// Skybox material Examples/SpaceSkyBox { technique { pass { // No dynamic lighting, fully lit lighting off // Depth writing off (always display stuff in front of it) depth_write off // Texture layer 0 texture_unit { // 6-sided texture, stevecube_fr.jpg, stevecube_up.jpg etc cubic_texture stevecube.jpg separateUV // clamp to avoid fuzziness at edges due to filtering tex_address_mode clamp } } } }
Creating our first Scene Node and Entity
A ship in space
What use is outer space if there are no space ships in it? Its time to make something that we can fly around in. We are going to do this by creating instances of two object types. Entities and Scene Nodes.
Member variables
In SpaceApplication.h, in the class interface definition, create some private member variables to store pointers to the Scene Node and Entity for use later.
Entity* mShip; SceneNode* mShipNode;
Entities
An Entity in Ogre is a moveable object based on a mesh. These meshes are loaded through the Resource Manager and its position is determined by a Scene Node that it is attached to. To create our ship, add a line into the SpaceApplication::createScene method in SpaceApplication.h (after setting the sky box)
mShip = mSceneMgr->createEntity("razor", "razor.mesh");
Just like the skybox, we supply a resource name to the Scene Manager and it will locate it. In this case it is the name of a mesh file (razor.mesh) which is found in the Media directory. Another important thing to note about we just did, we gave the entity a name. This name (razor) has to be unique within the application or the Scene Manager will give you an error.
The Scene Node
As mentioned above, Entities attach to Scene Nodes which dictate their position in the scene. Scene Nodes can be attached to other tree nodes to create a type of positional hierachy, but for the purposes of this tutorial just one will do.
To create this Scene Node we ask the root node of the scene to create it for us. The root node is maintained by Ogre and every entity that is part of the scene is directly or indirectly attached to it. When we ask the root node to create the node for us we are saying that the root node is the node's parent. After creating it we set its position orientation relative to the scene node (effectively the absolute position in the scene as far as this tutorial goes, but not necessarily true in other circumstances).
mShipNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
To have the ship stay still you must add:
mShipNode->setPosition(0,0,0);
Otherwise when you press the keys(WASD) to move it will move the ship and not you.
Almost there... attach the ship Entity to the Scene Node.
mShipNode->attachObject(mShip);
Lighting
Lastly, we have to turn on some lights or you won't be able to see anything. Lets just use some ambient lighting supplied by the Scene Manager for now. Place this line at the start of createScene.
mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
Compile and run. Have a walk around the ship.
Creating our first frame listener
The Frame Listener Class
A Frame Listener in Ogre is notified by the system at the start of every frame. The main method of interest to us is the frameStarted method. For the Frame Listener that we are creating we will give it a pointer to the Scene Node that the ship entity is attached to. This will allow us to move that Scene Node and therefore the ship as well.
class SpaceFrameListener : public ExampleFrameListener { protected: SceneNode* mShipNode; public: SpaceFrameListener(RenderWindow* win, Camera* cam, SceneNode* shipNode) : ExampleFrameListener(win, cam) { mShipNode = shipNode; }; bool frameStarted(const FrameEvent& evt); };
In this example we want to move the ship on the screen based on keypresses from the user. For now we will simply get the ship to respond to the arrow keys and move accordingly (up, down, left, right).
The first thing we do in this method is determine how much the ship can move in this frame. To do this we simply multiply how many units per second we want the ship to move with the value of evt.timeSinceLastFrame (measured in fractions of a second). We will also want to know which keys, if any, are being pressed. To do this we call mInputDevice->capture() which takes a copy of the state of the input devices which we then query by using mInputDevice->isKeyDown().
Once we have determined what, if any, movement is to take place we use the translate method of the Scene Node.
bool frameStarted(const FrameEvent& evt) { Real MoveFactor = 80.0 * evt.timeSinceLastFrame; mInputDevice->capture(); if(mInputDevice->isKeyDown(Ogre::KC_UP)) mShipNode->translate(0.0, MoveFactor, 0.0); if(mInputDevice->isKeyDown(Ogre::KC_DOWN)) mShipNode->translate(0.0, -MoveFactor, 0.0); if(mInputDevice->isKeyDown(Ogre::KC_LEFT)) mShipNode->translate(-MoveFactor, 0.0, 0.0); if(mInputDevice->isKeyDown(Ogre::KC_RIGHT)) mShipNode->translate(MoveFactor, 0.0, 0.0); if(mInputDevice->isKeyDown(Ogre::KC_ESCAPE)) return false; return true; }
Connecting the Frame Listener Class
All that remains now is to arrange for this Frame Listener class to be created and used by the application. The easy way to do this is to override the createFrameListener() method of the SpaceApplication class. The inherited version of createFrameListener creates an instance of ExampleFrameListener which implements the motion that we have seen in the examples that come with Ogre. By overriding this method and supplying our own class we will not get that behaviour any more, but we can then instead dictate our own.
void createFrameListener(void) { mFrameListener= new SpaceFrameListener(mWindow, mCamera, mShipNode); mRoot->addFrameListener(mFrameListener); }
Compile and run the program. The camera will remain in a fixed position but the ship will move up, down, left, and right as you press the arrow keys. It wasn't very hard was it? Here ends this tutorial, the next one we will have a bit more fun with moving and cameras.