/*Wave -- an applet to simulate waves in a rubber sheet.
 *by Michael Mandel, 7/25/00
 */

import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.util.*;

public class Wave extends Applet implements Runnable
{
	//parameters
	protected int WIDTH, HEIGHT;  //number of points on the string for which we calculate x and dx/dt
	protected double C;    //coefficient for force calculation, = t*k/m = time * spring constant/mass of each element
	protected double PUSH; //initial velocity of the middle point on the string
	protected double FRICTION;  //amount of dampening in each element's velocity
	protected double ZSCALE, HSCALE;  //scales of the drawing
	protected int DELAY;     //pause time between each iteration
	protected int TICK_COUNT; //number of reps to force string for forceClock;
	protected boolean ENABLE_NEGATIVE;  //lets plucks be both positive and negative
	
	//other variables
	protected Thread t;          //thread to run this applet in
	protected double[] string, velocity;  //two main arrays, hold the string points and the points' velocities
	protected double[] forceClock; //holds the time left to apply PUSH to each point
	protected int time;  //unused
	protected Image db;   //double buffer
	

	public void init() //initialize variables and create arrays
	{		
		WIDTH = this.getSize().width;
		HEIGHT = this.getSize().height;
		C = new Double(getParameter("C")).doubleValue();
		PUSH = new Double(getParameter("PUSH")).doubleValue();
		FRICTION = new Double(getParameter("FRICTION")).doubleValue();
		ZSCALE = new Double(getParameter("ZSCALE")).doubleValue();
		DELAY = Integer.parseInt(getParameter("DELAY"));
		TICK_COUNT = Integer.parseInt(getParameter("TICK_COUNT"));
		ENABLE_NEGATIVE = Boolean.valueOf(getParameter("ENABLE_NEGATIVE")).booleanValue();

		velocity = new double[HEIGHT*WIDTH];  
		string = new double[HEIGHT*WIDTH];
		forceClock = new double[HEIGHT*WIDTH];
		forceClock[(int)(WIDTH*HEIGHT/2 + WIDTH/2)] = TICK_COUNT;
	
		db = createImage(WIDTH, HEIGHT);
	}
	
	public void start()  //called when the applet comes into view
	{
		if(t == null)  //initialize thread t
			t = new Thread(this);
		t.start();       //go!!
	}
		
	public void run()
	{
		while(true)
		{
			calculateVelocity();  
			calculatePosition();
			repaint();
			//wait for a little before drawing the next frame
			try{Thread.sleep(DELAY);} catch (Exception ignored){System.out.println("HELP ME");};
		}
	}
	
	public void update(Graphics g)
	{
		paint(g);
	}
	
	protected void calculateVelocity()   //calculate physics
	{
		int x, y, position;
		double delta;
		
		for(y = 1; y < HEIGHT - 1; y++)
		{
			for(x = 1; x < WIDTH - 1; x++)  //don't worry about the ends of the string
			{
				position = y*WIDTH + x; //avoid redundant calculations
				delta = force(string[position], string[position + 1]);
				delta += force(string[position], string[position - 1]);
				delta += force(string[position], string[position + WIDTH]);
				delta += force(string[position], string[position - WIDTH]);

				velocity[position] += delta;
				
				if(forceClock[position] > 0)
				{
					velocity[position] += PUSH;
					forceClock[position]--;
				}
				if(forceClock[position] < 0)
				{
					velocity[position] -= PUSH;
					forceClock[position]++;
				}
			}	
		}
	}
	
	protected void calculatePosition()
	{		
		for(int x = 0; x < HEIGHT*WIDTH; x++)
		{
			velocity[x] *= FRICTION;
			string[x] += velocity[x];
		}
	}
		
	protected double force(double a, double b)
	{
		//assuming the distance between string elements is 1, use to pythagorian theorem and
		//f = k*(x1 - x2) to calculate force on element a from b and c.  
		//assumes also that a string is a bunch of masses held together with perfect springs.
			
		return C*(b - a);
	}
	
	public boolean mouseDown(Event e,int xmd,int ymd)
   {
		pluck(ymd*WIDTH + xmd);
			   
	   return true;
	}
	
	protected void pluck(int x)
	{
		if(ENABLE_NEGATIVE && (Math.random() < 0.5))  //short circuit the random() if unneeded
	   	forceClock[x] = -TICK_COUNT;
		else
			forceClock[x] = TICK_COUNT;
	}
	
	public void paint(Graphics g)
	{
		int red, x, y, array[];
		array = new int[HEIGHT*WIDTH];
		
		for(x = 0; x < HEIGHT*WIDTH; x++)
		{
			red = (int)(ZSCALE * string[x] + 128);
			if(red > 255)
				red = 255;
			if(red < 0)
				red = 0;
			
			array[x] = (red << 0) + (255 << 24) + (64 << 8) + (64 << 16);
		}
		
		db = createImage(new MemoryImageSource(WIDTH, HEIGHT, array, 0, WIDTH));
		g.drawImage(db, 0, 0, this);
	}
}
	
