Intermediate Tutorial 2: RaySceneQueries and Basic Mouse Usage

Ported to VB.NET by Aeauseth

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. How to select x and y coordinates on the terrain based on your camera view


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. We will gloss over the MOIS keyboard & mouse sections so you may want to review Tutorial 4.

Getting Started

First, you need to create a new VB.NET console Application for the demo. Make the necessary MOgre changes as described in Basic Tutorial 0. Replace the contents of Module1.vb with:

 <font color='blue'>Imports</font> Mogre
 
 
 <font color='blue'>Module</font> Module1
     <font color='blue'>Public</font> myKeyboard <font color='blue'>As</font> MOIS.Keyboard
     <font color='blue'>Public</font> myMouse <font color='blue'>As</font> MOIS.Mouse
     <font color='blue'>Public</font> myCamera <font color='blue'>As</font> Camera
     <font color='blue'>Public</font> MyWindow <font color='blue'>As</font> RenderWindow
     <font color='blue'>Public</font> myScene <font color='blue'>As</font> -SceneManager
     <font color='blue'>Public</font> myTranslation <font color='blue'>As</font> Vector3 = Vector3.ZERO
     <font color='blue'>Public</font> Quitting <font color='blue'>As</font> <font color='blue'>Boolean</font> = <font color='blue'>False
 </font>    <font color='blue'>Public</font> myRotating <font color='blue'>As</font> <font color='blue'>Boolean</font> = <font color='blue'>False
 </font>    <font color='blue'>Public</font> myLeftMouseDown <font color='blue'>As</font> <font color='blue'>Boolean</font> = <font color='blue'>False
 </font>    <font color='blue'>Public</font> myCurrentObject <font color='blue'>As</font> -SceneNode
     <font color='blue'>Public</font> myRoot <font color='blue'>As</font> Root
 
     <font color='blue'>Sub</font> Main()
 
         <font color='blue'>Try
 
 </font>            <font color='green'>'Creating the Root Object 
 </font>            myRoot = <font color='blue'>New</font> Root(<font color='darkred'>"Plugins.cfg"</font>, <font color='darkred'>"ogre.cfg"</font>, <font color='darkred'>"ogre.log"</font>)
 
             <font color='green'>'Defining the Resources 
 </font>            <font color='blue'>Dim</font> cf <font color='blue'>As</font> <font color='blue'>New</font> ConfigFile
             cf.Load(<font color='darkred'>"resources.cfg"</font>, vbTab + <font color='darkred'>":="</font>, <font color='blue'>True</font>)
             <font color='blue'>Dim</font> seci <font color='blue'>As</font> ConfigFile.SectionIterator = cf.GetSectionIterator
             <font color='blue'>Dim</font> secName <font color='blue'>As</font> <font color='blue'>String</font>, typeName <font color='blue'>As</font> <font color='blue'>String</font>, archName <font color='blue'>As</font> <font color='blue'>String
 </font>            <font color='blue'>While</font> (seci.MoveNext())
                 secName = seci.CurrentKey
                 <font color='blue'>Dim</font> settings <font color='blue'>As</font> ConfigFile.SettingsMultiMap = seci.Current
                 <font color='blue'>For</font> <font color='blue'>Each</font> pair <font color='blue'>As</font> KeyValuePair(<font color='blue'>Of</font> <font color='blue'>String</font>, <font color='blue'>String</font>) <font color='blue'>In</font> settings
                     typeName = pair.Key
                     archName = pair.Value
                     <font color='blue'>Select</font> <font color='blue'>Case</font> typeName
                         <font color='blue'>Case</font> <font color='darkred'>"FileSystem"
 </font>                            <font color='blue'>If</font> <font color='blue'>Not</font> IO.Directory.Exists(archName) <font color='blue'>Then
 </font>                                <font color='blue'>If</font> IO.Directory.Exists(<font color='darkred'>"../../"</font> & archName) <font color='blue'>Then
 </font>                                    archName = <font color='darkred'>"../../"</font> & archName
                                 <font color='blue'>End</font> <font color='blue'>If
 </font>                            <font color='blue'>End</font> <font color='blue'>If
 </font>                        <font color='blue'>Case</font> <font color='darkred'>"Zip"
 </font>                            <font color='blue'>If</font> <font color='blue'>Not</font> IO.File.Exists(archName) <font color='blue'>Then
 </font>                                <font color='blue'>If</font> IO.File.Exists(<font color='darkred'>"../../"</font> & archName) <font color='blue'>Then
 </font>                                    archName = <font color='darkred'>"../../"</font> & archName
                                 <font color='blue'>End</font> <font color='blue'>If
 </font>                            <font color='blue'>End</font> <font color='blue'>If
 </font>                    <font color='blue'>End</font> <font color='blue'>Select
 </font>                    ResourceGroupManager.Singleton.AddResourceLocation(archName, typeName, secName)
                 <font color='blue'>Next
 </font>            <font color='blue'>End</font> <font color='blue'>While
 
 </font>            <font color='green'>'Setting up the RenderSystem
 </font>            <font color='blue'>If</font> <font color='blue'>Not</font> myRoot.RestoreConfig <font color='blue'>Then
 </font>                <font color='blue'>If</font> <font color='blue'>Not</font> myRoot.ShowConfigDialog <font color='blue'>Then
 </font>                    <font color='blue'>Exit</font> <font color='blue'>Sub
 </font>                <font color='blue'>End</font> <font color='blue'>If
 </font>            <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='green'>'Creating the Render Window
 </font>            MyWindow = myRoot.Initialise(<font color='blue'>True</font>, <font color='darkred'>"Ogre RenderWindow"</font>)
             <font color='blue'>AddHandler</font> myRoot.FrameStarted, <font color='blue'>AddressOf</font> FrameStarted
 
             <font color='green'>'Initializing Resource Groups
 </font>            TextureManager.Singleton.DefaultNumMipmaps = 5
             ResourceGroupManager.Singleton.InitialiseAllResourceGroups()
 
             <font color='green'>'Creating the Scene
 </font>            myScene = myRoot.CreateSceneManager(SceneType.ST_EXTERIOR_CLOSE)
 
             <font color='green'>'Set the default lighting
 </font>            myScene.AmbientLight = <font color='blue'>New</font> ColourValue(0.5, 0.5, 0.5)
             myScene.SetSkyDome(<font color='blue'>True</font>, <font color='darkred'>"Examples/CloudySky"</font>, 5, 8)
 
             <font color='green'>'Camera
 </font>            myCamera = myScene.CreateCamera(<font color='darkred'>"Camera"</font>)
             myCamera.SetPosition(40, 200, 580)
             myCamera.Pitch(<font color='blue'>New</font> Degree(-30))
             myCamera.Yaw(<font color='blue'>New</font> Degree(-45))
             myCamera.NearClipDistance = 5
             myRoot.AutoCreatedWindow.AddViewport(myCamera)
 
             <font color='green'>'World geometry
 </font>            myScene.SetWorldGeometry(<font color='darkred'>"terrain.cfg"</font>)
 
             <font color='green'>'Overlay
 </font>            <font color='blue'>Dim</font> myPanelOverlay <font color='blue'>As</font> Overlay = OverlayManager.Singleton.GetByName(<font color='darkred'>"Core/DebugOverlay"</font>)
             myPanelOverlay.Show()
 
             <font color='green'>'Input handler
 </font>            InputClass.Init()
 
             <font color='green'>'The Render Loop
 </font>            myRoot.StartRendering()
 
             <font color='green'>'Cleanup
 </font>            MyWindow.Dispose()
             myRoot.Dispose()
 
         <font color='blue'>Catch</font> ex <font color='blue'>As</font> System.Runtime.InteropServices.SEHException
             <font color='blue'>If</font> OgreException.IsThrown <font color='blue'>Then
 </font>                MsgBox(OgreException.LastException.FullDescription, MsgBoxStyle.Critical, _
                      <font color='darkred'>"An Ogre SEHException has occured!"</font>)
             <font color='blue'>Else
 </font>                MsgBox(ex.ToString, <font color='darkred'>"An error has occured"</font>)
             <font color='blue'>End</font> <font color='blue'>If
 </font>        <font color='blue'>End</font> <font color='blue'>Try
 
 </font>    <font color='blue'>End</font> <font color='blue'>Sub
 
 </font>    <font color='blue'>Public</font> <font color='blue'>Function</font> FrameStarted(<font color='blue'>ByVal</font> e <font color='blue'>As</font> FrameEvent) <font color='blue'>As</font> <font color='blue'>Boolean
 </font>        myMouse.Capture()
         myKeyboard.Capture()
 
         <font color='green'>'Handle player/camera movement
 </font>        InputClass.ProcessKeyboard()
 
         <font color='green'>'Camera movement
 </font>        <font color='blue'>If</font> myTranslation <> Vector3.ZERO <font color='blue'>Then
 
 </font>            myCamera.Position += myCamera.Orientation * myTranslation * e.timeSinceLastFrame
 
             <font color='green'>'Setup the scene query
 
 </font>        <font color='blue'>End</font> <font color='blue'>If
 
 </font>        <font color='green'>'Now update the robot location if left mouse button is still down
 
 </font>        <font color='green'>'Debug Overlay
 </font>        <font color='blue'>Dim</font> myAvg <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/AverageFps"</font>)
         <font color='blue'>Dim</font> myCurr <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/CurrFps"</font>)
         <font color='blue'>Dim</font> myBest <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/BestFps"</font>)
         <font color='blue'>Dim</font> myWorst <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/WorstFps"</font>)
         <font color='blue'>Dim</font> myNumTris <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/NumTris"</font>)
         <font color='blue'>Dim</font> myNumBatches <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/NumBatches"</font>)
         <font color='blue'>Dim</font> myDebug <font color='blue'>As</font> OverlayElement = OverlayManager.Singleton.GetOverlayElement(<font color='darkred'>"Core/DebugText"</font>)
 
         myAvg.Caption = <font color='darkred'>"Average FPS: "</font> & Mogre.StringConverter.ToString(MyWindow.AverageFPS)
         myCurr.Caption = <font color='darkred'>"Current FPS: "</font> & Mogre.StringConverter.ToString(MyWindow.LastFPS)
         myBest.Caption = <font color='darkred'>"Best FPS: "</font> & Mogre.StringConverter.ToString(MyWindow.BestFPS)
         myWorst.Caption = <font color='darkred'>"Worst FPS: "</font> & Mogre.StringConverter.ToString(MyWindow.WorstFPS)
         myNumTris.Caption = <font color='darkred'>"Triangle Count: "</font> & Mogre.StringConverter.ToString(MyWindow.TriangleCount)
         myNumBatches.Caption = <font color='darkred'>"Batch Count: "</font> & Mogre.StringConverter.ToString(MyWindow.BatchCount)
 
 
 
         <font color='blue'>Return</font> <font color='blue'>Not</font> Quitting
     <font color='blue'>End</font> <font color='blue'>Function
 
 </font>    <font color='blue'>Public</font> <font color='blue'>Class</font> InputClass
 
         <font color='blue'>Const</font> TRANSLATE <font color='blue'>As</font> <font color='blue'>Single</font> = 200
         <font color='blue'>Const</font> ROTATE <font color='blue'>As</font> <font color='blue'>Single</font> = 0.003
 
         <font color='blue'>Shared</font> <font color='blue'>Sub</font> Init()
             <font color='green'>'Keyboard
 </font>            <font color='blue'>Dim</font> windowHnd <font color='blue'>As</font> <font color='blue'>Integer
 </font>            MyWindow.GetCustomAttribute(<font color='darkred'>"WINDOW"</font>, windowHnd)
             <font color='blue'>Dim</font> myInputManager <font color='blue'>As</font> MOIS.InputManager = MOIS.InputManager.CreateInputSystem(windowHnd)
             myKeyboard = myInputManager.CreateInputObject(MOIS.Type.OISKeyboard, <font color='blue'>False</font>)
             <font color='blue'>AddHandler</font> myKeyboard.KeyPressed, <font color='blue'>AddressOf</font> InputClass.KeyPressed
             <font color='blue'>AddHandler</font> myKeyboard.KeyReleased, <font color='blue'>AddressOf</font> InputClass.KeyReleased
 
             <font color='green'>'Mouse
 </font>            myMouse = myInputManager.CreateInputObject(MOIS.Type.OISMouse, <font color='blue'>True</font>)
             <font color='blue'>AddHandler</font> myMouse.MouseMoved, <font color='blue'>AddressOf</font> InputClass.MouseMovedListener
             <font color='blue'>AddHandler</font> myMouse.MousePressed, <font color='blue'>AddressOf</font> InputClass.MousePressedListener
             <font color='blue'>AddHandler</font> myMouse.MouseReleased, <font color='blue'>AddressOf</font> InputClass.MouseReleasedListener
 
         <font color='blue'>End</font> <font color='blue'>Sub
 
 </font>        <font color='blue'>Shared</font> <font color='blue'>Function</font> KeyPressed(<font color='blue'>ByVal</font> e <font color='blue'>As</font> MOIS.KeyEvent) <font color='blue'>As</font> <font color='blue'>Boolean
 </font>            <font color='green'>'Currently unused by this application
 
 </font>            <font color='blue'>Return</font> <font color='blue'>Nothing
 </font>        <font color='blue'>End</font> <font color='blue'>Function
 
 </font>        <font color='blue'>Shared</font> <font color='blue'>Function</font> KeyReleased(<font color='blue'>ByVal</font> e <font color='blue'>As</font> MOIS.KeyEvent) <font color='blue'>As</font> <font color='blue'>Boolean
 
 </font>            <font color='green'>'This function is just a placeholder
 </font>            <font color='green'>'It is unlikely you will ever use this
 </font>            <font color='green'>'Typically you either process unbuffered keyboard input (as in ProcessKeyboard)
 </font>            <font color='green'>'or you process buffered Keypress
 
 </font>            <font color='blue'>Return</font> <font color='blue'>Nothing
 </font>        <font color='blue'>End</font> <font color='blue'>Function
 
 </font>        <font color='blue'>Shared</font> <font color='blue'>Sub</font> ProcessKeyboard()
 
             <font color='green'>'This Sub is typically called via the FrameStarted event.
 
 </font>            <font color='green'>'Clear previous translation
 </font>            myTranslation.z = 0
             myTranslation.x = 0
             myTranslation.y = 0
 
             <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_ESCAPE) <font color='blue'>Then
 </font>                Quitting = <font color='blue'>True
 </font>            <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_UP) <font color='blue'>Or</font> _
                 myKeyboard.IsKeyDown(MOIS.KeyCode.KC_W) <font color='blue'>Then
 </font>                myTranslation.z += -TRANSLATE
             <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_S) <font color='blue'>Or</font> _
                 myKeyboard.IsKeyDown(MOIS.KeyCode.KC_DOWN) <font color='blue'>Then
 </font>                myTranslation.z += TRANSLATE
             <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_A) <font color='blue'>Or</font> _
                 myKeyboard.IsKeyDown(MOIS.KeyCode.KC_LEFT) <font color='blue'>Then
 </font>                myTranslation.x += -TRANSLATE
             <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_D) <font color='blue'>Or</font> _
                 myKeyboard.IsKeyDown(MOIS.KeyCode.KC_RIGHT) <font color='blue'>Then
 </font>                myTranslation.x += TRANSLATE
             <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_Q) <font color='blue'>Or</font> _
             myKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGUP) <font color='blue'>Or</font> _
             myKeyboard.IsKeyDown(MOIS.KeyCode.KC_SPACE) <font color='blue'>Then
 </font>                myTranslation.y += TRANSLATE
             <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> myKeyboard.IsKeyDown(MOIS.KeyCode.KC_Z) <font color='blue'>Or</font> _
                 myKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGDOWN) <font color='blue'>Then
 </font>                myTranslation.y += -TRANSLATE
             <font color='blue'>End</font> <font color='blue'>If
 </font>        <font color='blue'>End</font> <font color='blue'>Sub
 
 </font>        <font color='blue'>Shared</font> <font color='blue'>Function</font> MouseMovedListener(<font color='blue'>ByVal</font> e <font color='blue'>As</font> MOIS.MouseEvent) <font color='blue'>As</font> <font color='blue'>Boolean
 </font>            <font color='blue'>Static</font> myLastX <font color='blue'>As</font> <font color='blue'>Integer</font> = e.state.X.abs
             <font color='blue'>Static</font> myLastY <font color='blue'>As</font> <font color='blue'>Integer</font> = e.state.Y.abs
 
             <font color='blue'>If</font> myRotating <font color='blue'>Then
 </font>                myCamera.Yaw(e.state.X.rel * -ROTATE)
                 myCamera.Pitch(e.state.Y.rel * -ROTATE)
             <font color='blue'>End</font> <font color='blue'>If
 
 </font>            
         <font color='blue'>End</font> <font color='blue'>Function
 
 </font>        <font color='blue'>Shared</font> <font color='blue'>Function</font> MousePressedListener(<font color='blue'>ByVal</font> e <font color='blue'>As</font> MOIS.MouseEvent, <font color='blue'>ByVal</font> id <font color='blue'>As</font> MOIS.MouseButtonID) <font color='blue'>As</font> <font color='blue'>Boolean
 </font>            <font color='blue'>If</font> e.state.ButtonDown(MOIS.MouseButtonID.MB_Right) <font color='blue'>Then
 </font>                myRotating = <font color='blue'>True
 </font>            <font color='blue'>End</font> <font color='blue'>If
 
 </font>        <font color='blue'>End</font> <font color='blue'>Function
 
 </font>        <font color='blue'>Shared</font> <font color='blue'>Function</font> MouseReleasedListener(<font color='blue'>ByVal</font> e <font color='blue'>As</font> MOIS.MouseEvent, <font color='blue'>ByVal</font> id <font color='blue'>As</font> MOIS.MouseButtonID) <font color='blue'>As</font> <font color='blue'>Boolean
 </font>            <font color='blue'>If</font> <font color='blue'>Not</font> e.state.ButtonDown(MOIS.MouseButtonID.MB_Right) <font color='blue'>Then
 </font>                myRotating = <font color='blue'>False
 </font>            <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> <font color='blue'>Not</font> e.state.ButtonDown(MOIS.MouseButtonID.MB_Left) <font color='blue'>Then
 </font>                myLeftMouseDown = <font color='blue'>False
 </font>            <font color='blue'>End</font> <font color='blue'>If
 </font>        <font color='blue'>End</font> <font color='blue'>Function
 
 </font>    <font color='blue'>End</font> <font color='blue'>Class
 
 End</font> <font color='blue'>Module</font>

