import java.awt.*;
import java.awt.image.MemoryImageSource;
import java.awt.event.*;
import java.applet.Applet;
import java.util.Vector;

public class Particles extends Applet implements Runnable {
	
	Vector active_particles = new Vector();
	Vector inactive_particles = new Vector();
	Image offScrImage = null;
	Graphics offScrGr = null;
	MemoryImageSource imageSource = null;
	int[] pixels = null;
	Image image = null;
	int width, height;
	private double theta = Math.PI*0.75;
	private final int x   = 0,  y    = 1,  z   = 2, 
					  vx  = 3, vy    = 4, vz   = 5,
					  red = 6, green = 7, blue = 8;

	public void processMouseMotionEvent( MouseEvent me ) {
		if ( me.getID() == me.MOUSE_DRAGGED ) {
			int y = me.getY();
			theta = Math.PI*((y/(double)height) - 0.5);
		}
	}
	
	private volatile boolean running = true;
	
	public void start(){
		running = true;
		(new Thread(this)).start();
	}
	
	public void run(){
		Dimension size = getSize();
		offScrImage = createImage( size.width, size.height );
		offScrGr = offScrImage.getGraphics();
		width = size.width;
		height = size.height;
		
		for ( int i = 0; i < 2000; i++ ){
			float[] particle = new float[ 9 ];
			if ( i > 100 ) {
				inactive_particles.addElement( particle );
			} 
			else {
				particle[ red ] = particle[ green ] = particle[ blue ] = 0.5f;
				double px = Math.random()-0.5;
				double py = Math.random()-0.5;
				double pz = Math.random()-0.5;
				double dist = Math.sqrt( py*py + pz*pz );
				particle[ x ] = (float)(width*px);
				particle[ y ] = (float)(width*py/dist);
				particle[ z ] = (float)(width*pz/dist);
				active_particles.addElement( particle );
			}
		}
		
		pixels = new int[ size.width*size.height ];
		imageSource = new MemoryImageSource( size.width, size.height, pixels, 0, width );
		imageSource.setAnimated( true );
		imageSource.setFullBufferUpdates( true );
		image = createImage( imageSource );
		
		enableEvents( AWTEvent.MOUSE_MOTION_EVENT_MASK );
		Thread.currentThread().setPriority( Thread.MIN_PRIORITY );
		while( running ){
			long start = System.currentTimeMillis();
			
			int num_inactive_particles = inactive_particles.size();
			while( num_inactive_particles > 0 && Math.random() < 0.7 ){
				num_inactive_particles--;
				float[] particle = (float[])inactive_particles.elementAt( num_inactive_particles );
				inactive_particles.removeElementAt( num_inactive_particles );
				
				particle[ y ] = (float)((0.5-Math.random())*3);
				particle[ z ] = (float)((0.5-Math.random())*3);
				particle[ vx ] = (float)((0.5-Math.random())*3);
				particle[ vz ] = (float)((0.5-Math.random())*3);
				
				double ran = Math.random();
				if ( ran < 0.5 ){
					particle[ x ] = (float)((0.5-Math.random()) + (3*width/8));
					particle[ vy ] = (float)((0.5-Math.random()) + 10);
				}
				else {
					particle[ x ] = (float)((0.5-Math.random()) - (3*width/8));
					particle[ vy ] = (float)((0.5-Math.random()) - 10);
				}
				
				for ( int i = red; i <= blue; i++ ) {
					particle[ i ] = 0.2f + (float)(0.1f*Math.random());
				}
				if ( Math.random() < 0.7 )
					particle[ blue ] += 0.15;
				else {
					particle[ red ] += 0.3;
					particle[ green ] += 0.2;
				}
				
				active_particles.addElement( particle );
			}
				
			for ( int i = 0; i < pixels.length; i++ ){
				pixels[ i ] = 0xFF000000;
			}

			float sin_theta = (float)Math.sin( theta ),
				  cos_theta = (float)Math.cos( theta );
			double dt = 0.1f;
			
			for ( int i = active_particles.size()-1; i >= 0; i-- ){
				float[] particle = (float[])active_particles.elementAt( i );
				if ( i > 100 ) {
					double distSq = 0.0;
					
					for ( int j = 0; j < 3; j++ ) {
						float v = particle[ j + 3 ];
						particle[ j ] += dt*v;
						distSq += particle[ j ]*particle[ j ];
					}
			
					double dist = Math.sqrt( distSq );
					double force = -10000.0/distSq;
					double speedSq =  0.0;
				
					for ( int j = 0; j < 3; j++ ) {
						float v = particle[ j+3 ];
						particle[ j+3 ] = (float)(v + dt*force*particle[ j ]/dist)*0.999f;
						speedSq += v*v;
					}
				
					if ( distSq < 100 || distSq > 1000000 || speedSq > 2500 ) {
						active_particles.removeElementAt( i );
						inactive_particles.addElement( particle );
					}	
				}
				
				float py = particle[ this.y ]*cos_theta + particle[ this.z ]*sin_theta;
				float px = particle[ this.x ];
				px += 0.5f*width;
				py += 0.5f*height;
				
				if ( px >= 0 && py >= 0 ) {
					
					int ix = (int)px;
					int iy = (int)py;
					int ixPlusOne = ix+1;
					int iyPlusOne = iy+1;
					
					if ( ixPlusOne < width && iyPlusOne < height ){
		
						float fx = px - ix;
						float fy = py - iy;
		
						float oneMinusFx = 1.0f - fx,
				  		  	  oneMinusFy = 1.0f - fy;
				  
						float btl = oneMinusFx * oneMinusFy;
						float btr = fx     	   * oneMinusFy;
						float bbl = oneMinusFx * fy;
						float bbr = fx     	   * fy;

						float red =   (255f*particle[ this.red ]);
						float green = (255f*particle[ this.green ]);
						float blue =  (255f*particle[ this.blue ]);
					
						plotPixel( (btl*red), (btl*green), (btl*blue), ix,        iy );
						plotPixel( (btr*red), (btr*green), (btr*blue), ixPlusOne, iy );
						plotPixel( (bbl*red), (bbl*green), (bbl*blue), ix,        iyPlusOne );
						plotPixel( (bbr*red), (bbr*green), (bbr*blue), ixPlusOne, iyPlusOne );
					}
				}
				
			}
			
			long stop = System.currentTimeMillis();
			image.flush();
			offScrGr.drawImage( image, 0, 0, this );
			repaint();
			long time = start+40 - stop;
			if ( time > 0 ){ 
				try{
					Thread.sleep( time );
				}
				catch( Exception e ){}
			}
		}
	}
	
	public void stop(){
		running = false;
	}
	
	private void plotPixel( float red, float green, float blue, int x, int y ){
		int index = y*width + x;
		int prev_pixel = pixels[ index ];
		int new_red   = ((prev_pixel & 0x00FF0000) >> 16) + (int)red;
		new_red = new_red > 255 ? 255 : new_red; // so stays in range
		int new_green = ((prev_pixel & 0x0000FF00) >> 8)  + (int)green;
		new_green = new_green > 255 ? 255 : new_green; // so stays in range
		int new_blue  = ((prev_pixel & 0x000000FF))       + (int)blue;
		new_blue = new_blue > 255 ? 255 : new_blue; // so stays in range
		pixels[ index ] = 0xFF000000 | (new_red << 16) | (new_green << 8) | new_blue;
	}
			
	public void update( Graphics g ){
		if ( offScrImage == null ) 
			return;
		g.drawImage( offScrImage, 0, 0, this );
	}
	
}