Problema de detección de colisión círculo-línea

11

Actualmente estoy desarrollando un clon de ruptura y me he topado con un obstáculo para que la detección de colisión entre una bola (círculo) y un ladrillo (polígono convexo) funcione correctamente. Estoy usando una prueba de detección de colisión Circle-Line donde cada línea representa y borde en el ladrillo de polígono convexo.

Para la mayoría de las veces, la prueba Circle-Line funciona correctamente y los puntos de colisión se resuelven correctamente.

Detección de colisión funcionando correctamente.

Sin embargo, de vez en cuando mi código de detección de colisión devuelve falso debido a un discriminante negativo cuando la bola realmente cruza el ladrillo.

Error de detección de colisión.

Soy consciente de la ineficiencia con este método y estoy usando cuadros delimitadores alineados con ejes para reducir la cantidad de ladrillos probados. Mi principal preocupación es si hay algún error matemático en mi código a continuación.

/* 
 * from and to are points at the start and end of the convex polygons edge.
 * This function is called for every edge in the convex polygon until a
 * collision is detected. 
 */

bool circleLineCollision(Vec2f from, Vec2f to)
{
    Vec2f lFrom, lTo, lLine;
    Vec2f line, normal;
    Vec2f intersectPt1, intersectPt2;
    float a, b, c, disc, sqrt_disc, u, v, nn, vn;
    bool one = false, two = false;

    // set line vectors
    lFrom = from - ball.circle.centre;      // localised
    lTo = to - ball.circle.centre;          // localised
    lLine = lFrom - lTo;                    // localised
    line = from - to;

    // calculate a, b & c values
    a = lLine.dot(lLine);
    b = 2 * (lLine.dot(lFrom));
    c = (lFrom.dot(lFrom)) - (ball.circle.radius * ball.circle.radius);

    // discriminant
    disc = (b * b) - (4 * a * c);

    if (disc < 0.0f)
    {
        // no intersections
        return false;
    }
    else if (disc == 0.0f)
    {
        // one intersection
        u = -b / (2 * a);

        intersectPt1 = from + (lLine.scale(u));
        one = pointOnLine(intersectPt1, from, to);

        if (!one)
            return false;
        return true;
    }
    else
    {
        // two intersections
        sqrt_disc = sqrt(disc);
        u = (-b + sqrt_disc) / (2 * a);
        v = (-b - sqrt_disc) / (2 * a);
        intersectPt1 = from + (lLine.scale(u));
        intersectPt2 = from + (lLine.scale(v));

        one = pointOnLine(intersectPt1, from, to);
        two = pointOnLine(intersectPt2, from, to);

        if (!one && !two)
            return false;
        return true;
    }
}

bool pointOnLine(Vec2f p, Vec2f from, Vec2f to)
{
    if (p.x >= min(from.x, to.x) && p.x <= max(from.x, to.x) && 
        p.y >= min(from.y, to.y) && p.y <= max(from.y, to.y))
        return true;
    return false;
}
jazzdawg
fuente
No puedo encontrar ninguna diferencia entre lLine y line ...
FxIII
La prueba pointOnLine se puede simplificar y realizar antes de calcular el punto real.
FxIII
¿Cómo se calcula sqrt_disc?
FxIII
Lo siento, FxIII. Debo haberme confundido un poco cuando estaba localizando mis vectores. No me di cuenta de que los vectores serían iguales cuando se restaban entre sí. Estaba limpiando mi código un poco antes de publicar y olvidé sqrt_disc = sqrt(disc);volver a colocarlo . Muchas gracias por su respuesta a continuación, me ayudó mucho.
jazzdawg

Respuestas:

20

El segmento que va de A a B se puede calcular como

P (t) = A + D · t donde D es B - A y t va de 0 a 1

Ahora el círculo está centrado en el origen (mueve A y B si es necesario para poner el centro en el origen) y tiene un radio r .

Tienes una intersección si para alguna t obtienes que P tiene la misma longitud de r o, de manera equivalente, que la longitud de P al cuadrado es equivalente a

