¿Cómo detecto la dirección de las colisiones de objetos rectangulares 2D?

11

Después de esta pregunta , necesito más ayuda.

¿Cómo puedo averiguar de qué lado de un rectángulo proviene una colisión y reaccionar en consecuencia?

Rectángulos colisionados desde todos los lados

Las flechas azules son los caminos que seguirían algunos objetos circulares si antes y después de chocar con la caja.

¿Cómo puedo calcular esto?

NemoStein
fuente

Respuestas:

8

Dado que esto se basa en su otra pregunta, daré una solución para cuando el rectángulo esté alineado con el eje.

Primero, construye el rectángulo de su objeto actual con los siguientes valores:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

A continuación, debe tener la posición del objeto antiguo (que puede almacenar en cada objeto o simplemente pasar a una función) para crear el rectángulo del objeto antiguo (cuando no estaba colisionando):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

Ahora, para saber de dónde fue la colisión, debe encontrar el lado donde la posición anterior no estaba en el área de colisión y dónde está su nueva posición. Porque, cuando lo piensas, esto es lo que sucede cuando chocas: un lado que no colisionó entra en otro rectángulo.

Así es como podría hacerlo (estas funciones suponen que hay una colisión. No deberían llamarse si no hay colisión):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

Enjuague y repita.

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

Ahora, para el uso real con la respuesta de colisión de la otra pregunta:

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

Una vez más, esta puede no ser la mejor solución, pero esa es la forma en que generalmente utilizo la detección de colisiones.

Jesse Emond
fuente
Una vez más, tenías razón! ; D Gracias ... (envíeme más postales de su tablero la próxima vez ... ^ ___ ^)
NemoStein
Ahhh lamentablemente no sabía para qué podría haberlo usado ... ¡tal vez la próxima vez!
Jesse Emond
7

Como la pregunta es parcialmente idéntica a esta pregunta , reutilizaré algunas partes de mi respuesta para intentar responderla.


Vamos a definir un contexto y algunas variables para que la siguiente explicación sea más comprensible. La forma de representación que usaremos aquí probablemente no se ajuste a la forma de sus propios datos, pero debería ser más simple de entender de esta manera (de hecho, puede usar los siguientes métodos usando otros tipos de representaciones una vez que comprenda el principio)

Por lo tanto, consideraremos un cuadro delimitador alineado del eje (o un cuadro delimitado orientado ) y una entidad en movimiento .

  • El cuadro delimitador se compone de 4 lados, y definiremos cada uno como:
    Lado1 = [x1, y1, x2, y2] (dos puntos [x1, y1] y [x2, y2])

  • La entidad en movimiento se define como un vector de velocidad (posición + velocidad):
    una posición [posX, posY] y una velocidad [speedX, speedY] .


Puede determinar qué lado de un AABB / OBB es golpeado por un vector utilizando el siguiente método:

  • 1 / Encuentre los puntos de intersección entre las líneas infinitas que pasan por los cuatro lados de la AABB y la línea infinita que pasa a través de la posición de la entidad (pre-colisión) que usa el vector de velocidad de la entidad como pendiente. (Puede encontrar un punto de colisión o un número indefinido, que corresponde a paralelos o líneas superpuestas)

  • 2 / Una vez que conozca los puntos de intersección (si existen) puede buscar aquellos que están dentro de los límites del segmento.

  • 3 / Finalmente, si todavía hay varios puntos en la lista (el vector de velocidad puede pasar a través de múltiples lados), puede buscar el punto más cercano desde el origen de la entidad utilizando las magnitudes del vector desde la intersección hasta el origen de la entidad.

Luego puede determinar el ángulo de colisión utilizando un simple producto de puntos.

  • 4 / Encuentre el ángulo entre la colisión usando un producto de puntos del vector de entidad (¿probablemente una bola?) Con el vector del lado del golpe.

----------

