package bounce;
/**
 @author Oran
 *
 *Make some circles ("balls") bounce
 *around inside a window, colliding inelasticly with each other.
 *
 *I'm working from chapter one of 'Core Java 2'
 */

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Vector;
import java.awt.geom.RectangularShape;

//Bounce is the main class.  It opens a BounceFrame window.
public class Bounce {
    
    /** Creates a new instance of Main */
    public Bounce() {
    }
    
    /**
     @param args the command line arguments
     */
    public static void main(String[] args) {
        BounceFrame frame = new BounceFrame();
        frame.show();
        Thread myThread = new Thread(frame.bv);
        myThread.start();
    }
    
}//End Bounce

//BouceFrame is the only window.  It contains an Panel
//for the balls to live in, and also two buttons (Start and Kill) for input.
class BounceFrame extends JFrame{
    JPanel canvas,control_panel;  //Balls will go on the canvas; buttons on the control_panel
    JButton startButton, killButton;
    BallVector bv;
    
    //the constructor does the usual window intialization stuff,and also instatiates a
    //BallVector, into which it passes all the relevant information.
    public BounceFrame(){
        setSize(500,400);
        setTitle("Inelastic Collision Simulator");
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
        });
        
        canvas = new JPanel();
        canvas.setBackground(Color.black);
        getContentPane().add(canvas, "Center");
        control_panel = new JPanel();
        startButton = new JButton("Start New Ball");
        control_panel.add(startButton);
        killButton = new JButton("Kill Oldest Ball");
        control_panel.add(killButton);
        bv = new BallVector(canvas, startButton,killButton);
        startButton.addActionListener(bv);
        killButton.addActionListener(bv);
        getContentPane().add(control_panel,"South");
        
    }
    
}//End BounceFrame


//BallVector contains a dynamicly sized list of Balls, and some information about the
//enviroment in which the balls live.  It mainly exists to streamline the display of the balls;
//things get very confused if each ball tries to display itself.  It also handles interactions
//between balls, which right now means inelastic collisions.
class BallVector extends Vector implements ActionListener, Runnable{
    JPanel b;               //Draw balls on this panel
    JButton go,end;         //When go is clicked, create a new ball.  When end, remove a ball
    int time=0;             //time and TIME govern the regular destruction and creation of Balls
    final static int TIME=500;
    
    //Parallel required Vector methods, returning Balls instead of Objects
    public Ball ballAt(int i){return (Ball)(super.elementAt(i));}
    
    //This is the only valid constructor for BallVector
    public BallVector(JPanel box,JButton go,JButton end){
        super(10,10);
        b=box;
        this.go=go;
        this.end=end;
    }
    
    public void addBall(){
        Ball new_ball = new Ball(b);
        add(new_ball);
        //System.out.println(Integer.toString(size()));
    }
    
    //Destroy the oldest ball.  I call die() because I want to interupt the ball's thread myself;
    //  "You ever kill a Ball just to watch it die?"
    //Supposedly Vector.remove() will destroy the object, taking it's thread with it, and the
    //thread would stop then.  But if Vector has a memory leak and doesn't destroy its objects
    //then the threads of the balls will be 'loose;' the ball will keep updating, but never
    //interacting or displaying.  Rather than risk wasting cpu time on a ghost ball, I manually
    //interupt the balls thread.
    
    public void removeBall(){
        if(size()!=0) ((Ball)remove(0)).die();
        //System.out.println(Integer.toString(size()));
    }
    
