Picking objects in a scene - Best practices(?) Topic is solved

Problems building or running the engine, queries about how to use features etc.
Post Reply
uzurpator
Gnoblar
Posts: 18
Joined: Mon Nov 27, 2006 5:35 pm

Picking objects in a scene - Best practices(?)

Post by uzurpator »

Ogre Version: :13.1:
Operating System: :Windows 10:
Render System: :D3D11:

This is a general question of how to do picking of objects with Ogre. I mostly am quering whether I am not missing something.

I am making a strategy game. In a nutshell - you see a map, you have a mouse cursor, you click on stuff and things happen. Currently I am implementing picking of the stuff. I query the scene using this code:

Code: Select all

bool
SceneObjectQueryImpl::PickObjects( Core::VectorInt2D const& _clickPoint )
{
    auto resolution = Window->GetResolution();

    float positionX = static_cast< float >( _clickPoint.m_x ) / resolution.m_x;
    float positionY = static_cast< float >( _clickPoint.m_y ) / resolution.m_y;

    auto camera = Engine->GetSceneManager()->getCamera( CameraAccess->GetName() );
    const Ogre::Ray & ray = camera->getCameraToViewportRay( positionX, positionY );    

    auto query = Engine->GetSceneManager()->createRayQuery( ray );

    auto results = query->execute();

    for( auto queryResult : results )
    {
        // filter objects required
        if( queryResult.movable && queryResult.movable->isVisible() )
            m_objects.push_back( queryResult.movable->getName() );
    }

    return m_objects.size() > 0;
}
Most of my objects are going to be boxy-axis aliged items, so I don't need some elaborate filtering. However I do need some. For example - my terrain rendering method uses patches of terrain ( created on the fly via vertex buffers ) which are attached to Entities, thus are movable objects and I want to filter them out. Here is what I can do:

- use the 'Name' of an entity as an entry in a dictionary of entities and query what it is on my side. This would require parsing the name, which would introduce certain performance hit. Something I had to deal with in my job not too long ago and it was not pretty.
- query the MovableObject for its SceneNode and use a that node to coarsely filter objects of a certain category ( using a node as a 'bucket sort' of sorts ). I already attach terrain patches to 'Terrain' node and vegetation static geometry to 'Forest' node etc.
- 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?

Now:
Is there anything I am missing, like the best practice or a native Ogre mechanism to differentiate MovableObjects?
Can I attach some sort of custom data with an Ogre provided mechanism to a MovableObject?
Can I designate given entity/node/mesh to be a 'WorldFragment' - ie a part of the scene that SceneQuery returns as a 'worldFragment' instead of 'movable'?
rpgplayerrobin
Gnoll
Posts: 617
Joined: Wed Mar 18, 2009 3:03 am
x 353

Re: Picking objects in a scene - Best practices(?)

Post by rpgplayerrobin »

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.
paroj
OGRE Team Member
OGRE Team Member
Posts: 1993
Joined: Sun Mar 30, 2014 2:51 pm
x 1073
Contact:

Re: Picking objects in a scene - Best practices(?)

Post by paroj »

uzurpator wrote: Mon Dec 06, 2021 10:34 am Is there anything I am missing, like the best practice or a native Ogre mechanism to differentiate MovableObjects?
https://ogrecave.github.io/ogre/api/lat ... 3183f82cfb
Post Reply