▶ 书中第六章部分程序,包括在加上自己补充的代码,粒子碰撞系统及用到的粒子类
● 粒子系统
1 package package01; 2 3 import java.awt.Color; 4 import edu.princeton.cs.algs4.StdIn; 5 import edu.princeton.cs.algs4.StdDraw; 6 import edu.princeton.cs.algs4.MinPQ; 7 import edu.princeton.cs.algs4.Particle; 8 9 public class class01 10 { 11 private static final double HZ = 0.5; // 每个时间步长内重画的次数 12 private MinPQ<Event> pq; // 优先队列 13 private double t = 0.0; // 计时器 14 private Particle[] particles; // 粒子数据列表 15 16 private static class Event implements Comparable<Event> // 碰撞事件类 17 { 18 private final double time; // 预计事件发生时间 19 private final Particle a, b; // 事件中的粒子 20 private final int countA, countB; // 粒子在加入事件中时的已碰撞的次数 21 22 public Event(double inputT, Particle inputA, Particle inputB) // 输入当前时间和两个粒子,计算碰撞事件 23 { 24 time = inputT; 25 a = inputA; 26 b = inputB; 27 countA = (a != null) ? a.count() : -1; 28 countB = (b != null) ? b.count() : -1; 29 } 30 31 public int compareTo(Event that) // 比较两个事件哪个先发生 32 { 33 return Double.compare(this.time, that.time); 34 } 35 36 public boolean isValid() // 判断事件是否有效 37 { 38 return (a == null || a.count() == countA) && (b == null || b.count() == countB);// 原代码简化版 39 //if (a != null && a.count() != countA || b != null && b.count() != countB) // 原代码,只要有粒子当前实际碰撞数与事件中记录的 count 不等,说明事件失效 40 // return false; 41 //return true; 42 } 43 } 44 45 public class01(Particle[] inputParticle) 46 { 47 particles = inputParticle.clone(); // 输入列表的深拷贝 48 } 49 50 private void predict(Particle a, double limit) // 更新优先队列中关于粒子 a 的事件 51 { 52 if (a == null) 53 return; 54 for (int i = 0; i < particles.length; i++) // 预测 a 与各粒子碰撞的时间,只要时间小于阈值就将其放入优先队列 55 { 56 double targetTime = t + a.timeToHit(particles[i]); 57 if (targetTime <= limit) 58 pq.insert(new Event(targetTime, a, particles[i])); 59 } 60 double targetTimeX = t + a.timeToHitVerticalWall(), targetTimeY = t + a.timeToHitHorizontalWall(); 61 if (targetTimeX <= limit) 62 pq.insert(new Event(targetTimeX, a, null)); 63 if (targetTimeY <= limit) 64 pq.insert(new Event(targetTimeY, null, a)); 65 } 66 67 private void redraw(double limit) // 重画所有粒子位置 68 { 69 StdDraw.clear(); 70 for (int i = 0; i < particles.length; i++) 71 particles[i].draw(); 72 StdDraw.show(); 73 StdDraw.pause(20); // 暂停 20 ms 74 if (t < limit) // 还没到时限,加入重画事件 75 pq.insert(new Event(t + 1.0 / HZ, null, null)); 76 } 77 78 public void simulate(double limit) // 模拟器 79 { 80 pq = new MinPQ<Event>(); 81 for (int i = 0; i < particles.length; i++) // 首次计算所有粒子之间的碰撞 82 predict(particles[i], limit); 83 for (pq.insert(new Event(0, null, null)); !pq.isEmpty();) // 多加入一个重画所有粒子位置的事件 84 { 85 Event e = pq.delMin(); // 取出发生时间最近的时间,判断是否有效 86 if (!e.isValid()) 87 continue; 88 Particle a = e.a, b = e.b; 89 for (int i = 0; i < particles.length; i++) // 更新所有粒子位置 90 particles[i].move(e.time - t); 91 t = e.time; // 更新当前时间 92 93 if (a != null && b != null) // 粒子 - 粒子碰撞 94 a.bounceOff(b); 95 else if (a != null && b == null) // 粒子撞竖直墙壁 96 a.bounceOffVerticalWall(); 97 else if (a == null && b != null) 98 b.bounceOffHorizontalWall(); // 粒子撞水平墙壁 99 else if (a == null && b == null) 100 redraw(limit); // 仅重画所有粒子位置 101 predict(a, limit); // 重新预测 a 与 b相关的事件 102 predict(b, limit); 103 } 104 } 105 106 public static void main(String[] args) 107 { 108 StdDraw.setCanvasSize(600, 600); // 窗口大小 109 StdDraw.enableDoubleBuffering(); // double 缓冲区 110 Particle[] particles; // 粒子列表 111 112 if (args.length == 1) // 输入一个参数,生成相应个数的个粒子 113 { 114 int n = Integer.parseInt(args[0]); 115 particles = new Particle[n]; 116 for (int i = 0; i < n; i++) 117 particles[i] = new Particle(); 118 } 119 else // 否则按标准输入流依次输入每个粒子的信息 120 { 121 int n = StdIn.readInt(); 122 particles = new Particle[n]; 123 for (int i = 0; i < n; i++) 124 { 125 double rx = StdIn.readDouble(); 126 double ry = StdIn.readDouble(); 127 double vx = StdIn.readDouble(); 128 double vy = StdIn.readDouble(); 129 double radius = StdIn.readDouble(); 130 double mass = StdIn.readDouble(); 131 int r = StdIn.readInt(); 132 int g = StdIn.readInt(); 133 int b = StdIn.readInt(); 134 Color color = new Color(r, g, b); 135 particles[i] = new Particle(rx, ry, vx, vy, radius, mass, color); 136 } 137 } 138 class01 system = new class01(particles); // 模拟和输出 139 system.simulate(10000); // 模拟事件 10s 140 } 141 }
● 粒子类
package package01; import java.awt.Color; import edu.princeton.cs.algs4.StdDraw; import edu.princeton.cs.algs4.StdRandom; public class class01 { private static final double INFINITY = Double.POSITIVE_INFINITY; private double rx, ry; private double vx, vy; private int count; // 粒子已经碰撞的次数 private final double radius; private final double mass; private final Color color; public class01(double inputRx, double inputRy, double inputVx, double inputVy, double inputRadius, double inputMass, Color inputColor) { rx = inputRx; ry = inputRy; vx = inputVx; vy = inputVy; radius = inputRadius; mass = inputMass; color = inputColor; } public class01() { 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; } public void move(double dt) { rx += vx * dt; ry += vy * dt; } public void draw() // 绘制粒子 { StdDraw.setPenColor(color); StdDraw.filledCircle(rx, ry, radius); } public int count() { return count; } public double timeToHit(class01 that) { if (this == that) return INFINITY; double dx = that.rx - rx, dy = that.ry - ry, dvx = that.vx - vx, dvy = that.vy - vy; double dvdr = dx * dvx + dy * dvy; if (dvdr > 0) // Δx 与 Δv 同号,不会撞 return INFINITY; double dvdv = dvx * dvx + dvy * dvy; if (dvdv == 0) // 速度完全相等,不会撞 return INFINITY; double drdr = dx * dx + dy * dy; double dist = radius + that.radius; double d = (dvdr*dvdr) - dvdv * (drdr - dist * dist); return (d > 0) ? -(dvdr + Math.sqrt(d)) / dvdv : INFINITY; } public double timeToHitVerticalWall() { return (vx > 0) ? (1.0 - rx - radius) / vx : ((vx < 0) ? (radius - rx) / vx : INFINITY); } public double timeToHitHorizontalWall() { return (vy > 0) ? (1.0 - ry - radius) / vy : ((vy < 0) ? (radius - ry) / vy : INFINITY); } public void bounceOff(class01 that) { double dx = that.rx - rx, dy = that.ry - ry; double dvx = that.vx - vx, dvy = that.vy - vy; double dvdr = dx * dvx + dy * dvy; double dist = radius + that.radius; double magnitude = 2 * mass * that.mass * dvdr / ((mass + that.mass) * dist); double fx = magnitude * dx / dist, fy = magnitude * dy / dist; vx += fx / mass; vy += fy / mass; that.vx -= fx / that.mass; that.vy -= fy / that.mass; count++; that.count++; } public void bounceOffVerticalWall() { vx = -vx; count++; } public void bounceOffHorizontalWall() { vy = -vy; count++; } public double kineticEnergy() { return 0.5 * mass * (vx*vx + vy * vy); } }