Algorytm wykrywania kolizji między liniami i okręgami?


195

Mam linię od A do B i okrąg ustawiony w C o promieniu R.

Wizerunek

Jakiego algorytmu należy użyć, aby sprawdzić, czy linia przecina koło? I na jakiej współrzędnej wzdłuż krawędzi okręgów się pojawiło?


4
Hmm Jedno pytanie: czy mówisz o linii nieskończonej przez A i B, czy o odcinku linii skończonej od A do B?
Jason S

2
W tym przypadku jest to segment linii skończonej. Czy „linia” nazywana jest czymś innym w zależności od tego, czy jest skończona czy nieskończona?
Mizipzor

1
Czy istnieje wymóg wydajności? Czy powinna to być szybka metoda?
chmike,

W tym momencie nie, wszystkie algorytmy tutaj, które próbowałem, nie spowalniają zauważalnie aplikacji.
Mizipzor,

13
@Mizipzor tak, nazywane są czymś innym: segmenty linii . Jeśli po prostu powiesz „linia”, oznacza to nieskończoną.
MestreLion,

Odpowiedzi:


200

Nabierający

  1. E jest punktem początkowym promienia,
  2. L jest punktem końcowym promienia,
  3. C jest środkiem kuli, na której testujesz
  4. r jest promieniem tej kuli

Oblicz:
d = L - E (wektor kierunku promienia, od początku do końca)
f = E - C (wektor od kuli środkowej do początku promienia)

Następnie znajduje się przecięcie przez ...
Podłączanie:
P = E + t * d
Jest to równanie parametryczne:
P x = E x + td x
P y = E y + td y
do
(x - h) 2 + (y - k) 2 = r 2
(h, k) = środek okręgu.

Uwaga: Uprościliśmy tutaj problem do 2D, otrzymane rozwiązanie dotyczy również 3D

uzyskać:

  1. Rozwiń
    x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 = 0
  2. Wtyczka
    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. Explode
    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. Grupa
    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. Wreszcie,
    t 2 (_d * _d) + 2t (_e * _d - _d * _c) + _e * _e - 2 (_e * _c) + _c * _c - r 2 = 0
    * Gdzie _d jest wektorem d, a * jest iloczyn skalarny. *
  6. A następnie
    t 2 (_d * _d) + 2t (_d * (_e - _c)) + (_e - _c) * (_e - _c) - r 2 = 0
  7. Niech _f = _e - _c
    t 2 (_d * _d) + 2t (_d * _f) + _f * _f - r 2 = 0

Otrzymujemy więc:
t 2 * (d DOT d) + 2t * (f DOT d) + (f DOT f - r 2 ) = 0
Tak więc rozwiązanie równania kwadratowego:

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
Wydaje się działać, jeśli wykonam proste kopiowanie i wklejanie, ale chcę to zrozumieć. W (xh) ^ 2 + (yk) ^ 2 = r ^ 2 co to jest h i k? Czy k to stała, o którą linia / promień wzrasta na y powyżej x? A co to jest t Patrząc na kod, wygląda na to, że przyjąłeś jego 1 (więc po prostu „usunięto”). Czy te formuły mają imię czy coś? Może uda mi się je szczegółowo sprawdzić na Wolfram.
Mizipzor

3
h i k są środkiem okręgu, z którym się przecinasz. t jest parametrem równania liniowego. W kodzie rozwiązaniami są t1 i t2. t1 i t2 mówią „jak daleko wzdłuż promienia” miało miejsce skrzyżowanie.
bobobobo

1
Ok, rozumiem. Iloczyn skalarny jest po prostu obliczany na podstawie trzech elementów wektorów (x, y, z). Przełączę kod na ten algorytm.
chmike,

21
P = E + t * dCo to jest t?
Derek 朕 會 功夫

3
Nie jestem pewien, dlaczego, ale wydaje się, że kod nie działa w przypadku Impale. Dzieje się tak, gdy dodam, jeśli t1 <= 0 i& t1> = -1 i& t2 <= 0 && t2> = -1 jako warunek prawdziwy, ale wtedy daje również fałszywie dodatni po jednej stronie linii skończonej, gdy okrąg jest w części „nieskończonej”. Nie rozumiem jeszcze matematyki, ale uważajcie na kopie / pastery.
Nicolas Mommaerts,

