Download source: collide.zip
collide.javaimport 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