円線分衝突検出アルゴリズム?


195

AからBへの線と、半径RでCに配置された円があります。

画像

線が円と交差するかどうかを確認するために使用する適切なアルゴリズムは何ですか?そして、それは円の端に沿ったどの座標で発生しましたか?


4
うーん。1つの質問:AとBを通る無限の線、またはAからBへの有限の線分について話しているのですか?
Jason S、

2
この場合、その有限の線分です。「ライン」は、有限か無限かに応じて、別の名前で呼ばれますか?
Mizipzor 2009

1
パフォーマンス要件はありますか?それは速い方法でしょうか?
chmike 2009

この時点では、いいえ、私が試したすべてのアルゴリズムはアプリケーションを著しく遅くしません。
Mizipzor 2009

13
@Mizipzorはい、ラインセグメントと呼ばれます。「行」とだけ言うと、無限のものを意味します。
MestreLion 2014

回答:


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
into
(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 = EのY + TDのY
    (E X + TD X2 - 2(E X + TD X)H +、H 2 +(EのY + TDのY2 - 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次元Y 2 - 2EのYの K - 2TD Y K + k 2 -r 2 = 0
  4. グループ
    T 2(D X 2 + D Y 2)+ 2トン(E X D X + E のy次元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
  7. させる_f = _e-_c
    t 2(_d * _d)+ 2t(_d * _f)+ _f * _f-r 2 = 0

取得我々はそう:
T 2 *(DドットD)+ 2トン*(F DOT F - R(FドットD)+ 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でそれらを詳細に調べることができます。
Mizipzor 2009

3
hとkは、交差する円の中心です。tは線方程式のパラメーターです。コードでは、t1とt2がソリューションです。t1とt2は、交差が発生した「光線に沿った距離」を示します。
ボボボボ2009

1
はい、わかった。内積は、3つの要素(x、y、z)ベクトルに対して単純に計算されます。コードをこのアルゴリズムに切り替えます。
chmike

21
P = E + t * dなにt
Derek━會功夫

3
理由は不明ですが、コードはImpaleのケースでは機能しないようです。真の条件として、t1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1の場合に追加しますが、有限線の片側で、円が「無限」の部分にあります。私はまだ数学を理解していませんが、コピー/貼り付けには注意してください。
Nicolas Mommaerts 2013

141

誰も予測を考慮していないようです。私はここで完全に軌道に乗っていませんか?

ベクトルをにAC投影しABます。射影されたベクトルADは新しい点を与えDます。
距離場合Dとは、Cより小さい(又は等しい)であり、R我々が交差点を有します。

このような:
SchoolBoyによる画像


9
考慮すべき多くの詳細があります:DはABの間にありますか?Cの線への垂直距離は半径よりも大きいですか?これらはすべてベクトルの大きさ、つまり平方根を含みます。
ADB、2010

15
良いアイデアですが、2つの交点をどのように計算しますか?
ベン

4
@スパイダーそれは重要ではありません。一般に、これは球線交差問題の変形であるため、Mizipzorの戦略は完全に有効です。CD投影です。定義により垂直です。

2
これは古い質問ですが、このWebサイトには、これと関連するアルゴリズムに関する優れたリソースがあります。paulbourke.net
Andrew


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の場合に当てはまります。
2014

ありがとう。私の数学は錆びているので「ドット積」という言葉を使用しなかったので、このpgmコメントを説明として気に入った。ただし、tとdtは0..1の間ではないため、これをpythonに変更するときに、tをLAB ** 2で除算するように変更しました。私の理解は、LABによる最初の除算は円の中心を線ABに投影することであり、LABによる2番目の除算はそれを0..1の範囲に正規化することです。また、dtはLABで除算する必要があるため、正規化されます。したがって、「if(t-dt> = 0.0)」の最初の交差が存在します。「if(t + dt <= 1.0)」の2番目の交差が存在します。これはテストで機能しました。
パンチカード2015年

2
円との交点が「距離」にt+dtありt-dt、直線上にあるためです。t円の中心に最も近い線上の点です。円との交点はから対称的な距離にありtます。交点は「距離」t-dtとにありt+dtます。ユークリッド距離ではないため、距離を引用しました。ユークリッド距離をAどこから取得するにはt=0、値にを掛ける必要がありますLAB
2015年

1
@Matt W「交点がラインセクションABの端点の外にあるかどうかを判断する方法」という意味ですか?tを線に沿った距離の尺度と考えてください。ポイントAはにありt=0ます。ポイントBでt=LAB。両方の交点(t1=t-tdおよびt2=t+td)が負の値である場合、交点はセクションの外側(ポイントのセクション側から見てポイントAの後ろ)にあります。t1とt2がLABより大きい場合、それらも外側になります(今回はBポイントより遅れています)。交点t1(またはt2)がAとBの間で発生するのは、t1(またはt2)が0とLABの間である場合のみです。
Marconius

20

さて、私はあなたにコードを与えませんが、あなたがこれにタグ付けしたので 、それはあなたには関係ないと思います。まず、線に垂直なベクトルを取得する必要があります。

あなたには、未知の変数を持つことになりますy = ax + c c不明となります
ラインは円の中心を通過するとき、それは価値だ計算、そのために解決します。

つまり
、円の中心の位置を直線方程式に接続し、を解きcます。
次に、元の線とその法線の交点を計算します。

これにより、線上の円に最も近い点が得られます。
(ベクトルの大きさを使用して)この点と円の中心との間の距離を計算します。
これが円の半径よりも小さい場合-出来上がり、交差点があります!


2
それが実際、私が欲しかったものです。理論が欲しいのですが、線と円の衝突アルゴリズムをグーグルで検索すると、コードが見えるだけです。
Mizipzor 2009

OK、cは方程式では不明ですが、「a」とは何ですか?他の答えは、その変数を「アルファ」と「t」と呼んでいるようです。これは単なる線形関数(y = kx + m)ですが、非常に基本的な数学なので、突然、少しさびた感じがします。kも不明ですか?それとも、m = 0と仮定してkを解くことができるということですか?その後、m(つまり、c)は、解決されたkに対して常にゼロにならないでしょうか?
Mizipzor 2009

1
ああ、ごめんなさい-勾配とオフセットのある直線の簡単な方程式(デカルト方程式)を使用しています。私はあなたがそのような方程式として線を格納していると仮定しました-その場合、あなたはkに負の勾配を使用します。このように行を保存していない場合は、kを(y2-y1)/(x2-x1)
a_m0d

1
mがゼロであるとは想定していません。最初に勾配を計算します(そのため、線の方程式は、例としてy = 2x + mのようになります)。次に勾配が得られたら、yとxの円の中心を差し込むことでmを解くことができます。 。
a_m0d 2009

1
+1素晴らしい説明!しかし、これは線分ではなく線分を想定していると思います。したがって、この線上の円の中心に最も近い点が点AとBの間にない場合でも、カウントされます。
Hassan

12

別の方法では、三角形のABC面積式を使用します。交差テストは投影法よりも簡単で効率的ですが、交差点の座標を見つけるにはより多くの作業が必要です。少なくともそれが必要とされるまで遅れるでしょう。

三角形の面積を計算する式は次のとおりです。area = bh / 2

ここで、bはベースの長さ、hは高さです。hが円の中心であるCから直線までの最短距離になるように、ベースとしてセグメントABを選択しました。

三角形の面積はベクトルの内積でも計算できるため、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 2009

1
t = Dx *(Cx-Ax)+ Dy *(Cy-Ax)を読む必要がありますt = Dx *(Cx-Ax)+ Dy *(Cy-Ay)
Stonetip

これは正しいです。ご指摘ありがとうございます。ポストで修正しました。
chmike

編集したばかり-最初の行は、ドット積ではなくクロス積を使用して三角形の面積を計算します。:ここでのコードで検証stackoverflow.com/questions/2533011/...
ericsoco

4
また、この回答の前半は、(質問で尋ねられたように)ラインセグメントではなく、ラインとの交差をテストします。
ericsoco 2013年

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

スロープインターセプト形式で線の方程式を解きます。ただし、難しい方程式をc1つの点として扱う必要がないので、座標系をシフトして、円が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)

OK。次に、これらの方程式を等しく設定する必要があります。まず、円の方程式を解く必要があります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、2014年

underRadical「)」余分
byJeevan

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

編集:見つかった交点が実際にラインセグメント内にあるかどうかをチェックするコードを追加しました。


線分について話しているため、1つのケースを見逃しました。セグメントが円で終わっているときです。
ADB

@ADBは実際、私のアルゴリズムは無限の線分に対してのみ機能し、線分に対しては機能しません。線分では扱えない場合が多いです。
Juozas Kontvainis、2010

元の質問は、円と線の交差ではなく、線分に関するものでした。これははるかに簡単な問題です。
msumme 2017年

4

奇妙なことに、答えることはできるがコメントはできない...円の中心を原点に合わせるためにすべてをシフトするマルチタスクプロのアプローチが好きだった。残念ながら、彼のコードには2つの問題があります。最初に、ルートの下の部分で、ダブルパワーを削除する必要があります。そうではありません:

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の距離が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式をこの方程式に適用してアルファを解き、アルファの解を使用してPの座標を計算すると、交差点が存在します。


3

球体の中心(3Dなので、円ではなく球体を意味すると想定します)と線の間の距離を見つけたら、その距離がトリックを実行する半径よりも小さいかどうかを確認します。

衝突点は明らかに線と球の間の最も近い点です(球と線の間の距離を計算するときに計算されます)

点と線の間の距離:http :
//mathworld.wolfram.com/Point-LineDistance3-Dimensional.html


1
3Dではなく2Dです。あなたが言うように、これは本当に問題ではありません
Martijn

私は数学者ではないので、一般的なアプローチを概説し、特定の数学を理解するために他の人に任せる方がいいと思いました(ただし、私はかなり簡単に見えます)
Martin

2
強い賛成票で+1。(私は別のサイトにリンクしていましたが、pbourkeサイトは混乱しているように見えます)これまでの他のすべての回答は複雑すぎます。「その点は線上の交点でもあります」というコメントは正しくありませんが、計算プロセスで作成された点はありません。
Jason S、


私は最も近い点について少し良く説明し、pbourkeの代わりにmathworldにリンクしました:)
マーティン

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

この投稿では、円の中心から線分までの通常のN(画像2)の交点を表す、円の中心と線分上の点(Ipoint)の間の距離をチェックすることにより、円の線の衝突をチェックします。

https://i.stack.imgur.com/3o6do.png画像1.ベクトルEおよびDを見つける

画像1では、1つの円と1つの線が示されています。ベクトルAの点から線の始点、ベクトルBの点から線の終点、ベクトルCの点から円の中心。次に、ベクトルE(線の始点から円の中心まで)とベクトルD(線の始点から線の終点まで)を見つける必要があります。この計算は画像1に示されています。

https://i.stack.imgur.com/7098a.png画像2.ベクトルXを見つける

画像2では、ベクトルEがベクトルDにベクトルEと単位ベクトルDの「内積」によって投影されていることがわかります。内積の結果は、線の始点と交点(Ipoint)の間の距離を表すスカラーXpです。ベクトルNとベクトルD。次のベクトルXは、単位ベクトルDとスカラーXpを乗算して求められます。

次に、ベクトルZ(Ipointへのベクトル)、ベクトルA(ラインの開始点)、およびベクトルXの簡単なベクトルの追加を見つける必要があります。次に、チェックする必要がある特殊なケースに対処する必要があります。左か右かを知る必要はありません。最も近いベクトルを使用して、どの点が円に最も近いかを決定します。

https://i.stack.imgur.com/p9WIr.png画像3.最も近い点を見つける

投影Xpが負の場合、Ipointはラインセグメントの左側にあり、最も近いベクトルはラインの始点のベクトルに等しく、投影XpがベクトルDの大きさよりも大きい場合、Ipointはラインセグメントの右側にあり、最も近いベクトルはラインエンドのベクトルに等しいその他の場合の点最も近いベクトルはベクトルZに等しい。

次に、最も近いベクトルがある場合、円の中心からIpoint(距離ベクトル)へのベクトルを見つける必要があります。単純なのは、中心ベクトルから最も近いベクトルを引くだけです。次に、ベクトル距離のマグニチュードが円の半径よりも小さいかどうかを確認します。円の半径が小さい場合は、衝突します。衝突していない場合は、それらを確認します。

https://i.stack.imgur.com/QJ63q.png画像4.衝突の確認

最後に、コリジョンを解決するためにいくつかの値を返すことができます。最も簡単な方法は、コリジョンのオーバーラップ(ベクトル距離の大きさから半径を引く)を返し、コリジョンの軸であるベクトル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の2次方程式が得られ、その解は交点(ある場合)になります。が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 2012年

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の範囲外である可能性があります。
クリス

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

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

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. 可能であれば、原点からの距離が円の半径と等しいx、y座標を生成するtの値を制約することから生じる2次方程式を解きます。
  4. tが<0または> 1(開いているセグメントの場合、<= 0または> = 1)である解を破棄します。これらのポイントはセグメントに含まれていません。
  5. 元の座標に戻します。

ここで、2次の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]のようなポイントです。XY平面で解くためにz = 0を仮定しています

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点を等間隔に取ることができ、円の中にある場合は、衝突があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.