Page 1 of 1

problems picking scaled scene nodes

Posted: Thu Jul 05, 2007 10:27 pm
by vitek
The following code demonstrates an issue that occurs when picking scaled scene nodes. The left row of cubes has been scaled, and the right ones have not. Notice that when picking boxes on the left they aren't accurately picked?

Code: Select all

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

using namespace irr; 

// 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); 
} 

class MyEventReceiver : public IEventReceiver
{
public:
  MyEventReceiver(IrrlichtDevice* device)
    : Device(device)
    , Selected(0)
  {
  }

  virtual ~MyEventReceiver()
  {
    if (Selected)
      Selected->drop();
  }

  virtual bool OnEvent(SEvent event)
  {
    if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
        event.KeyInput.Key == irr::KEY_ESCAPE       &&
       !event.KeyInput.PressedDown)
    {
      Device->closeDevice();
      return true;
    }

    if (event.EventType == irr::EET_MOUSE_INPUT_EVENT &&
        event.MouseInput.Event == irr::EMIE_LMOUSE_LEFT_UP)
    {
      scene::ISceneManager* smgr = Device->getSceneManager();
      gui::ICursorControl* cursor = Device->getCursorControl();

      if (Selected)
        Selected->drop();

      Selected = 
        smgr->getSceneCollisionManager()->getSceneNodeFromScreenCoordinatesBB(cursor->getPosition());

      if (Selected)
        Selected->grab();

      return true;
    }


    return false;
  }

  IrrlichtDevice* Device;
  scene::ISceneNode* Selected;
};

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

   // 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. 

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

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

   // load the scene

   // rotate the root by 45 deg
   scene::ISceneNode* root = smgr->getRootSceneNode();
   root->setRotation(core::vector3df(0, 45, 0));

   u32 n;
   for (n = 0; n < 5; ++n)
   {
     scene::ISceneNode* cube = smgr->addCubeSceneNode();
     cube->setMaterialTexture(0, driver->getTexture("../../media/particlewhite.bmp"));
     cube->setMaterialFlag(video::EMF_LIGHTING, false);

     core::vector3df scale(1.f, 1.f, 1.f);
     scale *= (n + 1) / 5.f;
     cube->setScale(scale);

     core::vector3df position(-7.5f, 0.f, (n+1) * 15.f);
     cube->setPosition(position);     
   }

   for (n = 0; n < 5; ++n)
   {
     scene::ISceneNode* cube = smgr->addCubeSceneNode(10.f * (n + 1) / 5.f);
     cube->setMaterialTexture(0, driver->getTexture("../../media/particlewhite.bmp"));
     cube->setMaterialFlag(video::EMF_LIGHTING, false);

     core::vector3df position(7.5f, 0.f, (n+1) * 15.f);
     cube->setPosition(position);     
   }

   scene::IAnimatedMesh* tile = smgr->addHillPlaneMesh("floor", core::dimension2df(15, 15), core::dimension2di(1, 1));

   for (n = 0; n < 5; ++n)
   {
     scene::ISceneNode* floor = smgr->addAnimatedMeshSceneNode(tile);
     floor->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));
     floor->setMaterialFlag(video::EMF_LIGHTING, false);

     core::vector3df position(-7.5f, 0.f, (n+1) * 15.f);
     floor->setPosition(position);
   }

   for (n = 0; n < 5; ++n)
   {
     scene::ISceneNode* floor = smgr->addAnimatedMeshSceneNode(tile);
     floor->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));
     floor->setMaterialFlag(video::EMF_LIGHTING, false);

     core::vector3df position(7.5f, 0, (n+1) * 15.f);
     floor->setPosition(position);
   }

   // add camera 
   scene::ICameraSceneNode* camera = smgr->addCameraSceneNode();

   camera->setTarget(core::vector3df(20, 0, 30));
   camera->setPosition(core::vector3df(0, 25, 0));

   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(); 

            if (receiver.Selected)
              ISceneNode_drawObjectBoundingBox(receiver.Selected, driver); 

            driver->endScene(); 
         } 
      } 
   } 

   device->drop(); 

   return 0; 
}
The problem is that the line segment in getPickedNodeBB() is transformed into object coordinates, and then the line start position is used to find the farthest point from the near plane. The issue is that the line segment has been scaled into object coordinates, which throws off this distance measurement.

I actually caused this bug when fixing a problem with picking accuracy for rotated scene nodes [see this]. A simple, but less accurate, solution is to transform the nodes bounding box into world coordinates using transformBoxEx(). That works okay as long as you aren't picking objects that have been rotated.

Another option is to transform each corner of the box back to world coordinates before doing the distance test. This is an accurate solution, and it only creates additional expense if the bounding box tests passes. Here is the change to getPickedNodeBB()...

Code: Select all

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

    // find distance to farthest edge of bounding box in world space
    // note that ray is already in world space, and we convert each
    // edge into world space before testing.
    for (s32 e=0; e<8; ++e)
    {
      current->getAbsoluteTransformation().transformVect(edges[e]);

      f32 t = edges[e].getDistanceFromSQ(ray.start);
      if (t > distance)
        distance = t;
    }

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

Posted: Fri Jul 06, 2007 12:31 pm
by omar shaaban

Code: Select all

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

    // find distance to farthest edge of bounding box in world space
    // note that ray is already in world space, and we convert each
    // edge into world space before testing.
    for (s32 e=0; e<8; ++e)
    {
      current->getAbsoluteTransformation().transformVect(edges[e]);

      f32 t = edges[e].getDistanceFromSQ(ray.start);
      if (t > distance)
        distance = t;
    }

    if (distance < outbestdistance)
    {
      outbestnode = current;
      outbestdistance = distance;
    }
  } 
so this is the solution or just test!? :roll:

Posted: Fri Jul 06, 2007 12:50 pm
by vitek
I don't have commit access, so for now it is just some code I posted on the forums. If you want to try it, put it in place and rebuild the dll.

Travis

Posted: Fri Jul 06, 2007 1:07 pm
by omar shaaban
i have another solution without rebuilding the .dll that after u scale your model just make another invisible cube in the same size as your scaled model then just when the mouse select the cube then set the selection to the scaled mesh!

Posted: Fri Jul 06, 2007 1:20 pm
by vitek
Well, the simple solution is to just not scale the nodes at all. You can make the tiles in your game larger so you don't need to make the characters smaller.

Travis

Posted: Fri Jul 06, 2007 2:59 pm
by omar shaaban
vitek wrote:Well, the simple solution is to just not scale the nodes at all. You can make the tiles in your game larger so you don't need to make the characters smaller.

Travis
ya!! :)

Posted: Mon Jul 09, 2007 6:16 pm
by omar shaaban
will this error be fixed!? :roll:

Posted: Tue Jul 10, 2007 4:08 pm
by Rv