圆线段碰撞检测算法?

时间:2009-07-02 09:15:10

标签: algorithm math line collision-detection geometry

我有一条从A到B的线和一条位于C的圆,半径为R.

Image

用于检查线是否与圆相交的好算法是什么?它沿圆圈边缘的坐标发生了什么?

28 个答案:

答案 0 :(得分:188)

采取

  1. E 是光线的起点,
  2. L 是光线的终点,
  3. C 是您正在测试的球体的中心
  4. r 是该球体的半径
  5. 计算:
    d = L - E(光线的方向矢量,从开始到结束)
    f = E - C(从中心球到射线开始的矢量)

    然后通过...发现交叉点 堵:
    P = E + t * d
    这是一个参数方程:
    P x = E x + td x
    P y = E y + td y

    (x - h) 2 +(y - k) 2 = r 2
    (h,k)=圆心。

      

    注意:我们在这里将问题简化为2D,我们得到的解决方案也适用于3D

    得到:

    1. <强>展开
      x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 < / sup> = 0
    2. <强>插头
      x = e x + td x
      y = e y + td y
      (e x + td x 2 - 2(e x + td x )h + h 2 + (e y + td y 2 - 2(e y + td y )k + k 2 - r 2 = 0
    3. 爆炸
      e x 2 + 2e x td x + t 2 d x 2 - 2e x h - 2td x h + h 2 + e y 2 + 2e y td y + t 2 d y 2 - 2e y k - 2td y k + k 2 - r 2 = 0

    4. t 2 (d x 2 + d y 2 )+ 2t(e x d x + e y d y - d x h - d y k)+ e x 2 + e y 2 - 2e x h - 2e y k + h 2 + k 2 - r 2 = 0
    5. 最后,
      t 2 (_ d * _d)+ 2t(_e * _d - _d * _c)+ _e * _e - 2(_e * _c)+ _c * _c - r 2 = 0
      *其中_d是向量d,*是点积。*
    6. 然后,
      t 2 (_ d * _d)+ 2t(_d *(_ e - _c))+(_ e - _c)*(_ e - _c) - r 2 = 0 < / LI>
    7. 让_f = _e - _c
      t 2 (_ d * _d)+ 2t(_d * _f)+ _f * _f - r 2 = 0
    8. 所以我们得到:
      t 2 *(d DOT d)+ 2t *(f DOT d)+(f DOT f - r 2 )= 0
      所以求解二次方程:

      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 ;
      }
      

答案 1 :(得分:128)

似乎没有人考虑投影,我完全偏离这里吗?

将向量AC投影到AB。投影向量AD给出新点D 如果DC之间的距离小于(或等于)R,我们就会有一个交集点。

像这样:
Image by SchoolBoy

答案 2 :(得分:47)

我会用算法计算点(圆心)和直线(AB线)之间的距离。然后可以使用它来确定直线与圆的交点。

假设我们有点A,B,C。Ax和Ay是A点的x和y分量。 B和C相同。标量R是圆半径。

该算法要求A,B和C是不同的点,R不是0.

这是算法

// compute the euclidean distance between A and B
LAB = sqrt( (Bx-Ax)²+(By-Ay)² )

// compute the direction vector D from A to B
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB

// the equation of the line AB is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= LAB.

// compute the distance between the points A and E, where
// E is the point of AB closest the circle center (Cx, Cy)
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)    

// compute the coordinates of the point E
Ex = t*Dx+Ax
Ey = t*Dy+Ay

// compute the euclidean distance between E and C
LEC = sqrt((Ex-Cx)²+(Ey-Cy)²)

// test if the line intersects the circle
if( LEC < R )
{
    // compute distance from t to circle intersection point
    dt = sqrt( R² - LEC²)

    // compute first intersection point
    Fx = (t-dt)*Dx + Ax
    Fy = (t-dt)*Dy + Ay

    // compute second intersection point
    Gx = (t+dt)*Dx + Ax
    Gy = (t+dt)*Dy + Ay
}

// else test if the line is tangent to circle
else if( LEC == R )
    // tangent point to circle is E

else
    // line doesn't touch circle

答案 3 :(得分:18)

好的,我不会给你代码,但是因为你已经标记了这个,我认为这对你不重要。 首先,你必须得到一个垂直于线的矢量。

y = ax + c 中会有一个未知变量( c未知
要解决这个问题,请在线穿过圆心时计算它的值。

即,
将圆心的位置插入线方程并求解c 然后计算原始线与其法线的交点。

这将为您提供圆圈线上的最近点 计算此点与圆心之间的距离(使用矢量的大小) 如果这小于圆的半径 - 瞧,我们有一个交叉点!

答案 4 :(得分:9)

另一种方法使用三角形ABC区域公式。相交测试比投影方法更简单,更有效,但找到交点的坐标需要更多的工作。至少它会延迟到需要的程度。

计算三角形区域的公式为:area = bh / 2

其中b是基本长度,h是高度。我们选择AB段作为基础,因此h是从C,圆心到线的最短距离。

由于三角形区域也可以通过矢量点积计算,我们可以确定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 )
{
    ...
}        

