各位读者盆友,上午好!我们终于接近算法系列的尾声拉。这里介绍看起来很酷炫的一种算法实践:模拟粒子碰撞。
其实,这方面有非常广阔的使用天地,曾经在关于宇宙的记录片中就看到科学家们研究星球变化、宇宙演化就用计算你模拟的,很酷炫,真的是超酷炫!背后的技术也非常难。
先看结果,酷炫吧:
本博客代码示例均来自:算法 Algorithmes Forth Edition
[美] Robert Sedgewick Kevin Wayne 著 谢路云译
package com.cmh.algorithm.background;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdRandom;
import java.awt.*;
/**
* 基于事件驱动模拟的粒子碰撞
* 粒子对象
* Author:起舞的日子
* Date: 2020/5/1 00:51
*/
public class Particle {
private static final double INFINITY = Double.POSITIVE_INFINITY;
/**
* position(位置)
*/
private double rx, ry;
/**
* velocity(速度)
*/
private double vx, vy;
/**
* number of collisions so far
* 碰撞的次数
*/
private int count;
/**
* 半径
*/
private final double radius;
/**
* 质量
*/
private final double mass;
private final Color 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;
}
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;
}
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;
}
/**
* 距离该粒子和粒子b碰撞所需的时间
*/
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 (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;
double dist = this.radius + that.radius;
double magnitude = 2 * this.mass * that.mass * dvdr / ((this.mass + that.mass) * dist);
double fx = magnitude * dx / dist;
double fy = magnitude * dy / dist;
this.vx += fx / this.mass;
this.vy += fy / this.mass;
that.vx -= fx / that.mass;
that.vy -= fy / that.mass;
this.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);
}
}
private class Event implements Comparable<Event> {
private final double time;
private final Particle a, b;
private final int countA, countB;
public Event(double t, Particle a, Particle b) {
//创造一个发生在时间t且a和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;
}
}
public int compareTo(Event that) {
if (this.time < that.time) {
return -1;
} else if (this.time > that.time) {
return +1;
} else {
return 0;
}
}
public boolean isValid() {
if (a != null && a.count() != countA) {
return false;
}
if (b != null && b.count() != countB) {
return false;
}
return true;
}
}
package com.cmh.algorithm.background;
import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.StdDraw;
/**
* 基于事件模拟互相碰撞的粒子(框架)
* Author:起舞的日子
* Date: 2020/5/1 03:49
*/
public class CollisionSystem {
/**
* 优先队列
*/
private MinPQ<Event> pq;
/**
* 模拟时钟
*/
private double t = 0.0;
/**
* 粒子数组
*/
private Particle[] particles;
public CollisionSystem(Particle[] particles) {
this.particles = particles;
}
/**
* 预测其他粒子的碰撞事件
*/
private void predictCollisions(Particle a, double limit) {
if (a == null) {
return;
}
for (int i = 0; i < particles.length; i++) {
//将与particles[i]发生碰撞的事件插入pq中
double dt = a.timeToHit(particles[i]);
if (t + dt < limit) {
pq.insert(new Event(t + dt, a, particles[i]));
}
}
double dtX = a.timeToHitVerticalWall();
if (t + dtX <= limit) {
pq.insert(new Event(t + dtX, a, null));
}
double dtY = a.timeToHitHorizontalWall();
if (t + dtY <= limit) {
pq.insert(new Event(t + dtY, null, a));
}
}
public void redraw(double limit, double Hz) {
//重绘事件:重新画出所有的粒子
StdDraw.clear();
for (int i = 0; i < particles.length; i++) {
particles[i].draw();
}
StdDraw.pause(100);
if (t < limit) {
pq.insert(new Event(t + 1.0 / Hz, null, null));
}
}
/**
* 基于事件模拟互相碰撞的粒子(主循环)
*/
public void simulate(double limit, double Hz) {
pq = new MinPQ<>();
for (int i = 0; i < particles.length; i++) {
predictCollisions(particles[i], limit);
}
//添加重绘事件
pq.insert(new Event(0, null, null));
while (!pq.isEmpty()) {
//处理一个事件
Event event = pq.delMin();
if (!event.isValid()) {
continue;
}
for (int i = 0; i < particles.length; i++) {
//更新粒子的位置
particles[i].move(event.time - t);
}
t = event.time;
Particle a = event.a, b = event.b;
if (a != null && b != null) {
a.bounceOff(b);
} else if (a != null && b == null) {
a.bounceOffVerticalWall();
} else if (a == null && b != null) {
b.bounceOffHorizontalWall();
} else if (a == null && b == null) {
redraw(limit, Hz);
}
predictCollisions(a, limit);
predictCollisions(b, limit);
}
}
private class Event implements Comparable<Event> {
private final double time;
private final Particle a, b;
private final int countA, countB;
public Event(double t, Particle a, Particle b) {
//创造一个发生在时间t且a和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;
}
}
public int compareTo(Event that) {
if (this.time < that.time) {
return -1;
} else if (this.time > that.time) {
return +1;
} else {
return 0;
}
}
public boolean isValid() {
if (a != null && a.count() != countA) {
return false;
}
if (b != null && b.count() != countB) {
return false;
}
return true;
}
}
public static void main(String[] args) {
StdDraw.pause(0);
int N = Integer.parseInt(args[0]);
Particle[] particles = new Particle[N];
for (int i = 0; i < N; i++) {
particles[i] = new Particle();
}
CollisionSystem system = new CollisionSystem(particles);
system.simulate(10000, 0.5);
}
}
(develop)]$ javac -cp ~/Downloads/algs4.jar: ./com/cmh/algorithm/background/CollisionSystem.java
(develop)]$java -cp ~/Downloads/algs4.jar: com/cmh/algorithm/background/CollisionSystem 100
https://github.com/cmhhcm/my2020.git