/*
 * Copyright (C) Bob L. Sturm 2002
 * Copyright (C) Michael Fowler 1998 under Gnu Public License
 * http://www.gnu.org/copyleft/gpl.html
 *
 * Brownian.java
 * Drew Dolgert October 1998
 * This simulates little balls bouncing against each other.
 * Easy java.  Annoying kinematics.
 */
import java.awt.*;
import java.lang.*;

public class Brownian extends Canvas implements Runnable
{
        // Do these variables come from the .html file size parameters?
	private int rl,rh; // width and height of the Canvas.
	double bigxvel,bigyvel,mr=0.05; // bigx is the big blue ball.  mr=mass ratio red to blue
	double bigx, bigy;
	// When the blue ball leaves the screen edge, it wraps around.  The Add variables
	// record how far it has travelled since the start.
	int iBigXAdd, iBigYAdd;
	// Average velocity of the red balls in pixels per timestep.
	double dAverageVelocity = 5;
	int pBallRadius = 4;
	int pBlueRadius = 15;
        int blueStick = 1;
        int redStick = 1;
	int threadSleepTime = 40; // Time for thread to pause.
//	private int num=80; // Number of red balls.
	int num=80; // Number of red balls.
	boolean frozen=false; // Whether the simulation is stopped.
	private Graphics offscreen; // Offscreen buffer for double-buffering.
	Image offImage;
	Scope myScope; // Reference to the microscope view of this ball.
	Dimension thisSize; // Size of canvas.
	Thread animatorThread; // The thread of multi-threaded.
	// Because the balls live on a torus (wrap-around boundary conditions),
	// they can strike the blue particle on any side of the playing field.
	// offsets[][] carries the nine curren locations of the blue ball.
	int[][] offsets;
	// Positions (x,y) and velocities (xx,yy) of all red balls.
	// +1 for safety?  No, I thought about putting blue ball in with red.
	double[] x=new double[num+1];
	double[] xx=new double[num+1];
	double[] y=new double[num+1];
	double[] yy=new double[num+1];
	
	public Brownian(int nBalls, double avev, double dMassRatio)
	{
		offsets = new int[9][2];
//		if (nBalls>0) num = nBalls;
		iBigXAdd = 0;
		iBigYAdd = 0;
		mr = 1.0/dMassRatio;
		dAverageVelocity = avev;
		changeSize();
		int i;
		myScope = null;
                //Set initial positions and velocities
		for (i=0;i<num;i++)
		{
			x[i]=(Math.random()*(rl-20)+10);
			xx[i]=((Math.random()-0.5)*dAverageVelocity);
			y[i]=(Math.random()*(rh-20)+10);
			yy[i]=((Math.random()-0.5)*dAverageVelocity);
		}
		// Start the blue ball out in the center.
		bigx=rl/2;
		bigxvel=0;
		bigy=rh/2;
		bigyvel=0;
		frozen = false;
		// Start the main thread.
		animatorThread = new Thread(this);
                // This calls run().
		animatorThread.start();
	}

	public void setThreadSleep(int time)
	{
		if (time>0) threadSleepTime = time;
	}

	// Call whenever the canvas changes size.
	// It maintains the offscreen buffers and pixel offsets.
        // Doesn't work (BLS)
	private void changeSize()
	{
		thisSize = this.getSize();
		if (thisSize != null && thisSize.width>0) rl = thisSize.width;
		else rl = 300;
		if (thisSize != null && thisSize.height>0) rh = thisSize.height;
		else rh = 300;
		offImage=createImage(thisSize.width,thisSize.height);
		if (offImage!=null)
			offscreen=offImage.getGraphics();
		bigx = rh/2-pBlueRadius;
		bigy = rl/2-pBlueRadius;
		// Here we set offsets for the blue ball.
		// When we calculate collisions, what happens if a red ball travels
		// off the right side and should hit a blue ball on the left
		// side of the field?  That's where offset[1] comes into play.
		// Nine offsets: Zero offset, four sides, four corners.
		offsets[0][0] = 0; offsets[0][1] = 0;
		offsets[1][0] = rl; offsets[1][1] = 0;
		offsets[2][0] = -rl; offsets[2][1] = 0;
		offsets[3][0] = 0; offsets[3][1] = rh;
		offsets[4][0] = 0; offsets[4][1] = -rh;
		offsets[5][0] = rl; offsets[5][1] = rh;
		offsets[6][0] = rl; offsets[6][1] = -rh;
		offsets[7][0] = -rl; offsets[7][1] = rh;
		offsets[8][0] = -rl; offsets[8][1] = -rh;
		repaint();
	}

