圆线段碰撞检测算法?


195

我有一条从A到B的线和一个位于C且半径为R的圆。

图片

有什么好的算法可用来检查线是否与圆相交?沿圆边的坐标是什么?


4
嗯 一个问题:您是在谈论通过A和B的无限线,还是从A到B的有限线段?
詹森·S,2009年

2
在这种情况下,它是有限线段。“线”是否被称为其他东西,取决于它是有限的还是无限的?
Mizipzor

1
有性能要求吗?应该是一种快速的方法吗?
chmike

在这一点上,不,我已经尝试过的所有算法都不会明显降低应用程序的速度。
Mizipzor,2009年

13
@Mizipzor是的,它们被称为别的东西:线段。如果您只说“线”,则意味着无限。
MestreLion

Answers:


200

服用

  1. E是射线的起点,
  2. L是射线的终点,
  3. C是您要测试的球心
  4. r是那个球体的半径

计算:
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 = 0
  2. 插头
    X = E X + TD X
    Y =ë ý + TD ý
    (E X + TD X2 - 2(E X + TD X)H + H 2 +(E ý + TD Ý2 - 2(E ÿ + td y)k + k 2 -r 2 = 0
  3. 爆炸
    ë X 2 + 2e中X TD X + T 2 d X 2 - 2E X ħ - 2TD X H H + 2 + E ý 2 + 2E ý TD ý + T 2 d ý 2 - 2E ý的k - 2TD ÿ K + ķ 2 - R的 2 = 0

  4. 2(d X 2 + d ý 2)+ 2T(E X d X + E ý d ý - d X ħ - d Ÿ K)+ E X 2 + E ý 2 - 2E X ħ - 2E ÿ 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
  7. 令_f = _e-_c
    t 2(_d * _d)+ 2t(_d * _f)+ _f * _f-r 2 = 0

因此我们得到:
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
如果我直接复制和粘贴,似乎可以正常工作,但是我想了解它。在(xh)^ 2 +(yk)^ 2 = r ^ 2中,h和k是什么?k是常数,线/射线在y上比x增加吗?什么是t?查看该代码,您似乎已经假设其为1(因此只是“删除”了)。这些公式有名称吗?也许我可以在Wolfram上详细查找它们。
2009年

3
h和k是您相交的圆的中心。t是线方程的参数。在代码中,t1和t2是解决方案。t1和t2告诉您相交发生在“沿射线多远”位置。
bobobobo

1
好的,我知道了。点积可简单地在三个元素(x,y,z)向量上计算。我将代码切换到该算法。
chmike

21
P = E + t * d什么t
德里克·朕会功夫2012年

3
不知道为什么,但是该代码似乎不适用于Impale案例。当我将t1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1设为真实条件时,它会做,但是当圆在“无限”部分。我尚不懂数学,但请注意复制/粘贴。
Nicolas Mommaerts

140

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

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

像这样:
Image by SchoolBoy


9
有许多细节需要考虑:D是否位于AB之间?C到直线的垂直距离是否大于半径?所有这些都涉及向量的大小,即平方根。
亚行2010年

15
好主意,但是如何计算两个交点呢?
2012年

4
@Spider没关系。通常,由于这是球体线相交问题的一种变体,因此Mizipzor的策略是完全有效的。CD是投影,从定义上看它是垂直的。

2
这是一个古老的问题,但是在此网站上有关于此算法和相关算法的丰富资源:paulbourke.net/geometry/pointlineplane
Andrew

1
这个答案的一个很好的解释:scratchapixel.com/lessons/3d-basic-rendering/...
ShawnFeatherly

50

我将使用该算法来计算点(圆心)和直线(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

如果有任何不与圆相交的直线及其点p1和p2都在圆内。在这种情况下,您的算法如何工作?
Prashant 2014年

1
您必须测试t-dt和t + dt。如果t-dt <0,则p1在圆内。如果t + dt> 1,则p2在圆内。如果LEC <R当然是正确的。
chmike 2014年

谢谢。我喜欢这个pgm注释作为解释,因为我的数学很生疏,所以没有使用“点积”一词。但是t和dt不在0..1之间,因此在将其更改为python时,我将t更改为被LAB ** 2除。我的理解是,LAB进行的第一次除法是将圆心投影到直线AB上,LAB进行的第二次除法是将其归一化到0..1范围内。同样,dt需要用LAB进行除法,因此也要进行归一化。因此“ if(t-dt> = 0.0)”第一个交点存在“ if(t + dt <= 1.0)”第二个交点存在。这与测试一起工作。
穿孔卡

2
因为与圆的交点在“距离”上t+dt并且t-dt在直线上。t是直线上最接近圆心的点。与圆的相交点与的对称距离t。相交点位于“距离” t-dt和处t+dt。我引用距离,因为它不是欧几里得距离。为了充分利用欧氏距离A在那里t=0,你必须乘以值LAB
chmike 2015年

1
@Matt W您的意思是“如何确定相交是否发生在线段AB的端点之外”?只需考虑t作为沿线距离的度量即可。A点位于t=0。B点在t=LAB。当两个交点(t1=t-tdt2=t+td)都为负值时,交点位于截面的外部(从该点的截面侧面看在A点的后面)。当t1和t2大于LAB时,它们也位于外部(这次在B点之后)。仅当t1(或t2)在0和LAB之间时,交集t1(或t2)才出现在A和B之间。
Marconius

20

好的,我不会给您代码,但是由于您已标记了此代码 ,我认为这对您来说并不重要。首先,必须获得垂直于直线的向量。

为了解决这个问题,您将在y = ax + c c将是未知的)中有一个未知变量
,计算直线穿过圆心时的值。

也就是说,
将圆心的位置插入到直线方程中并求解c
然后计算原始线与其法线的交点。

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


2
实际上,这就是我想要的。我想要理论,就我所知,谷歌搜索圆-圆碰撞算法只会发现代码。
Mizipzor

好的,c在您的方程式中是未知的,但是“ a”是什么?其他答案似乎将该变量称为“ alpha”和“ t”。虽然,这只是一个线性函数(y = kx + m),这是非常基础的数学运算,但是我突然感到有些生锈。k还不知道吗?或者您是说我们可以假设m = 0并求解k?那么,对于我们求解的k,m(即c)是否始终为零?
Mizipzor

1
哦,对不起-我正在使用带有梯度和偏移量的直线的简单方程式(笛卡尔方程式)。我假设您将线存储为这样的方程式-在这种情况下,请对k使用渐变的负值。如果没有这样存储的行,则可以将k计算为(y2-y1)/(x2-x1)
a_m0d

1
我们不假定m为零。我们首先计算梯度(以直线方程为例,然后以y = 2x + m为例),然后一旦有了梯度,就可以通过插入y和x的圆心来求解m 。
a_m0d

1
+1很棒的解释!但是我认为这是假设一条线,而不是一条线段。因此,如果这条直线上距圆心的最近点不在A点和B点之间,则仍会计算在内。
哈桑

11

另一种方法使用三角形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:

您可以通过使用此处介绍的快速平方根逆运算来优化代码,以获得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是否不同,如果在应用程序中可能发生这种情况,则段长度不为null。


2
我喜欢这种方法的简单性。也许我可以修改一些周围的代码以使其本身不需要实际的碰撞点,但是如果我使用A或B而不是两者之间的计算点,将会发现会发生什么。
Mizipzor

1
t = Dx *(Cx-Ax)+ Dy *(Cy-Ax)应该读取t = Dx *(Cx-Ax)+ Dy *(Cy-Ay)
Stonetip 2012年

这是对的。感谢您指出。我在帖子中更正了它。
chmike 2012年

刚刚编辑过-第一行使用积而不是点积计算三角形面积。:用代码在此间证实stackoverflow.com/questions/2533011/...
ericsoco

4
另请注意,此答案的前半部分测试是否与直线相交,而不是与线段相交(如问题中所问)。
ericsoco

8

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

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/


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,然后将它们放到新的点,以防万一有人不知道。

无论如何,我现在用p3和解决线的方程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)

好。现在,我需要将这些等式设置为相等。首先,我需要求解圆的方程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 

然后将这两个方程的结果简单地插入xin 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];
    }
}

