bouncing balls

Download source: collide.zip

classes


collide.java

import javafx.animation.Animation;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import java.util.*;

class TestPanel extends Pane
{
    ArrayList<Ball>  balls;
    ArrayList<Collision> collisions;
    double width, height;
    
    TestPanel()
    {
    	Ball ball1, ball2;
	setStyle("-fx-background-color: honeydew;");
	balls = new ArrayList<Ball>();
	collisions = new ArrayList<Collision>();
	balls.add(new Ball());
	balls.add( new Ball(330,80,-5,8,Color.BLUE));
	    balls.add(new Ball(30,200,-9,7,Color.GREEN));
	for (Ball b: balls) getChildren().add(b.circle);
	//for (Ball b: balls) System.out.println(b);
	//for (Node n: getChildren()) System.out.println(n);
    }

    public void update_collision_list(double tstep)
    {
	Collision c;
	collisions.clear();
	for (Ball ball: balls)	{
	    c = intersect_window(ball,tstep);
	    if (c!=null) collisions.add(c);
	}
	int nballs = balls.size();
	for (int i=0; i<nballs-1; i++) {
	    for (int j=i+1; j<nballs; j++) {
		Ball b1 = balls.get(i);
		Ball b2 = balls.get(j);
		c = intersect_balls(b1,b2,tstep);
		if (c!=null) collisions.add(c);
	    }
	}
    }	

    public void update()
    {
	width = getWidth();
	height = getHeight();
	double tstep, tmore;
	tmore = 1.0;
	while (tmore>0.0) {
	    update_collision_list(tmore);
	    if (collisions.size()>0) {
		java.util.List<Collision> list = collisions; 
		Collections.<Collision>sort(list);
		Collision c = collisions.get(0);
		tstep = c.timestep;
		tmore = tmore - tstep;
		for (Ball ball: balls) ball.move(tstep);
		c.update_velocity();
	    }
	    else {
		tstep = tmore;
		tmore = 0.0;
		for (Ball ball: balls) ball.move(tstep);
	    }
	}
    }

   Collision intersect_window(Ball ball, double tstep)
    {
	double [] t = new double[4];
	double tmax = 1000;
	if (ball.vx<0) {
	    t[0] = (ball.px-ball.radius)/(-ball.vx);
	}
	else t[0] = tmax;
	if (ball.vx>0) {
	    t[1] = (width - ball.px - ball.radius)/(ball.vx);
	}
	else t[1] = tmax;
	if (ball.vy<0) {
	    t[2] = (ball.py-ball.radius)/(-ball.vy);
	}
	else t[2] =  tmax;
	if (ball.vy>0) {
	    t[3] = (height - ball.py - ball.radius)/ball.vy;
	}
	else t[3] = tmax;
	int idx = 0;
	double tx = t[0];
	for (int i=1; i<4; i++) {
	    if (t[i]>tx) continue;
	    tx = t[i];
	    idx = i;
	}
	if (tx>tstep) {
	    return null; // does not intersect
	}
	return new Collision(tx,ball,idx);
    }

    Collision intersect_balls(Ball ball1, Ball ball2, double tstep)
    {
	// relative velocity
	double rx = ball2.vx - ball1.vx;
	double ry = ball2.vy - ball1.vy;
	// relative position
	double px = ball2.px - ball1.px;
	double py = ball2.py - ball1.py;
	// test radius
	double r = ball1.radius + ball2.radius - 1;
	double C =  px*px + py*py - r*r;
	if (C<0) return null; // already intersecting
	double B = rx*px+ry*py;
	double A = rx*rx+ry*ry;
	if (A<=0.0) return null; // no relative velocity
	// quadratic At^2 + 2Bt + C = 0
	double radical = B*B-A*C;
	if (radical<=0.0) return null; // no intersection (balls miss)
	double R = Math.sqrt(radical);
	double t;
	if (B>0) t = (-B + R)/A;
	else t = -(B+R)/A;
	if (t<=0.0 || t>tstep) return null; // no intersection within time limit
	return new Collision(t,ball1,ball2);
    }
}

public class collide extends Application
{
    ArrayList<Ball> balls = new ArrayList<Ball>();

    @Override // Override the start method in the Application class
    public void start(Stage primaryStage) 
    {
	// Create a pane 
	TestPanel pane = new TestPanel();
    	// Create a handler for animation
    	EventHandler<ActionEvent> eventHandler = e -> {
		pane.update();
     };

    // Create an animation
    Timeline animation = new Timeline(
        new KeyFrame(Duration.millis(30), eventHandler));
        animation.setCycleCount(Timeline.INDEFINITE);
        animation.play(); // Start animation
 
   // Create a scene and place it in the stage
    Scene scene = new Scene(pane, 400, 400);
    primaryStage.setTitle("colliding balls"); // Set the stage title
    primaryStage.setScene(scene); // Place the scene in the stage
    primaryStage.show(); // Display the stage

    }
    public static void main(String args[] )
    {
 	launch(args);
    }
}


Maintained by John Loomis, updated Thu Feb 15 20:09:59 2018