Implementación de un cable de envoltura (como la cuerda Worms Ninja) en un motor de física 2D

34

He estado probando algo de física de la cuerda recientemente, y he descubierto que la solución "estándar" - hacer una cuerda a partir de una serie de objetos unidos con resortes o juntas - no es satisfactoria. Especialmente cuando el balanceo de la cuerda es relevante para el juego. Realmente no me importa la capacidad de una cuerda para envolverse o ceder (de todos modos, esto puede ser falso para las imágenes).

Para el juego, lo importante es la capacidad de la cuerda de envolver el entorno y luego desenvolverse. Ni siquiera tiene que comportarse como una cuerda; un "cable" hecho de segmentos de línea recta funcionaría. Aquí hay una ilustración:

Cuerda ninja, envolviendo obstáculos

Esto es muy similar a la "Cuerda Ninja" del juego Worms.

Como estoy usando un motor de física 2D, mi entorno está formado por polígonos convexos 2D. (Específicamente estoy usando SAT en Farseer).

Entonces mi pregunta es esta: ¿Cómo implementaría el efecto de "envoltura"?

Parece bastante obvio que el cable estará formado por una serie de segmentos de línea que se "dividen" y "unen". Y el segmento final (activo) de esa línea, donde se une el objeto en movimiento, será una unión de longitud fija.

Pero, ¿cuál es el algoritmo matemático involucrado para determinar cuándo y dónde se debe dividir el segmento de línea activo? ¿Y cuándo debe unirse al segmento anterior?

(Anteriormente, esta pregunta también se refería a hacer esto para un entorno dinámico; he decidido dividirlo en otras preguntas).

Andrew Russell
fuente

Respuestas:

18

Para determinar cuándo dividir la cuerda, debe mirar el área que cubre cada cuadro. Lo que haces es hacer una verificación de colisión con el área cubierta y la geometría de tu nivel. El área que cubre un columpio debe ser un arco. Si hay una colisión, debe hacer un nuevo segmento a la cuerda. Verifique las esquinas que chocan con el arco oscilante. Si hay varias esquinas que colisionan con el arco oscilante, debe elegir la que tenga el ángulo más pequeño entre la cuerda durante el cuadro anterior y el punto de colisión.

Diagrama útil de la situación de la cuerda ninja

La forma en que hace la detección de colisión es que para la raíz del segmento de cable actual, O, la posición final de la cuerda en el cuadro anterior, A, la posición final de la cuerda en el cuadro actual, B y cada punto de esquina P en un poligonal geometría de nivel, calcula (OA x OP), (OP x OB) y (OA x OB), donde "x" representa tomar la coordenada Z del producto cruzado entre los dos vectores. Si los tres resultados tienen el mismo signo, negativo o positivo, y la longitud de OP es menor que la longitud del segmento de la cuerda, el punto P está dentro del área cubierta por el columpio, y debe dividir la cuerda. Si tiene múltiples puntos de esquina en colisión, querrá usar el primero que golpee la cuerda, es decir, el ángulo donde el ángulo entre OA y OP es el más pequeño. Use el producto de puntos para determinar el ángulo.

En cuanto a unir segmentos, haga una comparación entre el segmento anterior y el arco de su segmento actual. Si el segmento actual ha oscilado del lado izquierdo al lado derecho o viceversa, debe unir los segmentos.

Para las matemáticas para unir segmentos usaremos el punto de conexión del segmento de cuerda anterior, Q, así como los que teníamos para el caso de división. Entonces, ahora querrás comparar los vectores QO, OA y OB. Si el signo de (QO x OA) es diferente del signo de (QO x OB), la cuerda se ha cruzado de izquierda a derecha o viceversa, y debe unir los segmentos. Por supuesto, esto también puede suceder si la cuerda se balancea 180 grados, por lo que si desea que la cuerda pueda envolverse alrededor de un solo punto en el espacio en lugar de una forma poligonal, es posible que desee agregar un caso especial para eso.

Por supuesto, este método asume que está haciendo una detección de colisión para el objeto que cuelga de la cuerda, de modo que no termine dentro de la geometría de nivel.

Pekuja
fuente
1
El problema con este enfoque es que los errores de precisión de punto flotante hacen posible que la cuerda "atraviese" un punto.
Andrew Russell el
16

Ha pasado un tiempo desde que jugué Worms, pero por lo que recuerdo, cuando la cuerda se enrolla alrededor de las cosas, solo hay una sección (recta) de cuerda que se mueve en cualquier momento. El resto de la cuerda se vuelve estático.

Entonces hay muy poca física real involucrada. La sección activa se puede modelar como un resorte rígido único con una masa en el extremo

Lo interesante será la lógica para dividir / unir secciones inactivas de la cuerda hacia / desde la sección activa.

Me imagino que habría 2 operaciones principales:

'Split': la cuerda ha cruzado algo. Divídalo en la intersección en una sección inactiva y la nueva sección más corta y activa

