Upload
david
View
54
Download
2
Embed Size (px)
Citation preview
Animation, Walking Between Points, and Basic Quaternions
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Table of contents
Introduction
Prerequisites
Getting Started
Setting up the Scene
Animation
Moving the Robot
Additional Information
Walking on terrain (aka modifying the robots y-axis)
My robot does not face to the direction at the beginning?
Exercises for Further Study
Easy Questions
Intermediate Questions
Difficult Questions
Expert Questions
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Introduction
In this tutorial we will be covering how to take an Entity, animate it, and have it walk between predefined points.
This will also cover the basics of Quaternion rotation by showing how to keep the Entity facing the direction it is
moving. As you go through the demo you should be slowly adding code to your own project and watching the
results as we build it.
You can see the final state of this tutorial here.
Prerequisites
This tutorial will assume that you already know how to set up an Ogre project and make it compile successfully.
This tutorial also makes use of the STL deque data structure. While no prior knowledge of how to use a deque
is required, you should at least know what templates are. If you are not familiar with STL, I would recommend
picking up STL Pocket Reference [ISBN 0-596-00556-3]. This will save you a lot of time in the future.
You can read the first part of "STL Pocket Reference" here
Getting Started
First, you need to create a new project (I called it ITutorial01) and add the following code:
ITutorial01 header
1 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
#ifndef __ITutorial01_h_
#define __ITutorial01_h_
#include "BaseApplication.h"
#include <deque>
class ITutorial01 : public BaseApplication
{
public:
ITutorial01(void);
virtual ~ITutorial01(void);
protected:
virtual void createScene(void);
virtual void createFrameListener(void);
virtual bool nextLocation(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent &evt);
Ogre::Real mDistance; // The distance the object has
left to travel
Ogre::Vector3 mDirection; // The direction the object is
moving
Ogre::Vector3 mDestination; // The destination the object
is moving towards
Ogre::AnimationState *mAnimationState; // The current animation state
of the object
Ogre::Entity *mEntity; // The Entity we are animating
Ogre::SceneNode *mNode; // The SceneNode that the
Entity is attached to
std::deque<Ogre::Vector3> mWalkList; // The list of points we are
walking to
Ogre::Real mWalkSpeed; // The speed at which the
object is moving
};
#endif // #ifndef __ITutorial01_h_
#include "ITutorial01.h"
//-------------------------------------------------------------------------
------------
ITutorial01::ITutorial01(void)
{
ITutorial01 implementation
2 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
}
//-------------------------------------------------------------------------
------------
ITutorial01::~ITutorial01(void)
{
}
//-------------------------------------------------------------------------
------------
void ITutorial01::createScene(void)
{
}
void ITutorial01::createFrameListener(void){
BaseApplication::createFrameListener();
}
bool ITutorial01::nextLocation(void){
return true;}
bool ITutorial01::frameRenderingQueued(const Ogre::FrameEvent &evt){
return BaseApplication::frameRenderingQueued(evt);
}
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
ITutorial01 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception
has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() << std::endl;
#endif
}
3 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
return 0;
}
#ifdef __cplusplus
}
#endif
Be sure you can compile this code before continuing.
Setting up the Scene
Before we begin, notice that we have already defined 3 variables in the header file. The mEntity will hold the
entity we create, mNode will hold the node we create, and mWalkList will contain all the points we wish the
object to walk to.
Go to the ITutorial01::createScene function and add the following code to it. First we are going to set the
ambient light to full so that we can see objects we put on the screen.
// Set the default lighting.
mSceneMgr->setAmbientLight(Ogre::ColourValue(1.0f, 1.0f, 1.0f));
Next we will create a Robot on the screen so that we can play with him. To do this we will create the entity for
the Robot, then create a SceneNode for him to dangle from.
// Create the entity
mEntity = mSceneMgr->createEntity("Robot", "robot.mesh");
// Create the scene node
mNode = mSceneMgr->getRootSceneNode()->
createChildSceneNode("RobotNode", Ogre::Vector3(0.0f, 0.0f,
25.0f));
mNode->attachObject(mEntity);
This all should be very basic, so I will not go into detail about any of it. In the next chunk of code, we are going
to tell the robot where he needs to be moved to. For those of you who don't know anything about STL, the
deque object is an efficient implementation of a double ended queue. We will only be using a few of its
methods. The push_front and push_back methods put items at the front and back of the deque respectively.
The front and back methods return the values at the front and back of the deque respectively. The pop_front
and pop_back methods remove the items from the front and back of the queue respectively. Finally, the empty
method returns whether or not the deque is empty. This code adds two Vectors to the deque, which we will
later make the robot move to.
// Create the walking list
mWalkList.push_back(Ogre::Vector3(550.0f, 0.0f, 50.0f ));
mWalkList.push_back(Ogre::Vector3(-100.0f, 0.0f, -200.0f));
Next, we want to place some objects on the scene to show where the robot is supposed to be moving to. This
will allow us to see the robot moving with respect to other objects on the screen. Notice the negative Y
4 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
component to their position. This puts the objects under where the robot is moving to, and he will stand on top
of them when he gets to the right spot.
// Create objects so we can see movement
Ogre::Entity *ent;
Ogre::SceneNode *node;
ent = mSceneMgr->createEntity("Knot1", "knot.mesh");
node = mSceneMgr->getRootSceneNode()->createChildSceneNode
("Knot1Node",
Ogre::Vector3(0.0f, -10.0f, 25.0f));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
ent = mSceneMgr->createEntity("Knot2", "knot.mesh");
node = mSceneMgr->getRootSceneNode()->createChildSceneNode
("Knot2Node",
Ogre::Vector3(550.0f, -10.0f, 50.0f));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
ent = mSceneMgr->createEntity("Knot3", "knot.mesh");
node = mSceneMgr->getRootSceneNode()->createChildSceneNode
("Knot3Node",
Ogre::Vector3(-100.0f, -10.0f,-200.0f));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
Finally, we want to set the camera to a good viewing point to see this from. We will move the camera to get a
better position.
// Set the camera to look at our handiwork
mCamera->setPosition(90.0f, 280.0f, 535.0f);
mCamera->pitch(Ogre::Degree(-30.0f));
mCamera->yaw(Ogre::Degree(-15.0f));
Now compile and run the code.
Animation
We are now going to setup some basic animation. Animation in Ogre is very simple. To do this, you need to get
the AnimationState from the Entity object, set its options, and enable it. This will make the animation active, but
you will also need to add time to it after each frame in order for the animation to run. We'll take this one step at
a time. First, go to ITutorial01::createFrameListener and add the following code, after the call to
BaseApplication::createFrameListener:
// Set idle animation
mAnimationState = mEntity->getAnimationState("Idle");
mAnimationState->setLoop(true);
mAnimationState->setEnabled(true);
5 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
The second line gets the AnimationState out of the entity. In the third line we call setLoop( true ), which makes
the animation loop over and over. For some animations (like the death animation), we would want to set this to
false instead. The fourth line actually enables the Animation. But wait...where did we get ‘idle’ from? How did
this magic constant slip in there? Every mesh has their own set of Animations defined for them. In order to see
all of the Animations for the particular mesh you are working on, you need to download the OgreMeshViewer
and view the mesh from there.
Now, if we compile and run the demo we see...nothing has changed. This is because we need to update the
animation state with a time every frame. Find the ITutorial01::frameRenderingQueued method, and add this line
of code at the beginning of the function:
mAnimationState->addTime(evt.timeSinceLastFrame);
Now build and run the application. You should see a robot performing his idle animation standing in place.
Moving the Robot
Now we are going to perform the tricky task of making the robot walk from point to point. Before we begin I
would like to describe the variables that we have defined. We are going to use 4 variables to accomplish the
task of moving the robot. First of all, we are going to store the direction the robot is moving in mDirection. We
will store the current destination the Robot is traveling to in mDestination. We will store the distance the robot
has left to travel in mDistance. Finally, we will store the robot's moving speed in mWalkSpeed.
The first thing we need to do is to set up these variables. We'll set the walk speed to 35 units per second. There
is one big thing to note here. We are explicitly setting mDirection to be the ZERO vector because later we will
use this to determine if we are moving the Robot or not. Add the following code to
ITutorial01::createFrameListener:
// Set default values for variables
mWalkSpeed = 35.0f;
mDirection = Ogre::Vector3::ZERO;
Now that this is done, we need to set the robot in motion. To make the robot move, we simply tell it to change
animations. However, we only want to start the robot moving if there is another location to move to. For this
reason we call the ITutorial01::nextLocation function. Add this code to the top of the
ITutorial01::frameRenderingQueued method just before the AnimationState::addTime call:
if (mDirection == Ogre::Vector3::ZERO)
{
if (nextLocation())
{
// Set walking animation
mAnimationState = mEntity->getAnimationState("Walk");
mAnimationState->setLoop(true);
mAnimationState->setEnabled(true);
}
}
6 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
If you compile and run the code right now, the robot will walk in place. This is because the robot starts out with
a direction of ZERO and our ITutorial01::nextLocation function always returns true. In later steps we will be
adding a bit more intelligence to the ITutorial01::nextLocation function.
Now we are going to actually move the robot in the scene. To do this we need to have him move a small bit
every frame. Go to the ITutorial01::frameRenderingQueued method. We will be adding the following code just
after our previous if statement and just above the AnimationState::addTime call. This code will handle the case
when the robot is actually moving; mDirection != Ogre::Vector3::ZERO.
the reason why mWalkspeed is multiplied by evt.timeSinceLastFrame, is to keep the walkspeed constant,
despite variations in framerate.
if you only had written Real move = mWalkspeed, the robot would walk slow on a slow computer, and walk fast
on a fast one.
else
{
Ogre::Real move = mWalkSpeed * evt.timeSinceLastFrame;
mDistance -= move;
Now, we need to check and see if we are going to overshoot the target position. That is, if mDistance is now
less than zero, we need to jump to the point and set up the move to the next point. Note that we are setting
mDirection to the ZERO vector. If the nextLocation method does not change mDirection (IE there is nowhere
left to go) then we no longer have to move around.
if (mDistance <= 0.0f)
{
mNode->setPosition(mDestination);
mDirection = Ogre::Vector3::ZERO;
Now that we have moved to the point, we need to setup the motion to the next point. Once we know if we need
to move to another point or not, we can set the appropriate animation; walking if there is another point to go to
and idle if there are no more destination points. This is a simple matter of setting the Idle animation if there are
no more locations.
// Set animation based on if the robot has another point to walk to.
if (! nextLocation())
{
// Set Idle animation
mAnimationState = mEntity->getAnimationState("Idle");
mAnimationState->setLoop(true);
mAnimationState->setEnabled(true);
}
else
{
// Rotation Code will go here later
7 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
}
}
Note that we have no need to set the walking animation again if there are more points in the queue to walk to.
Since the robot is already walking there is no reason to tell him to do so again. However, if the robot needs to
go to another point, then we need to rotate him to face that point. For now we leave a placeholder comment in
the else clause; remember this spot as we will come back to it later.
This takes care of when we are very close to the target position. Now we need to handle the normal case, when
we are just on the way to the position but we're not there yet. To do that we will translate the robot in the
direction we are traveling, and move it by the amount specified by the move variable. This is accomplished by
adding the following code:
else
{
mNode->translate(mDirection * move);
} // else
} // if
We are almost done. Our code now does everything except set up the variables required for movement. If we
can properly set the movement variables our Robot will move like he is supposed to. Find the
ITutorial01::nextLocation function. This function returns false when we run out of points to go to. This will be the
first line of our function. (Note you should leave the return true statement at the bottom of the function.)
if (mWalkList.empty())
return false;
Now we need to set the variables (still in the nextLocation method). First we will pull the destination vector from
the deque. We will set the direction vector by subtracting the SceneNode's current position from the destination.
We have a problem though. Remember how we multiplied mDirection by the move amount in
frameRenderingQueued? If we do this, we need the direction vector to be a unit vector (that is, it's length
equals one). The normalise function does this for us, and returns the old length of the vector. Handy that, since
we need to also set the distance to the destination.
mDestination = mWalkList.front(); // this gets the front of the deque
mWalkList.pop_front(); // this removes the front of the
deque
mDirection = mDestination - mNode->getPosition();
mDistance = mDirection.normalise();
Now compile and run the code. It works! Sorta. The robot now walks to all the points, but he is always facing
the Ogre::Vector3::UNIT_X direction (his default). We will need to change the direction he is facing when he is
moving towards points.
What we need to do is get the direction the Robot is facing, and use rotate function to rotate the object in the
right position. Insert the following code where we left our placeholder comment in the previous step. The first
line gets the direction the Robot is facing. The second line builds a Quaternion representing the rotation from
the current direction to the destination direction. The third line actually rotates the Robot.
8 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
Ogre::Vector3 src = mNode->getOrientation() * Ogre::Vector3::UNIT_X;
Ogre::Quaternion quat = src.getRotationTo(mDirection);
mNode->rotate(quat);
We briefly mentioned Quaternions in Basic Tutorial 4, but this is the first real use we have had for them.
Basically speaking, Quaternions are representations of rotations in 3 dimensional space. They are used to keep
track of how the object is positioned in space, and may be used to rotate objects in Ogre. In the first line we call
the getOrientation method, which returns a Quaternion representing the way the Robot is oriented in space.
Since Ogre has no idea which side of the Robot is the "front" of the robot, we must multiply this orientation by
the UNIT_X vector (which is the direction the robot "naturally" faces) to we obtain the direction the robot is
currently facing. We store this direction in the src variable. In the second line, the getRotationTo method gives
us a Quaternion that represents the rotation from the direction the Robot is facing to the direction we want him
to be facing. In the third line, we rotate the node so that it faces the new orientation.
There is only one problem with the code we have created. There is a special case where SceneNode::rotate
will fail. If we are trying to turn the robot 180 degrees, the rotate code will bomb with a divide by zero error. In
order to fix that, we will test to see if we are performing a 180 degree rotation. If so, we will simply yaw the robot
by 180 degrees instead of using rotate. To do this, delete the three lines we just put in and replace them with
this:
Ogre::Vector3 src = mNode->getOrientation() * Ogre::Vector3::UNIT_X;
if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
{
mNode->yaw(Ogre::Degree(180));
}
else
{
Ogre::Quaternion quat = src.getRotationTo(mDirection);
mNode->rotate(quat);
} // else
All of this should now be self explanatory except for what is wrapped in that if statement. If two unit vectors
oppose each other (that is, the angle between them is 180 degrees), then their dot product will be -1. So, if we
dotProduct the two vectors together and the result equals -1.0f, then we need to yaw by 180 degrees,
otherwise we use rotate instead. Why do I add 1.0f and check to see if it is less than 0.0001f? Don't forget
about floating point rounding error. You should never directly compare two floating point numbers. Finally, note
that in this case the dot product of these two vectors will fall in the range [-1, 1]. In case it is not abundantly
clear, you need to know at minimum basic linear algebra to do graphics programming! At the very least you
should review the Quaternion and Rotation Primer and consult a book on basic vector and matrix operations.
Now our code is complete! Compile and run the demo to see the Robot walk the points he was given.
Additional Information
Walking on terrain (aka modifying the robots y-axis)
The code for rotation of the robot will not work properly when also changing the y-axis of the robot. You might
see unexpected roll and pitch.
This can be fixed with the code below which was sponsered by the article Quaternion and Rotation Primer.
9 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
Vector3 mDestination = mWalkList.front( ); //
mDestination is the next location
Vector3 mDirection = mDestination - mNode->getPosition(); // B-A = A->B
(see vector questions above)
Vector3 src = mNode->getOrientation() * Vector3::UNIT_X; //
Orientation from initial direction
src.y = 0; // Ignore
pitch difference angle
mDirection.y = 0;
src.normalise();
Real mDistance = mDirection.normalise( ); // Both
vectors modified so renormalize them
Quaternion quat = src.getRotationTo(mDirection);
mNode->rotate(quat);
My robot does not face to the direction at the beginning?
The robot is turned when it arrives a waypoint. To properly turn the robot in all cases you best put the rotation
code to a new function:
//put this line to the header
void rotateRobotToDirection(void);
//this to the application itself
void ITutorial01::rotateRobotToDirection(void) {
Vector3 src = mNode->getOrientation() * Vector3::UNIT_X; //
Orientation from initial direction
src.y = 0; // Ignore
pitch difference angle
mDirection.y = 0;
src.normalise();
Real mDistance = mDirection.normalise( ); // Both
vectors modified so renormalize them
Quaternion quat = src.getRotationTo(mDirection);
mNode->rotate(quat);
}
Now you can use this function in the else-part of "if (! nextLocation())". And you can add it to this part of the
code to rotate the robot to the walking direction right at the beginning:
if (mDirection == Ogre::Vector3::ZERO)
{
if (nextLocation())
{
// Set walking animation
mAnimationState = mEntity->getAnimationState("Walk");
mAnimationState->setLoop(true);
10 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
mAnimationState->setEnabled(true);
rotateRobotToDirection();
}
}
else
//...
Exercises for Further Study
Easy Questions
1. Add more points to the robot's path. Be sure to also add more knots that sit under his position so you
can track where he is supposed to go.
2. Robots who have outlived their usefulness should not continue existing! When the robot has finished
walking, have him perform the death animation instead of idle. The animation for death is ‘Die’.
Intermediate Questions
1. There is something wrong with mWalkSpeed. Did you notice this when going through the tutorial? We
only set the value once, and never change it. This should be a constant static class variable. Change the
variable so that it is.
2. The code does something very hacky, and that's track whether or not the Robot is walking by looking at
the mDirection vector and comparing it to Vector3::ZERO. It would have been better if we instead had a
boolean variable called mWalking that kept track of whether or not the robot is moving. Implement this
change.
Difficult Questions
1. One of the limitations of this class is that you cannot add points to the robot's walking path after you
have created the object. Fix this problem by implementing a new method which takes in a Vector3 and
adds it to the mWalkList deque. (Hint, if the robot has not finished walking you will only need to add the
point to the end of the deque. If the robot has finished, you will need to make him start walking again,
and call nextLocation to start him walking again.)
Expert Questions
1. Another major limitation of this class is that it only tracks one object. Reimplement this class so that it
can move and animate any number of objects independently of each other. (Hint, you should create
another class that contains everything that needs to be known to animate one object completely. Store
this in a STL map object so that you can retrieve data later based on a key.) You get bonus points if you
can do this without registering any additional frame listeners.
2. After making the previous change, you might have noticed that Robots can now collide with each other.
Fix this by either creating a smart path finding function, or detecting when robots collide and stopping
them from passing through each other.
11 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
Proceed to Intermediate Tutorial 2 RaySceneQueries and Basic Mouse Usage
Alias: Intermediate_Tutorial_1?
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR
COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED
HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
• "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the
Work in its entirety in unmodified form, along with a number of other contributions, constituting separate
and independent works in themselves, are assembled into a collective whole. A work that constitutes a
Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this
License.
• "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works,
such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound
recording, art reproduction, abridgment, condensation, or any other form in which the Work may be
recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be
considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the
Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with
a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
• "Licensor" means the individual or entity that offers the Work under the terms of this License.
• "Original Author" means the individual or entity who created the Work.
• "Work" means the copyrightable work of authorship offered under the terms of this License.
• "You" means an individual or entity exercising rights under this License who has not previously violated
the terms of this License with respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous violation.
• "License Elements" means the following high-level license attributes as selected by Licensor and
indicated in the title of this License: Attribution, ShareAlike.
2. Fair Use Rights
Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other
limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
3. License Grant
Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-
exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as
stated below:
12 de 15Página Intermediate Tutorial 1e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=273&page=Intermediate...
RaySceneQueries and Basic Mouse Usage (Part 1 of 2)
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Table of contents
Introduction
Prerequisites
Getting Started
Setting up the Scene
Introducing the FrameListener
Setting up the FrameListener
Adding Mouse Look
Terrain Collision Detection
Terrain Selection
Exercises for Further Study
Easy Exercises
Intermediate Exercises
Advanced Exercises
Exercises for Further Study
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Introduction
In this tutorial we will create the beginnings of a basic Scene Editor. During this process, we will cover:
1. How to use RaySceneQueries to keep the camera from falling through the terrain
2. How to use the MouseListener and MouseMotionListener interfaces
3. Using the mouse to select x and y coordinates on the terrain
Here is the code for Intermediate Tutorial 2. As you go through the tutorial you should be slowly adding code
to your own project and watching the results as we build it.
Prerequisites
This tutorial will assume that you already know how to set up an Ogre project and make it compile successfully.
Knowledge of basic Ogre objects (SceneNodes, Entities, etc) is assumed. You should also be familiar with
basic STL iterators, as this tutorial uses them. (Ogre also uses a lot of STL, if you are not familiar with it, you
should take the time to learn it.)
This Tutorial makes use of CEGUI, you should have completed the steps in the Basic Tutorial 7 CEGUI and
Ogre To ensure that this tutorial will work as expected.
1 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
NOTE: If you want to try the tutorial without using CEGUI you can find the source code using the built in
SdkTrays here.
Getting Started
First, you need to create a new project and add the following code:
#ifndef __ITutorial02_h_
#define __ITutorial02_h_
#include "BaseApplication.h"
class ITutorial02 : public BaseApplication
{
public:
ITutorial02(void);
virtual ~ITutorial02(void);
protected:
virtual void createScene(void);
virtual void chooseSceneManager(void);
virtual void createFrameListener(void);
//frame listener
virtual bool frameRenderingQueued(const Ogre::FrameEvent &evt);
//mouse listener
virtual bool mouseMoved(const OIS::MouseEvent &arg);
virtual bool mousePressed(const OIS::MouseEvent
&arg,OIS::MouseButtonID id);
virtual bool mouseReleased(const OIS::MouseEvent
&arg,OIS::MouseButtonID id);
protected:
Ogre::RaySceneQuery *mRaySceneQuery;// The ray scene query pointer
bool mLMouseDown, mRMouseDown; // True if the mouse
buttons are down
int mCount; // The
number of robots on the screen
Ogre::SceneNode *mCurrentObject; // The newly created object
CEGUI::Renderer *mGUIRenderer; // CEGUI renderer
float mRotateSpeed;
};
#endif // #ifndef __ITutorial02_h_
ITutorial02 header
ITutorial02 implementation
2 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
#include <CEGUISystem.h>
#include <CEGUISchemeManager.h>
#include <RendererModules/Ogre/CEGUIOgreRenderer.h>
#include "ITutorial02.h"
//-------------------------------------------------------------------------
------------
ITutorial02::ITutorial02(void)
{
}
//-------------------------------------------------------------------------
------------
ITutorial02::~ITutorial02(void)
{
}
//-------------------------------------------------------------------------
------------
void ITutorial02::createScene(void)
{}
void ITutorial02::createFrameListener(void)
{
BaseApplication::createFrameListener();
}
void ITutorial02::chooseSceneManager(void)
{
// Use the terrain scene manager.
mSceneMgr = mRoot->createSceneManager(Ogre::ST_EXTERIOR_CLOSE);
}
bool ITutorial02::frameRenderingQueued(const Ogre::FrameEvent &evt)
{return BaseApplication::frameRenderingQueued(evt);}
bool ITutorial02::mouseMoved(const OIS::MouseEvent &arg)
{return true;}
bool ITutorial02::mousePressed(const OIS::MouseEvent &arg,
OIS::MouseButtonID id)
{return true;}
bool ITutorial02::mouseReleased(const OIS::MouseEvent &arg,
OIS::MouseButtonID id)
{return true;}
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
3 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
ITutorial02 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception
has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() << std::endl;
#endif
}
return 0;
}
#ifdef __cplusplus
}
#endif
Be sure this code compiles before continuing. You will most likely need to configure your project to recognise
CEGUI since it is no longer packaged with Ogre as of Ogre 1.7, so remember to add to your project properties
(in Visual Studio):
• In C/C++, add the path to the CEGUI include files to Additional Include Directories,
• In the Linker options, add the path to CEGUI lib files to Additional Library Directories and add
"CEGUIOgreRenderer_d.lib" and "CEGUIBase_d.lib" to the Additional Dependencies (remove the _d for
the Release configuration)
• Add the path to the CEGUI bin directory to the Environment Variables or copy the CEGUI DLLs
somewhere your project can find them.
Similarly, add these to your project (in Eclipse):
Right click project -> Properties -> C/C++ Build -> Settings
-> GCC C++ Compiler -> Includes
and add the full path : /usr/local/include/CEGUI (on Linux)
-> GCC C++ Linker -> Linker -> Libraries
4 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
and this to Libraries (-l): CEGUIOgreRenderer
Setting up the Scene
Go to the ITutorial02::createScene method. The following code should all be familiar. If you do not
know what something does, please consult the Ogre API reference before continuing. Add this to createScene:
// Set ambient light
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
// World geometry
mSceneMgr->setWorldGeometry("terrain.cfg");
// Set camera look point
mCamera->setPosition(40, 100, 580);
mCamera->pitch(Ogre::Degree(-30));
mCamera->yaw(Ogre::Degree(-45));
Now that we have the basic world geometry set up, we need to turn on the cursor. We do this using some
CEGUI function calls. Before we can do that, however, we need to start up CEGUI. This is now very easy- the
bootstrapSystem() method will do all the required setup for us. Note that this also makes CEGUI use
Ogre's resource management system.
// CEGUI setup
mGUIRenderer = &CEGUI::OgreRenderer::bootstrapSystem();
Now we need to actually show the cursor. Again, I'm not going to explain most of this code. We will revisit it in a
later tutorial.
// Mouse
CEGUI::SchemeManager::getSingleton().create
((CEGUI::utf8*)"TaharezLook.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook",
"MouseArrow");
If you compile and run the code, you will see a cursor at the center of the screen, but it will not move (yet).
It is likely that you will also need to tell the Ogre resource manager about the CEGUI resources. If you get
exceptions at runtime, try adding the following to resources.cfg:
[CEGUI]
FileSystem=/usr/share/CEGUI/schemes
FileSystem=/usr/share/CEGUI/fonts
FileSystem=/usr/share/CEGUI/imagesets
5 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
FileSystem=/usr/share/CEGUI/layouts
FileSystem=/usr/share/CEGUI/looknfeel
FileSystem=/usr/share/CEGUI/lua_scripts
FileSystem=/usr/share/CEGUI/schemes
FileSystem=/usr/share/CEGUI/xml_schemas
Introducing the FrameListener
That was all that needed to be done for the application. The FrameListener is the complicated portion of the
code, so I will spend some time outlining what we are trying to accomplish with the application so you have an
idea before we start implementing it.
• First, we want to bind the right mouse button to a "mouse look" mode. It's fairly annoying not being able
to use the mouse to look around, so our first priority will be adding mouse control back to the program
(though only when we hold the right mouse button down). NOTE: the tutorial framework already handles
camera control thanks to the sdkCameraMan class from OgreBites but for the sake of learning we will
be implementing camera control from scratch.
• Second, we want to make it so that the camera does not pass through the Terrain. This will make it
closer to how we would expect a program like this to work.
• Third, we want to add entities to the scene anywhere on the terrain we left click.
• Finally, we want to be able to "drag" entities around; that is, by left clicking and holding the button down
we want to see the entity, and move it to where we want to place it. Letting go of the button will actually
lock it in place.
To do this we are going to use several protected variables (these are already added to the class):
Ogre::RaySceneQuery *mRaySceneQuery; // The ray scene query pointer
bool mLMouseDown, mRMouseDown; // True if the mouse buttons are
down
int mCount; // The number of robots on the
screen
Ogre::SceneNode *mCurrentObject; // The newly created object
CEGUI::Renderer *mGUIRenderer; // cegui renderer
float mRotateSpeed;
The mRaySceneQuery variable holds a copy of the RaySceneQuery we will be using to find the coordinates
on the terrain. The mLMouseDown and mRMouseDown variables will track whether we have the mouse held
down (IE mLMouseDown is true when the user holds down the left mouse button, false otherwise). mCount
counts the number of entities we have on screen. mCurrentObject holds a pointer to the most recently
created SceneNode (we will be using this to "drag" the entity around). Finally, mGUIRenderer holds a pointer
to the CEGUI Renderer, which we will be using to update CEGUI.
Also note that there are many functions related to Mouse listeners that we are overriding to provide camera
control
Setting up the FrameListener
6 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
Go to the createFrameListener method, and add the following initialization code after the call to
BaseApplication::createFrameListener(). Note that we are also reducing rotation speed of the
camera since the Terrain is fairly small.
// Setup default variables
mCount = 0;
mCurrentObject = NULL;
mLMouseDown = false;
mRMouseDown = false;
// Reduce rotate speed
mRotateSpeed =.1;
Finally, we need to create the RaySceneQuery object. This is done with a call to the SceneManager:
// Create RaySceneQuery
mRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray());
This is all we need for createFrameListener(), but if we create a RaySceneQuery, we must later destroy
it. Go to the ITutorial02 destructor (~ITutorial02) and add the following line:
// We created the query, and we are also responsible for deleting it.
mSceneMgr->destroyQuery(mRaySceneQuery);
Be sure you can compile your code before moving on to the next section.
Adding Mouse Look
We are going to bind the mouse look mode to the right mouse button. To do this, we are going to:
• update CEGUI when the mouse is moved (so that the cursor is also moved)
• set mRMouseButton to true when the right mouse button is pressed
• set mRMouseButton to false when it is released
• change the view when the mouse is "dragged" (that is, when a button is held down as the mouse
moves)
• hide the mouse cursor when the mouse is dragging
Find the ITutorial02::mouseMoved method. We will be adding code to move the mouse cursor every time
the mouse has been moved. Add this code to the function:
// Update CEGUI with the mouse motion
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel,
arg.state.Y.rel);
7 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
Now find the ITutorial02::mousePressed method. This chunk of code hides the cursor when the right
mouse button goes down, and sets the mRMouseDown variable to true.
// Left mouse button down
if (id == OIS::MB_Left)
{
mLMouseDown = true;
} // if
// Right mouse button down
else if (id == OIS::MB_Right)
{
CEGUI::MouseCursor::getSingleton().hide();
mRMouseDown = true;
} // else if
Next we need to show the mouse cursor again and toggle mRMouseDown when the right button is let up. Find
the mouseReleased function, and add this code:
// Left mouse button up
if (id == OIS::MB_Left)
{
mLMouseDown = false;
} // if
// Right mouse button up
else if (id == OIS::MB_Right)
{
CEGUI::MouseCursor::getSingleton().show();
mRMouseDown = false;
} // else if
Now we have all of the prerequisite code written, we want to change the view when the mouse is moved while
holding the right button down. What we are going to do is read the distance it has moved since the last time the
method was called. This is done in the same way that we rotated the camera in Basic Tutorial 5. Find the
ITutorial::mouseMoved function and add the following code just before the return statement:
// If we are dragging the left mouse button.
if (mLMouseDown)
{
} // if
// If we are dragging the right mouse button.
else if (mRMouseDown)
{
mCamera->yaw(Ogre::Degree(-arg.state.X.rel * mRotateSpeed));
8 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
mCamera->pitch(Ogre::Degree(-arg.state.Y.rel * mRotateSpeed));
} // else if
Now if you compile and run this code you will be able to control where the camera looks by holding the right
mouse button down.
Terrain Collision Detection
We are now going to make it so that when we move towards the terrain, we cannot pass through it. Since
BaseApplication::createFrameListener() already handles the camera movement, we are not going
to touch that code. Instead, after BaseApplication::createFrameListener() moves the camera we are
going to make sure the camera is 10 units above the terrain. If it is not, we are going to move it there. Please
follow this code closely. We will use the RaySceneQuery to do several other things by the time this tutorial is
finished, and I will not go into as much detail after this section.
Go to the ITutorial02::frameRenderingQueued() method and remove its contents. The first thing we
are going to do is call the BaseApplication::frameRenderingQueued method to do all of its normal
functions. If it returns false, we will return false as well.
// Process the base frame listener code. Since we are going to be
// manipulating the translate vector, we need this to happen
first.
if (!BaseApplication::frameRenderingQueued(evt))
return false;
We do this at the top of our frameRenderingQueued function because the
BaseApplication::frameRenderingQueued member function handles the updating of the TrayManager
window from OgreBites (the FPS window and Ogre logo) and we need to perform the rest of our actions in this
function after this happens. Our goal is to find the camera's current position, and fire a Ray straight down into
the terrain. This is called a RaySceneQuery, and it will tell us the height of the Terrain below us. After getting
the camera's current position, we need to create a Ray. A Ray takes in an origin (where the ray starts), and a
direction. In this case our direction will be NEGATIVE_UNIT_Y, since we are pointing the ray straight down.
Once we have created the ray, we tell the RaySceneQuery object to use it.
// Setup the scene query
Ogre::Vector3 camPos = mCamera->getPosition();
Ogre::Ray cameraRay(Ogre::Vector3(camPos.x, 5000.0f, camPos.z),
Ogre::Vector3::NEGATIVE_UNIT_Y);
mRaySceneQuery->setRay(cameraRay);
Note that we have used a height of 5000.0f instead of the camera's actual position. If we used the camera's Y
position instead of this height, we would miss the terrain entirely if the camera were under it. Now we need to
execute the query and get the results. The results of the query come in the form of an std::iterator, which
I will briefly describe.
9 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
// Perform the scene query
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin();
The result of the query is basically (oversimplification here) a list of worldFragments (in this case the Terrain)
and a list of movables (we will cover movables in a later tutorial). If you are not familiar with STL iterators, just
know that to get the first element of the iterator, call the begin method. If the result.begin() ==
result.end(), then there were no results to return. In the next tutorial we will have to deal with multiple
return values for SceneQuerys. For now, we'll just do some hand waving and move through it. The following
line of code ensures that the query returned at least one result ( itr != result.end() ), and that the result
is the terrain (itr->worldFragment).
// Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{
The worldFragment struct contains the location where the Ray hit the terrain in the singleIntersection
variable (which is a Vector3). We are going to get the height of the terrain by assigning the y value of this
vector to a local variable. Once we have the height, we are going to see if the camera is below the height, and if
so we are going to move the camera up to that height. Note that we actually move the camera up by 10 units.
This ensures that we can't see through the Terrain by being too close to it.
Ogre::Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition( camPos.x, terrainHeight + 10.0f,
camPos.z );
}
return true;
Lastly, we return true to continue rendering. At this point you should compile and test your program.
Terrain Selection
In this section we will be creating and adding objects to the screen every time you click the left mouse button.
Every time you click and hold the left mouse button, an object will be created and "held" on your cursor. You
can move the object around until you let go of the button, at which point it will lock into place. To do this we are
going to need to change the mousePressed function to do something different when you click the left mouse
button. Find the following code in the ITutorial02::mousePressed function. We will be adding code
inside this if statement.
// Left mouse button down
if (id == OIS::MB_Left)
{
mLMouseDown = true;
} // if
10 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
The first piece of code will look very familiar. We will be creating a Ray to use with the mRaySceneQuery
object, and setting the Ray. Ogre provides us with Camera::getCameraToViewportRay; a nice function
that translates a click on the screen (x and y coordinates) into a Ray that can be used with a RaySceneQuery
object.
// Left mouse button down
if (id == OIS::MB_Left)
{
// Setup the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton
().getPosition();
Ogre::Ray mouseRay = mCamera->getCameraToViewportRay
(mousePos.d_x/float(arg.state.width), mousePos.d_y/float
(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
Next we will execute the query and make sure it returned a result.
// Execute query
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute
();
Ogre::RaySceneQueryResult::iterator itr = result.begin( );
// Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
Now that we have the worldFragment (and therefore the position that was clicked on), we are going to create
the object and place it on that position. Our first difficulty is that each Entity and SceneNode in ogre needs a
unique name. To accomplish this we are going to name each Entity "Robot1", "Robot2", "Robot3"... and each
SceneNode "Robot1Node", "Robot2Node", "Robot3Node"... and so on. First we create the name (consult a
reference on C for more information on sprintf).
char name[16];
sprintf( name, "Robot%d", mCount++ );
Next we create the Entity and SceneNode. Note that we use itr->worldFragment-
>singleIntersection for our default position of the Robot. We also scale him down to 1/10th size because
of how small the terrain is. Be sure to take note that we are assigning this newly created object to the member
variable mCurrentObject. We will be using that in the next section.
Ogre::Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()-
>createChildSceneNode(std::string(name) + "Node", itr->worldFragment-
11 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
>singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
mLMouseDown = true;
} // if
Now compile and run the demo. You can now place Robots on the scene by clicking anywhere on the Terrain.
We have almost completed our program, but we need to implement object dragging before we are finished. We
will be adding code inside this if statement:
// If we are dragging the left mouse button.
if (mLMouseDown)
{
} // if
This next chunk of code should now be self explanatory. We create a Ray based on the mouse's current
location, then execute a RaySceneQuery and move the object to the new position. Note that we don't have to
check mCurrentObject to see if it's the latest object or not, because mLMouseDown and mCurrentObject
are both set in the mousePressed() function: mLMouseDown is set to true, and mCurrentObject is set to
the most recently created SceneNode.
if (mLMouseDown)
{
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton
().getPosition();
Ogre::Ray mouseRay = mCamera->getCameraToViewportRay
(mousePos.d_x/float(arg.state.width),mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin();
if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment-
>singleIntersection);
} // if
Compile and run the program. We are now finished!
Note: You (= the Ray's origin) must be over the Terrain for the RaySceneQuery to report the intersection
when using the TerrainSceneManager.
Note: If you are using your own framework, make sure your scene query has access to the frame listener,
e.g. your frameStarted() method. Otherwise, if you use it in an init() function you may get no results.
12 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
Exercises for Further Study
Easy Exercises
1. To keep the camera from looking through the terrain, we chose 10 units above the Terrain. This
selection was arbitrary. Could we improve on this number and get closer to the Terrain without going
through it? If so, make this variable a static class member and assign it there.
2. We sometimes do want to pass through the terrain, especially in a SceneEditor. Create a flag which
turns toggles collision detection on and off, and bind this to a key on the keyboard. Be sure you do not
make a SceneQuery in frameStarted if collision detection is turned off.
Intermediate Exercises
1. We are currently doing the SceneQuery every frame, regardless of whether or not the camera has
actually moved. Fix this problem and only do a SceneQuery if the camera has moved. (Hint: Find the
translation vector in ExampleFrameListener, after the function is called test it against Vector3::ZERO.)
Advanced Exercises
1. Notice that there is a lot of code duplication every time we make a scene query call. Wrap all of the
SceneQuery related functionality into a protected function. Be sure to handle the case where the Terrain
is not intersected at all.
Exercises for Further Study
1. In this tutorial we used RaySceneQueries to place objects on the Terrain. We could have used it for
many other purposes. Take the code from Tutorial 1 and complete Difficult Question 1 and Expert
Question 1. Then merge that code with this one so that the Robot now walks on the terrain instead of
empty space.
2. Add code so that every time you click on a point on the scene, the robot moves to that location.
Proceed to Intermediate Tutorial 3 Mouse Picking (3D Object Selection) and SceneQuery Masks
Alias: Intermediate_Tutorial_2
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
13 de 17Página Intermediate Tutorial 2e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=274&page=Intermediate...
Mouse Picking (3D Object Selection) and SceneQuery Masks (Part 2 of 2)
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Introduction
In this tutorial we will continue the work on the previous tutorial. We will be covering how to select any object on
the screen using the mouse, and how to restrict what is selectable.
You can find the code for this tutorial here. As you go through the demo you should be slowly adding code to
your own project and watching the results as we build it.
NOTE: If you want to try the tutorial without using CEGUI you can find the source code using the built in
SdkTrays here.
Table of contents
Introduction
Prerequisites
Getting Started
Showing Which Object is Selected
Adding Ninjas
Selecting Objects
Query Masks
Query Type Masks
More on Masks
Setting a MovableObject's Mask
Querying for Multiple Masks
Querying for Everything but a Mask
Selecting all Objects or No Objects
Exercises
Easy Exercises
Intermediate Exercises
Advanced Exercises
Exercises for Further Study
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Prerequisites
This tutorial will assume that you have gone through the previous tutorial using the Ogre Tutorial Framework.
We will also be using STL iterators to go through multiple results of SceneQueries, so basic knowledge of them
will be helpful.
1 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
Getting Started
Now if you want to follow this tutorial completely, or you just copy and paste, make sure your .h file looks
something like this:
#ifndef __IntermediateTutorial3_h_
#define __IntermediateTutorial3_h_
#include "BaseApplication.h"
#include <CEGUISystem.h>
#include <CEGUISchemeManager.h>
#include <RendererModules/Ogre/CEGUIOgreRenderer.h>
class IntermediateTutorial3 : public BaseApplication
{
public:
IntermediateTutorial3(void);
virtual ~IntermediateTutorial3(void);
protected:
virtual void createScene(void);
virtual void chooseSceneManager(void);
virtual void createFrameListener(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent& arg);
virtual bool mouseMoved(const OIS::MouseEvent& arg);
virtual bool mousePressed(const OIS::MouseEvent& arg,
OIS::MouseButtonID id);
virtual bool mouseReleased(const OIS::MouseEvent& arg,
OIS::MouseButtonID id);
Ogre::SceneNode *mCurrentObject; //pointer to our currently
selected object
Ogre::RaySceneQuery* mRayScnQuery; //pointer to our ray scene
query
CEGUI::Renderer* mGUIRenderer; //our CEGUI renderer
bool bLMouseDown, bRMouseDown; //true if mouse buttons are held
down
int mCount; //number
of objects created
float mRotateSpeed; //the rotation
speed for the camera
};
#endif // #ifndef __IntermediateTutorial3_h_
2 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
You should also make sure IntermediateTutorial3.cpp looks like this. If you went through the previous tutorial
and are continuing on from it, your code should already look something like this. If it doesn't, make sure that it
does and that it compiles and runs successfully. The only thing different should be the comments and some of
the variable initialization has been moved to the constructor.
#include "IntermediateTutorial3.h"
//-------------------------------------------------------------------------
------------
IntermediateTutorial3::IntermediateTutorial3(void):
mCount(0),
mCurrentObject(0),
bLMouseDown(false),
bRMouseDown(false),
mRotateSpeed(0.1f)
{
}
//-------------------------------------------------------------------------
------------
IntermediateTutorial3::~IntermediateTutorial3(void)
{
mSceneMgr->destroyQuery(mRayScnQuery);
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial3::createScene(void)
{
//Scene setup
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5f, 0.5f, 0.5f));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
//World geometry
mSceneMgr->setWorldGeometry("terrain.cfg");
//Camera setup
mCamera->setPosition(40, 100, 580);
mCamera->pitch(Ogre::Degree(-30));
mCamera->yaw(Ogre::Degree(-45));
//CEGUI setup
mGUIRenderer = &CEGUI::OgreRenderer::bootstrapSystem();
//show the CEGUI cursor
CEGUI::SchemeManager::getSingleton().create
((CEGUI::utf8*)"TaharezLook.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook",
"MouseArrow");
}
3 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
void IntermediateTutorial3::chooseSceneManager(void)
{
//create a scene manager that is meant for handling outdoor scenes
mSceneMgr = mRoot->createSceneManager(Ogre::ST_EXTERIOR_CLOSE);
}
void IntermediateTutorial3::createFrameListener(void)
{
//we still want to create the frame listener from the base app
BaseApplication::createFrameListener();
//but we also want to set up our raySceneQuery after everything
has been initialized
mRayScnQuery = mSceneMgr->createRayQuery(Ogre::Ray());
}
bool IntermediateTutorial3::frameRenderingQueued(const Ogre::FrameEvent&
arg)
{
//we want to run everything in the previous frameRenderingQueued
call
//but we also want to do something afterwards, so lets start off
with this
if(!BaseApplication::frameRenderingQueued(arg))
{
return false;
}
/*
This next big chunk basically sends a raycast straight down from
the camera's position
It then checks to see if it is under world geometry and if it is
we move the camera back up
*/
Ogre::Vector3 camPos = mCamera->getPosition();
Ogre::Ray cameraRay(Ogre::Vector3(camPos.x, 5000.0f, camPos.z),
Ogre::Vector3::NEGATIVE_UNIT_Y);
mRayScnQuery->setRay(cameraRay);
Ogre::RaySceneQueryResult& result = mRayScnQuery->execute();
Ogre::RaySceneQueryResult::iterator iter = result.begin();
if(iter != result.end() && iter->worldFragment)
{
Ogre::Real terrainHeight = iter->worldFragment-
>singleIntersection.y;
if((terrainHeight + 10.0f) > camPos.y)
{
4 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
mCamera->setPosition(camPos.x, terrainHeight +
10.0f, camPos.z);
}
}
return true;
}
bool IntermediateTutorial3::mouseMoved(const OIS::MouseEvent& arg)
{
//updates CEGUI with mouse movement
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel,
arg.state.Y.rel);
//if the left mouse button is held down
if(bLMouseDown)
{
//find the current mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton
().getPosition();
//create a raycast straight out from the camera at the
mouse's location
Ogre::Ray mouseRay = mCamera->getCameraToViewportRay
(mousePos.d_x/float(arg.state.width), mousePos.d_y/float
(arg.state.height));
mRayScnQuery->setRay(mouseRay);
Ogre::RaySceneQueryResult& result = mRayScnQuery->execute
();
Ogre::RaySceneQueryResult::iterator iter = result.begin();
//check to see if the mouse is pointing at the world and
put our current object at that location
if(iter != result.end() && iter->worldFragment)
{
mCurrentObject->setPosition(iter->worldFragment-
>singleIntersection);
}
}
else if(bRMouseDown) //if the right mouse button is held down,
be rotate the camera with the mouse
{
mCamera->yaw(Ogre::Degree(-arg.state.X.rel *
mRotateSpeed));
mCamera->pitch(Ogre::Degree(-arg.state.Y.rel *
mRotateSpeed));
}
return true;
}
5 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
bool IntermediateTutorial3::mousePressed(const OIS::MouseEvent& arg,
OIS::MouseButtonID id)
{
if(id == OIS::MB_Left)
{
//find the current mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton
().getPosition();
//then send a raycast straight out from the camera at the
mouse's position
Ogre::Ray mouseRay = mCamera->getCameraToViewportRay
(mousePos.d_x/float(arg.state.width), mousePos.d_y/float
(arg.state.height));
mRayScnQuery->setRay(mouseRay);
/*
This next chunk finds the results of the raycast
If the mouse is pointing at world geometry we spawn a
robot at that position
*/
Ogre::RaySceneQueryResult& result = mRayScnQuery->execute
();
Ogre::RaySceneQueryResult::iterator iter = result.begin();
if(iter != result.end() && iter->worldFragment)
{
char name[16];
sprintf(name, "Robot%d", mCount++);
Ogre::Entity* ent = mSceneMgr->createEntity(name,
"robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()-
>createChildSceneNode(std::string(name) + "Node", iter->worldFragment-
>singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
}
bLMouseDown = true;
}
else if(id == OIS::MB_Right) // if the right mouse button is
held we hide the mouse cursor for view mode
{
CEGUI::MouseCursor::getSingleton().hide();
bRMouseDown = true;
}
return true;
}
6 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
bool IntermediateTutorial3::mouseReleased(const OIS::MouseEvent& arg,
OIS::MouseButtonID id)
{
if(id == OIS::MB_Left)
{
bLMouseDown = false;
}
else if(id == OIS::MB_Right) //when the right mouse is released
we then unhide the cursor
{
CEGUI::MouseCursor::getSingleton().show();
bRMouseDown = false;
}
return true;
}
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine,
INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
IntermediateTutorial3 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(),
"An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() <<
std::endl;
#endif
}
return 0;
7 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
}
#ifdef __cplusplus
}
#endif
Be sure you can compile and run this code before continuing. It should work the exact same as in the last
tutorial.
Showing Which Object is Selected
In this tutorial we will be making it so that you can "pick up" and move objects after you have placed them. We
would like to allow the user to know which object is currently being manipulated. In a game, we would probably
like to create a special way of highlighting the object, but for our tutorial (and for your applications before they
are release-ready), you can use the showBoundingBox method to create a box around objects.
Our basic idea is to disable the bounding box on the old current object when the mouse is first clicked, then
enable the bounding box as soon as we have the new object. To do this, we will add the following code to the
beginning of the mousePressed() function, inside the first if statement:
//show that the current object has been deselected by removing the bounding
box visual
if(mCurrentObject)
{
mCurrentObject->showBoundingBox(false);
}
Then add the following code near the end of the mousePressed() function, just before the return statement:
//now we show the bounding box so the user can see that this object is
selected
if(mCurrentObject)
{
mCurrentObject->showBoundingBox(true);
}
Now the mCurrentObject is always highlighted on the screen.
Adding Ninjas
We now want to modify the code to not just support robots, but also placing and moving ninjas. We will have a
"Robot Mode" and a "Ninja Mode" that will determine which object we are placing on the screen. Let's make the
space key be our mode switching button.
First, we will set up the IntermediateTutorial to be in Robot Mode from the beginning. We need to add a
variable to hold the state of the object (that is, if we are placing robots or ninjas). Go to the protected section of
IntermediateTutorial and add this variable:
bool bRobotMode; // The current state
Now add this code to the into the IntermediateTutorial3 constructor:
8 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
// Set the default state
bRobotMode = true;
This puts us in Robot Mode! If only it were that simple. Now we need to create either a Robot or a Ninja mesh
based on the bRobotMode variable. Locate this code in mousePressed:
char name[16];
sprintf(name, "Robot%d", mCount++);
Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
The replacement should be straight forward. Depending on the bRobotMode state we either put out a Robot or
a Ninja, and name it accordingly:
Entity *ent;
char name[16];
if (bRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
Now we are almost done. The only thing left to do is to bind the spacebar to change states. Go back to
IntermediateTutorial3.h and add an override to the keyPressed function like this:
virtual bool keyPressed(const OIS::KeyEvent& arg);
Now in IntermediateTutorial3.cpp let's fill in the function. We need to add functionality for the space bar, but we
also want to keep the functionality of the BaseApplication. So how do we do this? Easy, it's actually only a few
lines:
bool IntermediateTutorial3::keyPressed(const OIS::KeyEvent& arg)
{
//check and see if the spacebar was hit, and this will switch
which mesh is spawned
if(arg.key == OIS::KC_SPACE)
{
bRobotMode = !bRobotMode;
}
//then we return the base app keyPressed function so that we get
9 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
all of the functionality
//and the return value in one line
return BaseApplication::keyPressed(arg);
}
You see, we check for the space bar and switch the mode we are in. We also call the BaseApplication's
keyPressed() function within the return statement, that way we check everything programmed before, plus
we get its return value.
Now we are done! Compile and run the demo. You can now choose which object you place by using the space
bar.
Selecting Objects
Now we are going to dive into the meat of this tutorial: using RaySceneQueries to select objects on the screen.
Before we start making changes to the code I will first explain a RaySceneQueryResultEntry in more detail.
(Please follow the link and briefly look at the struct.)
The RaySceneQueryResult returns an iterator of RaySceneQueryResultEntry structs. This struct contains three
variables. The distance variable tells you how far away the object is along the ray. One of the other two
variables will be non-null. The movable variable will contain a MovableObject if the Ray intersected one.
The worldFragment will contain a WorldFragment object if it hit a world fragment (like the terrain).
MovableObjects are basically any object you would attach to a SceneNode (such as Entities, Lights, etc). See
the inheritance tree on this page to find out what type of objects would be returned. Most normal
applications of RaySceneQueries will involve selecting and manipulating either the MovableObject you have
clicked on, or the SceneNodes they are attached to. To get the name of the MovableObject, call the getName
method. To get the SceneNode (or Node) the object is attached to, call getParentSceneNode (or
getParentNode). The movable variable in a RaySceneQueryResultEntry will be equal to NULL if the result is
not a MovableObject.
The WorldFragment is a different beast all together. When the worldFragment member of a
RaySceneQueryResult is set, it means that the result is part of the world geometry created by the
SceneManager. The type of world fragment that is returned is based on the SceneManager. The way this is
implemented is that the WorldFragment struct contains the fragmentType variable which specifies the type
of world fragment it contains. Based on the fragmentType variable, one of the other variables will be set
(singleIntersection, planes, geometry, or renderOp ). Generally speaking, RaySceneQueries only return
WFT_SINGLE_INTERSECTION WorldFragments. The singleIntersection variable is simply a Vector3 reporting
the location of the intersection. Other types of world fragments are beyond the scope of this tutorial.
Now let's look at an example. Let's say we wanted to print out the results of a RaySceneQuery. The following
code would do this. (Assume that the fout object is of type ofstream, and that it has already been created
using the open() method.)
// Do not add this code to the program, just read along:
RaySceneQueryResult &result = mRayScnQuery->execute();
RaySceneQueryResult::iterator itr;
// loop through the results
for ( itr = result.begin( ); itr != result.end(); itr++ )
{
// Is this result a WorldFragment?
10 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
if ( itr->worldFragment )
{
Vector3 location = itr->worldFragment->singleIntersection;
fout << "WorldFragment: (" << location.x << ", " << location.y <<
", " << location.z << ")" << endl;
} // if
// Is this result a MovableObject?
else if ( itr->movable )
{
fout << "MovableObject: " << itr->movable->getName() << endl;
} // else if
} // for
This would print out the names of all MovableObjects that the ray intersects, and it would print the location of
where it intersected the world geometry (if it did hit it). Note that this can sometimes act in strange ways. For
example, if you are using the TerrainSceneManager, the origin of the Ray you fire must be over the Terrain or
the intersection query will not register it as a hit. Different scene managers implement RaySceneQueries in
different ways. Be sure to experiment with it when you use it with a new SceneManager.
Now, if we look back at our RaySceneQuery code, something should jump out at you: we are not looping
through all the results! In fact we are only looking at the first result, which (in the case of the
TerrainSceneManager) is the world geometry. This is bad, since we cannot be sure that the
TerrainSceneManager will always return the world geometry first. We need to loop through the results to make
sure we are finding what we are looking for. Another thing that we want to do is to "pick up" and drag objects
that have already been placed. Currently if you click on an object that has already been placed, the program
ignores it and places a robot behind it. Let's fix that now.
Go to the mousePressed function in the code. We first want to make sure that when we click, we get the first
thing along the Ray. To do that, we need to set the RaySceneQuery to sort by depth. Find this code in the
mousePressed() function:
// Set up the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float
(arg.state.width), mousePos.d_y/float(arg.state.height));
mRayScnQuery->setRay(mouseRay);
// Execute query
RaySceneQueryResult &result = mRayScnQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();
And change it to this:
// Set up the ray scene query
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float
(arg.state.width), mousePos.d_y/float(arg.state.height));
mRayScnQuery->setRay(mouseRay);
mRayScnQuery->setSortByDistance(true);
11 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
// Execute query
RaySceneQueryResult &result = mRayScnQuery->execute();
RaySceneQueryResult::iterator iter = result.begin();
Now that we are returning the results in order, we need to update the query results code. We are going to
rewrite that section, so remove this code:
// Get results, create a node/entity on the position
if (iter != result.end() && iter->worldFragment)
{
Entity *ent;
char name[16];
if (bRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
mCurrentObject = mSceneMgr->getRootSceneNode()-
>createChildSceneNode(String(name) + "Node", iter->worldFragment-
>singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
We want to make it so that we can select objects that are already placed on the screen. Our strategy is going to
have two parts. First, if the user clicks on an object, then make mCurrentObject refer to its parent SceneNode.
If the user does not click on an object (if he clicks on the terrain instead), place a new Robot there like we did
before. The first change is that we will be using a for loop instead of an if statement:
// Get results, create a node/entity on the position
for ( iter; iter != result.end(); iter++ )
{
First we will check if the first intersection is a MovableObject, if so we'll assign mCurrentObject to be its parent
SceneNode. There is a catch though. The TerrainSceneManager creates MovableObjects for the terrain itself,
so we might actually be intersecting one of the tiles. In order to fix that, I check the name of the object to make
sure that it does not resemble a terrain tile name; a sample tile name would be "tile[0][0,2]" . Finally, notice the
break statement. We only need to act on the first object, so as soon as we find a valid one we need to get out
of the for loop altogether.
if (iter->movable && iter->movable->getName().substr(0, 5) != "tile[")
{
mCurrentObject = iter->movable->getParentSceneNode();
12 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
break;
} // if
Next, we will check to see if the intersection returned a WorldFragment.
else if (iter->worldFragment)
{
Entity *ent;
char name[16];
Now we are either going to create a Robot entity or a Ninja entity based on mRobotState. After creating the
entity we will create the SceneNode and break out of the for loop.
if (bRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
mCurrentObject = mSceneMgr->getRootSceneNode()-
>createChildSceneNode(String(name) + "Node", iter->worldFragment-
>singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
break;
} // else if
} // for
Believe it or not, that's all that has to be done! Compile and play with the code. Now we create the correct type
of object when we click on terrain, and when we click on an object we will see the bounding box (no dragging it
around right now, this comes in the next step). One valid question is: since we only want the first intersection,
and since we sorted by depth, why not just use an if statement? The main reason is to have a fall-through if the
first returned object is one of those pesky tiles. We have to loop until we find something other than a tile or we
hit the end of the list.
Now that we are on the subject, we need to also update the RaySceneQuery code in other locations. In both
the frameRenderingQueued and the mouseMoved functions we only need to find the Terrain. There is no
reason to sort the results because the terrain will be at the end of the sorted list (so we are going to turn sorting
off). We still want to loop through the results though, just in case the TerrainSceneManager is changed at a
future date to not return the terrain first. First, find this section of code in frameRenderingQueued:
// Perform the scene query
Ogre::RaySceneQueryResult &result = mRayScnQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin();
13 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
// Get the results, set the camera height
if (iter != result.end() && iter->worldFragment)
{
Ogre::Real terrainHeight = iter->worldFragment-
>singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
{
mCamera->setPosition(camPos.x, terrainHeight + 10.0f,
camPos.z);
}
}
Then replace it with this:
// Perform the scene query
mRayScnQuery->setSortByDistance(false);
Ogre::RaySceneQueryResult &result = mRayScnQuery->execute();
Ogre::RaySceneQueryResult::iterator iter = result.begin();
// Get the results, set the camera height
for (iter; iter != result.end(); iter++)
{
if (iter->worldFragment)
{
Ogre::Real terrainHeight = iter->worldFragment-
>singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
{
mCamera->setPosition(camPos.x, terrainHeight +
10.0f, camPos.z);
}
break;
} // if
} // for
This should be self explanatory. We add a line to turn off sorting, then we convert the if statement into a for
loop, and break as soon as we have found the position we are looking for. We will do the exact same thing with
the mouseMoved function. Find this section of code in mouseMoved():
mRayScnQuery->setRay(mouseRay);
Ogre::RaySceneQueryResult &result = mRayScnQuery->execute();
Ogre::RaySceneQueryResult::iterator iter = result.begin();
if (iter != result.end() && iter->worldFragment)
{
mCurrentObject->setPosition(iter->worldFragment-
>singleIntersection);
}
14 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
And replace it with this code:
mRayScnQuery->setRay(mouseRay);
mRayScnQuery->setSortByDistance(false);
Ogre::RaySceneQueryResult &result = mRayScnQuery->execute();
Ogre::RaySceneQueryResult::iterator iter = result.begin();
for (iter; iter != result.end(); iter++)
{
if (iter->worldFragment)
{
mCurrentObject->setPosition(iter->worldFragment-
>singleIntersection);
break;
} // if
}
Compile and test the code. There shouldn't be any noticeable difference since the last time we ran the code,
but now we are doing it the correct way, and future updates to the TerrainSceneManager will not break our
code.
Query Masks
Notice that no matter what mode we are in we can select either object. Our RaySceneQuery will return either
Robots or Ninjas, whichever is in front. It doesn't have to be this way though. All MovableObjects allow you to
set a mask value for them, and SceneQueries allow you to filter your results based on this mask. All masks are
done using the binary AND operation, so if you are unfamiliar with this, you should brush up on it before
continuing.
The first thing we are going to do is create the mask values. Go to the very beginning of the
IntermediateTutorial3 class and add this after the public statement:
enum QueryFlags
{
NINJA_MASK = 1<<0,
ROBOT_MASK = 1<<1
};
This creates an enum with two values, which in binary are 0001 and 0010. Now, every time we create a Robot
entity, we call its "setMask" function to set the query flags to be ROBOT_MASK. Every time we create a Ninja
entity we call its "setMask" function and use NINJA_MASK instead. Now, when we are in Ninja mode, we will
make the RaySceneQuery only consider objects with the NINJA_MASK flag, and when we are in Robot mode
we will make it only consider ROBOT_MASK.
Find this section of mousePressed:
if (bRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
15 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
We will add two lines to set the mask on both of them:
if (bRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
ent->setQueryFlags(ROBOT_MASK);
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
ent->setQueryFlags(NINJA_MASK);
} // else
We still need to make it so that when we are in one mode, we can only click and drag objects of the
corresponding type. We need to set the query flags so that only the correct object type can be selected. We
accomplish this by setting the query mask in the RaySceneQuery to be the ROBOT_MASK in Robot mode, and
set it to NINJA_MASK in Ninja mode. Find this code in the mousePressed() function:
mRayScnQuery->setSortByDistance(true);
Add this line of code after it:
mRayScnQuery->setQueryMask(bRobotMode ? ROBOT_MASK : NINJA_MASK);
Compile and run the tutorial. We now select only the objects we are looking for. All rays that pass through other
objects go through them and hit the correct object. We are now finished working on this code. The next section
will not be modifying it.
Query Type Masks
There's one more thing to consider when using scene queries. Suppose you added a billboardset or a particle
system to your scene above, and you want to move it around. You will find that the query never returns the
billboardset that you click on. This is because the SceneQuery has another mask, the QueryTypeMask, that
limits you to selecting only the type specified as the flag. By default when you do a query, it returns only objects
of entity type.
In your code, if you want your query to return BillboardSets or ParticleSystems, you'll have to do this first before
executing your query:
mRayScnQuery->setQueryTypeMask(SceneManager::FX_TYPE_MASK);
16 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
Now the query will only return BillboardSets or ParticleSystems as results.
There are 6 types of QueryTypeMask defined in the SceneManager class as static members:
WORLD_GEOMETRY_TYPE_MASK //Returns world geometry.
ENTITY_TYPE_MASK //Returns entities.
FX_TYPE_MASK //Returns billboardsets / particle systems.
STATICGEOMETRY_TYPE_MASK //Returns static geometry.
LIGHT_TYPE_MASK //Returns lights.
USER_TYPE_MASK_LIMIT //User type mask limit.
The default QueryTypeMask when the property is not set manually is ENTITY_TYPE_MASK.
More on Masks
Our mask example is very simple, so I would like to go through a few more complex examples.
Setting a MovableObject's Mask
Every time we want to create a new mask, the binary representation must contain only one 1 in it. That is, these
are valid masks:
00000001
00000010
00000100
00001000
00010000
00100000
01000000
10000000
And so on. We can very easily create these values by taking 1 and bitshifting them by a position value. That is:
00000001 = 1<<0
00000010 = 1<<1
00000100 = 1<<2
00001000 = 1<<3
00010000 = 1<<4
00100000 = 1<<5
01000000 = 1<<6
10000000 = 1<<7
All the way up to 1<<31. This gives us 32 distinct masks we can use for MovableObjects.
Querying for Multiple Masks
We can query for multiple masks by using the bitwise OR operator. Let's say we have three different groups of
objects in a game:
enum QueryFlags
{
17 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
FRIENDLY_CHARACTERS = 1<<0,
ENEMY_CHARACTERS = 1<<1,
STATIONARY_OBJECTS = 1<<2
};
Now, if we wanted to query for only friendly characters we could do:
mRayScnQuery->setQueryMask(FRIENDLY_CHARACTERS);
If we want the query to return both enemy characters and stationary objects, we would use:
mRayScnQuery->setQueryMask(ENEMY_CHARACTERS | STATIONARY_OBJECTS);
If you use a lot of these types of queries, you might want to define this in the enum:
OBJECTS_ENEMIES = ENEMY_CHARACTERS | STATIONARY_OBJECTS
And then simply use OBJECTS_ENEMIES to query.
Querying for Everything but a Mask
You can also query for anything other than a mask using the bit inversion operator, like so:
mRayScnQuery->setQueryMask(~FRIENDLY_CHARACTERS);
Which will return everything other than friendly characters. You can also do this for multiple masks:
mRayScnQuery->setQueryMask(~(FRIENDLY_CHARACTERS | STATIONARY_OBJECTS));
Which would return everything other than friendly characters and stationary objects.
Selecting all Objects or No Objects
You can do some very interesting stuff with masks. The thing to remember is, if you set the query mask QM for
a SceneQuery, it will match all MovableObjects that have the mask OM if (QM & OM) contains at least one 1.
Thus, setting the query mask for a SceneQuery to 0 will make it return no MovableObjects. Setting the query
mask to ~0 (0xFFFFF...) will make it return all MovableObjects that do not have a 0 query mask.
Using a query mask of 0 can be highly useful in some situations. For example, the TerrainSceneManager does
not use QueryMasks when it returns a worldFragment. By doing this:
mRayScnQuery->setQueryMask(0);
You will get ONLY the worldFragment in your RaySceneQueries for that SceneManager. This can be very
useful if you have a lot of objects on screen and you do not want to waste time looping through all of them if
you only need to look for the Terrain intersection.
18 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
Exercises
Easy Exercises
1. The TerrainSceneManager creates tiles with a default mask of ~0 (all queries select it). We fixed this
problem by testing to see if the name of the movable object equaled "tile00,2". Even though it's not
implemented yet, the TerrainSceneManager supports multiple pages, and if there were more things than
just "tile00,2" this would cause our code to break down. Instead of making the test in the loop, fix the
problem properly by setting all of the tile objects created by the TerrainSceneManager to have a unique
mask. (Hint: The TerrainSceneManager creates a SceneNode called "Terrain" which contains all of
these tiles. Loop through them and set the attached object's masks to something of your choosing.)
Intermediate Exercises
1. Our program delt with two things, Robots and Ninjas. If we were going to implement a scene editor, we
would want to place any number of different object types. Generalize this code to allow the placement of
any type of object from a predefined list. Create an overlay with the list of objects you want the editor to
have (such as Ninjas, Robots, Knots, Ships, etc), and have the SceneQueries only select that type of
object.
2. Since we are using multiple types of objects now, use the Factory Pattern to properly create the
SceneNodes and Entities.
Advanced Exercises
1. Generalize the previous exercises to read in all of the meshes that Ogre knows about (IE everything that
was parsed in from the Media directory), and give the ability to place them. Note that there should not be
a limit as to how many types of objects ogre can place. Since you only have 32 unique query masks to
use, you may need to come up with a way to quickly change all of the query flags for objects on the
screen.
2. You might have noticed that when you click on an object, the object is "lifted" from the bottom of the
bounding box. To see this, click on the top of any character and move him. He will be transported
instantly elsewhere. Modify the program to fix this problem.
Exercises for Further Study
1. Add a way to select multiple objects to the program such that when you hold the Ctrl key and click
multiple objects are highlighted. When you move these objects, move all of them as a group.
2. Many scene editing programs allow you to group objects so that they are always moved together.
Implement this in the program.
Proceed to Intermediate Tutorial 4 Volume Selection and Basic Manual Objects
Alias: Intermediate_Tutorial_3
19 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR
COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED
HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
• "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the
Work in its entirety in unmodified form, along with a number of other contributions, constituting separate
and independent works in themselves, are assembled into a collective whole. A work that constitutes a
Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this
License.
• "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works,
such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound
recording, art reproduction, abridgment, condensation, or any other form in which the Work may be
recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be
considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the
Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with
a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
• "Licensor" means the individual or entity that offers the Work under the terms of this License.
• "Original Author" means the individual or entity who created the Work.
• "Work" means the copyrightable work of authorship offered under the terms of this License.
• "You" means an individual or entity exercising rights under this License who has not previously violated
the terms of this License with respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous violation.
• "License Elements" means the following high-level license attributes as selected by Licensor and
indicated in the title of this License: Attribution, ShareAlike.
2. Fair Use Rights
Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other
limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
3. License Grant
Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-
exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as
stated below:
• to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the
Work as incorporated in the Collective Works;
• to create and reproduce Derivative Works;
• to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means
of a digital audio transmission the Work including as incorporated in Collective Works;
• to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means
of a digital audio transmission Derivative Works.
• For the avoidance of doubt, where the work is a musical composition:
20 de 23Página Intermediate Tutorial 3e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=275&page=Intermediate...
Volume Selection and Basic Manual Objects
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Table of contents
Introduction
Prerequisites
ManualObjects
A Crash Course in 3D Objects
Introduction
Selection Box
The Code
Volume Selection
Setup
Mouse Handlers
PlaneBoundedVolumeListSceneQuery
A Final Note About Bounding Boxes
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Introduction
In this tutorial we will be covering how to do volume selection. The idea is that when you click and drag the
mouse across the screen, a white rectangle will trace the area you are selecting. When you let go of the
mouse, all objects within the selection area will be highlighted. In order to accomplish this we will be learning
how to use two objects: ManualObject (to create the rectangle) and PlaneBoundedVolumeListSceneQuery.
Note that while we will cover some basic uses of ManualObject, this is only an introduction to it, and not a
tutorial on how to completely create 3D objects with it. We will only cover what we need to use.
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.
Prerequisites
Make sure you have the Basic Tutorial Framework, which you should at this point. Then make sure that your
application header and cpp file look like this:
IntermediateTutorial4.h
#ifndef __IntermediateTutorial4_h_
#define __IntermediateTutorial4_h_
#include "BaseApplication.h"
1 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
#include <CEGUI.h>
#include <CEGUISchemeManager.h>
#include <RendererModules/Ogre/CEGUIOgreRenderer.h>
class IntermediateTutorial4 : public BaseApplication
{
public:
IntermediateTutorial4(void);
virtual ~IntermediateTutorial4(void);
protected:
virtual void createScene(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
virtual bool mouseMoved(const OIS::MouseEvent& arg);
virtual bool mousePressed(const OIS::MouseEvent& arg,
OIS::MouseButtonID id);
virtual bool mouseReleased(const OIS::MouseEvent& arg,
OIS::MouseButtonID id);
void performSelection(const Ogre::Vector2& first, const
Ogre::Vector2& second);
void deselectObjects();
void selectObject(Ogre::MovableObject* obj);
private:
Ogre::Vector2 mStart, mStop;
Ogre::PlaneBoundedVolumeListSceneQuery* mVolQuery;
std::list<Ogre::MovableObject*> mSelected;
bool mSelecting;
CEGUI::OgreRenderer* mGUIRenderer;
static void swap(float& x, float& y);
};
#endif // #ifndef __IntermediateTutorial4_h_
IntermediateTutorial.cpp
#include "IntermediateTutorial4.h"
//-------------------------------------------------------------------------
------------
IntermediateTutorial4::IntermediateTutorial4(void)
{
}
//-------------------------------------------------------------------------
------------
IntermediateTutorial4::~IntermediateTutorial4(void)
2 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
{
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial4::createScene(void)
{
mSceneMgr->setAmbientLight(Ogre::ColourValue(1.0f, 1.0f, 1.0f));
for(int i = 0; i < 10; ++i)
{
for(int j = 0; j < 10; ++j)
{
Ogre::Entity* ent = mSceneMgr->createEntity
("Robot" + Ogre::StringConverter::toString(i+j*10), "robot.mesh");
Ogre::SceneNode* node = mSceneMgr->getRootSceneNode
()->createChildSceneNode(Ogre::Vector3(i * 15, 0, j * 15));
node->attachObject(ent);
node->setScale(0.1f, 0.1f, 0.1f);
}
}
mCamera->setPosition(-60, 100, -60);
mCamera->lookAt(60, 0, 60);
mGUIRenderer = &CEGUI::OgreRenderer::bootstrapSystem();
CEGUI::SchemeManager::getSingleton().create
((CEGUI::utf8*)"TaharezLook.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook",
"MouseArrow");
}
//-------------------------------------------------------------------------
------------
bool IntermediateTutorial4::frameRenderingQueued(const Ogre::FrameEvent&
evt)
{
return BaseApplication::frameRenderingQueued(evt);
}
//-------------------------------------------------------------------------
------------
bool IntermediateTutorial4::mouseMoved(const OIS::MouseEvent& arg)
{
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel,
arg.state.Y.rel);
return true;
}
//-------------------------------------------------------------------------
3 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
------------
bool IntermediateTutorial4::mousePressed(const OIS::MouseEvent& arg,
OIS::MouseButtonID id)
{
return true;
}
//-------------------------------------------------------------------------
------------
bool IntermediateTutorial4::mouseReleased(const OIS::MouseEvent& arg,
OIS::MouseButtonID id)
{
return true;
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial4::performSelection(const Ogre::Vector2& first,
const Ogre::Vector2& second)
{
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial4::deselectObjects()
{
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial4::selectObject(Ogre::MovableObject* obj)
{
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial4::swap(float& x, float& y)
{
float temp = x;
x = y;
y = temp;
}
//-------------------------------------------------------------------------
------------
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
4 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine,
INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
IntermediateTutorial4 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(),
"An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() <<
std::endl;
#endif
}
return 0;
}
#ifdef __cplusplus
}
#endif
Be sure this code compiles before continuing. When you run it, you should be able to move the mouse cursor,
but the application does nothing else at this point. Press Escape to exit.
ManualObjects
A Crash Course in 3D Objects
Before we start diving directly into making a mesh, it would probably be useful to talk about what a mesh is, and
what it is made of. Though this is a gross oversimplification, a mesh consists of roughly two parts: the vertex
buffer and the index buffer.
Vertex buffers define points in 3D space. Each element in the vertex buffer is defined by several attributes you
can set. The only attribute you must set is the position of the vertex. Aside from that, there are many optional
5 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
properties you can set, such as the color of the vertex, the texture coordinates, and so on. Which ones you will
actually need to use is dependent on what you are trying to do with the mesh.
Index buffers "connect the dots" by selecting points from the vertex buffer. Every three indexes specified in the
index buffer defines a single triangle to be drawn by the GPU. The order in which you select vertices in the
index buffer tells the graphics card which way the triangle faces. A triangle which is drawn counter-clockwise is
facing you, one drawn clockwise is facing away from you. Normally only the front of a triangle is rendered, so it
is important to be sure that your triangles are set up properly.
Though all meshes have a vertex buffer, not all meshes will have an index buffer. For example, the mesh we
are about to create will not have an index buffer since we want to create an empty rectangle (as opposed to a
filled rectangle). Lastly, note that vertex and index buffers are usually stored in the video card's own memory,
so your software can just send the card one simple, discrete set of commands to tell it to use those predefined
buffers to render an entire 3D mesh in one go.
Introduction
There are two ways to create your own mesh within Ogre. The first way is to subclass the SimpleRenderable
object and provide it with the vertex and index buffers directly. This is the most direct way to create one, but
it's also the most cryptic. The Generating A Mesh code snippet shows an example of this. To make things
easier, Ogre provides a much nicer interface called ManualObject, which allows you to use some simple
functions to define a mesh instead of writing raw data to the buffer objects. Instead of dropping the position,
color, and so on into a buffer, you simply call the "position" and "colour" functions.
In this tutorial we need to create a white rectangle to display when we are dragging the mouse to select objects.
There really isn't a class in Ogre we could use to display a 2D rectangle. We will have to come up with a way of
doing it on our own. We could use an Overlay and resize it to display the selection rectangle, but the problem
with doing it this way is that the image you use for the selection rectangle could get stretched out of shape and
look awkward. Instead, we will generate a very simple 2D mesh to act as our selection rectangle.
Selection Box
Now, we can handle all of the selection box functionality within our tutorial application, but that is just going to
add clutter that doesn't need to be there. It is very good practice to keep separate objects in separate classes,
because it makes things neater and easier to use. So basically for the selection box we are going to make a
separate header and cpp file called SelectionBox.h and SelectionBox.cpp respectively, and we are just going to
declare this class just like any other class except we are going to extend from Ogre::ManualObject. So make
your header and cpp look something like this.
SelectionBox.h
#ifndef __SelectionBox_h_
#define __SelectionBox_h_
#include "OgreManualObject.h"
class SelectionBox : public Ogre::ManualObject
{
public :
SelectionBox(const Ogre::String& name);
~SelectionBox(void);
void setCorners(float left, float top, float right, float bottom);
6 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
void setCorners(const Ogre::Vector2& topLeft, const Ogre::Vector2&
bottomRight);
};
#endif
SelectionBox.cpp
#include "SelectionBox.h"
SelectionBox::SelectionBox(const Ogre::String& name): Ogre::ManualObject
(name)
{
}
SelectionBox::~SelectionBox()
{
}
void SelectionBox::setCorners(float left, float top, float right, float
bottom)
{
}
void SelectionBox::setCorners(const Ogre::Vector2& topLeft, const
Ogre::Vector2& bottomRight)
{
setCorners(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
}
The Code
When we create the selection rectangle, we have to create it such that it will render in 2D. We also have to be
sure that it will render when Ogre's Overlays render so that it sits on top of all other objects on screen. Doing
this is actually very easy. Find the SelectionBox's constructor and add the following code:
setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY); // when using this, ensure
Depth Check is Off in the material
setUseIdentityProjection(true);
setUseIdentityView(true);
setQueryFlags(0);
The first function sets the render queue for the object to be the Overlay queue. The next two functions set the
projection and view matrices to be the identity. Projection and view matrices are used by many rendering
systems (such as OpenGL and DirectX) to define where objects go in the world. Since Ogre abstracts this away
7 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
for us, we won't go into detail about what these matrices actually are or what they do. Instead what you need to
know is that if you set the projection and view matrix to be the identity, as we have here, we will basically create
a 2D object. When defining this object, the coordinate system changes a bit. We no longer deal with the Z axis
(if you are asked for the Z axis, set the value to -1). Instead we have a new coordinate system with X and Y
running from -1 to 1 inclusive. Lastly, we will set the query flags for the object to be 0, which will prevent the
selection rectangle from being included in the query results.
Now that the object is set up, we need to actually build the rectangle. We have one small snag before we get
started. We are going to be calling this function with mouse locations. That is, we will be given a number
between 0 and 1 for the x and y coordinates, yet we need to convert these to numbers in the range -1, 1. To
make matters slightly more complicated, the y coordinate is backwards too. The mouse cursor in CEGUI
defines the top of the screen at 0, the bottom at 1. In our new coordinate system, the top of the screen is +1,
the bottom is -1. Thankfully, a few quick conversions will take care of this problem. Find the setCorners()
function and add the following:
left = left * 2 - 1;
right = right * 2 - 1;
top = 1 - top * 2;
bottom = 1 - bottom * 2;
Now the positions are in the new coordinate system. Next we need to actually build the object. To do this, we
first call the begin() method. It takes in two parameters, the name of the material to use for this section of the
object, and the render operation to be used to draw it. Since we are not putting a texture on this, we will leave
the material blank. The second parameter is the RenderOperation. We can render the mesh using points, lines,
or triangles. We would use triangles if we were rendering a full mesh, but since we want an empty rectangle,
we will use the line strip. The line strip draws a line to each vertex from the previous vertex you defined. So to
create our rectangle, we will define five points (the first and the last point are the same to connect the entire
rectangle). Add the following to setCorners():
clear();
begin("", Ogre::RenderOperation::OT_LINE_STRIP);
position(left, top, -1);
position(right, top, -1);
position(right, bottom, -1);
position(left, bottom, -1);
position(left, top, -1);
end();
Note that since we will be calling this many times, we have added the clear() call at the beginning to remove the
previous rectangle before redrawing it. When defining a manual object, you may call begin/end multiple times to
create multiple sub-meshes (which can have different materials/RenderOperations). Note we have also set the
Z parameter to be -1, since we are trying to define a 2D object which will not use that axis. Setting it to be -1 will
ensure that we are not on top of, or behind, the camera when rendering.
The last thing we need to do is set the bounding box for this object. Many SceneManagers cull objects which
are off screen. Even though we've basically created a 2D object, Ogre is still a 3D engine, and treats our 2D
object as if it sits in 3D space. This means that if we create this object and attach it to a SceneNode (as we will
do in the next section), it will disappear on us when we look away. To fix this we will set the bounding box of the
object to be infinite, so that the camera will always be inside of it:
8 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
setBoundingBox(AxisAlignedBox::BOX_INFINITE);
Be sure to note that we have added this code after the clear() call. Every time you call ManualObject::clear(),
the bounding box is reset, so be careful if you create another ManualObject which is cleared often because the
bounding box will have to be set every time you recreate it.
That's all we have to do for the SelectionBox class. Be sure your code compiles before continuing, but note that
no functionality has changed in the program yet.
Volume Selection
Setup
Before we can jump into the selection code, we first have to set up a few things. First of all, include
SelectionBox.h to our IntermediateTutorial4.h, so that we can use it, and we also need to declare a pointer
variable for a selection box in the protected section of your IntermediateTutorial4 class.
#include SelectionBox.h
SelectionBox* mSelectionBox;
Now that it is all set up we need to create an instance of the SelectionBox class, and have the SceneManager
create a volume query for us. Add the following code at the end of createScene(), so we know for sure that the
SceneManager has been initialized:
mSelectionBox = new SelectionBox("SelectionBox");
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject
(mSelectionBox);
mVolQuery = mSceneMgr->createPlaneBoundedVolumeQuery(PlaneBoundedVolumeList
());
Secondly we need to be sure the frame listener cleans up after itself when we are done. Add the following code
to ~IntermediateTutorial4:
mSceneMgr->destroyQuery(mVolQuery);
if(mSelectionBox)
{
delete mSelectionBox;
}
Note that we let the SceneManager clean up the query for us instead of deleting it directly.
Mouse Handlers
9 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
The feature we are trying to implement is volume selection. This means that when the user clicks the mouse
down and drags it across the screen a rectangle will be drawn. When they let go of the mouse, all objects within
the rectangle will be selected. The first thing we will need to do is handle when the mouse is pressed. We'll
need to store the starting location and set the SelectionBox to be visible. Find the mousePressed function and
add the following code:
if (id == OIS::MB_Left)
{
CEGUI::MouseCursor *mouse = CEGUI::MouseCursor::getSingletonPtr();
mStart.x = mouse->getPosition().d_x / (float)arg.state.width;
mStart.y = mouse->getPosition().d_y / (float)arg.state.height;
mStop = mStart;
mSelecting = true;
mSelectionBox->clear();
mSelectionBox->setVisible(true);
}
Note that we use the CEGUI::MouseCursor's x and y coordinates and not OIS's mouse coordinates. This is
because OIS sometimes thinks the mouse is in a different location than what CEGUI is actually displaying. To
make sure we are in sync with what the user is seeing, we'll use CEGUI's mouse coordinates.
The next thing we will need to do is stop displaying the selection rectangle and perform the selection query
when the user releases the mouse. Add the following code to the mouseReleased code:
if (id == OIS::MB_Left)
{
performSelection(mStart, mStop);
mSelecting = false;
mSelectionBox->setVisible(false);
}
Additionally, every time the mouse is moved, we need to update the rectangle to the new coordinates:
if (mSelecting)
{
CEGUI::MouseCursor *mouse = CEGUI::MouseCursor::getSingletonPtr
();
mStop.x = mouse->getPosition().d_x / (float)arg.state.width;
mStop.y = mouse->getPosition().d_y / (float)arg.state.height;
mSelectionBox->setCorners(mStart, mStop);
}
We adjust the mStop vector every time the mouse is moved so that we can simply use it for the setCorners
member function. Finally, we initialize mSelecting to false in the constructor, since at start up we haven't made
a selection yet. Replace the IntermediateTutorial4 constructor by the following code:
IntermediateTutorial4::IntermediateTutorial4(void):
mSelecting(false)
10 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
{
}
Compile and run your application. You can now draw a rectangle using the mouse.
PlaneBoundedVolumeListSceneQuery
Now that we have the SelectionBox properly rendering, we need to actually perform the volume selection. Find
the performSelection() function and add the following code:
float left = first.x, right = second.x,
top = first.y, bottom = second.y;
if (left > right)
swap(left, right);
if (top > bottom)
swap(top, bottom);
In this code section, we have assigned the vector parameters into the left, right, top, and bottom variables. The
if statements ensure that we actually have the lowest value in left and top. (If the rectangle is drawn
"backwards", meaning bottom-right to top-left, then we have to perform this swap.)
Next, we have to check and see how big of an area the rectangle actually makes. If the rectangle is too small,
our method of creating a plane bound volumes will fail and we will end up selecting too many or too few
objects. If the rectangle is less than a certain percentage of the screen, we will simply return and not perform
the selection. I have arbitrarily selected 0.0001 as the threshold for canceling the query, though you should
probably play around with this value for your own program. Also, in a real application you should probably find
the center of the rectangle and perform a standard RaySceneQuery instead of doing nothing:
if ((right - left) * (bottom - top) < 0.0001)
return;
Now we are at the meat of the function, and we need to perform the query itself. PlaneBoundedVolumeQueries
use planes to enclose an area, then select any objects inside that area. For this example we will build an area
enclosed by five planes which face inward. To create these planes out of our rectangle, we will create 4 rays,
one for each corner of the rectangle. Once we have these four rays, we will grab points along the rays to create
the planes:
Ogre::Ray topLeft = mCamera->getCameraToViewportRay(left, top);
Ogre::Ray topRight = mCamera->getCameraToViewportRay(right, top);
Ogre::Ray bottomLeft = mCamera->getCameraToViewportRay(left, bottom);
Ogre::Ray bottomRight = mCamera->getCameraToViewportRay(right, bottom);
Now we will create the planes. Note that we are grabbing a point 100 units along the ray. This was chosen fairly
arbitrarily. We could have chosen 2 instead of 100. The only point which matters here is the front plane, which
we are starting 3 units in front of the Camera.
Ogre::PlaneBoundedVolume vol;
vol.planes.push_back(Ogre::Plane(topLeft.getPoint(3), topRight.getPoint
11 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
(3), bottomRight.getPoint(3))); // front plane
vol.planes.push_back(Ogre::Plane(topLeft.getOrigin(), topLeft.getPoint
(100), topRight.getPoint(100))); // top plane
vol.planes.push_back(Ogre::Plane(topLeft.getOrigin(), bottomLeft.getPoint
(100), topLeft.getPoint(100))); // left plane
vol.planes.push_back(Ogre::Plane(bottomLeft.getOrigin(),
bottomRight.getPoint(100), bottomLeft.getPoint(100))); // bottom plane
vol.planes.push_back(Ogre::Plane(topRight.getOrigin(), topRight.getPoint
(100), bottomRight.getPoint(100))); // right plane
These planes have now defined an "open box" which extends to infinity in front of the camera. You can think of
the rectangle we drew with the mouse as being the termination point of the box just in front of the camera. Now
that we have created the planes, we need to execute the query:
Ogre::PlaneBoundedVolumeList volList;
volList.push_back(vol);
mVolQuery->setVolumes(volList);
Ogre::SceneQueryResult result = mVolQuery->execute();
Finally we need to handle the results of the query. First we will deselect all previously selected objects, then we
will select all objects which were found by the query.
deselectObjects();
Ogre::SceneQueryResultMovableList::iterator iter;
for (iter = result.movables.begin(); iter != result.movables.end(); ++iter)
selectObject(*iter);
That's all we need to do for the query. Note we can also use query flags on volume queries, even though we
have not done so in this tutorial. See the previous tutorial for more information on query flags.
Now before we forget, we need to fill in our selectObject and deselectObjects functions.
void IntermediateTutorial4::deselectObjects()
{
std::list<Ogre::MovableObject*>::iterator iter = mSelected.begin();
for(iter; iter != mSelected.end(); iter++)
{
(*iter)->getParentSceneNode()->showBoundingBox(false);
}
}
void IntermediateTutorial4::selectObject(Ogre::MovableObject* obj)
{
obj->getParentSceneNode()->showBoundingBox(true);
mSelected.push_back(obj);
}
12 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
Compile and run the application. You can now volume select objects in the scene!
A Final Note About Bounding Boxes
As you may have noticed from this tutorial and the previous two tutorials, selection in Ogre relies on the
bounding box of the objects in question and not on the mesh itself. This means that a RaySceneQuery and
PlaneBoundedVolumeQuery will always be too accepting in what is actually hit by the query. There are ways of
performing pixel perfect ray selection (such as what you would need to do to see if a gunshot in an FPS
actually hit its target) and volume selection which will give you perfectly accurate results for a trade-off in
speed. Unfortunately, this is beyond the scope of this tutorial. Take a look at Raycasting to the polygon level
for more information on how to do this in pure Ogre. If you have integrated Ogre with a physics library, such as
OgreNewt, they should also provide methods to do this for you.
You did not learn all of this about ray queries and volume queries for nothing though. Doing a mesh based
selection is very time consuming and can quickly kill your framerate if you try to check everything in a scene. In
fact, the most common way to do "true" mouse selection is to first perform an Ogre query (such as a
RaySceneQuery), then individually test each intersection returned from the query with a physics engine that will
actually check the mesh's geometry to see if you actually hit it or just came very close.
Alias: Intermediate_Tutorial_4
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR
COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED
HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
• "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the
Work in its entirety in unmodified form, along with a number of other contributions, constituting separate
and independent works in themselves, are assembled into a collective whole. A work that constitutes a
Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this
License.
• "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works,
such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound
recording, art reproduction, abridgment, condensation, or any other form in which the Work may be
recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be
considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the
Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with
a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
• "Licensor" means the individual or entity that offers the Work under the terms of this License.
• "Original Author" means the individual or entity who created the Work.
• "Work" means the copyrightable work of authorship offered under the terms of this License.
13 de 16Página Intermediate Tutorial 4e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=276&page=Intermediate...
Static Geometry
Intermediate Tutorial 5: Static Geometry
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Introduction
There will be a lot of situations when you need to add objects to your scene, but you won't need to move them
at all. For example, unless you are adding some kind of physics into the mix, a rock or a tree will rarely ever
need to move. For these situations, Ogre provides the StaticGeometry class, which allows you to build batches
of objects to render in a big bunch. This is generally faster than doing it manually with SceneNodes. In this
tutorial we will cover basic uses of StaticGeometry in your application. We will also cover a few more things you
can do with ManualObject. See the previous tutorial for more information about ManualObject.
In this tutorial we will manually create a grass mesh, then add many of them to a StaticGeometry object in our
scene.
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. Note that this tutorial has been build largely from the
Grass Demo that comes with Ogre's samples. You can also dig around in the source of that demo for more
information.
Table of contents
Introduction
Prerequisites
Creating the Scene
Creating the Mesh
Adding Static Geometry
Conclusion
Modifying the StaticGeometry Object
Advanced Object Batching
Creating a ManualObject from the SceneManager
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Prerequisites
Load up a new project with the Ogre Tutorial Framework and set up your project to look like this:
IntermediateTutorial5.h
1 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
#ifndef __IntermediateTutorial5_h_
#define __IntermediateTutorial5_h_
#include "BaseApplication.h"
class IntermediateTutorial5 : public BaseApplication
{
public:
IntermediateTutorial5(void);
virtual ~IntermediateTutorial5(void);
protected:
virtual void createScene(void);
virtual void createGrassMesh(void);
};
#endif // #ifndef __IntermediateTutorial5_h_
IntermediateTutorial5.cpp
#include "IntermediateTutorial5.h"
//-------------------------------------------------------------------------
------------
IntermediateTutorial5::IntermediateTutorial5(void)
{
}
//-------------------------------------------------------------------------
------------
IntermediateTutorial5::~IntermediateTutorial5(void)
{
}
void IntermediateTutorial5::createGrassMesh(void)
{
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial5::createScene(void)
{
createGrassMesh();
mSceneMgr->setAmbientLight(Ogre::ColourValue::White);
mCamera->setPosition(150, 50, 150);
mCamera->lookAt(0, 0, 0);
Ogre::Entity* robot = mSceneMgr->createEntity("robot",
"robot.mesh");
2 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject
(robot);
Ogre::Plane plane;
plane.normal = Ogre::Vector3::UNIT_Y;
plane.d = 0;
Ogre::MeshManager::getSingleton().createPlane("floor",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 450.0f,
450.0f, 10, 10, true, 1, 50.0f, 50.0f, Ogre::Vector3::UNIT_Z);
Ogre::Entity* planeEnt = mSceneMgr->createEntity("plane", "floor");
planeEnt->setMaterialName("Examples/GrassFloor");
planeEnt->setCastShadows(false);
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject
(planeEnt);
}
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
IntermediateTutorial5 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception
has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() << std::endl;
#endif
}
return 0;
}
3 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
#ifdef __cplusplus
}
#endif
Be sure you can compile and run the application before continuing. You should see a Robot standing on a flat
plane.
Creating the Scene
Creating the Mesh
The first thing we need to do is create the grass mesh we will be rendering. The general idea is to create three
square quads which overlap each other. Each quad will have a grass texture on it, so that when you look at it
from any direction other than straight down it will look somewhat like 3D grass. The easiest way to overlap
these quads will be to create one, then create another at a 60 degree rotation, and then a third at yet another
60 degrees.
As with the previous tutorial, we will be using ManualObject to generate our object, but unlike the previous
tutorial we will be actually creating a mesh instead of simple line lists (which will require us to build an index
buffer too, specifying the triangles as we build it).
The first thing we will do is define some basic variables. Since we are going to be rotating a quad that we are
defining, it will be easiest if we use Ogre constructs like Vector3 and Quaternion to do the math for us instead
of trying to do it manually. Our plan is to create a Vector3 with the X and Z coordinates in it, build a single quad
from it, then rotate it with a Quaternion and repeat. Find the createGrassMesh member function and add the
following code:
const float width = 25;
const float height = 30;
Ogre::ManualObject mo("GrassObject");
Ogre::Vector3 vec(width/2, 0, 0);
Ogre::Quaternion rot;
rot.FromAngleAxis(Ogre::Degree(60), Ogre::Vector3::UNIT_Y);
Now that we have set up our variables, we need to start defining the ManualObject. Unlike the previous tutorial,
we will actually be designating a material to use with this object. Our RenderOperation will be set to be a
triangle list as well, meaning after we define our vertices we must define a list of triangles which create the
faces of our quads:
mo.begin("Examples/GrassBlades", Ogre::RenderOperation::OT_TRIANGLE_LIST);
for (int i = 0; i < 3; ++i)
{
For each quad we are going to define four vertices, one for each corner. For each vertex we are also going to
specify a texture coordinate. Texture coordinates tell Ogre how to sample the texture we have specified in the
Examples/GrassBlades material. We will make the top left corner the (0, 0) point of the texture, the bottom right
corner (1, 1) and so on:
4 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
mo.position(-vec.x, height, -vec.z);
mo.textureCoord(0, 0);
mo.position(vec.x, height, vec.z);
mo.textureCoord(1, 0);
mo.position(-vec.x, 0, -vec.z);
mo.textureCoord(0, 1);
mo.position(vec.x, 0, vec.z);
mo.textureCoord(1, 1);
Now that we have defined the four corners of our quad, we now need to create faces. As we mentioned briefly
in the previous tutorial, you must specify faces by creating triangles, and you must be sure to wind them
counter clockwise to face towards you. For each quad, we will build two triangles. The first will be from the (0th,
3rd, 1st) vertices defined, and the second from the (0th, 2nd, 3rd) vertices defined. This properly defines the
quad. Also remember that we are looping a few times, and every time through we create 4 vertices, thus we
have to use an offset variable to select the proper vertex:
int offset = i * 4;
mo.triangle(offset, offset+3, offset+1);
mo.triangle(offset, offset+2, offset+3);
Next we need to rotate the vector we are using to create the current quad and continue looping. After the loop
is finished we must call ManualObject::end to complete the object:
vec = rot * vec;
}
mo.end();
Now that we have defined the manual object, we are almost ready to start creating our StaticGeometry. One
last thing we are going to do is create a mesh out of our ManualObject. Meshes are a touch more optimized
than using a ManualObject directly for rendering. To do this, we simply need to call the
ManualObject::convertToMesh with a name to store the mesh as:
mo.convertToMesh("GrassBladesMesh");
We are now finished creating the grass mesh. Note that if you have created a highly complex mesh in this way,
you may save it to file and simply load the file back in instead of recreating the ManualObject every time you
load the program. To do this, we will take the return value of convertToMesh (which we discarded in the actual
code), and feed the Mesh it returns to the MeshSerializer::exportMesh function. Here is an example of how to
do that:
// Do not add to the code!
Ogre::MeshPtr ptr = mo.convertToMesh("GrassBladesMesh");
5 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
Ogre::MeshSerializer ser;
ser.exportMesh(ptr.getPointer(), "grass.mesh");
We are now ready to create the StaticGeometry.
Adding Static Geometry
The createScene method is already populated with several things. I have already added code to create a floor
plane, add a robot, set the Camera's position, and so on since we have covered how to do those things in
previous tutorials. Be sure to make sure you understand the current contents of this function before continuing.
The first thing we are going to do now is create an Entity based off of the grass mesh we created earlier and
create a StaticGeometry object. Note that we will only create one Entity to use with our StaticGeometry. Find
the createScene method and add the following code to the end of it:
Ogre::Entity *grass = mSceneMgr->createEntity("grass", "GrassBladesMesh");
Ogre::StaticGeometry *sg = mSceneMgr->createStaticGeometry
("GrassArea");
const int size = 375;
const int amount = 20;
The size variable will define how large of an area we are covering with grass and the amount variable will
define how many objects we will put in each row of our StaticGeometry.
The next thing we need to do is define the size and origin of the StaticGeometry. Once we build the object (by
calling StaticGeometry::build), we can no longer change the origin or region the StaticGeometry defines. The
origin is the top left corner of the region that the StaticGeometry defines. If you want to place the
StaticGeometry around a point, you will need set the origin's x and z coordinates to be half of the region's size
for x and z:
sg->setRegionDimensions(Ogre::Vector3(size, size, size));
sg->setOrigin(Ogre::Vector3(-size/2, 0, -size/2));
This will center the object around the point (0, 0, 0). To center it around a point in 3D space, you would need to
do something similar to this:
// Do not add to the project!
sg->setOrigin(Vector3(-size/2, -size/2, -size/2) + Vector3(x, y, z));
Where x, y, z is the point in 3D space to center it around. Also note that we do define the vertical height of the
object when setting the region. Be sure that the y component of setRegionDimensions is at least as large as
the highest object in the StaticGeometry.
The next thing we need to do is add objects to the StaticGeometry. This next piece of code is somewhat
complex because we are adding a whole grid of grass to the geometry, and giving a random shift in x, z
position, a random rotation, and a random vertical scale to it. In reality, the most important thing to understand
in this is the StaticGeometry::addEntity:
6 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
for (int x = -size/2; x < size/2; x += (size/amount))
{
for (int z = -size/2; z < size/2; z += (size/amount))
{
Ogre::Real r = size / (float)amount / 2;
Ogre::Vector3 pos(x + Ogre::Math::RangeRandom(-r, r), 0, z
+ Ogre::Math::RangeRandom(-r, r));
Ogre::Vector3 scale(1, Ogre::Math::RangeRandom(0.9, 1.1),
1);
Ogre::Quaternion orientation;
orientation.FromAngleAxis(Ogre::Degree
(Ogre::Math::RangeRandom(0, 359)), Ogre::Vector3::UNIT_Y);
sg->addEntity(grass, pos, orientation, scale);
}
}
The addEntity function takes in the Entity to use, the position of the object, the orientation of the object, and the
scale of the object. When you are defining StaticGeometry you will either use the addEntity function or the
addSceneNode function. The addSceneNode function walks the -SceneNode adding all Entities to the static
geometry, using the position, orientation, and scale of the children SceneNodes instead of specifying them
manually. Note that if you use the addSceneNode function, be sure to remove the node from its parent -
SceneNode, since the addSceneNode function does not remove it for you. If you do not, Ogre will render both
the StaticGeometry you created and the original -SceneNode which is not what you want.
Finally we need to build the StaticGeometry before it is displayed:
sg->build();
Compile and run your application, you should now see a robot standing in a small patch of grass.
Conclusion
Modifying the StaticGeometry Object
Once the StaticGeometry is created, you are not supposed to do too much with it, since that would mostly
defeat the purpose. You can, however, do things like wave the grass with the wind. If you are interested in how
to do this, take a look at the grass demo which comes with Ogre. The GrassListener::waveGrass function
modifies the grass to perform a wave-like motion.
Advanced Object Batching
This is, of course, just the beginnings of object batching. You should use StaticGeometry any time you have
objects that are grouped together and will not move. If you are trying to create something as intensive or as
expansive as a forest or trying to add grass to a huge amount of terrain, you should take a look at one of the
more advanced batching techniques, like the PagedGeometry Engine.
Creating a ManualObject from the SceneManager
7 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
In addition to instantiating a ManualObject directly, you can also use
Ogre::SceneManager::createManualObject to instantiate a ManualObject. One user reported an inability to
render ManualObjects when they were instantiated directly, so if you're encountering errors, it may be worth
using the SceneManager to instantiate your ManualObjects as part of troubleshooting.
Proceed to Intermediate Tutorial 6 Projective Decals
Alias: Intermediate_Tutorial_5
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR
COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED
HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
• "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the
Work in its entirety in unmodified form, along with a number of other contributions, constituting separate
and independent works in themselves, are assembled into a collective whole. A work that constitutes a
Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this
License.
• "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works,
such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound
recording, art reproduction, abridgment, condensation, or any other form in which the Work may be
recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be
considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the
Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with
a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
• "Licensor" means the individual or entity that offers the Work under the terms of this License.
• "Original Author" means the individual or entity who created the Work.
• "Work" means the copyrightable work of authorship offered under the terms of this License.
• "You" means an individual or entity exercising rights under this License who has not previously violated
the terms of this License with respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous violation.
• "License Elements" means the following high-level license attributes as selected by Licensor and
indicated in the title of this License: Attribution, ShareAlike.
2. Fair Use Rights
Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other
limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
3. License Grant
8 de 11Página Intermediate Tutorial 5e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=277&page=Intermediate...
Projective Decals
Projective Decals
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Table of contents
Projective Decals
Introduction
Getting Started
New Textures
The Initial Code
Projecting Decals
Frustums
Modifying the Material
Calling the Functions
Getting Rid of the Back Projection
Introduction
Modifying the Projector
Modifying the Material
Showing Off the Projection
Simple Rotation
One Final Note
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Introduction
In this tutorial we will be covering how to add projective decals to an object in the scene. Projective texturing is
useful when you want to do something like a selection indicator on the ground, an aiming sight that's projected
on what you're aiming at, or some other type of decal that's projected onto something (but doesn't become a
permanent part of the target like splatting). Here's a screenshot of an aiming site being projected onto
everyone's favorite ogre head:
1 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
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
New Textures
Before we get started on this project, we need to add two new images we will be using:
No such attachment on this page
No such attachment on this page
The best place to put these files is in the media/materials/textures folder (for most people this should be located
in the OgreSDK folder). Also note the character case by making sure the file names are all lower case as they
are referenced as such in the tutorial.
The Initial Code
Set up your Intermediate Tutorial 6 project to look something like this:
IntermediateTutorial6.h
#ifndef __IntermediateTutorial6_h_
#define __IntermediateTutorial6_h_
#include "BaseApplication.h"
class IntermediateTutorial6 : public BaseApplication
{
public:
IntermediateTutorial6(void);
virtual ~IntermediateTutorial6(void);
protected:
virtual void createScene(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
virtual void createProjector();
virtual void makeMaterialReceiveDecal(const Ogre::String& matName);
Ogre::SceneNode* mProjectorNode;
Ogre::Frustum* mDecalFrustum;
Ogre::Frustum* mFilterFrustum;
2 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
float mAnim;
};
#endif // #ifndef __IntermediateTutorial6_h_
IntermediateTutorial6.cpp
#include "IntermediateTutorial6.h"
//-------------------------------------------------------------------------
------------
IntermediateTutorial6::IntermediateTutorial6(void)
{
}
//-------------------------------------------------------------------------
------------
IntermediateTutorial6::~IntermediateTutorial6(void)
{
}
//-------------------------------------------------------------------------
------------
void IntermediateTutorial6::createScene(void)
{
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f));
Ogre::Light* light = mSceneMgr->createLight("MainLight");
light->setPosition(20, 80, 50);
mCamera->setPosition(60, 200, 70);
mCamera->lookAt(0,0,0);
Ogre::Entity* ent;
for (int i = 0; i < 6; i++)
{
Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()-
>createChildSceneNode();
ent = mSceneMgr->createEntity("head" +
Ogre::StringConverter::toString(i), "ogrehead.mesh");
headNode->attachObject(ent);
Ogre::Radian angle(i + Ogre::Math::TWO_PI / 6);
headNode->setPosition(75 * Ogre::Math::Cos(angle), 0, 75 *
Ogre::Math::Sin(angle));
}
}
bool IntermediateTutorial6::frameRenderingQueued(const Ogre::FrameEvent&
evt)
{
3 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
return BaseApplication::frameRenderingQueued(evt);
}
void IntermediateTutorial6::createProjector()
{
}
void IntermediateTutorial6::makeMaterialReceiveDecal(const Ogre::String&
matName)
{
}
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
IntermediateTutorial6 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception
has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() << std::endl;
#endif
}
return 0;
}
#ifdef __cplusplus
}
#endif
4 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
Compile and run this program before continuing. You should see six Ogre heads.
Projecting Decals
Frustums
A frustum represents a pyramid capped at the near and far ends, which represents a visible area or a
projection. Ogre uses this to represent cameras with (the Camera class derives directly from the Frustum
class). In this tutorial, we will be using a frustum to project the decal onto the meshes in the scene.
The first thing we will do to create the projector is to create the frustum which represents it and attach it to a
SceneNode. Find the createProjector() method and add the following code:
mDecalFrustum = new Ogre::Frustum();
mProjectorNode = mSceneMgr->getRootSceneNode()->createChildSceneNode
("DecalProjectorNode");
mProjectorNode->attachObject(mDecalFrustum);
mProjectorNode->setPosition(0,5,0);
This creates a projector which will grow the decal as you get farther and farther away from it, a lot like how a
film projector works. If you want to create a projector which maintains a constant size and shape of decal at
whatever distance we set, you should add the following code (but we do not for this tutorial):
// Do not add this to the project
mDecalFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
mDecalFrustum->setOrthoWindowHeight(100);
setOrthoWindowHeight() is used together with aspect ratio to set the size of an orthographic frustum.
Before continuing, please take note of where our frustum is projecting the decal. In this application there is a
ring of Ogre heads and the frustum sits in the dead center of them (though shifted up slightly, by 5 units),
pointed in the -Z direction (which is the default since we did not change the orientation). This means that,
eventually, when we run the application decals will be projected onto the back Ogre heads.
Modifying the Material
In order for the decal to actually show up on an object, the material that it uses has to receive the decal. We do
this by creating a new pass which renders the decal on top of the regular texture. The frustum determines the
location, size, and shape of the projected decal. In this demo we will be modifying the material itself to receive
the decal, but for most real applications, you should probably create a clone of the material to modify so you
can switch it off by setting the material back to the original one.
The first thing we will do is get the material and create a new pass for the material. Find
makeMaterialReceiveDecal() and add the following code:
Ogre::MaterialPtr mat = (Ogre::MaterialPtr)
Ogre::MaterialManager::getSingleton().getByName(matName);
Ogre::Pass *pass = mat->getTechnique(0)->createPass();
Now that we have created our pass, we need to set up blending and lighting. We will be adding a new texture
which must be blended properly with the current texture already on the object. To do this we will set the scene
5 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
blending to be transparent alpha, and the depth bias to be 1 (so that there is no transparency in the decal).
Lastly we need to disable lighting for the material so that it always shows up no matter what the lighting of the
scene is. If you want the decal in your application to be affected by the scene lighting you should not add that
last function call:
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setDepthBias(1);
pass->setLightingEnabled(false);
Now that we have our new pass we need to create a new texture unit state using our decal.png image. The
second function call turns on projective texturing and takes in the frustum we have created. The final two calls
set up the filtering and addressing modes:
Ogre::TextureUnitState *texState = pass->createTextureUnitState
("decal.png");
texState->setProjectiveTexturing(true, mDecalFrustum);
texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(Ogre::FO_POINT, Ogre::FO_LINEAR,
Ogre::FO_NONE);
We have set the texture addressing mode to clamp so that the decal doesn't "loop" itself on the object. For the
filtering options, we have set the magnification of the object to use standard linear, but we have basically turned
off filtering for minification (FO_POINT), and turned off mipmapping entirely. This prevents the border of the
decal (which is transparent) from getting blurred into the rest of the texture when it is minimized. If we do not do
that, there will be ugly smearing all over the outside of the place the decal is projected.
This is all you need to do to set up the material.
Calling the Functions
Now that we have built the functions, we need to call them to set up the projector and the material. Add the
following code to the end of the createScene method:
createProjector();
for (unsigned int i = 0; i < ent->getNumSubEntities(); i++)
{
makeMaterialReceiveDecal(ent->getSubEntity(i)->getMaterialName());
}
Note that the ent variable already has one of the ogre head entities stored in it from the previous loop. Since all
the ogre heads use the same material, we only need to select a random one of them to grab the material
names from.
Compile and run the application, you should see a few Ogre heads with a decal projected onto them.
Getting Rid of the Back Projection
Introduction
As you have probably noticed when running the application, there are actually two decals being projected. The
first is projected in the -Z direction, which is where our frustum is facing, the other is projected in the +Z
6 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
direction, onto the ogre heads behind the frustum we have created. The reason for this is when a decal is
projected out of the front of the frustum, a corresponding (inverted) decal is projected out of the back of it.
This is obviously not what we want. To fix it we will introduce a filter that will remove the back projection.
Modifying the Projector
To filter the back projection, we need a new frustum for the filter which points in the direction we wish to filter.
Add the following code to the createProjector() method:
mFilterFrustum = new Ogre::Frustum();
mFilterFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
Ogre::SceneNode *filterNode = mProjectorNode->createChildSceneNode
("DecalFilterNode");
filterNode->attachObject(mFilterFrustum);
filterNode->setOrientation(Ogre::Quaternion(Ogre::Degree
(90),Ogre::Vector3::UNIT_Y));
This should all be familiar. The only difference is that we have rotated the node by 90 degrees to face
backwards.
Modifying the Material
Now we need to add another texture state to the pass we added on the material. Add the following to
makeMaterialReceiveDecal:
texState = pass->createTextureUnitState("decal_filter.png");
texState->setProjectiveTexturing(true, mFilterFrustum);
texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(Ogre::TFO_NONE);
This all should look familiar. Note that we are using the filter texture, the filter frustum, and the we have turned
off filtering. Compile and run the application. You should now see only the forward projection of the decals.
Showing Off the Projection
Simple Rotation
To show off the projection, we will rotate the projector. To rotate the projector, simply add the following line of
code to the beginning of the frameRenderingQueued method:
mProjectorNode->rotate(Ogre::Vector3::UNIT_Y, Ogre::Degree
(evt.timeSinceLastFrame * 10));
Compile and run the application. You will now see the decal projected along the circle of ogre heads.
One Final Note
One last thing to note about decals, if you use decals in your application, be sure that the outer border pixels of
the decals are completely transparent (zero alpha). If not, the decal will smear due to the way texture clamping
works.
Work around
7 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
The above notice is true, but there's a way around it if your texture doesn't already have transparent borders:
As long as your texture is in a format that supports alpha channels (such as PNG), you can set the Texture
Address Mode to TAM_BORDER (or border, if you're doing this in a script) and set the Texture Border Colour
to (0, 0, 0, 0). This causes any texture coordinates outside the range 0, 1 to have the border color you specify,
which is black with 0 alpha. So essentially, you just added a transparent border to your texture.
You can find a quick implementation in Projective Decals.
Alias: Intermediate_Tutorial_6
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR
COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED
HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
• "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the
Work in its entirety in unmodified form, along with a number of other contributions, constituting separate
and independent works in themselves, are assembled into a collective whole. A work that constitutes a
Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this
License.
• "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works,
such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound
recording, art reproduction, abridgment, condensation, or any other form in which the Work may be
recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be
considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the
Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with
a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
• "Licensor" means the individual or entity that offers the Work under the terms of this License.
• "Original Author" means the individual or entity who created the Work.
• "Work" means the copyrightable work of authorship offered under the terms of this License.
• "You" means an individual or entity exercising rights under this License who has not previously violated
the terms of this License with respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous violation.
• "License Elements" means the following high-level license attributes as selected by Licensor and
indicated in the title of this License: Attribution, ShareAlike.
2. Fair Use Rights
Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other
limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
8 de 11Página Intermediate Tutorial 6e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=278&page=Intermediate...
Render to texture (RTT)
Intermediate Tutorial 7: Render to texture
Any problems you encounter during working with this tutorial should be posted in the Help Forum .
Table of contents
Introduction
The Initial Code
Render to texture
Creating the render textures
Write texture to file
Implementing a mini screen
General
Setting up the rectangle
Creating a material from scratch
Usage of RenderTargetListener
General
Implementing a RenderTargetListener
RTTs and shaders
Passing an RTT to a shader
Conclusion
1. Definitions
2. Fair Use Rights
3. License Grant
4. Restrictions
5. Representations, Warranties and Disclaimer
6. Limitation on Liability.
7. Termination
8. Miscellaneous
Introduction
This tutorial will teach you the basics of rendering to textures. This technique is necessary for a variety of effects
especially in combination with shaders, e.g Motion Blur.
The idea behind rendering to textures RTT is rather simple. Instead of sending the render output data of your
scene to your render window, you just send it to a texture. This texture can then be used just as a normal texture
from your harddrive.
1 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
You can find the code for this tutorial here.
The Initial Code
Set up your IntermediateTutorial7 application to look like this:
IntermediateTutorial7.h
#ifndef __IntermediateTutorial7_h_
#define __IntermediateTutorial7_h_
#include "BaseApplication.h"
class IntermediateTutorial7 : public BaseApplication
{
public:
IntermediateTutorial7(void);
virtual ~IntermediateTutorial7(void);
protected:
virtual void createScene(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
Ogre::MovablePlane* mPlane;
2 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
Ogre::Entity* mPlaneEnt;
Ogre::SceneNode* mPlaneNode;
};
#endif // #ifndef __IntermediateTutorial7_h_
IntermediateTutorial7.cpp
#include "IntermediateTutorial7.h"
//--------------------------------------------------------------------------
-----------
IntermediateTutorial7::IntermediateTutorial7(void)
{
}
//--------------------------------------------------------------------------
-----------
IntermediateTutorial7::~IntermediateTutorial7(void)
{
}
//--------------------------------------------------------------------------
-----------
void IntermediateTutorial7::createScene(void)
{
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f));
Ogre::Light* light = mSceneMgr->createLight("MainLight");
light->setPosition(20, 80, 50);
mCamera->setPosition(60, 200, 70);
mCamera->lookAt(0,0,0);
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create
("PlaneMat", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::TextureUnitState* tuisTexture = mat->getTechnique(0)->getPass
(0)->createTextureUnitState("grass_1024.jpg");
mPlane = new Ogre::MovablePlane("Plane");
mPlane->d = 0;
mPlane->normal = Ogre::Vector3::UNIT_Y;
Ogre::MeshManager::getSingleton().createPlane("PlaneMesh",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, *mPlane, 120, 120,
1, 1, true, 1, 1, 1, Ogre::Vector3::UNIT_Z);
mPlaneEnt = mSceneMgr->createEntity("PlaneEntity", "PlaneMesh");
mPlaneEnt->setMaterialName("PlaneMat");
mPlaneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mPlaneNode->attachObject(mPlaneEnt);
}
3 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
//--------------------------------------------------------------------------
-----------
bool IntermediateTutorial7::frameRenderingQueued(const Ogre::FrameEvent&
evt)
{
mPlaneNode->yaw(Ogre::Radian(evt.timeSinceLastFrame));
return BaseApplication::frameRenderingQueued(evt);
}
//--------------------------------------------------------------------------
-----------
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
// Create application object
IntermediateTutorial7 app;
try {
app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception
has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occured: " <<
e.getFullDescription().c_str() << std::endl;
#endif
}
return 0;
}
#ifdef __cplusplus
}
#endif
Compile and run this program before continuing. You should see a simple plane rotating around the Y-axis.
4 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
Render to texture
Creating the render textures
First of all, we need to create a texture.
Ogre::TexturePtr rtt_texture = Ogre::TextureManager::getSingleton
().createManual("RttTex",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D,
mWindow->getWidth(), mWindow->getHeight(), 0, Ogre::PF_R8G8B8,
Ogre::TU_RENDERTARGET);
("mWindow" is your Ogre::RenderWindow pointer)
The first parameter to createManual() is the name of the texture, which is commonly named RttTex. The second
specifies the resource group, the third the texture type (in our case a 2D texture), the fourth and the fifth the
width and height of the texture. You also have to pass the number of mip maps you want as well as the texture
format and a usage flag. Concerning the texture format: There are many different ones, but the most simple is
the PF_R8G8B8 which will create a 24-bit RGB-texture. If you are in the need of an alpha channel,
PF_R8G8B8A8 should suit best for this.
Now we need to get the render target of this texture in order to set some parameters and later also add a
RenderTargetListener.
5 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
Ogre::RenderTexture *renderTexture = rtt_texture->getBuffer()-
>getRenderTarget();
renderTexture->addViewport(mCamera);
renderTexture->getViewport(0)->setClearEveryFrame(true);
renderTexture->getViewport(0)->setBackgroundColour
(Ogre::ColourValue::Black);
renderTexture->getViewport(0)->setOverlaysEnabled(false);
After getting the render texture, we have to add a viewport to it. This is the viewport with whose content the
RenderTexture will be filled. We also tell the viewport to clear itself every frame, set the background color to
black and request to disable all the overlays for the texture as we don't want to have them on it.
Write texture to file
At this point we've got everything ready to do a first check: We just store the content of our created
RenderTexture in a file. The handy thing is that the RenderTextures are derived from RenderTarget and so have
a ready function to store the content of the texture in a file (as well as we can do it with RenderWindows). But
before saving the content to a file, you should update your RenderTexture. You can do this either manually via
the update() function or request the application to automatically update the RenderTexture by once calling the
setAutoUpdate() function.
// Either this way
renderTexture->setAutoUpdated(true);
// or this way
renderTexture->update();
// Now save the contents
renderTexture->writeContentsToFile("start.png");
After running the application, you will find a new .png file in the directory your .exe is in, that should show the
content of your screen (in our case the textured plane).
Implementing a mini screen
General
Now, we will add a mini screen in the lower right corner of our application window. To do this we'll add a
Rectangle2D to our scene which will get our RenderTexture as texture. Our scene will be shown twice: one time
rendered to the RenderWindow as normal, and a second time as the texture on our Rectangle2D.
Using this method it is also possible to implement TV screens (or CCTV cameras, etc). You can display other
parts of your level by putting a camera there, creating a RenderTexture (as described in the first section) and
displaying it on the TV screen.
Setting up the rectangle
Creating an Ogre::Rectangle2D is rather simple:
Ogre::Rectangle2D *mMiniScreen = new Ogre::Rectangle2D(true);
mMiniScreen->setCorners(0.5f, -0.5f, 1.0f, -1.0f);
mMiniScreen->setBoundingBox(Ogre::AxisAlignedBox(-100000.0f *
Ogre::Vector3::UNIT_SCALE, 100000.0f * Ogre::Vector3::UNIT_SCALE));
6 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
In the first line, we set the parameter to true to generate texture coordinates, which we later need to map the
texture on the rectangle. In the second line we have to specify the corners of the rectangle. Left is -1.0 and right
+1.0, top is +1.0 and bottom -1.0. So our rectangle is just in the lower right corner of our application window.
We also give the rectangle a real huge bounding box to prevent it from being culled when we are not facing its
scene node. You could also use AxisAlignedBox::setInfinite(), instead of manually setting the size of the box, but
there have been some problems with this in the past, so manually setting a huge box should be the safest way.
Now that we have created the rectangle, we just need to attach it to a SceneNode which should not be anything
new for you.
Ogre::SceneNode* miniScreenNode = mSceneMgr->getRootSceneNode()-
>createChildSceneNode("MiniScreenNode");
miniScreenNode->attachObject(mMiniScreen);
If you run the application at this stage, you will see a white rectangle in the lower right corner of our application
window.
Creating a material from scratch
The next step is now to show the RenderTexture we created in the first section on this rectangle. Therefore we
have to create a material for it. We can do this either in a material script, or directly in the code during runtime. In
this tutorial, we will use the second method to have everything together in one file.
7 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
Ogre::MaterialPtr renderMaterial = Ogre::MaterialManager::getSingleton
().create("RttMat",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Technique* matTechnique = renderMaterial->createTechnique();
matTechnique->createPass();
renderMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
renderMaterial->getTechnique(0)->getPass(0)->createTextureUnitState
("RttTex");
In the first line we create an empty material and add a technique and a pass in the next two lines. With the third
line we disable the lightning to prevent our mini screen from being darker than the actual texture. In the last line
we create a new TextureUnitState by passing our created RenderTexture from the first section.
Now we can apply this material to our mini screen.
mMiniScreen->setMaterial("RttMat");
If you run the application now, you will see an undesired effect: The mini screen itself has a miniscreen! To solve
this, Ogre introduces RenderTargetListener.
Usage of RenderTargetListener
General
In many cases you will need RTTs with only some scene objects on it. In our case we need a texture that only
contains the output of the application window without the mini screen yet on it, as this texture is intended to be
applied to our mini screen. So we have to hide the mini screen each time before the window output is stored in
our RenderTexture. And this is where the RenderTargetListener comes in.
This listener has two important functions: preRenderTargetUpdate() and postRenderTargetUpdate(). As the
names induce, the first function is automatically called by the listener before the RenderTexture is filled (so we
can hide our mini screen here) whereas the second function is automatically called after the RenderTexture has
been filled (so we can show our mini screen again).
Implementing a RenderTargetListener
Implementing a RenderTargetListener is quite simple. First of all we let our application class derive from the
RenderTargetListener to make it become its own listener. After that we just have to hide and show our mini
screen in the pre- and postRenderTargetUpdate() functions. So your application class should basically look like
this now:
IntermediateTutorial7.h
class IntermediateTutorial7 : public BaseApplication, public
Ogre::RenderTargetListener
{
public:
IntermediateTutorial7(void);
virtual ~IntermediateTutorial7(void);
protected:
virtual void createScene(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
virtual void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
virtual void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
8 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
Ogre::MovablePlane* mPlane;
Ogre::Entity* mPlaneEnt;
Ogre::SceneNode* mPlaneNode;
//This should be taken out of the createScene member and brought here
Ogre::Rectangle2D* mMiniScreen;
};
IntermediateTutorial7.cpp
void IntermediateTutorial7::preRenderTargetUpdate(const
Ogre::RenderTargetEvent& evt)
{
mMiniScreen->setVisible(false);
}
void IntermediateTutorial7::postRenderTargetUpdate(const
Ogre::RenderTargetEvent& evt)
{
mMiniScreen->setVisible(true);
}
Now, the last step we need to do is to add the listener to the RenderTexture. As the application class is derived
from RenderTargetListener we can just pass the this pointer as parameter at the end of the createScene
function.
renderTexture->addListener(this);
That's it. Now you have a simple mini screen in your app. It's that simple.
9 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
RTTs and shaders
Passing an RTT to a shader
As RTTs are often used with shaders, you have to know how to pass the RenderTexture to one. Fortunately, this
is really simple.
The most simple case is one where you never change the texture for the shader during runtime. If it remains
constant, you just have to tell your material script the name of the texture you create during runtime, in our case
"RttTex". So your texture_unit in the material should look like this:
texture_unit
{
texture RttTex
}
If you need to change the texture the shader should use during runtime, just add the two following lines:
Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().getByName
("Sepia");
material->getTechnique(0)->getPass(0)->getTextureUnitState(0)-
>setTextureName("OtherRttTex");
10 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...
With the first line we get a pointer to the material (in this case here a sepia shader material) where we change
the texture name in the second line to the desired one.
Now, as you have set the correct texture name in your material script with one of these two methods, you can
access the texture in your cg shader with the following line:
uniform sampler2D SceneSampler : register(s0)
Well, that's it. Your texture of the mini screen should now be passed through your shader and look like this (with
the sepia shader attached to this tutorial page):
Conclusion
These are all the basics you need to know to start with RTT. Now play around a bit with this code and discover a
new world of graphical effects.
Alias: Intermediate_Tutorial_7
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
11 de 15Página Intermediate Tutorial 7e
21-05-2012http://www.ogre3d.org/tikiwiki/tiki-print.php?page_ref_id=279&page=Intermediate...