Search code examples
c++c++113dopenframeworks

Check if ofRay intersects with ofMeshFace


I've tried many examples of code from the openframeworks forum and documentation, but I couldn't get one to do what I want.

I have an ofRay (from ofxRay), and a list of of3dPrimitive. I am trying to figure out if the ray intersects a primitive, and if so, to know which primitive the ray intersected with "first" (as in, which one is the closest to the screen).

    void renderer::selectPrimitive(int x, int y, bool shiftHeld)
{
    ofVec3f screenToWorld = (**cam).screenToWorld(ofVec3f(x, y, 0.0));

    primitive* intersectPrim = nullptr;
    int distanceClosest = std::numeric_limits<int>::max();

    ofVec3f vectNow = (screenToWorld - (**cam).getPosition());

    vectNow = vectNow.normalize();

    ofRay ray((**cam).getPosition(), vectNow, true);
    // To draw the ray on screen, for debugging
    // rays.push_back(ray);

    for (primitive& p : *scn)
    {
        if (!shiftHeld)
        {
            p.setSelected(false);
        }

        float* distance = new float(0);

        bool found = p.checkIntersectionPlaneAndLine(ray, distance);
        if (found)// && *distance >= 0 && *distance < distanceClosest)
        {
            intersectPrim = &p;
            //distanceClosest = *distance;
        }
    }

    if (distanceClosest < (std::numeric_limits<int>::max() - 1))
    {
        intersectPrim->setSelected(!intersectPrim->getSelected());
        std::cout << "Selected Primitive" << std::endl;
    }
    else
    {
        std::cout << "Selected Nothing" << std::endl;
    }
}

Here are the different methods I've tried, patched together from many examples on many sites, yet none of them works correctly.

First attempt:

bool primitive3d::calcTriangleIntersection(ofRay ray, float *result) const {

    ofMesh mesh = prim->getMesh();
    std::vector<ofMeshFace> indices = mesh.getUniqueFaces();

    for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i) {

        ofMeshFace face = *i;

        ofVec3f edge1, edge2, tvec, pvec, qvec;
        float det;
        float u, v;
        const float EPSILON = 0.000001f;

        edge1 = face.getVertex(1) - face.getVertex(0);
        edge2 = face.getVertex(2) - face.getVertex(0);

        pvec = ray.t.getCrossed(edge2);
        det = edge1.dot(pvec);

#if 0 // we don't want to backface cull
        if (det >= EPSILON)
        {
            tvec = getOrigin() - vert0;

            u = tvec.dot(pvec);
            if (!((u < 0.0f) || (u > det)))
            {

                qvec = tvec.getCrossed(edge1);
                v = getDirection().dot(qvec);
                if (!(v < 0.0f || u + v > det))
                {

                    *result = edge2.dot(qvec) / det;
                    return true;
                }
            }
        }
#else
        if (!(det > -EPSILON && det < EPSILON))
        {
            float inv_det = 1.0f / det;
            tvec = ray.s - face.getVertex(0);
            u = tvec.dot(pvec) * inv_det;
            if (!(u < 0.0f || u > 1.0f))
            {

                qvec = tvec.getCrossed(edge1);

                v = ray.t.dot(qvec) * inv_det;
                if (!(v < 0.0f || u + v > 1.0f))
                {

                    *result = edge2.dot(qvec) * inv_det;
                    return true;
                }
            }
        }
#endif
    }
    return false;
}

Second Attempt:

bool primitive3d::checkIntersectionPlaneAndLine(ofRay ray, float *result) const {

    ofMesh mesh = prim->getMesh();
    std::vector<ofMeshFace> indices = mesh.getUniqueFaces();

    for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i)
    {

        ofMeshFace face = *i;

        ofVec3f P1, P2;
        P1 = ray.getStart();
        P2 = ray.getEnd();

        ofVec3f p1, p2, p3;
        p1 = face.getVertex(0);
        p2 = face.getVertex(1);
        p3 = face.getVertex(2);

        ofVec3f v1 = p1 - p2;
        ofVec3f v2 = p3 - p2;

        float a, b, c, d;

        a = v1.y * v2.z - v1.z * v2.y;
        b = -(v1.x * v2.z - v1.z * v2.x);
        c = v1.x * v2.y - v1.y * v2.x;
        d = -(a * p1.x + b * p1.y + c * p1.z);

        ofVec3f O = P1;
        ofVec3f V = P2 - P1;

        float t;

        t = -(a * O.x + b * O.y + c * O.z + d) / (a * V.x + b * V.y + c * V.z);

        ofVec3f p = O + V * t;

        float xmin = std::min(P1.x, P2.x);
        float ymin = std::min(P1.y, P2.y);
        float zmin = std::min(P1.z, P2.z);

        float xmax = std::max(P1.x, P2.x);
        float ymax = std::max(P1.y, P2.y);
        float zmax = std::max(P1.z, P2.z);


        if (inside(p, xmin, xmax, ymin, ymax, zmin, zmax)) {
            *result = p.length();
            return true;
        }
    }
    return false;
}