141

Wydaje się, że nikt nie bierze pod uwagę projekcji, czy jestem całkowicie nie na miejscu?

Rzuć wektor ACna AB. Rzutowany wektor ADdaje nowy punkt D.
Jeśli odległość między Di Cjest mniejsza niż (lub równa) R, mamy skrzyżowanie.

Lubię to:
Zdjęcie SchoolBoy


9
Należy wziąć pod uwagę wiele szczegółów: czy D leży pomiędzy AB? Czy C jest prostopadła odległość do linii większa niż promień? Wszystkie z nich obejmują wielkość wektora, tj. Pierwiastek kwadratowy.
ADB

15
Dobry pomysł, ale jak obliczyć dwa punkty przecięcia?
Ben

4
@Spider to nie ma znaczenia. Ogólnie rzecz biorąc, ponieważ jest to wariant problemu przecięcia linii kuli, strategia Mizipzora jest całkowicie poprawna. CDjest rzutem, z definicji jest prostopadły.

2
To stare pytanie, ale na tej stronie znajduje się dobry zasób dotyczący tego i powiązanych algorytmów: paulbourke.net/geometry/pointlineplane
Andrew

1
Wielki wyjaśnienie tej odpowiedzi: scratchapixel.com/lessons/3d-basic-rendering/...
ShawnFeatherly

50

Użyłbym algorytmu do obliczenia odległości między punktem (środek okręgu) a linią (linia AB). Można to następnie wykorzystać do ustalenia punktów przecięcia linii z okręgiem.

Powiedzmy, że mamy punkty A, B, C. Ax i Ay są składowymi xiy punktów A. To samo dla B i C. Skalarą R jest promień okręgu.

Algorytm ten wymaga, aby A, B i C były odrębnymi punktami, a R nie był równy 0.

Oto algorytm

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

jeśli jakakolwiek linia, która nie przecina koła i jego zarówno punktu p1, jak i p2, znajduje się wewnątrz okręgu. w takim przypadku jak działa twój algorytm?
Prashant,

1
Musisz przetestować t-dt i t + dt. Jeśli t-dt <0, to p1 znajduje się wewnątrz okręgu. Jeśli t + dt> 1, to p2 znajduje się wewnątrz okręgu. Jest to prawdą, jeśli LEC <R oczywiście.
chmike

Dzięki. Spodobały mi się te komentarze PGM jako wyjaśnienie, ponieważ nie użyto słów „produkt kropkowy”, ponieważ moja matematyka jest zardzewiała. Jednak t i dt nie są między 0..1, więc zmieniając to na python zmieniłem t, aby podzielić LAB ** 2. Rozumiem, że pierwszym podziałem według LAB jest rzutowanie środka okręgu na linię AB, a drugim podziałem przez LAB jest znormalizowanie go do zakresu 0..1. Również dt musi być podzielony przez LAB, więc jest również znormalizowany. Zatem „if (t-dt> = 0,0)„ istnieje pierwsze skrzyżowanie ”if (t + dt <= 1,0)„ istnieje drugie skrzyżowanie. Działa to z testowaniem.
karta pocztowa

2
Ponieważ punkt przecięcia z okręgiem znajduje się w „odległości” t+dti t-dtna linii. tto punkt na linii najbliżej środka koła. Punkty przecięcia z okręgiem znajdują się w symetrycznej odległości od t. Punkty przecięcia znajdują się w „odległościach” t-dti t+dt. Podałem odległość, ponieważ nie jest to odległość euklidesowa. Aby uzyskać odległość euklidesową od Amiejsca t=0, należy pomnożyć wartość przez LAB.
chmike

1
@Matt W Masz na myśli „Jak ustalić, czy przecięcie występuje poza punktami końcowymi odcinka AB”? Pomyśl o t jako o miarie odległości wzdłuż linii. Punkt A znajduje się w t=0. Punkt B w t=LAB. Gdy oba punkty przecięcia ( t1=t-tdi t2=t+td) mają wartości ujemne, przecięcia znajdują się poza sekcją (za punktem A, patrząc od strony sekcji punktu). Gdy t1 i t2 są większe niż LAB, to również znajdują się na zewnątrz (tym razem za punktem B). Przecięcie t1 (lub t2) występuje między A i B tylko wtedy, gdy t1 (lub t2) jest między 0 a LAB.
Marconius,

