triangle selector gives weird results

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
Post Reply
morris
Posts: 36
Joined: Tue Jul 10, 2007 10:10 am

triangle selector gives weird results

Post by morris »

hi,

i've set up a triangle selector on my level mesh, nothing special, i've used the code from the example. the outline of the code looks like this:

Code: Select all

SMesh *mesh;

// ... create the mesh, this works perfectly
// mesh has multiple mesh buffers

IMeshSceneNode *Level = SMGR->addMeshSceneNode(mesh);
Level->setReadOnlyMaterials(true);

ITriangleSelector *selector = SMGR->createOctTreeTriangleSelector(Level->getMesh(), Level, 32);

Level->setTriangleSelector(selector);
selector->drop();
now, i don't use irr's collision response animator, but i use the ITriangleSelector::getTriangle() method with a bounding box to get the triangles that lie within that box (see api). i've set up an event receiver to move this box around, so that i can see which triangles are selected.

Code: Select all

while(Device->run()) {

  #define MAX_TRIANGLES 100; // just for debugging

  // ... update the box... this works because it is correctly drawn where i'm moving it with w,a,s,d

  triangle3df triangles[MAX_TRIANGLES];
  s32 out = 0;
  selector->getTriangles(triangles, MAX_TRIANGLES, out, box);

  // draw everything...
  // draw boundingbox
  // draw "triangles" array

}
however, i sometimes get quite weird and inacceptable results. see e.g. this screenshot, the floor triangles are not selected which would result in bad collision behaviour: http://team-mad.de/morris/tri.jpg

is there something i can do?? i am quite sure that this is not a problem of the mesh, as it displays correctly. also, the bounding boxes are all correct.

--morris
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

Uh oh. That does look borked; it seems like the provided box isn't being used. I'll have a poke at that.

Compilable demo:

Code: Select all

#include <irrlicht.h>
#include <iostream>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

class MyEventReceiver : public IEventReceiver
{
public:
	// This is the one method that we have to implement
	virtual bool OnEvent(const SEvent& event)
	{
		// Remember whether each key is down or up
		if (event.EventType == EET_KEY_INPUT_EVENT)
			KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;

		return false;
	}

	// This is used to check whether a key is being held down
	virtual bool IsKeyDown(EKEY_CODE keyCode) const
	{
		return KeyIsDown[keyCode];
	}

	MyEventReceiver()
	{
		for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
			KeyIsDown[i] = false;
	}

private:
	// We use this array to store the current state of each key
	bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

int main()
{
	IrrlichtDevice *device =
		createDevice(EDT_OPENGL, dimension2d<s32>(640, 480), 32, false);
	   
	if (device == 0)
		return 1; // could not create selected driver.
   
	MyEventReceiver receiver;
	device->setEventReceiver(&receiver);

	IVideoDriver* driver = device->getVideoDriver();
	ISceneManager* smgr = device->getSceneManager();

	IMetaTriangleSelector * meta = smgr->createMetaTriangleSelector();

	device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
	IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
	if (q3levelmesh)
	{
		ISceneNode* q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));
	   
		q3node->setPosition(vector3df(-1350,-130,-1400));

