Foros - Stratos

Programadores => Programación gráfica => Mensaje iniciado por: _Grey en 14 de Octubre de 2005, 10:57:48 PM

Título: Efecto Fuego Para J2me
Publicado por: _Grey en 14 de Octubre de 2005, 10:57:48 PM
 Holas, como ha todos les a pasado desde el anuncio del concurso también estoy tocando algo de J2ME, y esta tarde después de haber practicado algunas cosillas y recordando las limitaciones al tratar objetos Image, me dio por probar de aplicar el efecto fuego que todos habéis de haber visto alguna vez en alguna demo, además el otro dio ley algo de el en un librito de esos....

Me a parecido interesante para que se veo como es la programación en J2ME, y de paso me gustaría saber la opinión de los expertos en J2ME, sobre puntos concretos que no se si se podrían mejorar.

Bien, pasemos al código:

fire.java; este es el objeto MIDlet con el que se arranca la aplicacion.

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class fire extends MIDlet{
   public fire(){}

   public void startApp(){

       // Objeto genera efecto fuego
       FireCanvas fuego=new FireCanvas();

       // Canvas de fuego controlara el display
       Display.getDisplay(this).setCurrent(fuego);
   }

   public void pauseApp(){}

   public void destroyApp(boolean boo){
   }
}


FireCanvas.java; este es el objeto Canvas que genera el efecto del fuego

import java.util.*;
import javax.microedition.lcdui.*;

// Clase FireCanvas
public class FireCanvas extends Canvas implements Runnable {

   Image ImageColors[];    // Imagenes con los colores
   int buffer1[][];    // Buffer con fuego
   int buffer2[][];    // Buffer de apoño para calculos de fuego
   int flameSize;        // Alto ancho del pixel del fuego
   int width,height;    // Alto ancho de los bufferes buffer1 y buffer2
   int extincion;        // Velocidad a la que el fuego se extingue
   Random rand=new Random();    // Objeto de numeros aleatorios
   private Thread     t;        // Hilo de esta clase

   Image ImageSecundario;        // Backbuffer
   Graphics GraphicsSecundario;    // Backbuffer

   // Constructor de la clase Game
   public FireCanvas(){
       // Descubre dispositivo sobre el que trabajara
       System.out.println("Ancho:"+getWidth());
       System.out.println("Alto:"+getHeight());
       System.out.println("doubleBuffer:"+isDoubleBuffered());

       // Prepara backbuffer si es necesario
       ImageSecundario=null;
       GraphicsSecundario=null;
       if(!isDoubleBuffered()){
           ImageSecundario=Image.createImage(getWidth(),getHeight());
           GraphicsSecundario=ImageSecundario.getGraphics();
       }

       // Velocidad de extincion de fuego
       extincion=1;

       // Tamaños de los bueffer's, y del pixel del fuego
       flameSize=3;
       width=getWidth()/flameSize;
       height=getHeight()/flameSize;

       // Crea imagenes de color
       System.out.println("Creando imagenes de color");
       ImageColors= new Image[256];
       for(int i=0;i<256;i++){
           ImageColors[i]=Image.createImage(flameSize*width,flameSize);
           Graphics g=ImageColors[i].getGraphics();
           g.setColor(i,i/2,i/2);
           g.fillRect(0,0,flameSize,flameSize);
       }

       // Limpia buffer's
       buffer1=new int[height][width];
       buffer2=new int[height][width];
       System.out.println("limpia buffer");
       for(int y=0;y<height;y++)
           for(int x=0;x<width;x++){
               buffer1[y][x]=0;
               buffer2[y][x]=0;
           }

       // inicia hilo de ejecucion de la clase
       t= new Thread(this);
       t.start();
   }

   // Bucle(e hilo) donde se ejecuta el efecto
   public void run(){
       while(true){
           fireStep();        // Hace avanzar el efecto
           repaint();        // Llama a repintar
           serviceRepaints();    // Espera termine repintado (?)
       }
   }

   // Pinta pantalla
   public void paint(Graphics g){

       // Coge backbuffer si necesario
       Graphics gReal=g;
       if(GraphicsSecundario!=null) g=GraphicsSecundario;

       // Pinta buffer en pantalla
       for(int y=0;y<height;y++)
           for(int x=0;x<width;x++){
               int color=buffer1[y][x];
               g.drawImage(ImageColors[color],x*flameSize,y*flameSize,0);
           }

       // Pasa del backbuffer a buffer principal si es necesario
       if(GraphicsSecundario!=null) gReal.drawImage(ImageSecundario,0,0,0);

       System.out.println("Dibujado");
   }

   // Avanza efecto fuego
   private void fireStep(){
       // Emborrona
       for(int y=1;y<height;y++)
           for(int x=0;x<width;x++){
               // Hace la media de los valores del entorno
               int xless1=x==0?x:x-1;
               int yless1=y==0?y:y-1;
               int xmore1=x==width-1?x:x+1;
               int ymore1=y==height-1?y:y+1;
               int media=buffer1[yless1][x]+buffer1[ymore1][x]+
                   buffer1[y][xless1]+buffer1[y][xmore1];
               media/=4;
               media-=extincion;
               if(media<0) media=0;
               // Guarda en segundo buffer, una linea mas arriba
               buffer2[y-1][x]=media;
           }

       // Pasa de segundo buffer al primero
       for(int y=0;y<height-1;y++)
           for(int x=0;x<width;x++){
               buffer1[y][x]=buffer2[y][x];
           }

       // Rellena ultima linea con nuevos valores
       for(int x=0;x<width;x++){
           // Se asegura que no tenga signo, y redondea a 255, (el >>>1 es para quitar el

signo, nop?? lo e visto de una web... y me hace eso mismo)
           buffer1[height-1][x]=((rand.nextInt()>>>1)%200)+55;
       }
   }
}


Bien, este es el código, supongo que todos sabrían hacer un proyecto para KToolBar del Wireles toolkit de SUN.
Hay algunas cosas que reconozco que no se si es la mejor forma; Por ejemplo en el método run(), en lugar de usar un t.sleep(??) llamo al serviceRepaints(), por pruebas que hice el otro día, parece que el pintado de pantalla se hace en otro hilo y puedes llamar a repaint() antes de que termine, pero el serviceRepaints(), debería forzar ese pintado antes de continuar, pero en las pruebas que hice parece ir igual. Lo hago correctamente o no??
Por otro lado, no tengo nada para controlar la salida, en un principio no me hace falta, pero debería hacerlo por algún motivo?? cual??

Y ahora pasemos al código. Para pintar en pantalla pixel a pixel uso objetos Image, si, es mas rápido que un fillRect(). Trabajar a nivel de pixel es muy lento, así que se puede controlar el tamaño de los pixels del fuego, con un flameSize=3, tira bastante bien, con extinción se controla cuanto tarda en extinguirse el fuego, mas valor dura menos. La paleta se crea de forma muy simple, pero da muy buen resultado para lo que es.
El tema de las optimizaciones, use una referencia para controlar cual array escribe y cual lee en cada momento para eliminar los dos for's que pasan de un buffer a otro en fireStep(), pero se hace mas complicado de leer, y la mejora de rendimiento no existe, si va lento es por las llamadas a drawImage(), que se reducen ampliando el flameSize. Probé alguna cosa estrambótica, pero nada, parece que no correrá mas.

En fin si alguien quiere fuego para su móvil, ya puede ponérselo.

Saludos.

[Edit]
Ayadida imagen.

En la linea:
buffer1[height-1]