Intermediate Tutorial 3: Mouse Picking (3D Object Selection) and SceneQuery Masks

Original version by Clay Culver

Initial C# Port by DigitalCyborg

Mogre Port by lionceaudor

Note: This was written for Mogre 1.7.1 and is known to compile against it.
Any problems you encounter while working with this tutorial should be posted to the OgreDotNet 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.

Prerequisites

This tutorial will assume that you have gone through the previous tutorial. We will also be using C# Enumerators to go through multiple results of SceneQueries, so basic knowledge of them will be helpful.

Getting Started

Even though we will be editing the code from last time. Some changes was made to avoid repetition with the instanciation and the move of a robot. Check the method RobotToMouse and the two MouseEventHandler before continuing. Remember that this tutorial use the Mogre.Demo.ExampleApplication.Example class which is in the samples of Mogre SDK.
Here is the code we are starting from:

using System;
using System.Drawing;
using System.Text;
using Mogre.Demo.ExampleApplication;
using Mogre;
using System.Collections.Generic;

namespace MogreTutorial
{
  class IntermediateTutorial3 : Example
  {
    protected RaySceneQuery mRaySceneQuery = null;      // The ray scene query pointer
    protected int mCount = 0;                           // The number of robots on the screen
    protected SceneNode mCurrentObject = null;          // The newly created object

    public override void CreateInput()
    {
      MOIS.ParamList pl = new MOIS.ParamList();
      IntPtr windowHnd;
      window.GetCustomAttribute("WINDOW", out windowHnd);
      pl.Insert("WINDOW", windowHnd.ToString());
      //Non-exclusive input, show OS cursor
      //If you want to see the mouse cursor and be able to move it outside your OGRE window and use the keyboard outside the running application 
      pl.Insert("w32_mouse", "DISCL_FOREGROUND");
      pl.Insert("w32_mouse", "DISCL_NONEXCLUSIVE");
      pl.Insert("w32_keyboard", "DISCL_FOREGROUND");
      pl.Insert("w32_keyboard", "DISCL_NONEXCLUSIVE");
      inputManager = MOIS.InputManager.CreateInputSystem(pl);

      // Create all devices (except joystick, as most people have Keyboard/Mouse) using buffered input.
      inputKeyboard = (MOIS.Keyboard)inputManager.CreateInputObject(MOIS.Type.OISKeyboard, true);
      inputMouse = (MOIS.Mouse)inputManager.CreateInputObject(MOIS.Type.OISMouse, true);

      MOIS.MouseState_NativePtr mouseState = inputMouse.MouseState;
      mouseState.width = viewport.ActualWidth; //! update after resize window
      mouseState.height = viewport.ActualHeight;

      //base.CreateInput();
      inputMouse.MousePressed += new MOIS.MouseListener.MousePressedHandler(MousePressed);
      inputMouse.MouseMoved += new MOIS.MouseListener.MouseMovedHandler(MouseMotion);

      // Create RaySceneQuery
      mRaySceneQuery = sceneMgr.CreateRayQuery(new Ray());
    }

    public override void ChooseSceneManager()
    {
      // Get the SceneManager, in this case a specific for the terrain
      sceneMgr = root.CreateSceneManager(SceneType.ST_EXTERIOR_CLOSE, "SceneMgr");
    }

    protected bool MouseMotion(MOIS.MouseEvent arg)
    {
      // If we are dragging the left mouse button.
      if (arg.state.ButtonDown(MOIS.MouseButtonID.MB_Left))
      {
        MeshToMouse(arg);
      }

      return true;
    }

    protected bool MousePressed(MOIS.MouseEvent arg, MOIS.MouseButtonID id)
    {
      if (id == MOIS.MouseButtonID.MB_Left)
      {
        MeshToMouse(arg, false);
      }

      return true;
    }