	public void setScope(Scope scope)
	{
		myScope = scope;
	}

	public void start()
	{
	    if (frozen) {
		    frozen = false;
		    if (animatorThread == null) animatorThread = new Thread(this);
		    if (animatorThread !=null) animatorThread.start();
	    }
	}
	
	public void stop()
	{
		frozen = true;
	}
    	
	public void paint(Graphics g)
	{
		update(g);
	}
	
	// Just draw the balls.
	public void update( Graphics g ) {
		int i;
		Dimension d=getSize();
		if (!d.equals(thisSize)) changeSize();
		if (offscreen==null) return;

		offscreen.clearRect(0,0,(int)rl,(int)rh);
		offscreen.setColor(Color.red);
		for(i=0;i<num;i++)
		{
   		  offscreen.fillOval((int)x[i]-pBallRadius,(rh-(int)y[i])-pBallRadius,2*pBallRadius,2*pBallRadius);
		}
		offscreen.setColor(Color.blue);
		// We draw the blue ball at all nine positions so you can see it creep around
		// the edges of the field.
		for (i=0;i<9; i++) {
			offscreen.fillOval((int)bigx+offsets[i][0]-pBlueRadius,(rh+offsets[i][1]-(int)bigy)-pBlueRadius,2*pBlueRadius,2*pBlueRadius);
		}
		g.drawImage(offImage,0,0,this);
	}
	
