Offset in Collision Detection with Ray?

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
gabb
Posts: 8
Joined: Tue Aug 08, 2006 8:54 am

Offset in Collision Detection with Ray?

Post by gabb »

Hi all,

I'm experiencing a problem with my collision detection. I am trying to select/highlight a scenenode via mouse hover and it works, but I always have a offset. It seems like the bounding box is off by a dozen pixel or something.

Does anyone have an idea what could cause this? BackBuffer?

Please help :) ,
gabb
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Looks like an issue in the collision manager. The bounding box intersection test is using a poorly constructed bounding box when doing the ray-box intersection test. The following code will demonstrate...

Code: Select all

#include <irrlicht.h> 
#pragma comment(lib, "Irrlicht.lib") 

using namespace irr; 

class MyEventReceiver : public IEventReceiver
{
public:
   MyEventReceiver(scene::ISceneManager* smgr)
      : SceneManager(smgr)
      , ShiftKeyDown(false)
      , SelectedNode(0)
   {
      SceneManager->grab();
   }

   virtual ~MyEventReceiver()
   {
      SceneManager->drop();
   }

   // you may need to change the parameter type depending on your Irrlicht version
   virtual bool OnEvent(const SEvent& event)
   {
      if (event.EventType == EET_KEY_INPUT_EVENT &&
          event.KeyInput.Key == KEY_SHIFT)
      {
          ShiftKeyDown = event.KeyInput.PressedDown;
          return true;
      }

      // if shift is down and mouse clicked, then do collision test
      if (ShiftKeyDown &&
          event.EventType == EET_MOUSE_INPUT_EVENT &&
          event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
      {
         const core::position2di pos(event.MouseInput.X, event.MouseInput.Y);
         SelectedNode =
            SceneManager->getSceneCollisionManager()->getSceneNodeFromScreenCoordinatesBB(pos);
         return true;
      }

      return false;
   }

private:
   scene::ISceneManager* SceneManager;
   bool ShiftKeyDown;
public:
   scene::ISceneNode* SelectedNode;
};

// draw the world aligned bounding box that completely encloses the object bounding box
void ISceneNode_drawWorldBoundingBox(scene::ISceneNode* node,
                                     video::IVideoDriver* driver,
                                     video::SColor color = video::SColor(255, 255, 0, 0))
{
   if (!node || !driver)
      return;

   video::SMaterial matl;
   matl.Lighting = false;
   driver->setMaterial(matl);

   core::matrix4 matx;
   driver->setTransform(video::ETS_WORLD, matx);

   core::aabbox3df bbox(node->getBoundingBox());
   node->getAbsoluteTransformation().transformBoxEx(bbox);

   driver->draw3DBox(bbox, color);
}

// draw the object aligned bounding box
void ISceneNode_drawObjectBoundingBox(scene::ISceneNode* node,
                                      video::IVideoDriver* driver,
                                      video::SColor color = video::SColor(255, 0, 255, 0))
{
   if (!node || !driver)
      return;

   video::SMaterial matl;
   matl.Lighting = false;
   driver->setMaterial(matl);

   driver->setTransform(video::ETS_WORLD, node->getAbsoluteTransformation());

   driver->draw3DBox(node->getBoundingBox(), color);
}

// draw the transformed object bounding box
void ISceneNode_drawTransformedBoundingBox(scene::ISceneNode* node,
                                           video::IVideoDriver* driver,
                                           video::SColor color = video::SColor(255, 0, 0, 255))
{
   if (!node || !driver)
      return;

   video::SMaterial matl;
   matl.Lighting = false;
   driver->setMaterial(matl);

   core::matrix4 matx;
   driver->setTransform(video::ETS_WORLD, matx);

   driver->draw3DBox(node->getTransformedBoundingBox(), color);
}


int main()
{ 
   // ask user for driver 
   video::E_DRIVER_TYPE driverType =
      video::EDT_DIRECT3D9;

   // create device and exit if creation failed 
   IrrlichtDevice *device = createDevice(driverType, core::dimension2d<s32>(800, 600), 32, false, false, true);
   if (device == 0) 
      return 1; // could not create selected driver.

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

   MyEventReceiver receiver(smgr);
   device->setEventReceiver(&receiver);

   // add camera
   smgr->addCameraSceneNodeMaya();

   // load pre-built scene. you may want to remove the animators so things stay still
   smgr->loadScene("media/example.irr");

   s32 fps = 0;
   while(device->run()) 
   {
      // window is active, so render scene
      if (device->isWindowActive())
      {
         if (driver->beginScene(true, true, video::SColor(100, 50, 50, 100)))
         {
            smgr->drawAll();

            ISceneNode_drawWorldBoundingBox(receiver.SelectedNode, driver);
            ISceneNode_drawObjectBoundingBox(receiver.SelectedNode, driver);
            ISceneNode_drawTransformedBoundingBox(receiver.SelectedNode, driver);

            driver->endScene();
         }
      }
   }

   device->drop(); 

   return 0; 
}
The code uses the maya camera scene node controls, and if you press Shift+Click, you will select a node. Several bounding boxes are drawn for the selected node.

You will notice that the node selection works if you click in the area that is enclosed by the blue bounding box. Most users would expect that the green bounding box would be used for the bounding box tests, or maybe even the red box.

The issue is in the scene collision manager. It uses the nodes getTransformedBoundingBox() method to get the the bounding box for collision testing. For accurate collision testing, the ray should be transformed into object space [using the inverse of the object absolute transform] and then tested against the object space bounding box.

Travis
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

If you want the accurate bounding box test, the following code seems to work quite well. This is going to be slower than the original code, but at least the hit tests are accurate...

Code: Select all

//! recursive method for going through all scene nodes
void CSceneCollisionManager::getPickedNodeBB(ISceneNode* root,
               const core::line3df& ray,
               s32 bits,
               bool bNoDebugObjects,
               f32& outbestdistance,
               ISceneNode*& outbestnode)
{
   core::vector3df edges[8];

   const core::list<ISceneNode*>& children = root->getChildren();

   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))))
      {
         // get world to object space transform
         core::matrix4 mat;
         if (!current->getAbsoluteTransformation().getInverse(mat))
            continue;

         // transform vector from world space to object space
         core::line3df line(ray);
         mat.transformVect(line.start);
         mat.transformVect(line.end);

         const core::aabbox3df& box = current->getBoundingBox();

         // do intersection test in object space
         if (box.intersectsWithLine(line))
         {
            box.getEdges(edges);
            f32 distance = 0.0f;

            for (s32 e=0; e<8; ++e)
            {
               f32 t = edges[e].getDistanceFromSQ(line.start);
               if (t > distance)
                  distance = t;
            }

            if (distance < outbestdistance)
            {
               outbestnode = current;
               outbestdistance = distance;
            }
         }
      }

      getPickedNodeBB(current, ray, bits, bNoDebugObjects, outbestdistance, outbestnode);
   }
}
You'll have to fix the header also, but you should be able to figure that out on your own. If someone is going to add this fix to the core, maybe I could get them to add the scene node filter concept that was proposed here.

