Realtime Plasma using per pixel blits

Thanks BurnPizza and Riven!

Updated with a polar plasma.


//****************************************************************************
//
//   Simple Per Pixel Plasma Generator
//   Richard Eric Lope (relminator)
//   http://rel.phatcode.net
//
//   Thanks to BurntPizza and Riven for teaching me how to 
//   do a fast pixel write directly to a buffered image
//
//****************************************************************************


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.JFrame;
import javax.swing.JPanel;


//****************************************************************************
//
//   Main Entry Point
//
//****************************************************************************

public class SimplePlasma extends JFrame 
{

	private static final long serialVersionUID = 1L;

	final static int SCREEN_WIDTH = 640;
	final static int SCREEN_HEIGHT = 480;
	
	public SimplePlasma() 
    {

        add( new Screen() );

        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
        setLocationRelativeTo( null );
        setTitle( "Plazma by Relminator" );
        setResizable( false );
        setVisible( true );
        
    }

    public static void main(String[] args) 
    {
        new SimplePlasma();
    }
}


//****************************************************************************
//
//   
//
//****************************************************************************

class Screen extends JPanel implements Runnable  
{

	private static final long serialVersionUID = 1L;
	
	private Thread animator;
	
    private static final double FIXED_TIME_STEP = 1/60.0;
    private double accumulator = 0;
	
    private int fps = 0;
    private int framesPerSecond = 0;
    private double previousTime = 0;
    private double oldTime = 0;
    
    Plasma plasma = new Plasma();
    
    public Screen() 
    {

        addKeyListener(new TAdapter());    
        setFocusable(true);
        setBackground(Color.BLACK);
        setDoubleBuffered(true);		  
        
    }

    public void addNotify() 			  
    {
        super.addNotify();
        animator = new Thread( this );
        animator.start();
    }
    
    public void paint( Graphics g ) 	
    {
        super.paint(g);
        
        Graphics2D g2D = (Graphics2D)g;

        render( g2D );
        
        Toolkit.getDefaultToolkit().sync();		
        g.dispose();							
        
    }

    private class TAdapter extends KeyAdapter 	
    {

        public void keyPressed( KeyEvent e ) 
        {
            int key = e.getKeyCode();			
            if( key == KeyEvent.VK_ESCAPE ) 
            {
            	System.exit(0);
            }
            	
        }
        
    }
    
    
    public void update() 		// update ng gameloop here
    {
    	
    	plasma.update();
        repaint();

    }

    public void render( Graphics2D g2D )		// drawing dito
    {
    	
    	
    	
		g2D.drawImage( plasma.getImage(), 
				 	   0,
				 	   0,
				 	   null );

        g2D.setColor( Color.BLUE );
        g2D.drawString( "FPS :" + fps, 0, 10 );
        
                
    }

	public void run()		// runnable interface needs run() implemented
	{
		
		double dt = getDeltaTime( getSystemTime() );
		
		// gameloop
        while( true ) 
        {

        	dt = getDeltaTime( getSystemTime() );
			if( dt > FIXED_TIME_STEP ) dt = FIXED_TIME_STEP;
			accumulator += dt;
			
			while( accumulator >= FIXED_TIME_STEP )
			{
				update();
				accumulator -= FIXED_TIME_STEP;
				
			}
			
			try 
            {
                Thread.sleep(15);
            } 
            catch (InterruptedException e) 
            {
                System.out.println("interrupted");
            }

        }
        
	}
	
	
	public double getSystemTime() 
	{
		return System.nanoTime() / 1000000000.0;
	}

	public double getDeltaTime( double timerInSeconds )
	{
		
		double currentTime = timerInSeconds;
		double elapsedTime = currentTime - oldTime;
		oldTime = currentTime;
		
		framesPerSecond++;
		
		if( (currentTime - previousTime) > 1.0 )
		{
			previousTime = currentTime;
			fps = framesPerSecond;
			framesPerSecond = 0;
		}
		
		return elapsedTime;
	}
	
    
}




//****************************************************************************
//
//  Screen and logic constants
//
//****************************************************************************

class Constants 
{

	public static final double FIXED_TIME_STEP = 1.0 / 60.0;

	public final static int SCREEN_WIDTH = 640;
	public final static int SCREEN_HEIGHT = 480;

}


//****************************************************************************
//
//
//
//****************************************************************************

class Plasma
{
	private static final double K1 = 35;
	private static final double K2 = 18;
	private static final double K3 = 50;
	
	private int kSin1 = (int)(K1 *(360/57.3));
	private int kSin2 = (int)(K2 *(360/57.3));
	private int kSin3 = (int)(K3 *(360/57.3));
	
	private int[] sinLut1 = new int[2048 * 4];
	private int[] sinLut2 = new int[2048 * 4];
	private int[] sinLut3 = new int[2048 * 4];
	
	private int a, b, c;
	
	BufferedImage image = new BufferedImage( Constants.SCREEN_WIDTH, 
			   			  				     Constants.SCREEN_HEIGHT, 
			   			  				     BufferedImage.TYPE_INT_ARGB);

	int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();	

	BufferedImage texture = new BufferedImage( 256, 
			     							   256, 
			     							   BufferedImage.TYPE_INT_ARGB);
	int[] texPixels = ((DataBufferInt) texture.getRaster().getDataBuffer()).getData();	