		ITriangleSelector * selector =
			smgr->createOctTreeTriangleSelector(q3levelmesh->getMesh(0), q3node, 128);
		meta->addTriangleSelector(selector);
		selector->drop();
	}

	IAnimatedMesh * mesh = smgr->getMesh("../../media/sydney.md2");
	IAnimatedMeshSceneNode * sydney = smgr->addAnimatedMeshSceneNode( mesh );
	if (sydney)
	{
		sydney->setPosition(vector3df(15, -10, 15));
		sydney->setMaterialFlag(EMF_LIGHTING, false);
		sydney->setMD2Animation ( EMAT_STAND );
		sydney->setAnimationSpeed(0.f);
		sydney->setMaterialTexture( 0, driver->getTexture("../../media/sydney.bmp") );

		ITriangleSelector * selector =
			smgr->createTriangleSelector(sydney->getMesh()->getMesh(0), sydney);
		meta->addTriangleSelector(selector);
		selector->drop();
	}

	ICameraSceneNode* camera =
		smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f, -1, 0, 0, true);
	camera->setPosition(vector3df(-100,50,-150));
	camera->updateAbsolutePosition();
	camera->setTarget(camera->getAbsolutePosition() + vector3df(0, 0, 20));

	if (meta)
	{
		ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
			meta, camera, vector3df(30,50,30),
			vector3df(0,-3,0),
			vector3df(0,50,0));
		camera->addAnimator(anim);
		anim->drop();
	}

	device->getCursorControl()->setVisible(false);

	enum
	{
//		MAX_TRIANGLES = 3, // Small to test the count limits
		MAX_TRIANGLES = 5000, // Large to test getting all the triangles
		BOX_SIZE = 30
	};

	triangle3df triangles[MAX_TRIANGLES];
	vector3df boxPosition(camera->getAbsolutePosition());

	SMaterial unlit;
	unlit.Lighting = false;
	unlit.Thickness = 3.f;

	while(device->run())
	if (device->isWindowActive())
	{
		driver->beginScene(true, true, 0);
		smgr->drawAll();

		if(receiver.IsKeyDown(KEY_KEY_A))
			boxPosition.X -= 1.f;
		if(receiver.IsKeyDown(KEY_KEY_D))
			boxPosition.X += 1.f;
		if(receiver.IsKeyDown(KEY_KEY_W))
			if(receiver.IsKeyDown(KEY_SHIFT))
				boxPosition.Y += 1.f;
			else
				boxPosition.Z += 1.f;

		if(receiver.IsKeyDown(KEY_KEY_S))
			if(receiver.IsKeyDown(KEY_SHIFT))
				boxPosition.Y -= 1.f;
			else
				boxPosition.Z -= 1.f;

		if(receiver.IsKeyDown(KEY_SPACE))
			boxPosition = camera->getAbsolutePosition();

		aabbox3df box(boxPosition.X - BOX_SIZE, boxPosition.Y - BOX_SIZE, boxPosition.Z - BOX_SIZE,
					boxPosition.X + BOX_SIZE, boxPosition.Y + BOX_SIZE, boxPosition.Z + BOX_SIZE);

		driver->setTransform(ETS_WORLD, matrix4());
		driver->setMaterial(unlit);

		driver->draw3DBox(box, SColor(255, 0, 255, 0));

		if(meta)
		{
			s32 found;
			meta->getTriangles(triangles, sizeof(triangles) / sizeof(triangles[0]), found, box);

			while(--found >= 0)
				driver->draw3DTriangle(triangles[found], SColor(255, 255, 0, 0));
		}

		driver->endScene();
	}

	meta->drop();
	device->drop();
   
	return 0;
} 
Last edited by rogerborg on Thu Mar 13, 2008 11:30 pm, edited 1 time in total.
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

The only consideration given to the box in COctTreeTriangleSelector::getTrianglesFromOctTree() is this:

Code: Select all

	if (!box.intersectsWithBox(node->Box))
		return;
So if the box intersects the oct tree node's box (including if the selection box is completely contained by the node's box), you get all the triangles, regardless of whether they are individually inside the selection box or not.

Well, that technically honours the contract:

Code: Select all

	//! Gets all triangles which lie within a specific bounding box.
	//! Please note that unoptimized triangle selectors also may return
	//! triangles which are not in the specific box at all.
But you'd expect an oct tree to be "optimized", and to do the selection properly. If the user supplied a box, then presumably he really does want only the triangles that lie "within it". Efficiency arguments be damned, the method should do what the contract implies, or else the user will have to do it himself, which is even more wasteful.

This patch fixes the problem as seen above, interpreting "within" as meaning "intersects it". If the API really does mean triangles that are fully within the box, it's easier: just test tri.isTotalInsideBox(). However, for collision purposes, I suggest that "intersects it" is a more appropriate interpretation.

HOWEVER, it's not a candidate patch (yet) because it seems like collision animators are relying on the current behaviour. With the test program above and this patch, the camera just falls straight through the floor (comment out camera->addAnimator(anim); to test the box selection).

I'll take a look at that later, along with CTriangleSelector::getTriangles(), which completely ignores any provided box and which presumably could also do the pruning internally rather than leaving the user to do it afterwards.

Code: Select all

Index: include/triangle3d.h
===================================================================
--- include/triangle3d.h	(revision 1286)
+++ include/triangle3d.h	(working copy)
@@ -49,6 +49,47 @@
 				box.isPointInside(pointC));
 		}
 