20

Dobra, nie dam ci kodu, ale odkąd to oznaczyłeś , Nie sądzę, żeby to miało dla ciebie znaczenie. Najpierw musisz uzyskać wektor prostopadły do ​​linii.

Będziesz miał nieznaną zmienną w y = ax + c ( c będzie nieznany )
Aby rozwiązać ten problem, oblicz jej wartość, gdy linia przechodzi przez środek okręgu.

To znaczy,
podłącz lokalizację środka okręgu do równania linii i rozwiąż c.
Następnie oblicz punkt przecięcia oryginalnej linii i jej normalną.

To da ci najbliższy punkt na linii do koła.
Obliczyć odległość między tym punktem a środkiem okręgu (używając wielkości wektora).
Jeśli jest to mniej niż promień koła - voila, mamy skrzyżowanie!


2
Właśnie tego chciałem. Chcę teorii, wyszukiwarka Google algorytmu kolizji linia-koło pokazuje tylko kod, o ile widzę.
Mizipzor,

Ok, c jest nieznane w twoim równaniu, ale czym jest „a”? Inne odpowiedzi wydają się odnosić do tej zmiennej jako „alfa” i „t”. Chociaż jest to tylko funkcja liniowa (y = kx + m), dość podstawowa matematyka, więc nagle czuję się trochę zardzewiały. Czy k też nie jest nieznane? Czy masz na myśli, że możemy założyć m = 0 i rozwiązać k? Czy wtedy m (to znaczy c) zawsze będzie zero dla naszego rozwiązanego k?
Mizipzor

1
Och, przepraszam - używam prostego równania linii z gradientem i przesunięciem (równanie kartezjańskie). Założyłem, że przechowujesz linię jako takie równanie - w takim przypadku używasz ujemnego gradientu dla k. Jeśli nie masz takiej linii zapisanej, możesz obliczyć k jako (y2-y1) / (x2-x1)
a_m0d

1
Nie zakładamy, że m wynosi zero; najpierw obliczamy gradient (tak, aby równanie linii wyglądało jak y = 2x + m jako przykład), a następnie, gdy uzyskamy gradient, możemy rozwiązać dla m, wstawiając środek okręgu dla y i x .
a_m0d

1
+1 Niesamowite wyjaśnienie! Ale myślę, że zakłada to linię, a nie odcinek linii. Jeśli więc najbliższy punkt na tej linii do środka okręgu nie znajdował się między punktami A i B, nadal byłby liczony.
Hassan

12

Inna metoda wykorzystuje wzór trójkąta ABC. Test skrzyżowania jest prostszy i wydajniejszy niż metoda rzutowania, ale znalezienie współrzędnych punktu przecięcia wymaga więcej pracy. Przynajmniej opóźni się do wymaganego poziomu.

Wzór na obliczenie pola trójkąta jest następujący: area = bh / 2

gdzie b jest długością podstawy, a h jest wysokością. Wybraliśmy segment AB jako podstawę, aby h to najkrótsza odległość od C, środka okręgu, do linii.

Ponieważ pole trójkąta można również obliczyć za pomocą iloczynu wektorowego, możemy wyznaczyć 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 )
{
    ...
}        

AKTUALIZACJA 1:

Możesz zoptymalizować kod, korzystając z szybkiego odwrotnego obliczenia pierwiastka kwadratowego opisanego tutaj, aby uzyskać dobre przybliżenie 1 / LAB.

Obliczenie punktu przecięcia nie jest takie trudne. Oto jest

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

Jeśli h = R, to linia AB jest styczna do okręgu, a wartości dt = 0 i E = F. Współrzędne punktu to współrzędne E i F.

Powinieneś sprawdzić, czy A różni się od B, a długość segmentu nie jest zerowa, jeśli może się to zdarzyć w twojej aplikacji.


2
Podoba mi się prostota tej metody. Może mógłbym dostosować część otaczającego kodu, aby nie potrzebował samego punktu kolizji, ale nie zobaczę, co się stanie, jeśli użyję A lub B zamiast obliczonego punktu pomiędzy.
Mizipzor,