Be sure this code compiles before continuing.

Setting up the Scene

The scene has already been setup for you. Keyboard AWSD and mousemovement is also working.

You may have noticed that the mouse cursor is missing. The C++ version of this tutorial gives an introduction to CEGUI which is a GUI addon to Ogre. Unfortunatly CEGUI is not working properly with MOgre 1.4.6 SDK (Reference: Mogre CEGUI). The mouse cursor isn't really an important piece to this tutorial, so we will just ignore the lack of a mouse cursor for now.

Terrain Collision Detection

We are now going to make it so that when we move towards the terrain, we cannot pass through it. Since the BaseFrameListener already handles moving the camera, we going to add a bit of code to that section.

Go to the FrameStarted Function and find the Camera movement section. Our goal is to find the camera's current position, and fire a Ray straight down it 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.

         <font color='green'>'Camera movement
 </font>        <font color='blue'>If</font> myTranslation <> Vector3.ZERO <font color='blue'>Then
 
 </font>            myCamera.Position += myCamera.Orientation * myTranslation * e.timeSinceLastFrame
 
             <font color='green'>'Setup the scene query
 </font>            <font color='blue'>Dim</font> camPos <font color='blue'>As</font> Vector3 = myCamera.Position
             <font color='blue'>Dim</font> cameraRay <font color='blue'>As</font> Ray = <font color='blue'>New</font> Ray(<font color='blue'>New</font> Vector3(camPos.x, 5000, camPos.z), Vector3.NEGATIVE_UNIT_Y)
             <font color='blue'>Dim</font> myRaySceneQuery <font color='blue'>As</font> RaySceneQuery = myScene.CreateRayQuery(cameraRay)
             <font color='blue'>Dim</font> results <font color='blue'>As</font> RaySceneQueryResult = myRaySceneQuery.Execute 

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 is under the terrain. Now we need to execute the query and get the results. The result of the query is basically (oversimplification here) a collection of worldFragments (in this case the Terrain) and a list of movables (we will cover movables in a later tutorial). In the next demo we will have to deal with multiple return values for SceneQuerys. For now, we'll just do some hand waving and move through it. We make sure we have at least two values and pick the 1st one (our terrain).

             <font color='green'>'There should be at least 2 results, 1st one is the terrain.
 </font>            <font color='blue'>If</font> results.Count >= 2 <font color='blue'>Then</font> 

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 the camera NearClipDistance is 5, so 10 just makes sure we don't clip the terrain.

                 <font color='blue'>Dim</font> result <font color='blue'>As</font> RaySceneQueryResultEntry = results.Item(0)
                 <font color='blue'>Dim</font> terrainHeight <font color='blue'>As</font> <font color='blue'>Single</font> = result.worldFragment.singleIntersection.y
                 <font color='blue'>If</font> terrainHeight + 10 > camPos.y <font color='blue'>Then
 </font>                    myCamera.SetPosition(camPos.x, terrainHeight + 10, camPos.z)
                 <font color='blue'>End</font> <font color='blue'>If
 </font>            <font color='blue'>End</font> <font color='blue'>If
 </font>            myRaySceneQuery.Dispose()
         <font color='blue'>End</font> <font color='blue'>If</font>
 

