First problem is with core::matrix4::transformBox() - this gets the transformed bounding box for the intersection test. The transformed box should completely enclose the transformed scene node. However, this function just transforms the 2 corners of the box which are the maximum and minimum extents. This can result in a box that does not enclose the geometry completely. Should use transformBoxEx() instead, which calculates a completely new box.
i.e.
Code: Select all
inline void matrix4::transformBox(core::aabbox3d<f32>& box) const
{
transformBoxEx(box);
//transformVect(box.MinEdge);
//transformVect(box.MaxEdge);
//box.repair();
}
I've changed this function to do ray intersections against the oriented bounding box of the scene node, not the transformed axis-aligned box that was originally used (the transformed aabb can be larger than the actual mesh, which is not desirable). This really improves node picking.
My approach is to do plane-line intersections for each face of the box, and use crossproducts to see if the point lies within the 4 corners of the face (is there a better way to do this?).
This is slower than the original code of course, but I doubt that matters much.
Code: Select all
void CSceneCollisionManager::getPickedNodeBB(ISceneNode* root,
const core::vector3df& linemiddle,
const core::vector3df& linevect,
const core::vector3df& pos,
f32 halflength, s32 bits,
bool bNoDebugObjects,
f32& outbestdistance,
ISceneNode*& outbestnode)
{
const core::list<ISceneNode*>& children = root->getChildren();
core::vector3df corners[8];
const core::vector3df linestart = linemiddle - linevect * halflength;
const core::vector3df lineend = linemiddle + linevect*halflength;
const f32 length = halflength * 2;
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))))
{
const core::aabbox3df& box = current->getBoundingBox();
//get the 6 planes of the box sides
box.getEdges(corners);
//transform into an object-oriented bounding box
core::matrix4 mat = current->getAbsoluteTransformation();
core::aabbox3df box1;
for (int c=0; c<8; c++) {
mat.transformVect(corners[c]);
//make an aabb that encloses this box, for a quick cull test
box1.addInternalPoint(corners[c]);
}
//do a cull based on the aabb
if (!( (linestart.X < box1.MinEdge.X && lineend.X < box1.MinEdge.X) ||
(linestart.X > box1.MaxEdge.X && lineend.X > box1.MaxEdge.X) ||
(linestart.Y < box1.MinEdge.Y && lineend.Y < box1.MinEdge.Y) ||
(linestart.Y > box1.MaxEdge.Y && lineend.Y > box1.MaxEdge.Y) ||
(linestart.Z < box1.MinEdge.Z && lineend.Z < box1.MinEdge.Z) ||
(linestart.Z > box1.MaxEdge.Z && lineend.Z > box1.MaxEdge.Z)) )
{
//set up array of indices to corners, for each face
//winding order is critical here
const s32 indices[6][4] = {{0,4,6,2},{1,3,7,5},{0,2,3,1},{4,5,7,6},{2,6,7,3},{0,1,5,4}};
core::vector3df intersect, closest_hit(lineend);
core::vector3df v[4], cross[4];
int i, p, numhits = 0;
//test for intersection of line with the planes
for (i=0; i<6; i++)
{
core::plane3df plane(corners[indices[i][0]],corners[indices[i][1]],corners[indices[i][2]]);
if (plane.getIntersectionWithLimitedLine (linestart, lineend, intersect))
{
//now determine if hit is inside face of cube
//get vectors from intersection to each corner of the face
for (p=0; p<4; p++)
v[p] = (corners[indices[i][p]] - intersect);
//get crossproducts like this:
//cross[0] = v[1].crossProduct(v[0]);
//cross[1] = v[2].crossProduct(v[1]); etc.
for (p=0; p<4; p++)
cross[p] = v[(p+1)%4].crossProduct(v[p]);
//if all crossproducts are pointing same way, point is inside face
if (cross[0].dotProduct(cross[1]) > 0 &&
cross[1].dotProduct(cross[2]) > 0 &&
cross[2].dotProduct(cross[3]) > 0 &&
cross[3].dotProduct(cross[0]) > 0)
{
if (((intersect - linestart).getLengthSQ()) < ((closest_hit - linestart).getLengthSQ()))
{
//point is nearest
closest_hit = intersect;
if (++numhits == 2) break; //can only hit 2 planes
}
}
}
}
if (numhits > 0)
{
f32 distance = 0.0f;
for (s32 e=0; e<8; ++e)
{
f32 t = corners[e].getDistanceFromSQ(pos);
if (t > distance)
distance = t;
}
//it is more accurate to get the exact distance to the intersection point
//i.e. f32 distance = pos.getDistanceFromSQ(closest_hit);
//but this will sometimes pick the wrong node, such as an IShadowVolumeSceneNode
if (distance < outbestdistance)
{
outbestnode = current;
outbestdistance = distance;
}
}
}
}
getPickedNodeBB(current, linemiddle, linevect, pos,
halflength, bits, bNoDebugObjects, outbestdistance, outbestnode);
}
}
Incidentally, by removing the call to core::aabbox3d::intersectsWithLine(), the picking problems are fixed. I think this function is buggy anyway, but I don't know why.