+
+        //! Determines if the triangle is totally outside a bounding box.
+        //! \param box: Box to check.
+        //! \return Returns true if the triangle is outside the box,
+        //! and false otherwise.
+        bool isTotalOutsideBox(const aabbox3d<T>& box) const
+        {
+            if(pointA.X > box.MaxEdge.X
+                && pointB.X > box.MaxEdge.X
+                && pointC.X > box.MaxEdge.X)
+                return true;
+
+            if(pointA.Y > box.MaxEdge.Y
+                && pointB.Y > box.MaxEdge.Y
+                && pointC.Y > box.MaxEdge.Y)
+                return true;
+
+            if(pointA.Z > box.MaxEdge.Z
+                && pointB.Z > box.MaxEdge.Z
+                && pointC.Z > box.MaxEdge.Z)
+                return true;
+
+            if(pointA.X < box.MinEdge.X
+                && pointB.X < box.MinEdge.X
+                && pointC.X < box.MinEdge.X)
+                return true;
+
+            if(pointA.Y < box.MinEdge.Y
+                && pointB.Y < box.MinEdge.Y
+                && pointC.Y < box.MinEdge.Y)
+                return true;
+
+            if(pointA.Z < box.MinEdge.Z
+                && pointB.Z < box.MinEdge.Z
+                && pointC.Z < box.MinEdge.Z)
+                return true;
+
+            return false;
+        }
+
+
 		//! Get the closest point on a triangle to a point on the same plane.
 		//! \param p: Point which must be on the same plane as the triangle.
 		//! \return The closest point of the triangle
Index: source/Irrlicht/COctTreeTriangleSelector.cpp
===================================================================
--- source/Irrlicht/COctTreeTriangleSelector.cpp	(revision 1286)
+++ source/Irrlicht/COctTreeTriangleSelector.cpp	(working copy)
@@ -159,7 +159,35 @@
 	
 	for (i=0; i<cnt; ++i)
 	{
-		triangles[trianglesWritten] = node->Triangles[i];
+		core::triangle3df & tri = node->Triangles[i];
+
+		// This test is relatively cheap, so do it first.
+		if(tri.isTotalOutsideBox(box))
+		   continue;
+  
+		bool intersects = false;
+  
+		// This this is the next cheapest test.
+		if(tri.isTotalInsideBox(box))
+		   intersects = true;
+		else
+		{
+			// Then three relatively expensive tests.
+			core::line3df lineA(tri.pointA, tri.pointB);
+			core::line3df lineB(tri.pointB, tri.pointC);
+			core::line3df lineC(tri.pointC, tri.pointA);
+			if(box.intersectsWithLine(lineA)
+				||
+				box.intersectsWithLine(lineB)
+				||
+				box.intersectsWithLine(lineC))
+				intersects = true;
+		}
+  
+		if(!intersects)
+		   continue;
+
+		triangles[trianglesWritten] = tri;
 		mat->transformVect(triangles[trianglesWritten].pointA);
 		mat->transformVect(triangles[trianglesWritten].pointB);
 		mat->transformVect(triangles[trianglesWritten].pointC);
Last edited by rogerborg on Thu Mar 13, 2008 9:10 am, edited 2 times in total.
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
morris
Posts: 36
Joined: Tue Jul 10, 2007 10:10 am

Post by morris »

i've been reading the old method for a while now, and tried to make up some example cases where it might fail. as you said, technically it is okay.

the old method is provided with the root node. therefore, it WILL select any triangles that are inside any child node which intersects with the given bounding box.

therefore, i came to the conclusion that its actually IMPOSSIBLE that - assuming we have a correct oct-tree - some triangles are not selected if they actually ARE (totally) in the bounding box, just like in my example. which would mean that the construction of the oct-tree is not correct in the first place. am i right?
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

It's only "technically" ok because the API contract makes no promises about the box actually being used. ;)

I believe that the behaviour you are seeing is just down to an arbitrary 100 triangles being returned. If you increase the size of the triangle array that you're passing, you'll get all of the triangles from any oct tree nodes that intersect the box.

Are you comfortable with rebuilding Irrlicht yourself? If so, you could try the patch above, and I believe it should do what you want. I'll have a look at the animator problem... now.

[UPDATE]
Eh, we also need to deal with the box being inside the triangle. I should really do a proper box/triangle intersection test.

[UPDATE again]
... tomorrow. ;)
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

Actually, rejecting triangles with a (psuedo) AABB/AABB test cuts most of the wrong ones out. That by itself gives a big improvement, so I've submitted a candidate patch that does that. I don't want too big a patch all at once, as there's also some other improvements in there as well.

The test code above has been modified to show the improved behaviour in conjunction with the patch submitted to the tracker.
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Re: triangle selector gives weird results

Post by hybrid »

This is now fixed in SVN/trunk.
Post Reply