更新1:

您可以使用here描述的快速平方根计算来优化代码,以获得1 / LAB的良好近似值。

计算交叉点并不困难。在这里

// 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

如果h = R,那么AB线与圆相切,值dt = 0,E = F.点坐标是E和F的坐标。

如果您的应用程序中可能发生这种情况,则应检查A是否与B不同,并且段长度不为空。

答案 5 :(得分:7)

我写了一个小脚本,通过将圆圈的中心点投射到线上来测试交叉点。

vector distVector = centerPoint - projectedPoint;
if(distVector.length() < circle.radius)
{
    double distance = circle.radius - distVector.length();
    vector moveVector = distVector.normalize() * distance;
    circle.move(moveVector);
}

http://jsfiddle.net/ercang/ornh3594/1/

如果您需要检查与细分的碰撞,还需要考虑圆心开始和结束点的距离。

vector distVector = centerPoint - startPoint;
if(distVector.length() < circle.radius)
{
    double distance = circle.radius - distVector.length();
    vector moveVector = distVector.normalize() * distance;
    circle.move(moveVector);
}

https://jsfiddle.net/ercang/menp0991/

答案 6 :(得分:5)

我发现这个解决方案似乎比其他一些更容易理解。

服用:

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

我会用斜率截距形式求解线的方程。但是,我不想处理以c为点的困难方程式,所以我只是将坐标系移动到圆圈位于0,0

p3 = p1 - c
p4 = p2 - c

顺便说一下,每当我从彼此中减去点数时,我减去x然后减去y,然后将它们放到一个新点,以防万一有人没有不知道。

无论如何,我现在用p3p4来解决这一行的等式:

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)

确定。现在我需要将这些方程设置为相等。首先,我需要解决x

的圆的等式
x^2 + y^2 = r^2
y^2 = r^2 - x^2
y = sqrt(r^2 - x^2)

然后我将它们设置为相等:

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

求解二次方程(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

现在我有abc

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

所以我把它放到二次公式中:

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

然后用值替换然后尽可能地简化:

(-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

这几乎可以简化。最后,用±:

分离出方程式
(-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 

然后只需将这两个方程的结果插入x中的mx + b即可。为清楚起见,我写了一些JavaScript代码来说明如何使用它:

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];
    }
}

我希望这有帮助!

P.S。如果有人发现任何错误或有任何建议,请发表评论。我很新,欢迎所有的帮助/建议。

答案 7 :(得分:4)

很奇怪我可以回答但不是评论...... 我喜欢Multitaskpro的方法来改变一切,使圆圈的中心落在原点上。不幸的是,他的代码存在两个问题。首先在平方根部分,您需要移除双倍功率。所以不是:

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

但:

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

在最终坐标中,他忘记将解决方案移回。所以不是:

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

但:

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

然后整个功能变为:

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];
    }
}

答案 8 :(得分:4)

通过将矢量AC投影到矢量AB上,可以在无限直线上找到最接近圆心的点。计算该点与圆心之间的距离。如果R大于R,则没有交叉点。如果距离等于R,则线是圆的切线,最接近圆心的点实际上是交点。如果距离小于R,则有2个交叉点。它们与距离圆心最近的点位于相同的距离。使用毕达哥拉斯定理可以很容易地计算出这个距离。这是伪代码的算法:

{
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
  }
}

编辑:添加代码以检查找到的交叉点是否实际位于线段内。

答案 9 :(得分:3)

你需要一些数学:

假设A =(Xa,Ya),B =(Xb,Yb)和C =(Xc,Yc)。从A到B的线上的任何点都有坐标(alpha * Xa +(1-alpha) Xb,alpha Ya +(1-alpha)* Yb)= P

如果点P的距离为R到C,则它必须在圆上。你想要的是解决问题

distance(P, C) = R

(alpha*Xa + (1-alpha)*Xb)^2 + (alpha*Ya + (1-alpha)*Yb)^2 = R^2
alpha^2*Xa^2 + alpha^2*Xb^2 - 2*alpha*Xb^2 + Xb^2 + alpha^2*Ya^2 + alpha^2*Yb^2 - 2*alpha*Yb^2 + Yb^2=R^2
(Xa^2 + Xb^2 + Ya^2 + Yb^2)*alpha^2 - 2*(Xb^2 + Yb^2)*alpha + (Xb^2 + Yb^2 - R^2) = 0

如果你将ABC公式应用于此公式以解决alpha问题,并使用alpha解决方案计算P的坐标,则得到交点(如果存在)。

答案 10 :(得分:3)

如果你发现球体中心之间的距离(因为它是3D我假设你的意思是球体而不是圆形)和线条,那么检查那个距离是否小于将要做到这一点的半径。

碰撞点显然是直线和球体之间的最近点(当你计算球体和直线之间的距离时会计算出来)

点与线之间的距离:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html

答案 11 :(得分:3)

这是Javascript中的一个实现。我的方法是首先将线段转换为无限线,然后找到交叉点。从那里我检查找到的点是否在线段上。代码有详细记录,您应该能够遵循。