Travis
QuantumLeap
Posts: 38
Joined: Mon Sep 25, 2006 6:31 pm
Location: San Francisco, California

Post by QuantumLeap »

Wow! I've been struggling with this same problem myself. The standard BB test is really offset and this should be corrected in the Irrlicht source. I'll try your technique Vitek, many thanks.
It's easier to curse a candle than light the darkness
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I added a post to the bugs forum on this issue. Hopefully it will be addressed. I'm surprised nobody noticed it before.
QuantumLeap
Posts: 38
Joined: Mon Sep 25, 2006 6:31 pm
Location: San Francisco, California

Post by QuantumLeap »

...For whatever reason, after I started using the new SVN version with your fix, getSceneNodeFromScreenCoordinatesBB(device->getCursorControl()->getPosition()) stopped returning the selected scene node. Weird...
It's easier to curse a candle than light the darkness
QuantumLeap
Posts: 38
Joined: Mon Sep 25, 2006 6:31 pm
Location: San Francisco, California

Post by QuantumLeap »

Actually getSceneNodeFromScreenCoordinatesBB is always returning the camera node!
It's easier to curse a candle than light the darkness
QuantumLeap
Posts: 38
Joined: Mon Sep 25, 2006 6:31 pm
Location: San Francisco, California

Post by QuantumLeap »

