碰撞检测
采用事件驱动模型来模拟小球的运动及碰撞,首先预测所有的碰撞事件,按碰撞时间存入优先队列,
碰撞发生时重新计算发生碰撞的小球的速度,方向,重新预测该小球可能发生的的碰撞事件存入优先队列
同时定义一个重复的事件存入优先队列,用来模拟时钟及重绘每个小球的位置。
遍历优先队列模型小球的运动和碰撞,示意图如下:
public class CollisionSystem {
private static final double HZ = 0.5; // number of redraw events per clock tick
private MinPQ
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
//预测可能与小球a发生的所有碰撞事件存入优先队列
private void predict(Particle a, double limit) {
if (a == null) return;
// particle-particle collisions
for (int i = 0; i < particles.length; i++) {
//预测小球a与小球particles[i]发生碰撞的时间,如果时间有效存入优先队列
double dt = a.timeToHit(particles[i]);
if (t + dt <= limit)
pq.insert(new Event(t + dt, a, particles[i]));
}
// particle-wall collisions
//预测小球与墙发生碰撞的时间,如果时间有效存入优先队列
double dtX = a.timeToHitVerticalWall();
double dtY = a.timeToHitHorizontalWall();
if (t + dtX <= limit) pq.insert(new Event(t + dtX, a, null));
if (t + dtY <= limit) pq.insert(new Event(t + dtY, null, a));
}
// redraw all particles
private void redraw(double limit) {
StdDraw.clear();
for (int i = 0; i < particles.length; i++) {
particles[i].draw();
}
StdDraw.show();
StdDraw.pause(20);
if (t < limit) {
pq.insert(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 MinPQ
//预测每个小球可能的碰撞事件
for (int i = 0; i < particles.length; i++) {
predict(particles[i], limit);
}
//模拟时钟,重绘小球位置
pq.insert(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.delMin();
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,a与b碰撞,修改碰撞后a和b的速度
else if (a != null && b == null) a.bounceOffVerticalWall(); // particle-wall collision,a与垂直墙碰撞,修改a水平方向速度
else if (a == null && b != null) b.bounceOffHorizontalWall(); // particle-wall collision,b与水平墙碰撞,修改b垂直方向速度
else if (a == null && b == null) redraw(limit); // redraw event
// update the priority queue with new collisions involving a or b
//重新预测a和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
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;
}
}
/**
* 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) {
StdDraw.setCanvasSize(600, 600);
// enable double buffering
StdDraw.enableDoubleBuffering();
// the array of particles
Particle[] particles;
// create n random particles
//随机产生几个小球,定义每个小球的半径,x/y方向的位置,xy/方向的速度
if (args.length == 1) {
int n = Integer.parseInt(args[0]);
particles = new Particle[n];
for (int i = 0; i < n; i++)
particles[i] = new Particle();
}
// or read from standard input
else {
int n = StdIn.readInt();
particles = new Particle[n];
for (int i = 0; i < n; i++) {
double rx = StdIn.readDouble();
double ry = StdIn.readDouble();
double vx = StdIn.readDouble();
double vy = StdIn.readDouble();
double radius = StdIn.readDouble();
double mass = StdIn.readDouble();
int r = StdIn.readInt();
int g = StdIn.readInt();
int b = StdIn.readInt();
Color color = new Color(r, g, b);
particles[i] = new Particle(rx, ry, vx, vy, radius, mass, color);
}
}
// create collision system and simulate
CollisionSystem system = new CollisionSystem(particles);
//模拟小球的运动和碰撞
system.simulate(10000);
}
}
package chapter6_1;
/******************************************************************************
* Compilation: javac Particle.java
* Execution: none
* Dependencies: StdDraw.java
*
* A particle moving in the unit box with a given position, velocity,
* radius, and mass.
*
******************************************************************************/
import java.awt.Color;
import StdLib.StdDraw;
import StdLib.StdRandom;
/**
* 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.
*
* For additional documentation,
* see Section 6.1 of
* Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
public class Particle {
private static final double INFINITY = Double.POSITIVE_INFINITY;
private double rx, ry; // position
private double vx, vy; // velocity
private int count; // number of collisions so far
private final double radius; // radius
private final double mass; // mass
private final Color color; // color
/**
* Initializes a particle with the specified position, velocity, radius, mass, and color.
*
* @param rx x-coordinate of position
* @param ry y-coordinate of position
* @param vx x-coordinate of velocity
* @param vy y-coordinate of velocity
* @param radius the radius
* @param mass the mass
* @param color the color
*/
public Particle(double rx, double ry, double vx, double vy, double radius, double mass, Color color) {
this.vx = vx;
this.vy = vy;
this.rx = rx;
this.ry = ry;
this.radius = radius;
this.mass = mass;
this.color = color;
}
/**
* Initializes a particle with a random position and velocity.
* The position is uniform in the unit box; the velocity in
* either direciton is chosen uniformly at random.
*/
public Particle() {
rx = StdRandom.uniform(0.0, 1.0);
ry = StdRandom.uniform(0.0, 1.0);
vx = StdRandom.uniform(-0.005, 0.005);
vy = StdRandom.uniform(-0.005, 0.005);
radius = 0.02;
mass = 0.5;
color = Color.BLACK;
}
/**
* Moves this particle in a straight line (based on its velocity)
* for the specified amount of time.
*
* @param dt the amount of time
*/
public void move(double dt) {
rx += vx * dt;
ry += vy * dt;
}
/**
* Draws this particle to standard draw.
*/
public void draw() {
StdDraw.setPenColor(color);
StdDraw.filledCircle(rx, ry, radius);
}
/**
* Returns the number of collisions involving this particle with
* vertical walls, horizontal walls, or other particles.
* This is equal to the number of calls to {@link #bounceOff},
* {@link #bounceOffVerticalWall}, and
* {@link #bounceOffHorizontalWall}.
*
* @return 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.
*
* @param that the other particle
* @return the amount of time for this particle to collide with the specified
* particle, assuming no interening collisions;
* {@code Double.POSITIVE_INFINITY} if the particles will not collide
*/
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;
if (dvdv == 0) return INFINITY;
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;
}
/**
* Returns the amount of time for this particle to collide with a vertical
* wall, assuming no interening collisions.
*
* @return the amount of time for this particle to collide with a vertical wall,
* assuming no interening collisions;
* {@code Double.POSITIVE_INFINITY} if the particle will not collide
* with a vertical wall
*/
public double timeToHitVerticalWall() {
if (vx > 0) return (1.0 - rx - radius) / vx;
else if (vx < 0) return (radius - rx) / vx;
else return INFINITY;
}
/**
* Returns the amount of time for this particle to collide with a horizontal
* wall, assuming no interening collisions.
*
* @return the amount of time for this particle to collide with a horizontal wall,
* assuming no interening collisions;
* {@code Double.POSITIVE_INFINITY} if the particle will not collide
* with a horizontal wall
*/
public double timeToHitHorizontalWall() {
if (vy > 0) return (1.0 - ry - radius) / vy;
else if (vy < 0) return (radius - ry) / vy;
else return INFINITY;
}
/**
* Updates the velocities of this particle and the specified particle according
* to the laws of elastic collision. Assumes that the particles are colliding
* at this instant.
*
* @param that the other particle
*/
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++;
}
/**
* Updates the velocity of this particle upon collision with a vertical
* wall (by reflecting the velocity in the x-direction).
* Assumes that the particle is colliding with a vertical wall at this instant.
*/
public void bounceOffVerticalWall() {
vx = -vx;
count++;
}
/**
* Updates the velocity of this particle upon collision with a horizontal
* wall (by reflecting the velocity in the y-direction).
* Assumes that the particle is colliding with a horizontal wall at this instant.
*/
public void bounceOffHorizontalWall() {
vy = -vy;
count++;
}
/**
* Returns the kinetic energy of this particle.
* The kinetic energy is given by the formula 1/2 m v2,
* where m is the mass of this particle and v is its velocity.
*
* @return the kinetic energy of this particle
*/
public double kineticEnergy() {
return 0.5 * mass * (vx*vx + vy*vy);
}
}