您可以在此live demo上试用此处的代码。 代码来自我的algorithms repo

enter image description here

// 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 [];

}

答案 12 :(得分:3)

在此后圆线中,将通过检查圆心和线段(Ipoint)之间的距离来检查线圈,该线段表示从圆心到线段的法线N(图像2)之间的交点。

https://i.stack.imgur.com/3o6do.pngImage 1. Finding vectors E and D

在图像1上显示一个圆和一条线,矢量A点到线起点,矢量B指向线端点,矢量C指向圆心。现在我们必须找到矢量E(从行起点到圆心)和矢量D(从行起点到行终点),这个计算显示在图像1上。

https://i.stack.imgur.com/7098a.pngImage 2. Finding vector X

在图像2中,我们可以看到矢量E通过矢量E和单位矢量D的“点积”投射在矢量D上,点积的结果是标量Xp,表示线起点和交点之间的距离( Ipoint)向量N和向量D. 通过乘以单位矢量D和标量Xp找到下一个矢量X.

现在我们需要找到向量Z(向量到Ipoint),它很容易添加向量A(起始点在线)和向量X的简单向量。接下来我们需要处理特殊情况我们必须检查是否在线Ipoint如果它不是我们必须找到它是它的左侧还是右侧,我们将使用最接近的向量来确定哪个点最接近圆。

https://i.stack.imgur.com/p9WIr.pngImage 3. Finding closest point

当投影Xp为负时,Ipoint为线段左侧,矢量最接近等于线起点的矢量,当投影Xp大于矢量D的幅度时,则Ipoint为线段右侧,则最近矢量等于矢量在任何其他情况下,最终向量的行结束点等于向量Z.

现在当我们有最接近的矢量时,我们需要找到从圆心到Ipoint(dist矢量)的矢量,其简单我们只需要从中心矢量中减去最接近的矢量。接下来只检查矢量dist幅度是否小于圆半径,如果它是碰撞,如果它没有碰撞那么。

https://i.stack.imgur.com/QJ63q.pngImage 4. Checking for collision

为了结束,我们可以返回一些用于解决碰撞的值,最简单的方法是返回碰撞的重叠(从矢量dist幅度减去半径)和返回碰撞轴,它的矢量D.如果需要,交点也是矢量Z.

答案 13 :(得分:2)

只是这个帖子的补充...... 下面是pahlevan发布的代码版本,但对于C#/ XNA并整理了一点:

    /// <summary>
    /// Intersects a line and a circle.
    /// </summary>
    /// <param name="location">the location of the circle</param>
    /// <param name="radius">the radius of the circle</param>
    /// <param name="lineFrom">the starting point of the line</param>
    /// <param name="lineTo">the ending point of the line</param>
    /// <returns>true if the line and circle intersect each other</returns>
    public static bool IntersectLineCircle(Vector2 location, float radius, Vector2 lineFrom, Vector2 lineTo)
    {
        float ab2, acab, h2;
        Vector2 ac = location - lineFrom;
        Vector2 ab = lineTo - lineFrom;
        Vector2.Dot(ref ab, ref ab, out ab2);
        Vector2.Dot(ref ac, ref ab, out acab);
        float t = acab / ab2;

        if (t < 0)
            t = 0;
        else if (t > 1)
            t = 1;

        Vector2 h = ((ab * t) + lineFrom) - location;
        Vector2.Dot(ref h, ref h, out h2);

        return (h2 <= (radius * radius));
    }

答案 14 :(得分:2)

enter image description here

' VB.NET - Code

Function CheckLineSegmentCircleIntersection(x1 As Double, y1 As Double, x2 As Double, y2 As Double, xc As Double, yc As Double, r As Double) As Boolean
    Static xd As Double = 0.0F
    Static yd As Double = 0.0F
    Static t As Double = 0.0F
    Static d As Double = 0.0F
    Static dx_2_1 As Double = 0.0F
    Static dy_2_1 As Double = 0.0F

    dx_2_1 = x2 - x1
    dy_2_1 = y2 - y1

    t = ((yc - y1) * dy_2_1 + (xc - x1) * dx_2_1) / (dy_2_1 * dy_2_1 + dx_2_1 * dx_2_1)

    If 0 <= t And t <= 1 Then
        xd = x1 + t * dx_2_1
        yd = y1 + t * dy_2_1

        d = Math.Sqrt((xd - xc) * (xd - xc) + (yd - yc) * (yd - yc))
        Return d <= r
    Else
        d = Math.Sqrt((xc - x1) * (xc - x1) + (yc - y1) * (yc - y1))
        If d <= r Then
            Return True
        Else
            d = Math.Sqrt((xc - x2) * (xc - x2) + (yc - y2) * (yc - y2))
            If d <= r Then
                Return True
            Else
                Return False
            End If
        End If
    End If
End Function

答案 15 :(得分:2)

我已根据chmike

给出的答案为iOS创建了此功能
+ (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;
}

答案 16 :(得分:2)

如果线的坐标是A.x,A.y和B.x,B.y和圆心是C.x,C.y那么线公式是:

