¿Cómo calculo las rutas para objetos con aceleración limitada?

9

Por ejemplo, supongamos que tengo un automóvil y un automóvil tiene un radio de giro mínimo específico y quiero conducir ese automóvil desde el punto a hasta el punto b, pero el automóvil no está orientado hacia el punto b. ¿Cómo calculo una ruta al punto b? Ser capaz de especificar la orientación en el punto b también sería bueno (digamos que desea conducir hasta su camino de entrada y luego estacionarse en su garaje; no sirve de mucho si llega a su camino de entrada al conducir sobre su césped y están mirando hacia los lados :)

Un puntero a la documentación (o incluso solo un nombre) estaría perfectamente bien; estoy teniendo problemas para encontrar algo.

En mis intentos, funcionan en casos simples, pero fallan miserablemente en situaciones como cuando el punto b está más cerca de a que el radio de giro mínimo.

Por ejemplo, ¿cómo determinaría una ruta similar a esta (la ruta en negrita):

Solo un camino curvo con fines ilustrativos

editar: en mi problema real, hay algunas restricciones de ruta simples, pero ya tengo un algoritmo A * que funciona, pero permite que las cosas hagan cambios instantáneos en el rumbo, por lo que parece tonto ver que un automóvil de repente gira 90˚ en un centavo cuando llegan a un punto de inflexión.

xaxxon
fuente
gamedev.stackexchange.com/questions/86881/… pero no estoy seguro de entender la respuesta sobre cómo configurar el espacio 3d
xaxxon
"idealmente, este algoritmo podría manejar el cambio de velocidad" ¿El radio de giro mínimo está relacionado con la velocidad a medida que cambia, o es constante para cualquier automóvil?
DMGregory
Eliminaré esa parte. Por lo que estoy haciendo, es más "sim city" que "gran tourismo". Entiendo por qué preguntas eso y no estoy seguro de lo que estaba pensando cuando agregué eso, ya que entiendo que es irrelevante.
xaxxon
El diagrama de la curva de Bezier me recordó un poco a esta otra respuesta, también relacionada con la planificación del camino con aceleración limitada ; en ese caso, la aceleración se modeló como un propulsor de cohete direccional en lugar de un radio de giro, pero aún podría generar algunas ideas útiles.
DMGregory

Respuestas:

7

Todavía no he trabajado en las ecuaciones completas para esto, pero aquí hay algunas imágenes para ayudar a entender el problema. Se reduce a algo de geometría:

Un automóvil con círculos que indica su radio de giro. ( Iconos de autos a través de Kenney )

Desde cualquier punto de partida y orientación, podemos dibujar dos círculos con nuestro radio de giro mínimo: uno a la izquierda y otro a la derecha. Estos describen los puntos en el comienzo más estrecho posible de nuestro camino.

Podemos hacer lo mismo para cualquier posición final y orientación deseadas. Estos círculos describen el final más estrecho posible de nuestro camino.

Ahora el problema se reduce a encontrar un camino que une uno de los círculos iniciales a uno de los círculos finales, besando a cada uno a lo largo de su tangente.

(Esto supone que no necesitamos encontrar caminos en torno a obstáculos intermedios, lo cual no se mencionó en la pregunta. La respuesta de Stormwind se centra en cómo podemos usar la información del gráfico de navegación para este tipo de problemas. Una vez que tenemos la secuencia de nodos para pasar, podemos aplicar el siguiente método a cada segmento del plan).

Si, por simplicidad, usamos líneas rectas, obtenemos algo como esto:

Diagrama que muestra varios caminos que un automóvil podría tomar.

Esto nos da el caso limitante. Una vez que haya encontrado un camino con este método, puede inflar artificialmente uno o ambos círculos iniciales y finales para obtener un camino menos directo pero más suave, hasta el punto donde los dos círculos se besan.

Computando estos caminos

Analicemos los casos para una dirección de giro: digamos que comenzamos nuestro camino girando a la derecha.

El centro de nuestro círculo de giro a la derecha es:

startRightCenter = carStart.position + carStart.right * minRadius

Llamemos al ángulo de la sección recta de nuestro camino (medido desde el eje x positivo) pathAngle

Si dibujamos un vector desde rightCenterel punto donde dejamos el círculo de giro (en cuyo punto debemos estar orientados por pathAngle), entonces ese vector es ...

