¿Algoritmo de detección de colisiones de segmentos de línea circular?
Tengo una línea de A a B y un círculo colocado en C con radio R.
¿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ó?
Tomando
- E es el punto inicial del rayo,
- L es el punto final del rayo,
- C es el centro de la esfera contra la que estás probando
- 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:
- Expandir x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 = 0
- 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 - 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
- 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
- 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. - Y luego, t 2 ( d · d ) + 2t( d · ( e - c ) ) + ( e - c ) · ( e - c ) - r 2 = 0
- 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 ;
}
Nadie parece considerar la proyección, ¿estoy completamente fuera de lugar?
Proyecte el vector AC
sobre AB
. El vector proyectado, AD
da el nuevo punto D
.
Si la distancia entre D
y C
es menor (o igual) R
tenemos una intersección.
Como esto:
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};
}