Ray query with MOGRE        

The Ray Query class members in Mogre looks a little bit different to the Ogre API. Don't be confused by that. Mogre offers the same functionality.

An example for ray usage:
A character should walk on terrain. So you need to clamp it to the terrain surface.
After each movement, you perform a ray query straight downwards and read out the distance to the terrain. As result you know how to change the heigth value of the characters position.

Notes:

  • Only the bounding boxes (AABB) will be hit, not the object itself (see picture).
    • If you need distances on polygon level, look here.
  • Unvisible objects can be hit, too.
  • At least one frame must be rendered to get a result.
  • For rays starting from (or pointing to) a SceneNode you should call myNode._getDerivedPosition() and myNode._getDerivedOrientation() to get the wanted values for Ray.Origin and Ray.Direction.
  • If you use the Terrain Scene Manager (outdated, removed in Ogre 1.8), be aware that the distance is not very precise.
    • The returned distance is too long (up to 1 Ogre length unit). A demonstration screenshot is here.
    • An easy to use workaround for precise terrain distances was published here.
    • I repeat again: This issue is only related to the Terrain Scene Manager (and forks). The new Ogre Terrain Component works well by default.


Rays and BoundingBox 1a.gif

Code snippet

//-- set params --

 Vector3 startPosition = new Vector3(0, 0, 0); 
 Vector3 rayDirection = new Vector3(0, -1, 0);      // here: ray into depth
        
 // OR use position and orientation of a SceneNode
 //SceneNode someNode = mScene.Smgr.GetSceneNode("name_of_node");
 //Vector3 startPosition = someNode._getDerivedPosition;
 //Vector3 rayDirection = someNode._getDerivedOrientation.XAxis;  // in this case: base quaternion direction is along XAxis

 Ray myRay = new Ray(startPosition, rayDirection);
 RaySceneQuery raySQuery = mScene.Smgr.CreateRayQuery(myRay);

 // optionally sort the results
 raySQuery.SetSortByDistance(true);  // second possible parameter: maxResults

//-- calculate --

 Vector3 rayHitPoint;
 Single  rayDistance;
 String  rayHitPointName = "";

 RaySceneQueryResult raySQResult = raySQuery.Execute();
 RaySceneQueryResultEntry raySQREntry;

 for (Int16 i = 0; i < raySQResult.Count; i++)  // for all hits
 {
     raySQREntry = raySQResult[i];
     rayDistance = raySQREntry.distance;
     rayHitPoint = myRay.Origin + (myRay.Direction * rayDistance); // calculate hit point

     // save names of objects (or world fragment) that was hit
     if (raySQREntry.worldFragment != null)
         rayHitPointName = raySQREntry.worldFragment.ToString();
     if (raySQREntry.movable != null)
         rayHitPointName = raySQREntry.movable.Name;

     // Also bounding boxes of terrains can be hit.
     // Skip them to prevent hits on strange places ("just in the air").
     if (rayHitPointName.StartsWith("tile["))  // e.g. "tile[1][1,1]"
         continue;

     // ... do something with the hit ...

     if (raySQREntry.worldFragment != null)
     {
         // ... only do something with hits to terrain
     }

     if (raySQREntry.movable != null)
     {
         // ... only do something with hits to other objects
     }

     // If you just want to find a special hit, you can leave the for() loop.
     // The results will be stored in the variables (rayHitPoint, raySQREntry etc.)
     if (this_is_your_hit)
         break;

 } // for

//-- if nothing was hit --

 if (raySQResult.IsEmpty)
 {
     // ... do something ...

     // e.g. prevent null values
     rayHitPoint = Vector3.ZERO;
     rayDistance = 0;
     rayHitPointName = "no_hitPoint";
 }

Query flags

Query flags are a simple way to create a filter for ray queries.
For this you can define "masks" (flags) to several scene objects (e.g. for enemies, walls, etc.) and for your ray queries you tell Ogre, which objects you want to get or ignore.


Here is a pseudo code example:

(put this enum in a shared class)
[FlagsAttribute] // optional, but it makes clear, that this is a flag enum.
enum QueryMasks : uint //Depends on your needs, but I need a lot of flags and bit shifting needs big numbers (int = 32bit = 32 flags, byte = 8 bit = 8 flags, ...).
{
Ground = 1<<0, //bit shift required (2,4,8,16,32,64,128,256,...) 
Wall = 1<<1
[...]
}

(somewhere in your code, e.g. in CreateScene())

Entity Groundent = (...);
Groundent.QueryFlags = QueryMask.Ground; //assign a custom query flag

(Somewhere in FrameStarted or similar)

static CollisionTools ct = new CollisionTools(MySceneMgr);
//C# does not support static variables in methods (VB.Net does ;) ), make a global var instead
var Result = ct.RaycastFromCamera(RenderWindow, Camera, ScreenPosition, QueryMask.Ground); //search for a specific query flag
if (Result != null) 
{
 //success, this has to be an entity with the Mask "Ground".
}


For more details about query flags read the Intermediate Tutorial 3.

 Alternatives
There are of course helper classes to make raycasting easier.

Here are some:
MMOC (C# port of MOC) (used in example above)
Beauty's class (Preview release)
Both support ray casting to polygon level (very accurate but slow)
Of course, you could just query all AABBs: RaySceneQuery API
That is of course the fastest method but very inaccurate.
Another alternative: Most of the physics libraries have ray casting features, e.g. using Newton or Physx.

 Performance
I never did speed tests but all raycasting to polygone level are brute force attacks. All mentioned classes just iterate through all triangles of the meshes returned by a RayQuery. That's not soooo slow but just use this technique wise: Do you really need to do 20 raycasts to poly level (per frame)? Maybe just do it once and cache the result. Or prefilter the objects (that may need so tweaking of the helper classes), e.g. store all objects in a certain distance to your player and skip the rest.

An real alternative could be the "Selection buffer" (only c++): It colors all visible meshes in the scene for one frame in a (random but unique) color and returns the mesh at a specific screen cooridinate. This method (of course) only works for raycasting from the camera...

See also