1
t = Dx * (Cx-Axe) + Dy * (Cy-Axe) powinien brzmieć t = Dx * (Cx-Axe) + Dy * (Cy-Ay)
Stonetip

To prawda. Dziękujemy za zwrócenie na to uwagi. Poprawiłem to w poście.
chmike

właśnie edytowany - najpierw oblicza linii pola trójkąta przy użyciu przekrój produktu, a nie produktem kropka. zweryfikowany za pomocą kodu tutaj: stackoverflow.com/questions/2533011/…
ericsoco

4
zauważ również, że pierwsza połowa tej odpowiedzi sprawdza przecięcie z linią, a nie z segmentem linii (zgodnie z pytaniem).
ericsoco

8

Napisałem mały skrypt, aby przetestować przecięcie, rzutując punkt środkowy okręgu na linię.

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/

Jeśli chcesz sprawdzić kolizję z segmentem, musisz również wziąć pod uwagę odległość środka okręgu od punktu początkowego i końcowego.

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

Rozwiązanie, które znalazłem, wydawało się trochę łatwiejsze do naśladowania niż niektóre inne.

Nabierający:

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

Rozwiązałbym równanie linii w formie punktu przecięcia. Nie chciałem jednak zajmować się trudnymi równaniami cjako punktem, więc po prostu przesunąłem układ współrzędnych, aby okrąg znalazł się w0,0

p3 = p1 - c
p4 = p2 - c

Nawiasem mówiąc, kiedy tylko odjąć punkty z siebie jestem odjęcie x„s, a następnie odjęcie y” s, i wprowadzenie ich do nowego punktu, na wszelki wypadek, gdyby ktoś nie wiedział.

W każdym razie, ja teraz rozwiązania dla równania linii z p3i 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)

Dobrze. Teraz muszę wyrównać te równania. Najpierw muszę rozwiązać równanie kołax

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

Potem ustawiam je na równe:

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

Rozwiąż równanie kwadratowe ( 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

Teraz mam swoje a, bi c.

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

Włożyłem to do kwadratu:

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

Zastąp wartości, a następnie maksymalnie uprość:

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

Jest to prawie tak daleko, jak to uprości. Na koniec oddzielmy od równań ±:

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

Następnie wystarczy podłączyć wynik obu tych równań karne xin mx + b. Dla jasności napisałem kod JavaScript, aby pokazać, jak tego uż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((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];
    }
}

Mam nadzieję, że to pomoże!

PS Jeśli ktoś znajdzie jakieś błędy lub ma jakieś sugestie, prosimy o komentarz. Jestem bardzo nowy i witam wszelką pomoc / sugestie.


Jeśli to możliwe, opublikuj również niektóre próbki wartości, abyśmy mogli szybko uchwycić przepływ.
Prabindh

Z underRadicaldodatkowym „)”
autor: Jeevan,

4

Możesz znaleźć punkt na nieskończonej linii najbliżej środka okręgu, rzutując wektor AC na wektor AB. Oblicz odległość między tym punktem a środkiem okręgu. Jeśli jest większa niż R, nie ma przecięcia. Jeśli odległość jest równa R, linia jest styczną do okręgu, a punkt najbliższy środka okręgu jest w rzeczywistości punktem przecięcia. Jeśli odległość jest mniejsza niż R, wówczas istnieją 2 punkty przecięcia. Leżą w tej samej odległości od punktu najbliższego środka okręgu. Odległość tę można łatwo obliczyć za pomocą twierdzenia Pitagorasa. Oto algorytm w pseudokodzie:

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

EDYCJA: dodano kod, aby sprawdzić, czy znalezione punkty przecięcia faktycznie znajdują się w segmencie linii.


Pominąłeś jeden przypadek, ponieważ mówimy o segmencie linii: kiedy segment kończy się w kole.
ADB

@ ADB tak naprawdę mój algorytm działa tylko dla nieskończonych linii, a nie dla segmentów linii. Istnieje wiele przypadków, w których nie obsługuje segmentów linii.
Juozas Kontvainis

Pierwotne pytanie dotyczyło segmentów linii, a nie przecięcia okręgu z linią, co jest znacznie łatwiejszym problemem.
msumme

4

