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{
if( ballAt(i).touches( ballAt(j)) ){
ballAt(i).bounceOffOf( ballAt(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++){
try{ ballAt(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;
}
}
|