problems picking scaled scene nodes
Posted: Thu Jul 05, 2007 10:27 pm
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?
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
#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;
}
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;
}
}