startOffset = minRadius * (-cos(pathAngle), sin(pathAngle))

Eso significa que el punto donde dejamos el círculo debe ser ...

departure = startRightCenter + startOffset

El punto en el que volvemos a entrar en un círculo de giro depende de si pretendemos terminar con un giro a la izquierda o a la derecha:

// To end with a right turn:
reentry = endRightCenter + startOffset

// To end with a left turn: (crossover)
reentry = endLeftCenter - startOffset

Ahora, si hemos hecho bien nuestro trabajo, la línea que se une departurea reentrydebería ser perpendicular a startOffset:

dot(reentry - departure,  startOffset) = 0

Y resolver esta ecuación nos dará los ángulos en los que esto es cierto. (Uso un plural aquí porque técnicamente hay dos ángulos de este tipo, pero uno de ellos implica conducir en reversa, lo que generalmente no es lo que queremos)

Sustituyamos el caso de giro a la derecha a la derecha como ejemplo:

dot(endRightCenter + startOffset - startRightCenter - startOffset, startOffset) = 0
dot(endRightCenter - startRightCenter, startOffset) = 0
pathAngle = atan2(endRightCenter - startRightCenter)

El caso cruzado es más complicado: es el que aún no he resuelto todas las matemáticas. Publicaré la respuesta sin por ahora, en caso de que sea útil para usted mientras resuelvo los detalles restantes.

Editar: Destino dentro del radio de giro mínimo

Resulta que este método a menudo funciona fuera de la caja incluso cuando el destino está más cerca de nuestra distancia mínima de giro. Al menos una parte de uno de los círculos de reentrada termina fuera del radio de giro, lo que nos permite encontrar un camino viable siempre que no nos importe que se vuelva un poco como un pretzel ...

Demostrar opciones al planificar la ruta a un destino cercano.

Si no nos gusta el camino que tomamos de esa manera (o si uno no es factible, no he revisado todos los casos exhaustivamente, tal vez hay uno imposible), siempre podemos conducir hacia adelante o hacia atrás hasta obtener un adecuado contacto de besos entre un círculo inicial y final, como se muestra arriba

DMGregory
fuente
Esa es una manera sencilla y sencilla de pensarlo, y es muy fácil trabajar con las tangentes en círculos. Hasta ahora solo he leído su respuesta, pero un problema con cada enfoque que he tomado es si el objetivo está dentro de los círculos de giro del punto de inicio.
xaxxon
La política más simple con la que tengo que lidiar es revertir hasta que el objetivo esté en uno de tus círculos de giro y luego convertirlo en uno. Con una orientación de destino, invertirías hasta que los círculos de giro de inicio y final se besen en algún lugar. Agregaré un diagrama para visualizar ese caso.
DMGregory
2
Un mes (y varias distracciones) más tarde conseguí que esto funcionara. Calculo 4 tangentes: las tangentes "externas" e "internas" (o "cruzadas"). Entonces start.left_circle a goal.left_circle, start.left_circle "cruzando" a goal.right_circle (y luego los otros dos simplemente cambian los círculos). Aquí hay una ruta "externa": youtube.com/watch?v=99e5Wm8OKb0 y aquí hay una ruta de "cruce": youtube.com/watch?v=iEMt8mBheZU
xaxxon
1

Esto depende mucho del resto de su modelo de datos para la navegación. Es decir. qué datos tiene a mano, qué puede agregar fácilmente y cómo los consume.

Tomando un escenario similar de un sistema de tráfico en el agua, y con la suposición de que

  • estás en un circuito de juego
  • tienes un sistema de ruta de nodo
  • sus autos se comportan como objetos autónomos que se controlan a sí mismos "desde adentro", utilizando su propia fuerza y ​​dirección
  • sus autos no se mueven como en rieles

podrías tener algo como a continuación (perdóname por la apariencia infantil de las imágenes)

ingrese la descripción de la imagen aquí

(Los cuadrados rojos son los nodos, las líneas rojas son las interconexiones de los nodos. Suponga que usó un solucionador de búsqueda de ruta que le dio a los nodos 1-9 para pasar; nodos 4-9 vistos en la imagen y desea pasar a través de los nodos indicados por la línea verde , al garaje en el nodo n. ° 9; sin embargo, no desea ir con precisión a la línea verde, sino que debe permanecer naturalmente en el carril del lado derecho y realizar maniobras suaves).

