miércoles, febrero 22, 2006

Sistema de Partículas II : Explosiones

Esta serie que estoy escribiendo tiene fundamentalmente 2 objetivos. El primero es mostrar una posible manera de comenzar a hacer una sistema de partículas, comenzando por lo básico y luego con la práctica añadir efectos y mejoras al sistema. El otro objetivo es repuntar ideas de mi parte para luego formar el sistema independiente, expansible a medida que sea necesario. Dejando los discursos de lado, en este capítulo voy a mostrar una manera básica de implementar explosiones, y a partir de ella se podrá adaptar una explosión distinta para cada necesidaad. Más adelante veremos otros tipos de implementación para explosiones mas avanzadas.


  • Set up y movimiento de las partículas

Para modelar una explosión lo que hago primero que nada es inicializar cada partícula estableciendo las siguientes características:
  • Punto en común de orígen
  • Dirección
  • Energía

En las explosiones aéreas la dirección de cada partícula es distribuída en forma esférica centrada en el punto de orígen. Este tipo de explosiones podrían modelarse con la distribución de direcciones de cada partícula de manera uniforme. Si cortamos la esfera que forma esta distribución con un plano donde ocurre la explosión, una pared por ejemplo, la dirección e cada partíucula será distribuida en forma semi-esférica dado que cada una es disparada en la dirección normal al plano de impacto. Habría que adaptar esto ya que si la explosión surge en un ángulo las partículas deberían explotar correctamente para dar mas realismo, dado que necesariamente la distribución será esférica o semi-esférica.

Veamos el código de inicialización para cada partícula (set up). Como parámetros de entrada ingresamos las padrtículas, el vector coordenada de impacto y la cantidad total de partículas. Este último campo es para una implementación estática del sistema, podría obviarse con una celda dummy en el caso de implementación dinámica (recomendado). Minimizar la cantidad de operaciones con memoria (new y delete) hace mas eficiente el sistema, en el caso de la implementacion dinámica a medida que se necesitan partículas se siguen los siguientes pasos: Determinar si hay partículas muertas pero con alojo en memoria, en este caso resucitar las necesarias. Sin no hay muyertas,, entonces empezar a crear las que sean necesarias, pero claro, esto tampoco es barato. El alojamiento en memoria tiende a crecer dependiendo de la cantidad de eventos simultáneos, probablmenete sean partículas muertas las que estén alojadas. Mediante la implementacoión estática también logramos eficiencia, pero a un costo bastante mayor de entrada, dado que aunque haya solo 1 partícula viva en el sistema, siempre se tendrá el máximo de partículas alojado en memoria. Para las explosiones por lo general se conoce de entrada la cantidad de partículas máxima del sistema, y todas interactúan en la explosión (inicialmente). Sin embargo no siempre las explosiones toman el mismo tamaño y energía, entonces en necesario acotar conjuntos independientes del total para adaptar la explosión deseada. Es aquí donde entra en utilidad el campo active que indica si la partícula está activa (viva) o si no lo está (muerta). En nuestro caso usaremos arreglos dinámicos, y este campo solo servirá para descartar partículas muertas, no para acotar sistemas, por ahora almenos.

NOTA (sobre los vectores): en las imlementaciones aparecen directamente funciones de la clase vector. No vale la pena detenerese en esta clase, es muy intuitiva y las funciones también lo son, de hecho hablan por si solas.

// constantes del sistema
#define PARTICLES 1000
#define GRAVITY 9.8
#define ENERGY 7
...
// set up del sistema
particle* p= new particle*[PARTICLES];
...


void explosion::setup(particle* &p, vector coord, long tpar)=
{
float len, ie, dx, dy;
particle* paux=p;

for(long i=0; i < tpar; i++)
{
//activamos la partícula
paux->active= true;

//coordenadas del orígen
paux->pos.setx(coord.getx());

paux->pos.sety(coord.gety());

//dirección aleatoria al explotar
//negativa o positiva (a suerte)
paux->dir.setx(((rand()%4)-1.5));
paux->dir.sety(((rand()%4)-1.5));

//setup de energía inicial
ie= (rand() % ENERGY) + 1;

//registramos las coord para el cálculo
//del módulo, esto debe ir en la clase vector
dx= paux->pos.getx();
dy= paux->pos.gety();

//cálculo del módulo
len= sqrt(dx*dx + dy*dy);

//norma
if (len != 0.0)
len= 1.0 / len;

//normalizar para encontrar la dir.
//del vector, la distr. deja de ser uniforme
paux->pos.setx((paux->pos.getx())*len*ie);
paux->pos.sety((paux->pos.gety())*len*ie);

//color inicial
paux->color= 255;

paux++
}
}



