Ogre中做精确到三角面片的鼠标点选

开发环境:windows7,IDE:visual studio 2008 sp1版,Ogre版本:1.7.2,CEGUI版本:0.7.1。

Ogre最原始的点选方式,只能在物体包围盒的级别上点选物体,误差十分的大。

很容易返回不属于用户本意的目标。

于是在真正发布的应用程序中需要对返回的结果进行加工,也就是rayResult进行一次加工,从模型三角面片的级别去选择物体。

当然,这个并不是原创,我也遇到了点选中不精确的问题,但是有一个开源的ogre程序Ogitor,它的点选很精确,于是看它的源代码之后,从它里面抠出来的这一部分改善鼠标点选功能的代码,下面是两个功能函数:

使用起来非常简单,只需要将最原始的点选方式选中的结果集合rayResult传入这个函数,处理一下即可。

void GetMeshInformationEx(const Ogre::MeshPtr mesh,
        size_t &vertex_count,
        Ogre::Vector3* &vertices,
        size_t &index_count,
        unsigned long* &indices,
        const Ogre::Vector3 &position,
        const Ogre::Quaternion &orient,
        const Ogre::Vector3 &scale)
{
 bool added_shared = false;
 size_t current_offset = 0;
 size_t shared_offset = 0;
 size_t next_offset = 0;
 size_t index_offset = 0;

 vertex_count = index_count = 0;

 // Calculate how many vertices and indices we're going to need
 for (unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i)
 {
  Ogre::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 Ogre::Vector3[vertex_count];
 indices = new unsigned long[index_count];

 added_shared = false;

 // Run through the submeshes again, adding the data into the arrays
 for ( unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i)
 {
  Ogre::SubMesh* submesh = mesh->getSubMesh(i);

  Ogre::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;
   }

   const Ogre::VertexElement* posElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);

   Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(posElem->getSource());

   unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
   float* pReal;

   for( size_t j = 0; j < vertex_data->vertexCount; ++j, vertex += vbuf->getVertexSize())
   {
    posElem->baseVertexPointerToElement(vertex, &pReal);

    Ogre::Vector3 pt(pReal[0], pReal[1], pReal[2]);

    vertices[current_offset + j] = (orient * (pt * scale)) + position;
   }

   vbuf->unlock();
   next_offset += vertex_data->vertexCount;
  }


  Ogre::IndexData* index_data = submesh->indexData;
  size_t numTris = index_data->indexCount / 3;
  Ogre::HardwareIndexBufferSharedPtr ibuf = index_data->indexBuffer;

  bool use32bitindexes = (ibuf->getType() == Ogre::HardwareIndexBuffer::IT_32BIT);

  unsigned long*  pLong = static_cast<unsigned long*>(ibuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
  unsigned short* pShort = reinterpret_cast<unsigned short*>(pLong);

  size_t offset = (submesh->useSharedVertices)? shared_offset : current_offset;

  if ( use32bitindexes )
  {
   for ( size_t k = 0; k < numTris*3; ++k)
   {
    indices[index_offset++] = pLong[k] + static_cast<unsigned long>(offset);
   }
  }
  else
  {
   for ( size_t k = 0; k < numTris*3; ++k)
   {
    indices[index_offset++] = static_cast<unsigned long>(pShort[k]) + static_cast<unsigned long>(offset);
   }
  }

  ibuf->unlock();
  current_offset = next_offset;
 }
}


bool PickEntity(Ogre::RaySceneQuery* mRaySceneQuery, Ogre::Ray &ray, Ogre::Entity **result, Ogre::uint32 mask ,Ogre::Vector3 &hitpoint, bool excludeInVisible,const Ogre::String& excludeobject, Ogre::Real max_distance)
{
 mRaySceneQuery->setRay(ray);
 mRaySceneQuery->setSortByDistance(true);

 if (mRaySceneQuery->execute().size() <= 0) return (false);
 Ogre::Real closest_distance = max_distance;
 Ogre::Vector3 closest_result;
 Ogre::RaySceneQueryResult &query_result = mRaySceneQuery->getLastResults();
 for (size_t qr_idx = 0; qr_idx < query_result.size(); qr_idx++)
 {
  
  // than all remaining entities
  if ((closest_distance >= 0.0f) && (closest_distance < query_result[qr_idx].distance))
  {
   break;
  }

  // only check this result if its a hit against an entity
  if ((query_result[qr_idx].movable != NULL) && (query_result[qr_idx].movable->getMovableType().compare("Entity") == 0))
  {
   // get the entity to check
   Ogre::Entity *pentity = static_cast<Ogre::Entity*>(query_result[qr_idx].movable);
   if(excludeInVisible)
    if (!pentity->getVisible())
     continue;
   if(pentity->getName() == excludeobject) 
    continue;

   // mesh data to retrieve
   size_t vertex_count;
   size_t index_count;
   Ogre::Vector3 *vertices;
   unsigned long *indices;

   // get the mesh information
   GetMeshInformationEx(pentity->getMesh(), vertex_count, vertices, index_count, indices,
    pentity->getParentNode()->_getDerivedPosition(),
    pentity->getParentNode()->_getDerivedOrientation(),
    pentity->getParentNode()->_getDerivedScale());

   // test for hitting individual triangles on the mesh
   bool new_closest_found = false;
   for (int i = 0; i < static_cast<int>(index_count); i += 3) 
   {

    // check for a hit against this triangle
    std::pair<bool, Ogre::Real> hit = Ogre::Math::intersects(ray, vertices[indices[i]],
     vertices[indices[i+1]], vertices[indices[i+2]], true, true);

    // 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;
      new_closest_found = true;
     }
    }
   }

   // free the verticies and indicies memory
   delete[] vertices;
   delete[] indices;

   // if we found a new closest raycast for this object, update the
   // closest_result before moving on to the next object.
   if (new_closest_found)
   {
    closest_result = ray.getPoint(closest_distance);
    (*result) = pentity;
   }
  }
 }

 // return the result
 if (closest_distance != max_distance)
 {
  hitpoint = closest_result;
  return true;
 }
 else
 {
  // raycast failed
  return false;
 }
}

 

具体如何使用呢?

下面是例子代码:

 CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
 Ray mouseRay = m_pCamera->getCameraToViewportRay(mousePos.d_x/float(m_pWindow->getWidth()),
  mousePos.d_y/float(m_pWindow->getHeight()));

 Entity* rayResult=NULL;
 Vector3 hitPoint;
if(PickEntity(m_pRayQuery,mouseRay,&rayResult,SceneManager::FX_TYPE_MASK,hitPoint,true,"x",5000))
 {

//如果选中了一个物体,那么代码会执行到这里来。rayResult就是被选中的Entity的指针。
 }
 else
 {

//如果没有选中任何物体,会执行这里的代码。
 }

是否很简单呢?

没必要担心这个功能的速度问题,这个速度很快的,不会对程序造成任何影响。

你可能感兴趣的:(Ogre中做精确到三角面片的鼠标点选)