scene node picking problems (and fix)

You discovered a bug in the engine, and you are sure that it is not a problem of your code? Just post it in here. Please read the bug posting guidelines first.
Post Reply
nomad
Posts: 53
Joined: Thu Jan 05, 2006 12:35 pm
Location: Wales

scene node picking problems (and fix)

Post by nomad »

Having built my own world editor, I got annoyed with the rather unreliable 'picking' of scene nodes. As I move the cursor over a node, it would either not be selected at all, or would get intermittently selected and un-selected.

First problem is with core::matrix4::transformBox() - this gets the transformed bounding box for the intersection test. The transformed box should completely enclose the transformed scene node. However, this function just transforms the 2 corners of the box which are the maximum and minimum extents. This can result in a box that does not enclose the geometry completely. Should use transformBoxEx() instead, which calculates a completely new box.

i.e.

Code: Select all

inline void matrix4::transformBox(core::aabbox3d<f32>& box) const
{
	transformBoxEx(box);

	//transformVect(box.MinEdge);
	//transformVect(box.MaxEdge);
	//box.repair();
}
Secondly, scene::CSceneCollisionManager::getPickedNodeBB() is the function responsible for finding the scene node that intersects with the cursor position (actually the line from the cursor 'into' the world).
I've changed this function to do ray intersections against the oriented bounding box of the scene node, not the transformed axis-aligned box that was originally used (the transformed aabb can be larger than the actual mesh, which is not desirable). This really improves node picking.


My approach is to do plane-line intersections for each face of the box, and use crossproducts to see if the point lies within the 4 corners of the face (is there a better way to do this?).
This is slower than the original code of course, but I doubt that matters much.

Code: Select all

