How to-list
Written by Oogst, last update on 25-05-2004, check the Oogst-site for more Oogst-stuff.
About this list
This
is a list of how to do simple things in Ogre. It contains mainly simple
code examples, which is something I myself really missed while learning
the basics of Ogre. The explanations provided here are all kept brief.
For more details on all the things mentioned, see:
-the official manual for the Ogre-basics and the script-definitions;
-the API for descriptions of all classes and functions in Ogre;
-the tutorials provided with Ogre;
-the search-function of the Ogre-forum.
Why I made this list
I made this list for four reasons:
-I am working on a group project at school and the rest of the team would like to be able to find code examples quickly;
-I really missed this kind of thing when learning Ogre myself and it would have saved me a lot of time;
-I keep forgetting function-names and this way I can look them up quickly;
-I wanted to do something in return for the fantastic engine the
Ogre-team has provided and which I can use for free without any
expectation from the creators of getting something back for it.
ExampleApplication
This
list works from the ExampleApplication and supposes the user uses
ExampleApplication and ExampleFrameListener. Some variables, like
mSceneMgr, are only available with this name through
ExampleApplication. If you are not using ExampleApplication, you will
have to fill in the different variables yourself.
Furthermore,
all examples use arbitrary names for variables and such; of course you
should change these to what you find appropriate for your own
application.
How to...
...set up an application using ExampleApplication
...put a 3D-model in the scene
...remove a 3D-model from the scene
...move, reposition, scale and rotate a SceneNode
...put a light in the scene
...set the ambient lighting
...control the camera
...add a billboard/sprite to the scene
...create a basic FrameListener using ExampleFrameListener
...control some object from your scene in a FrameListener
...get the time since the previous frame
...react to key-presses
...make sure key-presses are not reacted to too shortly after each other
...quit you application
...efficiently add and remove 3D-objects from the scene during run-time (like rockets being fired)
...show an Overlay (and hide it again)
...change the text in a TextArea
...show the mouse-cursor
...create a working button
...find out which button was pressed
...quit the application using an ActionListener
...get a different SceneManager
...efficiently get a list of all possible collisions
...find out to which of your own objects a MovableObject belongs
...exclude objects from collision detection
If you do not know how this
works already, you MUST read more about it in Ogre’s tutorial-section:
it is really important to understand this properly. In short it
requires creating a class that is derived from ExampleApplication. This
class can implement the functions createScene() and
createFrameListener(). It will probably look something like this:
#include "ExampleApplication.h"
class MyClass: public ExampleApplication,
{
public:
MyClass(void);
~MyClass(void);
protected:
void createScene(void);
void createFrameListener(void);
};
You will also need a
.cpp-file that creates one instance of MyClass and runs it. In general
it will look something like this (taken from the “Guide To Setting Up
Application Project Files”-tutorial):
#include "Ogre.h"
#include "MyClass.h"
#if OGRE_PLATFORM == 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
{
MyClass app;
try
{
app.go();
}
catch( Exception& e )
{
#if OGRE_PLATFORM == 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;
}
A 3D-model is an Entity and
it must be attached to a SceneNode to be placed in the scene. The
SceneNode can be taken from the rootSceneNode in the SceneManager.
Creating an Entity and SceneNode and attaching the Entity to the
SceneNode will look something like this:
Entity* thisEntity = mSceneMgr->createEntity("nameInTheScene", "FileName.mesh");
SceneNode* thisSceneNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());
thisSceneNode->attachObject(thisEntity);
To remove a 3D-model, you
must detach the Entity from its SceneNode, delete it and if needed
destroy the SceneNode as well, which must be done using the
SceneManager. If you have a SceneNode* called myNode you can completely
destroy all its contents (both MovableObjects and child SceneNodes) and
the node itself using the following code:
while(myNode->numAttachedObjects() > 0)
{
MovableObject* thisObject = myNode->detachObject(static_cast<unsigned short>(0));
delete thisObject;
}
myNode->removeAndDestroyAllChildren();
mSceneMgr->getRootSceneNode()->removeAndDestroyChild(myNode->getName());
If you have a SceneNode with
some MovableObjects, like Entities, Lights and Cameras, attached to it,
you can move it using a lot of different functions, see the API for all
of them. The following functions respectively move it, reposition it,
scale it and rotate it over its X-, Y- and Z-axis:
thisSceneNode->translate(10, 20, 30);
thisSceneNode->setPosition(1.8, 20.1, 10.5);
thisSceneNode->scale(0.5, 0.8, 1.3);
thisSceneNode->pitch(45);
thisSceneNode->yaw(90);
thisSceneNode->roll(180);
To add a light to the scene,
you must ask the SceneManager for one. You can then set its settings,
of which some examples are given below:
Light* myLight = mSceneMgr->createLight("nameOfTheLight");
myLight->setType(Light::LT_POINT);
myLight->setPosition(200, 300, 400);
myLight->setDiffuseColour(1, 1, 0.7);
myLight->setSpecularColour(1, 1, 0.7);
You can also attach a light to a SceneNode. The following code creates a SceneNode and attaches myLight to it:
SceneNode* thisSceneNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());
thisSceneNode->attachObject(myLight);
The ambient lighting is controlled by the Scenemanager, so that is where you can set it:
mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
The standard camera in
ExampleApplication is called mCamera and is available in the class that
is derived from ExampleApplication. The following code changes its
position, changes the point it looks at, creates a SceneNode and
attaches the camera to it:
mCamera->setPosition(0, 130, -400);
mCamera->lookAt(0, 40, 0);
SceneNode* thisSceneNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());
thisSceneNode->attachObject(mCamera);
A Billboard is a
square polygon that is always pointed at the camera. It is also known
as a sprite. To make one, you must first make a BillboardSet. Then the
billboard can be added to it on a given position. The BillboardSet is a
MovableObject en should therefore be added to a SceneNode. The whole
procedure is as follows:
SceneNode* myNode = static_cast(mSceneMgr->getRootSceneNode()->createChild());
BillboardSet* mySet = mSceneMgr->createBillboardSet("mySet");
Billboard* myBillboard = mySet->createBillboard(Vector3(100, 0, 200));
myNode->attachObject(mySet);
A
FrameListener gives you the opportunity to do something at the start
and the end of every frame. You must first create a class that is
derived from ExampleFrameListener and in this class you can implement
frameStarted() and frameEnded(). This will look something like this:
#include "ExampleFrameListener.h"
class myFrameListener: public ExampleFrameListener
{
public:
myFrameListener(RenderWindow* win, Camera* cam);
~myFrameListener(void);
bool frameStarted(const FrameEvent& evt);
bool frameEnded(const FrameEvent& evt);
};
The constructor should call its parent-constructor, which will look something like this:
myFrameListener::myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam){}
You must
also register your FrameListener to the Root. This can be done in the
createFrameListener()-function of the class that is derived from
ExampleApplication. You can register as many FrameListeners to the root
as you want. It will look something like this:
void createFrameListener(void)
{
MyFrameListener listener = new MyFrameListener(mWindow, mCamera);
mRoot->addFrameListener(listener);
}
If you want to control
some object you have in your scene in a FrameListener, the
FrameListener must have access to it. An easy way to do this, is by
providing a pointer to it in the constructor of the FrameListener. Its
constructor will now look something like this:
myFrameListener(RenderWindow* win, Camera* cam, Car* car);
In the functions frameEnded()
and frameStarted() of a FrameListener you can get the time in seconds
(this is a float) in the following way:
bool frameStarted(const FrameEvent& evt)
{
float time = evt.timeSinceLastFrame;
return true;
}
You can now for instance
multiply the speed per second with this float in order to get the
movement since the last frame. This will make the pace of the game
framerate-independent.
In the functions frameEnded()
and frameStarted() of a FrameListener you can react to key-presses by
first ordering Ogre to capture them and then checking which key is
being pressed. You only have to capture the InputDevice once per frame.
This will look something like this:
mInputDevice->capture();
if (mInputDevice->isKeyDown(Ogre::KC_DOWN))
{
//react however you like
}
if (mInputDevice->isKeyDown(Ogre::KC_UP))
{
//react however you like
}
If you implement reactions
to key-presses in the above way, they will happen each frame. If you
want them to happen, for instance, at most twice per second, you can
achieve this by setting a timer. This timer must be kept in the class
itself and not in the function in order to be able to access it through
different calls of the function. Implementing this will look something
like this:
class myFrameListener: public ExampleFrameListener
{
protected:
float buttonTimer;
public:
myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam)
{
buttonTimer = 0;
}
bool frameStarted(const FrameEvent& evt)
{
float time = evt.timeSinceLastFrame;
buttonTimer -= time;
mInputDevice->capture();
if (mInputDevice->isKeyDown(Ogre::KC_DOWN) && buttonTimer <= 0)
{
buttonTimer = 0.5;
//react however you like
}
return true;
}
};
You can quit you application
in the frameStarted() or frameEnded()-function of the FrameListener by
returning false. If you want to tie this to pressing the escape-button
on the keyboard, this will look as follows:
bool frameStarted(const FrameEvent& evt)
{
mInputDevice->capture();
if (mInputDevice->isKeyDown(Ogre::KC_ESCAPE))
return false;
return true;
}
In the createScene()-function
you can load meshes from a file, but this is too slow to do if new
objects must be added in run-time (though in a very simple application
you will not see the difference in speed). To add objects faster, you
can load a lot of meshes in the createScene()-function and then take
them from a stack in run-time. After the objects are not used anymore,
you can put them back on the stack for later use. A good example of the
usefulness of this is a canon that fires rockets: these must be created
when fired and removed when exploded.
In order to always have access
to this stack of rockets, you can make it a global variable (outside
any class) or access it through a singleton. The latter is much nicer,
but takes more code so I will not do so in this example. So you keep a
stack of Entities somewhere:
stack rocketEntities;
In createScene() you fill this
stack with a lot of rockets. Each Entity is set invisible for now and
must have a unique name, which is achieved using the
sprintf()-function. All in all it looks like this:
for (unsigned int t = 0; t < 100; t++)
{
char tmp[20];
sprintf(tmp, "rocket_%d", t);
Entity* newRocketEntity = mSceneMgr->createEntity(tmp, "Rocket.mesh");
newRocketEntity->setVisible(false);
rocketEntities.push(newRocketEntity);
}
Now when creating a new Rocket
we can take a mesh from rocketEntities. In this example I do so in the
constructor of the Rocket and put it pack in the destructor of the
Rocket. In order to get the Entity back from the SceneNode in the
destructor, I store its name in rocketEntityName. I also position the
rocket correctly in the constructor. To make this all work, the
SceneManager must be available throughout your program, in this case
this is achieved by having it be a global variable (outside any class).
I also handle creation and destruction of the SceneNode in the
constructor and destructor. The Rocket-class will now look like this:
class Rocket
{
protected:
SceneNode* rocketNode;
string rocketEntityName;
public:
Rocket(const Vector3& position, const Quaternion& direction)
{
rocketNode = static_cast<SceneNode*>(sceneMgr->getRootSceneNode()->createChild());
Entity* rocketEntity = rocketEntities.top();
rocketEntities.pop();
rocketEntity->setVisible(true);
rocketEntityName = rocketEntity->getName();
rocketNode->attachObject(rocketEntity);
rocketNode->setOrientation(direction);
rocketNode->setPosition(position);
}
~Rocket()
{
Entity* rocketEntity = static_cast<Entity*>(rocketNode->detachObject(rocketEntityName));
rocketEntity->setVisible(false);
rocketEntities.push(rocketEntity);
sceneMgr->getRootSceneNode()->removeAndDestroyChild(rocketNode->getName());
}
};
If you use this construction,
you should be aware of the fact that this crashes if no rockets are
left. You can solve this by checking whether the stack of Entities is
empty and if it is, load new meshes to add to the stack.
To show and hide an
Overlay, you must first get a pointer to it using the OverlayManager,
which is a singleton. If you have an Overlay that is defined in a
.overlay-script with the name “myOverlay” you can get it, show it and
hide it again using the following code:
Overlay* thisOverlay = static_cast<Overlay*>(OverlayManager::getSingleton().getByName("myOverlay"));
thisOverlay->show();
thisOverlay->hide();
You can change all
parameters of Containers and Elements in the GUI in runtime. In order
to do so, you must first get a pointer to the Element or Container you
want and, if needed for what you want to do, cast it to the type it is.
Getting a pointer to a TextArea that is defined in a .overlay-script as
“myTextArea” and changing its caption will look something like this:
GuiElement* thisTextArea = GuiManager::getSingleton().getGuiElement("myTextArea");
thisTextArea->setCaption(“blaat”);
In this case no
casting was needed, as every GuiElement has a caption. If you want to
set a setting that is specific for one type of GuiElement, you will
have to cast it to that type. Changing the font-name of a TextArea will
look something like this:
TextAreaGuiElement* thisTextArea = static_cast<TextAreaGuiElement*>(GuiManager::getSingleton().getGuiElement("myTextArea"));
thisTextArea->setFontName(“RealCoolFont”);
If you want to show the
mouse-cursor on the screen, you have to do two things: set it to be
shown and tell a FrameListener to track it. Telling it to be shown can
be done as follows:
GuiContainer* cursor = OverlayManager::getSingleton().getCursorGui();
cursor->setMaterialName("Cursor/default");
cursor->setDimensions(32.0/640.0, 32.0/480.0);
cursor->show();
Telling a FrameListener to
track it should be done in its parents’ constructor by setting its last
boolean-parameter to true. The constructor will now look something like
this:
myFrameListener::myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam, false, true){}
Beware though that after
doing this, this specific FrameListener will not react to
button-presses anymore as the capture()- and isKeyPressed()-functions
of mInputDevice now do not work properly anymore. A different
FrameListener should be made to handle keyboard input now.
You can define a button in
an Overlay-script using something like the following syntax, where it
is important to set all these materials:
container Button(myButton)
{
metrics_mode relative
horz_align left
vert_align top
top 0.1
left 0.1
width 0.18
height 0.1
material NCG/GuiSession/RedMaterial
button_down_material NCG/GuiSession/RedMaterial
button_up_material NCG/GuiSession/RedMaterial
button_hilite_down_material NCG/GuiSession/RedMaterial
button_hilite_up_material NCG/GuiSession/RedMaterial
button_disabled_material NCG/GuiSession/RedMaterial
}
Buttons are not standard
in Ogre and therefore you must make sure they are found by the compiler
in the folder ogrenew\PlugIns\GuiElements\Include. Now you can include
it:
#include “OgreButtonGuiElement.h”
To make the button work, an ActionListener must be registered to it, which can be done as follows:
ActionTarget* thisButton = static_cast<ButtonGuiElement*>(GuiManager::getSingleton().getGuiElement("myButton"));
thisButton->addActionListener(this);
This examples supposes
‘this’ is an ActionListener, which is not true by default. Of cource
any ActionListener will do, it does not have to be ‘this’. You can make
a class an ActionListener by deriving it from ActionListener and
implementing actionPerformed(). This will look something like this:
class Listener: public ActionListener
{
public:
void actionPerformed(ActionEvent *e);
};
If you register
one ActionListener to several buttons, they will all call the same
function actionPerformed(). You can find out which button was actually
being pressed by comparing its name with the name in the ActionEvent e.
This will look something like this:
#include
void actionPerformed(ActionEvent *e)
{
std::string action = e->getActionCommand();
if (action == "myButton")
{
//handle the button-press
}
}
Quiting an
application can be done in the frameStarted()- and
frameEnded()-functions of a FrameListener, not in the
actionPerformed()-function. So how do we get there? For this we can
introduce a simple FrameListener that does nothing more than quit if
asked to. This FrameListener will look something like this (from the
Gui-demo provided with Ogre):
#include “ExampleFrameListener.h”
class QuitListener: public ExampleFrameListener
{
public:
QuitListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam, false, false)
{
quit = false;
}
bool frameStarted(const FrameEvent& evt)
{
if (quit)
return false;
return true;
}
void scheduleQuit(void)
{
quit = true;
}
protected:
bool quit;
};
Now if your
ActionListener has a pointer to this QuitListener, it can simply
schedule a quit by calling scheduleQuit() and the QuitListener will
perform it before the next frame starts.
The
SceneManager is chosen automatically using an identifier that tells
what kind of scene it is. This is done in ExampleApplication, so to
change this, you will have to change ExampleApplication itself.
Normally, ExampleApplication contains the following piece of code:
virtual void chooseSceneManager(void)
{
// Get the SceneManager, in this case a generic one
mSceneMgr = mRoot->getSceneManager(ST_GENERIC);
}
You can change ST_GENERIC to any of the following identifiers to get a SceneManager that is appropriate for your specific scene:
ST_GENERIC
ST_EXTERIOR_CLOSE
ST_EXTERIOR_FAR
ST_EXTERIOR_REAL_FAR
ST_INTERIOR
Ogre can provide you
with a list of all objects that are possible colliding. Objects that
are close but not in a collision might be in this list as well, so you
will have to make sure which one is a collision yourself afterwards.
You can ask for this list of collisions using an
IntersectionSceneQuery, which you can get using the following code:
IntersectionSceneQuery* intersectionQuery = sceneMgr->createIntersectionQuery();
Now you can ask it for a list of all possible collisions:
IntersectionSceneQueryResult& queryResult = intersectionQuery->execute();
If you intend to get
this list several times before updating your scene, there is no need to
have Ogre calculate it again over and over. You can get back the same
list again without a new calculation using the following line:
IntersectionSceneQueryResult& queryResult = intersectionQuery->getLastResults();
After use, you can
store the IntersectionSceneQuery for later use or remove it. If you
remove it, you must tell the SceneManager to do so using the following
code:
mSceneMgr->destroyQuery(intersectionQuery);
The
IntersectionSceneQueryResult is a list of pairs of MovableObjects. An
example of its use is getting the name of the first of the two
colliding objects:
queryResult.movables2movables.begin()->first->getName();
See the Ogre-API for more details on the types of the results.
The list of
colliding objects IntersectionSceneQuery delivers is of MovableObjects,
but in general you will want to relate this to some custom object in
your own scene. To do so, you can attach a pointer to your own object
to a MovableObject and get this pointer back later on. If you have an
Entity (which is a MovableObject) and a custom object (like myRocket
here below), you can attach your custom object to the Entity as follows:
Entity* myEntity = sceneMgr->createEntity("myEntity”, "Rocket.mesh");
Rocket* myRocket = new Rocket();
myEntity->setUserObject(myRocket);
Now you can get a pointer to myRocket back from the Entity using the following code:
myEntity->getUserobject();
To make this
work, your own custom class must be derived from UserDefinedObject.
This also allows you to find out what kind of class the returned object
is, so that you can cast it back. The definition for Rocket could now
look something like this:
class Rocket: public UserDefinedObject
{
public:
const string& getTypeName(void)
{
const string& typeName = "Rocket";
return typeName;
}
};
Now after
having detected which MovableObjects are involved in a collision, you
can also find out which of your own objects are involved in this
collision.
You can exclude certain
objects from collision detection by using flags. You can add a flag to
any MovableObject, for instance 100 in the following example:
Entity* ground = mSceneMgr->createEntity("ground", "Ground.mesh");
ground->setQueryFlags(100);
Now you can tell your
IntersectionSceneQuery to exclude objects with this flag from its list
of intersections by setting its mask. This will look like this:
IntersectionSceneQuery* intersectionQuery = sceneMgr->createIntersectionQuery();
intersectionQuery->setQueryMask(~100);