Marcel
Marcel

Reputation: 1109

Detect 2d collision Circle to Rectangle (with angle) and vice versa

Hi i'm trying to detect when two objects connect , either a circle or rectangle. I can't figure it out how to do it. Most examples/tutorials online are really math heavy Or doesn't use the Angle for rectangle

This is what i have so far.

public abstract record Location
{
    public abstract double DistanceTo(Location location);
    public abstract double DirectionTo(Location location);
}

public sealed record CircleLocation(double X, double Y, int Radius) : Location
{
    public override double DistanceTo(Location location)
    {
       return location switch
       {
           CircleLocation circleLocation => Math.Sqrt(Math.Pow(X - circleLocation.X, 2) + Math.Pow(Y - circleLocation.Y, 2)) - circleLocation.Radius,
           RectangleLocation rectangleLocation => Math.Max(rectangleLocation.Left - X, X - rectangleLocation.Right) + Math.Max(rectangleLocation.Top - Y, Y - rectangleLocation.Bottom),
           _ => throw new Exception("Invalid location type")
       };
    }

    public override double DirectionTo(Location location)
    {
        return location switch
        {
            CircleLocation circleLocation => Math.Atan2(circleLocation.Y - Y, circleLocation.X - X),
            RectangleLocation rectangleLocation => Math.Atan2(rectangleLocation.CenterY - Y, rectangleLocation.CenterX - X),
            _ => throw new Exception("Invalid location type")
        };
    }
}

// Angle in radians
public sealed record RectangleLocation(double X, double Y, double Width, double Height, double Angle) : Location
{
    public double Left => X - Math.Cos(Angle) * Width / 2;
    public double Top => Y - Math.Sin(Angle) * Height / 2;
    public double Right => Left + (double)Math.Cos(Angle) * Width;
    public double Bottom => Top + Math.Sin(Angle) * Height;
    public double CenterX => X - Width / 2;
    public double CenterY => Y - Height / 2;

    public override double DistanceTo(Location location)
    {
        return location switch
        {
            CircleLocation circleLocation => Math.Sqrt(Math.Pow(X - circleLocation.X, 2) + Math.Pow(Y - circleLocation.Y, 2)) - circleLocation.Radius,
            RectangleLocation rectangleLocation => Math.Max(rectangleLocation.Left - X, X - rectangleLocation.Right) + Math.Max(rectangleLocation.Top - Y, Y - rectangleLocation.Bottom),
            _ => throw new Exception("Invalid location type")
        };
    }

    public override double DirectionTo(Location location)
    {
        return location switch
        {
            CircleLocation circleLocation => Math.Atan2(circleLocation.Y - Y, circleLocation.X - X),
            RectangleLocation rectangleLocation => Math.Atan2(rectangleLocation.CenterY - CenterY, rectangleLocation.CenterX - CenterX),
            _ => throw new Exception("Invalid location type")
        };
    }
}

Here's the unit tests i'm playing with

public class UnitTest1
{
    [Fact]
    public void CircleToCircleTouches()
    {
        var circle1 = new CircleLocation(0, 0, 1);
        var circle2 = new CircleLocation(2, 0, 1);

        var distance = circle1.DistanceTo(circle2);

        Assert.Equal(0, distance);
    }

    [Fact]
    public void CircleToCircleDoesNotTouch()
    {
        var circle1 = new CircleLocation(0, 0, 1);
        var circle2 = new CircleLocation(4, 0, 1);

        var distance = circle1.DistanceTo(circle2);

        Assert.Equal(2, distance);
    }

    [Fact]
    public void RectangleToRectangleTouches()
    {
        var rectangle1 = new RectangleLocation(0, 0, 2, 2, 0);
        var rectangle2 = new RectangleLocation(2, 2, 2, 2, 0);

        var distance = rectangle1.DistanceTo(rectangle2);

        Assert.Equal(0, distance);
    }

    [Fact]
    public void RectangleToRectangleDoesNotTouch()
    {
        var rectangle1 = new RectangleLocation(0, 0, 2, 2, 0);
        var rectangle2 = new RectangleLocation(3, 3, 2, 2, 0);

        var distance = rectangle1.DistanceTo(rectangle2);

        Assert.Equal(1, distance);
    }

    [Fact]
    public void TestCircleToRectangleDistance()
    {
        var circle = new CircleLocation(0, 0, 1);
        var rectangle = new RectangleLocation(3, 4, 2, 2, 0);

        var distance = circle.DistanceTo(rectangle);

        Assert.Equal(5, distance);
    }

    [Fact]
    public void TestRectangleToCircleDistance()
    {
        var rectangle = new RectangleLocation(0, 0, 2, 2, 0);
        var circle = new CircleLocation(3, 4, 1);

        var distance = rectangle.DistanceTo(circle);

        Assert.Equal(5, distance, 1);
    }
}

Upvotes: 1

Views: 79

Answers (1)

MBo
MBo

Reputation: 80232

Example of Python function. Acts like this but uses length of projections of vector (CircleCenter-RectangleCenter) onto rectangle axes

# cx, cy - circle center
# r - circle radius

# rcx, rcy - rectangle center
# halfw, halfh - half-width and half-height of rectangle

# dirx, diry - unit vector of rectangle "width" side
# it is equal to cos(angle) and sin(angle) for rectangle rotation angle relative to OX axis
# (1,0) for axis-aligned rectangle

def RotRectCircleIntersection(cx, cy, r, rcx, rcy, halfw, halfh, dirx, diry):
    distw = max(0, abs(dirx*(cx-rcx) + diry*(cy-rcy)) - halfw)
    disth = max(0, abs(diry*(cx-rcx) - dirx*(cy-rcy)) - halfh)
    return(distw*distw+distw*distw <= r*r)

print(RotRectCircleIntersection(3,3,3,11,4,3,2,1,0))
print(RotRectCircleIntersection(3,3,3,7.5,4.5,2.92,1.94,0.507839,0.8614519))
print(RotRectCircleIntersection(5,0,2,7.5,4.5,2.92,1.94,0.507839,0.8614519))

Output

False
True
False

enter image description here

Upvotes: 0

Related Questions