Circle line-segment çarpışma algılama algoritması?


197

A'dan B'ye bir çizgi ve C yarıçapı R olan bir dairem var.

görüntü

Çizginin daireyle kesişip kesişmediğini kontrol etmek için kullanılacak iyi bir algoritma nedir? Ve daireler kenarındaki hangi koordinatta meydana geldi?


4
Hmm. Bir soru: A ve B'deki sonsuz çizgi veya A'dan B'ye sonlu çizgi segmentinden mi bahsediyorsunuz?
Jason S

2
Bu durumda, sonlu çizgi segmenti. "Çizgi", sonlu veya sonsuz olmasına bağlı olarak başka bir şey olarak mı adlandırılır?
Mizipzor

1
Performans gerekliliği var mı? Hızlı bir yöntem olmalı mı?
chmike

Bu noktada, hayır, burada denediğim tüm algoritmalar uygulamayı belirgin şekilde yavaşlatmıyor.
Mizipzor

13
@Mizipzor evet, başka bir şey denir: çizgi parçaları . Eğer sadece "çizgi" derseniz, bu sonsuzdur.
MestreLion

Yanıtlar:


200

alma

  1. E ışının başlangıç ​​noktasıdır,
  2. L , ışının bitiş noktasıdır,
  3. C , test ettiğiniz kürenin merkezidir
  4. r o kürenin yarıçapıdır

Hesaplama:
d = L - E (Işının baştan sona yön vektörü)
f = E - C (Orta küreden ışın başlangıcına vektör)

Daha sonra kesişme .. tarafından bulunan
takma:
p = D + t * d
Parametrik denklemdir:
p x = E x + td x
P y = E y + td y
içine
(x - h) 2 + (y - k) 2 = r 2
(h, k) = dairenin merkezi.

Not: Sorunu burada 2D'ye basitleştirdik, elde ettiğimiz çözüm 3D'de de geçerlidir

almak:

  1. Genişlet
    x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 = 0
  2. Fiş
    , x = E x + td x
    , y = E y + td y
    (E x + td X ) 2 - 2 (E x + td x ) H + H 2 + (e y + td y ) 2 - 2 (e y + td y ) k + k 2 - r 2 = 0
  3. Patla
    e x 2 + 2e x td x + t 2 d x 2 - 2e x h - 2td x h + h 2 + e y 2 + 2e y td y + t 2 d y 2 - 2e y k - 2td y k + k 2 - r 2 = 0
  4. Grup
    t 2 (d x 2 + d y 2 ) + 2t (e x d x + e y d y - d x h - d y k) + e x 2 + e y 2 - 2e x h - 2e y k + h 2 + k 2 - r 2 = 0
  5. Son olarak,
    t 2 (_d * _d) + 2t (_e * _d - _d * _c) + _e * _e - 2 (_e * _c) + _c * _c - r 2 = 0
    * _d, d vektöridir ve * nokta ürün. *
  6. Ve sonra,
    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

Yani şunu elde ederiz:
t 2 * (d DOT d) + 2t * (f DOT d) + (f DOT f - r 2 ) = 0
Yani kuadratik denklemi çözmek:

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
Düz kopyalayıp yapıştırırsam çalışır gibi görünüyor, ama anlamaya çalışıyorum. (Xh) ^ 2 + (yk) ^ 2 = r ^ 2'de h ve k nedir? K, y üzerinde x üzerinde çizgi / ışının arttığı sabit mi? Ve t nedir? Koda bakarak onun 1 (yani sadece "kaldırıldı") var gibi görünüyor. Bu formüllerin bir adı var mı? Belki de onları Wolfram'da detaylı olarak arayabilirim.
Mizipzor

3
h ve k, kesiştiğiniz dairenin merkezidir. t, çizgi denkleminin parametresidir. Kodda, t1 ve t2 çözümlerdir. t1 ve t2 kesişimin "ışın boyunca ne kadar ilerlediğini" söyler.
bobobobo