Dziwnie mogę odpowiadać, ale nie komentować ... Podobało mi się podejście Multitaskpro polegające na przesunięciu wszystkiego, aby środek koła spadł na początek. Niestety w jego kodzie są dwa problemy. Najpierw w części pod pierwiastkiem kwadratowym musisz usunąć podwójną moc. Zatem nie:

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

ale:

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

W końcowych współrzędnych zapomina cofnąć rozwiązanie z powrotem. Zatem nie:

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

ale:

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

Cała funkcja staje się wtedy:

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
sugestie: Po pierwsze, niech zajmie się przypadkiem, w którym segment linii jest pionowy (tzn. ma nieskończone nachylenie). Po drugie, niech zwraca tylko te punkty, które faktycznie mieszczą się w zakresie oryginalnego segmentu linii - uważam, że z radością zwraca wszystkie punkty, które spadają na nieskończoną linię, nawet jeśli te punkty leżą poza segmentem linii.
Gino

Uwaga: Działa to dobrze w przypadku linii, ale nie działa w przypadku segmentów linii.
Mike

3

Potrzebujesz trochę matematyki tutaj:

Załóżmy, że A = (Xa, Ya), B = (Xb, Yb) i C = (Xc, Yc). Dowolny punkt na linii od A do B ma współrzędne (alfa * Xa + (1-alfa) Xb, alfa Ya + (1-alfa) * Yb) = P

Jeśli punkt P ma odległość R do C, musi znajdować się na okręgu. Co chcesz rozwiązać

distance(P, C) = R

to jest

(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

jeśli zastosujesz formułę ABC do tego równania, aby rozwiązać je dla alfa, i obliczyć współrzędne P za pomocą rozwiązania (ów) dla alfa, otrzymasz punkty przecięcia, jeśli takie istnieją.


3

Jeśli znajdziesz odległość między środkiem kuli (ponieważ jest to 3D, zakładam, że masz na myśli kulę, a nie okrąg) i linią, sprawdź, czy odległość ta jest mniejsza niż promień, który załatwi sprawę.

Punkt kolizji jest oczywiście najbliższym punktem między linią a kulą (który zostanie obliczony podczas obliczania odległości między kulą a linią)

Odległość między punktem a linią:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html


1
Jest w 2D, a nie w 3D; jak mówisz, to tak naprawdę nie ma znaczenia
Martijn

Nie jestem matematykiem, więc pomyślałem, że lepiej nakreślę ogólne podejście i zostawię to innym, aby zastanowili się nad konkretną matematyką (chociaż wyglądam raczej banalnie)
Martin

2
+1 z silnym poparciem. (chociaż miałbym link do innej strony, strona pbourke wygląda na mylącą) Wszystkie pozostałe odpowiedzi są zbyt skomplikowane. Chociaż twój komentarz „Ten punkt jest także punktem przecięcia na linii” jest niepoprawny, nie ma żadnego punktu, który został zbudowany w procesie obliczeniowym.
Jason S,


Wyjaśniłem trochę lepiej o najbliższym punkcie i powiązałem z matematyką zamiast pbourke :)
Martin

3

Oto implementacja w JavaScript. Moje podejście polega na tym, aby najpierw przekształcić segment linii w linię nieskończoną, a następnie znaleźć punkt (punkty) przecięcia. Stamtąd sprawdzam, czy znalezione punkty znajdują się na odcinku linii. Kod jest dobrze udokumentowany, powinieneś być w stanie postępować zgodnie z nim.

Możesz wypróbować kod tutaj w tym demo na żywo . Kod został pobrany z mojego repozytorium algorytmów .

wprowadź opis zdjęcia tutaj

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

W tym słupku kolizja linii zostanie sprawdzona poprzez sprawdzenie odległości między środkiem okręgu a punktem na segmencie linii (Ipoint), które reprezentują punkt przecięcia normalnej N (Zdjęcie 2) od środka okręgu do segmentu linii.

