Line Segment to Circle Collision/Intersection Detection

Style and Notation

  • All variables are represented in italics except in the diagrams and Python code.
  • Vectors are shown in bold except in the diagrams and Python code.
  • The notation |a| is used to represent the length of a vector named a.

Introduction to the problem

Start by defining a few points (vectors from the world origin):
2

Now, from these points, you can easily calculate two useful values, the segment vector, seg_v (from seg_a to seg_b) and the position of circ_pos relative to seg_a, pt_v.

31

seg_v = seg_bseg_a

pt_v = circ_posseg_a

Closest point to the circle’s center on the segment

The next step is to find the closest point to the circle’s center on the segment (labeled “closest” on the diagram). To do this, we must project pt_v onto seg_v:

4

To project on vector onto another, take the dot product of the vector and the unit vector of the projection target. That is:

|proj\_v| = \textbf{pt\_v} \cdot \frac{\textbf{seg\_v}}{|seg\_v|}

Note that this dot product returns a scalar value (the length of proj_v), not a vector.

We now need to take a small break from the main goal and look into a special case: the ends of the segment. If |proj_v| is less than 0 or greater than |seg_v|, the closest point to circ_pos on the segment will be one of the segment’s endpoints. That is:
if |proj_v| < 0: closest = seg_a
if |proj_v| > |seg_v|: closest = seg_b

Next, calculate the actual proj_v vector, rather than just its length (|proj_v|). To do this, simply multiply by the seg_v unit vector:

\textbf{proj\_v} = |proj\_v| \frac{\textbf{seg\_v}}{|seg\_v|}

The only thing left to do is to convert this into world coordinates (rather than coordinates relative to seg_a) to get the closest point:

closest = seg_a + proj_v

Checking for a intersection

Now that we’ve calculated the closest point on the segment (the variable “closest“), we can check if the circle and segment intersect. To do this, first find the vector from closest to circ_pos:

dist_v = circ_posclosest

If the length of this vector is less than the circle’s radius, the circle and segment are intersecting:
if |dist_v| < circ_rad: They are intersecting
else: They are not intersecting

Collision response

A useful bit of data in collision response is the amount of overlap between two shapes and the direction a shape must be moved in order to resolve the collision. This can be represented as a vector that points in the same direction as dist_v whose length is equal to the difference between circ_rad and |dist_v|.

\textbf{offset} = (circ\_rad - |dist\_v|) \frac{\textbf{dist\_v}}{|dist\_v|}

Python implementation

Here is a Python implementation of everything in this post. For the sake of clarity, I avoided optimizations. vec is assumed to be a fully implemented 2d vector class.

def closest_point_on_seg(seg_a, seg_b, circ_pos):
	seg_v = seg_b - seg_a
	pt_v = circ_pos - seg_a
	if seg_v.len() <= 0:
		raise ValueError, "Invalid segment length"
	seg_v_unit = seg_v / seg_v.len()
	proj = pt_v.dot(seg_v_unit)
	if proj <= 0:
		return seg_a.copy()
	if proj >= seg_v.len():
		return seg_b.copy()
	proj_v = seg_v_unit * proj
	closest = proj_v + seg_a
	return closest
 
def segment_circle(seg_a, seg_b, circ_pos, circ_rad):
	closest = closest_point_on_seg(seg_a, seg_b, circ_pos)
	dist_v = circ_pos - closest
	if dist_v.len() > circ_rad:
		return vec(0, 0)
	if dist_v.len() <= 0:
		raise ValueError, "Circle's center is exactly on segment"
	offset = dist_v / dist_v.len() * (circ_rad - dist_v.len())
	return offset

Similar algorithms

Collisions involving stadiums (a type of rounded rectangle) can be calculated in a similar manner. A stadium is essentially a line segment with a radius.

A stadium-point collision is the same as a segment-circle collision with a circle whose radius is equal to the stadium’s radius.

A stadium-circle collision is the same as a segment-circle collision with a circle whose radius is equal to the sum of the original circle’s radius and the stadium’s radius.

Download

segment_circle.zip – An implementation in Python and Cython.

Conclusion

If you find this post useful or have any questions, please leave a comment.

This entry was posted in Computational Geometry, Mathematics, Physics, Programming. Bookmark the permalink.

6 Responses to Line Segment to Circle Collision/Intersection Detection

  1. Colin says:

    Super good tutorial. Very clear and good pictures. Now I am going to be colliding circles with line segments all day. Thanks!

  2. sandra742 says:

    Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.

  3. Joseph Winston S says:

    Hi! Well explained and i am looking for an implementation for the spatial hyper redundant robot link interference with staggered pipes . I am also implementing through python.
    Thanks for the post. It really helped me to visualize and implement obstacle avoidance.
    S. Joseph Winston

  4. David says:

    @Joseph Winston S
    What sort of robot are you building?

  5. Ben@CHINA says:

    Your procedure is not complete for checking the interaction. For example, both A and B are inside the circle. So, you should add a farthest point in your algorithm. Only when the farthest point of this segment is outside the circle, the intersection does exit.
    Another algorithm:
    http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/

    P.S.
    The font of your site is beautiful, could you tell me what’s the font type?

    Thanks.

  6. David says:

    @Ben@CHINA

    I don’t think I understand. The algorithm works fine if A and B are both inside the circle.

    The font you’re seeing is probably Georgia, but it could also be Times New Roman, Times, or a generic serif font, depending on which fonts you have installed.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">