problems picking scaled scene nodes

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
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

problems picking scaled scene nodes

Post 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;
    }
  }
omar shaaban
Posts: 616
Joined: Wed Nov 01, 2006 6:26 pm
Location: Cairo,Egypt
Contact:

Post 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:
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post 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
omar shaaban
Posts: 616
Joined: Wed Nov 01, 2006 6:26 pm
Location: Cairo,Egypt
Contact:

Post 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!
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post 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
omar shaaban
Posts: 616
Joined: Wed Nov 01, 2006 6:26 pm
Location: Cairo,Egypt
Contact:

Post 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!! :)
omar shaaban
Posts: 616
Joined: Wed Nov 01, 2006 6:26 pm
Location: Cairo,Egypt
Contact:

Post by omar shaaban »

will this error be fixed!? :roll:
Rv
Posts: 10
Joined: Fri Mar 31, 2006 9:38 am

Post by Rv »

Harvey Gilpin, code monkey
Post Reply