Circle line-segment collision detection algorithm?

前端 未结 28 1364
被撕碎了的回忆
被撕碎了的回忆 2020-11-22 06:38

I have a line from A to B and a circle positioned at C with the radius R.

\"Image\"

What is a good alg

相关标签:
28条回答
  • 2020-11-22 07:15

    Solution in python, based on @Joe Skeen

    def check_line_segment_circle_intersection(line, point, radious):
        """ Checks whether a point intersects with a line defined by two points.
    
        A `point` is list with two values: [2, 3]
    
        A `line` is list with two points: [point1, point2]
    
        """
        line_distance = distance(line[0], line[1])
        distance_start_to_point = distance(line[0], point)
        distance_end_to_point = distance(line[1], point)
    
        if (distance_start_to_point <= radious or distance_end_to_point <= radious):
            return True
    
        # angle between line and point with law of cosines
        numerator = (math.pow(distance_start_to_point, 2)
                     + math.pow(line_distance, 2)
                     - math.pow(distance_end_to_point, 2))
        denominator = 2 * distance_start_to_point * line_distance
        ratio = numerator / denominator
        ratio = ratio if ratio <= 1 else 1  # To account for float errors
        ratio = ratio if ratio >= -1 else -1  # To account for float errors
        angle = math.acos(ratio)
    
        # distance from the point to the line with sin projection
        distance_line_to_point = math.sin(angle) * distance_start_to_point
    
        if distance_line_to_point <= radious:
            point_projection_in_line = math.cos(angle) * distance_start_to_point
            # Intersection occurs whent the point projection in the line is less
            # than the line distance and positive
            return point_projection_in_line <= line_distance and point_projection_in_line >= 0
        return False
    
    def distance(point1, point2):
        return math.sqrt(
            math.pow(point1[1] - point2[1], 2) +
            math.pow(point1[0] - point2[0], 2)
        )
    
    0 讨论(0)
  • 2020-11-22 07:17

    Here's an implementation in Javascript. My approach is to first convert the line segment into an infinite line then find the intersection point(s). From there I check if the point(s) found are on the line segment. The code is well documented, you should be able to follow along.

    You can try out the code here on this live demo. The code was taken from my algorithms repo.

    // Small epsilon value
    var EPS = 0.0000001;
    
    // point (x, y)
    function Point(x, y) {
      this.x = x;
      this.y = y;
    }
    
    // Circle with center at (x,y) and radius r
    function Circle(x, y, r) {
      this.x = x;
      this.y = y;
      this.r = r;
    }
    
    // A line segment (x1, y1), (x2, y2)
    function LineSegment(x1, y1, x2, y2) {
      var d = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
      if (d < EPS) throw 'A point is not a line segment';
      this.x1 = x1; this.y1 = y1;
      this.x2 = x2; this.y2 = y2;
    }
    
    // An infinite line defined as: ax + by = c
    function Line(a, b, c) {
      this.a = a; this.b = b; this.c = c;
      // Normalize line for good measure
      if (Math.abs(b) < EPS) {
        c /= a; a = 1; b = 0;
      } else { 
        a = (Math.abs(a) < EPS) ? 0 : a / b;
        c /= b; b = 1; 
      }
    }
    
    // Given a line in standard form: ax + by = c and a circle with 
    // a center at (x,y) with radius r this method finds the intersection
    // of the line and the circle (if any). 
    function circleLineIntersection(circle, line) {
    
      var a = line.a, b = line.b, c = line.c;
      var x = circle.x, y = circle.y, r = circle.r;
    
      // Solve for the variable x with the formulas: ax + by = c (equation of line)
      // and (x-X)^2 + (y-Y)^2 = r^2 (equation of circle where X,Y are known) and expand to obtain quadratic:
      // (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
      // Then use quadratic formula X = (-b +- sqrt(a^2 - 4ac))/2a to find the 
      // roots of the equation (if they exist) and this will tell us the intersection points
    
      // In general a quadratic is written as: Ax^2 + Bx + C = 0
      // (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
      var A = a*a + b*b;
      var B = 2*a*b*y - 2*a*c - 2*b*b*x;
      var C = b*b*x*x + b*b*y*y - 2*b*c*y + c*c - b*b*r*r;
    
      // Use quadratic formula x = (-b +- sqrt(a^2 - 4ac))/2a to find the 
      // roots of the equation (if they exist).
    
      var D = B*B - 4*A*C;
      var x1,y1,x2,y2;
    
      // Handle vertical line case with b = 0
      if (Math.abs(b) < EPS) {
    
        // Line equation is ax + by = c, but b = 0, so x = c/a
        x1 = c/a;
    
        // No intersection
        if (Math.abs(x-x1) > r) return [];
    
        // Vertical line is tangent to circle
        if (Math.abs((x1-r)-x) < EPS || Math.abs((x1+r)-x) < EPS)
          return [new Point(x1, y)];
    
        var dx = Math.abs(x1 - x);
        var dy = Math.sqrt(r*r-dx*dx);
    
        // Vertical line cuts through circle
        return [
          new Point(x1,y+dy),
          new Point(x1,y-dy)
        ];
    
      // Line is tangent to circle
      } else if (Math.abs(D) < EPS) {
    
        x1 = -B/(2*A);
        y1 = (c - a*x1)/b;
    
        return [new Point(x1,y1)];
    
      // No intersection
      } else if (D < 0) {
    
        return [];
    
      } else {
    
        D = Math.sqrt(D);
    
        x1 = (-B+D)/(2*A);
        y1 = (c - a*x1)/b;
    
        x2 = (-B-D)/(2*A);
        y2 = (c - a*x2)/b;
    
        return [
          new Point(x1, y1),
          new Point(x2, y2)
        ];
    
      }
    
    }
    
    // Converts a line segment to a line in general form
    function segmentToGeneralForm(x1,y1,x2,y2) {
      var a = y1 - y2;
      var b = x2 - x1;
      var c = x2*y1 - x1*y2;
      return new Line(a,b,c);
    }
    
    // Checks if a point 'pt' is inside the rect defined by (x1,y1), (x2,y2)
    function pointInRectangle(pt,x1,y1,x2,y2) {
      var x = Math.min(x1,x2), X = Math.max(x1,x2);
      var y = Math.min(y1,y2), Y = Math.max(y1,y2);
      return x - EPS <= pt.x && pt.x <= X + EPS &&
             y - EPS <= pt.y && pt.y <= Y + EPS;
    }
    
    // Finds the intersection(s) of a line segment and a circle
    function lineSegmentCircleIntersection(segment, circle) {
    
      var x1 = segment.x1, y1 = segment.y1, x2 = segment.x2, y2 = segment.y2;
      var line = segmentToGeneralForm(x1,y1,x2,y2);
      var pts = circleLineIntersection(circle, line);
    
      // No intersection
      if (pts.length === 0) return [];
    
      var pt1 = pts[0];
      var includePt1 = pointInRectangle(pt1,x1,y1,x2,y2);
    
      // Check for unique intersection
      if (pts.length === 1) {
        if (includePt1) return [pt1];
        return [];
      }
    
      var pt2 = pts[1];
      var includePt2 = pointInRectangle(pt2,x1,y1,x2,y2);
    
      // Check for remaining intersections
      if (includePt1 && includePt2) return [pt1, pt2];
      if (includePt1) return [pt1];
      if (includePt2) return [pt2];
      return [];
    
    }
    
    0 讨论(0)
  • 2020-11-22 07:18

    Weirdly I can answer but not comment... I liked Multitaskpro's approach of shifting everything to make the centre of the circle fall on the origin. Unfortunately there are two problems in his code. First in the under-the-square-root part you need to remove the double power. So not:

    var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2));

    but:

    var underRadical = Math.pow(r,2)*(Math.pow(m,2)+1)) - Math.pow(b,2);

    In the final coordinates he forgets to shift the solution back. So not:

    var i1 = {x:t1,y:m*t1+b}

    but:

    var i1 = {x:t1+c.x, y:m*t1+b+c.y};

    The whole function then becomes:

    function interceptOnCircle(p1, p2, c, r) {
        //p1 is the first line point
        //p2 is the second line point
        //c is the circle's center
        //r is the circle's radius
    
        var p3 = {x:p1.x - c.x, y:p1.y - c.y}; //shifted line points
        var p4 = {x:p2.x - c.x, y:p2.y - c.y};
    
        var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
        var b = p3.y - m * p3.x; //y-intercept of line
    
        var underRadical = Math.pow(r,2)*Math.pow(m,2) + Math.pow(r,2) - Math.pow(b,2); //the value under the square root sign 
    
        if (underRadical < 0) {
            //line completely missed
            return false;
        } else {
            var t1 = (-m*b + Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //one of the intercept x's
            var t2 = (-m*b - Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //other intercept's x
            var i1 = {x:t1+c.x, y:m*t1+b+c.y}; //intercept point 1
            var i2 = {x:t2+c.x, y:m*t2+b+c.y}; //intercept point 2
            return [i1, i2];
        }
    }
    
    0 讨论(0)
  • 2020-11-22 07:20

    Another method uses the triangle ABC area formula. The intersection test is simpler and more efficient than the projection method, but finding the coordinates of the intersection point requires more work. At least it will be delayed to the point it is required.

    The formula to compute the triangle area is : area = bh/2

    where b is the base length and h is the height. We chose the segment AB to be the base so that h is the shortest distance from C, the circle center, to the line.

    Since the triangle area can also be computed by a vector dot product we can determine h.

    // compute the triangle area times 2 (area = area2/2)
    area2 = abs( (Bx-Ax)*(Cy-Ay) - (Cx-Ax)(By-Ay) )
    
    // compute the AB segment length
    LAB = sqrt( (Bx-Ax)² + (By-Ay)² )
    
    // compute the triangle height
    h = area2/LAB
    
    // if the line intersects the circle
    if( h < R )
    {
        ...
    }        
    

    UPDATE 1 :

    You could optimize the code by using the fast inverse square root computation described here to get a good approximation of 1/LAB.

    Computing the intersection point is not that difficult. Here it goes

    // compute the line AB direction vector components
    Dx = (Bx-Ax)/LAB
    Dy = (By-Ay)/LAB
    
    // compute the distance from A toward B of closest point to C
    t = Dx*(Cx-Ax) + Dy*(Cy-Ay)
    
    // t should be equal to sqrt( (Cx-Ax)² + (Cy-Ay)² - h² )
    
    // compute the intersection point distance from t
    dt = sqrt( R² - h² )
    
    // compute first intersection point coordinate
    Ex = Ax + (t-dt)*Dx
    Ey = Ay + (t-dt)*Dy
    
    // compute second intersection point coordinate
    Fx = Ax + (t+dt)*Dx
    Fy = Ay + (t+dt)*Dy
    

    If h = R then the line AB is tangent to the circle and the value dt = 0 and E = F. The point coordinates are those of E and F.

    You should check that A is different of B and the segment length is not null if this may happen in your application.

    0 讨论(0)
提交回复
热议问题