Forget it, my bad :oops:
It's easier to curse a candle than light the darkness
gabb
Posts: 8
Joined: Tue Aug 08, 2006 8:54 am

Post by gabb »

QuantumLeap: what was the reason it stopped working for you? I have the exactly same problem. After building it from source my collision detection also stopped returning the nodes. :?

BTW: do you happen to have TextSceneNodes? Those disappeared for me too, but I am not sure if this is not a glitch in my .NET wrapper or whatsoever ..
gabb
Posts: 8
Joined: Tue Aug 08, 2006 8:54 am

Post by gabb »

Actually, nevermind. The new changes actually make it worse - for my project that is. I used to be able to select nodes that are behind other nodes, with the new code that stopped working - apparently the *best node is now the very first node that has a collision with the ray - not the one who's been aimed at best ... if you know what I mean.

Also: I still got that offset, it has not become better at all as far as I can tell.

Here is a screenshot. In the upper right corner of the 3d screen you can see a white node - this is the currently selected node, although the mousepointer is at least one centimeter underneath it. (unfortunately mouse pointers are not included in screenshots - take my word for it).

Image

EDIT: marked the node and mouse position
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Are you absolutely sure that you replaced the Irrlicht.lib/.dll and relinked? The test code I used makes 40 randomly placed cubes and displays the picked node. It works as I would expect.
best node is now the very first node that has a collision with the ray - not the one who's been aimed at best ...
It has always been that way. The picked node is the node closest to the start of the ray [or camera position] that has its bounding box intersected by that ray. I didn't change that code at all. The only thing that I changed was the bounding box that is used for picking.

It might be useful to show the object bounding box for each of your scene nodes. If the bounding box is not accurate the picking won't work right.

If you can provide a simple testcase that shows the problem, I'd be happy to look at it for you. Something simple, not your entire app...


Travis
QuantumLeap
Posts: 38
Joined: Mon Sep 25, 2006 6:31 pm
Location: San Francisco, California

Post by QuantumLeap »

Gabb

I have exactly the same problem as you! Vitek's patch corrected the bounding boxes problem (I display them and the location is as should be) but now it seems the ray and not the box has an offset. Just like in your case, it's about 80 pixels under the picked node, but other then that it works perfectly.
BTW, which GUI are you using for your project? It seems really cool! Is it wxWidgets?

Vitek, yes I replaced the .dll and .lib files. Did you try a test application with the whole new SVN code?
It's easier to curse a candle than light the darkness
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Vitek, yes I replaced the .dll and .lib files. Did you try a test application with the whole new SVN code.
Good point. I've been using my custom Irrlicht that is based off SVN revision 243. I'll try with latest and let you know what I see.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

When using the SVN version I occasionally see the camera as a selected node, but I'm not seeing any issues with the pick position being off. There is obviously something that you guys are doing that my test code is not. From the screenshot it looks like gabb is rendering to an external window. Is this the same for you? If so, this could be the source of the problem.

The camera pick is a seperate issue I'm looking at. I need to find out why the camera nod is not picked with my version of Irrlicht, but it is with the SVN version. Of course that is a totally different problem, and you could eliminate it by simply setting the camera id to 0.

Also note that there is an issue with the picking of text scene nodes. The bounding box of the node is always 1x1x1, but the font appears the same size. This means that if the camera is very near to the text scene node, the node bounding box will take up most of the screen, but the text only takes up a very small area.

Travis
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

The camera selection issue happens with the SVN version only if the HighPrecisionFPU flag is not set with createDeviceEx. I removed this flag on my custom Irrlicht build because I manually control the FPU state myself.
Post Reply