Basic Tutorial 4         Frame Listeners and Unbuffered Input
Print

Tutorial Introduction
Image

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.


Any problems you encounter while working with this tutorial should be posted to the Help Forum(external link).

Prerequisites

  • This tutorial assumes you have knowledge of C++ programming and are able to setup and compile an Ogre application.
  • This tutorial also assumes that you have created a project using the Ogre Wiki Tutorial Framework, either manually, using CMake or the Ogre AppWizard - see Setting Up An Application for instructions.
  • This tutorial builds on the previous basic tutorials, and it assumes you have already worked through them.

You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.

Getting Started

This is the code we're starting off with:

BasicTutorial4 header
class BasicTutorial4 : public BaseApplication
{
public:
    BasicTutorial4(void);
    virtual ~BasicTutorial4(void);
protected:
    virtual void createScene(void);
    virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
private:
    bool processUnbufferedInput(const Ogre::FrameEvent& evt);
};
BasicTutorial4 implementation
void BasicTutorial4::createScene(void)
{
}
//-------------------------------------------------------------------------------------
bool BasicTutorial4::processUnbufferedInput(const Ogre::FrameEvent& evt)
{
    return true;
}
//-------------------------------------------------------------------------------------
bool BasicTutorial4::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
    bool ret = BaseApplication::frameRenderingQueued(evt);
    return ret;
}
//-------------------------------------------------------------------------------------

We are overriding the frameRenderingQueued function, and defining a private function called processUnbufferedInput.

Let's spend the rest of the tutorial making them do something interesting. smile

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:

virtual bool frameStarted(const FrameEvent& evt);
virtual bool frameRenderingQueued(const FrameEvent& evt);
virtual bool frameEnded(const FrameEvent& evt);

frameStarted Called just before a frame is rendered.
frameRenderingQueued Called after all render targets have had their rendering commands issued, but before
the render windows have been asked to flip their buffers over
frameEnded Called just after a frame has been rendered.

This loops until any of the FrameListeners return false from frameStarted, frameRenderingQueued or frameEnded. The return values for these functions basically mean "keep rendering".
If you return false from either, 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. 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.
Do This
Image

Use the frameRenderingQueued function of the FrameListener to update on a per frame basis if you want performance.

Registering a FrameListener

Our BasicTutorial4 class already is a FrameListener - surprise. :-)
It derives from BaseApplication which inherits a FrameListener:

class BaseApplication : public Ogre::FrameListener

BaseApplication implements the frameRenderingQueued function, and we actually overrode that function in the BasicTutorial3 class in the previous tutorial.
Actually, we also overrode the createFrameListener function as well.

But now we'll explain what these functions actually do.

In order 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 framelisteners to call when a frame event occurs.

To add or remove a FrameListener, we can use two functions: Ogre::Root::addFrameListener and Ogre::Root::removeFrameListener.

The addFrameListener method adds a FrameListener, and the removeFrameListener method removes a FrameListener (that is, the FrameListener will no longer receive updates).
Note that the add|removeFrameListener methods only take in a pointer to a FrameListener (that is, FrameListeners do not have names you can use to remove them).

BaseApplication uses the following code in createFrameListener to register itself with Ogre::Root as a FrameListener:

mRoot->addFrameListener(this);

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 Ogre::Root::renderOneFrame:

bool Root::renderOneFrame(void)
{
    if(!_fireFrameStarted())
        return false;
 
        if (!_updateAllRenderTargets())
            return false;
 
    return _fireFrameEnded();
}

Here you can see that Ogre::Root, when rendering a frame, fires a FrameStarted event before updating all render targets.
And then fires the FrameEnded event when it's done updating.

To see where Ogre::Root fires the FrameRenderingQueued event, we'll take a look at an excerpt from Ogre::Root::_updateAllRenderTargets:

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 ...

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.

Setting up the Scene

Introduction

Before we dive directly into the code, let's briefly outline what we will be doing so that you understand where we are going when we create and add things to the scene.

We will be placing one object (a ninja) in the scene, and one point light in the scene.
If you left click the mouse, the light will toggle on and off.
You can move the node on which the ninja is attached using the IKJL keys: I moves forward, K moves backward, J strafes right, right-shift + J yaws to the right, L strafes right, L + right-shift yaws right. The U and O keys moves the ninja scene node up and down.

The Code

Find our BasicTutorial4::createScene 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:

mSceneMgr->setAmbientLight(Ogre::ColourValue(0.25, 0.25, 0.25));