1
Tamam anladım. Nokta ürün basitçe üç element (x, y, z) vektörü üzerinden hesaplanır. Kodumu bu algoritmaya geçireceğim.
chmike

21
P = E + t * dNedir t?
Derek 朕 會 功夫

3
Neden olduğundan emin değilim, ancak Impale davası için kod çalışmıyor gibi görünüyor. T1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1 'i doğru koşul olarak eklediğimde yapar, ancak sonlu çizginin bir tarafına da yanlış pozitif verir, daire "sonsuz" kısımdadır. Henüz matematiği anlamıyorum ama kopyala / macunlara dikkat et.
Nicolas Mommaerts

142

Kimse izdüşümü düşünmüyor, ben burada tamamen yolun dışında mıyım?

Vektörü ACüzerine yansıtın AB. Yansıtılan vektör, ADyeni noktayı verir D.
Arasındaki mesafe ise Dve Cdaha küçük olan (veya eşit) Rbiz kesişim.

Bunun gibi:
Image SchoolBoy tarafından


9
Dikkate alınması gereken birçok detay vardır: D AB arasında mı? C çizgiye dik olan mesafe yarıçaptan daha mı büyük? Bunların hepsi vektör büyüklüğünü, yani kare kökü içerir.
ADB

15
İyi fikir, ama o zaman iki kavşak noktasını nasıl hesaplarsın?
Ben

4
@ Örümcek önemli değil. Genel olarak, bu küre çizgisi kavşak probleminin bir çeşidi olduğu için Mizipzor'un stratejisi mükemmel şekilde geçerlidir. CDbir çıkıntıdır, tanımı gereği diktir.

2
Bu eski bir soru, ama bu web sitesinde bu ve ilgili algoritmalarda iyi bir kaynak var: paulbourke.net/geometry/pointlineplane
Andrew


50

Bir nokta (daire merkezi) ve bir çizgi (AB hattı) arasındaki mesafeyi hesaplamak için algoritmayı kullanırdım. Bu daha sonra çizginin daire ile kesişim noktalarını belirlemek için kullanılabilir.

Diyelim ki A, B, C noktaları var. Ax ve Ay, A noktalarının x ve y bileşenleridir. B ve C için aynıdır. Skaler R, daire yarıçapıdır.

Bu algoritma, A, B ve C'nin farklı noktalar olmasını ve R'nin 0 olmamasını gerektirir.

İşte algoritma

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

daireyle kesişmeyen herhangi bir çizgi ve her iki noktası p1 ve p2 dairenin içindeyse. Bu durumda algoritmanız nasıl çalışır?
Prashant

1
T-dt ve t + dt'yi test etmeniz gerekir. T-dt <0 ise, p1 dairenin içinde. T + dt> 1 ise, p2 dairenin içindedir. Elbette LEC <R ise bu doğrudur.
chmike

Teşekkürler. Bu pgm yorumlarını açıklama olarak beğendim çünkü matematiğim paslı olduğu için "nokta çarpımı" kelimelerinin hiçbir faydası yoktu. Ancak t ve dt 0..1 arasında değildir, bu yüzden bunu python'a değiştirirken t'yi LAB ** 2'ye bölünecek şekilde değiştirdim. Anladığım kadarıyla, LAB'ın ilk bölümü, dairenin merkezini AB çizgisine yansıtmak ve LAB'nin ikinci bölümü, onu 0..1 aralığına normalleştirmek. Ayrıca dt'nin LAB'ye bölünmesi gerekir, böylece normalize edilir. Bu nedenle, "(t-dt> = 0.0)" ilk kavşak varsa "(t + dt <= 1.0)" ikinci kavşak varsa. Bu testle çalıştı.
punchcard

2
Çünkü daire ile kesişme noktası "mesafe" t+dtve t-dtçizgi üzerindedir. tdairenin merkezine en yakın çizgideki noktadır. Daire ile kesişme noktaları simetrik bir mesafede bulunmaktadır t. Kavşak noktaları "mesafelerde" t-dtve t+dt. Mesafeden alıntı yaptım çünkü öklid mesafesi değil. Dan euclidian mesafeyi almak için Anereye t=0sen tarafından değeri çarpmak zorunda LAB.
chmike

