Particles

Hoy, tras un descansito, vamos a seguir dibujando cosas en OpenGL, algo bastante genérico que vamos a necesitar para casi cualquier proyecto: un sistema de partículas.

En el de hoy tampoco nos vamos a calentar mucho la cabeza, es mejor ir por partes y ver en un principio lo fácil que va a resultar tener muchas partículas y moverlas sin dificultad. En la siguiente entra ya os prometo hacer un sistema usable.

Crear un Proyecto OpenGL

Como siempre vamos a empezar desde 0 y es creando un proyecto en OpenGL Es, abrimos el XCode vamos a File > New Proyect y seleccionamos un proyecto “OpenGL ES Application“, pulsamos el botón choose y le damos el nombre que nos interese:

Nuestra base de Sprite

Para empezar partimos de nuestro sample Sprites en OGL para tener las rutinas básicas para dibujar Sprites en 2D por pantalla. De todos modos al final del tuto se incluyen tanto el .m como el .h, aunque igualmente deberíais echarle un vistazo porque aquí no explico como agregar el framework necesario ni como adjuntar la imagen que usaremos.

Particles and Dots

La lógica al crear cualquier sistema de partículas será siempre el de tener una clase principal que hará de contenedor de las partículas, sobre está clase será donde crearemos las partículas, desde donde se cargarán las texturas y desde donde se dibujarán. Esta clase tendrá un array con todas las partículas. Nunca trabajéis con partículas dinámicas, se supone que cuando se usan partículas para simular cualquier efecto o simplemente para añadir vistosidad a una aplicación vamos a estar continuamente creando y destruyéndolas por lo que no es nada optimo tirar de peticiones de memoria para esos casos, plantearos siempre un número máximo de partículas y crear un array de tamaño fijo.

Cada particula tendrá siempre una variable booleana que indica si la partícula está en uso o no (‘live’ suele ser un nombre bastante descriptivo). Cuando el sistema necesita una partícula recorre el array buscando una partícula muerta y nos la da … si no encuentra ninguna no deberia pasar nada, simplemente no se crea la partícula y listo.

De momento empezaremos viendo nuestra clase Particles:

Particles: h and m

 C |  copy code |? 
01
 
02
 #import "Dot.h" 
03
 #define MAXDOT 64 
04
 
05
 @interface Particles : NSObject { 
06
 
07
 GLuint texture; 
08
 Dot *dot[MAXDOT]; 
09
 } 
10
 
11
 -(void)inicializar; 
12
 -(void)setTouch:(GLfloat)x y:(GLfloat)y; 
13
 -(void)draw; 
14
 

Esto es nuestra cabecera del sistema de partículas, vemos que nos hemos puesto un máximo de 64 partículas, tenemos una declaración para la textura, y el array de la clase Dot (cada una de las partículas).
Y la implementación es de lo mas sencillo que vais a encontrar. Como veis esta clase solo inicializa los items del array, le da el touch a cada punto y los dibuja.

 C |  copy code |? 
01
 
02
 #define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI) 
03
 #define RANDOM_SEED() srandom(time(NULL)) 
04
 #define RANDOM_INT(__MIN__, __MAX__) ((__MIN__) + random() % ((__MAX__+1) - (__MIN__))) 
05
 
06
 @implementation Particles 
07
 
08
 -(void)inicializar 
09
 { 
10
 // cargamos la textura 
11
 texture  = [GL loadTexture:@"Particles-1.png"]; 
12
 
13
 // inicializamos el random 
14
 RANDOM_SEED(); 
15
 
16
 // y creamos todos los dots 
17
 for(int i=0;i<MAXDOT;i++) 
18
 { 
19
 dot[i] = [Dot alloc]; 
20
 [dot[i] createNew:true]; 
21
 } 
22
 
23
 } 
24
 
25
 -(void)setTouch:(GLfloat)x y:(GLfloat)y 
26
 { 
27
 // le damos el touch a todas particulas 
28
 for(int i=0;i<MAXDOT;i++) 
29
 [dot[i] setTouch:x y:y]; 
30
 
31
 } 
32
 
33
 -(void) draw 
34
 { 
35
 glLoadIdentity(); 
36
 
37
 // activamos la textura 
38
 [GL setTexture:texture]; 
39
 
40
 // y a dibujar todo 
41
 for(int i=0;i<MAXDOT;i++) 
42
 { 
43
 [dot[i] steep]; 
44
 [dot[i] render]; 
45
 } 
46
 } 
47
 
48
 @end

Lo del Touch era una sorpresa, jeje, vamos a dibujar los puntos en pantalla pero tiene algo mas de gracia si hacemos que interactuen al tocar la pantalla. Pues bien … nuestro sistema de partículas (como hemos dicho al principio) solo hará de contenedor, como podeis ver al hacer un touch no evaluamos nada, simplemente le damos ese touch a todas las partículas y que sean ellas las que decidan si hacen algo.