x = A.x * t + B.x *(1-t)

y = A.y * t + B.y *(1 - t)

其中0 <= t <= 1

,圆圈是

(C.x-x)^ 2 +(C.y-y)^ 2 = R ^ 2

如果将线的x和y公式替换为圆公式,则得到t的二阶方程,其解是交点(如果有的话)。如果你得到一个小于0或大于1的t,那么它不是一个解决方案,但它表明这条线是“指向”圆的方向。

答案 17 :(得分:1)

此Java函数返回DVec2对象。圆的中心,圆的半径和直线需要DVec2

public static DVec2 CircLine(DVec2 C, double r, Line line)
{
    DVec2 A = line.p1;
    DVec2 B = line.p2;
    DVec2 P;
    DVec2 AC = new DVec2( C );
    AC.sub(A);
    DVec2 AB = new DVec2( B );
    AB.sub(A);
    double ab2 = AB.dot(AB);
    double acab = AC.dot(AB);
    double t = acab / ab2;

    if (t < 0.0) 
        t = 0.0;
    else if (t > 1.0) 
        t = 1.0;

    //P = A + t * AB;
    P = new DVec2( AB );
    P.mul( t );
    P.add( A );

    DVec2 H = new DVec2( P );
    H.sub( C );
    double h2 = H.dot(H);
    double r2 = r * r;

    if(h2 > r2) 
        return null;
    else
        return P;
}

答案 18 :(得分:1)

也许还有另一种方法可以使用坐标系的旋转来解决此问题。

通常,如果一个线段是水平或垂直的,这意味着平行于x或y轴,则求解交点非常容易,因为我们已经知道交点的一个坐标(如果有)。其余的显然是使用圆的方程式找到另一个坐标。

受此想法启发,我们可以应用坐标系旋转以使一个轴的方向与线段的方向一致。

让我们以在x-y系统中圆为x^2+y^2=1且段为P1-P2且具有P1(-1.5,0.5)和P2(-0.5,-0.5)的示例为例。以下方程式提醒您旋转原理,其中theta是逆时针的角度,x'-y'是旋转后的系统:

x'= x * cos(theta)+ y * sin(theta)

y'=-x * sin(θ)+ y * cos(θ)

反之

x = x'* cos(theta)-y'* sin(theta)

y = x'* sin(θ)+ y'* cos(theta)

考虑线段P1-P2的方向(以-x表示45°),我们可以采用theta=45°。在x-y系统x^2+y^2=1中将第二个旋转方程转化为圆方程,简单操作后,在x'-y'系统x'^2+y'^2=1中得到“相同”方程。

使用第一个旋转方程,分段端点成为x'-y'系统=> P1(-sqrt(2)/ 2,sqrt(2)),P2(-sqrt(2)/ 2,0)。

假设交点为P。我们在x'-y'中具有Px = -sqrt(2)/ 2。使用新的圆方程,我们得到Py = + sqrt(2)/ 2。将P转换为原始的x-y系统,我们最终得到P(-1,0)。

要以数字方式实现此目的,我们首先可以查看片段的方向:水平,垂直还是不垂直。如果它属于前两种情况,就像我说的那样简单。如果是最后一种情况,请应用上述算法。

要判断是否有交点,可以将解与端点坐标进行比较,以查看它们之间是否有一个根。

我相信只要有方程式,该方法也可以应用于其他曲线。唯一的缺点是我们应该在x'-y'系统中为另一个坐标求解方程,这可能很困难。

答案 19 :(得分:1)

我知道距此线程打开已经有一段时间了。从chmike给出的答案到Aqib Mumtaz的改进。他们给出了很好的答案,但仅适用于无限行,如Aqib所述。因此,我添加了一些比较,以了解线段是否接触到圆,我用Python编写了它。

def LineIntersectCircle(c, r, p1, p2):
    #p1 is the first line point
    #p2 is the second line point
    #c is the circle's center
    #r is the circle's radius

    p3 = [p1[0]-c[0], p1[1]-c[1]]
    p4 = [p2[0]-c[0], p2[1]-c[1]]

    m = (p4[1] - p3[1]) / (p4[0] - p3[0])
    b = p3[1] - m * p3[0]

    underRadical = math.pow(r,2)*math.pow(m,2) + math.pow(r,2) - math.pow(b,2)

    if (underRadical < 0):
        print("NOT")
    else:
        t1 = (-2*m*b+2*math.sqrt(underRadical)) / (2 * math.pow(m,2) + 2)
        t2 = (-2*m*b-2*math.sqrt(underRadical)) / (2 * math.pow(m,2) + 2)
        i1 = [t1+c[0], m * t1 + b + c[1]]
        i2 = [t2+c[0], m * t2 + b + c[1]]

        if p1[0] > p2[0]:                                           #Si el punto 1 es mayor al 2 en X
            if (i1[0] < p1[0]) and (i1[0] > p2[0]):                 #Si el punto iX esta entre 2 y 1 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i1[1] < p1[1]) and (i1[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i1[1] > p1[1]) and (i1[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] < p2[0]:                                           #Si el punto 2 es mayor al 1 en X
            if (i1[0] > p1[0]) and (i1[0] < p2[0]):                 #Si el punto iX esta entre 1 y 2 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i1[1] < p1[1]) and (i1[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i1[1] > p1[1]) and (i1[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] > p2[0]:                                           #Si el punto 1 es mayor al 2 en X
            if (i2[0] < p1[0]) and (i2[0] > p2[0]):                 #Si el punto iX esta entre 2 y 1 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i2[1] < p1[1]) and (i2[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i2[1] > p1[1]) and (i2[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] < p2[0]:                                           #Si el punto 2 es mayor al 1 en X
            if (i2[0] > p1[0]) and (i2[0] < p2[0]):                 #Si el punto iX esta entre 1 y 2 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i2[1] < p1[1]) and (i2[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i2[1] > p1[1]) and (i2[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

