c++geometrycollision-detectiontriangle

Fast, but not necessarily pinpoint accurate, closest point on 3D triangle?


I have a case where I need to find the closest point to a sphere's center on a LOT of triangles. However, the accuracy isn't quite so important... it needs to be vaguely accurate, but an error rate of like 5% would be acceptable.

Currently I'm using two tests-- IsPointInTriangle and closest point on line for all three triangle edges. Specifically, this code:

Vector Math::GetClosestPointOnLine(Vector thePos, Vector theL1, Vector theL2)
{
    //
    // Do NOT try to optimize this like the 2D version... when I did it screwed up all the Hamsterball physics!
    //
    Vector aC=thePos-theL1;
    Vector aV=theL2-theL1; 

    float aD=aV.Length();
    aV/=aD;

    float aT=Dot(aV,aC);

    if (aT<0.0f) return (theL1);
    if (aT>aD) return (theL2);


    // Return the point between ‘a’ and ‘b’
    //set length of V to t. V is normalized so this is easy
    aV*=aT;
    return (theL1+aV);  
}

bool Math::IsPointInTriangle(Vector thePos, Vector theT1, Vector theT2, Vector theT3)

//bool IsPointInTriangle(const VECTOR& point,
//  const VECTOR& pa,const VECTOR& pb, const VECTOR& pc)
{
    Vector e10=theT2-theT1;
    Vector e20=theT3-theT1;
    float a = e10.Dot(e10);
    float b = e10.Dot(e20);
    float c = e20.Dot(e20);
    float ac_bb=(a*c)-(b*b);
    Vector vp(thePos.mX-theT1.mX, thePos.mY-theT1.mY, thePos.mZ-theT1.mZ);
    float d = vp.Dot(e10);
    float e = vp.Dot(e20);
    float x = (d*c)-(e*b);
    float y = (e*a)-(d*b);
    float z = x+y-ac_bb;
    return (( in(z)& ~(in(x)|in(y)) ) & 0x80000000)!=0;
}

Both of these are pretty fast, aside from a sqrt in closestpointonline. But it seems like there is probably a faster way. Any math wizards who can tell me a speedier-- at the cost of accuracy-- way to roughly say "closestpointontriangle, with maybe a 5% error threshold?"

Edit to add: The only alternative way I can think to do this would be to make four planes (plane of triangle + plane of edges in the direction of triangle normal) and fall to the triangle's plane then clip to all edge planes. But I'm not sure if that would be faster or not.


Solution

  • Let the vertices of your triangle be A, B and C.

    Find the centroid of the triangle P (we need an arbitrary point inside the triangle, and this is an easy one to calculate as the mean of the vertices).

    Now say you want to find the point in the triangle closest to point X. First project X into the plane of the triangle (call this point Y).

    Now calculate norm(P-A).(P-Y), norm(P-B).(P-Y), norm(P-C).(P-Y). Find for which two points this value is the greatest. These are the two vertices with the smallest angle triangle_vertex-centroid-Y, and will be the two points of the line the cloest point on the triangle will lie on.

    Now find the closest point from Y to the line defined by these two points. It will necessarily lie in between the two points. Call this point Z.

    Now return abs(P-Z)<abs(P-Y) ? Z : Y (This will return Y if it was already on the triangle and Z is further away).

    This should be faster as it only calculates the closest point on a line once, and you skip the explicit check for if the point is in a triangle.

    (norm(vector) is the normalized vector, or vector/abs(vector), abs(vector) is the magnitude of the vector, which seems to be vector.Length() in your code)