    //BallVector implements Runnable.  This function doesn't check for interupts correctly.
    //This function does two things: calls drawAll() to draw everything to the panel, and
    //checks each pair of balls to see if they've collided.  If they have, then it tells them to 
    //bouce off of each other.  It doesn't move the balls; they do that themselves.
    //Balls sometimes get removed while I checking pair collisions, so I need to be
    //as careful about that as possible.
    //Update:run() now also regularly destroys a ball and adds a ball.  This is to
    //keep energy in the system despite the inelastic collisions.  An elastic implementation
    //would not do this.
    public void run(){
        //System.out.println("New vector up and running!");
        while(true){
            drawAll();
            int i,j;
            for(i=0;i<size();i++){
                for(j=0;j<i;j++){
                    try{
                        ifballAt(i).touchesballAt(j)) ){
                            ballAt(i).bounceOffOfballAt(j) );
                        }
                    }catch(Exception e){System.out.println("Ballvector.run():Ball not found");}
                    
                }
            }
            
            //Cycle of Life:Regularly Destroy the oldest ball and create a new ball
            time++;
            if(size()!=0)
                if(time%TIME==0)try{
                    time=0;
                    addBall();removeBall();
                catch(Exception e){System.out.println("BallVector.run():Cycle of Life Broken!");}
            //end Cycle of Life.
            try{Thread.sleep(13);catch(Exception e){System.out.println("BallVector.run():Interupted!");}
            
        }// end while(true) infinite loop
        
    }
    
    //Clears the screen, and then asks each ball in turn to draw itself
    public void drawAll(){
        Graphics g = b.getGraphics();
        Dimension d= b.getSize();
        if(g==null)System.out.println("BallVector.drawAll():null g ");
        if(d==null)System.out.println("BallVector.drawAll():null d ");
        if(b==null)System.out.println("BallVector.drawAll():null b ");
        
        //Clear the screen
        g.setColor(b.getBackground());
        g.fillRect(0,0,d.width,d.height);
            
        //Draw each ball.
        for(int i=0;i<size();i++){
            tryballAt(i).draw(g);catch(Exception e){System.out.println("BallVector.drawAll():Ball not found!");}
        }
        g.dispose();
    }
    
    //BallVector also implements ActionListener.  This function gets called on an action,
    //determines who caused the action, and either creates or removes a ball accordingly.
    //go and end are passed into BallVector's constructor.  Note that I don't have to
    //check if they're null; if they are nothing happens anyway, since getSource!=null
    public void actionPerformed(ActionEvent e){
        if(e.getSource()==go){addBall();drawAll();}
        if(e.getSource()==end){removeBall();drawAll();}
        
    }//end function actionPerformed
}//end BallVector

//Each Ball is essentially a sprite; it knows it's location, how to move, and what it looks like.
//It also knows how to interact with other Balls, although it does not do so of its own accord;
//BallVector tells balls when to interact.  Each Ball is a circle with random radius, color, and
//velocity.  In a physics sense, each ball has the same mass, regardless of size.
//The location and velocity are handled as floats, and then cast into integers when it comes
//time to draw.  This makes it smoother.
class Ball implements Runnable{
    JPanel box;         //A Ball's home.  Passed into the constuctor, the ball will bounce inside box.
    Thread myThread;    
    private int Delay;  //Determines how often a ball moves.
    private int Diam;   //Diameter
    private Color color;
    private float x=1,y=1,dy=1,dx=2;    //position and velocity
    private int intx,inty;              //integer form of position
    
    //Randomly sets attributes, and starts myThread.
    //So, to create a ball is to start a ball bouncing
    public Ball(JPanel b){
        box=b;
        //Delay=(int)(Math.random()*7)+8;
        Delay=3;
        Diam=(int)(Math.random()*35)+5;
        color = new Color((float)Math.random(),(float)Math.random(),(float)Math.random());
        dx=(1f-(float)(Math.random()*2)) *2/(float)Delay;
        dy=(1f-(float)(Math.random()*2)) *2/(float)Delay;
        intx=(int)x;inty=(int)y;
        //System.out.println("New Ball under construction!");
        myThread = new Thread(this);
        myThread.start();
    }
    