    /// <summary>
    /// Place a node that contains the chosen mesh where the mouse pointed.
    /// </summary>
    /// <param name="arg">The arguement that the MouseEventHandler get</param>
    /// <param name="useCurrent">
    /// true : it will work on the mCurrentObject node.
    /// false : it will work on a new node that will be stored in mCurrentObject.
    /// </param>
    private void MeshToMouse(MOIS.MouseEvent arg, bool useCurrent=true)
    {
      // Setup the ray scene query
      float screenX = arg.state.X.abs / (float)arg.state.width;
      float screenY = arg.state.Y.abs / (float)arg.state.height;
      Ray mouseRay = camera.GetCameraToViewportRay(screenX, screenY);
      mRaySceneQuery.Ray = mouseRay;

      // Execute query
      RaySceneQueryResult result = mRaySceneQuery.Execute();
      RaySceneQueryResult.Enumerator itr = (RaySceneQueryResult.Enumerator)(result.GetEnumerator());

      // Get results, create a node/entity on the position
      if (itr != null && itr.MoveNext())
      {
        if (useCurrent)
        {
          mCurrentObject.Position = itr.Current.worldFragment.singleIntersection;
        }
        else
        {
          Entity ent = sceneMgr.CreateEntity(
                            "Robot" + mCount.ToString(), "robot.mesh");

          mCurrentObject = sceneMgr.RootSceneNode.CreateChildSceneNode(
              "RobotNode" + mCount.ToString(),
              itr.Current.worldFragment.singleIntersection);

          mCount++;
          mCurrentObject.AttachObject(ent);
          mCurrentObject.SetScale(0.1f, 0.1f, 0.1f);
        }
      }
    }

    public override void CreateFrameListener()
    {
      base.CreateFrameListener();
      Root.Singleton.FrameStarted += FrameStarted;
    }

    bool FrameStarted(FrameEvent evt)
    {
      // Setup the scene query
      Vector3 camPos = camera.Position;
      Ray cameraRay = new Ray(new Vector3(camPos.x, 5000.0f, camPos.z),
          Vector3.NEGATIVE_UNIT_Y);
      mRaySceneQuery.Ray=cameraRay;

      // Perform the scene query;
      RaySceneQueryResult result = mRaySceneQuery.Execute();
      RaySceneQueryResult.Enumerator itr = (RaySceneQueryResult.Enumerator)(result.GetEnumerator());

      // Get the results, set the camera height
      if((itr != null) && itr.MoveNext()){
        float terrainHeight = itr.Current.worldFragment.singleIntersection.y;

        if ((terrainHeight + 10.0f) > camPos.y)
          camera.SetPosition(camPos.x, terrainHeight + 10.0f, camPos.z);
      }
      return true;
    }

    public override void CreateScene()
    {
      // Set ambient light 
      sceneMgr.AmbientLight=new ColourValue(0.5f, 0.5f, 0.5f);

      // World geometry & Sky
      sceneMgr.SetSkyDome(true, "Examples/CloudySky", 5, 8);
      sceneMgr.SetWorldGeometry("terrain.cfg");

      // Set camera look point           
      camera.Position=new Vector3(40, 100, 280);
      camera.Pitch(new Radian(new Degree(-30)));
      camera.Yaw(new Radian(new Degree(-45)));
    }

    public IntermediateTutorial3()
      : base()
    {
    }

    public override void DestroyScene()
    {
      base.DestroyScene();
      mRaySceneQuery.Dispose();
    }
  }
}

namespace Mogre.Demo.CameraTrack
{
  using MogreTutorial;

  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        IntermediateTutorial3 app = new IntermediateTutorial3();
        app.Go();
      }
      catch (System.Runtime.InteropServices.SEHException)
      {
        // Check if it's an Ogre Exception
        if (OgreException.IsThrown)
          ExampleApplication.Example.ShowOgreException();
        else
          throw;
      }
    }
  }

}