'Unir': la cuerda activa se ha movido a una posición donde la intersección más cercana ya no existe (¿esto puede ser una simple prueba de ángulo / punto del producto?). Vuelva a unirse a 2 secciones, creando una nueva sección más larga y activa

En una escena construida a partir de polígonos 2D, todos los puntos de división deben estar en un vértice en la malla de colisión. La detección de colisión puede simplificarse a lo largo de las líneas de 'Si la cuerda pasa sobre un vértice en esta actualización, ¿dividir / unir la cuerda en ese vértice?

bluescrn
fuente
2
Este tipo estaba justo en el lugar ... En realidad, no es un resorte "rígido", solo giras una línea recta alrededor ...
deslizador
Su respuesta es técnicamente correcta. Pero supuse que era obvio tener segmentos de línea y dividirlos y unirlos. Estoy interesado en el algoritmo / matemática real para hacer eso. He hecho mi pregunta más específica.
Andrew Russell el
3

Mira cómo se implementó la cuerda ninja en Gusanos :

  • La cuerda actúa como una partícula hasta que se adhiere a algo.
  • Una vez unida, la cuerda solo aplica una fuerza sobre el gusano.
  • Adjuntar a objetos dinámicos (como otros gusanos) sigue siendo un TODO: en este código.
  • No recuerdo si se admite envolver objetos / esquinas ...

Extracto relevante de ninjarope.cpp :


void NinjaRope::think()
{
    if ( m_length > game.options.ninja_rope_maxLength )
        m_length = game.options.ninja_rope_maxLength;

    if (!active)
        return;

    if ( justCreated && m_type->creation )
    {
        m_type->creation->run(this);
        justCreated = false;
    }

    for ( int i = 0; !deleteMe && i < m_type->repeat; ++i)
    {
        pos += spd;

        BaseVec<long> ipos(pos);

        // TODO: Try to attach to worms/objects

        Vec diff(m_worm->pos, pos);
        float curLen = diff.length();
        Vec force(diff * game.options.ninja_rope_pullForce);

        if(!game.level.getMaterial( ipos.x, ipos.y ).particle_pass)
        {
            if(!attached)
            {
                m_length = 450.f / 16.f - 1.0f;
                attached = true;
                spd.zero();
                if ( m_type->groundCollision  )
                    m_type->groundCollision->run(this);
            }
        }
        else
            attached = false;

        if(attached)
        {
            if(curLen > m_length)
            {
                m_worm->addSpeed(force / curLen);
            }
        }
        else
        {
            spd.y += m_type->gravity;

            if(curLen > m_length)
            {
                spd -= force / curLen;
            }
        }
    }
}
Leftium
fuente
1
Uhmn ... esto no parece responder a mi pregunta en absoluto. Todo el punto de mi pregunta es envolver una cuerda alrededor de un mundo hecho de polígonos. Gusanos parece no tener envoltura y un mundo de mapa de bits.
Andrew Russell
1

Me temo que no puedo darle un algoritmo concreto en la parte superior de mi cabeza, pero se me ocurre que solo hay dos cosas importantes para detectar una colisión con la cuerda ninja: cualquiera y todos los vértices potencialmente colisionantes sobre los obstáculos. dentro de un radio de la última "división" igual a la longitud restante del segmento; y la dirección actual de oscilación (en sentido horario o antihorario). Si creó una lista temporal de ángulos desde el vértice "dividido" a cada uno de los vértices cercanos, su algoritmo solo necesitaría preocuparse si su segmento estaba a punto de oscilar más allá de ese ángulo para cualquier vértice dado. Si es así, debe realizar una operación de división, que es fácil como un pastel: es solo una línea desde el último vértice de división hasta la nueva división, y luego se calcula un nuevo resto.

Creo que solo importan los vértices. Si está en peligro de golpear un segmento entre los vértices de un obstáculo, entonces su detección de colisión normal para el tipo que cuelga al final de la cuerda debería funcionar. En otras palabras, su cuerda solo se "enganchará" esquinas de todos modos, por lo que los segmentos intermedios no serán importantes

Lo siento, no tengo nada concreto, pero espero que eso te lleve a donde necesitas estar, conceptualmente, para que esto suceda. :)

Pi-3
fuente
1

Aquí hay una publicación que tiene enlaces a documentos sobre tipos similares de simulaciones (en contextos de ingeniería / académicos en lugar de juegos): https://gamedev.stackexchange.com/a/10350/6398

He probado al menos dos enfoques diferentes para la detección de colisión + respuesta para este tipo de simulación de "cable" (como se ve en el juego Umihara Kawase); al menos, creo que esto es lo que buscas: no parece haber un término específico para este tipo de simulación, solo tiendo a llamarlo "cable" en lugar de "cuerda" porque parece que la mayoría de las personas considere "cuerda" como sinónimo de "una cadena de partículas". Y, si desea el comportamiento de cuerda de ninja (es decir, puede empujar Y tirar), esto es más como un cable rígido que una cuerda. De todas formas..

La respuesta de Pekuja es buena, puede implementar la detección de colisión continua resolviendo el momento en que el área firmada de los tres puntos es 0.

