Algoritmo di rilevamento delle collisioni a segmenti di linea circolare?


195

Ho una linea da A a B e un cerchio posizionato in C con il raggio R.

Immagine

Qual è un buon algoritmo da usare per verificare se la linea interseca il cerchio? E a quale coordinata lungo il bordo dei cerchi si è verificato?


4
Hmm. Una domanda: stai parlando della linea infinita attraverso A e B o del segmento di linea finita da A a B?
Jason S,

2
In questo caso, è il segmento di linea finita. La "linea" si chiama qualcos'altro a seconda che sia finita o infinita?
Mizipzor,

1
C'è un requisito prestazionale? Dovrebbe essere un metodo veloce?
chmike,

A questo punto, no, tutti gli algoritmi che ho provato qui non rallentano notevolmente l'applicazione.
Mizipzor,

13
@Mizipzor sì, si chiamano qualcos'altro: segmenti di linea . Se dite semplicemente "linea" è implicita una infinita.
MestreLion,

Risposte:


200

Prendere

  1. E è il punto di partenza del raggio,
  2. L è il punto finale del raggio,
  3. C è il centro della sfera su cui stai testando
  4. r è il raggio di quella sfera

Calcola:
d = L - E (vettore di direzione del raggio, dall'inizio alla fine)
f = E - C (vettore dalla sfera centrale all'inizio del raggio)

Quindi l'intersezione viene trovata da ..
Collegamento:
P = E + t * d
Questa è un'equazione parametrica:
P x = E x + td x
P y = E y + td y
in
(x - h) 2 + (y - k) 2 = r 2
(h, k) = centro del cerchio.

Nota: abbiamo semplificato il problema in 2D qui, la soluzione che otteniamo si applica anche in 3D

ottenere:

  1. Espandi
    x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 = 0
  2. Plug
    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. Esplodi
    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. Gruppo
    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. Infine,
    t 2 (_d * _d) + 2t (_e * _d - _d * _c) + _e * _e - 2 (_e * _c) + _c * _c - r 2 = 0
    * Dove _d è il vettore d e * è il prodotto punto. *
  6. E poi,
    t 2 (_d * _d) + 2t (_d * (_e - _c)) + (_e - _c) * (_e - _c) - r 2 = 0
  7. Lasciare _f = _e - _c
    t 2 (_d * _d) + 2t (_d * _f) + _f * _f - r 2 = 0

Quindi otteniamo:
t 2 * (d DOT d) + 2t * (f DOT d) + (f DOT f - r 2 ) = 0
Quindi risolvendo l'equazione quadratica:

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
Sembra funzionare se faccio copia e incolla diritte, ma sto cercando di capirlo. In (xh) ^ 2 + (yk) ^ 2 = r ^ 2 cos'è h e k? K è costante con la quale la linea / raggio aumenta su y su x? E cos'è t? Guardando il codice sembra che tu abbia assunto il suo 1 (quindi è appena "rimosso"). Queste formule hanno un nome o qualcosa del genere? Forse posso cercarli in dettaglio su Wolfram.
Mizipzor,

3
h e k sono il centro del cerchio su cui si intersecano. t è il parametro dell'equazione di linea. Nel codice, t1 e t2 sono le soluzioni. t1 e t2 indicano "quanto lontano lungo il raggio" è avvenuta l'intersezione.
Bobobobo,

1
Ok capito. Il prodotto punto viene semplicemente calcolato su tre vettori (x, y, z). Passerò il mio codice a questo algoritmo.
chmike,

21
P = E + t * dChe cosa è t?
Derek 朕 會 功夫

3
Non so perché, ma il codice non sembra funzionare per il caso Impale. Lo fa quando aggiungo se t1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1 come condizione vera, ma poi dà anche un falso positivo su un lato della linea finita, quando il cerchio è nella parte "infinita". Non capisco ancora la matematica, ma copia / passo, attenzione.
Nicolas Mommaerts,

142

Nessuno sembra prendere in considerazione la proiezione, sono completamente fuori strada qui?

Proiettare il vettore ACsu AB. Il vettore proiettato AD, indica il nuovo punto D.
Se la distanza tra De Cè minore di (o uguale a) Rabbiamo un'intersezione.

Come questo:
Immagine di SchoolBoy


9
Ci sono molti dettagli da prendere in considerazione: D sta tra AB? La distanza perpendicolare alla linea C è maggiore del raggio? Tutti questi implicano la grandezza del vettore, cioè la radice quadrata.
ADB

15
Buona idea, ma come si calcolano i due punti di intersezione?
Ben

4
@Spider non importa. In generale, poiché questa è una variante del problema dell'intersezione sfera-linea, la strategia di Mizipzor è perfettamente valida. CDè una proiezione, è perpendicolare per definizione.

2
È una vecchia domanda, ma c'è una buona risorsa su questo e gli algoritmi correlati in questo sito Web: paulbourke.net/geometry/pointlineplane
Andrew

1
Una grande spiegazione di questa risposta: scratchapixel.com/lessons/3d-basic-rendering/...
ShawnFeatherly

50

Vorrei usare l'algoritmo per calcolare la distanza tra un punto (centro del cerchio) e una linea (linea AB). Questo può quindi essere usato per determinare i punti di intersezione della linea con il cerchio.

Supponiamo che abbiamo i punti A, B, C. Ax e Ay sono i componenti xey dei punti A. Lo stesso vale per B e C. La scala R è il raggio del cerchio.

Questo algoritmo richiede che A, B e C siano punti distinti e che R non sia 0.

Ecco l'algoritmo

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

se una linea che non interseca il cerchio e il suo punto p1 e p2 sono entrambi all'interno del cerchio. in questo caso come funziona il tuo algoritmo ??
Prashant,

1
Devi testare t-dt e t + dt. Se t-dt <0, allora p1 è all'interno del cerchio. Se t + dt> 1, allora p2 è all'interno del cerchio. Questo è vero se LEC <R ovviamente.
Chmike,

Grazie. Mi sono piaciuti questi commenti su pgm come spiegazione perché non c'era uso delle parole "punto prodotto" poiché la mia matematica è arrugginita. Comunque t e dt non sono compresi tra 0..1, quindi cambiando questo in python ho cambiato t per essere diviso per LAB ** 2. La mia comprensione è che la prima divisione di LAB è di proiettare il centro del cerchio sulla linea AB, e la seconda divisione di LAB è di normalizzarlo nell'intervallo 0..1. Anche il dt deve essere diviso per LAB, quindi è anche normalizzato. Pertanto "if (t-dt> = 0.0)" esiste la prima intersezione "if (t + dt <= 1.0)" esiste la seconda intersezione. Questo ha funzionato con i test.
punchcard

2
Perché il punto di intersezione con il cerchio è a "distanza" t+dte t-dtsulla linea. tè il punto sulla linea più vicina al centro del cerchio. I punti di intersezione con il cerchio sono a una distanza simmetrica da t. I punti di intersezione sono a "distanze" t-dte t+dt. Ho citato la distanza perché non è la distanza euclidea. Per ottenere la distanza euclidea da Adove t=0, devi moltiplicare il valore per LAB.
Chmike,

1
@Matt W Intendi "Come determinare se l'intersezione si verifica al di fuori dei punti finali della sezione AB"? Pensa a t come una misura della distanza lungo la linea. Il punto A è a t=0. Punto B a t=LAB. Quando entrambi i punti di intersezione ( t1=t-tde t2=t+td) hanno valori negativi rispetto alle intersezioni si trovano all'esterno della sezione (dietro il punto A guardando dal lato della sezione del punto). Quando t1 e t2 sono più grandi di LAB, anche loro sono all'esterno (questa volta dietro il punto B). L'intersezione t1 (o t2) si verifica tra A e B solo quando t1 (o t2) è compreso tra 0 e LAB.
Marconius,

20

Ok, non ti darò il codice, ma dato che l'hai taggato , Non penso che ti importerà. Innanzitutto, devi ottenere un vettore perpendicolare alla linea.

Avrai una variabile sconosciuta in y = ax + c ( c sarà sconosciuta )
Per risolverlo, calcola il suo valore quando la linea passa attraverso il centro del cerchio.

Cioè,
collega la posizione del centro del cerchio all'equazione della linea e risolvi c.
Quindi calcola il punto di intersezione della linea originale e la sua normale.

Questo ti darà il punto più vicino sulla linea al cerchio.
Calcola la distanza tra questo punto e il centro del cerchio (usando la grandezza del vettore).
Se questo è inferiore al raggio del cerchio - voilà, abbiamo un incrocio!


2
Era, in effetti, quello che volevo. Voglio la teoria, una ricerca su google dell'algoritmo di collisione del cerchio di linea rivela solo il codice per quanto posso vedere.
Mizipzor,

Ok, c è sconosciuto nella tua equazione, ma che cos'è "a"? Le altre risposte sembrano riferirsi a quella variabile come "alfa" e "t". Anche se, questa è solo una funzione lineare (y = kx + m), matematica abbastanza elementare, quindi improvvisamente mi sento un po 'arrugginito. K non è anche sconosciuto? O vuoi dire che possiamo assumere m = 0 e risolvere k? Quindi m (cioè c) sarebbe sempre zero per il nostro risolto k?
Mizipzor,

1
Oh, scusa, sto usando la semplice equazione di una linea con un gradiente e un offset (l'equazione cartesiana). Supponevo che stavi memorizzando la linea come tale equazione - nel qual caso usi il negativo del gradiente per k. Se la linea non è memorizzata in questo modo, potresti calcolare k come (y2-y1) / (x2-x1)
a_m0d

1
Non assumiamo che m sia zero; calcoliamo prima il gradiente (in modo che l'equazione della linea appaia come y = 2x + m come esempio), e quindi una volta ottenuto il gradiente possiamo risolvere per m collegando il centro del cerchio per ye x .
a_m0d,

1
+1 Spiegazione fantastica! Ma penso che questo presupponga una linea, non un segmento di linea. Quindi, se il punto più vicino su questa linea al centro del cerchio non fosse tra i punti A e B, verrebbe comunque conteggiato.
Hassan,

12

Un altro metodo utilizza la formula dell'area ABC triangolo. Il test di intersezione è più semplice ed efficiente del metodo di proiezione, ma trovare le coordinate del punto di intersezione richiede più lavoro. Almeno verrà ritardato al punto richiesto.

La formula per calcolare l'area del triangolo è: area = bh / 2

dove b è la lunghezza della base e h è l'altezza. Abbiamo scelto il segmento AB come base in modo che h sia la distanza più breve da C, il centro del cerchio, alla linea.

Poiché l'area del triangolo può anche essere calcolata da un prodotto punto vettoriale, possiamo determinare 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 )
{
    ...
}        

AGGIORNAMENTO 1:

È possibile ottimizzare il codice utilizzando il calcolo della radice quadrata inversa veloce descritto qui per ottenere una buona approssimazione di 1 / LAB.

Il calcolo del punto di intersezione non è così difficile. Eccolo

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

Se h = R la linea AB è tangente al cerchio e il valore dt = 0 ed E = F. Le coordinate del punto sono quelle di E e F.

Verificare che A sia diverso da B e che la lunghezza del segmento non sia nulla se ciò può accadere nella propria applicazione.


2
Mi piace la semplicità in questo metodo. Forse potrei adattare parte del codice circostante per non aver bisogno del punto di collisione vero e proprio, vedrò cosa succede se uso A o B anziché il punto calcolato tra.
Mizipzor,

1
t = Dx * (Cx-Ax) + Dy * (Cy-Ax) dovrebbe leggere t = Dx * (Cx-Ax) + Dy * (Cy-Ay)
Stonetip

È giusto. Grazie per averlo precisato. L'ho corretto nel post.
chmike,

appena modificato: la prima riga calcola l'area del triangolo usando un prodotto incrociato , non un punto. verificata con il codice qui: stackoverflow.com/questions/2533011/...
ericsoco

4
si noti inoltre che la prima metà di questa risposta verifica l'intersezione con una linea, non un segmento di linea (come richiesto nella domanda).
ericsoco,

8

Ho scritto una piccola sceneggiatura per testare l'intersezione proiettando il punto centrale del cerchio sulla linea.

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/

Se è necessario controllare la collisione con il segmento, è necessario considerare anche la distanza del centro del cerchio per iniziare e terminare i punti.

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

Questa soluzione che ho trovato sembrava un po 'più facile da seguire rispetto ad alcune delle altre.

prendendo:

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

Vorrei risolvere per l'equazione della linea in forma di intercetta pendenza. Tuttavia, non volevo avere a che fare con equazioni difficili ccome punto, quindi ho semplicemente spostato il sistema di coordinate in modo che il cerchio si trovasse0,0

p3 = p1 - c
p4 = p2 - c

A proposito, ogni volta che sottraggo punti l'uno dall'altro sottraggo quelli xe quindi sottraggo quelli ye li inserisco in un nuovo punto, nel caso in cui qualcuno non lo sapesse.

Ad ogni modo, ora risolvo per l'equazione della linea con p3e p4:

m = (p4_y - p3_y) / (p4_x - p3) (the underscore is an attempt at subscript)
y = mx + b
y - mx = b (just put in a point for x and y, and insert the m we found)

Ok. Ora devo impostare queste equazioni uguali. Per prima cosa devo risolvere l'equazione del cerchio perx

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

Quindi li ho impostati uguali:

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

E risolvi per l'equazione quadratica ( 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

Ora ho il mio a, be c.

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

Quindi ho inserito questo nella formula quadratica:

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

E sostituisci con i valori, quindi semplifica il più possibile:

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

Questo è quasi quanto semplificherà. Infine, separa le equazioni con ±:

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

Quindi collegare semplicemente il risultato di entrambe queste equazioni in xin mx + b. Per chiarezza, ho scritto un codice JavaScript per mostrare come utilizzare questo:

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

Spero che aiuti!

PS Se qualcuno trova errori o ha qualche suggerimento, si prega di commentare. Sono molto nuovo e accolgo con favore tutti gli aiuti / suggerimenti.


Se possibile, pubblica anche con alcuni valori di esempio in modo che possiamo cogliere rapidamente il flusso.
Prabindh,

Con underRadicalextra ')'
Jeevan il

4

È possibile trovare un punto su una linea infinita più vicina al centro del cerchio proiettando il vettore AC sul vettore AB. Calcola la distanza tra quel punto e il centro del cerchio. Se è maggiore di R, non c'è intersezione. Se la distanza è uguale a R, la linea è una tangente del cerchio e il punto più vicino al centro del cerchio è in realtà il punto di intersezione. Se la distanza è inferiore a R, allora ci sono 2 punti di intersezione. Si trovano alla stessa distanza dal punto più vicino al centro del cerchio. Tale distanza può essere facilmente calcolata usando il teorema di Pitagora. Ecco l'algoritmo in pseudocodice:

{
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: aggiunto codice per verificare se i punti di intersezione trovati si trovano effettivamente all'interno del segmento di linea.


Hai perso un caso poiché stiamo parlando di un segmento di linea: quando il segmento termina nel cerchio.
ADB

@ADB in realtà il mio algoritmo funziona solo per linee infinite, non segmenti di linea. Esistono molti casi che non gestisce con i segmenti di linea.
Juozas Kontvainis,

La domanda originale riguardava i segmenti di linea, non l'intersezione di linea di cerchio, che è un problema molto più semplice.
msgme

4

Stranamente posso rispondere ma non commentare ... Mi è piaciuto l'approccio di Multitaskpro di spostare tutto per far cadere il centro del cerchio sull'origine. Sfortunatamente ci sono due problemi nel suo codice. Prima nella parte sotto la radice quadrata è necessario rimuovere il doppio potere. Quindi no:

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

ma:

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

Nelle coordinate finali si dimentica di spostare indietro la soluzione. Quindi no:

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

ma:

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

L'intera funzione diventa quindi:

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
suggerimenti: in primo luogo, gestiscilo nel caso in cui il segmento di linea sia verticale (ovvero abbia una pendenza infinita). In secondo luogo, hanno solo restituito quei punti che effettivamente rientrano nell'intervallo del segmento di linea originale - credo che ritorni felicemente tutti i punti che cadono sulla linea infinita, anche se quei punti si trovano al di fuori del segmento di linea.
Gino,

Nota: funziona bene per le linee, ma non funziona per i segmenti di linea.
Mike,

3

Avrai bisogno di un po 'di matematica qui:

Supponiamo che A = (Xa, Ya), B = (Xb, Yb) e C = (Xc, Yc). Qualsiasi punto sulla linea da A a B ha coordinate (alfa * Xa + (1-alfa) Xb, alfa Ya + (1-alpha) * Yb) = P

Se il punto P ha una distanza da R a C, deve trovarsi sul cerchio. Quello che vuoi è risolvere

distance(P, C) = R

questo è

(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

se si applica la formula ABC a questa equazione per risolverla per l'alfa e si calcolano le coordinate di P utilizzando la soluzione o le soluzioni per l'alfa, si ottengono i punti di intersezione, se presenti.


3

Se trovi la distanza tra il centro della sfera (dato che è 3D, suppongo che tu voglia dire sfera e non cerchio) e la linea, quindi controlla se quella distanza è inferiore al raggio che farà il trucco.

Il punto di collisione è ovviamente il punto più vicino tra la linea e la sfera (che verrà calcolato quando si calcola la distanza tra la sfera e la linea)

Distanza tra un punto e una linea:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html


1
È in 2D, non in 3D; come dici tu, questo non ha molta importanza
Martijn,

Non sono un matematico, quindi ho pensato che sarebbe meglio delineare un approccio generale e lasciarlo agli altri per capire la matematica specifica (anche se sembro piuttosto banale)
Martin

2
+1 con un forte voto. (anche se avrei collegato a un altro sito, il sito Pbourke sembra confuso) Tutte le altre risposte finora sono eccessivamente complicate. Sebbene il tuo commento "Quel punto sia anche il punto di intersezione sulla linea" non è corretto, non c'è nessun punto che è stato costruito nel processo di calcolo.
Jason S,


Ho spiegato un po 'meglio il punto più vicino, e collegato al mondo matematico invece che a Pbourke :)
Martin

3

Ecco un'implementazione in Javascript. Il mio approccio è prima convertire il segmento di linea in una linea infinita, quindi trovare i punti di intersezione. Da lì controllo se i punti trovati si trovano sul segmento di linea. Il codice è ben documentato, dovresti essere in grado di seguirlo.

Puoi provare il codice qui in questa demo dal vivo . Il codice è stato preso dal repository dei miei algoritmi .

inserisci qui la descrizione dell'immagine

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

In questo post, la collisione della linea del cerchio verrà verificata controllando la distanza tra il centro del cerchio e il segmento punto sulla linea (Ipoint) che rappresentano il punto di intersezione tra N normale (Immagine 2) dal centro del cerchio al segmento di linea.

( https://i.stack.imgur.com/3o6do.png )Immagine 1. Ricerca dei vettori E e D

Nell'immagine 1 sono mostrati un cerchio e una linea, vettore A punto per punto iniziale, vettore B punto per fine linea, vettore C punto per centro cerchio. Ora dobbiamo trovare il vettore E (dal punto iniziale della linea al centro del cerchio) e il vettore D (dal punto iniziale della linea al punto finale della linea) questo calcolo è mostrato nell'immagine 1.

( https://i.stack.imgur.com/7098a.png )Immagine 2. Trovare il vettore X

Nell'immagine 2 possiamo vedere che il vettore E è proiettato sul vettore D dal "prodotto punto" del vettore E e il vettore unità D, il risultato del prodotto punto è Xp scalare che rappresenta la distanza tra il punto iniziale della linea e il punto di intersezione (Ipoint) di vettore N e vettore D. Il vettore X successivo viene trovato moltiplicando il vettore unità D e Xp scalare.

Ora dobbiamo trovare il vettore Z (dal vettore all'Ipoint), è facile la sua semplice aggiunta vettoriale del vettore A (punto iniziale sulla linea) e del vettore X. Successivamente dobbiamo occuparci di casi speciali che dobbiamo controllare è il segmento Ipoint on line, se non dobbiamo scoprire se è a sinistra o a destra, useremo il vettore più vicino per determinare quale punto è più vicino al cerchio.

( https://i.stack.imgur.com/p9WIr.png )Immagine 3. Trovare il punto più vicino

Quando la proiezione Xp è negativa, l'Ipoint è a sinistra del segmento di linea, il vettore più vicino è uguale al vettore del punto iniziale della linea, quando la proiezione Xp è maggiore della grandezza del vettore D, allora Ipoint è a destra del segmento di linea, quindi il vettore più vicino è uguale al vettore della fine della linea punto in ogni altro caso il vettore più vicino è uguale al vettore Z.

Ora, quando abbiamo il vettore più vicino, dobbiamo trovare il vettore dal centro del cerchio a Ipoint (vettore dist), è semplice dobbiamo solo sottrarre il vettore più vicino dal vettore centrale. Quindi controlla se la magnitudine della distanza del vettore è inferiore al raggio del cerchio se è allora si scontrano, se non c'è non c'è collisione.

( https://i.stack.imgur.com/QJ63q.png )Immagine 4. Verifica della collisione

Per finire, possiamo restituire alcuni valori per risolvere la collisione, il modo più semplice è quello di restituire la sovrapposizione della collisione (sottrarre il raggio dalla magnitudine della distanza del vettore) e restituire l'asse della collisione, il suo vettore D. Se necessario, anche il punto di intersezione è il vettore Z.


2

Se le coordinate della linea sono Ax, Ay e Bx, By e il centro dei cerchi è Cx, Cy, le formule delle linee sono:

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

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

dove 0 <= t <= 1

e il cerchio è

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

se sostituisci le formule xey della linea nella formula dei cerchi ottieni un'equazione del secondo ordine di te le sue soluzioni sono i punti di intersezione (se ce ne sono). Se ottieni che è minore di 0 o maggiore di 1, allora non è una soluzione ma mostra che la linea 'punta' alla direzione del cerchio.


2

Solo un'aggiunta a questo thread ... Di seguito è riportata una versione del codice pubblicato da pahlevan, ma per C # / XNA e riordinato un po ':

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

inserisci qui la descrizione dell'immagine

' 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

Ho creato questa funzione per iOS seguendo la risposta data da 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

Un altro in c # (classe Circle parziale). Testato e funziona come un fascino.

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

Necessario:

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

Ecco una buona soluzione in JavaScript (con tutta la matematica richiesta e l'illustrazione dal vivo) https://bl.ocks.org/milkbread/11000965

Sebbene la is_onfunzione in quella soluzione abbia bisogno di modifiche:

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


2

Circle è davvero un cattivo ragazzo :) Quindi un buon modo è quello di evitare il vero cerchio, se puoi. Se si esegue il controllo delle collisioni per i giochi, è possibile eseguire alcune semplificazioni e disporre di soli 3 punti e alcuni confronti.

Io chiamo questo "punto grasso" o "cerchio sottile". il suo tipo di ellisse con raggio zero in una direzione parallela a un segmento. ma raggio completo in una direzione perpendicolare a un segmento

Innanzitutto, prenderei in considerazione la ridenominazione e il cambio del sistema di coordinate per evitare dati eccessivi:

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

In secondo luogo, l'indice h in hvec2f significa che il vettore deve favorire le operazioni orizzontali, come dot () / det (). Il che significa che i suoi componenti devono essere posizionati in un registro xmm separato, per evitare mescolamenti / adattamenti / annullamenti. Ed eccoci qui, con la versione più performante del più semplice rilevamento delle collisioni per giochi 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;
}

Dubito che tu possa ottimizzarlo ulteriormente. Lo sto usando per il rilevamento delle collisioni di corse automobilistiche guidate dalla rete neurale, per elaborare milioni di milioni di passaggi di iterazione.


Se il segmento di linea interseca il cerchio ma solo leggermente in modo che non passi il suo punto centrale, questa funzione non restituirà false quando dovrebbe restituire vero? Il valore t potrebbe essere al di fuori dell'intervallo 0..1.
Chris,

1

Questa funzione Java restituisce un oggetto DVec2. Prende un DVec2 per il centro del cerchio, il raggio del cerchio e una linea.

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

Ecco la mia soluzione in TypeScript, seguendo l'idea suggerita da @Mizipzor (usando la proiezione):

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

So che è passato un po 'di tempo da quando questa discussione era aperta. Dalla risposta data da Chmike e migliorata da Aqib Mumtaz. Danno una buona risposta ma funzionano solo per una linea infinita come diceva Aqib. Quindi aggiungo alcuni confronti per sapere se il segmento di linea tocca il cerchio, lo scrivo in Python.

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

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

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

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

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

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

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

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

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

0

Ecco una soluzione scritta in golang. Il metodo è simile ad alcune altre risposte pubblicate qui, ma non è lo stesso. È facile da implementare ed è stato testato. Ecco i passaggi:

  1. Traduci le coordinate in modo che il cerchio sia all'origine.
  2. Esprimi il segmento di linea come funzioni parametrizzate di t per entrambe le coordinate xey. Se t è 0, i valori della funzione sono un punto finale del segmento e se t è 1, i valori della funzione sono l'altro punto finale.
  3. Risolvi, se possibile, l'equazione quadratica risultante dai valori vincolanti di t che producono coordinate x, y con distanze dall'origine pari al raggio del cerchio.
  4. Eliminare le soluzioni in cui t è <0 o> 1 (<= 0 o> = 1 per un segmento aperto). Quei punti non sono contenuti nel segmento.
  5. Traduci di nuovo alle coordinate originali.

I valori di A, B e C per il quadratico sono derivati ​​qui, dove (n-et) e (m-dt) sono le equazioni per le coordinate x e y della linea, rispettivamente. r è il raggio del cerchio.

(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

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

Ecco il codice Golang per la funzione:

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
}

L'ho provato con questa funzione, che conferma che i punti di soluzione si trovano all'interno del segmento di linea e sul cerchio. Crea un segmento di prova e lo sposta attorno al cerchio dato:

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

Ecco l'output del test:

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

Infine, il metodo è facilmente estendibile al caso di un raggio che inizia in un punto, attraversa l'altro e si estende all'infinito, testando solo se t> 0 o t <1 ma non entrambi.


0

Ne avevo solo bisogno, quindi ho trovato questa soluzione. La lingua è maxscript, ma dovrebbe essere facilmente tradotta in qualsiasi altra lingua. sideA, sideB e CircleRadius sono scalari, il resto delle variabili sono punti come [x, y, z]. Sto assumendo z = 0 per risolvere sul piano 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

Soluzione in pitone, basata su @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
}
}

Puoi prendere X punti equidistanti dalla linea e se qualcuno si trova all'interno del cerchio, c'è una collisione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.