Siempre que podáis delegar sobre clases inferiores mucho mejor para todos, imaginar que en un futuro vamos a tener particulas que se ven afectada por el touch, y otras que no, mediante esta filosofía no tendríamos ningún problema, porque será la partícula quien decida lo que hace. Si en nuestra clase ‘Particles’ hubiéramos tomado alguna decisión ya estaríamos limitados a que todas las partículas se comportasen igual.

 Particles.m (892 Bytes)
 Particles.h (419 Bytes)
 Particles-1.png (31.54 Kb)

Dots: h and m

 C |  copy code |? 
01
 
02
 
03
 @interface Dot : NSObject { 
04
 
05
 GLfloat posx, posy; // Posicion 
06
 GLfloat dx, dy;     // desplazamiento XY 
07
 GLfloat radio;      // radio 
08
 GLfloat vy;	        // velocidad en Y 
09
 GLfloat frame;      // frame de la textura 
10
 GLfloat colorR, colorG, colorB, colorA; 
11
 } 
12
 
13
 -(void)setPosition:(GLfloat)x y:(GLfloat)y; 
14
 -(void)setRadio:(GLfloat)value; 
15
 -(void)setVy:(GLfloat)value; 
16
 -(void)setFrame:(GLfloat)value; 
17
 -(void)setColor:(int)r g:(int)g b:(int)b alpha:(int)alpha; 
18
 
19
 -(void)setTouch:(GLfloat)x y:(GLfloat)y; 
20
 -(void)createNew:(bool)inScreen; 
21
 
22
 -(void)steep; 
23
 -(void)render; 
24
 
25
 @end 
26
 

Para cada particula podemos ver las propiedades que tiene y como casi todos sus métodos son para establecer esas propiedades (a pesar de que en este ejemplo no los vamos usar desde el contenedor).

Así que vamos a ir a la parte de la implementación que requiera algo más de explicación:

 C |  copy code |? 
01
 
02
 -(void)createNew:(bool)inScreen 
03
 { 
04
 // primero un random para las coordenadas XY (siempre creamos la particula en pantalla) 
05
 int x = RANDOM_INT(0, 320) - 160; 
06
 int y = RANDOM_INT(0, 600) - 300; 
07
 
08
 // y si no la queriamos dentro, la bajamos 600 pixeles pabajo 
09
 if(!inScreen) y += 600; 
10
 
11
 // random para el radio 
12
 int r = RANDOM_INT(4, 16); 
13
 
14
 // la velocidad va en funcion del radio tb, asi los puntos 
15
 // mas grandes se moverán mas lentamente 
16
 float velocidad = 2.0f - r * 0.1f; 
17
 int fr = RANDOM_INT(0, 4) ; 
18
 
19
 [self setPosition:x y:y]; 
20
 [self setRadio:r * (fr+2) * 2]; 
21
 [self setVy:velocidad]; 
22
 [self setFrame:fr]; 
23
 [self setColor:RANDOM_INT(0, 0xFF) 
24
 g:RANDOM_INT(0, 0xFF) 
25
 b:RANDOM_INT(0, 0xFF) 
26
 alpha:RANDOM_INT(0, 0x80)]; 
27
 
28
 dx = 0; 
29
 dy = 0; 
30
 } 
31
 

Esto podría ser nuestro constructor ya que inicializa la partícula en pantalla, nos interesa que sea un método aparte y no el constructor porque lo vamos a llamar cada vez que queramos resetear una partícula. Como veis pilla casi todo de forma aleatoria, lo único que deberíamos resaltar es que mediante una booleana que le damos como parámetro le indicamos si queremos que la partícula nazca por la pantalla o fuera de ella. Así por ejemplo todas las partículas se crearán por primera vez dentro de la pantalla, pero luego al resetearlas las pondremos fuera de pantalla.

Touching particles

 C |  copy code |? 
01
 
02
 
03
 Este metodo se llama para cada partícula cuando se toca la pantalla, lo que hace es medir la distancia entre el punto donde tocamos y la partícula (no tiene en cuenta el radio .. aunque seria muy facil de implementar) y si se encuentra a una distancia menor de 40 pixels desplaza la partícula (ponemos en dx y dy el factor de separación que hay en el ejex y en el ejey respectivamente, así luego haremos que la partícula se desplace en función de la dirección inversa a donde se ha tocado) 
04
 
05
 -(void)setTouch:(GLfloat)x y:(GLfloat)y 
06
 { 
07
 GLfloat sepx = posx-x; 
08
 GLfloat sepy = posy-y; 
09
 
10
 GLfloat sep = sepx * sepx + sepy * sepy; 
11
 
12
 if(sep < 2000) // esto parece mucho pero son unos 45 pixels 
13
 { 
14
 dx += sepx ; 
15
 dy += sepy ; 
16
 } 
17
 } 
18
 