答案 20 :(得分:1)

以下是@Mizipzor建议的想法(使用投影),这是我在TypeScript中的解决方案:

/**
 * 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;
}

答案 21 :(得分:1)

圈子确实是一个坏人:)所以一个好方法是避免真正的圈子,如果可以的话。如果您正在对游戏进行碰撞检查,您可以进行一些简化,只需要3个点产品,并进行一些比较。

我称之为“胖点”或“薄圈”。它是一种在平行于一个段的方向上具有零半径的椭圆。但在垂直于一段的方向上的全半径

首先,我会考虑重命名和切换坐标系以避免过多的数据:

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

其次,hvec2f中的索引h意味着比矢量必须支持水平操作,如dot()/ det()。这意味着它的组件将被放置在一个单独的xmm寄存器中,以避免混乱/充电/ hsub'ing。在这里,我们将为2D游戏提供最简单的最简单的碰撞检测版本:

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;
}

我怀疑你是否可以进一步优化它。我正在将它用于神经网络驱动的赛车碰撞检测,以处理数亿次迭代步骤。

答案 22 :(得分:1)

这是JavaScript中的好解决方案(包含所有必需的数学和实时插图) https://bl.ocks.org/milkbread/11000965

虽然该解决方案中的task_hello = BashOperator(task_id='print_hello', bash_command="mkdir ~/airflow/test_airflow", dag=dag) 函数需要修改:

&#13;
&#13;
is_on
&#13;
&#13;
&#13;

答案 23 :(得分:1)

c#中的另一个(部分Circle类)。 经过测试,就像魅力一样。

public class Circle : IEquatable<Circle>
{
    // ******************************************************************
    // The center of a circle
    private Point _center;
    // The radius of a circle
    private double _radius;

   // ******************************************************************
    /// <summary>
    /// Find all intersections (0, 1, 2) of the circle with a line defined by its 2 points.
    /// Using: http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle
    /// Note: p is the Center.X and q is Center.Y
    /// </summary>
    /// <param name="linePoint1"></param>
    /// <param name="linePoint2"></param>
    /// <returns></returns>
    public List<Point> GetIntersections(Point linePoint1, Point linePoint2)
    {
        List<Point> intersections = new List<Point>();

        double dx = linePoint2.X - linePoint1.X;

        if (dx.AboutEquals(0)) // Straight vertical line
        {
            if (linePoint1.X.AboutEquals(Center.X - Radius) || linePoint1.X.AboutEquals(Center.X + Radius))
            {
                Point pt = new Point(linePoint1.X, Center.Y);
                intersections.Add(pt);
            }
            else if (linePoint1.X > Center.X - Radius && linePoint1.X < Center.X + Radius)
            {
                double x = linePoint1.X - Center.X;

                Point pt = new Point(linePoint1.X, Center.Y + Math.Sqrt(Radius * Radius - (x * x)));
                intersections.Add(pt);

                pt = new Point(linePoint1.X, Center.Y - Math.Sqrt(Radius * Radius - (x * x)));
                intersections.Add(pt);
            }

            return intersections;
        }

        // Line function (y = mx + b)
        double dy = linePoint2.Y - linePoint1.Y;
        double m = dy / dx;
        double b = linePoint1.Y - m * linePoint1.X;

        double A = m * m + 1;
        double B = 2 * (m * b - m * _center.Y - Center.X);
        double C = Center.X * Center.X + Center.Y * Center.Y - Radius * Radius - 2 * b * Center.Y + b * b;

        double discriminant = B * B - 4 * A * C;

        if (discriminant < 0)
        {
            return intersections; // there is no intersections
        }

        if (discriminant.AboutEquals(0)) // Tangeante (touch on 1 point only)
        {
            double x = -B / (2 * A);
            double y = m * x + b;

            intersections.Add(new Point(x, y));
        }
        else // Secant (touch on 2 points)
        {
            double x = (-B + Math.Sqrt(discriminant)) / (2 * A);
            double y = m * x + b;
            intersections.Add(new Point(x, y));

            x = (-B - Math.Sqrt(discriminant)) / (2 * A);
            y = m * x + b;
            intersections.Add(new Point(x, y));
        }

        return intersections;
    }

    // ******************************************************************
    // Get the center
    [XmlElement("Center")]
    public Point Center
    {
        get { return _center; }
        set
        {
            _center = value;
        }
    }

    // ******************************************************************
    // Get the radius
    [XmlElement]
    public double Radius
    {
        get { return _radius; }
        set { _radius = value; }
    }

    //// ******************************************************************
    //[XmlArrayItemAttribute("DoublePoint")]
    //public List<Point> Coordinates
    //{
    //    get { return _coordinates; }
    //}

    // ******************************************************************
    // Construct a circle without any specification
    public Circle()
    {
        _center.X = 0;
        _center.Y = 0;
        _radius = 0;
    }

    // ******************************************************************
    // Construct a circle without any specification
    public Circle(double radius)
    {
        _center.X = 0;
        _center.Y = 0;
        _radius = radius;
    }

    // ******************************************************************
    // Construct a circle with the specified circle
    public Circle(Circle circle)
    {
        _center = circle._center;
        _radius = circle._radius;
    }

    // ******************************************************************
    // Construct a circle with the specified center and radius
    public Circle(Point center, double radius)
    {
        _center = center;
        _radius = radius;
    }

    // ******************************************************************
    // Construct a circle based on one point
    public Circle(Point center)
    {
        _center = center;
        _radius = 0;
    }

    // ******************************************************************
    // Construct a circle based on two points
    public Circle(Point p1, Point p2)
    {
        Circle2Points(p1, p2);
    }

必需:

using System;

namespace Mathematic
{
    public static class DoubleExtension
    {
        // ******************************************************************
        // Base on Hans Passant Answer on:
        // http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre

        /// <summary>
        /// Compare two double taking in account the double precision potential error.
        /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
        public static bool AboutEquals(this double value1, double value2)
        {
            if (double.IsPositiveInfinity(value1))
                return double.IsPositiveInfinity(value2);

            if (double.IsNegativeInfinity(value1))
                return double.IsNegativeInfinity(value2);

            if (double.IsNaN(value1))
                return double.IsNaN(value2);

            double epsilon = Math.Max(Math.Abs(value1), Math.Abs(value2)) * 1E-15;
            return Math.Abs(value1 - value2) <= epsilon;
        }

        // ******************************************************************
        // Base on Hans Passant Answer on:
        // http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre

        /// <summary>
        /// Compare two double taking in account the double precision potential error.
        /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
        /// You get really better performance when you can determine the contextual epsilon first.
        /// </summary>
        /// <param name="value1"></param>
        /// <param name="value2"></param>
        /// <param name="precalculatedContextualEpsilon"></param>
        /// <returns></returns>
        public static bool AboutEquals(this double value1, double value2, double precalculatedContextualEpsilon)
        {
            if (double.IsPositiveInfinity(value1))
                return double.IsPositiveInfinity(value2);

            if (double.IsNegativeInfinity(value1))
                return double.IsNegativeInfinity(value2);

            if (double.IsNaN(value1))
                return double.IsNaN(value2);

            return Math.Abs(value1 - value2) <= precalculatedContextualEpsilon;
        }

        // ******************************************************************
        public static double GetContextualEpsilon(this double biggestPossibleContextualValue)
        {
            return biggestPossibleContextualValue * 1E-15;
        }

        // ******************************************************************
        /// <summary>
        /// Mathlab equivalent
        /// </summary>
        /// <param name="dividend"></param>
        /// <param name="divisor"></param>
        /// <returns></returns>
        public static double Mod(this double dividend, double divisor)
        {
            return dividend - System.Math.Floor(dividend / divisor) * divisor;
        }

        // ******************************************************************
    }
}

答案 24 :(得分:0)

这是用golang编写的解决方案。该方法类似于此处发布的其他一些答案,但不完全相同。它易于实施,并已经过测试。以下是步骤:

  1. 翻译坐标,使圆圈位于原点。
  2. 将线段表示为x和y坐标的t的参数化函数。如果t为0,则函数的值是段的一个端点,如果t为1,则函数的值是另一个端点。
  3. 如果可能的话,解决由约束t的值产生的二次方程,该值产生x,y坐标,距离原点的距离等于圆的半径。
  4. 丢弃t为&lt;的解决方案。 0或&gt; 1(对于开放段,&lt; = 0或&gt; = 1)。这些点不包含在细分中。
  5. 翻译回原始坐标。
  6. 这里导出了二次方的A,B和C的值,其中(n-et)和(m-dt)分别是线的x和y坐标的方程。 r是圆的半径。

    (n-et)(n-et) + (m-dt)(m-dt) = rr
    nn - 2etn + etet + mm - 2mdt + dtdt = rr
    (ee+dd)tt - 2(en + dm)t + nn + mm - rr = 0
    

    因此A = ee + dd,B = - 2(en + dm),C = nn + mm - rr。

    这是函数的golang代码:

    package geom
    
    import (
        "math"
    )
    
    // SegmentCircleIntersection return points of intersection between a circle and
    // a line segment. The Boolean intersects returns true if one or
    // more solutions exist. If only one solution exists, 
    // x1 == x2 and y1 == y2.
    // s1x and s1y are coordinates for one end point of the segment, and
    // s2x and s2y are coordinates for the other end of the segment.
    // cx and cy are the coordinates of the center of the circle and
    // r is the radius of the circle.
    func SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r float64) (x1, y1, x2, y2 float64, intersects bool) {
        // (n-et) and (m-dt) are expressions for the x and y coordinates
        // of a parameterized line in coordinates whose origin is the
        // center of the circle.
        // When t = 0, (n-et) == s1x - cx and (m-dt) == s1y - cy
        // When t = 1, (n-et) == s2x - cx and (m-dt) == s2y - cy.
        n := s2x - cx
        m := s2y - cy
    
        e := s2x - s1x
        d := s2y - s1y
    
        // lineFunc checks if the  t parameter is in the segment and if so
        // calculates the line point in the unshifted coordinates (adds back
        // cx and cy.
        lineFunc := func(t float64) (x, y float64, inBounds bool) {
            inBounds = t >= 0 && t <= 1 // Check bounds on closed segment
            // To check bounds for an open segment use t > 0 && t < 1
            if inBounds { // Calc coords for point in segment
                x = n - e*t + cx
                y = m - d*t + cy
            }
            return
        }
    
        // Since we want the points on the line distance r from the origin,
        // (n-et)(n-et) + (m-dt)(m-dt) = rr.
        // Expanding and collecting terms yeilds the following quadratic equation:
        A, B, C := e*e+d*d, -2*(e*n+m*d), n*n+m*m-r*r
    
        D := B*B - 4*A*C // discriminant of quadratic
        if D < 0 {
            return // No solution
        }
        D = math.Sqrt(D)
    
        var p1In, p2In bool
        x1, y1, p1In = lineFunc((-B + D) / (2 * A)) // First root
        if D == 0.0 {
            intersects = p1In
            x2, y2 = x1, y1
            return // Only possible solution, quadratic has one root.
        }
    
        x2, y2, p2In = lineFunc((-B - D) / (2 * A)) // Second root
    
        intersects = p1In || p2In
        if p1In == false { // Only x2, y2 may be valid solutions
            x1, y1 = x2, y2
        } else if p2In == false { // Only x1, y1 are valid solutions
            x2, y2 = x1, y1
        }
        return
    }
    

    我使用此功能对其进行了测试,确认解决方案点位于线段和圆上。它会创建一个测试段并围绕给定的圆圈进行扫描:

    package geom_test
    
    import (
        "testing"
    
        . "**put your package path here**"
    )
    
    func CheckEpsilon(t *testing.T, v, epsilon float64, message string) {
        if v > epsilon || v < -epsilon {
            t.Error(message, v, epsilon)
            t.FailNow()
        }
    }
    
    func TestSegmentCircleIntersection(t *testing.T) {
        epsilon := 1e-10      // Something smallish
        x1, y1 := 5.0, 2.0    // segment end point 1
        x2, y2 := 50.0, 30.0  // segment end point 2
        cx, cy := 100.0, 90.0 // center of circle
        r := 80.0
    
        segx, segy := x2-x1, y2-y1
    
        testCntr, solutionCntr := 0, 0
    
        for i := -100; i < 100; i++ {
            for j := -100; j < 100; j++ {
                testCntr++
                s1x, s2x := x1+float64(i), x2+float64(i)
                s1y, s2y := y1+float64(j), y2+float64(j)
    
                sc1x, sc1y := s1x-cx, s1y-cy
                seg1Inside := sc1x*sc1x+sc1y*sc1y < r*r
                sc2x, sc2y := s2x-cx, s2y-cy
                seg2Inside := sc2x*sc2x+sc2y*sc2y < r*r
    
                p1x, p1y, p2x, p2y, intersects := SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r)
    
                if intersects {
                    solutionCntr++
                    //Check if points are on circle
                    c1x, c1y := p1x-cx, p1y-cy
                    deltaLen1 := (c1x*c1x + c1y*c1y) - r*r
                    CheckEpsilon(t, deltaLen1, epsilon, "p1 not on circle")
    
                    c2x, c2y := p2x-cx, p2y-cy
                    deltaLen2 := (c2x*c2x + c2y*c2y) - r*r
                    CheckEpsilon(t, deltaLen2, epsilon, "p2 not on circle")
    
                    // Check if points are on the line through the line segment
                    // "cross product" of vector from a segment point to the point
                    // and the vector for the segment should be near zero
                    vp1x, vp1y := p1x-s1x, p1y-s1y
                    crossProd1 := vp1x*segy - vp1y*segx
                    CheckEpsilon(t, crossProd1, epsilon, "p1 not on line ")
    
                    vp2x, vp2y := p2x-s1x, p2y-s1y
                    crossProd2 := vp2x*segy - vp2y*segx
                    CheckEpsilon(t, crossProd2, epsilon, "p2 not on line ")
    
                    // Check if point is between points s1 and s2 on line
                    // This means the sign of the dot prod of the segment vector
                    // and point to segment end point vectors are opposite for
                    // either end.
                    wp1x, wp1y := p1x-s2x, p1y-s2y
                    dp1v := vp1x*segx + vp1y*segy
                    dp1w := wp1x*segx + wp1y*segy
                    if (dp1v < 0 && dp1w < 0) || (dp1v > 0 && dp1w > 0) {
                        t.Error("point not contained in segment ", dp1v, dp1w)
                        t.FailNow()
                    }
    
                    wp2x, wp2y := p2x-s2x, p2y-s2y
                    dp2v := vp2x*segx + vp2y*segy
                    dp2w := wp2x*segx + wp2y*segy
                    if (dp2v < 0 && dp2w < 0) || (dp2v > 0 && dp2w > 0) {
                        t.Error("point not contained in segment ", dp2v, dp2w)
                        t.FailNow()
                    }
    
                    if s1x == s2x && s2y == s1y { //Only one solution
                        // Test that one end of the segment is withing the radius of the circle
                        // and one is not
                        if seg1Inside && seg2Inside {
                            t.Error("Only one solution but both line segment ends inside")
                            t.FailNow()
                        }
                        if !seg1Inside && !seg2Inside {
                            t.Error("Only one solution but both line segment ends outside")
                            t.FailNow()
                        }
    
                    }
                } else { // No intersection, check if both points outside or inside
                    if (seg1Inside && !seg2Inside) || (!seg1Inside && seg2Inside) {
                        t.Error("No solution but only one point in radius of circle")
                        t.FailNow()
                    }
                }
            }
        }
        t.Log("Tested ", testCntr, " examples and found ", solutionCntr, " solutions.")
    }
    

    以下是测试的输出:

    === RUN   TestSegmentCircleIntersection
    --- PASS: TestSegmentCircleIntersection (0.00s)
        geom_test.go:105: Tested  40000  examples and found  7343  solutions.
    

    最后,该方法可以容易地扩展到光线从一点开始,穿过另一点并延伸到无穷大的情况,仅通过测试是否t> 1。 0或t <0 1但不是两者。

答案 25 :(得分:0)

我只需要那个,所以我想出了这个解决方案。该语言是maxscript,但应轻松翻译成任何其他语言。 sideA,sideB和CircleRadius是标量,其余变量为[x,y,z]点。我假设z = 0可以在XY平面上求解

fn projectPoint p1 p2 p3 = --project  p1 perpendicular to the line p2-p3
(
    local v= normalize (p3-p2)
    local p= (p1-p2)
    p2+((dot v p)*v)
)
fn findIntersectionLineCircle CircleCenter CircleRadius LineP1 LineP2=
(
    pp=projectPoint CircleCenter LineP1 LineP2
    sideA=distance pp CircleCenter
    --use pythagoras to solve the third side
    sideB=sqrt(CircleRadius^2-sideA^2) -- this will return NaN if they don't intersect
    IntersectV=normalize (pp-CircleCenter)
    perpV=[IntersectV.y,-IntersectV.x,IntersectV.z]
    --project the point to both sides to find the solutions
    solution1=pp+(sideB*perpV)
    solution2=pp-(sideB*perpV)
    return #(solution1,solution2)
)

答案 26 :(得分:0)

基于@Joe Skeen的python解决方案

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)
    )

答案 27 :(得分:0)

另一种解决方案,首先考虑您不关心碰撞位置的情况。请注意,此特定功能是在假设xB和yB为矢量输入的情况下构建的,但如果不是这种情况,则可以轻松进行修改。变量名在函数开始处定义

#Line segment points (A0, Af) defined by xA0, yA0, xAf, yAf; circle center denoted by xB, yB; rB=radius of circle, rA = radius of point (set to zero for your application)
def staticCollision_f(xA0, yA0, xAf, yAf, rA, xB, yB, rB): #note potential speed up here by casting all variables to same type and/or using Cython
    
    #Build equations of a line for linear agents (convert y = mx + b to ax + by + c = 0 means that a = -m, b = 1, c = -b
    m_v = (yAf - yA0) / (xAf - xA0)
    b_v = yAf - m_v * xAf
    rEff = rA + rB #radii are added since we are considering the agent path as a thin line

    #Check if points (circles) are within line segment (find center of line segment and check if circle is within radius of this point)
    segmentMask = np.sqrt( (yB - (yA0+yAf)/2)**2 + (xB - (xA0+xAf)/2)**2 ) < np.sqrt( (yAf - yA0)**2 + (xAf - xA0)**2 ) / 2 + rEff

    #Calculate perpendicular distance between line and a point
    dist_v = np.abs(-m_v * xB + yB - b_v) / np.sqrt(m_v**2 + 1)
    collisionMask = (dist_v < rEff) & segmentMask

    #return True if collision is detected
    return collisionMask, collisionMask.any()

如果需要碰撞的位置,则可以使用此站点上详述的方法,并将其中一个特工的速度设置为零。这种方法也适用于向量输入:http://twobitcoder.blogspot.com/2010/04/circle-collision-detection.html