void CSceneCollisionManager::getPickedNodeBB(ISceneNode* root,
					   const core::vector3df& linemiddle,
					   const core::vector3df& linevect,
					   const core::vector3df& pos,
					   f32 halflength, s32 bits,
					   bool bNoDebugObjects,
					   f32& outbestdistance,
					   ISceneNode*& outbestnode)
{
	const core::list<ISceneNode*>& children = root->getChildren();
	core::vector3df corners[8];
	const core::vector3df linestart = linemiddle - linevect * halflength;
	const core::vector3df lineend = linemiddle + linevect*halflength;
	const f32 length = halflength * 2;

	core::list<ISceneNode*>::Iterator it = children.begin();

	for (; it != children.end(); ++it)
	{
		ISceneNode* current = *it;

		if (current->isVisible() && 
			(bNoDebugObjects ? !current->isDebugObject() : true) &&
			(bits==0 || (bits != 0 && (current->getID() & bits))))
		{
			const core::aabbox3df& box = current->getBoundingBox();

			//get the 6 planes of the box sides
			box.getEdges(corners);

			//transform into an object-oriented bounding box
			core::matrix4 mat = current->getAbsoluteTransformation();
			core::aabbox3df box1;
			for (int c=0; c<8; c++) {
				mat.transformVect(corners[c]);
				//make an aabb that encloses this box, for a quick cull test
				box1.addInternalPoint(corners[c]);
			}

			//do a cull based on the aabb
			if (!(	(linestart.X < box1.MinEdge.X && lineend.X < box1.MinEdge.X) ||
					(linestart.X > box1.MaxEdge.X && lineend.X > box1.MaxEdge.X) ||
					(linestart.Y < box1.MinEdge.Y && lineend.Y < box1.MinEdge.Y) ||
					(linestart.Y > box1.MaxEdge.Y && lineend.Y > box1.MaxEdge.Y) ||
					(linestart.Z < box1.MinEdge.Z && lineend.Z < box1.MinEdge.Z) ||
					(linestart.Z > box1.MaxEdge.Z && lineend.Z > box1.MaxEdge.Z)) )
			{
				//set up array of indices to corners, for each face
				//winding order is critical here
				const s32 indices[6][4] = {{0,4,6,2},{1,3,7,5},{0,2,3,1},{4,5,7,6},{2,6,7,3},{0,1,5,4}};
				core::vector3df intersect, closest_hit(lineend);
				core::vector3df v[4], cross[4];
				int i, p, numhits = 0;
	
				//test for intersection of line with the planes
				for (i=0; i<6; i++) 
				{
					core::plane3df plane(corners[indices[i][0]],corners[indices[i][1]],corners[indices[i][2]]);

					if (plane.getIntersectionWithLimitedLine (linestart, lineend, intersect)) 
					{
						//now determine if hit is inside face of cube
						//get vectors from intersection to each corner of the face
						for (p=0; p<4; p++) 
							v[p] = (corners[indices[i][p]] - intersect);

						//get crossproducts like this:
						//cross[0] = v[1].crossProduct(v[0]);
						//cross[1] = v[2].crossProduct(v[1]);	etc.						
						for (p=0; p<4; p++) 
							cross[p] = v[(p+1)%4].crossProduct(v[p]);

						//if all crossproducts are pointing same way, point is inside face
						if (cross[0].dotProduct(cross[1]) > 0 &&
							cross[1].dotProduct(cross[2]) > 0 &&
							cross[2].dotProduct(cross[3]) > 0 &&
							cross[3].dotProduct(cross[0]) > 0) 
						{
							if (((intersect - linestart).getLengthSQ()) < ((closest_hit - linestart).getLengthSQ())) 
							{ 
								//point is nearest
								closest_hit = intersect;
								if (++numhits == 2) break; //can only hit 2 planes
							}
						}
					}
				}
			
				if (numhits > 0) 
				{
					f32 distance = 0.0f;
					for (s32 e=0; e<8; ++e)
					{
						f32 t = corners[e].getDistanceFromSQ(pos);
						if (t > distance)
							distance = t;
					}
					//it is more accurate to get the exact distance to the intersection point
					//i.e. f32 distance = pos.getDistanceFromSQ(closest_hit);
					//but this will sometimes pick the wrong node, such as an IShadowVolumeSceneNode				
				
					if (distance < outbestdistance)
					{
						outbestnode = current;
						outbestdistance = distance;
					}
				}
			}
		}

		getPickedNodeBB(current, linemiddle, linevect, pos, 
			halflength, bits, bNoDebugObjects, outbestdistance, outbestnode);
	}
}
Anyone want to give this a try?
Incidentally, by removing the call to core::aabbox3d::intersectsWithLine(), the picking problems are fixed. I think this function is buggy anyway, but I don't know why.
neotoma
Posts: 10
Joined: Tue Mar 29, 2005 9:49 am
Location: Germany
Contact:

Post by neotoma »

Hi Nomad.

I use your 'patch', and it works very fine. It should exists as a alternative in Irrlicht.
Maybe Hybrid/Bitplane or niko can put it in the Engine.

Mike
bitplane
Admin
Posts: 3204
Joined: Mon Mar 28, 2005 3:45 am
Location: England
Contact:

Post by bitplane »

Are you using svn irrlicht? I thought getPickedNodeBB was fixed now
Submit bugs/patches to the tracker!
Need help right now? Visit the chat room
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I provided a fix that is used in the SVN version a few weeks ago. You can see my comments and the suggested solution here. There were also some relevant changes that bitplane mentions in this post.

Picking is working much better for me, but as you can see from the original post, others were having problems before and after my fix. I wasn't able to get them to provide a test case or debug the problem, so I don't really know if the problem is related to something they were doing or something that I didn't see in the collision manager code.

One thing to note is that the transformBoxEx() call will give you the bounding box that encloses the object space axis aligned bounding box. The resulting box will be at least as big as the original, usually larger. If you transform the ray into object space, you are testing against the original object space axis aligned bounding box which will give more accurate hit tests.
Post Reply