¿Detección de colisión hexagonal para objetos que se mueven rápidamente?

39

Un objeto tiene una posición y un vector de velocidad. Por lo general, solo se usa la posición para verificar si dos objetos colisionan, esto es problemático para los objetos que se mueven muy rápido, ya que puede suceder que el objeto se mueva tan rápido que esté delante del primer objeto en la primera verificación de colisión y detrás de él en El segundo control de colisión.

BoundingBox Collision Fail

Ahora también hay comprobaciones de colisión basadas en líneas, en las que solo se verifica si el vector de movimiento de cada objeto se cruza con el cuadro delimitador del otro. Esto puede verse como una expansión de un punto. Sin embargo, esto solo funciona si el objeto que se mueve rápidamente es realmente pequeño.

Hexagon Collision Win

Entonces, mi idea es, en lugar de expandir un punto, ¿por qué no expandir un rectángulo? Esto da como resultado un hexágono.

Ahora, hasta ahora todo bien. Pero, ¿cómo verifico si dos hexágonos de este tipo se cruzan? Tenga en cuenta que estos son hexágonos muy específicos.

Especificaciones del hexágono

Pregunta adicional : ¿Es posible calcular exactamente dónde (o más bien después de cuánto tiempo) ocurrió la colisión? Esto podría ser muy útil para detectar lo que realmente sucedió, como dónde y con cuánta potencia y para simular cómo se movieron en el tiempo entre la colisión y el final del cuadro.

API-Bestia
fuente
for (líneas en A) for (líneas en B) si (líneas cruzan) colisión, excepto que no cubre A en B o B en casos A. Hm. =)
Jari Komppa
44
¿Estás comprometido con las cajas? Los cuadros que dibujó se pueden representar mediante círculos con una pérdida mínima de precisión pero un algoritmo de colisión relativamente fácil. Busque la detección de colisión de círculo barrido. Si su relación longitud / ancho se aleja de 1, sería menos atractivo.
Steve H
@SteveH Estoy buscando la solución más flexible, por lo que la relación longitud / ancho es un gran problema.
API-Beast
1
Debes darte cuenta de que solo porque los hexágonos se cruzan no significa que ocurra la colisión. Incluso si pudieras decir sin error si se cruzan, aún tendrías que trabajar para determinar si hay una colisión y, obviamente, dónde y cuándo sucede. Por lo tanto, no puede saltar a su pregunta adicional todavía.
jrsala
2
No he intentado esto antes, pero parece que en lugar de hexágonos en el espacio 2d, puedes pensar en el movimiento en 2d como volúmenes en el espacio 3d donde un eje es el tiempo. Luego se cruzan dos poliedros 3d con coordenadas (x, y, t). Si los dos objetos sólidos se cruzan, entonces desea encontrar el valor t mínimo. Puede simplificar un poco convirtiendo todas las coordenadas de B para que estén en el marco de referencia de A. No he implementado esto, pero ahí es donde comenzaría.
amitp

Respuestas:

34

La solución es en realidad más simple de lo esperado. El truco es usar la resta de Minkowski antes de tu técnica hexagonal.

Aquí están tus rectángulos A y B, con sus velocidades vAy vB. Tenga en cuenta que vAy vBno son realmente velocidades, son la distancia recorrida durante un cuadro.

paso 1

Ahora reemplace el rectángulo B con un punto P, y el rectángulo A con el rectángulo C = A + (- B), que tiene dimensiones de la suma de las dimensiones de A y B. Las propiedades de adición de Minkowski indican que se produce una colisión entre el punto y el nuevo rectángulo si y solo si se produce una colisión entre los dos rectángulos originales:

paso 2

Pero si el rectángulo C se mueve a lo largo del vector vA, y el punto P se mueve a lo largo del vector vB, un simple cambio de marco de referencia nos dice que es lo mismo que si el rectángulo C estuviera quieto, y el punto P se moviera a lo largo del vector vB-vA:

paso 3

Luego puede usar una fórmula simple de intersección de segmento de caja para indicar dónde se produce la colisión en el nuevo marco de referencia.