Cada nodo tendría metadatos que contienen, por ejemplo, un radio, o múltiples, para diversos fines. Uno de ellos es el círculo azul, que proporciona orientación para apuntar a los autos .

En cualquier ocasión , el vehículo debe conocer los siguientes dos puntos de nodo P (siguiente) y P (siguiente + 1), y sus posiciones. Naturalmente, el automóvil también tiene una posición. Un automóvil apunta a la tangente del lado derecho del círculo azul de metadatos de P (siguiente). Entonces, los autos van en dirección opuesta, por lo tanto, no chocarán. Apuntar a la tangente significa que el automóvil puede acercarse al círculo desde cualquier dirección y mantenerse siempre a la derecha. Este es un principio básico básico que se puede mejorar de muchas maneras.

Se necesita P (siguiente + 1) para determinar una distancia: cuando el automóvil llega a P (siguiente), o entra dentro de un radio de sus metadatos, puede ajustar su ángulo de dirección dependiendo de qué tan lejos esté P (siguiente + 1). Es decir. si está cerca, gire mucho, si está lejos, gire poco. Aparentemente, también debe haber otras reglas y condiciones de borde, por ejemplo, el cálculo de una distancia entre el automóvil y una línea de ayuda basada en las tangentes del lado derecho de P (siguiente) y P (siguiente + 1), y una corrección por eso - voluntad de permanecer en la línea discontinua (imagen superior) y punteada (imagen inferior).

En cualquier caso, cuando el automóvil pasa un nodo , lo olvida y comienza a mirar los dos siguientes .

A su pregunta Aparentemente, al llegar al nodo 7 (en la imagen de arriba, visto como el nodo 2 en la imagen a continuación), no puede girar lo suficiente .

ingrese la descripción de la imagen aquí

Una posible solución es construir algunas líneas de ayuda y mantener un objetivo todo el tiempo , y luego hacer que el automóvil se mueva según sus propias configuraciones físicas (acelere a una velocidad específica, retroceda más lento, tenga en cuenta los límites de velocidad de metadatos de los nodos, frene en un determinado o calculado G, etc.). Como se dijo, el automóvil es un objeto autónomo, autodescriptivo y autoportante en este escenario.

Tener las líneas verdes de ayuda 1,2,3 . Cuando el automóvil llega al círculo magenta , comienza a girar a la derecha. En este punto, ya puede calcular que no tendrá éxito (conoce la velocidad de giro máxima y puede calcular la curva, y puede ver que cruzará ambas líneas de ayuda 2 y 3). Gire la dirección completamente a la derecha y déjela avanzar (en incrementos físicos) y reduzca la velocidad cuando llegue a la línea de ayuda 3 (se acerca a los umbrales de uso, f (dist a la línea de ayuda), etc.). Cuando se encuentre en la línea de ayuda 3, ingrese al modo inverso , gire la dirección completamente opuesta . Déjelo retroceder hasta que llegue a la línea de ayuda 4(la línea de conexión entre el nodo 1 y 2 - google para "algoritmo de punto al lado de la línea"). Disminuya la velocidad, cuando llegue, vuelva al modo de avance , gire la rueda. Repita hasta que el camino esté despejado, esta vez aparentemente fue suficiente con 1 maniobra adicional.

Esta es la idea general: durante el ciclo del juego, o al verificar el sistema de tareas de juegos:

  • Verifique la posición, la velocidad, el ángulo, etc. del automóvil con los límites y el objetivo actuales del borde ,
  • Si aún no lo ha alcanzado , continúe con lo que estaba haciendo (deje que la física lo mueva; el automóvil tiene un rpm y una marcha). Inserte una nueva verificación en su sistema que, por ejemplo, en 0.1 s.
  • Si se alcanza , calcule nuevas condiciones, configure los datos y comience . Inserte una nueva comprobación para que ocurra en el sistema de que, por ejemplo, 0.1 s.
  • Complete el ciclo del ciclo: continúe, repita.

Al dar a los nodos y a los autos datos suficientes, habrá movimiento y continuación.

Editar: Y agregando: Esto naturalmente necesita un ajuste fino. Su comportamiento de simulación puede requerir diferentes líneas de ayuda, metadatos, círculos, cualquier cosa. Sin embargo, esto daría una idea de una posible solución.