	// This is the main thread.  It's a doosey.
	public void run()
	{
		double tx1,ty1,tx2, ty2;
		double dotprod, crossprod,dv2;
		double dv, dvx, dvy, dx, dxx, dxy, b, cosphi, sinphi;
		double cmx, cmy,v1cmx, v1cmy, invm, dt, dTheta;
		int r, i, k;
		long startTime;

		while (!frozen)
		{
                        // Update graphics
			repaint();
			// Measure start time so we can restrict the timing of the
			// whole loop.  This is a little more efficient than just making
			// a set sleeptime each loop.  It costs more b/c we call a System
			// function, but this is such an expensive loop, I don't think it
			// matters.
			startTime = System.currentTimeMillis();
			// First order of business is all collisions with the blue ball.
			invm = 1/(1+mr); //mr = mass ratio of blue to red
			// Loop through the nine possible blue ball positions.
			for (i=0; i<num; i++) {
				for (k=0; k<9; k++) {
				// dv is the neg velocity differential v1-v2
				dvx = xx[i]-bigxvel;
				dvy = yy[i]-bigyvel;
				dv2 = dvx*dvx+dvy*dvy;
				// dx is the distance x2-x1
				dxx=bigx+offsets[k][0]-x[i];
				dxy=bigy+offsets[k][1]-y[i];
				dx= Math.sqrt(dxx*dxx+dxy*dxy);
				dotprod = dvx*dxx+dvy*dxy; // dv . dx
				// dotprod>0 if they are going towards each other.
				if (dotprod>0) {
					crossprod = dvy*dxx-dvx*dxy; // dx x dv
					dv = Math.sqrt(dv2);
					b = (1/((pBlueRadius+pBallRadius)*dv))*crossprod;
					dt = dotprod/dv2-(pBlueRadius+pBallRadius)*(1-b*b)/dv;
					if (b*b<1 && dt>=0 && dt<1) {
						cosphi = b*b*2-1;
						sinphi = 2*b*Math.sqrt(1-b*b);
						dTheta = Math.atan2(dvy,dvx)+Math.atan2(sinphi,cosphi);
						sinphi = Math.sin(dTheta);
						cosphi = Math.cos(dTheta);
						cmx = invm*(mr*xx[i]+bigxvel);
						cmy = invm*(mr*yy[i]+bigyvel);
						v1cmx = blueStick*invm*dv*cosphi;
						v1cmy = blueStick*invm*dv*sinphi;
						xx[i]=v1cmx+cmx;
						yy[i]=v1cmy+cmy;
						bigxvel=-mr*v1cmx+cmx;
						bigyvel=-mr*v1cmy+cmy;
					}}
				}
			}
			
			// Now loop over all pairs of red balls.
			// This is a little less precise than the blue ball routine.
			// e.g. A red ball can't strike one on the opposite edge of
			// the field.
			for (i=0;i<num;i++)
			{
				for (k=i+1;k<num;k++)
				{
					invm = 0.5;
					// dv is the neg velocity differential v1-v2
					dvx = xx[i]-xx[k];
					dvy = yy[i]-yy[k];
					dv2 = dvx*dvx+dvy*dvy;
					// dx is the distance x2-x1
					dxx=x[k]-x[i];
					dxy=y[k]-y[i];
					//dx= Math.sqrt(dxx*dxx+dxy*dxy);
					dotprod = dvx*dxx+dvy*dxy; // dv . dx
					// dotprod>0 if they are going towards each other.
					if (dotprod>0) {
						crossprod = dvy*dxx-dvx*dxy; // dx x dv
						dv = Math.sqrt(dv2);
						b = (1/(2*pBallRadius*dv))*crossprod;
						dt = dotprod/dv2-2*pBallRadius*(1-b*b)/dv;
						if (b*b<1 && dt>=0 && dt<1) {
							// bring them to the point of collision.
							x[i]+= dt*xx[i];
							y[i]+= dt*yy[i];
							x[k]+= dt*xx[k];
							y[k]+= dt*yy[k];
							// dx= Math.sqrt(dxx*dxx+dxy*dxy);
							cosphi = b*b*2-1;
							sinphi = 2*b*Math.sqrt(1-b*b);
							dTheta = Math.atan2(dvy,dvx)+Math.atan2(sinphi,cosphi);
							sinphi = Math.sin(dTheta);
							cosphi = Math.cos(dTheta);
							cmx = invm*(xx[i]+xx[k]);
							cmy = invm*(yy[i]+yy[k]);
							v1cmx = redStick*invm*dv*cosphi;
							v1cmy = redStick*invm*dv*sinphi;
							xx[i]=v1cmx+cmx;
							yy[i]=v1cmy+cmy;
							xx[k]=-v1cmx+cmx;
							yy[k]=-v1cmy+cmy;
							// bring them the rest of the way
							//x[i]+= (1-dt)*xx[i];
							//y[i]+= (1-dt)*yy[i];
							//x[k]+= (1-dt)*xx[k];
							//y[k]+= (1-dt)*yy[k];
						}
					}
				}
				// Update red ball positions.				
				x[i]+=xx[i]; 
				if (x[i]<0) x[i]+=rl;
				if (x[i]>rl) x[i]-=rl;
				y[i]+=yy[i];
				if (y[i]<0) y[i]+=rh;
				if (y[i]>rh) y[i]-=rh;
			}
			// Update blue ball positions.
			bigx+=bigxvel;
			if(bigx<0) {
				bigx+=rl;
				// If the ball switches sides of the field,
				// record it as an offset from the original position.
				iBigXAdd -= rl;
			}
			if(bigx>rl) {
				bigx-=rl;
				iBigXAdd += rl;
			}
			bigy+=bigyvel;
			if(bigy<0) {
				bigy+=rh;
				iBigYAdd -= rh;
			}
			if(bigy>rh) {
				bigy-=rh;
				iBigYAdd += rh;
			}
			try {
				// Sleep only the rest of the allotted time.
				long td = threadSleepTime-System.currentTimeMillis()+startTime;
				if (td>0)
					Thread.sleep(td);
					// If the allotted time is all used, still call sleep because
					// it gives the system a moment to catch its breath.
					// That way we might not cream slower machines.
				else Thread.sleep(1);
			} catch (InterruptedException e) { ; }
				
		}
		animatorThread = null;
		frozen = true;
	}

    public void reset()
    {
        iBigXAdd = 0;
        iBigYAdd = 0;
    }

    public void zoomBlueBall(int speed)
    {
      if (bigxvel < 0) bigxvel -= speed;
      if (bigxvel > 0) bigxvel += speed;
      if (bigyvel < 0) bigyvel -= speed;
      if (bigyvel > 0) bigyvel += speed;
    }
    public void setBlueCollisionFactor(int colFactor)
    {
      blueStick = colFactor;
    }
    public void setRedCollisionFactor(int colFactor)
    {
      redStick = colFactor;
    }

	public Dimension getPreferredSize() {
		return new Dimension(rl,rh);
	}

	public Dimension getMinimumSize() {
		return new Dimension(40,40);
	}	
} 
