Círculo dentro del círculo colisión

9

En uno de mis proyectos tengo un área de juego en forma de círculo. Dentro de este círculo se mueve otro círculo pequeño. Lo que quiero hacer es evitar que el círculo pequeño se mueva fuera del más grande. A continuación puede ver que en el cuadro 2 el círculo pequeño está parcialmente afuera, necesito una forma de moverlo de regreso justo antes de que esté a punto de moverse afuera. ¿Cómo se puede hacer esto?

Ejemplo básico

Además, necesito el punto de colisión a lo largo del arco del círculo grande para poder actualizar la velocidad del círculo pequeño. ¿Cómo se podría calcular este punto?

Lo que me gustaría hacer es antes de mover el círculo pequeño, predigo su próxima posición y si está afuera encuentro el tiempo de colisión entre t = 0 y t = 1 (t = 1 paso de tiempo completo). Si tengo el tiempo de colisión t, entonces solo muevo el círculo pequeño durante t en lugar de un paso de tiempo completo. Pero nuevamente, el problema es que no sé cómo detectar en ese momento que la colisión ocurre cuando se trata de dos círculos y uno está dentro del otro.

EDITAR:

Ejemplo de punto de colisión (verde) que quiero encontrar. Tal vez la imagen está un poco apagada, pero entiendes la idea.

ingrese la descripción de la imagen aquí

dbostream
fuente

Respuestas:

10

Supongamos que el círculo grande tiene centro Ay radio Ry el círculo pequeño tiene centro By radio rmoviéndose hacia la ubicación C.

Hay una manera elegante de resolver este problema, utilizando sumas de Minkovski (sustracciones, en realidad): reemplazar el disco de radio Rcon un disco de radio R-ry el disco de radio rcon un disco de radio 0, es decir. un punto simple ubicado en B. El problema se convierte en un problema de intersección línea-círculo.

Entonces solo necesita verificar si la distancia ACes menor que R-r. Si es así, los círculos no chocan. Si es mayor, acaba de encontrar el punto Den BCa una distancia R-rde Ay esta es la nueva ubicación del centro de su pequeño círculo. Esto es equivalente a encontrar ktal que:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

Sustituyendo vec(AD)con vec(AB) + vec(BD)da:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

Siempre que la posición inicial estuviera dentro del círculo grande, esta ecuación cuadrática ktiene una raíz positiva. Aquí se explica cómo resolver la ecuación, en pseudocódigo:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

Con este valor de k, el nuevo centro del círculo pequeño es Dtal que BD = kBC.

Editar : agregar solución de ecuación cuadrática

sam hocevar
fuente
Gracias, se ve elegante, pero no estoy seguro de entenderlo. Por ejemplo: "solo encuentre el punto D en BC a una distancia Rr de A". Hice un dibujo para tratar de entenderlo mejor. Entonces, si comenzamos en B (AX, AY- (Rr)) y C es donde terminaremos con la velocidad actual. La forma en que entiendo el texto citado: Encuentre un punto D en el segmento de línea BC que esté a una distancia de Rr de A. Pero la forma en que lo veo en la imagen que dibujé es que solo exactamente B está Rr lejos de A. Todos otros puntos estarán> Rr lejos de A. ¿Qué me estoy perdiendo?
dbostream
@dbostream No te falta nada. Si los dos círculos ya están en contacto, entonces no hay una colisión real para detectar : la colisión ocurre en B, y k=0. Ahora, si lo que desea es la resolución de colisión , no lo he cubierto en mi respuesta porque requeriría conocimiento sobre las propiedades físicas de los objetos. ¿Qué se supone que debe pasar? ¿Debería el círculo interno rebotar dentro? O rodar? ¿Barrer?
Sam Hocevar
Quiero que el círculo pequeño comience a deslizarse a lo largo del arco del círculo grande. Entonces, si no me equivoco, quiero el punto de colisión en el arco del círculo grande para poder usar su normalidad para actualizar la velocidad.
dbostream
@dbostream si el movimiento se debe restringir de tal manera, le sugiero que siga esa restricción lo antes posible: si la velocidad es V, haga que el círculo interno avance a lo V*tlargo de la circunferencia del R-rcírculo. Esto significa una rotación de V*t/(R-r)radianes en ángulo alrededor del punto A. Y el vector de velocidad se puede girar de la misma manera. No es necesario saber lo normal (que siempre está orientado hacia el centro del círculo de todos modos) o actualizar la velocidad de otra manera.
sam hocevar
Todavía necesito mover el círculo pequeño al punto de colisión antes de rotar. Y cuando intenté rotar la posición usando una matriz de rotación, la nueva posición no estaba exactamente (pero casi) Rr lejos del centro del círculo grande, pero esa pequeña diferencia fue suficiente para que mi prueba de colisión fallara en la próxima carrera. ¿Tengo que rotar la posición para encontrar la nueva? ¿No es posible usar operaciones de vectores como si pudiéramos chocar con una pared recta?
dbostream
4