1
@Matt W "Kesişimin AB çizgi bölümünün bitiş noktalarının dışında olup olmadığını nasıl belirleyebilirim?" T'yi çizgi boyunca bir mesafe ölçüsü olarak düşünün. A noktasıdır t=0. B noktasında t=LAB. Her iki kavşak noktası ( t1=t-tdve t2=t+td) negatif değerlere sahip olduğunda, kavşaklar bölümün dışındadır (A noktasının arkasında, noktanın bölüm tarafından bakıldığında). T1 ve t2 LAB'den büyük olduğunda, onlar da dışarıdadır (bu sefer B noktasının arkasında). Kavşak t1 (veya t2) A ve B arasında sadece t1 (veya t2) 0 ile LAB arasında olduğunda meydana gelir.
Marconius

20

Tamam, sana kod vermeyeceğim, ama bunu etiketlediğin için , Bunun senin için önemli olacağını sanmıyorum. İlk olarak, çizgiye dik bir vektör almalısınız.

Bunun için bilinmeyen bir değişkeniniz olacak y = ax + c ( c bilinmiyor )
Bunu çözmek için, çizgi dairenin merkezinden geçerken değerini hesaplayın.

Yani,
daire merkezinin konumunu çizgi denklemine takın ve çözün c.
Daha sonra orijinal çizginin kesişme noktasını ve normalini hesaplayın.

Bu size dairenin çizgisinde en yakın noktayı verecektir.
Bu nokta ile daire merkezi arasındaki mesafeyi hesaplayın (vektörün büyüklüğünü kullanarak).
Bu dairenin yarıçapından daha azsa - voila, bir kavşak var!


2
Aslında istediğim buydu. Ben teori istiyorum, bir google çizgi daire çarpışma algoritması arama görebildiğim kadarıyla sadece kod ortaya çıkıyor.
Mizipzor

Tamam, denkleminizde c bilinmiyor, ama "a" nedir? Diğer cevaplar bu değişkeni "alfa" ve "t" olarak adlandırmaktadır. Bu sadece doğrusal bir işlev olmasına rağmen (y = kx + m), oldukça temel matematik, bu yüzden aniden biraz paslı hissediyorum. Isnt k da bilinmiyor? Yoksa m = 0 olduğunu varsayabilir ve k'yi çözebilir miyiz? O zaman m (yani, c) çözülmüş k için her zaman sıfır olmaz mıydı?
Mizipzor

1
Oh, özür dilerim - Degrade ve ofset (kartezyen denklem) ile bir çizginin basit denklemini kullanıyorum. Çizgiyi böyle bir denklem olarak sakladığınızı varsaydım - bu durumda k için degradenin negatifini kullanırsınız. Bu şekilde depolanan çizginiz yoksa, k
değerini

1
M'nin sıfır olduğunu varsaymıyoruz; önce degradeyi hesaplıyoruz (böylece çizginin denklemi örnek olarak y = 2x + m gibi görünüyor) ve sonra degradeyi elde ettikten sonra y ve x için dairenin ortasını takarak m için çözebileceğimiz .
a_m0d

1
+1 Harika açıklama! Ama bence bu bir çizgi değil, bir çizgi varsayıyor. Dolayısıyla, bu çizgideki dairenin merkezine en yakın nokta A ve B noktaları arasında olmasaydı, yine de sayılırdı.
Hassan

12

Başka bir yöntem, üçgen ABC alan formülünü kullanır. Kavşak testi, projeksiyon yönteminden daha basit ve daha verimlidir, ancak kavşak noktasının koordinatlarını bulmak daha fazla çalışma gerektirir. En azından gerekli olan noktaya kadar ertelenecek.

Üçgen alanını hesaplamak için formül: alan = bh / 2

burada b taban uzunluğu ve h yüksekliktir. AB segmentini taban olarak seçtik, böylece h, daire merkezinden C'ye, hatta en kısa mesafedir.

