Search code examples
c#collision-detectiongame-physicsmonogame

Ray vs Rect collision detection not working properly


I've been following this tutorial to make a collision detection algorithm for a game in MonoGame. But for some reason it's not working properly. The first part of the algorithm is a funcion that detects a Ray vs Rect intersection. As a test I set up a Ray from the coordinates (1,1), with the direction following the mouse, and it seems to work fine.

First test, works fine

But as soon as I put the Ray closer to the center, like in (100, 1), it doesn't work at all. The contact point it returns is along the Rectangle, but it's nowhere near the trajectory of the Ray. And if I put the Ray along the Rectangle, it doesn't detect a collision.

Second test, contactPoint is inside the Rect but not in the trajectory

Here's the code for the collision detection:

public static bool RayVsRect(Vector2 rayOrigin, Vector2 rayDirection, Rectangle rect,
            out Vector2 contactPoint, out Vector2 contactNormal, out float tHitNear)
        {
            contactPoint = Vector2.Zero;
            contactNormal = Vector2.Zero;
            tHitNear = 0;

            if (rayDirection.X == 0) rayDirection.X = float.Epsilon;
            if (rayDirection.Y == 0) rayDirection.Y = float.Epsilon;

            Vector2 tNear;
            Vector2 tFar;

            tNear = (rect.Location.ToVector2() - rayOrigin) / rayDirection;
            tFar = (rect.Location.ToVector2() + rect.Size.ToVector2() - rayOrigin) / rayDirection;

            if (tNear.X > tFar.X) Ray2D.Swap(ref tNear.X, ref tFar.X);
            if (tNear.Y > tFar.Y) Ray2D.Swap(ref tNear.Y, ref tFar.Y);

            if (tNear.X > tFar.Y || tNear.Y > tFar.X) return false;

            tHitNear = MathF.Max(tNear.X, tNear.Y);
            float tHitFar = MathF.Min(tFar.X, tFar.Y);

            if (tHitFar < 0 || tHitNear > 1) return false;

            contactPoint = rayOrigin + tHitNear * rayDirection;

            Debug.WriteLine($"Origin: {rayOrigin}, Direction: {rayDirection}, Point: {contactPoint}");

            if (tNear.X > tNear.Y)
            {
                if (rayDirection.X < 0)
                    contactNormal = Vector2.UnitX;
                else
                    contactNormal = -Vector2.UnitX;
            }
            else if (tNear.X < tNear.Y)
            {
                if (rayDirection.Y < 0)
                    contactNormal = Vector2.UnitY;
                else
                    contactNormal = -Vector2.UnitY;
            }

            return true;
        }

There may be some differences between C# and C++, which the tutorial was made in, that make my code not work properly. Like, dividing by 0 seemed to give him infinity but game me NaN, which is why I added the part that changes the rayDirection to Epsilon if it was 0. But I don't see anything else that could be an issue.

If I ignore the issue and follow along with the tutorial, the collision detection works fine some times, from some directions, but completely bugs out in others.

Even though in both tests, where one was in (1, 1) and one in (100, 1), the Rays were going in positive X and Y towards the Rect, one worked and one didn't.


Solution

  • Ok, so I found the problem, it's kind of dumb. I was treating the rayDirection as just the end position of the Ray, when it should've been relative to the rayOrigin. Subtracting rayOrigin from the end position fixes the problem. Adding rayDirection -= rayOrigin; would work, but that's not how the function was supposed to work, instead I did the subtraction when calling the function.

    The collision system still doesn't work, but I guess it's because of another problem. I'll edit this if it's related.