El último paso es volver al marco de referencia adecuado. Simplemente divida la distancia recorrida por el punto hasta la intersección dentro del círculo por la longitud del vector vB-vAy obtendrá un valor stal que 0 < s < 1. La colisión ocurre en el momento s * Ten que Tes la duración de su cuadro.

Comentario de madshogo :
Una GRAN ventaja de esta técnica sobre la respuesta de Mr Beast es que si no hay rotación, entonces la "resta de Minkowski" A + (- B) se puede calcular una vez para todos los pasos de tiempo posteriores. !

Así que el único algoritmo que toma tiempo en todo esto (suma de Minkowski, la complejidad O (mn) , donde m es el número de vértices en A y n el número de vértices en B ) se puede usar solamente una vez, haciendo efectivamente detección de colisiones una constante- problema de tiempo!

Más tarde, puede tirar la suma una vez que sepa con certeza que A y B están en diferentes partes de su escena (¿de su quadtree?) Y que ya no colisionarán.

En contraste, el método del Sr. Beast requiere bastantes cálculos en cada paso de tiempo.

Además, para los rectángulos alineados a los ejes, A + (- B) se puede calcular de manera mucho más simple que calculando realmente todas las sumas, vértice por vértice. Simplemente expanda A agregando la altura de B a su altura y el ancho de B a su ancho (la mitad de cada lado).

Pero todo esto solo funciona si ni A ni B están girando y si ambos son convexos. Si hay rotación o si usa formas cóncavas, debe usar volúmenes / áreas barridas.
fin del comentario

sam hocevar
fuente
44
Parece un enfoque bastante interesante, sin embargo, aún no lo entiendo al 100%, ¿qué sucede cuando el objeto es realmente pequeño y se mueve entre las dos líneas? i.imgur.com/hRolvAF.png
API-Beast
-1: Este método de ninguna manera le permite estar seguro de que se produce una colisión. Solo le permite asegurarse de que no suceda, en el caso de que el segmento y el volumen extruido no se crucen. Pero es completamente posible que se crucen y, sin embargo, que no haya colisión. Lo que está mal es la parte "Ahora puede usar [...] una simple intersección segmento-segmento para decidir dónde ocurrió la colisión".
jrsala
2
@madshogo Tienes razón. Supuse que el paso de tiempo era lo suficientemente pequeño en comparación con el tamaño de los objetos que no sería un problema, pero ciertamente no es muy robusto en el caso general. Me ocuparé de arreglarlo.
Sam Hocevar
@SamHocevar Si pudiera revisar la respuesta, sería genial.
API-Beast
1
@LuisAlves sí y no ... toda la lógica funciona, pero tendrá que reemplazar vB-vAcon g(t)-f(t)dónde fy gson las posiciones de A y B con el tiempo. Como esto ya no es una línea recta, tendrá que resolver un problema de intersección de curva paramétrica de cuadro.
sam hocevar
17

En primer lugar, en el caso de los rectángulos alineados a los ejes, la respuesta de Kevin Reid es la mejor y el algoritmo es el más rápido.

Segundo, para formas simples, use velocidades relativas (como se ve a continuación) y el teorema del eje de separación para la detección de colisiones. Se le dirá si ocurre una colisión en el caso de movimiento lineal (sin rotación). Y si hay rotación, necesita un pequeño paso de tiempo para que sea preciso. Ahora, para responder la pregunta:


¿Cómo saber en el caso general si dos formas convexas se cruzan?

Le daré un algoritmo que funciona para todas las formas convexas y no solo para hexágonos.

Supongamos que X e Y son dos formas convexas. Se cruzan si y solo si tienen un punto en común, es decir, hay un punto x X y un punto y ∈ Y tal que x = y . Si considera el espacio como un espacio vectorial, esto equivale a decir x - y = 0 . Y ahora llegamos a este negocio de Minkowski:

La suma de Minkowski de X y Y es el conjunto de todos los x + y para x ∈ X y y ∈ Y .


Un ejemplo para X e Y


X, Y y su suma Minkowski, X + Y

