/*********************************************************************************************/
/*                                                                                           */
/*  Java Image Demo - Dynamic Physics                                                        */
/*  Auth: bkenwright@xbdev.net                                                               */
/*  URL: www.xbdev.net                                                                       */
/*                                                                                           */
/*********************************************************************************************/

import java.awt.image.*;
import java.awt.*;
import java.applet.*;

import java.util.Random;



class physics
{
	public double x, y;    // position
	public double vx, vy;  // velocity
	public double mass;    // make use this ain't zero
	public double fx, fy;  // accumulated forces

        public physics()
        {
                x=y=vx=vy=mass=fx=fy=0;
        }

	/** Apply a force to the unit. **/
	public void applyForce( double fx, double fy ) 
        {
		this.fx += fx;
		this.fy += fy;
	}

        // Heun Predictor (Correction Integrator)
        // Ref: http://panoramix.ift.uni.wroc.pl/~maq/soft2d/node18.html
        public void integrateHP( double dt )
        {
		
		// uses newtons second law f = ma
		double ax = fx/mass;
		double ay = fy/mass;
                
                double vxo = vx;
                double vyo = vy;

		// update the velocities given accelerations
		// again using euler
		vx = vx + dt*ax;
		vy = vy + dt*ay;

                x = x + dt/2*(vx + vxo);
		y = y + dt/2*(vy + vyo);

		// Then cancel out the accumulated forces
		// as they have been used up for this timestep
		fx = 0;
		fy = 0;
	}

	// Integrate using basic euler method
	public void integrateEuler( double dt )
        {	
		// uses newtons second law f = ma
		double ax = fx/mass;
		double ay = fy/mass;

		// update the velocities given accelerations
		// using euler
		vx = vx + dt*ax;
		vy = vy + dt*ay;

                x = x + dt*vx;
		y = y + dt*vy;

		// Then cancel out the accumulated forces
		// as they have been used up for this timestep
		fx = 0;
		fy = 0;
	}

        // Nice ref: http://www.racer.nl/tech/phystips.htm
        // 2nd order Runge-Kutta
	public void integrateRK2( double dt )
        {	
                // Uses newtons second law f = ma
		double ax = fx/mass;
		double ay = fy/mass;

                // update the velocities given accelerations
                vx = vx + dt*ax;
                vy = vy + dt*ay;
              
                // update position given current velocity
                x = x + dt*vx + 0.5*ax*dt*dt;
                y = y + dt*vy + 0.5*ay*dt*dt;
                

		// Then cancel out the accumulated forces
		// as they have been used up for this timestep
		fx = 0;
		fy = 0;
	}
}




class ball extends physics
{
   public ball( ball p )
   {
      x = p.x;
      y = p.y;
      vx = p.vx;
      vy = p.vy;
      fx = p.fx;
      fy = p.fy;
      w  = p.w;
      h  = p.h;
      mass = p.mass;
   }
   public ball(){ x = y = vx = vy = fx = fy = alive = 0; }
   public float w, h;
   public Color colour;
   public int alive;

}//End class ball




class many_balls
{
   int NumBalls;
   ball[] ArrayBalls;
   int w, h;  // Screen Width and Height

   Random generator = new Random();
   
   public many_balls(int n, int ww, int hh)
   {
      NumBalls = n;
      w = ww;
      h = hh;

      ArrayBalls = new ball[NumBalls];

      for(int i=0; i<NumBalls; i++)
         ArrayBalls[i] = create_rand_ball();
   }

   public int Rand(int min, int max)
   {
      int v = generator.nextInt();
      v = Math.abs(v);
      int dif = max-min;
      v = v%(dif+1);
      v += min;
      return v;
   }

   public ball create_rand_ball()
   {
      ball b = new ball();
           
      
      b.y = Rand( 30, 50 ); // 50
      b.mass  = Rand(1, 3);
      b.w = b.h = Rand(10, 40);

      b.alive = 1;

      int left_right = Rand( 1, 2 );
      
      if(left_right ==1)
         b.x = w + b.w;
      else
         b.x = 0 - b.w;

      if( left_right == 1 )
         b.vx = Rand(-8,-3); // right
      else
         b.vx = Rand(3,8);

      b.colour = new Color( Rand(0,254), Rand(0,254), Rand(0,254) );
      return b;
   }

   public void update( double dt )
   {
       /* gravity 9.8 m/s */
       double fg = 9.8;

       for(int i=0; i<NumBalls; i++)
       {
            ball b = ArrayBalls[i];
            if(b.alive == 0 ) ArrayBalls[i] = create_rand_ball();

            b.applyForce(0, fg);

            b.integrateRK2(dt);
            collision_wall_check(b);
       }
   }//End update(..)


   public void render(Graphics g)
   {
      for(int i=0; i<NumBalls; i++)
      {
         if( ArrayBalls[i].alive == 1 )
              drawBall(g, ArrayBalls[i]);
      }//End for loop
   }

   public void drawBall(Graphics g, ball b)
   {
      g.setColor( b.colour );
      g.fillOval( (int)b.x, (int)b.y, (int)b.w, (int)b.h);

   }//End drawBall(..)

   public int collision_wall_check(ball bb)
   {
      double k = 1.05; // damping coeficient
      if( (bb.y + bb.h) >= h )
      {
            bb.vy = -bb.vy * k;
            bb.y = h - bb.h;

            //return 1;
      }

      // We could kill and rebuild balls that go off screen
      // To the right
      if( (bb.x - bb.w) > w )
      {
         bb.alive = 0;
      }
      // Left
      if( (bb.x + bb.w) < 0 )
      {
         bb.alive = 0;
      }
  

      return 0;
   }//End collisioncheck()


}//End class many_balls()



public class manybouncyballs extends Applet implements Runnable
{
	private Thread m_thread        = null;
	// Screen width and height
	int w, h;

        many_balls bb;

        public void init()
        {
             w = getSize().width;
             h = getSize().height;
             bb = new many_balls( 5,w,h );

        }//End init()


	public synchronized void update(Graphics g)
	{
            bb.update(0.5);               
            clearscreen(g);
            bb.render(g);
            
	}// End update(..)



        public void clearscreen(Graphics g)
        {
            g.setColor( Color.white );
            g.fillRect( 0, 0, w, h );

        }//End clearscreen(..)

        public void destroy()
        {
            bRun = false;
            m_thread = null;
        }

        boolean bRun = true;
        public void run()
	{
             // Remember the starting time
             long thenTime = System.currentTimeMillis();
             while (bRun) 
             {
                   long nowTime = System.currentTimeMillis();
                   long difTime = nowTime - thenTime;
			    
                   // I've set the delay to 0, so its pushing for the MAX fps, you would
                   // set a value in there to limit for example to 25-50 fps refresh rate
                   if( difTime > 45 )
                   {
                        thenTime = nowTime;
                        // This method provides better updates, instead of filling the
                        // message queue up
                        Graphics g=this.getGraphics();
                        update(g);
                   }
                   try{
                   m_thread.sleep(10);
                   }catch (InterruptedException e){}
             }// end while loop
	}// End of run() method
	
	
	public void start()
	{
		if(m_thread == null)
                {
			m_thread = new Thread(this);
			//m_thread.setPriority(Thread.MAX_PRIORITY);
			m_thread.start();
		}
		System.out.println( "Starting Bouncing Ball Graphics Demo\n" );
	}// End of start()
	
}// End of Applet



