History: Intermediate Tutorial 2
Source of version: 21
- «
- »
Copy to clipboard
%tutorialhelp%
{BOX(title="WARNING!",bg="#FFBB00")}As of OGRE 1.8, this tutorial is not compatible, due to the removal of the Terrain Scene Manager!{BOX}
{maketoc}
!!Introduction
In this tutorial we will create the beginnings of a basic Scene Editor. During this process, we will cover:
# How to use RaySceneQueries to keep the camera from falling through the terrain
# How to use the MouseListener and MouseMotionListener interfaces
# Using the mouse to select x and y coordinates on the terrain
Here is the ((IntermediateTutorial2Source|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.
* __Changes needed for Ogre 1.8 and above__:
- The Terrain Scene Manager is obsolete and has been replaced by the terrain component system. Please go through ''Basic Tutorial 3'' to learn how to use it.
- For now, use the terrain generated in Basic Tutorial 3 for this tutorial. There will be glitches (such as the camera being able to go through parts of the terrain if you follow this tutorial's code).
- This tutorial will ask you to use RaySceneQueries to find objects of type worldFragment. General-purpose queries are no longer used for this. Instead, call the rayIntersects() function in your TerrainGroup object. It takes an __Ogre::Ray__ as an argument (and a distance limit as an optional second argument, of type Real) and returns an __Ogre::TerrainGroup::RayResult__ object (ex. __Ogre::TerrainGroup::RayResult result = terrain_group->rayIntersects(mouse_ray)__). __RayResult__ has three public members: __bool hit__, true if the given ray hit the TerrainGroup object, __Ogre::Vector3 position__, the x, y and z coordinates of the point of intersection and __Ogre::Terrain terrain__, the terrain object that was hit.
!!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| Basic Tutorial 7 CEGUI and Ogre)) To ensure that this tutorial will work as expected.
%note% __NOTE:__ If you want to try the tutorial without using CEGUI you can find the source code using the built in SdkTrays ((IntermediateTutorial2SdkTraysSource|here.))
!!Getting Started
First, you need to create a new project and add the following code:
{CODE(caption="ITutorial02 header",wrap="1", colors="c++")}
#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_
{CODE}
{CODE(caption="ITutorial02 implementation",wrap="1", colors="c++")}
#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" {
#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
{CODE}
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 : {MONO()}/usr/local/include/CEGUI{MONO} (on Linux)
-> GCC C++ Linker -> Linker -> Libraries
and this to Libraries (-l): {MONO()}CEGUIOgreRenderer{MONO}
!!Setting up the Scene
Go to the {MONO()}ITutorial02::createScene{MONO} 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:
{CODE(wrap="1", colors="c++")} // 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));{CODE}
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 {MONO()}bootstrapSystem(){MONO} method will do all the required setup for us. Note that this also makes CEGUI use Ogre's resource management system.
{CODE(wrap="1", colors="c++")} // CEGUI setup
mGUIRenderer = &CEGUI::OgreRenderer::bootstrapSystem();{CODE}
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.
{CODE(wrap="1", colors="c++")} // Mouse
CEGUI::SchemeManager::getSingleton().create((CEGUI::utf8*)"TaharezLook.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");{CODE}
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:
{CODE(wrap="1", colors="ini")} [CEGUI]
FileSystem=/usr/share/CEGUI/schemes
FileSystem=/usr/share/CEGUI/fonts
FileSystem=/usr/share/CEGUI/imagesets
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{CODE}
!!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):
{CODE(wrap="1", colors="c++")}
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;{CODE}
The {MONO()}mRaySceneQuery{MONO} variable holds a copy of the {MONO()}RaySceneQuery{MONO} we will be using to find the coordinates on the terrain. The {MONO()}mLMouseDown{MONO} and {MONO()}mRMouseDown{MONO} variables will track whether we have the mouse held down (IE {MONO()}mLMouseDown{MONO} is true when the user holds down the left mouse button, false otherwise). {MONO()}mCount{MONO} counts the number of entities we have on screen. {MONO()}mCurrentObject{MONO} holds a pointer to the most recently created SceneNode (we will be using this to "drag" the entity around). Finally, {MONO()}mGUIRenderer{MONO} 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
Go to the {MONO()}createFrameListener{MONO} method, and add the following initialization code after the call to {MONO()}BaseApplication::createFrameListener(){MONO}. Note that we are also reducing rotation speed of the camera since the Terrain is fairly small.
{CODE(wrap="1", colors="c++")} // Setup default variables
mCount = 0;
mCurrentObject = NULL;
mLMouseDown = false;
mRMouseDown = false;
// Reduce rotate speed
mRotateSpeed =.1;{CODE}
Finally, we need to create the {MONO()}RaySceneQuery{MONO} object. This is done with a call to the {MONO()}SceneManager{MONO}:
{CODE(wrap="1", colors="c++")} // Create RaySceneQuery
mRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray());{CODE}
This is all we need for {MONO()}createFrameListener(){MONO}, but if we create a RaySceneQuery, we must later destroy it. Go to the {MONO()}ITutorial02{MONO} destructor ({MONO()}~ITutorial02{MONO}) and add the following line:
{CODE(wrap="1", colors="c++")} // We created the query, and we are also responsible for deleting it.
mSceneMgr->destroyQuery(mRaySceneQuery);{CODE}
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 {MONO()}mRMouseButton{MONO} to true when the right mouse button is pressed
* set {MONO()}mRMouseButton{MONO} 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 {MONO()}ITutorial02::mouseMoved{MONO} method. We will be adding code to move the mouse cursor every time the mouse has been moved. Add this code to the function:
{CODE(wrap="1", colors="c++")} // Update CEGUI with the mouse motion
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);{CODE}
Now find the {MONO()}ITutorial02::mousePressed{MONO} method. This chunk of code hides the cursor when the right mouse button goes down, and sets the {MONO()}mRMouseDown{MONO} variable to true.
{CODE(wrap="1", colors="c++")} // 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{CODE}
Next we need to show the mouse cursor again and toggle {MONO()}mRMouseDown{MONO} when the right button is let up. Find the {MONO()}mouseReleased{MONO} function, and add this code:
{CODE(wrap="1", colors="c++")} // 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{CODE}
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 {MONO()}ITutorial::mouseMoved{MONO} function and add the following code just before the return statement:
{CODE(wrap="1", colors="c++")} // 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));
mCamera->pitch(Ogre::Degree(-arg.state.Y.rel * mRotateSpeed));
} // else if{CODE}
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 {MONO()}BaseApplication::createFrameListener(){MONO} already handles the camera movement, we are not going to touch that code. Instead, after {MONO()}BaseApplication::createFrameListener(){MONO} 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 {MONO()}RaySceneQuery{MONO} 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 {MONO()}ITutorial02::frameRenderingQueued(){MONO} method and remove its contents. The first thing we are going to do is call the {MONO()}BaseApplication::frameRenderingQueued{MONO} method to do all of its normal functions. If it returns false, we will return false as well.
{CODE(wrap="1", colors="c++")} // 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;{CODE}
We do this at the top of our {MONO()}frameRenderingQueued{MONO} function because the {MONO()}BaseApplication::frameRenderingQueued{MONO} 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 {MONO()}Ray{MONO} straight down into the terrain. This is called a {MONO()}RaySceneQuery{MONO}, 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 {MONO()}Ray{MONO} takes in an origin (where the ray starts), and a direction. In this case our direction will be {MONO()}NEGATIVE_UNIT_Y{MONO}, since we are pointing the ray straight down. Once we have created the ray, we tell the {MONO()}RaySceneQuery{MONO} object to use it.
{CODE(wrap="1", colors="c++")} // 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);{CODE}
Note that we have used a height of {MONO()}5000.0f{MONO} 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 {MONO()}std::iterator{MONO}, which I will briefly describe.
{CODE(wrap="1", colors="c++")} // Perform the scene query
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin();{CODE}
The result of the query is basically (oversimplification here) a list of {MONO()}worldFragments{MONO} (in this case the Terrain) and a list of {MONO()}movables{MONO} (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 {MONO()}result.begin() == result.end(){MONO}, then there were no results to return. In the next tutorial we will have to deal with multiple return values for {MONO()}SceneQuery{MONO}s. 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 ( {MONO()}itr != result.end(){MONO} ), and that the result is the terrain ({MONO()}itr->worldFragment{MONO}).
{CODE(wrap="1", colors="c++")} // Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{{CODE}
The {MONO()}worldFragment{MONO} struct contains the location where the {MONO()}Ray{MONO} hit the terrain in the {MONO()}singleIntersection{MONO} variable (which is a {MONO()}Vector3{MONO}). 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.
{CODE(wrap="1", colors="c++")} Ogre::Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );
}
return true;{CODE}
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 {MONO()}mousePressed{MONO} function to do something different when you click the left mouse button. Find the following code in the {MONO()}ITutorial02::mousePressed{MONO} function. We will be adding code __inside__ this if statement.
{CODE(wrap="1", colors="c++")} // Left mouse button down
if (id == OIS::MB_Left)
{
mLMouseDown = true;
} // if{CODE}
The first piece of code will look very familiar. We will be creating a {MONO()}Ray{MONO} to use with the {MONO()}mRaySceneQuery{MONO} object, and setting the {MONO()}Ray{MONO}. Ogre provides us with {MONO()}Camera::getCameraToViewportRay;{MONO} a nice function that translates a click on the screen (x and y coordinates) into a {MONO()}Ray{MONO} that can be used with a {MONO()}RaySceneQuery{MONO} object.
{CODE(wrap="1", colors="c++")} // 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);{CODE}
Next we will execute the query and make sure it returned a result.
{CODE(wrap="1", colors="c++")} // 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)
{{CODE}
Now that we have the {MONO()}worldFragment{MONO} (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 {MONO()}Entity{MONO} and {MONO()}SceneNode{MONO} in ogre needs a unique name. To accomplish this we are going to name each {MONO()}Entity{MONO} "Robot1", "Robot2", "Robot3"... and each {MONO()}SceneNode{MONO} "Robot1Node", "Robot2Node", "Robot3Node"... and so on. First we create the name (consult a reference on C for more information on {MONO()}sprintf{MONO}).
{CODE(wrap="1", colors="c++")} char name[16];
sprintf( name, "Robot%d", mCount++ );{CODE}
Next we create the {MONO()}Entity{MONO} and {MONO()}SceneNode{MONO}. Note that we use {MONO()}itr->worldFragment->singleIntersection{MONO} for our default position of the {MONO()}Robot{MONO}. 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 {MONO()}mCurrentObject{MONO}. We will be using that in the next section.
{CODE(wrap="1", colors="c++")} Ogre::Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(std::string(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
mLMouseDown = true;
} // if{CODE}
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:
{CODE(wrap="1", colors="c++")} // If we are dragging the left mouse button.
if (mLMouseDown)
{
} // if{CODE}
This next chunk of code should now be self explanatory. We create a {MONO()}Ray{MONO} based on the mouse's current location, then execute a {MONO()}RaySceneQuery{MONO} and move the object to the new position. Note that we don't have to check {MONO()}mCurrentObject{MONO} to see if it's the latest object or not, because {MONO()}mLMouseDown{MONO} and {MONO()}mCurrentObject{MONO} are both set in the {MONO()}mousePressed(){MONO} function: {MONO()}mLMouseDown{MONO} is set to true, and {MONO()}mCurrentObject{MONO} is set to the most recently created SceneNode.
{CODE(wrap="1", colors="c++")} 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{CODE}
Compile and run the program. We are now finished!
%note% __Note:__ You (= the Ray's origin) must be over the Terrain for the ''{MONO()}RaySceneQuery{MONO}'' to report the intersection when using the ''{MONO()}TerrainSceneManager{MONO}''.
%note% __Note:__ If you are using your own framework, make sure your scene query has access to the frame listener, e.g. your ''{MONO()}frameStarted(){MONO}'' method. Otherwise, if you use it in an ''{MONO()}init(){MONO}'' function you may get no results.
!!Exercises for Further Study
!!!Easy Exercises
# 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.
# 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
# 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
# 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
# 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.
# 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: (alias(Intermediate_Tutorial_2))