Supongamos que (-Y) es el conjunto de todos -y para y ∈ Y , dado el párrafo anterior, X e Y se cruzan si y solo si X + (-Y) contiene 0 , es decir, el origen .

Comentario lateral: ¿por qué escribo X + (-Y) en lugar de X - Y ? Bueno, porque en matemáticas, hay una operación llamada la diferencia de Minkowski de A y B, que a veces se escribe X - Y, pero no tiene nada que ver con el conjunto de todos x - y para x ∈ X e y ∈ Y (el verdadero Minkowski La diferencia es un poco más compleja).

Por lo tanto, nos gustaría calcular la suma de Minkowski de X e -Y y determinar si contiene el origen. El origen no es especial en comparación con cualquier otro punto, por lo que para encontrar si el origen está dentro de un determinado dominio, utilizamos un algoritmo que podría decirnos si algún punto dado pertenece a ese dominio.

La suma de X e Y de Minkowski tiene una propiedad genial, que es que si X e Y son convexos, entonces X + Y también lo es. Y descubrir si un punto pertenece a un conjunto convexo es mucho más fácil que si ese conjunto no fuera (se sabe que es) convexo.

No podemos calcular todas las x - y para x ∈ X e y ∈ Y porque hay una infinidad de tales puntos x e y , así que con suerte, dado que X , Y e X + Y son convexos, podemos usar los puntos "más externos" que definen las formas X e Y , que son sus vértices, y obtendremos los puntos más externos de X + Y , y también algunos más.

Estos puntos adicionales están "rodeados" por los más externos de X + Y para que no contribuyan a definir la forma convexa recién obtenida. Decimos que no definen el " casco convexo " del conjunto de puntos. Entonces, lo que hacemos es deshacernos de ellos en preparación para el algoritmo final que nos dice si el origen está dentro del casco convexo.


El casco convexo de X + Y. Hemos eliminado los vértices "internos".

Por lo tanto, obtenemos

Un primer algoritmo ingenuo

boolean intersect(Shape X, Shape Y) {

  SetOfVertices minkowski = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      minkowski.addVertice(x-y);
    }
  }
  return contains(convexHull(minkowski), Vector2D(0,0));

}

Los bucles obviamente tienen complejidad O (mn) , donde m y n son el número de vértices de cada forma. El minkoswkiconjunto contiene elementos mn como máximo. El convexHullalgoritmo tiene una complejidad que depende del algoritmo que utilizó , y puede apuntar a O (k log (k)) donde k es el tamaño del conjunto de puntos, por lo que en nuestro caso obtenemos O (mn log (mn) ) . El containsalgoritmo tiene una complejidad que es lineal con el número de aristas (en 2D) o caras (en 3D) del casco convexo, por lo que realmente depende de sus formas iniciales, pero no será mayor que O (mn) .

Te dejaré googlear el containsalgoritmo para formas convexas, es bastante común. Puedo ponerlo aquí si tengo tiempo.


Pero lo que estamos haciendo es la detección de colisiones, por lo que podemos optimizarlo mucho

Originalmente teníamos dos cuerpos A y B moviéndose sin rotación durante un paso de tiempo dt (por lo que puedo ver mirando sus fotos). Llamemos a v A y v B las velocidades respectivas de A y B , que son constantes durante nuestro paso de tiempo de duración dt . Obtenemos lo siguiente:

y, como señala en sus imágenes, estos cuerpos barren áreas (o volúmenes, en 3D) a medida que se mueven:

y terminan como A ' y B' después del paso de tiempo.

Para aplicar nuestro algoritmo ingenuo aquí, solo tendríamos que calcular los volúmenes barridos. Pero no estamos haciendo esto.

En el marco de referencia de B , B no se mueve (¡duh!). Y A tiene una cierta velocidad con respecto a B que obtienes calculando v A - v B (puedes hacer lo contrario, calcular la velocidad relativa de B en el marco de referencia de A ).

Movimiento relativo

De izquierda a derecha: velocidades en el marco de referencia base; velocidades relativas; calcular velocidades relativas.

Al considerar a B como inmóvil en su propio marco de referencia, solo tiene que calcular el volumen que A recorre a medida que se mueve durante dt con su velocidad relativa v A - v B .