Digamos que el círculo grande es el círculo A y el círculo pequeño es el círculo B.

Verifique si B está dentro de A:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

Si en el cuadro n-1B estaba dentro de A y en el cuadro nB está fuera de A y el tiempo entre cuadros no era demasiado grande (también conocido como B no se movía demasiado rápido), podemos aproximar el punto de colisión simplemente encontrando las coordenadas cartesianas de B relativo a A:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

Luego podemos convertir estos puntos en un ángulo:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

Si quiere saber más exactamente qué tB está fuera de A por primera vez, puede hacer una intersección de círculo de rayos en cada cuadro y luego comparar si la distancia desde B hasta el punto de colisión es mayor, entonces la distancia B puede viajar dado que es velocidad actual. Si es así, puede calcular el tiempo exacto de colisión.

Roy T.
fuente
Gracias, pero ¿es realmente correcto disparar un rayo desde el centro del círculo pequeño al hacer la prueba de intersección? ¿No terminaremos con el escenario en el medio de esta imagen ? Quiero decir que el primer punto en el arco del círculo pequeño que choca con el círculo grande no es necesariamente el que está en el arco en la dirección de la velocidad. Creo que necesito algo como en el escenario inferior de la imagen a la que me vinculé. He agregado una nueva imagen en la primera publicación que muestra un ejemplo de lo que creo que necesito.
dbostream
Hmm, supongo que ese escenario es posible. Tal vez pruebe con un nuevo círculo C que tenga B.Radius + el movimiento máximo de B en este cuadro, verifique si eso colisiona con A y luego ejercite el punto en C que está más lejos de A. (No he intentado esto por cierto)
Roy T.
3
Usando menos palabras: si (distancia (A, B))> (Ra-Rb) ocurre una colisión y simplemente mueve el círculo pequeño para obtener una distancia igual a Ra-Rb. De lo contrario, mueves el círculo pequeño normalmente. Por cierto, @dbostream está utilizando algo similar a una forma simplificada de Contactos especulativos, intente buscar eso.
Darkwings
@Darkwings +1 tiene toda la razón, ¡y eso lo hace parecer mucho más simple!
Roy T.
Suena simple porque eliminé toda la geometría base necesaria. En lugar de nombrarlo 'colisión', podría haberlo llamado boundAB ya que eso es lo que realmente es: el vector libre AB unido a (0,0). Una vez que lo normaliza, obtiene tanto la ecuación para el conjunto de líneas rectas paralelas a AB como también un vector unitario útil. Luego puede multiplicar ese vector unitario para cualquier distancia D y agregar los parámetros recién encontrados a A para encontrar el punto de colisión que necesita: C (Ax + Dx, Ay + Dy). Ahora suena más complicado pero es lo mismo: P
Darkwings
0

Deje (Xa, Ya) la posición del círculo grande y su radio R, y (Xb, Yb) la posición del círculo más pequeño y su radio r.

Puede verificar si estos dos círculos colisionan si

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

Para averiguar la posición de la colisión, encuentre el momento exacto en que los círculos colisionan, utilizando una búsqueda binaria pero con un número fijo de pasos. Dependiendo de cómo esté hecho su juego, puede optimizar esta parte del código (proporcioné esta solución para que sea independiente de cómo se comporta la bola pequeña. Si tiene aceleración constante o velocidad constante, esta parte del código puede optimizarse y reemplazado con una fórmula simple).

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

Una vez que sepa el tiempo de colisión, calcule las posiciones de los dos círculos en el momento final y el punto de colisión final es

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya
vlad
fuente
0

He implementado una demostración de una pelota que rebota en un círculo en jsfiddle usando el algoritmo descrito por Sam Hocevar :

http://jsfiddle.net/klenwell/3ZdXf/

Aquí está el javascript que identifica el punto de contacto:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
klenwell
fuente