(No puedo recordar completamente OTOH, pero puede abordarlo de la siguiente manera: encuentre el tiempo t cuando el punto a está contenido en la línea que pasa por b, c, (creo que hice esto resolviendo cuándo dot (ab, cb) = 0 para encontrar valores de t), y luego dado un tiempo válido 0 <= t <1, encuentre la posición paramétrica s de a en el segmento bc, es decir, a = (1-s) b + s c y si a está entre byc (es decir, si 0 <= s <= 1) es una colisión válida.

AFAICR también puede abordarlo al revés (es decir, resolver s y luego enchufarlo para encontrar t), pero es mucho menos intuitivo. (Lo siento si esto no tiene sentido, ¡no tengo tiempo para desenterrar mis notas y han pasado algunos años!))

Por lo tanto, ahora puede calcular todos los momentos en que ocurren los eventos (es decir, los nodos de cuerda deben insertarse o eliminarse); procesar el primer evento (insertar o eliminar un nodo) y luego repetir / repetir hasta que no haya más eventos entre t = 0 y t = 1.

Una advertencia sobre este enfoque: si los objetos que la cuerda puede envolver son dinámicos (especialmente si los está simulando Y sus efectos en la cuerda, y viceversa), entonces puede haber problemas si esos objetos se enganchan / atraviesan otro: el cable puede enredarse. Y definitivamente será un desafío evitar este tipo de interacción / movimiento (las esquinas de los objetos que se deslizan entre sí) en una simulación física de estilo box2d ... pequeñas cantidades de penetración entre objetos es un comportamiento normal en ese contexto.

(Al menos ... esto fue un problema con una de mis implementaciones de "cable").

Una solución diferente, que es mucho más estable pero que pierde algunas colisiones en ciertas condiciones es simplemente usar pruebas estáticas (es decir, no se preocupe por ordenar por tiempo, simplemente subdividir recursivamente cada segmento en colisión a medida que los encuentre), que puede ser mucho más robusto: el cable no se enredará en las esquinas y pequeñas cantidades de penetración estarán bien.

Creo que el enfoque de Pekuja también funciona para esto, sin embargo, hay enfoques alternativos. Un enfoque que he usado es agregar datos auxiliares de colisión: en cada vértice convexo v del mundo (es decir, las esquinas de las formas alrededor de las cuales puede envolver la cuerda), agregue un punto u formando el segmento de línea dirigida uv, donde u apunte "dentro de la esquina" (es decir, dentro del mundo, "detrás" de v; para calcular u puede lanzar un rayo hacia adentro desde v a lo largo de su normal interpolado y detenerse a cierta distancia después de v o antes de que el rayo se cruce con un borde del mundo y sale de la región sólida, o simplemente puede pintar manualmente los segmentos en el mundo usando una herramienta visual / editor de niveles).

De todos modos, ahora tienes un conjunto de "líneas de esquina" uv; para cada uv y cada segmento ab en el cable, verifique si ab y uv se cruzan (es decir, consulta de intersección estática, booleana lineseg-lineseg); si es así, recurse (divida el lineseg ab en av y vb, es decir, inserte v), registrando en qué dirección se dobló la cuerda en v. Luego, para cada par de linesegs vecinos ab, bc en el cable, pruebe si la dirección de curva actual en b es lo mismo que cuando se generó b (todas estas pruebas de "dirección de curvatura" son solo pruebas de área con signo); de lo contrario, combine los dos segmentos en ac (es decir, elimine b).

O tal vez me fusioné y luego me separé, lo olvido, ¡pero definitivamente funciona en al menos una de las dos posibles órdenes! :)

Teniendo en cuenta todos los segmentos de cable calculados para el cuadro actual, puede simular una restricción de distancia entre los dos puntos finales del cable (e incluso puede involucrar los puntos interiores, es decir, los puntos de contacto entre el cable y el mundo, pero eso es un poco más complicado) )

De todos modos, espero que esto sea de alguna utilidad ... los documentos en la publicación que he vinculado también deberían darte algunas ideas.

raigan
fuente
0

Un enfoque para esto es modelar la cuerda como partículas colisionables, conectadas por resortes. (bastante rígidos, posiblemente incluso como un hueso en su lugar). Las partículas chocan con el medio ambiente, asegurándose de que la cuerda se enrolle alrededor de los artículos.

Aquí hay una demostración con fuente: http://www.ewjordan.com/rgbDemo/

(Muévase hacia la derecha en el primer nivel, hay una cuerda roja con la que puede interactuar)

Rachel Blum
fuente
2
Uh, esto es específicamente lo que no quiero (ver la pregunta).
Andrew Russell
Ah Eso no estaba claro en la pregunta original. Gracias por tomarte el tiempo de aclararlo. (¡Gran diagrama!) Todavía iría con una serie de juntas fijas muy pequeñas, en lugar de hacer divisiones dinámicas, a menos que sea un gran problema de rendimiento en su entorno, es mucho más fácil de codificar.
Rachel Blum