我希望这有帮助!

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


如果可能,还发布一些样本值,以便我们快速掌握流程。
Prabindh'3

带有underRadical额外的“)”
Jeevan,2013年

4

通过将矢量AC投影到矢量AB上,可以在最接近圆心的无限线上找到一个点。计算该点与圆心之间的距离。如果大于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
  }
}

编辑:添加代码以检查找到的相交点是否实际上在线段内。


您错过了一种情况,因为我们在谈论线段:线段在圆弧中结束。
亚行2010年

@ADB实际上我的算法仅适用于无限行,而不适用于线段。在许多情况下,它无法处理线段。
Juozas Kontvainis

最初的问题是关于线段,而不是圆线相交,这是一个容易得多的问题。
msumme

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

1
建议:首先,让它处理线段是垂直的(即具有无限斜率)的情况。其次,让它只返回那些实际上在原始线段范围内的点-我相信它很高兴地返回所有落在无限线上的点,即使这些点位于线段之外。
吉诺

注意:这对线有效,但对线段无效。
迈克,

3

您需要在这里进行一些数学运算:

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

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

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的坐标,则将获得相交点(如果存在)。


3

如果您发现球体中心(因为它是3D,我假设您是指球体而不是圆形)与直线之间的距离,请检查该距离是否小于将完成此操作的半径。

碰撞点显然是线与球之间的最近点(将在计算球与线之间的距离时进行计算)

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


1
它是2D模式,而不是3D模式;正如您所说,这并不重要
Martijn,2009年

