Change ray/bounding box selection so that the nearest node is determined by the actual collision point with the bounding box (or the distance to the box centre if the ray starts inside the box). Since this is a functional change from the old behaviour, I'm only committing to the trunk. This will be slightly slower than the old method, but should return the correct node; I consider accuracy to take priority over efficiency for collision. tests/sceneCollisionManager.cpp has been updated, as has examples/07.Collision/main.cpp. git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@2047 dfc29bdd-3216-0410-991c-e03cc46cb475
This commit is contained in:
parent
6fe8e2d89d
commit
bac02b5bb0
@ -64,6 +64,8 @@ int main()
|
||||
if (q3levelmesh)
|
||||
q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));
|
||||
|
||||
q3node->setID(0); // Make it an invalid target for bounding box collision
|
||||
|
||||
/*
|
||||
So far so good, we've loaded the quake 3 level like in tutorial 2. Now,
|
||||
here comes something different: We create a triangle selector. A
|
||||
@ -132,6 +134,7 @@ int main()
|
||||
scene::ICameraSceneNode* camera =
|
||||
smgr->addCameraSceneNodeFPS(0, 100.0f, .3f, -1, 0, 0, true, 3.f);
|
||||
camera->setPosition(core::vector3df(-100,50,-150));
|
||||
camera->setID(0); // Make it an invalid target for bounding box collision
|
||||
|
||||
if (selector)
|
||||
{
|
||||
@ -164,8 +167,11 @@ int main()
|
||||
bill->setMaterialFlag(video::EMF_LIGHTING, false);
|
||||
bill->setMaterialFlag(video::EMF_ZBUFFER, false);
|
||||
bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));
|
||||
bill->setID(0); // Make it an invalid target for bounding box collision
|
||||
|
||||
// add 3 animated faeries.
|
||||
// add 3 animated faeries. We'll make their bounding boxes visible so
|
||||
// that we can see the same boxes that the scene collision manager is
|
||||
// using to perform the getSceneNodeFromCameraBB() check below.
|
||||
|
||||
video::SMaterial material;
|
||||
material.setTexture(0, driver->getTexture("../../media/faerie2.bmp"));
|
||||
@ -180,16 +186,19 @@ int main()
|
||||
node->setPosition(core::vector3df(-70,0,-90));
|
||||
node->setMD2Animation(scene::EMAT_RUN);
|
||||
node->getMaterial(0) = material;
|
||||
|
||||
node->setDebugDataVisible(scene::EDS_BBOX_ALL);
|
||||
|
||||
node = smgr->addAnimatedMeshSceneNode(faerie);
|
||||
node->setPosition(core::vector3df(-70,0,-30));
|
||||
node->setMD2Animation(scene::EMAT_SALUTE);
|
||||
node->getMaterial(0) = material;
|
||||
node->setDebugDataVisible(scene::EDS_BBOX_ALL);
|
||||
|
||||
node = smgr->addAnimatedMeshSceneNode(faerie);
|
||||
node->setPosition(core::vector3df(-70,0,-60));
|
||||
node->setMD2Animation(scene::EMAT_JUMP);
|
||||
node->getMaterial(0) = material;
|
||||
node->setDebugDataVisible(scene::EDS_BBOX_ALL);
|
||||
}
|
||||
|
||||
material.setTexture(0, 0);
|
||||
@ -197,9 +206,10 @@ int main()
|
||||
|
||||
// Add a light
|
||||
|
||||
smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
|
||||
scene::ILightSceneNode * light = smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
|
||||
video::SColorf(1.0f,1.0f,1.0f,1.0f),
|
||||
600.0f);
|
||||
light->setID(0); // Make it an invalid target for bounding box collision
|
||||
|
||||
|
||||
/*
|
||||
@ -261,10 +271,18 @@ int main()
|
||||
we ask the collision manager for this, and if we've got a scene
|
||||
node, we highlight it by disabling Lighting in its material, if
|
||||
it is not the billboard or the quake 3 level.
|
||||
We use a collision bitmask of 1, i.e. any scene node with a scene ID
|
||||
with bit 1 set is valid for collision. Scene nodes ID defaults to 0xFFFFFFFF
|
||||
so all nodes will have this bit by default. We have called setID(0) on
|
||||
the nodes that we don't care about in order to clear this bit and make
|
||||
them invalid targets for collision. This makes the test a bit more
|
||||
efficient, and also stops us from accidentally picking unexpected nodes,
|
||||
e.g. the quake3 level, camera, light and billboard nodes. By default,
|
||||
these *are* valid targets for bounding box selection.
|
||||
*/
|
||||
|
||||
selectedSceneNode =
|
||||
smgr->getSceneCollisionManager()->getSceneNodeFromCameraBB(camera);
|
||||
smgr->getSceneCollisionManager()->getSceneNodeFromCameraBB(camera, 1);
|
||||
|
||||
if (lastSelectedSceneNode)
|
||||
lastSelectedSceneNode->setMaterialFlag(video::EMF_LIGHTING, true);
|
||||
|
@ -79,9 +79,9 @@ void CSceneCollisionManager::getPickedNodeBB(ISceneNode* root,
|
||||
f32& outbestdistance,
|
||||
ISceneNode*& outbestnode)
|
||||
{
|
||||
core::vector3df edges[8];
|
||||
|
||||
const core::list<ISceneNode*>& children = root->getChildren();
|
||||
core::line3df truncatedRay(ray);
|
||||
const core::vector3df rayVector = ray.getVector().normalize();
|
||||
|
||||
core::list<ISceneNode*>::ConstIterator it = children.begin();
|
||||
for (; it != children.end(); ++it)
|
||||
@ -99,41 +99,96 @@ void CSceneCollisionManager::getPickedNodeBB(ISceneNode* root,
|
||||
continue;
|
||||
|
||||
// transform vector from world space to object space
|
||||
core::line3df line(ray);
|
||||
core::line3df line(truncatedRay);
|
||||
mat.transformVect(line.start);
|
||||
mat.transformVect(line.end);
|
||||
|
||||
const core::aabbox3df& box = current->getBoundingBox();
|
||||
core::aabbox3df box = current->getBoundingBox();
|
||||
|
||||
// do the initial intersection test in object space, since the
|
||||
// object space box is more accurate.
|
||||
if (box.intersectsWithLine(line))
|
||||
|
||||
if(box.isPointInside(line.start))
|
||||
{
|
||||
// If the line starts inside the box, then consider the distance as being
|
||||
// to the centre of the box.
|
||||
const f32 toIntersectionSq = line.start.getDistanceFromSQ(box.getCenter());
|
||||
if(toIntersectionSq < outbestdistance)
|
||||
{
|
||||
outbestdistance = toIntersectionSq;
|
||||
outbestnode = current;
|
||||
|
||||
// And we can truncate the ray to stop us hitting further nodes.
|
||||
truncatedRay.end = truncatedRay.start + (rayVector * sqrtf(toIntersectionSq));
|
||||
}
|
||||
}
|
||||
else if (box.intersectsWithLine(line))
|
||||
{
|
||||
// Now transform into world space, to take scaling into account.
|
||||
current->getAbsoluteTransformation().transformBox(box);
|
||||
|
||||
core::vector3df edges[8];
|
||||
box.getEdges(edges);
|
||||
f32 distance = 0.0f;
|
||||
|
||||
for (s32 e=0; e<8; ++e)
|
||||
/* We need to check against each of 6 faces, composed of these corners:
|
||||
/3--------/7
|
||||
/ | / |
|
||||
/ | / |
|
||||
1---------5 |
|
||||
| 2- - -| -6
|
||||
| / | /
|
||||
|/ | /
|
||||
0---------4/
|
||||
|
||||
Note that we define them as opposite pairs of faces.
|
||||
*/
|
||||
static const s32 faceEdges[6][3] =
|
||||
{
|
||||
{ 0, 1, 5 }, // Front
|
||||
{ 6, 7, 3 }, // Back
|
||||
{ 2, 3, 1 }, // Left
|
||||
{ 4, 5, 7 }, // Right
|
||||
{ 1, 3, 7 }, // Top
|
||||
{ 2, 0, 4 } // Bottom
|
||||
};
|
||||
|
||||
core::vector3df intersection;
|
||||
core::plane3df facePlane;
|
||||
|
||||
bool gotHit = false;
|
||||
for(s32 face = 0; face < 6; ++face)
|
||||
{
|
||||
// Transform the corner into world coordinates, to take
|
||||
// scaling into account.
|
||||
current->getAbsoluteTransformation().transformVect(edges[e]);
|
||||
facePlane.setPlane(edges[faceEdges[face][0]],
|
||||
edges[faceEdges[face][1]],
|
||||
edges[faceEdges[face][2]]);
|
||||
|
||||
// and compare it against the world space ray start position
|
||||
f32 t = edges[e].getDistanceFromSQ(ray.start);
|
||||
// Only consider lines that might be entering through the plane, since we know
|
||||
// that the start point is outside the box.
|
||||
if(facePlane.classifyPointRelation(ray.start) != core::ISREL3D_FRONT)
|
||||
continue;
|
||||
|
||||
// We're looking for the furthest corner; this is a crude
|
||||
// test; we should be checking the actual ray/plane
|
||||
// intersection.
|
||||
// http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?p=181419
|
||||
if (t > distance)
|
||||
distance = t;
|
||||
// Don't bother using a limited ray, since we already know that it should be long
|
||||
// enough to intersect with the box.
|
||||
if(facePlane.getIntersectionWithLine(ray.start, rayVector, intersection))
|
||||
{
|
||||
|
||||
const f32 toIntersectionSq = ray.start.getDistanceFromSQ(intersection);
|
||||
if(toIntersectionSq < outbestdistance)
|
||||
{
|
||||
outbestdistance = toIntersectionSq;
|
||||
outbestnode = current;
|
||||
}
|
||||
}
|
||||
|
||||
// If the ray is entering through the first face of a pair, then it can't
|
||||
// also be entering through the opposite face, and so we can skip that face.
|
||||
if(0 == (face % 2))
|
||||
++face;
|
||||
}
|
||||
|
||||
if (distance < outbestdistance)
|
||||
{
|
||||
outbestnode = current;
|
||||
outbestdistance = distance;
|
||||
}
|
||||
// If we got a hit, we can now truncate the ray to stop us hitting further nodes.
|
||||
if(gotHit)
|
||||
truncatedRay.end = truncatedRay.start + (rayVector * sqrtf(outbestdistance));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,9 +78,12 @@ static bool testGetSceneNodeFromScreenCoordinatesBB(IrrlichtDevice * device,
|
||||
ISceneManager * smgr,
|
||||
ISceneCollisionManager * collMgr)
|
||||
{
|
||||
IMeshSceneNode * cubeNode1 = smgr->addCubeSceneNode(10.f, 0, -1, vector3df(0, 0, 20));
|
||||
// Create 3 nodes. The nearest node actually contains the camera.
|
||||
IMeshSceneNode * cubeNode1 = smgr->addCubeSceneNode(10.f, 0, -1, vector3df(0, 0, 4));
|
||||
IMeshSceneNode * cubeNode2 = smgr->addCubeSceneNode(10.f, 0, -1, vector3df(0, 0, 30));
|
||||
cubeNode2->setRotation(vector3df(90.f, 90.f, 90.f)); // Just check that rotation doesn't stop us hitting it.
|
||||
IMeshSceneNode * cubeNode3 = smgr->addCubeSceneNode(10.f, 0, -1, vector3df(0, 0, 40));
|
||||
cubeNode3->setRotation(vector3df(180.f, 180.f, 180.f)); // Just check that rotation doesn't stop us hitting it.
|
||||
|
||||
ICameraSceneNode * camera = smgr->addCameraSceneNode();
|
||||
device->run();
|
||||
@ -88,7 +91,7 @@ static bool testGetSceneNodeFromScreenCoordinatesBB(IrrlichtDevice * device,
|
||||
|
||||
ISceneNode * hitNode = collMgr->getSceneNodeFromScreenCoordinatesBB(position2d<s32>(80, 60));
|
||||
|
||||
// Expect the first node to be hit.
|
||||
// Expect the first node to be hit, since we're starting the check from inside it.
|
||||
bool result = true;
|
||||
if(hitNode != cubeNode1)
|
||||
{
|
||||
@ -175,9 +178,19 @@ static bool getScaledPickedNodeBB(IrrlichtDevice * device,
|
||||
farTarget->setPosition(vector3df(0.f, 0.f, 500.f));
|
||||
farTarget->updateAbsolutePosition();
|
||||
|
||||
// Create a node that's slightly further away than the closest node,
|
||||
// but thinner. Its furthest corner is closer, but the collision
|
||||
// position is further, so it should not be selected.
|
||||
ISceneNode* middleTarget = smgr->addCubeSceneNode(10.f);
|
||||
middleTarget->setPosition(vector3df(0.f, 0.f, 101.f));
|
||||
middleTarget->setScale(vector3df(1.f, 1.f, 0.5f));
|
||||
middleTarget->updateAbsolutePosition();
|
||||
|
||||
ISceneNode* nearTarget = smgr->addCubeSceneNode(10.f);
|
||||
nearTarget->setPosition(vector3df(0.f, 0.f, 100.f));
|
||||
nearTarget->updateAbsolutePosition();
|
||||
// We'll rotate this node 90 degrees to show that we can hit its side.
|
||||
nearTarget->setRotation(vector3df(0.f, 90.f, 0.f));
|
||||
|
||||
line3df ray(0.f, 0.f, 0.f, 0.f, 0.f, 500.f);
|
||||
|
||||
@ -189,6 +202,8 @@ static bool getScaledPickedNodeBB(IrrlichtDevice * device,
|
||||
logTestString("getSceneNodeFromRayBB() didn't hit anything.\n");
|
||||
else if(hit == farTarget)
|
||||
logTestString("getSceneNodeFromRayBB() hit the far (scaled) target.\n");
|
||||
else if(hit == middleTarget)
|
||||
logTestString("getSceneNodeFromRayBB() hit the far (scaled) target.\n");
|
||||
|
||||
if(!result)
|
||||
assert(false);
|
||||
|
@ -1,2 +1,2 @@
|
||||
Test suite pass at GMT Mon Jan 5 15:14:30 2009
|
||||
|
||||
Test suite pass at GMT Tue Jan 06 15:07:16 2009
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user