Üçgen alanı bir vektör nokta ürünü ile de hesaplanabildiğinden, h belirleyebiliriz.

// 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 )
{
    ...
}        

GÜNCELLEME 1:

1 / LAB hakkında iyi bir yaklaşım elde etmek için burada açıklanan hızlı ters kare kök hesaplamasını kullanarak kodu optimize edebilirsiniz .

Kavşak noktasını hesaplamak o kadar da zor değil. İşte gidiyor

// 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 ise, AB çizgisi daireye teğettir ve dt = 0 ve E = F değeridir. Nokta koordinatları E ve F çizgilerindedir.

Uygulamanızda bu gerçekleşebiliyorsa A'nın B'den farklı olduğunu ve segment uzunluğunun boş olmadığını kontrol etmelisiniz.


2
Bu yöntemdeki sadeliği seviyorum. Belki çevreleyen kodun bazılarını gerçek çarpışma noktasına ihtiyaç duymayacak şekilde uyarlayabilirim, aralarında hesaplanan nokta yerine A veya B kullanırsam ne olacağını göreceğim.
Mizipzor

1
t = Dx * (Cx-Ax) + Dy * (Cy-Axe) t = Dx * (Cx-Axe) + Dy * (Cy-Ay)
Stonetip 21:02

Bu doğru. Bu konuya işaret ettiğiniz için teşekkür ederiz. Gönderiyi düzelttim.
chmike

yeni düzenlenmiş - ilk satır , nokta ürün değil, çapraz ürün kullanarak üçgen alanını hesaplar . burada kodla doğrulandı: stackoverflow.com/questions/2533011/…
ericsoco

4
Ayrıca, bu cevabın ilk yarısı bir çizgi segmentiyle değil, bir soru ile kesişme açısından test edilir (soruda sorulduğu gibi).
ericsoco

8

Çemberin merkez noktasını çizgiye yansıtarak kavşağı test etmek için küçük bir senaryo yazdım.

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/

Çarpışmayı segmentle kontrol etmeniz gerekiyorsa, daire merkezinin başlangıç ​​ve bitiş noktalarına olan mesafesini de dikkate almanız gerekir.

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

Bulduğum bu çözümü takip etmek biraz daha kolay görünüyordu.

çıkarak:

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

Eğim-kesme noktası formunda çizginin denklemini çözerdim. Bununla birlikte, zor denklemlerle cbir nokta olarak uğraşmak istemedim , bu yüzden dairenin yerinde olması için koordinat sistemini kaydırdım.0,0

p3 = p1 - c
p4 = p2 - c

Bu arada, her ne zaman puanları birbirinden çıkarsam, x'leri çıkarıyorum ve sonra y' ları çıkarıyorum ve yeni bir noktaya koyuyorum, birisinin bilmemesi durumunda.

Her neyse, şimdi doğrultusunda denklemi için çözmek p3ve 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)

Tamam. Şimdi bu denklemleri eşitlemeliyim. Önce dairenin denklemini çözmem gerekiyorx

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

Sonra onları eşit olarak ayarladım:

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

Ve ikinci dereceden denklemi ( 0 = ax^2 + bx + c) çözmek :