Esto disminuye el número de vértices que se utilizarán en el cálculo de la suma de Minkowski (a veces en gran medida).

Otra posible optimización es en el punto donde calcula el volumen barrido por uno de los cuerpos, digamos A. No tiene que traducir todos los vértices que conforman A. Solo aquellos que pertenecen a bordes (caras en 3D) cuyos "cara" normal exterior la dirección del barrido. Seguramente ya lo habrás notado cuando calculaste las áreas barridas para los cuadrados. Puede saber si una normal es hacia la dirección de barrido utilizando su producto de punto con la dirección de barrido, que tiene que ser positivo.

La última optimización, que no tiene nada que ver con su pregunta sobre las intersecciones, es realmente útil en nuestro caso. Utiliza esas velocidades relativas que mencionamos y el llamado método de eje de separación. Seguramente ya lo sabes.

Suponga que conoce los radios de A y B con respecto a sus centros de masa (es decir, la distancia entre el centro de masa y el vértice más alejado de él), así:

Una colisión puede ocurrir sólo si es posible que el círculo de delimitación de A se encuentran la de B . Vemos aquí que no lo hará, y la manera de decirle a la computadora que consiste en calcular la distancia de C B a I como en la siguiente imagen y asegurarse de que es más grande que la suma de los radios de A y B . Si es más grande, no hay colisión. Si es más pequeño, entonces colisión.

Esto no funciona muy bien con formas que son bastante largas, pero en el caso de cuadrados u otras formas similares, es una muy buena heurística para descartar colisión .

El teorema del eje de separación aplicado a B y el volumen barrido por ASin embargo, el le dice si ocurre la colisión. La complejidad del algoritmo asociado es lineal con la suma de los números de vértices de cada forma convexa, pero es menos mágico cuando llega el momento de manejar la colisión.

Nuestro nuevo y mejor algoritmo que usa intersecciones para ayudar a detectar colisiones, pero aún no es tan bueno como el teorema del eje de separación para saber si ocurre una colisión

boolean mayCollide(Body A, Body B) {

  Vector2D relativeVelocity = A.velocity - B.velocity;
  if (radiiHeuristic(A, B, relativeVelocity)) {
    return false; // there is a separating axis between them
  }

  Volume sweptA = sweptVolume(A, relativeVelocity);
  return contains(convexHull(minkowskiMinus(sweptA, B)), Vector2D(0,0));

}

boolean radiiHeuristic(A, B, relativeVelocity)) {
  // the code here
}

Volume convexHull(SetOfVertices s) {
  // the code here
}

boolean contains(Volume v, Vector2D p) {
  // the code here
}

SetOfVertices minkowskiMinus(Body X, Body Y) {

  SetOfVertices result = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      result.addVertice(x-y);
    }
  }
  return result;

}
jrsala
fuente
2

No creo que usar el 'hexágono' sea tan útil. Aquí hay un bosquejo de una forma de obtener colisiones exactas para rectángulos alineados a ejes:

Dos rectángulos alineados con ejes se superponen si y solo si sus rangos de coordenadas X se superponen y sus rangos de coordenadas Y se superponen. (Esto puede verse como un caso especial del teorema del eje de separación). Es decir, si proyecta los rectángulos en los ejes X e Y, ha reducido el problema a dos intersecciones línea-línea.

Calcule el intervalo de tiempo en el que se cruzan las dos líneas en un eje (por ejemplo, comienza en el tiempo (separación actual de objetos / velocidad relativa de los objetos que se aproxima)), y haga lo mismo para el otro eje. Si esos intervalos de tiempo se superponen, entonces el tiempo más temprano dentro de la superposición es el momento de la colisión.

Kevin Reid
fuente
3
Olvidaste tu boceto.
MichaelHouse
2
@ Byte56 No, quiero decir que es un boceto de un algoritmo, ni siquiera un seudocódigo.
Kevin Reid
Oh ya veo. Mi error.
MichaelHouse
Este es en realidad el método más fácil. Agregué el código correspondiente para implementarlo.
Pasha
1