Be sure you can compile and run this code before continuing. It should work the exact same as 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 have a way for the user to know which object he's currently manipulating. 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, just above the call to MeshToMouse:

// Turn off bounding box.
        if ( mCurrentObject != null)
          mCurrentObject.ShowBoundingBox = false;

Then add the following code below the call to MeshToMouse:

// Show the bounding box to highlight the selected object
        if ( mCurrentObject != null)
            mCurrentObject.ShowBoundingBox=true;

Now the mCurrentObject is always highlighted on the screen. cool!

Adding Ninjas

Whoa. 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. We will make the space key be our mode switching button.

First, we will setup the MouseQueryListener 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 variables section of the class and add this variable:

bool mRobotMode = true;            // The current state

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 mRobotMode variable. Locate this code in MeshToMouse:

Entity ent = mSceneManager.CreateEntity("Robot" + mCount.ToString(), "robot.mesh");

The replacement should be straight forward. Depending on the mRobotMode state we either put out a Robot or a Ninja, and name it accordingly:

Entity ent;
                if (mRobotMode)
                {
                    ent = sceneMgr.CreateEntity("Robot" + mCount.ToString(), "robot.mesh");
                }
                else
                {
                    ent = sceneMgr.CreateEntity("Ninja" + mCount.ToString(), "ninja.mesh");
                }


Now, we'll make the state's changing in an KeyPressedHandler. First add this line in the CreateInput method(above the creation of the RaySceneQuery):

inputKeyboard.KeyPressed += new MOIS.KeyListener.KeyPressedHandler(OnKeyPressed);


We have now to create the OnKeyPressed method

protected bool OnKeyPressed(MOIS.KeyEvent arg)
    {
      switch (arg.key)
      {
        case MOIS.KeyCode.KC_SPACE:
          mRobotMode = !mRobotMode;
          break;
      }

      return true;
    }

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 look at the struct briefly.)

The RaySceneQueryResult returns an Enumerator 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 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 lets look at an example. Lets say we wanted to print out a list of results after 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.)
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.

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 equal 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. Change the if statement in the MeshToMouse method for this while loop:

//ok.. what we need to do, is iterate through the list and see if there
      //are any objects that aren't tile.
      //if there are.. this is the one to MOVE!!
      // Get results, create a node/entity on the position
      bool placeObject = true;
      while (itr.MoveNext())
      {
        if ((itr.Current.movable != null) &&
            (itr.Current.movable.Name.Substring(0, 4) != "tile"))
        {
          //System.Console.WriteLine("movable Object {0}", itr.Current.movable.GetName());
          if (!useCurrent)
          {
            mCurrentObject = itr.Current.movable.ParentSceneNode;
            placeObject = false;
          }
          break;
        }
      }

      if (placeObject)
      {
        itr.Reset();
        itr.MoveNext();
        if (useCurrent)
        {
          mCurrentObject.Position = itr.Current.worldFragment.singleIntersection;
        }
        else
        {
          Entity ent;
          if (mRobotMode)
          {
            ent = sceneMgr.CreateEntity("Robot" + mCount.ToString(), "robot.mesh");
          }
          else
          {
            ent = sceneMgr.CreateEntity("Ninja" + mCount.ToString(), "ninja.mesh");
          }
          mCurrentObject = sceneMgr.RootSceneNode.CreateChildSceneNode("ObjectNode" + mCount.ToString(),
              itr.Current.worldFragment.singleIntersection);
          mCount++;
          mCurrentObject.AttachObject(ent);
          if (mRobotMode)
            mCurrentObject.SetScale(0.1f, 0.1f, 0.1f);
          else
            mCurrentObject.SetScale(0.05f, 0.05f, 0.05f);
        }
      }

First we will check if the first intersection is a MovableObject, but there is a catch. 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 while loop.
You'll notice that I snuck in a little extra code to scale the Ninja Object since it was about twice the size of the Robot.

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 can drag it around. 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 we could actually have a fallthrough if there 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.

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 class and add this:

enum QueryFlags
    {
        NINJA_MASK = 0x1,
        ROBOT_MASK = 0x2
    };

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 MeshToMouse:

if (mRobotMode)
                {
                    ent = sceneMgr.CreateEntity("Robot" + mCount.ToString(), "robot.mesh");
                }
                else
                {
                    ent = sceneMgr.CreateEntity("Ninja" + mCount.ToString(), "ninja.mesh");
                }

We will add two lines to set the mask on both of them:

if (mRobotMode)
          {
            ent = sceneMgr.CreateEntity("Robot" + mCount.ToString(), "robot.mesh");
            ent.QueryFlags=((uint)QueryFlags.ROBOT_MASK);
          }
          else
          {
            ent = sceneMgr.CreateEntity("Ninja" + mCount.ToString(), "ninja.mesh");
            ent.QueryFlags=((uint)QueryFlags.NINJA_MASK);
          }

We still need to make it so that when we are in a mode, we can only click and drag objects of that 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. In the MeshToMouse function, find this code:

mRaySceneQuery.Ray = mouseRay;

Add this line of code after it:

mRaySceneQuery.QueryMask=(mRobotMode ? (uint)QueryFlags.ROBOT_MASK : (uint)QueryFlags.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.

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.

Note that these are all powers of 2. 0x1 (1), 0x2 (2), 0x4 (4), 0x8 (8), 0x10 (16), 0x20 (32) ...

Querying for Multiple Masks

We can query for multiple masks by using the bitwise OR operator. Let say we have three different groups of objects in a game:

enum QueryFlags
 {
     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:

mRaySceneQuery.QueryMask=( (uint)QueryFlags.FRIENDLY_CHARACTERS );

If we want the query to return both enemy characters and stationary objects, we would use:

mRaySceneQuery.QueryMask=( (uint)QueryFlags.ENEMY_CHARACTERS | (uint)QueryFlags.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 (uint)QueryFlags.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:

mRaySceneQuery.QueryMask=( (uint) ~QueryFlags.FRIENDLY_CHARACTERS );

Which will return everything other than friendly characters. You can also do this for multiple masks:

mRaySceneQuery.QueryMask=( (uint) ~( QueryFlags.FRIENDLY_CHARACTERS | QueryFlags.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:

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

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.
  3. You might have noticed that when you click on a group of objects, this is not always the nearest who is selected. Fix this with the instruction :
sceneMgr.SetSortByDistance( true );

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.

COMPLETE TUTORIAL CODE BEFORE EXERCISES

using System;
using System.Drawing;
using System.Text;
using Mogre.Demo.ExampleApplication;
using Mogre;
using System.Collections.Generic;

namespace MogreTutorial
{
  class IntermediateTutorial3 : Example
  {
    protected RaySceneQuery mRaySceneQuery = null;      // The ray scene query pointer
    protected int mCount = 0;                           // The number of robots on the screen
    protected SceneNode mCurrentObject = null;          // The newly created object
    bool mRobotMode = true;                             // The current state
    enum QueryFlags
    {
        NINJA_MASK = 0x1,
        ROBOT_MASK = 0x2
    };

    public override void CreateInput()
    {
      MOIS.ParamList pl = new MOIS.ParamList();
      IntPtr windowHnd;
      window.GetCustomAttribute("WINDOW", out windowHnd);
      pl.Insert("WINDOW", windowHnd.ToString());
      //Non-exclusive input, show OS cursor
      //If you want to see the mouse cursor and be able to move it outside your OGRE window and use the keyboard outside the running application 
      pl.Insert("w32_mouse", "DISCL_FOREGROUND");
      pl.Insert("w32_mouse", "DISCL_NONEXCLUSIVE");
      pl.Insert("w32_keyboard", "DISCL_FOREGROUND");
      pl.Insert("w32_keyboard", "DISCL_NONEXCLUSIVE");
      inputManager = MOIS.InputManager.CreateInputSystem(pl);

      // Create all devices (except joystick, as most people have Keyboard/Mouse) using buffered input.
      inputKeyboard = (MOIS.Keyboard)inputManager.CreateInputObject(MOIS.Type.OISKeyboard, true);
      inputMouse = (MOIS.Mouse)inputManager.CreateInputObject(MOIS.Type.OISMouse, true);

      MOIS.MouseState_NativePtr mouseState = inputMouse.MouseState;
      mouseState.width = viewport.ActualWidth; //! update after resize window
      mouseState.height = viewport.ActualHeight;

      //base.CreateInput();
      inputMouse.MousePressed += new MOIS.MouseListener.MousePressedHandler(MousePressed);
      inputMouse.MouseMoved += new MOIS.MouseListener.MouseMovedHandler(MouseMotion);

      inputKeyboard.KeyPressed += new MOIS.KeyListener.KeyPressedHandler(OnKeyPressed);

      // Create RaySceneQuery
      mRaySceneQuery = sceneMgr.CreateRayQuery(new Ray());
    }

    protected bool OnKeyPressed(MOIS.KeyEvent arg)
    {
      switch (arg.key)
      {
        case MOIS.KeyCode.KC_SPACE:
          mRobotMode = !mRobotMode;
          break;
      }

      return true;
    }

    public override void ChooseSceneManager()
    {
      // Get the SceneManager, in this case a specific for the terrain
      sceneMgr = root.CreateSceneManager(SceneType.ST_EXTERIOR_CLOSE, "SceneMgr");
    }

    protected bool MouseMotion(MOIS.MouseEvent arg)
    {
      // If we are dragging the left mouse button.
      if (arg.state.ButtonDown(MOIS.MouseButtonID.MB_Left))
      {
        MeshToMouse(arg);
      }

      return true;
    }

    protected bool MousePressed(MOIS.MouseEvent arg, MOIS.MouseButtonID id)
    {
      if (id == MOIS.MouseButtonID.MB_Left)
      {
        // Turn off bounding box.
        if (mCurrentObject != null)
          mCurrentObject.ShowBoundingBox = false;

        MeshToMouse(arg, false);

        // Show the bounding box to highlight the selected object
        if (mCurrentObject != null)
          mCurrentObject.ShowBoundingBox=true;
      }

      return true;
    }

    /// <summary>
    /// Place a node that contains the chosen mesh where the mouse pointed.
    /// </summary>
    /// <param name="arg">The arguement that the MouseEventHandler get</param>
    /// <param name="useCurrent">
    /// true : it will work on the mCurrentObject node.
    /// false : it will work on a new node that will be stored in mCurrentObject.
    /// </param>
    private void MeshToMouse(MOIS.MouseEvent arg, bool useCurrent = true)
    {
      // Setup the ray scene query
      float screenX = arg.state.X.abs / (float)arg.state.width;
      float screenY = arg.state.Y.abs / (float)arg.state.height;
      Ray mouseRay = camera.GetCameraToViewportRay(screenX, screenY);
      mRaySceneQuery.Ray = mouseRay;
      mRaySceneQuery.QueryMask=(mRobotMode ? (uint)QueryFlags.ROBOT_MASK : (uint)QueryFlags.NINJA_MASK);

      // Execute query
      RaySceneQueryResult result = mRaySceneQuery.Execute();
      RaySceneQueryResult.Enumerator itr = (RaySceneQueryResult.Enumerator)(result.GetEnumerator());

      //ok.. what we need to do, is iterate through the list and see if there
      //are any objects that aren't tile.
      //if there are.. this is the one to MOVE!!
      // Get results, create a node/entity on the position
      bool placeObject = true;
      while (itr.MoveNext())
      {
        if ((itr.Current.movable != null) &&
            (itr.Current.movable.Name.Substring(0, 4) != "tile"))
        {
          //System.Console.WriteLine("movable Object {0}", itr.Current.movable.GetName());
          if (!useCurrent)
          {
            mCurrentObject = itr.Current.movable.ParentSceneNode;
            placeObject = false;
          }
          break;
        }
      }

      if (placeObject)
      {
        itr.Reset();
        itr.MoveNext();
        if (useCurrent)
        {
          mCurrentObject.Position = itr.Current.worldFragment.singleIntersection;
        }
        else
        {
          Entity ent; 
          if (mRobotMode)
          {
            ent = sceneMgr.CreateEntity("Robot" + mCount.ToString(), "robot.mesh");
            ent.QueryFlags=((uint)QueryFlags.ROBOT_MASK);
          }
          else
          {
            ent = sceneMgr.CreateEntity("Ninja" + mCount.ToString(), "ninja.mesh");
            ent.QueryFlags=((uint)QueryFlags.NINJA_MASK);
          }
          mCurrentObject = sceneMgr.RootSceneNode.CreateChildSceneNode("ObjectNode" + mCount.ToString(),
              itr.Current.worldFragment.singleIntersection);
          mCount++;
          mCurrentObject.AttachObject(ent);
          if (mRobotMode)
            mCurrentObject.SetScale(0.1f, 0.1f, 0.1f);
          else
            mCurrentObject.SetScale(0.05f, 0.05f, 0.05f);
        }
      }
    }

    public override void CreateFrameListener()
    {
      base.CreateFrameListener();
      Root.Singleton.FrameStarted += FrameStarted;
    }

    bool FrameStarted(FrameEvent evt)
    {
      // Setup the scene query
      Vector3 camPos = camera.Position;
      Ray cameraRay = new Ray(new Vector3(camPos.x, 5000.0f, camPos.z),
          Vector3.NEGATIVE_UNIT_Y);
      mRaySceneQuery.Ray=cameraRay;

      // Perform the scene query;
      RaySceneQueryResult result = mRaySceneQuery.Execute();
      RaySceneQueryResult.Enumerator itr = (RaySceneQueryResult.Enumerator)(result.GetEnumerator());

      // Get the results, set the camera height
      if((itr != null) && itr.MoveNext()){
        float terrainHeight = itr.Current.worldFragment.singleIntersection.y;

        if ((terrainHeight + 10.0f) > camPos.y)
          camera.SetPosition(camPos.x, terrainHeight + 10.0f, camPos.z);
      }
      return true;
    }

    public override void CreateScene()
    {
      // Set ambient light 
      sceneMgr.AmbientLight=new ColourValue(0.5f, 0.5f, 0.5f);

      // World geometry & Sky
      sceneMgr.SetSkyDome(true, "Examples/CloudySky", 5, 8);
      sceneMgr.SetWorldGeometry("terrain.cfg");

      // Set camera look point           
      camera.Position=new Vector3(40, 100, 280);
      camera.Pitch(new Radian(new Degree(-30)));
      camera.Yaw(new Radian(new Degree(-45)));
    }

    public IntermediateTutorial3()
      : base()
    {
    }

    public override void DestroyScene()
    {
      base.DestroyScene();
      mRaySceneQuery.Dispose();
    }
  }
}

namespace Mogre.Demo.CameraTrack
{
  using MogreTutorial;

  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        IntermediateTutorial3 app = new IntermediateTutorial3();
        app.Go();
      }
      catch (System.Runtime.InteropServices.SEHException)
      {
        // Check if it's an Ogre Exception
        if (OgreException.IsThrown)
          ExampleApplication.Example.ShowOgreException();
        else
          throw;
      }
    }
  }

}

Credits

Thanks Clay Culver for providing the original tutorial and DigitalCyborg for the initial .NET C# port.


Alias: MOGRE IntermediateTutorial 3