The technique didn’t change at all. It’s still the same. As first it’s needed to make good palette. The better palette, the better fire looks. I just used PNG image with 1-pixel in height and 256 pixels in width. Of course it’s possible to make the palette on fly by simple gradient generation method but this time I just wanted to skip it and get to the main stream. The colors have been grabbed and placed inside the integer array ( using getRGB() ).
public void init()
{
// Grabbing palette
try
{
Image img = Image.createImage( "/fire5.png" );
final int imgW = img.getWidth();
palette = new int[ imgW ];
img.getRGB( palette, 0, imgW, 0, 0, imgW, 1 );
LAST_COLOR = palette.length - 1;
COLOR_LENGTH = palette.length;
img = null;
// the brightest color must be white:
palette[ LAST_COLOR ] = 0xFFFFFF;
// ..and the darkest is black:
palette[ 0 ] = 0x0;
// Clean the pixelBuffer
final int bufferSize = VIEWPORT_SCREEN_WIDTH * VIEWPORT_HEIGHT;
for ( int i = 0; i < bufferSize; i ++ )
{
pixelBuffer[ i ] = 0;
if ( i < VIEWPORT_TOTAL_LENGTH )
{
colorIndexBuffer[ i ] = 0;
}
}
}
catch ( Exception e )
{}
}
The method above does several other things too. It clears all the buffers. It may looks bit strange that colorIndexBuffer is not same size as pixelBuffer. Due to some performance reasons I have implemented upscaling, so the fire is painted using smaller array and then it may fill entire screen. The array called colorIndexbuffer stores actuall colors indices as the technique can’t base on colors 32-bit values.
The algorithm works pretty simple. To be able to “burn” pixels it’s necessary to set some pixels from the bottom of the framebuffer on fire. This must be done as much random as possible. Also it’s good to feed the fire every n-frames. The less n is the more fire we’ll see. But also setting n to low ( like 1 or 2 ) will not give good effect. ‘n’ should be adjusted later. When fire is set up then every pixel from the framebuffer is recalculated to get average color index from 4 its neigbours and itself, in other words we need to get all 5 values and divide by 5. Then store new index in the array. Dividing by five is done by two bit shifts and addition.
There’s not much more to say about the effect. Here’s the code of main tick method which updates framebuffer.
int m_lightUpCounter = 1;
public void tick()
{
if ( ! start ) return;
int sx = VIEWPORT_WIDTH;
int offset = VIEWPORT_TOTAL_LENGTH - 1;
// First it's needed to light up the fire at the lowest row
int idx = VIEWPORT_TOTAL_LENGTH - 1 - VIEWPORT_WIDTH;
int idx_screen = VIEWPORT_SCREEN_TOTAL_LENGTH - 1 - VIEWPORT_SCREEN_WIDTH;
int x = VIEWPORT_WIDTH_M_1;
try
{
// Now we need to calculate colors for each pixel and fill both buffers with data
while ( true )
{
int newIndex = 0;
final int topOffset = idx - VIEWPORT_WIDTH;
final int bottomOffset = idx + VIEWPORT_WIDTH;
final int p_bottom = VIEWPORT_TOTAL_LENGTH - bottomOffset;
if ( ( ( topOffset >> 31 ) & 0x1 ) == 0 )
{
newIndex += colorIndexBuffer[ topOffset ];
}
if ( bottomOffset < VIEWPORT_TOTAL_LENGTH )
{
newIndex += colorIndexBuffer[ bottomOffset ];
if ( x != 0 )
{
newIndex += colorIndexBuffer[ bottomOffset - 1 ];
}
if ( x < VIEWPORT_WIDTH_M_1 )
{
newIndex += colorIndexBuffer[ bottomOffset + 1 ];
}
}
newIndex >>= 2;
newIndex += colorIndexBuffer[ idx ];
newIndex >>= 1;
colorIndexBuffer[ idx ] = newIndex;
pixelBuffer[ idx_screen ] = palette[ newIndex ];
pixelBuffer[ idx_screen - 1 ] = palette[ newIndex ];
if ( x == 0 )
{
x = VIEWPORT_WIDTH;
}
x --;
if ( idx < VIEWPORT_WIDTH )
{
break;
}
idx --;
idx_screen -= 2;
}
m_lightUpCounter <<= 1;
int lastVal = 0;
final int NUMBER_OF_LIGHT_UPS = 12;
while ( sx != 0 )
{
int i = NUMBER_OF_LIGHT_UPS;
int value = 0;
while ( ( value = rnd.nextInt() ) == lastVal );
lastVal = value;
rnd.setSeed( value );
while ( i != 0 && sx != 0 )
{
// We need to know if we should put the pixel or not.
if ( ( m_lightUpCounter & ( 1 << STEPS ) ) != 0 )
{
if ( ( value & 3 ) == 1 )
{
colorIndexBuffer[ offset ] = LAST_COLOR;
}
if ( ( value & 3 ) == 2 )
{
colorIndexBuffer[ offset ] = 0;
}
}
else
{
colorIndexBuffer[ offset ] -= 8;
if ( colorIndexBuffer[ offset ] < 0 )
{
colorIndexBuffer[ offset ] = 0;
}
}
offset --;
value >>= 2;
i--;
sx --;
}
}
if ( ( m_lightUpCounter & SHR_STEPS ) != 0 )
{
m_lightUpCounter = 1;
}
offset = 0;
for ( int i = 0; i < VIEWPORT_HEIGHT; i ++ )
{
System.arraycopy( pixelBuffer, offset, pixelBufferDoubleV, (offset << 1 ), VIEWPORT_SCREEN_WIDTH );
System.arraycopy( pixelBuffer, offset, pixelBufferDoubleV, (offset << 1 ) + VIEWPORT_SCREEN_WIDTH, VIEWPORT_SCREEN_WIDTH );
offset += VIEWPORT_SCREEN_WIDTH;
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
And the final effect:
Also I did some experiment by porting exactly same code to XNA using C#:
No comments:
Post a Comment