Search code examples
calgorithm2dintersectionaabb

In 2D, how to find if a triangle and an AABB intersect


I am coding a physic engine for a custom doom engine.

I don't aim to replicate the exact behavior of the original doom.

In doom, every "thing" (player, monsters etc) is an axis aligned bounding box. I want to keep that.

I already have a working tesselation algorithm for the sectors.

I already have a working quadtree, using AABB boxes of triangles of sectors.

The quad tree will return a list of candidate for intersection with a given AABB (the player, for example).

What I want : an algorithm to test if a triangle intersect an AABB in 2D.

What I can do : split the AABB into two triangles, then do triangle-triangle intersection check. What I can do : use a "triangle vs aabb" algorithm working with 3D and set the "z" to 0. What I can do :

1/ check if a point of the triangle is inside the AABB.

2/ check if the center of the AABB is inside the triangle (the center is the better candidate to avoid rounding problems).

3/ check if a line segment of the triangle intersects a segment of the AABB.

I don't want to do that because : I think that given this precise situation, there should be a more optimized way to do it. It is precisely something that the GPU has to face often : find if a triangle is inside the viewport. I think that question, more than any other question, has been solved to hell.

Note that the situation can be even simpler : I can translate everything so the AABB start at (0,O). I can resize everything so the question becomes: "how to determine if a triangle intersect [0,1][0,1]".

I already did some research but:

1/ most results are for 3D stuff.

2/ this is strangely enough a not often covered case. Even in the book "game physics cookbook" this question is not mentionned.

3/ answers I found goes the hard, generic, way, with SAT (separation axis theorem) or equivalent stuff.

I'll have to do this test for a lot of "thing" (player, monster) everyframe for every candidate triangle given by the quadtree.

There is one last option I have, but I really don't know where to start or even if it's a good idea. Here is a quick summary of what I have in my minds.

1/ as the gpu already has all these triangles.

2/ as it's massively parallelizable.

3/ as, passed the fixed cost, there'll be no additionnal cost.

=> ask the gpu.

But I don't know how to do that. The communication cpu/gpu will have a cost, but a fixed cost (it will roughly cost the same thing for 1 or 100.000 things). I prefer to avoid this solution (but as the website ask me to say the ideas I had, I am talking about it).

Please note that this is my first message on this website. Please note that english is not my native language. Please note that, right now right here, it's 3:32 am (in the night). Please note that I will not be able to answer before tomorrow at roughly the same hour (and this is true for every day actually).

Thanks for reading, thanks in advance for answers.


