Oldschool fire effect from processing

BlitzMax Forums/BlitzMax Programming/Oldschool fire effect from processing

Hi. Im having some trouble converting a sample from processing (www.processing.org) for creating the old school pixel fire effect.

My fire just doesnt travel up the screen more that 3 pixels. Ok 6 pixels it is.

The code looks correct to me, but the result is different.



Below:
My Bmax code and luis2048's FireCube (without the cube) and with a HSB to RGB function:


SuperStrict
Framework BRL.GlMax2D
Import BRL.Random


AppTitle = "firefx, thanks to Merx for making it work ;)"
'converted from a processing example by luis2048.
'check www.processing.org

Global resX:Int=320, resY:Int=240
SetGraphicsDriver GLMax2DDriver()

Graphics resX,resY,0,0

Local frames:Int=0
Local fps:Int=0
Local sec:Long = MilliSecs()


Local fireEffect:Fire = New Fire
fireEffect.setup()

Global pixmap:TPixmap = CreatePixmap(resX,resY,PF_RGB888) 'PF_RGBA8888
Global mx:Int = resX/2
Global my:Int = resY/2
Global fireIntensity:Int = 39

While(Not KeyHit(KEY_ESCAPE) And Not AppTerminate()	)

	If(MouseX() > 3 And MouseX() < resX-3)
		mx = MouseX() + Rand(3)
	EndIf
	If(MouseY() > 3 And MouseY() < resY-3)
		my = MouseY() + Rand(3)
	EndIf
	
	If KeyHit(KEY_UP) fireIntensity:+1
	If KeyHit(KEY_DOWN) fireIntensity:-1
	If fireIntensity < 35 fireIntensity = 35
	If fireIntensity > 45 fireIntensity = 45

	fireEffect.draw()
	DrawPixmap(pixmap,0,0)
	DrawText("fps: "+fps, 0,0)
	DrawText("fireIntensity: "+Int(fireIntensity),0,25)	
	If(sec < MilliSecs() )
		sec = MilliSecs() + 1000
		fps = frames
		frames = 0
	EndIf
	frames :+1
	
	Flip
	Cls
Wend
End


Type Fire
	Field fire:Int[,]
	Field paletteR:Int[]
	Field paletteG:Int[]
	Field paletteB:Int[]
	Field calc1:Int[]
	Field calc2:Int[]
	Field calc3:Int[]
	Field calc4:Int[]
	Field calc5:Int[]

	Method setup()
		calc1 = New Int[resX]
		calc3 = New Int[resX]
		calc4 = New Int[resX]
		calc2 = New Int[resY]
		calc5 = New Int[resY]
		
		fire = New Int[resX,resY]
		paletteR = New Int[255]
		paletteG = New Int[255]
		paletteB = New Int[255]
		
		'generate palette
		Local i:Int
		For i=0 Until 255
			Local h#= i/3.0
			Local s#= 1.0
			Local l#= i*2.0 Mod 255
			Local rgb:Float[] = New Float[3]
			HSV2RGB( h, s, l, rgb )
			paletteR[i] = rgb[0]*255.0
			paletteG[i] = rgb[1]*255.0
			paletteB[i] = rgb[2]*255.0
		Next
		
		'precalc wich pixel values to add during animation
		For Local x:Int=0 Until resX
			calc1[x] = x Mod resX
			calc3[x] = (x - 1 + resX) Mod resX
			calc4[x] = (x + 1)        Mod resX
		Next
		
		
		For Local y:Int=0 Until resY
			calc2[y] = (y + 1) Mod resY
			calc5[y] = (y + 2) Mod resY
		Next
	End Method
	
	Method draw()
		'randomize the bottom row of the fire buffer
		For Local x:Int = 0 Until resX
			fire[x,resY-1] = Rand(0,190)
		Next
		
		fire[mx,my] = 100
		fire[mx+1,my] = 100
		fire[mx-1,my] = 100
		fire[mx+2,my] = 100

		
		'get pointer to the first pixel
		Local pixel:Byte Ptr = PixmapPixelPtr(pixmap) 'RGBA
		
		'Do the fire calc for every pixel
		For Local y:Int = 0 Until resY
			For Local x:Int = 0 Until resX
				fire[x,y] =((.. 
							  fire[ calc3[x], calc2[y] ]..
							+ fire[ calc1[x], calc2[y] ]..
							+ fire[ calc4[x], calc2[y] ]..
							+ fire[ calc1[x], calc5[y] ]  *32) / fireIntensity) 'change the last value here for more/less fire
							'multiply by 32 or shl 5
						
				' Output everything to screen using our palette colors
				pixel[0] = paletteR[ fire[x,y] ] 'R
				pixel[1] = paletteG[ fire[x,y] ] 'G
				pixel[2] = paletteB[ fire[x,y] ] 'B
				'pixel[3] = 1 'A
				pixel:+3 'next pixel
			Next
		Next
	End Method