( https://i.stack.imgur.com/3o6do.png )Zdjęcie 1. Znajdowanie wektorów E i D

Na obrazie 1 pokazano jedno koło i jedną linię, wektor A punkt początkowy do linii, wektor B punkt do linii końcowej, wektor C punkt do środka okręgu. Teraz musimy znaleźć wektor E (od punktu początkowego linii do środka okręgu) i wektor D (od punktu początkowego linii do punktu końcowego linii) to obliczenie pokazano na obrazie 1.

( https://i.stack.imgur.com/7098a.png )Zdjęcie 2. Znajdowanie wektora X

Na obrazie 2 widzimy, że wektor E jest rzutowany na Wektor D przez „iloczyn iloczynu” wektora E i wektora jednostkowego D, wynikiem iloczynu iloczynu jest skalarne Xp, które reprezentują odległość między punktem początkowym linii a punktem przecięcia (Ipoint) wektor N i wektor D. Następny wektor X znajduje się przez pomnożenie wektora jednostki D i skalara Xp.

Teraz musimy znaleźć wektor Z (wektor do Ipoint), łatwo jest dodać prosty wektor wektor A (punkt początkowy na linii) i wektor X. Następnie musimy zająć się szczególnymi przypadkami, które musimy sprawdzić, to Ipoint na odcinku linii, jeśli nie musimy się dowiedzieć, czy jest on na lewo, czy na prawo, użyjemy wektora najbliższego, aby ustalić, który punkt jest najbliżej okręgu.

( https://i.stack.imgur.com/p9WIr.png )Zdjęcie 3. Znajdowanie najbliższego punktu

Gdy rzut Xp jest ujemny, Ipoint znajduje się na lewo od odcinka linii, najbliższy wektor jest równy wektorowi punktu początkowego linii, gdy rzut Xp jest większy niż wielkość wektora D, wówczas Ipoint znajduje się na prawo od odcinka linii, a najbliższy wektor jest równy wektorowi końca linii punkt w każdym innym przypadku najbliższy wektor jest równy wektorowi Z.

Teraz, gdy mamy najbliższy wektor, musimy znaleźć wektor od środka okręgu do punktu Ipoint (wektor dist), wystarczy odjąć najbliższy wektor od wektora środka. Następnie sprawdź, czy wielkość odległości wektora jest mniejsza niż promień okręgu, jeśli tak jest, to zderzają się, jeśli nie ma kolizji.

( https://i.stack.imgur.com/QJ63q.png )Zdjęcie 4. Sprawdzanie kolizji

Na koniec możemy zwrócić niektóre wartości dla rozwiązania kolizji, najłatwiejszym sposobem jest przywrócenie nakładania się kolizji (odjęcie promienia od wielkości dystansu wektora) i powrót osi kolizji, jej wektora D. Również punktem przecięcia jest wektor Z, jeśli to konieczne.


2

Jeśli współrzędne linii to Ax, Ay i Bx, By, a środek koła to Cx, Cy, wówczas formuły linii to:

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

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

gdzie 0 <= t <= 1

a koło jest

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

jeśli podstawisz x i y formuł linii w formułę okręgów, otrzymasz równanie t drugiego rzędu, a jego rozwiązania są punktami przecięcia (jeśli istnieją). Jeśli uzyskasz wartość mniejszą niż 0 lub większą niż 1, nie jest to rozwiązanie, ale pokazuje, że linia „wskazuje” kierunek koła.


2

Tylko dodatek do tego wątku ... Poniżej znajduje się wersja kodu opublikowana przez pahlevan, ale dla C # / XNA i trochę uporządkowana:

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

W C # / XNA możesz użyćRay.Intersects(BoundingSphere)
bobobobo

2

wprowadź opis zdjęcia tutaj

' 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

Utworzyłem tę funkcję dla iOS po odpowiedzi udzielonej przez 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

Kolejny w c # (częściowa klasa Circle). Testowany i działa jak urok.

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

Wymagany:

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

Krąg jest naprawdę złym facetem :) Więc dobrym sposobem jest uniknięcie prawdziwego kręgu, jeśli możesz. Jeśli wykonujesz kontrolę kolizji dla gier, możesz przejść z pewnymi uproszczeniami i mieć tylko 3 kropki produktów i kilka porównań.

Nazywam to „grubym punktem” lub „cienkim kołem”. swego rodzaju elipsa o zerowym promieniu w kierunku równoległym do segmentu. ale pełny promień w kierunku prostopadłym do segmentu

Najpierw rozważę zmianę nazwy i zmianę układu współrzędnych, aby uniknąć nadmiernej ilości danych:

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

Po drugie, indeks h w hvec2f oznacza, że ​​wektor musi sprzyjać operacjom poziomym, takim jak dot () / det (). Co oznacza, że ​​jego komponenty należy umieścić w osobnych rejestrach xmm, aby uniknąć przetasowania / hadd'ingu / hsubowania. I oto proszę, z najbardziej wydajną wersją najprostszego wykrywania kolizji w grze 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;
}

Wątpię, czy możesz to jeszcze bardziej zoptymalizować. Używam go do wykrywania kolizji wyścigów samochodowych z wykorzystaniem sieci neuronowej, aby przetwarzać miliony milionów kroków iteracji.


Jeśli segment linii przecina okrąg, ale tylko nieznacznie, aby nie przekroczył punktu środkowego, czy ta funkcja nie zwróci fałszu, gdy powinna zwrócić wartość prawda? Wartość t może być poza zakresem 0..1.
Chris

1

Ta funkcja Java zwraca obiekt DVec2. Zajmuje DVec2 dla środka okręgu, promienia koła i Linii.

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

Oto moje rozwiązanie w TypeScript, zgodnie z ideą sugerowaną przez @Mizipzor (przy użyciu projekcji):

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

Wiem, że minęło trochę czasu, odkąd ten wątek był otwarty. Z odpowiedzi udzielonej przez chmike i poprawionej przez Aqib Mumtaz. Dają dobrą odpowiedź, ale działa tylko na nieskończoną linię, jak powiedział Aqib. Dodaję więc porównania, aby wiedzieć, czy segment linii dotyka okręgu, piszę go w Pythonie.

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

Oto rozwiązanie napisane w golang. Metoda jest podobna do niektórych innych odpowiedzi zamieszczonych tutaj, ale nie do końca taka sama. Jest łatwy do wdrożenia i został przetestowany. Oto kroki:

  1. Przetłumacz współrzędne, aby okrąg znajdował się na początku.
  2. Wyrazić segment linii jako sparametryzowane funkcje t dla współrzędnych xiy. Jeśli t wynosi 0, wartości funkcji są jednym punktem końcowym segmentu, a jeśli t wynosi 1, wartości funkcji są drugim punktem końcowym.
  3. Rozwiąż, jeśli to możliwe, równanie kwadratowe wynikające z ograniczeń wartości t, które dają współrzędne x, y przy odległościach od początku równych promieniowi okręgu.
  4. Wyrzuć rozwiązania, w których t wynosi <0 lub> 1 (<= 0 lub> = 1 dla segmentu otwartego). Punkty te nie są zawarte w segmencie.
  5. Przetłumacz z powrotem na oryginalne współrzędne.

Wartości A, B i C dla kwadratyki są tu wyprowadzone, gdzie (n-et) i (m-dt) są równaniami odpowiednio dla współrzędnych xiy linii. r jest promieniem koła.

(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

Dlatego A = ee + dd, B = - 2 (en + dm), a C = nn + mm - rr.

Oto kod golang dla funkcji:

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
}

Przetestowałem to za pomocą tej funkcji, która potwierdza, że ​​punkty rozwiązania znajdują się w segmencie linii i na okręgu. Tworzy odcinek testowy i przeciąga go wokół danego koła:

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

Oto wynik testu:

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

Wreszcie sposób można łatwo rozszerzyć na przypadek promienia rozpoczynającego się w jednym punkcie, przechodzącego przez drugi i rozciągającego się do nieskończoności, poprzez testowanie tylko, czy t> 0 lub t <1, ale nie oba.


0

Właśnie tego potrzebowałem, więc wymyśliłem to rozwiązanie. Językiem jest maxscript, ale należy go łatwo przetłumaczyć na dowolny inny język. sideA, sideB i CircleRadius są skalarami, pozostałe zmienne są punktami jako [x, y, z]. Zakładam, że z = 0 do rozwiązania na płaszczyźnie XY

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

0

Rozwiązanie w python, oparte na @Joe Skeen

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

Możesz wziąć X równomiernie rozmieszczonych punktów z linii, a jeśli jakieś znajdują się wewnątrz koła, nastąpi kolizja

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.