Ventormenta
fuente
Me tomará un poco leer tu respuesta. Ya tengo el pathfinding genérico configurado y funcionando, pero permite que los objetos realicen una aceleración infinita en cualquier punto.
xaxxon
Aleatoriamente, tengo algo bastante parecido a lo que tú describes. La línea púrpura "en movimiento" se genera completamente a partir de dos líneas rectas: youtube.com/watch?v=EyhBhrkmRiY, pero no funciona en situaciones "ajustadas" y la curva no se utiliza para el trazado de ruta real.
xaxxon
0

Terminé haciendo lo que DMGregory sugirió y funciona bien. Aquí hay un código relevante (aunque no independiente) que se puede usar para calcular los dos estilos de tangentes. Estoy seguro de que este código no es eficiente, y probablemente ni siquiera sea correcto en todas las situaciones, pero hasta ahora funciona para mí:

bool Circle::outer_tangent_to(const Circle & c2, LineSegment & shared_tangent) const {
    if (this->direction != c2.direction) {
        return false;
    }
    if (this->radius != c2.radius) {
        // how to add it: http://mathworld.wolfram.com/Circle-CircleTangents.html
        // just subtract smaller circle radius from larger circle radius and find path to center
        //  then add back in the rest of the larger circle radius
        throw ApbException("circles with different length radius not supported");
    }

    auto vector_to_c2 = c2.center - this->center;
    glm::vec2 normal_to_c2;
    if (this->direction == Circle::CW) {
        normal_to_c2 = glm::normalize(glm::vec2(-vector_to_c2.y, vector_to_c2.x));
    } else {
        normal_to_c2 = glm::normalize(glm::vec2(vector_to_c2.y, -vector_to_c2.x));
    }

    shared_tangent = LineSegment(this->center + (normal_to_c2 * this->radius),
                                 c2.center + (normal_to_c2 * this->radius));
    return true;
}


bool Circle::inner_tangent_to(const Circle & c2, LineSegment & tangent) const {

    if (this->radius != c2.radius) {
        // http://mathworld.wolfram.com/Circle-CircleTangents.html
        // adding this is non-trivial
        throw ApbException("inner_tangents doesn't support circles with different radiuses");
    }

    if (this->direction == c2.direction) {
        // inner tangents require opposing direction circles
        return false;
    }

    auto vector_to_c2 = c2.center - this->center;
    auto distance_between_circles = glm::length(vector_to_c2);

    if ( distance_between_circles < 2 * this->radius) {
//      throw ApbException("Circles are too close and don't have inner tangents");
        return false;
    } else {
        auto normalized_to_c2 = glm::normalize(vector_to_c2);
        auto distance_to_midpoint = glm::length(vector_to_c2) / 2;
        auto midpoint = this->center + (vector_to_c2 / 2.0f);

        // if hypotenuse is oo then cos_angle = 0 and angle = 90˚
        // if hypotenuse is radius then cos_angle = r/r = 1 and angle = 0
        auto cos_angle = radius / distance_to_midpoint;
        auto angle = acosf(cos_angle);

        // now find the angle between the circles
        auto midpoint_angle = glm::orientedAngle(glm::vec2(1, 0), normalized_to_c2);

        glm::vec2 p1;
        if (this->direction == Circle::CW) {
            p1 = this->center + (glm::vec2{cos(midpoint_angle + angle), sin(midpoint_angle + angle)} * this->radius);
        } else {
            p1 = this->center + (glm::vec2{cos(midpoint_angle - angle), sin(midpoint_angle - angle)} * this->radius);
        }

        auto tangent_to_midpoint = midpoint - p1;
        auto p2 = p1 + (2.0f * tangent_to_midpoint);
        tangent = {p1, p2};

        return true;
    }
};

Aquí hay dos películas del código anterior en acción:

Aquí hay una ruta "externa": http://youtube.com/watch?v=99e5Wm8OKb0 y aquí hay una ruta de "cruce": http://youtube.com/watch?v=iEMt8mBheZU

Si este código ayuda pero tiene preguntas sobre algunas de las partes que no se muestran aquí, simplemente publique un comentario y debería verlo en uno o dos días.

xaxxon
fuente