At this point you should compile and test your program. Try to fly thru the terrain. Fly to the edge and try going under the terrain.

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 InputClass.MousePressedListener function to do something different when you click the left mouse button. Replace the MousePressedListener code with the following:

             <font color='blue'>If</font> e.state.ButtonDown(MOIS.MouseButtonID.MB_Right) <font color='blue'>Then
 </font>                myRotating = <font color='blue'>True
 </font>            <font color='blue'>End</font> <font color='blue'>If
 
 </font>            <font color='blue'>If</font> e.state.ButtonDown(MOIS.MouseButtonID.MB_Left) <font color='blue'>Then
 </font>                myLeftMouseDown = <font color='blue'>True
 
 </font>                <font color='green'>'Find location we are aiming at
 </font>                <font color='blue'>Dim</font> mouseRay <font color='blue'>As</font> Ray = myCamera.GetCameraToViewportRay(0.5, 0.5)
                 <font color='blue'>Dim</font> myRaySceneQuery <font color='blue'>As</font> RaySceneQuery = myScene.CreateRayQuery(mouseRay)
 
                 <font color='blue'>Dim</font> results <font color='blue'>As</font> RaySceneQueryResult = myRaySceneQuery.Execute
 
 
                 <font color='green'>'There should be at least 2 results, 1st one is terrain
 </font>                <font color='blue'>If</font> results.Count >= 2 <font color='blue'>Then
 </font>                    <font color='blue'>Static</font> intRobot <font color='blue'>As</font> <font color='blue'>Integer</font> = 0
                     intRobot += 1
                     <font color='blue'>Dim</font> RobotName <font color='blue'>As</font> <font color='blue'>String</font> = <font color='darkred'>"Robot"</font> & intRobot
                     <font color='blue'>Dim</font> result <font color='blue'>As</font> RaySceneQueryResultEntry = results.Item(0)
                     <font color='blue'>Dim</font> myEntity <font color='blue'>As</font> Entity = myScene.CreateEntity(RobotName, <font color='darkred'>"robot.mesh"</font>)
                     myEntity.GetAnimationState(<font color='darkred'>"Idle"</font>).Loop = <font color='blue'>True
 </font>                    myEntity.GetAnimationState(<font color='darkred'>"Idle"</font>).Enabled = <font color='blue'>True
 </font>                    myCurrentObject = myScene.RootSceneNode.CreateChildSceneNode(RobotName & <font color='darkred'>"Node"</font>, result.worldFragment.singleIntersection)
                     myCurrentObject.AttachObject(myEntity)
                     myCurrentObject.SetScale(0.1, 0.1, 0.1)
                 <font color='blue'>Else
 </font>                    <font color='blue'>Stop
 </font>                <font color='blue'>End</font> <font color='blue'>If
 </font>                myRaySceneQuery.Dispose()
 
             <font color='blue'>End</font> <font color='blue'>If</font>