End Type

' Value ranges:
' hue [0,360]
' saturation [0.0,1.0]
' value [0.0, 1.0]
' rgb[] values to be set
Function HSV2RGB(H:Float, S:Float, V:Float, rgb:Float[])
	Local r:Float = 0
	Local g:Float = 0
	Local b:Float = 0
	
	If V = 0                                                         
		R = 0.0;
		G = 0.0;
		B = 0.0;                                           
	Else If S = 0  
	  R = V;                                                            
	  G = V;                                                            
	  B = V;    
	Else       
	  Local hf:Float = H / 60.0;  
	  Local i:Int  = Int( Floor(hf) );        
	  Local f:Float  = hf - i;    
	  Local pv:Float  = V * ( 1 - S )     
	  Local qv:Float  = V * ( 1 - S * f );  
	  Local tv:Float  = V * ( 1 - S * ( 1 - f ) );  
	  Select( i )
		Case 0                                                       
	      R = V;                                                        
	      G = tv;                                                       
	      B = pv;
		Case 1                                                        
	      R = qv;                                                       
	      G = V;                                                        
	      B = pv; 
	    Case 2                                                        
	      R = pv;                                                       
	      G = V;                                                        
	      B = tv;                                                       
		Case 3                                                        
	      R = pv;                                                       
	      G = qv;                                                       
	      B = V; 
	    Case 4                                                        
	      R = tv;                                                       
	      G = pv;                                                       
	      B = V;                                                         
		Case 5                                                         
	      R = V;                                                        
	      G = pv;                                                       
	      B = qv;                                                       
		Case 6                                                         
	      R = V;                                                        
	      G = tv;                                                       
	      B = pv;
	    Case -1
		  R = V;
		  G = pv;
		  B = qv;
	End Select
 EndIf

	rgb[0] = r;
	rgb[1] = g;
	rgb[2] = b;
End Function



/**
 * Fire Cube demo effect
 * by luis2048.
 * 
 * A rotating wireframe cube with flames rising up the screen.
 * The fire effect has been used quite often for oldskool demos.
 * First you create a palette of 256 colors ranging from red to 
 * yellow (including black). For every frame, calculate each row 
 * of pixels based on the two rows below it: The value of each pixel, 
 * becomes the sum of the 3 pixels below it (one directly below, one 
 * to the left, and one to the right), and one pixel directly two 
 * rows below it. Then divide the sum so that the fire dies out as 
 * it rises.
 */
 
// This will contain the pixels used to calculate the fire effect
int[][] fire;

// Flame colors
color[] palette;
int[] calc1,calc2,calc3,calc4,calc5;

PGraphics pg;