(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

Şimdi benim var a, bve c.

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

Bu yüzden bunu ikinci dereceden formüle koydum:

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

Ve değerlerle değiştirin, ardından mümkün olduğunca basitleştirin:

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

Bu neredeyse basitleştireceği kadarıyla. Son olarak, ± ile denklemlere ayırın:

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

Sonra basitçe içine bu denklemlerin hem sonucunu fiş xin mx + b. Açıklık için, bunu nasıl kullanacağımı göstermek için bazı JavaScript kodu yazdım:

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

Umarım bu yardımcı olur!

Not: Herhangi bir hata bulursa veya herhangi bir öneriniz varsa, lütfen yorum yapın. Ben çok yeniyim ve tüm yardım / önerilerinizi bekliyoruz.


Mümkünse, akışı hızlı bir şekilde kavrayabilmemiz için bazı örnek değerlerle de gönderin.
Prabindh

İle underRadical')' ekstra
byJeevan

4

Vektör AC üzerine vektör AC yansıtarak daire merkezine en yakın sonsuz bir çizgi üzerinde bir nokta bulabilirsiniz. Bu nokta ile daire merkezi arasındaki mesafeyi hesaplayın. R'den büyükse, kesişme yoktur. Mesafe R'ye eşitse, çizgi dairenin tanjantıdır ve daire merkezine en yakın nokta aslında kesişim noktasıdır. Mesafe R'den daha azsa, 2 kavşak noktası vardır. Daire merkezine en yakın noktadan aynı mesafede uzanırlar. Bu mesafe Pisagor teoremi kullanılarak kolayca hesaplanabilir. İşte sözde kod algoritması:

{
dX = bX - aX;
dY = bY - aY;
if ((dX == 0) && (dY == 0))
  {
  // A and B are the same points, no way to calculate intersection
  return;
  }

dl = (dX * dX + dY * dY);
t = ((cX - aX) * dX + (cY - aY) * dY) / dl;

// point on a line nearest to circle center
nearestX = aX + t * dX;
nearestY = aY + t * dY;

dist = point_dist(nearestX, nearestY, cX, cY);

if (dist == R)
  {
  // line segment touches circle; one intersection point
  iX = nearestX;
  iY = nearestY;

  if (t < 0 || t > 1)
    {
    // intersection point is not actually within line segment
    }
  }
else if (dist < R)
  {
  // two possible intersection points

  dt = sqrt(R * R - dist * dist) / sqrt(dl);

  // intersection point nearest to A
  t1 = t - dt;
  i1X = aX + t1 * dX;
  i1Y = aY + t1 * dY;
  if (t1 < 0 || t1 > 1)
    {
    // intersection point is not actually within line segment
    }

  // intersection point farthest from A
  t2 = t + dt;
  i2X = aX + t2 * dX;
  i2Y = aY + t2 * dY;
  if (t2 < 0 || t2 > 1)
    {
    // intersection point is not actually within line segment
    }
  }
else
  {
  // no intersection
  }
}

EDIT: Bulunan kesişme noktalarının gerçekten çizgi segmenti içinde olup olmadığını kontrol etmek için kod eklendi.


Bir çizgi segmenti hakkında konuştuğumuz için bir vakayı kaçırdınız: segment daire içinde sona erdiğinde.
ADB

@ADB aslında algoritmam sadece sonsuz satırlar için çalışıyor, satır segmentleri için değil. Çizgi parçalarıyla işlemediği birçok durum vardır.
Juozas Kontvainis

Asıl soru, daire çizgisi kesişimi değil, çizgi kesimleriyle ilgiliydi, bu çok daha kolay bir sorundur.
msumme

4

Garip bir şekilde cevap verebilirim ama yorum yapamam ... Multitaskpro'un dairenin merkezini başlangıç ​​noktasına düşürmek için her şeyi kaydırma yaklaşımını beğendim. Maalesef kodunda iki sorun var. İlk önce karenin altındaki kısımda çift gücü çıkarmanız gerekir. Öyleyse değil:

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

fakat:

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

Son koordinatlarda çözümü geri kaydırmayı unutur. Öyleyse değil:

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

fakat:

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

Tüm fonksiyon daha sonra:

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
öneriler: İlk olarak, çizgi segmentinin dikey olduğu (yani sonsuz eğime sahip olduğu) durumu ele almasını sağlayın. İkincisi, sadece orijinal çizgi segmentinin aralığına giren noktaları geri getirin - bu noktalar çizgi segmentinin dışında olsa bile sonsuz çizgiye düşen tüm noktaları mutlu bir şekilde döndürdüğüne inanıyorum.
Gino

Not: Bu, çizgiler için iyi çalışır, ancak çizgi segmentleri için çalışmaz.
Mike

3

Burada biraz matematik lazım:

A = (Xa, Ya), B = (Xb, Yb) ve C = (Xc, Yc) diyelim. A ile B arasındaki çizgi üzerindeki herhangi bir noktanın koordinatları vardır (alfa * Xa + (1-alfa) Xb, alfa Ya + (1-alfa) * Yb) = P

P noktasının R ila C mesafesi varsa, daire üzerinde olmalıdır. İstediğinizi çözmek

distance(P, C) = R

yani

(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

alfa için çözmek üzere ABC denklemini bu denkleme uygularsanız ve alfa için çözümleri kullanarak P koordinatlarını hesaplarsanız, varsa kesişim noktalarını elde edersiniz.


3

Kürenin merkezi arasındaki mesafeyi bulursanız (3D olduğundan, küreyi değil küreyi kastettiğinizi varsayalım) ve çizgi, bu mesafenin hile yapacak yarıçaptan daha az olup olmadığını kontrol edin.

Çarpışma noktası, çizgi ile küre arasındaki en yakın noktadır (küre ile çizgi arasındaki mesafeyi hesapladığınızda hesaplanacaktır).

Bir nokta ile bir çizgi arasındaki mesafe:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html


1
2D değil, 2D değil; Dediğiniz gibi, bu gerçekten önemli değil
Martijn

Ben matematikçi değilim, bu yüzden genel bir yaklaşımı özetlemek ve belirli matematikleri anlamak için başkalarına bırakmak daha iyi olacağını düşündüm (oldukça önemsiz görünmeme rağmen)
Martin

2
+1 ile güçlü bir upvote. (başka bir siteye bağlı olsa da, pbourke sitesi kafa karıştırıcı görünüyor) Şimdiye kadar diğer tüm cevaplar aşırı karmaşık. "Bu nokta aynı zamanda çizgi üzerindeki kesişme noktasıdır" yorumunuz yanlış olsa da, hesaplama sürecinde oluşturulmuş bir nokta yoktur.
Jason S


En yakın nokta hakkında biraz daha iyi açıkladım ve pbourke yerine mathworld ile bağlantı kurdum :)
Martin

3

İşte Javascript'te bir uygulama. Benim yaklaşımım önce çizgi parçasını sonsuz bir çizgiye dönüştürmek ve sonra kesişim noktalarını bulmaktır. Oradan bulduğum noktaların çizgi segmentinde olup olmadığını kontrol ediyorum. Kod iyi belgelenmiştir, takip edebilmeniz gerekir.

Kodu bu canlı demoda deneyebilirsiniz . Kod algoritma depomdan alınmıştır .

resim açıklamasını buraya girin

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

Bu direkte daire çarpışması, daire merkezi ile daire merkezinden noktaya (Ipoint) nokta arasındaki mesafeyi kontrol ederek daire merkezinden çizgiye doğru normal N (Resim 2) arasındaki kesişim noktasını kontrol ederek kontrol edilecektir.

( https://i.stack.imgur.com/3o6do.png )Resim 1. E ve D vektörlerini bulma

Resim 1'de bir daire ve bir çizgi gösterilmektedir, vektör Bir noktadan satıra başlangıç ​​noktası, vektör B noktadan satıra bitiş noktası, vektör C noktasından daire merkezine. Şimdi E vektörünü (çizgi başlangıç ​​noktasından daire merkezine) ve vektör D'yi (çizgi başlangıç ​​noktasından çizgi bitiş noktasına) bulmalıyız, bu hesaplama resim 1'de gösterilmektedir.

( https://i.stack.imgur.com/7098a.png )Resim 2. X bulma

Resim 2'de E vektörünün Vektör D üzerine vektör E ve birim vektör D'nin "nokta ürünü" ile yansıtıldığını görebiliriz, nokta ürününün sonucu, çizgi başlangıç ​​noktası ile kesişme noktası (Ipoint) arasındaki mesafeyi temsil eden skaler Xp'dir. vektör N ve vektör D Sonraki vektör X birim vektör D ve skaler Xp çarpılarak bulunur.

Şimdi vektör Z'yi (Ipoint'e vektör) bulmalıyız, bu basit vektör A (çizgi üzerinde başlangıç ​​noktası) ve vektör X eklenmesi kolaydır. Sonra kontrol etmemiz gereken özel durumlar ile ilgilenmeliyiz, eğer çizgi segmentindeki Ipoint onun sol ya da sağ olduğunu öğrenmemiz gerekmiyor, daireye en yakın noktayı belirlemek için en yakın vektörü kullanacağız.

( https://i.stack.imgur.com/p9WIr.png )Resim 3. En yakın noktayı bulma

Projeksiyon Xp negatif olduğunda Ipoint çizgi segmentinin solundadır, en yakın vektör çizgi başlangıç ​​noktasının vektörüne eşittir, Xp projeksiyonu D vektöründen daha büyük olduğunda Ipoint çizgi segmentinin sağındadır ve en yakın vektör çizgi uç vektörüne eşittir herhangi bir başka durumda en yakın vektör Z vektörüne eşittir.

Şimdi en yakın vektörüne sahip olduğumuzda, daire merkezinden Ipoint'e (dist vektör) vektör bulmamız gerekiyor, basit olanı, sadece en yakın vektörü merkez vektörününden çıkartmamız gerekiyor. Daha sonra sadece vektör dağılım büyüklüğünün daha küçük olup olmadığını kontrol edin, eğer çarpışma yoksa, çarpışıyorlarsa daire yarıçapından.

( https://i.stack.imgur.com/QJ63q.png )Resim 4. Çarpışma kontrolü

Sonunda, çarpışmayı çözmek için bazı değerler döndürebiliriz, en kolay yol çarpışmanın üst üste gelmesini (vektör dist büyüklüğünden yarıçapı çıkarmak) ve çarpışma eksenini, onun D vektörünü döndürmektir.


2

Çizginin koordinatları Ax, Ay ve Bx, By ve daire merkezi Cx ise Cy ise çizgi formülleri şunlardır:

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

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

burada 0 <= t <= 1

ve daire

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

Eğer çizginin x ve y formüllerini daire formülüne koyarsanız, ikinci bir t denklemi elde edersiniz ve çözümleri kesişim noktalarıdır (eğer varsa). Eğer 0'dan küçük veya 1'den büyük olursa, bu bir çözüm değildir, ancak çizginin dairenin yönüne 'işaret ettiğini' gösterir.


2

Sadece bu iş parçacığına bir ek ... Aşağıda pahlevan tarafından yayınlanan kodun bir sürümü, ancak C # / XNA için ve biraz tidied:

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


2

resim açıklamasını buraya girin

' 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

Bu işlevi iOS için verilen cevabın ardından oluşturdum 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

Başka bir c # (kısmi Circle sınıfı). Test edildi ve bir cazibe gibi çalışıyor.

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

Gereklidir:

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

İşte JavaScript (gerekli tüm matematik ve canlı illüstrasyon ile) iyi bir çözüm https://bl.ocks.org/milkbread/11000965

Bu is_onçözümdeki işlevde değişiklik yapılması gerekmesine rağmen :

function is_on(a, b, c) {
    return Math.abs(distance(a,c) + distance(c,b) - distance(a,b))<0.000001;
}


2

Daire gerçekten kötü bir adam :) Yani iyi bir yol, gerçek çemberden kaçınmaktır, eğer yapabilirseniz. Oyunlar için çarpışma kontrolü yapıyorsanız, bazı basitleştirmelerle gidebilir ve sadece 3 nokta ürününüz ve birkaç karşılaştırmanız olabilir.

Buna "şişman nokta" veya "ince daire" diyorum. bir segmente paralel bir yönde sıfır yarıçaplı bir elips türüdür. ancak bir segmente dik yönde tam yarıçap

İlk olarak, aşırı veri önlemek için yeniden adlandırma ve geçiş koordinat sistemi düşünün:

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

İkincisi, hvec2f'deki h indeksi, vektöre göre nokta () / det () gibi yatay işlemleri desteklemelidir. Bu, karıştırma / haddeleme / hsub'lamadan kaçınmak için bileşenlerinin ayrı bir xmm kayıtlarına yerleştirileceği anlamına gelir. Ve işte başlıyoruz, 2D oyun için en basit çarpışma tespitinin en performanslı versiyonu ile:

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

Daha fazla optimize edebileceğinizden şüpheliyim. Milyonlarca milyon iterasyon basamağını işlemek için sinir ağı ile çalışan araba yarışı çarpışma tespiti için kullanıyorum.


Çizgi segmenti daireyle kesişir, ancak merkez noktasını geçmeyecek kadar hafifse, bu işlev doğru olması gerektiğinde yanlış döndürmez mi? T değeri 0..1 aralığının dışında olabilir.
Chris

1

Bu Java İşlevi bir DVec2 Nesnesi döndürür. Dairenin merkezi, dairenin yarıçapı ve bir Çizgi için bir DVec2 gerekir .

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'un önerdiği fikri (projeksiyon kullanarak) izleyerek TypeScript'teki çözümümü burada bulabilirsiniz:

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

Bu konu açık olduğundan bu yana bir süre geçtiğini biliyorum. Chmike tarafından verilen ve Aqib Mumtaz tarafından geliştirilen cevaptan. İyi bir cevap veriyorlar ancak Aqib'in söylediği gibi sadece sonsuz bir çizgi için çalışıyorlar. Bu yüzden çizgi segmentinin daireye dokunup dokunmadığını bilmek için bazı karşılaştırmalar ekliyorum, Python'a yazıyorum.

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

İşte golang'da yazılmış bir çözüm. Yöntem, burada yayınlanan diğer cevaplara benzer, ancak aynı değildir. Uygulaması kolaydır ve test edilmiştir. İşte adımlar:

  1. Daire başlangıç ​​noktasında olacak şekilde koordinatları çevirin.
  2. Çizgi parçasını, hem x hem de y koordinatları için parametrelenmiş t işlevleri olarak ifade edin. T 0 ise, işlevin değerleri segmentin bir bitiş noktasıdır ve t 1 ise işlevin değerleri diğer bitiş noktasıdır.
  3. Mümkünse, x, y üreten t değerlerinin sınırlanmasından kaynaklanan ikinci dereceden denklemi, başlangıç ​​noktasından dairenin yarıçapına eşit mesafelerle koordine edin.
  4. T'nin <0 veya> 1 olduğu çözeltileri atın (açık segment için <= 0 veya> = 1). Bu noktalar segmentte yer almıyor.
  5. Orijinal koordinatlara geri çevirin.

Kuadratik için A, B ve C değerleri burada türetilir, burada (n-et) ve (m-dt) sırasıyla satırın x ve y koordinatları için denklemlerdir. r dairenin yarıçapıdı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

Bu nedenle A = ee + dd, B = - 2 (en + dm) ve C = nn + mm - rr.

İşte fonksiyon için golang kodu:

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
}

Çözüm noktalarının çizgi segmenti içinde ve daire üzerinde olduğunu doğrulayan bu fonksiyonla test ettim. Bir test segmenti oluşturur ve verilen çemberin etrafında süpürür:

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

İşte testin çıktısı:

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

Son olarak, yöntem bir noktada başlayan, diğerinden geçen ve sonsuza kadar uzanan bir ışın durumunda, sadece t> 0 veya t <1 olup olmadığını test ederek her ikisini birden değil, kolayca genişletilebilir.


0

Sadece buna ihtiyacım vardı, bu yüzden bu çözümü buldum. Dil maxscript'tir, ancak kolayca başka bir dile çevrilmelidir. sideA, sideB ve CircleRadius skalerdir, değişkenlerin geri kalanı [x, y, z] olarak işaretlenir. XY düzleminde çözmek için z = 0 olduğunu varsayıyorum

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 tabanlı python çözümü

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

Çizgiden X eşit aralıklı noktaları alabilir ve daire içinde varsa, bir çarpışma olur

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.