bool primitive3d::inside(ofVec3f p, float xmin, float xmax, float ymin, float ymax, float zmin, float zmax) const {

    if (p.x >= xmin && p.x <= xmax && p.y >= ymin && p.y <= ymax && p.z >= zmin && p.z <= zmax)
        return true;

    return false;

}

Third attempt:

#define SMALL_NUM   0.00000001 // anything that avoids division overflow
// dot product (3D) which allows vector operations in arguments
#define dot(u,v)   ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)

bool primitive3d::checkIntersectionTriangleRay(ofRay ray, ofPoint* inter)
{
    ofMesh mesh = prim->getMesh();
    std::vector<ofMeshFace> indices = mesh.getUniqueFaces();

    for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i)
    {
        ofMeshFace triangle = *i;

        ofVec3f   u, v, n;              // Vecs of triangle
        ofVec3f   dir, w0, w;           // Vecs of ofRay
        float     r, a, b;              // params to calc ray-plane intersect

                                        // get triangle edge vectors and plane normal
        u = triangle.getVertex(1) - triangle.getVertex(0);
        v = triangle.getVertex(2) - triangle.getVertex(0);
        n = u * v;              // cross product
        if (!(n == ofVec3f(0, 0, 0)))           // if triangle is not degenerate
        {

            dir = ray.getEnd() - ray.getStart();              // ray direction vector
            w0 = ray.getStart() - triangle.getVertex(0);
            a = -dot(n, w0);
            b = dot(n, dir);
            if (!(fabs(b) < SMALL_NUM))
            {     // if ray is not parallel to triangle

                // get intersect point of ray with triangle plane
                r = a / b;
                if (!(r < 0.0))                    // ray goes toward the triangle
                {
                    // for a segment, also test if (r > 1.0) => no intersect

                    *inter = ray.getStart() + r * dir;            // intersect point of ray and plane

                                                    // is I inside T?
                    float    uu, uv, vv, wu, wv, D;
                    uu = dot(u, u);
                    uv = dot(u, v);
                    vv = dot(v, v);
                    w = *inter - triangle.getVertex(0);
                    wu = dot(w, u);
                    wv = dot(w, v);
                    D = uv * uv - uu * vv;

                    // get and test parametric coords
                    float s, t;
                    s = (uv * wv - vv * wu) / D;
                    if (!(s < 0.0 || s > 1.0))         // I is inside T
                    {
                        t = (uv * wu - uu * wv) / D;
                        if (!(t < 0.0 || (s + t) > 1.0))  // I is inside T
                            return true;                       // I is in T
                    }
                }
            }
        }
    }
    return false;
}

I've tried so many things, but none of them works. I'm also drawing my rays to the screen, so I know for a fact that they are indeed created correctly and go in the right direction for an infinite distance

Just to be clear, I removed a lot of the code to make this easily readable. I'm only missing the // Detection Here part in the second method, because I have no idea how to make it work.


Solution

  • Assuming plenty of triangle/mesh raycasting code on the internet you have tried would have solved your problem, I think your problem is in the first method, in which you set a "found" variable, but do not return from the method if found or pick the object with min distance.

    The code you've provided will just return the last primitive's hit test result.

    If you have over-simplified your code, please post again with more detail.

    Edit:

    The coordinates you get from the mesh's faces are in object space. You need to convert them to world space before any calculations, or better, convert the ray to object space.

    See this code for a working implementation: https://github.com/mrdoob/three.js/blob/master/src/objects/Mesh.js

    Notice the applyMatrix4 calls that apply a world matrix to bring them to the same space.