¿Algoritmo de detección de colisiones de segmentos de línea circular?

Resuelto Mizipzor asked hace 15 años • 29 respuestas

Tengo una línea de A a B y un círculo colocado en C con radio R.

Imagen

¿Cuál es un buen algoritmo a utilizar para comprobar si la línea cruza el círculo? ¿Y en qué coordenada a lo largo del borde del círculo ocurrió?

Mizipzor avatar Jul 02 '09 16:07 Mizipzor
Aceptado

Tomando

  1. E es el punto inicial del rayo,
  2. L es el punto final del rayo,
  3. C es el centro de la esfera contra la que estás probando
  4. r es el radio de esa esfera

Calcular:
d = L - E (Vector de dirección del rayo, de principio a fin)
f = E - C (Vector desde el centro de la esfera hasta el inicio del rayo)

Entonces la intersección se encuentra mediante...
Sustituyendo:
P = E + t * d
Esta es una ecuación paramétrica:
P x = E x + td x
P y = E y + td y
en
(x - h) 2 + (y - k) 2 = r 2
(h,k) = centro del círculo.

Nota: Aquí hemos simplificado el problema a 2D, la solución que obtenemos se aplica también en 3D.

Llegar:

  1. Expandir x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 = 0
  2. Enchufe 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. Explotar 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. Grupo 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. Finalmente, t 2 ( d · d ) + 2t( e · d - d · c ) + e · e - 2( e · c ) + c · c - r 2 = 0
    Donde d es el vector d y · es el producto escalar.
  6. Y luego, t 2 ( d · d ) + 2t( d · ( e - c ) ) + ( e - c ) · ( e - c ) - r 2 = 0
  7. Sea f = e - c t 2 ( d · d ) + 2t( d · f ) + f · f - r 2 = 0

Entonces obtenemos:
t 2 * ( d · d ) + 2t*( f · d ) + ( f · f - r 2 ) = 0

Entonces resolviendo la ecuación cuadrática:

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 ;
}
bobobobo avatar Jul 05 '2009 21:07 bobobobo

Nadie parece considerar la proyección, ¿estoy completamente fuera de lugar?

Proyecte el vector ACsobre AB. El vector proyectado, ADda el nuevo punto D.
Si la distancia entre Dy Ces menor (o igual) Rtenemos una intersección.

Como esto:
Imagen de colegial

Edición de la comunidad:

Para cualquiera que se encuentre con esta publicación más adelante y se pregunte cómo se puede implementar un algoritmo de este tipo, aquí hay una implementación general escrita en JavaScript que utiliza funciones comunes de manipulación de vectores.

/**
 * Returns the distance from line segment AB to point C
 */
function distanceSegmentToPoint(A, B, C) {
    // Compute vectors AC and AB
    const AC = sub(C, A);
    const AB = sub(B, A);

    // Get point D by taking the projection of AC onto AB then adding the offset of A
    const D = add(proj(AC, AB), A);

    const AD = sub(D, A);
    // D might not be on AB so calculate k of D down AB (aka solve AD = k * AB)
    // We can use either component, but choose larger value to reduce the chance of dividing by zero
    const k = Math.abs(AB.x) > Math.abs(AB.y) ? AD.x / AB.x : AD.y / AB.y;

    // Check if D is off either end of the line segment
    if (k <= 0.0) {
        return Math.sqrt(hypot2(C, A));
    } else if (k >= 1.0) {
        return Math.sqrt(hypot2(C, B));
    }

    return Math.sqrt(hypot2(C, D));
}

Para esta implementación utilicé un par de funciones comunes de manipulación de vectores que probablemente ya haya proporcionado en cualquier entorno en el que esté trabajando. Sin embargo, si aún no tiene estas funciones disponibles, así es como se pueden implementar.

// Define some common functions for working with vectors
const add = (a, b) => ({x: a.x + b.x, y: a.y + b.y});
const sub = (a, b) => ({x: a.x - b.x, y: a.y - b.y});
const dot = (a, b) => a.x * b.x + a.y * b.y;
const hypot2 = (a, b) => dot(sub(a, b), sub(a, b));

// Function for projecting some vector a onto b
function proj(a, b) {
    const k = dot(a, b) / dot(b, b);
    return {x: k * b.x, y: k * b.y};
}
Mizipzor avatar Jul 03 '2009 13:07 Mizipzor