Now, add a Ninja entity to the scene at the origin:
Ogre::Entity* ninjaEntity = mSceneMgr->createEntity("Ninja", "ninja.mesh");
Ogre::SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
node->attachObject(ninjaEntity);

Now we will create a white point light and place it in the Scene, a small distance (relatively) away from the Ninja:
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);

That's it for the createScene function. On to the frameRenderingQueued function...

The FrameListener

We need to put some code in our framelistener frameRenderingQueued function:

bool BasicTutorial4::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
    bool ret = BaseApplication::frameRenderingQueued(evt);
 
    if(!processUnbufferedInput(evt)) return false;
 
    return ret;
}

It merely calls BaseApplication::frameRenderingQueued and our (yet to be written) processUnbufferedInput 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 have defined a few variables in the TutorialFrameListener class.
Let's go over them before we get any further:

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

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 mMouseDown variable keeps track of whether or not the mouse was also down the previous frame (so if mMouse down is true, we do not perform the same action again until the mouse is released).
The mToggle 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 Open 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 BaseApplication::frameRenderingQueued.
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:

mMouse->capture();
mKeyboard->capture();

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.
We can find out if a mouse button is down by calling the getMouseButton method of InputReader with the button we want to query for. Usually 0 is the left mouse button, 1 is the right mouse button, and 2 is the center mouse button. On some systems button 1 is the middle and 2 is the right mouse button. Try this configuration if the mouse buttons don't work as expected.
Add this to our BasicTutorial4::processUnbufferedInput function:

bool currMouse = mMouse->getMouseState().buttonDown(OIS::MB_Left);

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 setVisible method of the Light class determines if the object actually emits light or not:
if (currMouse && ! mMouseDown)
{
    Ogre::Light* light = mSceneMgr->getLight("pointLight");
    light->setVisible(! light->isVisible());
}

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.
mMouseDown = currMouse;

Compile and run the application.
Now left clicking toggles the light on and off!
Note that since we no longer call the ExampleFrameListener's frameStarted method, we cannot move the camera around (yet).

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:

mToggle -= evt.timeSinceLastFrame;

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:

if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1))
{
    mToggle  = 0.5;
    Ogre::Light* light = mSceneMgr->getLight("pointLight");
    light->setVisible(! light->isVisible());
}

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 where we want to translate to:

Ogre::Vector3 transVector = Ogre::Vector3::ZERO;

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):
if (mKeyboard->isKeyDown(OIS::KC_I)) // Forward
{
    transVector.z -= mMove;
}

We do almost the same thing for the K key, but we move in the positive z axis instead:
if (mKeyboard->isKeyDown(OIS::KC_K)) // Backward
{
    transVector.z += mMove;
}

For left and right movement, we go in the positive or negative x direction, or yaw to the left or right when right-shift is held:
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
    }
}

Finally, we also want to give a way to move up and down along the y axis, using keys U and O:
if (mKeyboard->isKeyDown(OIS::KC_U)) // Up
{
    transVector.y += mMove;
}
if (mKeyboard->isKeyDown(OIS::KC_O)) // Down
{
    transVector.y -= mMove;
}

Now, our transVector variable has the translation we wish to apply to the camera'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 node. This is actually simpler than it sounds.

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.

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:

mSceneMgr->getSceneNode("NinjaNode")->translate(transVector * evt.timeSinceLastFrame, Ogre::Node::TS_LOCAL);

Now we have introduced something new.
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 camera along the global -Z axis. However, since we are trying to make a camera which goes forward when we press I, we need it to go in the direction that the node is actually facing. Hence, we use the "local" transformation space.

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. This would be perfectly valid:

// Do not add this to the program
mCamNode->translate(mCamNode->getOrientation() * transVector * evt.timeSinceLastFrame, Ogre::Node::TS_WORLD);

This also translates the camera node in the local space. In this case, there is no real reason to do this.
Ogre defines three transform 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 similar 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.

Proceed to Basic Tutorial 5 Buffered Input


Alias: Basic_Tutorial_4


Contributors to this page: LST1 points  , peters166 points  , makism1 points  , JacobM217 points  , jacmoe60863 points  and Harkathmaker .
Page last modified on Monday 05 of July, 2010 10:33:29 GMT by LST1 points .


The content on this page is licensed under the terms of the Creative Commons Attribution-ShareAlike License.
As an exception, any source code contributed within the content is released into the Public Domain.