Michael
Michael

Reputation: 595

Circle line segment collision on corners

I found this 2 amazing tutorials how to detect and handle circle vs line segment collision.

seb.ly
tuts plus

And I implemented it in C# without problems. I like this solution because its a clean and simple one to understand.
But there is not explained how to handle collisions on the line ends/corners.
Whats the best way to add this feature?

On the left side is illustrated how my function would behave so far. On the right how I want to behave. The cases 1 and 2 are working great, exactly how I want. But in case 3 the circle does not collide. I have to implement something like on the right side. But I don't know how this could exactly work. enter image description here
So far I have this:

// A line is defined by two points.
// A circle is define by a point and a radius.
public static bool CircleVsLine(Circle circle, Vector2d circleDirection, ref Vector2d circleSolved, Line line)
{
    // Circle position before movement.
    Vector2d circle0 = circle.Position;

    // Circle position after movement.
    Vector2d circle1 = circle.Position + circleDirection;

    Vector2d lineDirection = line.Position1 - line.Position0;
    Vector2d lineNormal = new Vector2d(lineDirection.Y, -lineDirection.X).Normalized();

    Vector2d circle0ToLine0Direction = line.Position0 - circle0;
    Vector2d circle1ToLine0Direction = line.Position0 - circle1;

    // Calculate distance to line before movement.
    double circle0DistanceToLine = Vector2d.Dot(lineNormal, circle0ToLine0Direction);

    // Calculate distance to line after movement.
    double circle1DistanceToLine = Vector2d.Dot(lineNormal, circle1ToLine0Direction);

    // The time when the circle radius equals the distance to the line.
    double t = (circle.Radius - circle0DistanceToLine) / (circle1DistanceToLine - circle0DistanceToLine);

    // If true collision on endless line occured.
    if (t >= 0 && t <= 1)
    {
        // EPSILON is a very small double number to prevent bugs caused by rounding errors.
        circleSolved = circle0 + circleDirection * t - lineNormal * EPSILON;

        Vector2d line0ToPlayerSolved = circleSolved - line.Position0;
        Vector2d line1ToPlayerSolved = circleSolved - line.Position1;

        // If true collision happened on the line sgment.
        if (Vector2d.Dot(lineDirection, line0ToPlayerSolved) >= 0 && Vector2d.Dot(lineDirection, line1ToPlayerSolved) < 0)
        {
            return true;
        }
    }

    // No collision so circle can be moved.
    circleSolved = circle1;
    return false;
}

Upvotes: 2

Views: 581

Answers (1)

Nico Schertler
Nico Schertler

Reputation: 32597

The first part of your code assumes an infinite line. Then, the second part tries to correct the first decision. However, this is not always possible as your examples show. So we need to take the line length into account in the first step.

First, we analyze the line endpoints. We want to find the parameter t where the circle touches the endpoint:

|| circle0 + t * circleDirection - endpoint || == r

The solution is:

discriminant = 4 * (dot(circle0, circleDirection) - dot(circleDirection, endpoint))^2 
              -4 * circleDirection^2 * (circle0^2 - 2 * dot(circle0, endpoint) + endpoint^2 - r^2)

t = ( -dot(circle0, circleDirection) + dot(circleDirection, endpoint) 
      -1/2 * sqrt(discriminant) ) / circleDirection^2

The notation someVector^2 denotes the vector's squared length. If the discriminant is negative, the circle will never touch the end point. Then, either it will pass the line completely or it will hit it somewhere in the middle. Your code can already handle this case, so I skip this. Basically, you would check, which case it is and then continue.

If the discriminant is positive, the circle can touch the endpoint. If t is greater than 1, it will not happen in the current simulation time step. So you can ignore it. But if it is between 0 and 1, you have to act.

First, you have to check if it is the end point that will stop the circle or the line segment. You can check this by projecting the circle center onto the line:

circleCollisionPosition = circle0 + t * circleDirection
directionToCollisionPosition = circleCollisionPosition - line.Position0
s = dot(directionToCollisionPosition, lineDirection) / lineDirection.SquaredLength

Now, if s is between 0 and 1, the circle is stopped by the line segment. Not by one of the endpoints. Then, you can recalculate t from the infinite line (as you did in your code). If s is smaller than 0, the circle will be stopped by the first endpoint and you should use the t from the first endpoint. If s is greater than 1, the circle is stopped by the second endpoint and you should use the according t. If one endpoint yields a s < 0 and one s > 1, use the smaller of both t.

Then continue by calculating circleSolved as you did in your code. This will be the end position of the circle where it will not move any more. The subsequent check is not necessary anymore, because it has already taken place.

Upvotes: 1

Related Questions