/******************************************************************************
* Compilation: javac CollisionSystem.java
* Execution: java CollisionSystem
* Creates n random particles and simulates their motion according
* to the laws of elastic collisions.
*
******************************************************************************/
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.util.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* The {@code CollisionSystem} class represents a collection of particles
* moving in the unit box, according to the laws of elastic collision.
* This event-based simulation relies on a priority queue.
*/
public class CollisionSystem {
private final static double HZ = 0.5; // number of redraw events per clock tick
private PriorityQueue<Event> pq; // the priority queue
private double t = 0.0; // simulation clock time
private Particle[] particles; // the array of particles
/**
* Initializes a system with the specified collection of particles.
* The individual particles will be mutated during the simulation.
*
* @param particles the array of particles
*/
public CollisionSystem(Particle[] particles) {
this.particles = particles.clone(); // defensive copy
}
// updates priority queue with all new events for particle a
private void predict(Particle a, double limit) {
if (a == null) return;
// particle-particle collisions
for (int i = 0; i < particles.length; i++) {
double dt = a.timeToHit(particles[i]);
if (t + dt <= limit)
pq.add(new Event(t + dt, a, particles[i]));
}
// particle-wall collisions
double dtX = a.timeToHitVerticalWall();
double dtY = a.timeToHitHorizontalWall();
if (t + dtX <= limit) pq.add(new Event(t + dtX, a, null));
if (t + dtY <= limit) pq.add(new Event(t + dtY, null, a));
}
// redraw all particles
private void redraw(double limit) {
MyStdDraw.clear();
for (int i = 0; i < particles.length; i++) {
MyStdDraw.draw(particles[i]);
}
MyStdDraw.show();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (t < limit) {
pq.add(new Event(t + 1.0 / HZ, null, null));
}
}
/**
* Simulates the system of particles for the specified amount of time.
*
* @param limit the amount of time
*/
public void simulate(double limit) {
// initialize PQ with collision events and redraw event
pq = new PriorityQueue<Event>();
for (int i = 0; i < particles.length; i++) {
predict(particles[i], limit);
}
pq.add(new Event(0, null, null)); // redraw event
// the main event-driven simulation loop
while (!pq.isEmpty()) {
// get impending event, discard if invalidated
Event e = pq.remove();
if (!e.isValid()) continue;
Particle a = e.a;
Particle b = e.b;
// physical collision, so update positions, and then simulation clock
for (int i = 0; i < particles.length; i++)
particles[i].move(e.time - t);
t = e.time;
// process event
if (a != null && b != null) a.bounceOff(b); // particle-particle collision
else if (a != null && b == null) a.bounceOffVerticalWall(); // particle-wall collision
else if (a == null && b != null) b.bounceOffHorizontalWall(); // particle-wall collision
else if (a == null && b == null) redraw(limit); // redraw event
// update the priority queue with new collisions involving a or b
predict(a, limit);
predict(b, limit);
}
}
/***************************************************************************
* An event during a particle collision simulation. Each event contains
* the time at which it will occur (assuming no supervening actions)
* and the particles a and b involved.
*
* - a and b both null: redraw event
* - a null, b not null: collision with vertical wall
* - a not null, b null: collision with horizontal wall
* - a and b both not null: binary collision between a and b
*
***************************************************************************/
private static class Event implements Comparable<Event> {
private final double time; // time that event is scheduled to occur
private final Particle a, b; // particles involved in event, possibly null
private final int countA, countB; // collision counts at event creation
// create a new event to occur at time t involving a and b
public Event(double t, Particle a, Particle b) {
this.time = t;
this.a = a;
this.b = b;
if (a != null) countA = a.count();
else countA = -1;
if (b != null) countB = b.count();
else countB = -1;
}
// compare times when two events will occur
public int compareTo(Event that) {
return Double.compare(this.time, that.time);
}
// has any collision occurred between when event was created and now?
public boolean isValid() {
if (a != null && a.count() != countA) return false;
if (b != null && b.count() != countB) return false;
return true;
}
}
private static final int N = 100;
/**
* Unit tests the {@code CollisionSystem} data type.
* Reads in the particle collision system from a standard input
* (or generates {@code N} random particles if a command-line integer
* is specified); simulates the system.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
Particle[] particles = new Particle[N];
// create n random particles
for (int i = 0; i < N; i++) {
particles[i] = new Particle();
}
// create collision system and simulate
CollisionSystem system = new CollisionSystem(particles);
system.simulate(10000);
}
}
/*Encapsulating the knowledge from swing graph*/
class MyStdDraw {
private static final int BORDERSIZE = 512;
static JFrame frame;
static BufferedImage offscreenImage;
static BufferedImage onscreenImage;
static Graphics2D offscreen;
static Graphics2D onscreen;
static final Color BALLCOLOR = Color.BLACK;
static {init();}
public static void draw(Particle p) {
offscreen.fill(new Ellipse2D.Double((p.rx - p.radius/2) * BORDERSIZE, (p.ry - p.radius/2) * BORDERSIZE
, p.radius * BORDERSIZE, p.radius * BORDERSIZE));
}
public static void show() {
offscreen.setColor(BALLCOLOR);
onscreen.drawImage(offscreenImage, 0, 0, null);
frame.repaint();
}
public static void clear() {
offscreen.setColor(Color.WHITE);
offscreen.fillRect(0, 0, BORDERSIZE, BORDERSIZE);
show();
}
// init
private static void init() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
offscreenImage = new BufferedImage(BORDERSIZE, BORDERSIZE, BufferedImage.TYPE_INT_ARGB);
onscreenImage = new BufferedImage(BORDERSIZE, BORDERSIZE, BufferedImage.TYPE_INT_ARGB);
offscreen = offscreenImage.createGraphics();
onscreen = onscreenImage.createGraphics();
clear();
// frame stuff
ImageIcon icon = new ImageIcon(onscreenImage);
JLabel draw = new JLabel(icon);
frame.setContentPane(draw);
frame.pack();
frame.setVisible(true);
}
}
/**
* Encapsulating the knowledge from physics course
*
* The {@code Particle} class represents a particle moving in the unit box,
* with a given position, velocity, radius, and mass. Methods are provided
* for moving the particle and for predicting and resolvling elastic
* collisions with vertical walls, horizontal walls, and other particles.
* This data type is mutable because the position and velocity change.
*/
class Particle {
private static final double INFINITY = Double.POSITIVE_INFINITY;
private static final Random r = new Random();
public double rx, ry; // position
private double vx, vy; // velocity
private int count; // number of collisions so far
public final double radius; // radius
private final double mass; // mass
public Particle() {
rx = r.nextDouble();
ry = r.nextDouble();
vx = r.nextDouble()*0.005 - 0.005;
vy = r.nextDouble()*0.005 - 0.005;
radius = 0.02;
mass = 0.5;
}
public void move(double dt) {
rx += vx * dt;
ry += vy * dt;
}
/**
* the number of collisions involving this particle with
* vertical walls, horizontal walls, or other particles
*/
public int count() {
return count;
}
/**
* Returns the amount of time for this particle to collide with the specified
* particle, assuming no interening collisions.
*/
public double timeToHit(Particle that) {
if (this == that) return INFINITY;
double dx = that.rx - this.rx;
double dy = that.ry - this.ry;
double dvx = that.vx - this.vx;
double dvy = that.vy - this.vy;
double dvdr = dx*dvx + dy*dvy;
if (dvdr > 0) return INFINITY;
double dvdv = dvx*dvx + dvy*dvy;
double drdr = dx*dx + dy*dy;
double sigma = this.radius + that.radius;
double d = (dvdr*dvdr) - dvdv * (drdr - sigma*sigma);
// if (drdr < sigma*sigma) StdOut.println("overlapping particles");
if (d < 0) return INFINITY;
return -(dvdr + Math.sqrt(d)) / dvdv;
}
public double timeToHitVerticalWall() {
if (vx > 0) return (1.0 - rx - radius) / vx;
else if (vx < 0) return (radius - rx) / vx;
else return INFINITY;
}
public double timeToHitHorizontalWall() {
if (vy > 0) return (1.0 - ry - radius) / vy;
else if (vy < 0) return (radius - ry) / vy;
else return INFINITY;
}
public void bounceOff(Particle that) {
double dx = that.rx - this.rx;
double dy = that.ry - this.ry;
double dvx = that.vx - this.vx;
double dvy = that.vy - this.vy;
double dvdr = dx*dvx + dy*dvy; // dv dot dr
double dist = this.radius + that.radius; // distance between particle centers at collison
// magnitude of normal force
double magnitude = 2 * this.mass * that.mass * dvdr / ((this.mass + that.mass) * dist);
// normal force, and in x and y directions
double fx = magnitude * dx / dist;
double fy = magnitude * dy / dist;
// update velocities according to normal force
this.vx += fx / this.mass;
this.vy += fy / this.mass;
that.vx -= fx / that.mass;
that.vy -= fy / that.mass;
// update collision counts
this.count++;
that.count++;
}
public void bounceOffVerticalWall() {
vx = -vx;
vx *= 0.5;
count++;
}
public void bounceOffHorizontalWall() {
vy = -vy;
vy *= 0.5;
count++;
}
}
/*
* copyright: GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* */