No creo que haya una manera fácil de calcular la colisión de polígonos con más lados que un rectángulo. Lo dividiría en formas primitivas como líneas y cuadrados:

function objectsWillCollide(object1,object2) {
    var lineA, lineB, lineC, lineD;
    //get projected paths of objects and store them in the 'line' variables

    var AC = lineCollision(lineA,lineC);
    var AD = lineCollision(lineA,lineD);
    var BC = lineCollision(lineB,lineC);
    var BD = lineCollision(lineB,lineD);
    var objectToObjectCollision = rectangleCollision(object1.getRectangle(), object2.getRectangle());

    return (AC || AD || BC || BD || objectToObjectCollision);
}

Ilustración de las rutas de objetos y estados finales

Observe cómo ignoro el estado de inicio de cada objeto, ya que debería haberse verificado durante el cálculo anterior.

eazimmerman
fuente
3
El problema con esto es que si los tamaños de los objetos son muy diferentes, el objeto más pequeño puede moverse dentro de la trayectoria del objeto grande sin provocar una colisión.
API-Beast
0

Teorema del eje separado

El teorema del eje separado dice "Si podemos encontrar un eje en el que dos formas convexas no se crucen, entonces las dos formas no se cruzan" o más factible para TI:

"Dos formas convexas solo se cruzan si se cruzan en todos los ejes posibles".

Para los rectángulos alineados a los ejes hay exactamente 2 ejes posibles: x e y. Pero el teorema no se limita a los rectángulos, se puede aplicar a cualquier forma convexa simplemente agregando los otros ejes en los que las formas podrían cruzarse. Para obtener más detalles sobre el tema, consulte este tutorial del desarrollador de N: http://www.metanetsoftware.com/technique/tutorialA.html#section1

Implementado se ve así:

axes = [... possible axes ...];
collision = true;
for every index i of axes
{
  range1[i] = shape1.getRangeOnAxis(axes[i]);
  range2[i] = shape2.getRangeOnAxis(axes[i]);
  rangeIntersection[i] = range1[i].intersectionWith(range2[i]);
  if(rangeIntersection[i].length() <= 0)
  {
    collision = false;
    break;
  }
}

Los ejes pueden ser tan representados como vectores normalizados.

Un rango es una línea unidimensional. El inicio debe establecerse en el punto proyectado más pequeño, el final al punto proyectado más grande.

Aplicarlo al rectángulo "barrido"

El hexágono en la pregunta se produce al "barrer" el AABB del objeto. El barrido agrega exactamente un posible eje de colisión a cualquier forma: el vector de movimiento.

shape1 = sweep(originalShape1, movementVectorOfShape1);
shape2 = sweep(originalShape2, movementVectorOfShape2);

axes[0] = vector2f(1.0, 0.0); // X-Axis
axes[1] = vector2f(0.0, 1.0); // Y-Axis
axes[2] = movementVectorOfShape1.normalized();
axes[3] = movementVectorOfShape2.normalized();

Hasta ahora todo bien, ahora ya podemos verificar si los dos hexágonos se cruzan. Pero se pone aún mejor.

Esta solución funcionará para cualquier forma convexa (por ejemplo, triángulos) y cualquier forma convexa barrida (por ejemplo, octágonos barridos). Sin embargo, cuanto más compleja sea la forma, menos efectiva será.


Bonus: donde sucede la magia.

Como dije, los únicos ejes adicionales son los vectores de movimiento. El movimiento es tiempo multiplicado por velocidad, por lo que, en cierto sentido, no son solo ejes espaciales, son ejes espacio-temporales.

Esto significa que podemos derivar el tiempo en que la colisión podría haber ocurrido a partir de estos dos ejes. Para esto necesitamos encontrar la intersección entre las dos intersecciones en los ejes de movimiento. Sin embargo, antes de que podamos hacer esto, debemos normalizar ambos rangos, para poder compararlos.