La longitud al cuadrado de un vector se obtiene haciendo el producto punto de un vector por sí mismo (esto es tan cierto que si uno encuentra una operación adecuada para el producto punto puede definir un concepto nuevo y consistente de longitud)

P · P = ( A + D · t) · ( A + D · t) =

A · A + 2 A · D t + D · D

Queremos encontrar para qué t obtenemos P · P = r², así que terminamos preguntándonos cuándo

A · A + 2 A · D t + D · D t² = r²

o cuando

D · D t² + 2 A · D t + A · A -r² = 0

esta es la famosa ecuación cuadrática

at² + bt + c = 0

con

a = D · D ; b = 2 A · D yc = A · A -r²

Tenemos que verificar si el determinante b² - 4ac es positivo, por lo que encontramos 2 valores de t que nos dan los puntos de intersección P (t).

t debe estar entre 0 y 1; de lo contrario, encontramos soluciones que se encuentran en la línea que pasa por A y B pero que están antes de A o después de B

[EDITAR]

Como otras preguntas pueden encontrar alguna ayuda para esta respuesta, decidí intentar simplificar el razonamiento en esta edición usando algunas imágenes. condición inicial Esta es la condición inicial. Ahora concéntrese en el segmento A_B

Segmento que va de A a B

D es el vector que mueve A hacia B, por lo que si t está entre 0 y 1, D · t es una "fracción propia" de D, por lo que el punto A + D · t se encuentra en el segmento A_B : los puntos marrones aparecen cuando t es entre 0 y 1 y el verde oscuro es cuando t> 1.

Ahora podemos simplificar las cosas si movemos el centro del círculo al origen. Esto siempre se puede hacer porque es un simple cambio de sistema de coordenadas que preserva la geometría, los ángulos, la intersección, las medidas, etc.

círculo moviéndose al centro

Ahora tenemos una manera simple de calcular la longitud de P cuando t varía y decir para qué t P cruza los límites del círculo.

Ejemplos

Como puede ver, P ' tiene una longitud mayor que r, mientras que P " es menor que r. Dado que tanto la longitud del vector como r son números positivos, la relación de orden de ser mayor o menor que la preservada es si calculamos la relación entre las longitudes al cuadrado y el radio al cuadrado. P * 1 y P * 2 son el punto que hace que | P | ² sea igual a r²

Como se mencionó en la sección previa a la edición, llegamos para obtener una ecuación cuadrática donde t es nuestra variable. Como se sabe, los valores de solución de t varían desde el caso en que t es un par de números complejos, lo que significa que no hay intersección; el caso cuando t son dos soluciones iguales, eso significa que hay una intersección; el caso cuando hay dos soluciones distintas, eso significa que hay dos intersecciones.

El Discriminante se usa para discriminar la condición anterior y se realiza una prueba de validez en t para ver si es una intersección válida pero fuera de nuestro segmento, es decir, la solución t debe ser real y entre 0 y 1 para considerarse una intersección adecuada que cae en el segmento A_B

FxIII
fuente
3
Este es el algoritmo correcto para usar. Puede encontrar una muy buena descripción de cómo funciona en Real Time Rendering Third Edition , páginas 787 a 791. Si puede encontrarlo en una biblioteca, vale la pena echarle un vistazo.
Darcy Rayner
44
Con el octavo voto a favor de esta respuesta, he alcanzado 2k puntos de reputación. Le agradezco mucho la confianza que ha depositado en mí. Esto es tanto un reconocimiento de mis esfuerzos como un estímulo para continuar haciendo mi mejor esfuerzo en producir la respuesta de mayor calidad posible. Gracias
FxIII
Espera, ¿esto explica los dos casos de esquina correctamente? Por ejemplo, un círculo puede cruzar el plano definido por la línea fuera de t0 <= t <= t1, pero golpea los puntos finales del segmento de línea un poco más tarde. Debe verificar la distancia mínima entre los puntos finales de la línea y la ruta de los círculos. Si esa distancia es menor que el radio del círculo, entonces la línea ha sido golpeada.
Darcy Rayner
@DarcyRayner, ¿te refieres al caso cuando ambos puntos están dentro del área del círculo?
FxIII