[XNA] Jugando con las Leyes de Newton con XNA (II de II)

 

Las leyes de Newton darían para 100 artículos pero tranquilos, no tengo tanto tiempo libre 😛 En este artículo completaré el experimento iniciado en la primera parte, en base a la tercera ley de Newton: toda acción provoca una reacción equivalente (insultante resumen para los puristas de la física). Esto viene a decir que si un obteo A ejerce una fuerza sobre un objeto B, este recibe una fuerza equivalente en la dirección opuesta.

Por ello partiremos del código anterior. Si os acordáis, teníamos unos círculos que reaccionaban a la «fuerza ejercida por el ratón» -el ratón evidentemente no ejerce fuerza, fué una simulación-. En esta ocasión seguiremos trabajando con los círculos. Ahora múltiples círculos colisionarán entre sí, reaccionando según la tercera ley de newton.

Lo primero que haremos, para evitar que se nos desmadren los círculos, va a ser evitar que estos, a fuerza de impactos, puedan salir de la pantalla. Será como si existiese un muro que las impidiese salir del monitor. Esto se consigue con poco código. Necesitamos saber las dimensiones de la pantalla, y la posición del círculo, que ya tenemos. Si hay colisión con la pared, invertimos la velocidad, por eso de la acción-reacción 🙂

            // Actualizamos el vector posición del objeto, dependiendo de todos los demás!!
            Vector2 nuevaPosicion = this.posicion + this.velocidad;
          

            if (nuevaPosicion.X >= 0
                && nuevaPosicion.X <= this.dimensionesContenedor.X – this.textura.Width)
            {
                // Establecemos la nueva posición de X
                this.posicion.X = nuevaPosicion.X;
            }
            else
            {
                // Invertimos el vector velocidad, ya que hay colisión con la pared
                this.velocidad.X *= -1;
            }

            if (nuevaPosicion.Y >= 0
                && nuevaPosicion.Y <= this.dimensionesContenedor.Y – this.textura.Height)
            {
                // Establecemos la nueva posición de Y
                this.posicion.Y = nuevaPosicion.Y;
            }
            else
            {
                // Invertimos el vector velocidad, ya que hay colisión con la pared
                this.velocidad.Y *= -1;
            }

Ahora si ejecutamos, los círculos rebotarán como locos si impactamos sobre ellos con el mouse. Pero esto es lo fácil… ahora nos falta detectar las colisiones entre los círculos, y producir la consecuente reacción. No nos asustemos, primero hay otros ajustes que podemos hacer. Antes estaba controlando el impacto entre el cursor y el círculo mediante la creación de un objeto de tipo Rectangle, con las dimensiones del círculo, y evaluar si hacía intersección con otro rectángulo basado en las dimensiones del puntero. Eso es poco preciso…

Necesitamos una solución más elegante, pero sobretodo más precisa, y que sea eficiente. En vez de comprobar la intersección de dos objetos Rectangle, calcularemos la distancia entre el círculo y el puntero. Si esta es menor o igual al radio del círculo… tenemos colisión! Vendría ser algo así como lo siguiente:

De cara al rendimiento, aquí estamos haciendo una comprobación de «todos contra todos». Esto normalmente no se haría así en un juego más optimizado, en este artículo de mi blog anterior (catalán) explicaba algunas de estas técnicas más comunes.

Por suerte ni siquiera debemos acordarnos de las matemáticas de vecotres para conocer la distancia entre dos vectores en XNA. Lo que se traduce en la siguiente línea de código:

// Si hay colisión, le damos caña al objeto
Vector2 centroCirculo = new Vector2(this.posicion.X + this.textura.Width / 2, this.posicion.Y + this.textura.Height / 2);
Vector2 posicionMouse = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);

if (Vector2.Distance(centroCirculo, posicionMouse) < this.textura.Width / 2)

 

Ahora viene lo realmente difícil.. la colisión contra la pared era sencilla, porque era un elemento recto, y basta con invertir la velocidad del objeto que colisiona -en nuestro caso el círculo-. Para reaccionar a la colisión entre dos círculos hay que considerar que cada uno de ellos tendrá un vector dirección distinto… lo cual, como decía, complica las cosas. Además, vamos a implementar una reacción a la colisión de tipo elástico, algo inexistente en el mundo real (en el espacio habría una situación parecida, pero nunca totalmente elástica). En el mundo real, gobernado por las leyes físicas, las reacciones a las colisiones son inelásticas, es decir, que la velocidad después del impacto no será la misma a la velocidad antes del impacto. Esto es porque se produce una liberación de energía calorífica con este (y que además produce el sonido que podemos oir del impacto).

