Skip to main content

History: Raycasting to the polygon level - Mogre

Source of version: 15 (current)

Copy to clipboard
            This is a Mogre version of ((Raycasting to the polygon level)) as ported by Funguine (ketarax on #ogre3d) & Antont.

Here is the [http://www.ogre3d.org/phpBB2/viewtopic.php?t=23440|forum thread] of the original code (C++).

!!Introducion
An Ogre ray query generally only detect bounding boxes (((-AABB))s), like you see on this image. With this code you can avoid this disadvantage.
{img src="img/wiki_up/Rays_and_BoundingBox_1a.gif" alt="Rays_and_BoundingBox_1a.gif"}

!!Important note
This code only returns correct results for polygons defined by rendertype ((ManualObject|#Rendering_Types|OT_TRIANGLE_LIST)). If a mesh contains other rendertypes (different triangle definition), lines or points, you will get wrong results or even a crash. Terrain and ManualObject aren't supported. Also it's not recommend for heavy usage (multiple queries per second). For details [http://www.ogre3d.org/forums/viewtopic.php?f=1&t=67355|look here].

!!An improved alternative
Because of disadvantages with the code on this wiki page, user ((User:Beauty|Beauty)) wrotes a new polygon raycasting class from scratch. 
In general the code is ready to use. Only some helper classes (e.g. for debugging) are added quick and dirty to a second code file. In the future Beauty wants to polish up the code and publish it in a common way later (not only as attachment of a forum post).

The (alternative) polygon raycasting code you find [http://www.ogre3d.org/forums/viewtopic.php?p=446083#p446083|here] in the forum. Notes about you find [http://www.ogre3d.org/forums/viewtopic.php?p=448551#p448551|here].
For questions about Beautys code, please us the same forum topic.

Note: The code on this wiki page (below this section) is the old and "primitive" polygon raycasting version. 

!!Code for initialization
Define this field (variable) in your class
{CODE(wrap="1", colors="c++")}RaySceneQuery m_pray_scene_query;{CODE}

Initialize it, for example in CreateScene()
{CODE(wrap="1", colors="c++")}
// create the ray scene query object
m_pray_scene_query = sceneMgr.CreateRayQuery(new Ray(), SceneManager.WORLD_GEOMETRY_TYPE_MASK);
if (null == m_pray_scene_query)
{
    return false;
}
m_pray_scene_query.SetSortByDistance(true);{CODE}

!!Method for raycast
{CODE(wrap="1", colors="c++")}// raycast from a point in to the scene.
// returns success or failure.
// on success the point is returned in the result.
public bool RaycastFromPoint(Vector3 point, Vector3 normal, ref Vector3 result,
    ref Vector3 resNormal)
{
    // create the ray to test
    Ray ray = new Ray(point, normal);

    // check we are initialised
    if (m_pray_scene_query != null)
    {
        // create a query object
        m_pray_scene_query.Ray = ray;

        // execute the query, returns a vector of hits
        RaySceneQueryResult rayresult = m_pray_scene_query.Execute();
        if (rayresult.Count <= 0)
        {
           // raycast did not hit an objects bounding box
            return false;
        }
    }
    else
    {
        return false;
    }

    // at this point we have raycast to a series of different objects bounding boxes.
    // we need to test these different objects to see which is the first polygon hit.
    // there are some minor optimizations (distance based) that mean we wont have to
    // check all of the objects most of the time, but the worst case scenario is that
    // we need to test every triangle of every object.
    float closest_distance = -1.0f;
    Vector3 closest_result = Vector3.ZERO;
    Vector3 vNormal = Vector3.ZERO;
    RaySceneQueryResult query_result = m_pray_scene_query.GetLastResults();

    foreach (RaySceneQueryResultEntry this_result in query_result)
    {
        // stop checking if we have found a raycast hit that is closer
        // than all remaining entities
        if ((closest_distance >= 0.0f) &&
            (closest_distance < this_result.distance))
        {
            break;
        }

        // only check this result if its a hit against an entity
        if ((this_result.movable != null) &&
            (this_result.movable.MovableType == "Entity"))
        {
            // get the entity to check
            Entity pentity = (Entity)this_result.movable;

            // mesh data to retrieve 
            uint vertex_count = 0;
            uint index_count = 0;
            Vector3[] vertices = new Vector3[0];
            UInt64[] indices = new UInt64[0];

            // get the mesh information
            GetMeshInformation(pentity.GetMesh(),
                ref vertex_count, ref vertices, ref index_count, ref indices,
                pentity.ParentNode._getDerivedPosition(),    // WorldPosition
                pentity.ParentNode._getDerivedOrientation(), // WorldOrientation
                pentity.ParentNode.GetScale());

            int ncf = -1; // new_closest_found

            // test for hitting individual triangles on the mesh
            for (int i = 0; i < (int)index_count; i += 3)
            {
                // check for a hit against this triangle
                Pair<bool, float> hit = Mogre.Math.Intersects(ray, vertices[indices[i]],
                    vertices[indices[i + 1]], vertices[indices[i + 2]], true, false);

                // if it was a hit check if its the closest
                if (hit.first)
                {
                    if ((closest_distance < 0.0f) ||
                        (hit.second < closest_distance))
                    {
                        // this is the closest so far, save it off
                        closest_distance = hit.second;
                        ncf = i;
                    }
                }
            }

            if (ncf > -1)
            {
                closest_result = ray.GetPoint(closest_distance);
                // if you don't need the normal, comment this out; you'll save some CPU cycles.
                Vector3 v1 = vertices[indices[ncf]] - vertices[indices[ncf + 1]];
                Vector3 v2 = vertices[indices[ncf + 2]] - vertices[indices[ncf + 1]];
                vNormal = v1.CrossProduct(v2);
            }

            // free the verticies and indicies memory
            vertices = null;
            indices = null;
        }
    }

    // if we found a new closest raycast for this object, update the
    // closest_result before moving on to the next object.
    if (closest_distance >= 0.0f)
    {
        result = new Vector3(closest_result.x, closest_result.y, closest_result.z);
        resNormal = vNormal / vNormal.Normalise();

        /*
        // this visualizes the 'result' position 
        if (!sceneMgr.HasSceneNode("marker"))
        {
            SceneNode node = sceneMgr.CreateSceneNode("marker");
            Entity ent = sceneMgr.CreateEntity("marker", "Cube.mesh");
            node.AttachObject(ent);
            node.Position = result;
            node.Scale(0.25f, 0.25f, 0.25f);
            sceneMgr.RootSceneNode.AddChild(node);
        }
        else
        {
            sceneMgr.GetSceneNode("marker").Position = result;
        }
        */

        // raycast success
        return true;
    }
    else
    {
        // raycast failed
        return false;
    }
} // RayCastFromPoint{CODE}

!!GetMeshInformation
This code is a port from ((RetrieveVertexData)).

There are ((Raycasting to the polygon level|#Adapted_version|two alternative versions)) of ''GetMeshInformation()''. One takes into account the animation state of an entity and the other one solves compiling errors for 64bit using GCC.


{CODE(wrap="1", colors="c++")}// Get the mesh information for the given mesh.
// Code found in Wiki: www.ogre3d.org/wiki/index.php/RetrieveVertexData
public unsafe void GetMeshInformation(MeshPtr mesh,
    ref uint vertex_count,
    ref Vector3[] vertices,
    ref uint index_count,
    ref UInt64[] indices,
    Vector3 position,
    Quaternion orientation,
    Vector3 scale)
{
    bool added_shared = false;
    uint current_offset = 0;
    uint shared_offset = 0;
    uint next_offset = 0;
    uint index_offset = 0;

    vertex_count = index_count = 0;

    // Calculate how many vertices and indices we're going to need
    for (ushort i = 0; i < mesh.NumSubMeshes; ++i)
    {
        SubMesh submesh = mesh.GetSubMesh(i);

        // We only need to add the shared vertices once
        if (submesh.useSharedVertices)
        {
            if (!added_shared)
            {
                vertex_count += mesh.sharedVertexData.vertexCount;
                added_shared = true;
            }
        }
        else
        {
            vertex_count += submesh.vertexData.vertexCount;
        }

        // Add the indices
        index_count += submesh.indexData.indexCount;
    }

    // Allocate space for the vertices and indices
    vertices = new Vector3[vertex_count];
    indices = new UInt64[index_count];
    added_shared = false;

    // Run through the submeshes again, adding the data into the arrays
    for (ushort i = 0; i < mesh.NumSubMeshes; ++i)
    {
        SubMesh submesh = mesh.GetSubMesh(i);
        VertexData vertex_data = submesh.useSharedVertices ? mesh.sharedVertexData : submesh.vertexData;

        if (!submesh.useSharedVertices || (submesh.useSharedVertices && !added_shared))
        {
            if (submesh.useSharedVertices)
            {
                added_shared = true;
                shared_offset = current_offset;
            }

            VertexElement posElem =
                vertex_data.vertexDeclaration.FindElementBySemantic(VertexElementSemantic.VES_POSITION);
            HardwareVertexBufferSharedPtr vbuf =
                vertex_data.vertexBufferBinding.GetBuffer(posElem.Source);

            byte* vertex = (byte*)vbuf.Lock(HardwareBuffer.LockOptions.HBL_READ_ONLY);
            float* pReal;

            // There is _no_ baseVertexPointerToElement() which takes an Ogre::Real or a double
            //  as second argument. So make it float, to avoid trouble when Ogre::Real will
            //  be comiled/typedefed as double:
            //      Ogre::Real* pReal;
            for (int j = 0; j < vertex_data.vertexCount; ++j, vertex += vbuf.VertexSize)
            {
                posElem.BaseVertexPointerToElement(vertex, &pReal);
                Vector3 pt = new Vector3(pReal[0], pReal[1], pReal[2]);
                vertices[current_offset + j] = (orientation * (pt * scale)) + position;
            }
            // |!| Important: VertexBuffer Unlock() + Dispose() avoids memory corruption
            vbuf.Unlock();
            vbuf.Dispose();
            next_offset += vertex_data.vertexCount;
        }

        IndexData index_data = submesh.indexData;
        uint numTris = index_data.indexCount / 3;
        HardwareIndexBufferSharedPtr ibuf = index_data.indexBuffer;

        // UNPORTED line of C++ code (because ibuf.IsNull() doesn't exist in C#)
            // if( ibuf.isNull() ) continue
            // need to check if index buffer is valid (which will be not if the mesh doesn't have triangles like a pointcloud)
            
        bool use32bitindexes = (ibuf.Type == HardwareIndexBuffer.IndexType.IT_32BIT);

        uint* pLong = (uint*)ibuf.Lock(HardwareBuffer.LockOptions.HBL_READ_ONLY);
        ushort* pShort = (ushort*)pLong;
        uint offset = submesh.useSharedVertices ? shared_offset : current_offset;
        if (use32bitindexes)
        {
            for (int k = 0; k < index_data.indexCount; ++k)
            {
                indices[index_offset++] = (UInt64)pLong[k] + (UInt64)offset;
            }
        }
        else
        {
            for (int k = 0; k < index_data.indexCount; ++k)
            {
                indices[index_offset++] = (UInt64)pShort[k] + (UInt64)offset;
            }
        }
        // |!| Important: IndexBuffer Unlock() + Dispose() avoids memory corruption
        ibuf.Unlock();
        ibuf.Dispose();
        current_offset = next_offset;
    }

    // |!| Important: MeshPtr Dispose() avoids memory corruption
    mesh.Dispose(); // This dispose the MeshPtr, not the Mesh

} // GetMeshInformation {CODE}

---
Alias: (alias(Raycasting_to_the_polygon_level_-_Mogre))
Alias: (alias(Raycasting_to_the_polygon_level_%28Mogre%29))