I have a picking function in the map editor in my game.
What I do is I loop through all objects in the scene (which can be barrels, boxes, birds, terrain, etc) which is a list of my own class "CStaticObject".
First you should probably pick against their AABB:
Code: Select all
AxisAlignedBox tmpAABB = model->m_node->_getWorldAABB();
std::pair<bool, Real> tmpRayIntersection = ray.intersects(tmpAABB);
if( tmpRayIntersection.first )
{
Then in the CStaticObject class, I have a function which gets their 3D data and caches it for each unique mesh.
You get the data with a function something like this:
Code: Select all
// Code found in Wiki: www.ogre3d.org/wiki/index.php/RetrieveVertexData
void CMeshInformation::GetMeshInformation(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));
// 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;
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;
if( !ibuf ) 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->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;
size_t index_start = index_data->indexStart;
size_t last_index = numTris*3 + index_start;
if (use32bitindexes)
for (size_t k = index_start; k < last_index; ++k)
{
indices[index_offset++] = pLong[k] + static_cast<unsigned long>( offset );
}
else
for (size_t k = index_start; k < last_index; ++k)
{
indices[ index_offset++ ] = static_cast<unsigned long>( pShort[k] ) +
static_cast<unsigned long>( offset );
}
ibuf->unlock();
current_offset = next_offset;
}
}
With caching the above data per mesh, if I pick against a barrel it will fetch its 3D data and then save it, so the next time I pick against any barrel it will already have the data.
That data is basically only a list of triangles, which you can easily pick against using (with any scene node with that mesh):
Code: Select all
// Intersects with the mesh information with a ray
Vector3 CMeshInformation::GetTriangleIntersection(SceneNode* node, Ray ray, Vector3* vertices, size_t index_count, unsigned long* indices, int* triangleIndex)
{
// Loop through all triangles
Quaternion tmpNodeOrientation = node->_getDerivedOrientation();
Vector3 tmpNodeScale = node->getScale();
Vector3 tmpNodePosition = node->_getDerivedPosition();
Vector3 tmpRet = CDefine::INVALID_POSITION;
int i = 0;
float tmpBestDistance = FLT_MAX;
for(; i < static_cast<int>(index_count); i += 3)
{
// Calculate the current triangles vertexes from the node specified
Vector3 tmpVertex0 = vertices[indices[i]];
Vector3 tmpVertex1 = vertices[indices[i+1]];
Vector3 tmpVertex2 = vertices[indices[i+2]];
tmpVertex0 = (tmpNodeOrientation * (tmpVertex0 * tmpNodeScale)) + tmpNodePosition;
tmpVertex1 = (tmpNodeOrientation * (tmpVertex1 * tmpNodeScale)) + tmpNodePosition;
tmpVertex2 = (tmpNodeOrientation * (tmpVertex2 * tmpNodeScale)) + tmpNodePosition;
// Check if we intersected with the triangle
std::pair<bool, Ogre::Real> tmpIntersectionData = Ogre::Math::intersects(ray, tmpVertex0, tmpVertex1, tmpVertex2, true, true);
if( tmpIntersectionData.first )
{
// Check if this is the closest intersection so far
float tmpDistance = (ray.getPoint(tmpIntersectionData.second) - ray.getOrigin()).length();
if(tmpDistance < tmpBestDistance)
{
// Set the current intersection to be the best intersection
tmpBestDistance = tmpDistance;
tmpRet = ray.getPoint(tmpIntersectionData.second);
if(triangleIndex)
(*triangleIndex) = i;
}
}
}
// Return the intersection point (if any)
return tmpRet;
}
With the above explained, you have a perfect system for picking against 3D data.
You can also easily extend this to a "picking area", which I also use in my map editor (start/end are 2D coordinates of the screen where you dragged your mouse):
Code: Select all
// Picks objects in an area cursor and returns it
void PickArea(Vector2 start, Vector2 end, CList<CModel*>* objects)
{
// Validate the start and the end of the area
AxisAlignedBox tmpAreaAABB;
tmpAreaAABB.merge(CGeneric::To3DVector(start));
tmpAreaAABB.merge(CGeneric::To3DVector(end));
start = CGeneric::To2DVector(tmpAreaAABB.getMinimum());
end = CGeneric::To2DVector(tmpAreaAABB.getMaximum());
Ray tmpTopLeftRay = app->m_Camera->getCameraToViewportRay(start.x, start.y);
Ray tmpTopRightRay = app->m_Camera->getCameraToViewportRay(end.x, start.y);
Ray tmpBottomLeftRay = app->m_Camera->getCameraToViewportRay(start.x, end.y);
Ray tmpBottomRightRay = app->m_Camera->getCameraToViewportRay(end.x, end.y);
// Check we have a width or height of zero
if( start.x == end.x ||
start.y == end.y )
// Return the function, we are done
return;
// Create the planes of the area
CList<Plane> tmpPlanes;
// From camera pointing forward
tmpPlanes.Add(Plane(CGeneric::GetDirection(app->m_Camera), CGeneric::GetPosition(app->m_Camera)));
// From the left pointing to the right
Vector3 tmpForward = tmpTopLeftRay.getDirection();
Vector3 tmpUp = (tmpTopLeftRay.getOrigin() - tmpBottomLeftRay.getOrigin()).normalisedCopy();
Vector3 tmpDirection = tmpForward.crossProduct(tmpUp).normalisedCopy();
tmpPlanes.Add(Plane(tmpDirection, tmpTopLeftRay.getOrigin()));
// From the right pointing to the left
tmpForward = tmpTopRightRay.getDirection();
tmpUp = (tmpTopRightRay.getOrigin() - tmpBottomRightRay.getOrigin()).normalisedCopy();
tmpDirection = -tmpForward.crossProduct(tmpUp).normalisedCopy();
tmpPlanes.Add(Plane(tmpDirection, tmpBottomRightRay.getOrigin()));
// From the top pointing down
tmpForward = tmpTopLeftRay.getDirection();
Vector3 tmpLeft = (tmpTopLeftRay.getOrigin() - tmpTopRightRay.getOrigin()).normalisedCopy();
tmpDirection = -tmpForward.crossProduct(tmpLeft).normalisedCopy();
tmpPlanes.Add(Plane(tmpDirection, tmpTopLeftRay.getOrigin()));
// From the bottom pointing up
tmpForward = tmpBottomLeftRay.getDirection();
tmpLeft = (tmpBottomLeftRay.getOrigin() - tmpBottomRightRay.getOrigin()).normalisedCopy();
tmpDirection = tmpForward.crossProduct(tmpLeft).normalisedCopy();
tmpPlanes.Add(Plane(tmpDirection, tmpBottomRightRay.getOrigin()));
// Loop through all objects
for(XXXXX)
{
// Get the current object
CStaticObject* tmpStaticObject = XXX;
// Get the axis aligned bounding box of the current object
AxisAlignedBox tmpAABB;
if( tmpStaticObject->m_model )
tmpAABB = tmpStaticObject->m_model->m_node->_getWorldAABB();
// Check if the AABB of the current object is invalid
if(tmpAABB.isNull())
// Continue to the next loop
continue;
// Loop through all planes of the pick area
bool tmpIsVisible = true;
Vector3 tmpCentre = tmpAABB.getCenter();
Vector3 tmpHalfSize = tmpAABB.getHalfSize();
for(int n = 0; n < tmpPlanes.Size(); n++)
{
// Check if all corners of the AABB is on the negative side (which means that it is out of view)
Plane::Side tmpSide = tmpPlanes[n].getSide(tmpCentre, tmpHalfSize);
if (tmpSide == Plane::NEGATIVE_SIDE)
{
// Set that we are not visible
tmpIsVisible = false;
// Break out of the loop, we are done
break;
}
}
// Check if we are visible
if(tmpIsVisible)
// Add the current object to the list of picked objects
objects->Add(tmpStaticObject->m_model);
}
}
That above code only works for picking an area using the objects AABB. But it can easily be extended to be using their 3D data instead of course.
- use the item pointer as a key to a lookup table of client side 'game objects' - is this legal? If I create an entity using SceneManager::createEntity, is this pointer stable during the runtime of the application or until I explicitly destroy it?
Yes, all objects created from Ogre can be used forever anywhere, unless you delete their object.
That is why I use CStaticObject above. That class handles the creation and deletion of the Ogre objects, and then I just loop through a list of all CStaticObjects that I use in the game. That way it is impossible to use an Ogre object that has been deleted.