Circle line-segment collision detection algorithm?

前端 未结 28 1361
被撕碎了的回忆
被撕碎了的回忆 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 06:54

    Circle is really a bad guy :) So a good way is to avoid true circle, if you can. If you are doing collision check for games you can go with some simplifications and have just 3 dot products, and a few comparisons.

    I call this "fat point" or "thin circle". its kind of a ellipse with zero radius in a direction parallel to a segment. but full radius in a direction perpendicular to a segment

    First, i would consider renaming and switching coordinate system to avoid excessive data:

    s0s1 = B-A;
    s0qp = C-A;
    rSqr = r*r;
    

    Second, index h in hvec2f means than vector must favor horisontal operations, like dot()/det(). Which means its components are to be placed in a separate xmm registers, to avoid shuffling/hadd'ing/hsub'ing. And here we go, with most performant version of simpliest collision detection for 2D game:

    bool fat_point_collides_segment(const hvec2f& s0qp, const hvec2f& s0s1, const float& rSqr) {
        auto a = dot(s0s1, s0s1);
        //if( a != 0 ) // if you haven't zero-length segments omit this, as it would save you 1 _mm_comineq_ss() instruction and 1 memory fetch
        {
            auto b = dot(s0s1, s0qp);
            auto t = b / a; // length of projection of s0qp onto s0s1
            //std::cout << "t = " << t << "\n";
            if ((t >= 0) && (t <= 1)) // 
            {
                auto c = dot(s0qp, s0qp);
                auto r2 = c - a * t * t;
                return (r2 <= rSqr); // true if collides
            }
        }   
        return false;
    }
    

    I doubt you can optimize it any further. I am using it for neural-network driven car racing collision detection, to process millions of millions iteration steps.

    0 讨论(0)
  • 2020-11-22 06:56

    You can find a point on a infinite line that is nearest to circle center by projecting vector AC onto vector AB. Calculate the distance between that point and circle center. If it is greater that R, there is no intersection. If the distance is equal to R, line is a tangent of the circle and the point nearest to circle center is actually the intersection point. If distance less that R, then there are 2 intersection points. They lie at the same distance from the point nearest to circle center. That distance can easily be calculated using Pythagorean theorem. Here's algorithm in pseudocode:

    {
    dX = bX - aX;
    dY = bY - aY;
    if ((dX == 0) && (dY == 0))
      {
      // A and B are the same points, no way to calculate intersection
      return;
      }
    
    dl = (dX * dX + dY * dY);
    t = ((cX - aX) * dX + (cY - aY) * dY) / dl;
    
    // point on a line nearest to circle center
    nearestX = aX + t * dX;
    nearestY = aY + t * dY;
    
    dist = point_dist(nearestX, nearestY, cX, cY);
    
    if (dist == R)
      {
      // line segment touches circle; one intersection point
      iX = nearestX;
      iY = nearestY;
    
      if (t < 0 || t > 1)
        {
        // intersection point is not actually within line segment
        }
      }
    else if (dist < R)
      {
      // two possible intersection points
    
      dt = sqrt(R * R - dist * dist) / sqrt(dl);
    
      // intersection point nearest to A
      t1 = t - dt;
      i1X = aX + t1 * dX;
      i1Y = aY + t1 * dY;
      if (t1 < 0 || t1 > 1)
        {
        // intersection point is not actually within line segment
        }
    
      // intersection point farthest from A
      t2 = t + dt;
      i2X = aX + t2 * dX;
      i2Y = aY + t2 * dY;
      if (t2 < 0 || t2 > 1)
        {
        // intersection point is not actually within line segment
        }
      }
    else
      {
      // no intersection
      }
    }
    

    EDIT: added code to check whether found intersection points actually are within line segment.

    0 讨论(0)
  • 2020-11-22 06:57

    Taking

    1. E is the starting point of the ray,
    2. L is the end point of the ray,
    3. C is the center of sphere you're testing against
    4. r is the radius of that sphere

    Compute:
    d = L - E ( Direction vector of ray, from start to end )
    f = E - C ( Vector from center sphere to ray start )

    Then the intersection is found by..
    Plugging:
    P = E + t * d
    This is a parametric equation:
    Px = Ex + tdx
    Py = Ey + tdy
    into
    (x - h)2 + (y - k)2 = r2
    (h,k) = center of circle.

    Note: We've simplified the problem to 2D here, the solution we get applies also in 3D

    to get:

    1. Expand
      x2 - 2xh + h2 + y2 - 2yk + k2 - r2 = 0
    2. Plug
      x = ex + tdx
      y = ey + tdy
      ( ex + tdx )2 - 2( ex + tdx )h + h2 + ( ey + tdy )2 - 2( ey + tdy )k + k2 - r2 = 0
    3. Explode
      ex2 + 2extdx + t2dx2 - 2exh - 2tdxh + h2 + ey2 + 2eytdy + t2dy2 - 2eyk - 2tdyk + k2 - r2 = 0
    4. Group
      t2( dx2 + dy2 ) + 2t( exdx + eydy - dxh - dyk ) + ex2 + ey2 - 2exh - 2eyk + h2 + k2 - r2 = 0
    5. Finally,
      t2( _d * _d ) + 2t( _e * _d - _d * _c ) + _e * _e - 2( _e*_c ) + _c * _c - r2 = 0
      *Where _d is the vector d and * is the dot product.*
    6. And then,
      t2( _d * _d ) + 2t( _d * ( _e - _c ) ) + ( _e - _c ) * ( _e - _c ) - r2 = 0
    7. Letting _f = _e - _c
      t2( _d * _d ) + 2t( _d * _f ) + _f * _f - r2 = 0

    So we get:
    t2 * (d DOT d) + 2t*( f DOT d ) + ( f DOT f - r2 ) = 0
    So solving the quadratic equation:

    float a = d.Dot( d ) ;
    float b = 2*f.Dot( d ) ;
    float c = f.Dot( f ) - r*r ;
    
    float discriminant = b*b-4*a*c;
    if( discriminant < 0 )
    {
      // no intersection
    }
    else
    {
      // ray didn't totally miss sphere,
      // so there is a solution to
      // the equation.
    
      discriminant = sqrt( discriminant );
    
      // either solution may be on or off the ray so need to test both
      // t1 is always the smaller value, because BOTH discriminant and
      // a are nonnegative.
      float t1 = (-b - discriminant)/(2*a);
      float t2 = (-b + discriminant)/(2*a);
    
      // 3x HIT cases:
      //          -o->             --|-->  |            |  --|->
      // Impale(t1 hit,t2 hit), Poke(t1 hit,t2>1), ExitWound(t1<0, t2 hit), 
    
      // 3x MISS cases:
      //       ->  o                     o ->              | -> |
      // FallShort (t1>1,t2>1), Past (t1<0,t2<0), CompletelyInside(t1<0, t2>1)
    
      if( t1 >= 0 && t1 <= 1 )
      {
        // t1 is the intersection, and it's closer than t2
        // (since t1 uses -b - discriminant)
        // Impale, Poke
        return true ;
      }
    
      // here t1 didn't intersect so we are either started
      // inside the sphere or completely past it
      if( t2 >= 0 && t2 <= 1 )
      {
        // ExitWound
        return true ;
      }
    
      // no intn: FallShort, Past, CompletelyInside
      return false ;
    }
    
    0 讨论(0)
  • 2020-11-22 06:57

    Here is my solution in TypeScript, following the idea that @Mizipzor suggested (using projection):

    /**
     * Determines whether a line segment defined by a start and end point intersects with a sphere defined by a center point and a radius
     * @param a the start point of the line segment
     * @param b the end point of the line segment
     * @param c the center point of the sphere
     * @param r the radius of the sphere
     */
    export function lineSphereIntersects(
      a: IPoint,
      b: IPoint,
      c: IPoint,
      r: number
    ): boolean {
      // find the three sides of the triangle formed by the three points
      const ab: number = distance(a, b);
      const ac: number = distance(a, c);
      const bc: number = distance(b, c);
    
      // check to see if either ends of the line segment are inside of the sphere
      if (ac < r || bc < r) {
        return true;
      }
    
      // find the angle between the line segment and the center of the sphere
      const numerator: number = Math.pow(ac, 2) + Math.pow(ab, 2) - Math.pow(bc, 2);
      const denominator: number = 2 * ac * ab;
      const cab: number = Math.acos(numerator / denominator);
    
      // find the distance from the center of the sphere and the line segment
      const cd: number = Math.sin(cab) * ac;
    
      // if the radius is at least as long as the distance between the center and the line
      if (r >= cd) {
        // find the distance between the line start and the point on the line closest to
        // the center of the sphere
        const ad: number = Math.cos(cab) * ac;
        // intersection occurs when the point on the line closest to the sphere center is
        // no further away than the end of the line
        return ad <= ab;
      }
      return false;
    }
    
    export function distance(a: IPoint, b: IPoint): number {
      return Math.sqrt(
        Math.pow(b.z - a.z, 2) + Math.pow(b.y - a.y, 2) + Math.pow(b.x - a.x, 2)
      );
    }
    
    export interface IPoint {
      x: number;
      y: number;
      z: number;
    }
    
    0 讨论(0)
  • 2020-11-22 06:58

    This solution I found seemed a little easier to follow then some of the other ones.

    Taking:

    p1 and p2 as the points for the line, and
    c as the center point for the circle and r for the radius
    

    I would solve for the equation of the line in slope-intercept form. However, I didn't want to have to deal with difficult equations with c as a point, so I just shifted the coordinate system over so that the circle is at 0,0

    p3 = p1 - c
    p4 = p2 - c
    

    By the way, whenever I subtract points from each other I am subtracting the x's and then subtracting the y's, and putting them into a new point, just in case someone didn't know.

    Anyway, I now solve for the equation of the line with p3 and p4:

    m = (p4_y - p3_y) / (p4_x - p3) (the underscore is an attempt at subscript)
    y = mx + b
    y - mx = b (just put in a point for x and y, and insert the m we found)
    

    Ok. Now I need to set these equations equal. First I need to solve the circle's equation for x

    x^2 + y^2 = r^2
    y^2 = r^2 - x^2
    y = sqrt(r^2 - x^2)
    

    Then I set them equal:

    mx + b = sqrt(r^2 - x^2)
    

    And solve for the quadratic equation (0 = ax^2 + bx + c):

    (mx + b)^2 = r^2 - x^2
    (mx)^2 + 2mbx + b^2 = r^2 - x^2
    0 = m^2 * x^2 + x^2 + 2mbx + b^2 - r^2
    0 = (m^2 + 1) * x^2 + 2mbx + b^2 - r^2
    

    Now I have my a, b, and c.

    a = m^2 + 1
    b = 2mb
    c = b^2 - r^2
    

    So I put this into the quadratic formula:

    (-b ± sqrt(b^2 - 4ac)) / 2a
    

    And substitute in by values then simplify as much as possible:

    (-2mb ± sqrt(b^2 - 4ac)) / 2a
    (-2mb ± sqrt((-2mb)^2 - 4(m^2 + 1)(b^2 - r^2))) / 2(m^2 + 1)
    (-2mb ± sqrt(4m^2 * b^2 - 4(m^2 * b^2 - m^2 * r^2 + b^2 - r^2))) / 2m^2 + 2
    (-2mb ± sqrt(4 * (m^2 * b^2 - (m^2 * b^2 - m^2 * r^2 + b^2 - r^2))))/ 2m^2 + 2
    (-2mb ± sqrt(4 * (m^2 * b^2 - m^2 * b^2 + m^2 * r^2 - b^2 + r^2)))/ 2m^2 + 2
    (-2mb ± sqrt(4 * (m^2 * r^2 - b^2 + r^2)))/ 2m^2 + 2
    (-2mb ± sqrt(4) * sqrt(m^2 * r^2 - b^2 + r^2))/ 2m^2 + 2
    (-2mb ± 2 * sqrt(m^2 * r^2 - b^2 + r^2))/ 2m^2 + 2
    (-2mb ± 2 * sqrt(m^2 * r^2 + r^2 - b^2))/ 2m^2 + 2
    (-2mb ± 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2
    

    This is almost as far as it will simplify. Finally, separate out to equations with the ±:

    (-2mb + 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2 or     
    (-2mb - 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2 
    

    Then simply plug the result of both of those equations into the x in mx + b. For clarity, I wrote some JavaScript code to show how to use this:

    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((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2)); //the value under the square root sign 
    
        if (underRadical < 0){
        //line completely missed
            return false;
        } else {
            var t1 = (-2*m*b+2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //one of the intercept x's
            var t2 = (-2*m*b-2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //other intercept's x
            var i1 = {x:t1,y:m*t1+b} //intercept point 1
            var i2 = {x:t2,y:m*t2+b} //intercept point 2
            return [i1,i2];
        }
    }
    

    I hope this helps!

    P.S. If anyone finds any errors or has any suggestions, please comment. I am very new and welcome all help/suggestions.

    0 讨论(0)
  • 2020-11-22 06:58

    I have created this function for iOS following the answer given by chmike

    + (NSArray *)intersectionPointsOfCircleWithCenter:(CGPoint)center withRadius:(float)radius toLinePoint1:(CGPoint)p1 andLinePoint2:(CGPoint)p2
    {
        NSMutableArray *intersectionPoints = [NSMutableArray array];
    
        float Ax = p1.x;
        float Ay = p1.y;
        float Bx = p2.x;
        float By = p2.y;
        float Cx = center.x;
        float Cy = center.y;
        float R = radius;
    
    
        // compute the euclidean distance between A and B
        float LAB = sqrt( pow(Bx-Ax, 2)+pow(By-Ay, 2) );
    
        // compute the direction vector D from A to B
        float Dx = (Bx-Ax)/LAB;
        float Dy = (By-Ay)/LAB;
    
        // Now the line equation is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= 1.
    
        // compute the value t of the closest point to the circle center (Cx, Cy)
        float t = Dx*(Cx-Ax) + Dy*(Cy-Ay);
    
        // This is the projection of C on the line from A to B.
    
        // compute the coordinates of the point E on line and closest to C
        float Ex = t*Dx+Ax;
        float Ey = t*Dy+Ay;
    
        // compute the euclidean distance from E to C
        float LEC = sqrt( pow(Ex-Cx, 2)+ pow(Ey-Cy, 2) );
    
        // test if the line intersects the circle
        if( LEC < R )
        {
            // compute distance from t to circle intersection point
            float dt = sqrt( pow(R, 2) - pow(LEC,2) );
    
            // compute first intersection point
            float Fx = (t-dt)*Dx + Ax;
            float Fy = (t-dt)*Dy + Ay;
    
            // compute second intersection point
            float Gx = (t+dt)*Dx + Ax;
            float Gy = (t+dt)*Dy + Ay;
    
            [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Fx, Fy)]];
            [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Gx, Gy)]];
        }
    
        // else test if the line is tangent to circle
        else if( LEC == R ) {
            // tangent point to circle is E
            [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Ex, Ey)]];
        }
        else {
            // line doesn't touch circle
        }
    
        return intersectionPoints;
    }
    
    0 讨论(0)
提交回复
热议问题