Más detalles:

  • 1 / Buscar intersecciones

    • a / Determine las líneas infinitas (Ax + Bx = D) usando sus formas paramétricas (P (t) = Po + tD).

      Origen del punto: Po = [posX, posY]
      Vector de dirección: D = [velocidadX, velocidadY]

      A = Dy = velocidadY
      B = -Dx = -velocidadX
      D = (Po.x * Dy) - (Po.y * Dx) = (posX speedY) - (posY speedX)

      Ax + By = D <====> (speedY x) + (-speedX y) = (posX speedY) - (posY speedX)

      Utilicé los valores de puntos de entidad para ilustrar el método, pero este es exactamente el mismo método para determinar las líneas infinitas de 4 lados del cuadro delimitador (Use Po = [x1, y1] y D = [x2-x1; y2-y1] en lugar).

    • b / A continuación, para encontrar la intersección de dos líneas infinitas podemos resolver el siguiente sistema:

      A1x + B1x = D1 <== Línea que pasa por el punto de entidad con el vector de velocidad como pendiente.
      A2x + B2x = D2 <== Una de las líneas que pasan por los lados AABB.

      que produce las siguientes coordenadas para la intercepción:

      Intercepción x = (( B 2 * D 1) - ( B 1 * D 2)) / (( A 1 * B 2) - ( A 2 * B 1))
      Intercepción y = (( A 1 * D 2) - ( A 2 * D 1)) / (( A 1 * B 2) - ( A 2 * B 1))

      Si el denominador ((A1 * B2) - (A2 * B1)) es igual a cero, entonces ambas líneas son paralelas o superpuestas, de lo contrario, debe encontrar una intersección.

  • 2 / Prueba de los límites del segmento. Como esto es fácil de verificar, no hay necesidad de más detalles.

  • 3 / Buscar el punto más cercano. Si todavía hay varios puntos en la lista, podemos encontrar qué lado es el más cercano al punto de origen de la entidad.

    • a / Determine el vector que va del punto de intersección al punto de origen de la entidad

      V = Po - Int = [Po.x - Int.x; Po.y - Int.y]

    • b / Calcular la magnitud del vector

      || V || = sqrt (V.x² + V.y²)

    • c / encuentra el más pequeño.
  • 4 / Ahora que sabe qué lado será golpeado, puede determinar el ángulo con un producto de puntos.

    • a / Sea S = [x2-x1; y2-y1] será el vector lateral que será golpeado y E = [speedX; speedY] sea ​​el vector de velocidad de la entidad.

      Usando la regla del producto de puntos vectoriales, sabemos que

      S · E = Sx Ex + Sy Ey
      y
      S · E = || S || || E || cos θ

      Entonces podemos determinar θ manipulando un poco esta ecuación ...

      cos θ = (S · E) / (|| S || || E ||)

      θ = acos ((S · E) / (|| S || || E ||))

      con

      S · E = Sx * Ex + Sy * Ey
      || S || = sqrt (Sx² + Sy²)
      || E || = sqrt (Ex² + Ey²)


Nota: Como dije en el otro hilo de preguntas, probablemente esta no sea la forma más eficiente ni la más simple de hacerlo, esto es lo que viene a la mente, y alguna parte de las matemáticas podría ayudar.

No verifiqué con un ejemplo OBB concreto (lo hice con un AABB) pero también debería funcionar.

Valkea
fuente
6

Una manera simple es resolver la colisión, luego traducir el cuadro de colisión del objeto en movimiento por un píxel en cada dirección y ver cuáles resultan en colisión.

Si desea hacerlo "correctamente" y con formas de colisión rotadas o polígonos arbitrarios, le sugiero que lea el Teorema del eje de separación. El software Metanet (la gente que creó el juego N), por ejemplo, tiene un increíble artículo de desarrollo sobre SAT . También discuten la física involucrada.

Anko
fuente
2

Una forma sería rotar el mundo alrededor de su rectángulo. "El mundo" en este caso son solo los objetos que te interesan: el rectángulo y la pelota. Gira el rectángulo alrededor de su centro hasta que sus límites estén alineados con los ejes x / / y, luego gira la bola en la misma cantidad.

El punto importante aquí es que giras la bola alrededor del centro del rectángulo, no la suya.

Luego, puede probar fácilmente la colisión como lo haría con cualquier otro rectángulo no girado.


Otra opción es tratar el rectángulo como cuatro segmentos de línea distintos y probar la colisión con cada uno de ellos por separado. Esto le permite probar la colisión y descubrir qué lado colisionó al mismo tiempo.

BlueRaja - Danny Pflughoeft
fuente
1

Utilicé ángulos fijos en mis cálculos, pero esto debería ayudarte

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
David Sopala
fuente