我不是数学家,所以我认为最好概述一种通用的方法,然后让其他人找出具体的数学方法(尽管我看起来确实很琐碎)
Martin

2
+1表示强烈反对。(尽管我会链接到另一个站点,但pbourke站点看起来令人困惑)到目前为止,所有其他答案都过于复杂。尽管您的注释“该点也是直线上的交点”是错误的,但是在计算过程中没有构造任何点。
Jason S


我对最接近的点做了更好的解释,并链接到mathworld而不是pbourke :)
Martin

3

这是Javascript中的实现。我的方法是先将线段转换为无限线,然后找到相交点。从那里,我检查找到的点是否在线段上。该代码有据可查,您应该可以继续学习。

您可以在此现场演示中试用代码。该代码取自我的算法库

在此处输入图片说明

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

}

3

在本文中,将通过检查圆心与线段上的点(Ipoint)之间的距离来检查圆线碰撞,该线段代表法线N(图2)从圆心到线段之间的交点。

https://i.stack.imgur.com/3o6do.png图1.查找向量E和D

在图像1上显示了一个圆和一条直线,矢量A点到线的起点,矢量B点到线的终点,矢量C点到圆的中心。现在我们必须找到向量E(从线的起点到圆心)和向量D(从线的起点到线终点),该计算如图1所示。

https://i.stack.imgur.com/7098a.png图2.查找向量X

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

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

https://i.stack.imgur.com/p9WIr.png图3.查找最近的点

当投影Xp为负时,Ipoint位于线段的左侧,最接近的矢量等于线起点的矢量,当投影Xp大于矢量D的大小,则Ipoint位于线段的右侧,那么最近的矢量等于线端的矢量在任何其他情况下,最接近的矢量等于矢量Z。

现在,当我们有最接近的向量时,我们需要找到从圆心到Ipoint的向量(距离向量),这很简单,我们只需要从中心向量中减去最接近的向量即可。接下来,仅检查向量dist的大小是否小于圆半径,如果小于则不发生碰撞。

https://i.stack.imgur.com/QJ63q.png图4.检查碰撞

最后,我们可以返回一些值来解决碰撞,最简单的方法是返回碰撞的重叠部分(减去矢量dist大小的半径)和返回碰撞轴,即其矢量D。如果需要,交点也为矢量Z。


2

如果线的坐标为Ax,Ay和Bx,By,圆心为Cx,Cy,则线公式为:

x = Ax * t + Bx *(1-t)

y = Ay * t + By *(1-t)

其中0 <= t <= 1

圆圈是

(Cx-x)^ 2 +(Cy-y)^ 2 = R ^ 2

如果将直线的x和y公式替换为圆形公式,则会得到t的二阶方程,其解是交点(如果有)。如果得到小于0或大于1的值,则它不是解决方案,但它表明该线“指向”圆的方向。


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

在C#/ XNA中,您可以使用 Ray.Intersects(BoundingSphere)
bobobobo

2

在此处输入图片说明

' 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

2

我已经按照给出的答案为iOS创建了此功能 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;
}

2

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

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


2

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

我称其为“胖点”或“细圈”。它是一种椭圆,在平行于线段的方向上半径为零。但在垂直于线段的方向上是完整半径

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

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

其次,hvec2f中的索引h表示矢量必须支持水平操作,例如dot()/ det()。这意味着其组件将放置在单独的xmm寄存器中,以避免混洗/添加/添加。接下来,我们为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;
}

我怀疑您可以进一步优化它。我将其用于神经网络驱动的赛车碰撞检测,以处理数百万个迭代步骤。


如果线段与圆仅相交但仅与圆相交,因此它不通过其中心点,则该函数在应返回true时不返回false吗?t值可能在0..1范围之外。
克里斯(Chris

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

1

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

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

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

0

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

  1. 平移坐标,以使圆位于原点。
  2. 对于x和y坐标,将线段表示为t的参数化函数。如果t为0,则函数的值为端点的一个端点;如果t为1,则函数的值为另一端点。
  3. 如有可能,解二次方程,该二次方程由t的约束值产生,这些值产生x,y坐标,且距原点的距离等于圆的半径。
  4. 抛出t小于0或> 1的解决方案(对于开放式段,<= 0或> = 1)。这些点不包含在细分中。
  5. 翻译回原始坐标。

此处求出二次方的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> 0或t <1而不是两者都可以,该方法很容易扩展到射线从一个点开始穿过另一点并延伸到无限远的情况。


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

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

0
Function lineCircleCollision(p1,p2,c,r,precision){
Let dx = (p2.x-p1.x)/precision
Let dy = (p2.y-p1.y)/precision
Let collision=false
For(let i = 0;i<precision:i++){
If(Math.sqrt((p1.x+dx*i-c.x)**2+(p1.y+dy*i-c.y)**2).<r {
Collision=true
}
}

您可以从直线上获取X个等距的点,如果圆内有任何点,则发生碰撞

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.