	public Plasma()
	{
		int size = sinLut1.length;
		for( int i = 0; i < size; i++ )
		{
			sinLut1[i] = (int)(Math.sin(i/K1) * 127 + Math.cos(i/K1) * 67);
			sinLut2[i] = (int)(Math.sin(i/K2) * 164 + Math.cos(i/K2) * 26);
			sinLut3[i] = (int)(Math.sin(i/K3) * 32 + Math.cos(i/K3) * 280);
			
		}
		
		
		double j = 255 / 360.0f * 3;
		double k = 255 / 360.0f * 2;
		double l = 255 / 360.0f * 1;
		double pa = 0;
		double pb = 0;
		double pc = 0;
		
		
		for( int y = 0; y < 256; y++ )
		{
			
			int r = (int)( 255 * Math.abs(Math.sin(pa * Math.PI / 180.0)) );
			int g = (int)( 255 * Math.abs(Math.sin(pb * Math.PI / 180.0)) );
			int b = (int)( 255 * Math.abs(Math.sin(pc * Math.PI / 180.0)) );
			c = (255 << 24) | (r << 16) | (g << 8) | (b);
			
			for( int x = 0; x < 256; x++ )
			{
				texPixels[y * 256 + x] = c;
			}
			pa += j;
			pb += k;
			pc += l;
			
		}
				
	}
	
	public void update()
	{
	
		a = (++a) % (kSin1 + 1);
		b = (++b) % (kSin2 + 1);
		c = (++c) % (kSin3 + 1);
		
		int offset = 0;
    	for( int y = 0; y < Constants.SCREEN_HEIGHT; y++ )
		{
			for( int x = 0; x < Constants.SCREEN_WIDTH; x++ )
			{
				int tx = ( sinLut1[x + a + 2048] + 
						   sinLut2[y + b + 2048] + 
						   sinLut3[x + y + c + 2048] );
		        int ty = ( sinLut3[x + y + c + 2048] + 
		        		   sinLut1[(tx) & 2047] +
		        		   sinLut1[x + a + 2048] + 
		        		   sinLut1[y + a + 2048] + 
		        		   sinLut2[x + b + 2048]);
				tx = tx & 255; 
				ty = ty & 255;
		        int c = texPixels[ ty * 256 + tx ];
				pixels[offset++] = c;
			}
			
		}
    	
	}
i
	public BufferedImage getImage()
	{
		return image;
	}

	
	
}

Jar and code:
www.rel.phatcode.net/junk.php?id=146

Enjoy!<

Wow that hurts my eyes, so good job! Looks pretty realistic to me.

Hmm, that is quite neat looking… I bet I could be modified into a nice water “shader,” it looks pretty ripply.
I may investigate this later.

Good job! :smiley:

If you’re looking for a water ripple effect using direct pixel manipulation you might also want to check out -

http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
http://www.neilwallis.com/projects/java/water/index.php

There’s loads of variations of this algorithm around. I used it in this - http://youtu.be/YNe03b5BxLM

Ah, good stuff, I wasn’t really looking for a water shader, but I’ll definitely save these for later :smiley: Thanks!

Thanks guys. It’s a little too slow for my tastes though. appx 40 fps on my netbook. :(.

Netbook? What’s the specs? 40 FPS doesn’t sound too shabby for this kind of thing on a netbook. (If you are rendering at the resolution of that picture)

EDIT: Tested my benchmark from the other thread on the crappy laptop I’m on right now, got 42 FPS when rendering 512x512, every pixel. Normally it’s about 130 FPS on my desktop, so for a system of any quality, I think you can expect a decent framerate.
You could also try the byte[] method in my benchmark code, similar to the int[], just interleaved rgb channels, it’s a little bit faster.

I’d take the FPS value with a big pinch of salt! Your game loop looks completely screwy. I get 50 fps as is, but if I change the sleep to Thread.sleep(1) I get 600 fps, though it visibly looks the same.

Changing your loop to just


dt = getDeltaTime(getSystemTime());
update();

which is a quick guess at maxing it out (may be wrong) gives ~300 fps and is now visibly faster.

The problem is also that you’re using passive rendering, so although you might be able to measure how fast this can update the plasma, you’re not measuring actual rendering time. It’s also theoretically not thread safe as you’re updating the image in one thread and painting it in another (EDT).

You want to look into an active rendering loop using BufferStrategy and disable the standard repaint() method - something like ra4king’s code here - http://www.java-gaming.org/topics/about-on-my-main-loop/26222/msg/229028/view.html#msg229028

That’s the idea(bolded). The simulation should run at 60 logical FPS even if the actual FPS could be 60 to 1000+.

I also like to slowdown instead of skipping frames when the actual FPS goes below 60. AFAIK, there’s also just a single thread that updates and draws. Care to explain where am I sharing data between threads?

BurntPizza: An intel atom (acer aspire one) which is actually my daughter’s. My laptop runs this at about 70 FPS. I’ll try to read up on the byte[] thingy(ARGB is the order right?). Thanks!

I understood that was the idea. However, you’re not measuring anything useful otherwise (see next point).

repaint() does not call paint(). It queues up a repaint request on the Swing event dispatch thread - it’s asynchronous and returns immediately. Therefore you’re updating the image in update() in the thread you create, and accessing it from the EDT in paint().

Also, as repaint() returns immediately, you’re not actually measuring FPS because you’re at no point measuring the actual cost of rendering to the screen.

You need to look at that active rendering loop I linked to.

Okay, I do have a bufferstrategy gameloop. Thanks for the heads up.

The order is actually ABGR, or just BGR, weird, I know.

Updated with a newer plasma. Code is in the zip.

do
texPixels[y * 256 + x] = c << 16;

much better result :smiley: