213 votes

Algorithme de détection des collisions entre segments de ligne en cercle ?

Je dispose d'une ligne allant de A à B et d'un cercle positionné en C avec le rayon R.

Image

Quel est un bon algorithme à utiliser pour vérifier si la ligne intersecte le cercle ? Et à quelle coordonnée le long de l'arête du cercle cela se produit-il ?

4 votes

Hmm. Une question : parlez-vous de la ligne infinie passant par A et B, ou du segment de ligne fini de A à B ?

2 votes

Dans ce cas, c'est le segment de ligne fini. La "ligne" est-elle appelée différemment selon qu'elle est finie ou infinie ?

1 votes

Existe-t-il une exigence de performance ? Doit-il s'agir d'une méthode rapide ?

217voto

bobobobo Points 17477

En prenant

  1. E est le point de départ du rayon,
  2. L est le point final du rayon,
  3. C est le centre de la sphère que vous testez.
  4. r est le rayon de cette sphère

Calculer :
d \= L - E (vecteur directionnel du rayon, du début à la fin)
f \= E - C (vecteur du centre de la sphère au début du rayon)

Ensuite, l'intersection est trouvée par
Branchement :
P = E + t * d
Il s'agit d'une équation paramétrique :
P x \= E x + td x
P y \= E y + td y
en
(x - h) 2 + (y - k) 2 \= r 2
(h,k) = centre du cercle.

Note : Nous avons simplifié le problème en 2D ici, la solution que nous obtenons s'applique également en 3D.

pour obtenir :

  1. Développez x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 \= 0
  2. Bouchon 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. Explosion 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. Groupe 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. Enfin, t 2 ( d - d ) + 2t( e - d - d - c ) + e - e - 2( e - c ) + c - c - r 2 \= 0
    d est le vecteur d et - est le produit scalaire.
  6. Et puis, t 2 ( d - d ) + 2t( d - ( e - c ) ) + ( e - c ) - ( e - c ) - r 2 \= 0
  7. Location f \= e - c t 2 ( d - d ) + 2t( d - f ) + f - f - r 2 \= 0

Donc on a :
t 2 * ( d - d ) + 2t*( f - d ) + ( f - f - r 2 ) = 0

Donc, en résolvant l'équation quadratique :

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 votes

Ça semble fonctionner si je fais un simple copier-coller, mais je cherche à le comprendre. Dans (x-h)^2+(y-k)^2=r^2 que représentent h et k ? Est-ce que k est la constante par laquelle la ligne/rayon augmente en y par rapport à x ? Et qu'est-ce que t ? En regardant le code, il semble que vous ayez supposé qu'il est égal à 1 (donc qu'il est simplement "supprimé"). Ces formules ont-elles un nom ou quelque chose comme ça ? Je peux peut-être les chercher en détail sur Wolfram.

3 votes

H et k sont le centre du cercle que vous croisez. t est le paramètre de l'équation de la ligne. Dans le code, t1 et t2 sont les solutions. t1 et t2 vous indiquent "à quelle distance le long du rayon" l'intersection a eu lieu.

0 votes

Une solution très élégante qui ne nécessite qu'un seul calcul de racine carrée et qui devrait en principe fonctionner en 3D. Quelle serait l'équation en 3D et comment trouver les racines ?

145voto

Mizipzor Points 10952

Personne ne semble envisager la projection, suis-je complètement à côté de la plaque ?

Projeter le vecteur AC sur AB . Le vecteur projeté, AD donne le nouveau point D .
Si la distance entre D et C est inférieur (ou égal) à R nous avons une intersection.

Comme ça :
Image by SchoolBoy

9 votes

Il y a de nombreux détails à prendre en considération : D est-il situé entre AB ? La distance perpendiculaire à la ligne C est-elle plus grande que le rayon ? Toutes ces questions font intervenir la magnitude du vecteur, c'est-à-dire la racine carrée.

16 votes

Bonne idée, mais comment calculer ensuite les deux points d'intersection ?

0 votes

@Mizipzor. Un peu tard, mais je ne suis pas d'accord avec ce point. En effet, il ne prend pas du tout en compte la position de la ligne du segment AB. Votre argument est vrai lorsque seulement AB est situé perpendiculairement au point CD ! Considérez la ligne du segment AB où CD n'est pas du tout perpendiculaire à AB. Quelle est votre opinion à ce sujet ?

50voto

chmike Points 2514

J'utiliserais l'algorithme pour calculer la distance entre un point (centre du cercle) et une ligne (ligne AB). Cette distance peut ensuite être utilisée pour déterminer les points d'intersection de la ligne avec le cercle.

Disons que nous avons les points A, B, C. Ax et Ay sont les composantes x et y des points A. De même pour B et C. Le scalaire R est le rayon du cercle. Idem pour B et C. Le scalaire R est le rayon du cercle.

Cet algorithme exige que A, B et C soient des points distincts et que R soit différent de 0.

Voici l'algorithme

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

0 votes

Si une ligne qui n'intersecte pas le cercle et dont les deux points p1 et p2 sont à l'intérieur du cercle, comment fonctionne votre algorithme ?

1 votes

Vous devez tester t-dt et t+dt. Si t-dt < 0, alors p1 est à l'intérieur du cercle. Si t+dt > 1, alors p2 est à l'intérieur du cercle. Ceci est vrai si LEC < R bien sûr.

0 votes

Merci. J'ai aimé les commentaires de ce pgm comme explication parce qu'il n'y avait pas l'utilisation des mots "produit scalaire" puisque mes mathématiques sont rouillées. Cependant, t et dt ne sont pas compris entre 0 et 1, donc en passant en python, j'ai changé t pour qu'il soit divisé par LAB**2. D'après ce que j'ai compris, la première division par LAB sert à projeter le centre du cercle sur la ligne AB, et la seconde division par LAB sert à le normaliser dans l'intervalle 0..1. De même, le dt doit être divisé par LAB pour être également normalisé. Ainsi, "si (t-dt >=0.0)" la première intersection existe "si (t+dt <= 1.0)" la deuxième intersection existe. Cela a fonctionné lors des tests.

22voto

a_m0d Points 5784

Ok, je ne vous donnerai pas de code, mais puisque vous avez marqué ceci algorithme mais je ne pense pas que cela vous concerne. D'abord, tu dois obtenir un vecteur perpendiculaire à la ligne.

Vous aurez une variable inconnue dans y = ax + c ( c sera inconnu )
Pour résoudre cela, calculez sa valeur lorsque la ligne passe par le centre du cercle.

C'est-à-dire,
Introduisez la position du centre du cercle dans l'équation de la ligne et résolvez pour c .
Calculez ensuite le point d'intersection de la ligne d'origine et de sa normale.

Vous obtiendrez ainsi le point de la ligne le plus proche du cercle.
Calculez la distance entre ce point et le centre du cercle (en utilisant la magnitude du vecteur).
Si cette valeur est inférieure au rayon du cercle, voilà, nous avons une intersection !

2 votes

C'était, en fait, ce que je voulais. Je veux la théorie, une recherche google de l'algorithme de collision ligne-cercle ne donne que du code pour autant que je puisse voir.

0 votes

Ok, c est inconnu dans votre équation, mais qu'est-ce que "a" ? Les autres réponses semblent se référer à cette variable comme "alpha" et "t". Bien qu'il ne s'agisse que d'une fonction linéaire (y=kx+m), des mathématiques assez basiques, je me sens soudainement un peu rouillé. K n'est-il pas également inconnu ? Ou voulez-vous dire que nous pouvons supposer que m=0 et résoudre k ? Dans ce cas, m (c'est-à-dire c) ne serait-il pas toujours nul pour notre k résolu ?

1 votes

Oh, désolé - j'utilise l'équation simple d'une ligne avec un gradient et un décalage (l'équation cartésienne). J'ai supposé que vous stockiez la ligne sous la forme d'une telle équation - dans ce cas, vous utilisez le négatif du gradient pour k. Si vous n'avez pas stocké la ligne de cette manière, vous pouvez calculer k comme (y2-y1)/(x2-x1).

14voto

chmike Points 2514

Une autre méthode utilise la formule de l'aire du triangle ABC. Le test d'intersection est plus simple et plus efficace que la méthode de projection, mais trouver les coordonnées du point d'intersection demande plus de travail. Au moins, il sera retardé jusqu'au moment où il sera nécessaire.

La formule pour calculer l'aire du triangle est : aire = bh/2

où b est la longueur de la base et h est la hauteur. Nous avons choisi le segment AB comme base pour que h soit la plus courte distance entre C, le centre du cercle, et la ligne.

Puisque l'aire du triangle peut également être calculée par un produit scalaire vectoriel, nous pouvons déterminer 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 )
{
    ...
}        

UPDATE 1 :

Vous pouvez optimiser le code en utilisant le calcul rapide de la racine carrée inverse décrit dans le document suivant ici pour obtenir une bonne approximation de 1/LAB.

Le calcul du point d'intersection n'est pas si difficile. Voici comment procéder

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

Si h = R alors la droite AB est tangente au cercle et la valeur dt = 0 et E = F. Les coordonnées du point sont celles de E et F.

Vous devez vérifier que A est différent de B et que la longueur du segment n'est pas nulle si cela peut se produire dans votre application.

2 votes

J'aime la simplicité de cette méthode. Je pourrais peut-être adapter une partie du code environnant pour ne pas avoir besoin du point de collision lui-même. Je verrai ce qui se passe si j'utilise A ou B plutôt que le point calculé entre les deux.

1 votes

T = Dx*(Cx-Ax) + Dy*(Cy-Ax) devrait se lire t = Dx*(Cx-Ax) + Dy*(Cy-Ay)

0 votes

C'est exact. Merci de l'avoir signalé. Je l'ai corrigé dans le post.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X