Y el vector velocidad? La velocidad inicial no se tiene en cuenta en esta implementación, todas se mueven a velocidad constante. Recordar que esto es un primer intento, luego de tener la idea es fácil mejorarlo y agregar lo que venga. Con esta implementación mover las partóuclas es muy sencillo. Esta inicialización posiciona las partículas en el núcleo de la explosión. Ahora debemos expandir las partículas, recordando que no aceleran para este caso. Una explosión con partículas aceleradas y considerando adicinoalmente la aceleración de la gravedad, aunque se forme con pixeles, va de lujo en cualquier cosa :p, pero primero bastaría con formar una explosión común y corriente, para luego empezar a modificar a gusto teniendo el concepto.
Consideraremos una explosión en una habitación, para observar las colisiones. La habitación será un cadrado de 100x100 pixeles cuyo vértice superior izquierdo se ubica en el punto de coordenadas (350,250). También consideraremos la aceleración de la gravedad, y un efecto muy simple de "chispa pixelada" para indicar las partículas próximas a la muerte.

Veamos ahora la implementación para el movimiento.


void explosion::move_particles(particle*p, long tpar)
{
particle* paux= p;

for(long i=0; i < tpar; i++)
{
if (paux->active) {

//movemos la partícula de acuerdo al
//vector dir
paux->pos.setx(paux->pos.getx()+paux->dir.getx());
paux->pos.sety(paux->pos.gety()+paux->dir.gety());

//chequeamos colisiones con piso: muerte
if (paux->pos.gety() > 350)
paux->active= false;
else

//colisiones con techo
if (paux->pos.gety()) < 250)
{
paux->pos.sety(250);
paux->dir.setx(paux->dir.getx()/4);
paux->dir.sety(-(paux->dir.gety())/2);
}
else

//no actúan fuerzas en el eje x -> 0
//gravedad en el eje y -> 9.8
//en update sumo gravedad a la dirección en y
paux.update(0, GRAVITY);

//colisiones con paredes
if (paux->pos.getx() < 350)
{
paux->pos.setx(350);
paux->dir.setx(-(paux->dir.getx())/2);
paux->dir.sety(paux->dir.gety()/4);
}
else

if (paux->pos.getx() > 450) {
paux->pos.setx(450);
paux->pos.setx(-(paux->dir.getx())/2);
paux->dir.sety(paux->dir.gety()/4);
}

//si la partícula está próxima al piso
//hacemos un efecto de chispa cambiando
//el color del pixel

if (paux->pos.gety() >= 348)
paux->color= (rand() % 128) + 128;

}
paux++;
}
}



Esta implementación requiere de sincronismo. SDL_GetTicks() es la solución. Para los que no tienen ni idea de que es lo que hace esta función, devuelve (en milisegundos) el tiempo desde que se ha encendido la máquina. Podemos entonces implementar una función, que dado un punto de inicio retorna el tiempo transcurrido hasta el punto actual (en términos de tiempo):

inline Uint32 lapsedTime(Uint32 &t)
{
return t= SDL_GetTicks()-t;
}

Esto es todo por hoy. Recordar que éstas implementaciones pueden ser optimizadas. Pero al optimizar algunas cosas teóricas sencillas pueden resultar engorrosas, y lo importante es que quede el concepto, la implementación en este caso es lo de menos, cada cual implementa a su manera :).





2 Comments:

Anonymous Anónimo said...

No entiendo donde usas la energía, podrias dar una mano?

6:58 p. m.  
Blogger ZuNdFoLGe said...

La energía es decrementada cada vez que la partícula colisiona con paredes o techo, con la implementación dada esto se traduce en decrementar las componentes del vector dirección.

7:23 p. m.  

Publicar un comentario

<< Home