The bug is that getIntersectionOfPlaneWithLine returns a line-triangle intersection, but a following call to isPointInside using that returned intersection point sometimes returns false and the result is very unpredictable. This happens when the point is exactly on a triangle border.
From what I can see it's a combination of float-inaccuracies happening in triangle3d::getIntersectionOfPlaneWithLine and in triangle3d::isOnSameSide. The problem in isOnSameSide is that points on borders should cause a cross-product to be exactly 0, but we get minor non-zero deviations. I think it could be improved with the check I marked with "Fix 1", but it will be a little expensive. Which is probably fine in this case...
The other inaccuracy happens already in getIntersectionOfPlaneWithLine simply because we work with floats. People can work around that by just using triangle3d<irr::f64>, but it probably would mean a lot of conversions in user-case (we don't even have draw functions for those). So maybe we could add another version of the function which does the conversion to double internally - I marked it with "Fix 2".
In combination those 2 fixes would allow running our triangle tests again without errors. Any comments?
Fixes + some test-code which makes visualizing the problem a little easier (code split into 2 posts as forum does not like long code):
Code: Select all
#include <irrlicht.h>
#include <iostream>
#include <cassert>
using namespace irr;
using namespace core;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
// helper function to draw a box around center
void dummyDrawBox(irr::core::vector3df center, irr::video::SColor col, irr::video::IVideoDriver * videoDriver )
{
aabbox3df box(center);
irr::f32 r = 0.5f;
box.addInternalPoint( center + vector3df(r, 0, 0) );
box.addInternalPoint( center + vector3df(0, r, 0) );
box.addInternalPoint( center + vector3df(0, 0, r) );
box.addInternalPoint( center + vector3df(-r, 0, 0) );
box.addInternalPoint( center + vector3df(0, -r, 0) );
box.addInternalPoint( center + vector3df(0, 0, -r) );
videoDriver->draw3DBox( box, col);
}
template<class T>
static bool isOnSameSide(const vector3d<T>& p1, const vector3d<T>& p2,
const vector3d<T>& a, const vector3d<T>& b)
{
vector3d<T> bminusa = b - a;
vector3d<T> cp1 = bminusa.crossProduct(p1 - a);
vector3d<T> cp2 = bminusa.crossProduct(p2 - a);
T res = cp1.dotProduct(cp2);
if ( res < 0 )
{
// Fix 1
// this will catch many cases, but expensive and we don't really know the necessary epsilon for iszero
vector3d<T> cp1 = bminusa.normalize().crossProduct((p1 - a).normalize());
if ( core::iszero(cp1.X, 0.01f)
&& core::iszero(cp1.Y, 0.01f)
&& core::iszero(cp1.Z, 0.01f) )
{
res = 0.f;
}
}
return (res >= 0.0f);
}
template<class T>
static bool isOnSameSideUsingPlanes(const vector3d<T>& p1, const vector3d<T>& p2,
const vector3d<T>& a, const vector3d<T>& b)
{
// Bad, bad idea - just here so the next person doesn't try it again
irr::core::line3d<T> ab(a, b);
vector3d<T> orig = ab.getClosestPoint(p2);
plane3d<T> plane(orig, (p2-orig).normalize());
EIntersectionRelation3D rel = plane.classifyPointRelation(p1);
return rel == ISREL3D_FRONT || rel == ISREL3D_PLANAR;
}
// Fix 2 - work internally with doubles to get a more precise result
template<class T>
bool getIntersectionOfPlaneWithLinePrecise(const core::triangle3d<T>& triangleIn, const vector3d<T>& linePointIn,
const vector3d<T>& lineVectIn, vector3d<T>& outIntersectionIn)
{
const vector3d<f64> linePoint(linePointIn.X, linePointIn.Y, linePointIn.Z);
const vector3d<f64> lineVect(lineVectIn.X, lineVectIn.Y, lineVectIn.Z);
vector3d<f64> outIntersection;
core::triangle3d<irr::f64> triangle(vector3d<f64>(triangleIn.pointA.X, triangleIn.pointA.Y, triangleIn.pointA.Z)
,vector3d<f64>(triangleIn.pointB.X, triangleIn.pointB.Y, triangleIn.pointB.Z)
, vector3d<f64>(triangleIn.pointC.X, triangleIn.pointC.Y, triangleIn.pointC.Z));
const vector3d<irr::f64> normal = triangle.getNormal().normalize();
irr::f64 t2;
if ( core::iszero ( t2 = normal.dotProduct(lineVect) ) )
return false;
irr::f64 d = triangle.pointA.dotProduct(normal);
irr::f64 t = -(normal.dotProduct(linePoint) - d) / t2;
outIntersection = linePoint + (lineVect * t);
outIntersectionIn.X = (f32)outIntersection.X;
outIntersectionIn.Y = (f32)outIntersection.Y;
outIntersectionIn.Z = (f32)outIntersection.Z;
return true;
}