Solution

  • Here is my attempt. In principle you can always test for segment intersections, but if you want to save floating point operations, there are cases where you can take a shortcut. An AABB divides the plane in nine regions (top-left, top, top-right, left, inside, right, bottom-left, bottom and bottom-right). If you just look at the regions in which the points of the triangle fall, you may be able to determine that an intersection must or cannot take place. There are some cases which cannot be decided on this basis, though, and falling back to geometrical intersection is necessary. Here is my code, which I think is correct (as in, all the region-based tests are well defined), although I didn't test thoroughly. It is rather long, but most of it is bitwise operations so it should actually be quite fast. The entry point is the function intersects, and there is an example in the main function.

    #include <math.h>
    #include <stdio.h>
    
    #define EPSILON 1e-6
    
    typedef struct AABB {
        float x0, y0, x1, y1;
    } AABB;
    
    typedef struct Point {
        float x, y, z;
    } Point;
    
    typedef struct Triangle {
        Point p1, p2, p3;
    } Triangle;
    
    // Naming assumes (0, 0) is top-left corner
    typedef enum Region {
        TOP_LEFT     = 1 << 0,
        TOP          = 1 << 1,
        TOP_RIGHT    = 1 << 2,
        LEFT         = 1 << 3,
        INSIDE       = 1 << 4,
        RIGHT        = 1 << 5,
        BOTTOM_LEFT  = 1 << 6,
        BOTTOM       = 1 << 7,
        BOTTOM_RIGHT = 1 << 8
    } Region;
    
    // Find the region in which a point is with respect to the AABB
    Region aabb_region(const AABB* aabb, const Point* point) {
        if (point->x < aabb->x0) {
            if (point->y < aabb->y0) {
                return TOP_LEFT;
            } else if (point->y > aabb->y1) {
                return BOTTOM_LEFT;
            } else {
                return LEFT;
            }
        } else if (point->x > aabb->x1) {
            if (point->y < aabb->y0) {
                return TOP_RIGHT;
            } else if (point->y > aabb->y1) {
                return BOTTOM_RIGHT;
            } else {
                return RIGHT;
            }
        } else {
            if (point->y < aabb->y0) {
                return TOP;
            } else if (point->y > aabb->y1) {
                return BOTTOM;
            } else {
                return INSIDE;
            }
        }
    }
    
    // 1: There is intersection
    // 0: There may or may not be intersection
    int regions_intersect_2(Region r1, Region r2) {
        if ((((r1 | r2) & INSIDE) != 0) ||
            ((r1 | r2) == (LEFT | RIGHT)) ||
            ((r1 | r2) == (TOP | BOTTOM))) {
            return 1;
        } else {
            return 0;
        }
    }
    
    // 1: There is intersection
    // 0: There may or may not be intersection
    // -1: There is no intersection
    // Does not check cases already covered by regions_intersect_2
    int regions_intersect_3(Region r1, Region r2, Region r3) {
        Region r23 = r2 | r3;
        switch (r1) {
        case TOP_LEFT:
            if (r23 == (BOTTOM | RIGHT) ||
                r23 == (BOTTOM | TOP_RIGHT) ||
                r23 == (RIGHT | BOTTOM_LEFT)) {
                return 1;
            } else if ((r23 & (TOP_LEFT | LEFT | BOTTOM_LEFT)) == r23 ||
                       (r23 & (TOP_LEFT | TOP | TOP_RIGHT)) == r23) {
                return -1;
            }
        case TOP:
            if (r23 == (LEFT | BOTTOM_RIGHT) ||
                r23 == (RIGHT | BOTTOM_LEFT)) {
                return 1;
            } else if ((r23 & (TOP_LEFT | TOP | TOP_RIGHT)) == r23) {
                return -1;
            }
        case TOP_RIGHT:
            if (r23 == (BOTTOM | LEFT) ||
                r23 == (BOTTOM | TOP_LEFT) ||
                r23 == (LEFT | BOTTOM_RIGHT)) {
                return 1;
            } else if ((r23 & (TOP_RIGHT | RIGHT | BOTTOM_RIGHT)) == r23 ||
                       (r23 & (TOP_RIGHT | TOP | TOP_LEFT)) == r23) {
                return -1;
            }
        case LEFT:
            if (r23 == (TOP | BOTTOM_RIGHT) ||
                r23 == (BOTTOM | TOP_RIGHT)) {
                return 1;
            } else if ((r23 & (TOP_LEFT | LEFT | BOTTOM_LEFT)) == r23) {
                return -1;
            }
        case RIGHT:
            if (r23 == (TOP | BOTTOM_LEFT) ||
                r23 == (BOTTOM | TOP_LEFT)) {
                return 1;
            } else if ((r23 & (TOP_RIGHT | RIGHT | BOTTOM_RIGHT)) == r23) {
                return -1;
            }
        case BOTTOM_LEFT:
            if (r23 == (TOP | RIGHT) ||
                r23 == (TOP | BOTTOM_RIGHT) ||
                r23 == (RIGHT | TOP_LEFT)) {
                return 1;
            } else if ((r23 & (BOTTOM_LEFT | LEFT | TOP_LEFT)) == r23 ||
                       (r23 & (BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT)) == r23) {
                return -1;
            }
        case BOTTOM:
            if (r23 == (LEFT | TOP_RIGHT) ||
                r23 == (RIGHT | TOP_LEFT)) {
                return 1;
            } else if ((r23 & (BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT)) == r23) {
                return -1;
            }
        case BOTTOM_RIGHT:
            if (r23 == (TOP | LEFT) ||
                r23 == (TOP | BOTTOM_LEFT) ||
                r23 == (LEFT | TOP_RIGHT)) {
                return 1;
            } else if ((r23 & (BOTTOM_RIGHT | RIGHT | TOP_RIGHT)) == r23 ||
                       (r23 & (BOTTOM_RIGHT | BOTTOM | BOTTOM_LEFT)) == r23) {
                return -1;
            }
        default:
            return 0;
        }
        return 0;
    }
    
    // Check if a segment intersects with the AABB
    // Does not check cases already covered by regions_intersect_2 or regions_intersect_3
    int segment_intersects(const AABB* aabb, const Point* p1, const Point* p2, Region r1, Region r2) {
        // Skip if intersection is impossible
        Region r12 = r1 | r2;
        if ((r12 & (TOP_LEFT | TOP | TOP_RIGHT)) == r12 ||
            (r12 & (BOTTOM_LEFT | BOTTOM | BOTTOM_RIGHT)) == r12 ||
            (r12 & (TOP_LEFT | LEFT | BOTTOM_LEFT)) == r12 ||
            (r12 & (TOP_RIGHT | RIGHT | BOTTOM_RIGHT)) == r12) {
            return 0;
        }
        float dx = p2->x - p1->x;
        float dy = p2->y - p1->y;
        if (fabsf(dx) < EPSILON || fabs(dy) < EPSILON) {
            // Vertical or horizontal line (or zero-sized vector)
            // If there were intersection we would have already picked it up
            return 0;
        }
        float t = (aabb->x0 - p1->x) / dx;
        if (t >= 0.f && t <= 1.f) {
            return 1;
        }
        t = (aabb->x1 - p1->x) / dx;
        if (t >= 0.f && t <= 1.f) {
            return 1;
        }
        t = (aabb->y0 - p1->y) / dy;
        if (t >= 0.f && t <= 1.f) {
            return 1;
        }
        t = (aabb->y1 - p1->y) / dy;
        if (t >= 0.f && t <= 1.f) {
            return 1;
        }
        return 0;
    }
    
    int intersects(const AABB* aabb, const Triangle* triangle) {
        // Find plane regions for each point
        Region r1 = aabb_region(aabb, &triangle->p1);
        Region r2 = aabb_region(aabb, &triangle->p2);
        Region r3 = aabb_region(aabb, &triangle->p3);
        // Check if any pair of regions implies intersection
        if (regions_intersect_2(r1, r2) ||
            regions_intersect_2(r1, r3) ||
            regions_intersect_2(r2, r3)) {
            return 1;
        }
        // Check if the three regions imply or forbid intersection
        switch (regions_intersect_3(r1, r2, r3)) {
        case 1:
            return 1;
        case -1:
            return 0;
        }
        // Check segment intersections
        if (segment_intersects(aabb, &triangle->p1, &triangle->p2, r1, r2)) {
            return 1;
        } else if (segment_intersects(aabb, &triangle->p1, &triangle->p3, r1, r3)) {
            return 1;
        } else if (segment_intersects(aabb, &triangle->p2, &triangle->p3, r2, r3)) {
            return 1;
        }
        return 0;
    }
    
    int main(int argc, char* argv[]) {
        AABB aabb = {
            /* x0 = */ 2.f,
            /* y0 = */ 1.f,
            /* x1 = */ 5.f,
            /* y1 = */ 6.f };
        Triangle triangle = {
            {1.f, 0.f}, {2.f, 2.f}, {2.f, -3.f}
        };
        int inter = intersects(&aabb, &triangle);
        printf("Intersects: %s.\n", inter ? "yes" : "no");
        return 0;
    }
    

    Output:

    Intersects: yes.
    

    See it in Rextester