shapeRange1 = originalShape1.getRangeOnAxis(axes[2]);
shapeRange2 = originalShape2.getRangeOnAxis(axes[3]);
// Project them on a scale from 0-1 so we can compare the time ranges
timeFrame1 = (rangeIntersection[2] - shapeRange1.center())/movementVectorOfShape1.project(axes[2]);
timeFrame2 = (rangeIntersection[3] - shapeRange2.center())/movementVectorOfShape2.project(axes[3]);
timeIntersection = timeFrame1.intersectionWith(timeFrame2);

Cuando hice esta pregunta, ya acepté el compromiso de que habrá algunos falsos positivos raros con este método. Pero me equivoqué, al verificar esta intersección de tiempo podemos probar si la colisión "realmente" ocurrió y podemos resolver esos falsos positivos con ella:

if(collision)
{
  [... timeIntersection = see above ...]
  if(timeIntersection.length() <= 0)
    collision = false;
  else
    collisionTime = timeIntersection.start; // 0: Start of the frame, 1: End of the frame
}

Si observa algún error en los ejemplos de código, avíseme, aún no lo he implementado y, por lo tanto, no pude probarlo.

API-Bestia
fuente
1
¡Felicitaciones por haber encontrado una solución! Pero como dije antes: solo porque los hexágonos se cruzan no significa que habrá una colisión. Puede usar su método para calcular el tiempo de colisión todo lo que desee, si no hay colisión, no es muy útil. En segundo lugar, puede usar velocidades relativas para tener que calcular solo 1 volumen barrido y simplificar los cálculos al usar el SAT. Finalmente, solo tengo una idea aproximada de cómo funciona su truco de "tiempo de intersección", porque tal vez confundieron sus índices, ya que se shapeRange1 == shapeRange2trata de su código, ¿no?
jrsala
@madshogo debería tener más sentido ahora.
API-Beast
Todavía no entiendo cómo funciona la normalización del rango, pero supongo que es porque necesito una imagen. Espero que te funcione.
jrsala
-2

Siempre que las áreas barridas estén cerradas (sin espacios en el límite formado por las líneas de borde), lo siguiente funcionará (solo reduzca sus pruebas de colisión a línea-línea y punto-rect / punto-tri):

  1. ¿Se tocan sus bordes? (colisiones línea-línea) Compruebe si alguna línea de borde de un área barrida se cruza con alguna línea de borde de la otra área barrida. Cada área barrida tiene 6 lados.

  2. ¿Está el pequeño dentro del grande? (Utilice formas alineadas por eje (point-rect y point-tri)) Reoriente (rote) las áreas barridas para que la más grande esté alineada con el eje y pruebe si la más pequeña es interna (probando si hay puntos de esquina ( debe ser todo o nada) están dentro del área barrida alineada con el eje). Esto se hace descomponiendo tu hex en tris y rectos.

La prueba que haga primero depende de la probabilidad de cada una (haga primero la más común).

Es posible que le resulte más fácil usar un círculo de delimitación barrido (cápsula en lugar de hex) porque es más fácil dividirlo en dos semicírculos y un rectángulo cuando está alineado en el eje. .. te dejaré dibujar la solución

axon
fuente
No funciona si uno de los rectángulos es realmente pequeño y se mueve dentro del espacio entre dos líneas de borde.
jrsala
@madshogo que acabo de agregar a mi respuesta. Debería ser una solución completa ahora.
axon
1
"Usar formas alineadas de eje (punto-rect y punto-tri)": ¿Cómo se alinea un triángulo o un "punto-triángulo" (lo que sea que eso signifique) con los ejes? "para que el más grande esté alineado con el eje": ¿cómo puede saber cuál es más grande que el otro? ¿Calculan sus áreas? "Esto se hace descomponiendo tu hex en tris y rectos": ¿qué hex? Hay dos. "(o vota esta respuesta si quieres que te la ilustre)": ¿Hablas en serio?
jrsala
"¿Cómo alineas un triángulo con los ejes?" A: Alinee el camino del obj haciendo el área barrida. Elige una ventaja y usa trigonometría. "¿Cómo puedes saber cuál es más grande que el otro?" R: Por ejemplo, use la distancia entre dos puntos diagonalmente opuestos del rectángulo (centro del hex). "¿Qué hechizo?" A: el grande.
axon