    //Interupts the balls thread.  This doesn't destroy the ball, just stops it.
    //A 'dead' ball still in BallVector will still get displayed.
    public void die(){
        try{myThread.interrupt();catch(Exception e){System.out.println("exception while interrupting!");}
    }
    
    public void draw(Graphics g) {
        g.setColor(color);
        g.drawOval(intx,inty,Diam,Diam);
    }
    
    //Move according to velocity, and bounce of the edges of box, its home JPanel
    public void move() {
        x+=dx;
        y+=dy;
        Dimension d = box.getSize();
        if(x<0){x=0f;if(dx<0f)dx=-dx;}
        if(y<0){y=0f;if(dy<0f)dy=-dy;}
        if(x+Diam>=d.width){x=(float)(d.width-Diam);if(dx>0f)dx=-dx;}
        if(y+Diam>=d.height){y=(float)(d.height-Diam);if(dy>0f)dy=-dy;}
        intx=(int)x;inty=(int)y;
    }
    
    //call move, then sleep.  (A ball is a simple creature.)
    public void run() {
        //System.out.println("Ball Running!");
        try {
            while(!myThread.interrupted()) {
                move();
                Thread.sleep(Delay);
            }
        }catch(InterruptedException e){/*System.out.println("Ball Gone!");*/}
        
    }
    
    //Treats the balls as circles and checks if their centers are closer together
    //than the sum of their radii.
    public boolean touches(Ball other){
        if(this==other)return false;
        float r1=Diam/2+1;float r2=other.Diam/2+1;
        float deltax =(x+r1-other.x-r2);
        float deltay =(y+r1-other.y-r2);
        return ( (deltax*deltax+deltay*deltay)<=((r1+r2)*(r1+r2)) );
        
    }
    
    //This complex method implements an inelastic collision between two balls;
    //both balls get updated, so this method should only get called once per collision.
    //It changes the ball's velocities, and also moves them apart so they don't overlap.
    public void bounceOffOf(Ball other){
        UnitVector I
                =new UnitVector(x+Diam/2-other.x-other.Diam/2,y+Diam/2-other.y-other.Diam/2);
        float r1=Diam/2+1;int r2=other.Diam/2+1;
        float deltax =(x+r1-other.x-r2);
        float deltay =(y+r1-other.y-r2);
        
        //keep the circles from overlapping
        float overlap=((r1+r2)-(float)Math.sqrt(deltax*deltax+deltay*deltay)+1)/2;
        if(overlap>=0){
            x+=I.x*overlap;y+=I.y*overlap;
            intx=(int)x;inty=(int)y;
            other.x-=I.x*overlap;other.y-=I.y*overlap;//move this one way
            other.intx=(int)other.x;other.inty=(int)other.y;//and other the opposite way
        }
        
        float Cdx=(dx+other.dx)/2f;   //the velocity of the center of mass
        float Cdy=(dy+other.dy)/2f;
        
        float Rdx= dx-Cdx; // velocity of this relative to COM
        float Rdy= dy -Cdy;
        float Rdv=Rdx*I.x+Rdy*I.y;  //Component along impulse direction
        //subtract off all velocity towards other ball
        Rdx-=Rdv*I.x;
        Rdy-=Rdv*I.y;
        float R2dx=other.dx -Cdx; //velocity of other relative to COM
        float R2dy=other.dx -Cdy;
        float R2dv=R2dx*I.x+R2dy*I.y;  //Component along impulse direction
        //subtract off all velocity towards this ball
        R2dx-=R2dv*I.x;
        R2dy-=R2dv*I.y;
        
        //almost done here; change members based on calculations
        //recompute dx and dy from relative and COM velocity
        dx=Rdx+Cdx;
        dy=Rdy+Cdy;
        //ditto for other
        other.dx=R2dx+Cdx;
        other.dy=R2dy+Cdy;
    }
}//end Ball

//A silly little class I used to help with the math in Ball.bounceOffOf()
class UnitVector{
    float x,y;
    public UnitVector(){x=1f;y=0f;}
    public UnitVector(float in_x,float in_y){
        x=in_x;y=in_y;
        normalize();
    }
    public float dot(UnitVector v){
        return x*v.x + y*v.y;
    }
    public void rotate(float angle){
        double dx,dy,dl,c,s;
        dx=(double)x;dy=(double)y;
        c=Math.cos(angle);
        s=Math.sin(angle);
        dx=dx*c -dy*s;
        dy=dy*c+dx*s;
        dl=Math.sqrt(dx*dx+dy*dy);
        dx=dx/dl;
        dy=dy/dl;
        x=(float)dx;y=(float)dy;
    }
    public void normalize(){
        double dx,dy,dl;
        dx=(double)x;dy=(double)y;
        dl=Math.sqrt(dx*dx+dy*dy);
        dx=dx/dl;
        dy=dy/dl;
        x=(float)dx;y=(float)dy;
    }
}
Java2html