void setup(){
  size(640, 360, P2D);
  
  // Create buffered image for 3d cube
  pg = createGraphics(width, height, P2D);

  calc1 = new int[width];
  calc3 = new int[width];
  calc4 = new int[width];
  calc2 = new int[height];
  calc5 = new int[height];

  colorMode(RGB);

  fire = new int[width][height];
  palette = new color[255];

  // Generate the palette
  for(int x = 0; x < palette.length; x++) {
    //Hue goes from 0 to 85: red to yellow
    //Saturation is always the maximum: 255
    //Lightness is 0..255 for x=0..128, and 255 for x=128..255
    float myHue = x/3.0;
    float mySaturation = 1.0;
    float myLightness = x*2 % 255;
    float rgb[] = new float[3];
    //HSL2RGB( myHue, mySaturation, myLightness, rgb);
    hsv_to_rgb( myHue, mySaturation, myLightness, rgb);
    palette[x] =color( rgb[0], rgb[1], rgb[2] );
  }

  // Precalculate which pixel values to add during animation loop
  // this speeds up the effect by 10fps
  for (int x = 0; x < width; x++) {
    calc1[x] = x % width;
    calc3[x] = (x - 1 + width) % width;
    calc4[x] = (x + 1) % width;
  }
  
  for(int y = 0; y < height; y++) {
    calc2[y] = (y + 1) % height;
    calc5[y] = (y + 2) % height;
  }
}

void draw() {
  // Randomize the bottom row of the fire buffer
  for(int x = 0; x < width; x++)
  {
    fire[x][height-1] = int(random(30,190)) ;
  }

  loadPixels();

  int counter = 0;
  // Do the fire calculations for every pixel, from top to bottom
  for (int y = 0; y < height; y++) {
    for(int x = 0; x < width; x++) {
      // Add pixel values around current pixel

      fire[x][y] =
          ((fire[calc3[x]][calc2[y]]
          + fire[calc1[x]][calc2[y]]
          + fire[calc4[x]][calc2[y]]
          + fire[calc1[x]][calc5[y]]) *32) / 129;

      // Output everything to screen using our palette colors
      pixels[counter] = palette[fire[x][y]];
      counter++;
    }
  }

  updatePixels();
}

// 
void hsv_to_rgb(float H, float S, float V,float rgb[]){
  float R=0;
  float G=0;
  float B=0;
  
  if( V == 0 )                                                        
  { 
    R = 0; 
    G = 0; 
    B = 0; 
  }                                            
  else if( S == 0 )                                                   
  {                                                                   
    R = V;
    G = V;
    B = V;
  } 
  else
  {    
    float hf = H / 60.0f;
    int    i  = (int)floor( hf );
    float f  = hf - i;
    float pv  = V * ( 1 - S );
    float qv  = V * ( 1 - S * f );
    float tv  = V * ( 1 - S * ( 1 - f ) );
    switch( i )
      {
      case 0:
        R = V;                                                        
        G = tv;                                                       
        B = pv;                                                       
        break; 
  case 1:
        R = qv;                                                       
        G = V;                                                        
        B = pv;                                                       
        break;                                                        
  case 2:
        R = pv;                                                       
        G = V;                                                        
        B = tv;                                                       
        break; 
  case 3:
        R = pv;                                                       
        G = qv;                                                       
        B = V;                                                        
        break;                                                        
 case 4:
        R = tv;                                                       
        G = pv;                                                       
        B = V;                                                        
 break;   
  	case 5:                                                         
        R = V;                                                        
        G = pv;                                                       
        B = qv;                                                       
        break; 
  case 6: 
        R = V;                                                        
        G = tv;                                                       
        B = pv;                                                       
        break;                                                        
 case -1:
        R = V;                                                        
        G = pv;                                                       
        B = qv;                                                       
        break;                                                        
      }                                                               
  }

  rgb[0] = R;
  rgb[1] = G;
  rgb[2] = B;
  
}



Edit: Updated my code. Thanks Merx!

First guess would be that there is a rounding error going on here, this can happen when you do calculations with integers and floats or doubles, you could try:

1, making sure all variables are float or double
2. extending any values used to be float e.g. 10 -> 10.0

Had a mess and altering the 129.0 value on this line chanes the effect...

+ fire[ calc1[x], calc5[y] ]  *32) / 129) 


For me values under 40 and greater than 35 seemed to work best!

Great! It works now. Still its very different from the processing version, maybe its because processing uses OpenGL...

In theory they should not be although it depends on how the pixels are formatted e.g. how the bits correspond to colours RGBA ect.

Other than that it could be something to do with the calculations, e.g. a rouding error of some kind.

To really check you would have to build and run both versions for a cycle with a full debug printout and compare the values!