Un detalle importante a la hora de medir distancias entre 2 puntos: todos sabemos (o deberiamos) que la distancia entre 2 puntos es la raíz cuadrada del cuadrado de sus componentes sqrt(x*x+y*y), hasta ahi todo es fácil, pero se puede ganar algo de tiempo si obviamos el sqrt, y en lugar de esperar un resultado de 45 pixels si hiciéramos la raíz, trabajar con 45*45, es lo mismo decir sqrt(x^2+x^2)<45 que x^2+y^2<45*45

Drawing

 C |  copy code |? 
01
 
02
 -(void)steep 
03
 { 
04
 posx += dx; 
05
 posy += dy; 
06
 
07
 dx*=0.7f; 
08
 dy*=0.7f; 
09
 
10
 posy -= vy; 
11
 
12
 // cuando la particula se sale por arriba creamos otra, dandole un false para 
13
 // que se cree fuera de pantalla, por abajo 
14
 if(posy + radio < - 240) [self createNew:false]; 
15
 } 
16
 
17
 En primer lugar mirar como la posición <strong>x </strong>e <strong>y </strong>se desplazan en funcion de <strong>dx </strong>y  <strong>dy </strong>que se establecian con el touch, y luego esas variables dx y dy se van multiplicando con 0.7f para que paso a paso se vayan desacelerando. Luego la posicion y se va decrementando en funcion de vy, osea .. se mueven todas parriba. Y finalmente comprobamos cuando las partículas quedan fuera de pantalla para crearlas de nuevo. 
18
 
19
 -(void)render 
20
 { 
21
 // ponemos el color de esta particula 
22
 [GL setColor:colorR g:colorG b:colorB alpha:colorA]; 
23
 
24
 // ponemos el frame de la textura 
25
 [GL setFrame:frame]; 
26
 
27
 // y dibujamos el sprite 
28
 [GL drawSprite:posx-radio y1:posy-radio x2:posx+radio y2:posy+radio]; 
29
 
30
 }

 Dot.m (2.15 Kb)
 Dot.h (791 Bytes)

Lo unico que debemos indicar en nuestro método ‘render’ es que hemos separado los métodos setTexture y setFrame, ya que todas las partículas usan la misma textura y no es nada lógico activar la textura con cada partícula …. una buena (muy muy muy buena) forma de optimizar esto es dibujar las partículas agrupadas por el frame que usan, así podríamos activar el frame 0 y dibujar solo las partículas que usen ese frame, y así sucesivamente.

 GL.m (4.93 Kb)
 GL.h (649 Bytes)

EAGLView

Empezamos por añadir estas variables en EAGLView.h

 C |  copy code |? 
1
 
2
 Particles *particles; 
3
 bool touching; 
4
 GLfloat touchx, touchy; 
5
 

Y seguimos con el .m, lo primero que vamos a hacer es meter los métodos para manejar el touch de la pantalla, esto es una versión reducida de lo que ya hicimos en nuetro Touch-sample:

 C |  copy code |? 
01
 
02
 #pragma mark - 
03
 #pragma mark === Touch handling  === 
04
 #pragma mark 
05
 
06
 // Handles the start of a touch 
07
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
08
 { 
09
 UITouch *touch = [[event allTouches] anyObject]; 
10
 CGPoint location = [touch locationInView:touch.view]; 
11
 
12
 touching = true; 
13
 touchx = location.x - 160; 
14
 touchy = location.y - 240; 
15
 } 
16
 
17
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
18
 { 
19
 UITouch *touch = [[event allTouches] anyObject]; 
20
 CGPoint location = [touch locationInView:touch.view]; 
21
 
22
 touching = true; 
23
 touchx = location.x - 160; 
24
 touchy = location.y - 240; 
25
 } 
26
 
27
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
28
 { 
29
 touching = false; 
30
 } 
31
 
32
 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 
33
 { 
34
 touching = false; 
35
 } 
36
 

Y a continuación viene la inicialización que se encarga de crear nuestra clase de partículas:

 C |  copy code |? 
01
 
02
 -(void)inicializar 
03
 { 
04
 particles = [Particles alloc]; 
05
 [particles inicializar]; 
06
 
07
 touching = false; 
08
 
09
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
10
 
11
 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 
12
 glEnable(GL_BLEND); 
13
 
14
 }

Y finalmente nuestro método Draw solo haría esto:

 C |  copy code |? 
01
 
02
 - (void)drawView 
03
 { 
04
 [self clearBuffer]; 
05
 
06
 if(touching) 
07
 [particles setTouch:touchx y:touchy]; 
08
 
09
 [particles draw]; 
10
 [self swapBuffer]; 
11
 }

 EAGLView.m (5.81 Kb)
 EAGLView.h (1.22 Kb)

  • Delicious
  • Twitter
  • Facebook
  • Meneame
  • WordPress
  • Digg
  • Google Reader
  • Google Bookmarks
  • Slashdot
  • Share/Bookmark

Leave a Comment