The first piece of code will look very familiar. We will be creating a Ray to use with the myRaySceneQuery object, and setting the Ray. Ogre provides us with 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. In our case we don't have a mouse cursor we we just pick the center of the screen for a ray trace; the GetCameraToViewportRay(0.5, 0.5) is the center of the screen. If we had a mouse cursor then it would be GetCameraToViewportRay(Mouse.X/Screen.Width, Mouse.Y/Screen.Height)

Remember that each entity needs a unique name, so we make one. About the only thing of note here is the public variable MyCurrentObject; which will be used later along with myLeftMouseDown.

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.

This next chunk of code should be self explanatory now. We create a Ray based on center of the screen, we 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 is valid or not, because myLMouseDown would not be true if mCurrentObject had not been set by mousePressed.

         <font color='green'>'Now update the robot location if left mouse button is still down
 </font>        <font color='blue'>If</font> myLeftMouseDown <font color='blue'>Then
 </font>            <font color='green'>'Find location we are aiming at
 </font>            <font color='blue'>Dim</font> mouseRay <font color='blue'>As</font> Ray = myCamera.GetCameraToViewportRay(0.5, 0.5)
             <font color='blue'>Dim</font> myRaySceneQuery <font color='blue'>As</font> RaySceneQuery = myScene.CreateRayQuery(mouseRay)
 
             <font color='blue'>Dim</font> results <font color='blue'>As</font> RaySceneQueryResult = myRaySceneQuery.Execute
 
             <font color='green'>'There should be only 2 result, the terrain.sss
 </font>            <font color='green'>'Other results might be less, for example fell off terrain
 </font>            <font color='blue'>If</font> results.Count >= 2 <font color='blue'>Then
 </font>                myCurrentObject.Position = results.Item(0).worldFragment.singleIntersection
             <font color='blue'>End</font> <font color='blue'>If
 </font>            myRaySceneQuery.Dispose()
         <font color='blue'>End</font> <font color='blue'>If</font>
 

Compile and run the program. We are now finished! Your result should look something like this, after some strategic clicking:
MOgre_VB_Intermediate_Tutorial2a.jpg

Notice: You (the Ray's origin) must be over the Terrain for the RaySceneQuery to report the intersection when using the TerrainSceneManager.

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 MOgre VB.NET Intermediate Tutorial 3 Mouse Picking (3D Object Selection) and SceneQuery Masks


Category:Tutorials
Category:MOGRE

<HR>
Creative Commons Copyright -- Some rights reserved.


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:
    • Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
    • Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
    • Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).


The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.

4. Restrictions

The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:

  • You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
  • You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
  • If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.

5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

6. Limitation on Liability.

EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. Termination

  • This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
  • Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.

8. Miscellaneous

  • Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
  • Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
  • If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
  • No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
  • This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.