History: Basic Tutorial 4
Source of version: 64
- «
- »
Copy to clipboard
{TRANSCLUDE(page="tutbox")}In this tutorial we will be introducing one of the most useful Ogre constructs: the FrameListener. By the end of this tutorial you will understand FrameListeners, how to use FrameListeners to do things that require updates every frame, and how to use OIS's unbuffered input system.
The full source for this tutorial is ((BasicTutorial4SourceCurrent|here)).{TRANSCLUDE}
%tutorialhelp%
!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.
{maketoc}
!Setting Up the Scene
To start, set up your TutorialApplication class like this:
{CODE(caption="TutorialApplication.h" wrap="1" colors="c++")}
#include "BaseApplication.h"
class TutorialApplication : public BaseApplication
{
public:
TutorialApplication();
virtual ~TutorialApplication();
protected:
virtual void createScene();
virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);
private:
bool processUnbufferedInput(const Ogre::FrameEvent& fe);
};
{CODE}
{CODE(caption="TutorialApplication.cpp" wrap="1" colors="c++")}
#include "TutorialApplication.h"
TutorialApplication::TutorialApplication()
{
}
TutorialApplication::~TutorialApplication()
{
}
bool TutorialApplication::processUnbufferedInput(const Ogre::FrameEvent& fe)
{
return true;
}
bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
bool ret = BaseApplication::frameRenderingQueued(fe);
return ret;
}
{CODE}
We will create a scene with a single ninja and one point light.
!!The Code
Find our {MONO()}BasicTutorial4::createScene{MONO} method. The first thing we will be doing is setting the ambient light of the scene very low. We want scene objects to still be visible when the light is off, but we also want the light going on/off to be noticable:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.25, 0.25, 0.25));
{CODE}
Now, add a Ninja entity to the scene at the origin:
{CODE(wrap="1", colors="c++")}
Ogre::Entity* ninjaEntity = mSceneMgr->createEntity("Ninja", "ninja.mesh");
Ogre::SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
node->attachObject(ninjaEntity);
{CODE}
Now we will create a white point light and place it in the Scene, a small distance (relatively) away from the Ninja:
{CODE(wrap="1", colors="c++")}
Ogre::Light* pointLight = mSceneMgr->createLight("pointLight");
pointLight->setType(Ogre::Light::LT_POINT);
pointLight->setPosition(Ogre::Vector3(250, 150, 250));
pointLight->setDiffuseColour(Ogre::ColourValue::White);
pointLight->setSpecularColour(Ogre::ColourValue::White);
{CODE}
That's it for the {MONO()}createScene{MONO} function. On to the {MONO()}frameRenderingQueued{MONO} function...
!FrameListeners
!!Introduction
In Ogre, we can register a class to receive notification before and after a frame is rendered to the screen.
That class is known as a FrameListener.
This FrameListener interface declares three functions which can be used to receive frame events:
{CODE(wrap="1", colors="c++")}
virtual bool frameStarted(const FrameEvent& evt);
virtual bool frameRenderingQueued(const FrameEvent& evt);
virtual bool frameEnded(const FrameEvent& evt);
{CODE}
|| {MONO()}frameStarted{MONO} | Called just before a frame is rendered.
{MONO()}frameRenderingQueued{MONO} | Called after all render targets have had their rendering commands issued, but before%%%the render windows have been asked to flip their buffers over
{MONO()}frameEnded{MONO} | Called just after a frame has been rendered. ||
This loops until any of the FrameListeners return false from {MONO()}frameStarted{MONO}, {MONO()}frameRenderingQueued{MONO} or {MONO()}frameEnded{MONO}. The positive return values for these functions basically mean "keep rendering".
If you return false from any, the program will exit.
The FrameEvent object contains two variables, but only the timeSinceLastFrame is useful in a FrameListener. This variable keeps track of how long it's been since the frameStarted or frameEnded last fired (in seconds). Note that in the frameStarted method, FrameEvent::timeSinceLastFrame will contain how long it has been since the last __frameStarted__ event was last fired (not the last time a frameEnded method was fired).
One important concept to realize about Ogre's FrameListeners is that the order in which they are called is entirely up to Ogre. You cannot determine which FrameListener is called first, second, third...and so on. If you need to ensure that FrameListeners are called in a certain order, then you should register only one FrameListener and have it call all of the objects in the proper order.
So, which one of the three FrameListener methods should you choose?
That of course depends on what you need to do, but if you only want to update your stuff once per frame, put it in the frameRenderingQueued event, because that one is called just before the GPU is made busy by flipping the render buffer.
So you want to keep your CPU busy while the GPU works.
Or, to quote the API docs:
{QUOTE()} The usefulness of this event comes from the fact that rendering
commands are queued for the GPU to process. These can take a little
while to finish, and so while that is happening the CPU can be doing
useful things. Once the request to 'flip buffers' happens, the thread
requesting it will block until the GPU is ready, which can waste CPU
cycles. Therefore, it is often a good idea to use this callback to
perform per-frame processing. Of course because the frame's rendering
commands have already been issued, any changes you make will only
take effect from the next frame, but in most cases that's not noticeable.{QUOTE}
{TRANSCLUDE(page="dobox")}Use the {MONO()}frameRenderingQueued{MONO} function of the FrameListener to update on a per frame basis if you want performance.{TRANSCLUDE}
!!Registering a FrameListener
Our BasicTutorial4 class already is a FrameListener - surprise. :)
It derives from BaseApplication which inherits a FrameListener:
{CODE(wrap="1", colors="c++")}class BaseApplication : public Ogre::FrameListener{CODE}
{MONO()}BaseApplication{MONO} implements the {MONO()}frameRenderingQueued{MONO} function, which we actually overrode in the BasicTutorial3 class in the previous tutorial, along with the {MONO()}createFrameListener{MONO} function.
It's time to explain what these functions do.
In order for our class to become a fully functional FrameListener, you need to register it with Ogre::Root. You need to do that because Ogre::Root needs to know what FrameListener's to call when a frame event occurs.
To add or remove a FrameListener, we can use two functions: {MONO()}Ogre::Root::addFrameListener{MONO} and {MONO()}Ogre::Root::removeFrameListener{MONO}.
The [http://www.ogre3d.org/docs/api/1.9/class_ogre_1_1_root.html#aed3244a81b1c1cec76c675f8e62d7f5e|addFrameListener] method adds a FrameListener, and the [http://www.ogre3d.org/docs/api/1.9/class_ogre_1_1_root.html#aba900bb388dd68af993df311076a9205|removeFrameListener] method removes a FrameListener (that is, the FrameListener will no longer receive updates). Note that the {MONO()}add{MONO}|{MONO()}removeFrameListener{MONO} methods only take in a pointer to a FrameListener, which means they do not have names you can use to refer to them.
{MONO()}BaseApplication{MONO} uses the following code in {MONO()}createFrameListener{MONO} to register itself with Ogre::Root as a FrameListener:
{CODE(wrap="1", colors="c++")}mRoot->addFrameListener(this);{CODE}
After having done that, it's able to receive frame events from Ogre::Root, by means of the FrameListener functions frameStarted, frameRenderingQueued and frameEnded.
!!So, how does it work?
Let's take a peek at {MONO()}Ogre::Root::renderOneFrame{MONO}:
{CODE(wrap="1", colors="c++")}
bool Root::renderOneFrame(void)
{
if(!_fireFrameStarted())
return false;
if (!_updateAllRenderTargets())
return false;
return _fireFrameEnded();
}
{CODE}
Here you can see that Ogre::Root, when rendering a frame, fires a {MONO()}FrameStarted{MONO} event before updating all render targets.
And then fires the {MONO()}FrameEnded{MONO} event when it's done updating.
To see where Ogre::Root fires the {MONO()}FrameRenderingQueued{MONO} event, we'll take a look at an excerpt from {MONO()}Ogre::Root::_updateAllRenderTargets{MONO}:
{CODE(wrap="1", colors="c++")}
bool Root::_updateAllRenderTargets(void)
{
// update all targets but don't swap buffers
mActiveRenderer->_updateAllRenderTargets(false);
// give client app opportunity to use queued GPU time
bool ret = _fireFrameRenderingQueued();
// block for final swap
mActiveRenderer->_swapAllRenderTargetBuffers(mActiveRenderer->getWaitForVerticalBlank());
// more code follows ...
{CODE}
There you can see that it fires the FrameRenderingQueued event after updating the render targets, but before swapping the render target buffers.
That's all you need to know - for now - about the inner workings of FrameListeners.
Be sure you can compile the application before continuing.
!The FrameListener
We need to put some code in our framelistener frameRenderingQueued function:
{CODE(wrap="1", colors="c++")}
bool BasicTutorial4::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
bool ret = BaseApplication::frameRenderingQueued(evt);
if(!processUnbufferedInput(evt)) return false;
return ret;
}
{CODE}
It merely calls {MONO()}BaseApplication::frameRenderingQueued{MONO} and our (yet to be written) {MONO()}processUnbufferedInput{MONO} function and then returns to the loop.
In order to continue rendering the frameStarted method must return a positive boolean value.
If anything returns false, Ogre breaks out of the render loop and the application exits.
Let's put something in the '''processUnbufferedInput''' function.
!Processing Input
!!Variables
We need to define a few static variables in the {MONO()}processUnbufferedInput{MONO} function:
{CODE(wrap="1", colors="c++")}
bool BasicTutorial4::processUnbufferedInput(const Ogre::FrameEvent& evt)
{
static bool mMouseDown = false; // If a mouse button is depressed
static Ogre::Real mToggle = 0.0; // The time left until next toggle
static Ogre::Real mRotate = 0.13; // The rotate constant
static Ogre::Real mMove = 250; // The movement constant
{CODE}
The mRotate and mMove are our constants of rotation and movement. If you want the movement or rotation to be faster or slower, tweak these variables to be higher or lower.
The other two variables (mToggle and mMouseDown) control our input. We will be using "unbuffered" mouse and key input in this tutorial (buffered input will be the subject of our next tutorial). This means that we will be calling methods during our frame listener to query the state of the keyboard and mouse.
We run into an interesting problem when we try to use the keyboard to change the state of some object on the screen. If we see that a key is down, we can act on this information, but what happens the next frame? Do we see that the same key is down and do the same thing again?
In some cases (like movement with the arrow keys) this is what we want to do. However, let's say we want the "T" key to toggle between a light being on or off. The first frame the T key is down, the light gets toggled; the next frame, the T key is still down, so it's toggled again... and again and again until the key is released.
We have to keep track of the key's state between frames to avoid this problem. We will present two separate methods for solving this.
The {MONO()}mMouseDown{MONO} variable keeps track of whether or not the mouse button was also down the previous frame (so if {MONO()}mMouseDown{MONO} is true, we do not perform the same action again until the mouse is released). The {MONO()}mToggle{MONO} variable specifies the time until we are allowed to perform an action again. That is, when a button is pressed, mToggle is set to some length of time where no other actions can occur.
The variables are static local variables, mainly for convenience.
They could just as well have been first class data members of our BasicTutorial4 class, but as they're only used by that function, it makes more sense to have them there.
The Object Oriented Input System (OIS) provides three primary classes to retrieve input: Keyboard, Mouse, and Joystick. In these tutorials we will really only be covering how to use the Keyboard and Mouse objects.
If you are interested in using a joystick (or gamepad) with Ogre, you should look into the Joystick class.
Before moving on, let's take a small look at {MONO()}BaseApplication::frameRenderingQueued{MONO}.
The current state of the keyboard and mouse each frame must be captured each frame, by calling the capture method of the Mouse and Keyboard objects.
This happens by these two lines in BaseApplication::frameRenderingQueued:
{CODE(wrap="1", colors="c++")}
mMouse->capture();
mKeyboard->capture();
{CODE}
Don't add this to our function! :)
The first thing we are going to do is make the left mouse button toggle the light on and off.
Add this to our {MONO()}BasicTutorial4::processUnbufferedInput{MONO} function:
{CODE(wrap="1", colors="c++")}
bool currMouse = mMouse->getMouseState().buttonDown(OIS::MB_Left);
{CODE}
The currMouse variable will be true if the mouse button is down.
Now we will toggle the light depending on whether or not currMouse is true, and if the mouse was not held down the previous frame (because we only want to toggle the light once every time the mouse is pressed).
Also note that the [http://www.ogre3d.org/docs/api/1.9/class_ogre_1_1_light.html#a264a43fc2cf02093736eae60316dda8e|setVisible] method of the [http://www.ogre3d.org/docs/api/1.9/class_ogre_1_1_light.html|Light] class determines if the object actually emits light or not:
{CODE(wrap="1", colors="c++")}
if (currMouse && ! mMouseDown)
{
Ogre::Light* light = mSceneMgr->getLight("pointLight");
light->setVisible(! light->isVisible());
}
{CODE}
Now we need to set the mMouseDown variable to equal whatever the currMouse variable contains.
Next frame this will tell us if the mouse button was up or down previously.
{CODE(wrap="1", colors="c++")}
mMouseDown = currMouse;
{CODE}
Compile and run the application.
Now left clicking toggles the light on and off!
Because we call the BaseApplication's frameRenderingQueued method we can still use the WASD keys to move the camera around.
This method of storing the previous state of the mouse button works well, since we know we already have acted on the mouse state.
The drawback is to use this for every key we bind to an action, we'd need a boolean variable for it.
One way we can get around this is to keep track of the last time any button was pressed, and only allow actions to happen after a certain amount of time has elapsed. We keep track of this state in the mToggle variable.
If mToggle is greater than 0, then we do not perform any actions, if mToggle is less than 0, then we do perform actions.
We'll use this method for the following two key bindings.
The first thing we want to do is decrement the mToggle variable by the time that has elapsed since the last frame:
{CODE(wrap="1", colors="c++")}
mToggle -= evt.timeSinceLastFrame;
{CODE}
Now that we have updated mToggle, we can act on it.
mToggle acts as a 0.5 second delay before any additional changes can take place.
In practice, this delay is longer than necessary, but it illustrates the point.
Let's add an additional way to toggle light on and off:
{CODE(wrap="1", colors="c++")}if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1))
{
mToggle = 0.5;
Ogre::Light* light = mSceneMgr->getLight("pointLight");
light->setVisible(! light->isVisible());
}
{CODE}
Compile and run the tutorial. We can now turn the light on and off by pressing '''1'''.
The next thing we need to do is translate the node holding the ninja whenever the user holds down one of the IJKL keys. Unlike the code above, we do not need to keep track of the last time we moved the camera, since for every frame the key is held down we want to translate it again. This makes our code relatively simple.
First we will create a Vector3 to hold the position we want to translate to:
{CODE(wrap="1", colors="c++")}
Ogre::Vector3 transVector = Ogre::Vector3::ZERO;
{CODE}
Now, when the I key is pressed, we want to move straight forward (which is the negative z axis, remember negative z is straight into the computer screen):
{CODE(wrap="1", colors="c++")}
if (mKeyboard->isKeyDown(OIS::KC_I)) // Forward
{
transVector.z -= mMove;
}
{CODE}
We do almost the same thing for the K key, but we move in the positive z axis instead:
{CODE(wrap="1", colors="c++")}
if (mKeyboard->isKeyDown(OIS::KC_K)) // Backward
{
transVector.z += mMove;
}
{CODE}
For left and right movement, we go in the positive or negative x direction, or yaw to the left or right when left-shift is held:
{CODE(wrap="1", colors="c++")}
if (mKeyboard->isKeyDown(OIS::KC_J)) // Left - yaw or strafe
{
if(mKeyboard->isKeyDown( OIS::KC_LSHIFT ))
{
// Yaw left
mSceneMgr->getSceneNode("NinjaNode")->yaw(Ogre::Degree(mRotate * 5));
} else {
transVector.x -= mMove; // Strafe left
}
}
if (mKeyboard->isKeyDown(OIS::KC_L)) // Right - yaw or strafe
{
if(mKeyboard->isKeyDown( OIS::KC_LSHIFT ))
{
// Yaw right
mSceneMgr->getSceneNode("NinjaNode")->yaw(Ogre::Degree(-mRotate * 5));
} else {
transVector.x += mMove; // Strafe right
}
}
{CODE}
Finally, we also want to give a way to move up and down along the y axis, using keys U and O:
{CODE(wrap="1", colors="c++")}
if (mKeyboard->isKeyDown(OIS::KC_U)) // Up
{
transVector.y += mMove;
}
if (mKeyboard->isKeyDown(OIS::KC_O)) // Down
{
transVector.y -= mMove;
}
{CODE}
Now, our transVector variable has the translation we wish to apply to the Ninja's SceneNode. The first pitfall we can encounter when doing this is that if you rotate the SceneNode, then our x, y, and z coordinates will be wrong when translating. To fix this, we need to apply all of the rotations we have done to the SceneNode to our translation vector. This is actually simpler than it sounds. Whenever you translate a node, or rotate it about any axis, you can specify which Transformation Space you want to use to move the object.
Normally when you translate an object, you do not have to set this parameter. It defaults to TS_PARENT, meaning that the object is moved in whatever transformation space the parent node is in. In this case, the parent node is the root scene node. When we press the I button (to move forward), we subtracted from the Z direction, meaning we move towards the negative Z axis. If we did not specify TS_LOCAL in this previous line of code, we would move the ninja along the global -Z axis. However, since we are trying to make the ninja go ''forward'' when we press I, we need it to go in the direction that the node is actually facing, so we use the ''local'' transformation space.
The second pitfall we have to watch out for is we have to scale the amount we translate by the amount of time since the last frame. Otherwise, how fast you move would be dependent on the framerate of the application. Definitely not what we want. This is the function call we need to make to translate our camera node without encountering these problems:
{CODE(wrap="1", colors="c++")}
mSceneMgr->getSceneNode("NinjaNode")->translate(transVector * evt.timeSinceLastFrame, Ogre::Node::TS_LOCAL);
{CODE}
There is another way we can do this (though it is less direct). We could have gotten the orientation of the node, a quaternion, and multiplied this by the direction vector to get the same result. To represent rotations, Ogre does not use transformation matrices like some graphics engines. Instead it uses Quaternions for all rotation operations. The math behind Quaternions involves four dimensional linear algebra, which is very difficult to understand.
Thankfully, you do not have to understand the math behind them to understand how to use them. Quite simply, to use a Quaternion to rotate a vector, all you have to do is multiply the two together. In this case, we want to apply all of the rotations done to the SceneNode to the translation vector. We can get a Quaternion representing these rotations by calling SceneNode::getOrientation(), then we can apply them to the translation node using multiplication. This would be perfectly valid:
{CODE(wrap="1", colors="c++")}
// Do not add this to the program
mSceneMgr->getSceneNode("NinjaNode")->translate(mSceneMgr->getSceneNode("NinjaNode")->getOrientation() * transVector * evt.timeSinceLastFrame, Ogre::Node::TS_WORLD);
{CODE}
This ''also'' translates the ninja node in the local space. In this case, there is no real reason to do this.
Ogre defines three transformation spaces: TS_LOCAL, TS_PARENT, and TS_WORLD.
There may be a case where you need to make a translation or a rotation in ''another'' vector space than these three. If that is the case, you would do it similarly to the previous line of code.
Take a quaternion representing the vector space (or the orientation of whatever object you are trying to match), multiply it by the translation vector to get the corrected translation vector, and then move it in the TS_WORLD space. This will probably not come up for quite a while though, and we will not refer to it in any of the future tutorials.
Compile the program and try it out.
This tutorial is not meant to be a full walkthrough on rotations and Quaternions (that is enough material to fill an entire tutorial by itself). In the next tutorial, we will use buffered mouse input instead of checking for keys being down every frame.
!Conclusion
Now you should have a base understanding of frame listeners and unbuffered input using OIS.
!!Full Source
If you are having difficulty building this tutorial, take a look at the ((BasicTutorial4Source|source code)) for it and compare it to your project.
!!Next
Proceed to ((Basic Tutorial 5)) ''Buffered Input''
---
Alias: (alias(Basic_Tutorial_4))