Para resolver la colisión entre múltiples círculos utilizaremos este algoritmo diseñado por Sunil Changrani. Esto, en XNA, viene a ser lo siguiente:

        private void resolverColision(ObjetoFisico circuloA, ObjetoFisico circuloB)
        {
            float x1 = circuloA.posicion.X;
            float y1 = circuloA.posicion.Y;
            float dx = circuloB.posicion.X – x1;
            float dy = circuloB.posicion.Y – y1;

            float radio = this.textura.Width / 2;

            // Calculamos la distancia
            float distancia = (float)Math.Sqrt(dx * dx + dy * dy);

            // Normal de los vectores y puntos intermedios
            float normalX = dx / distancia;
            float normalY = dy / distancia;
            float midPointX = (x1 + circuloB.posicion.X) / 2;
            float midPointY = (y1 + circuloB.posicion.Y) / 2;
           
            // Impide que los círculos se solapen
            circuloA.posicion.X = midPointX – normalX * radio;
            circuloA.posicion.Y = midPointY – normalY * radio;
            circuloB.posicion.X = midPointX + normalX * radio;
            circuloB.posicion.Y = midPointY + normalY * radio;

            // Calcular la nueva velocidad de los círculos
            float spdVector = (circuloA.velocidad.X – circuloB.velocidad.X) * normalX + (circuloA.velocidad.Y – circuloB.velocidad.Y) * normalY;

            float dvx = spdVector * normalX;
            float dvy = spdVector * normalY;

            // Asignar los nuevos valores
            circuloA.velocidad.X -= dvx;
            circuloA.velocidad.Y -= dvy;
            circuloB.velocidad.X += dvx;
            circuloB.velocidad.Y += dvy;
        }

El resultado sería este, un montón de «bolas locas» rebotando entre ellas y por toda la pantalal al pasar el ratón por encima. Otro experimento interesante es modificar la constante coeficienteFriccion y ponerla a 0, con ello las bolas nunca perderán velocidad y rebotarán entre ellas «eternamente». 

El código puede ser descargado en este enlace.

14 comentarios sobre “[XNA] Jugando con las Leyes de Newton con XNA (II de II)”

  1. Muy buen ejemplo. He creado un modesto motor 3d con colisiones BoudongBox i BoudingShere. He utilizado tu content pipelin modificado de terreny i tus formulas para trayectorias. todo ok. Tengo un problema para la «multicolision» Basicamente unos proyectiles en movimiento que deben colisionar con diferentes objetos del escenario 3D. A ver quando tengo algo de tiempo y cuelgo un video. Ya de paso que soft utilizas para obtener el vido de XNA. Gracia!

    Salutacions!

  2. Me has servido de inspiración, lo que necesitava para la «multicolisión» 3d. Colisión de proyectiles en movimiento con los objetos del escenario 3D.

    Esto me ha desencallado

    // Lista de objetos físicos
    List objetos;

    objetos = new List();

    // Rellenamos rapidillo la lista de objetos
    for(int i=0; i<= numeroObjetos; i++) { objetos.Add(new ObjetoFisico(new Vector2(i * 80, i* 60), texturaGenerica)); } Saludos!

  3. Encantado de ayudarte. De todos modos utilizar un List no se si sería lo más eficiente -teniendo en cuenta que lo quieres para un engine, cuyo objetivo es exprimir los recursos al máximo-.

    Seguramente preferirás utilizar Arrays, con los que obtendrás mayor rendimiento. Al parecer, los arrays aprovechan mejor los sistemas multithreading (algo habitual en videojuegos).

    http://developer.amd.com/documentation/articles/Pages/PerformanceOptimizationofWindowsApplicationsonAMDProcessors2.aspx

    Prometo intentar no usar lists a partir de ahora, pero es que facilitan tanto el código… 🙂

  4. Por cierto, si te refieres al soft de grabación, es CamStudio, que tiene sus limitaciones pero es free. Ya me harás saber cuando cuelgues el vídeo, me encantará verlo.

    Saludos

  5. Pués probé anteriormente con Array i no encontré la forma, hasta que vi en tu codigo el uso de List para Clases. Volveré a probar con Array.

    Si no tengo éxito me quedo con el List intentando que se le parezca a esto:
    «But what about the other cases, where the versatility of a linked list is necessary? Fortunately, there are some tricks for making linked lists work faster. These techniques are especially relevant for relatively small data items, in a list that doesn’t change too rapidly.»

  6. mmm, no entiendo porqué no te funcionaba con un array.

    De todos modos tampoco creo que la diferencia entre usar el array y la lista vaya a representar una enorme diferencia, habría que hacer algunas pruebas para comprobarlo.

    El List<> tiene eso, que es muy cómodo, con su .Add, .RemoveAt y todo eso.

  7. se ve muy interesante sí. Lo que no se es si podré instalarme el GS 2.0 teniendo instalado el 3.1, habrá que probar. O eso o buscar alguna guía de como actualizar el proyecto.

    Saludos

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *