玩转重力
一个重力模拟粒子系统
引言
这篇文章是一个很好的例子,说明了程序员无聊时会做什么。代码本身没什么激动人心的,也许是 GDI+ 的一个温和的介绍。这只是一个有趣的实验项目,玩起来很上瘾。所以我想我应该分享一下。
我一直在想,宇宙中的每一个粒子都在不断地受到宇宙中每一个粒子的引力影响,并想看看在软件中模拟出来会是什么样子。自从我翻开物理书以来已经很多年了,所以我不能保证这个项目的准确性……但它确实很漂亮。
代码
计算重力加速度
我选择的计算每个粒子 p<sub>n</sub>
对粒子 p
的引力的方法是:我创建一个从一个粒子指向另一个粒子的向量,方法是将 p
的位置向量减去 p<sub>n</sub>
的位置向量。
Vector unit = p2.Location - p.Location;
然后,计算所得向量的模
float magnitude = (float)Math.Sqrt(
(unit.X * unit.X)
+ (unit.Y * unit.Y)
+ (unit.Z * unit.Z)
);
然后,你就拥有了计算加速度所需的一切。
float factor = (
GravitationalConstant * (
(p.Mass * p2.Mass) / (magnitude * magnitude * magnitude)
)
) / p.Mass;
所得向量是改变你的粒子由于粒子 p<sub>n</sub>
的引力而产生的速度所必需的。我最初使用的是实际的引力常数 G = 6.672e-11
,但为了模拟的目的将其放大了,允许使用更小的质量。
完整的计算循环如下。请注意,对于每个粒子,你都要计算它受到的来自所有其他粒子的引力影响。
foreach (Particle p in particles) {
...
if (particles.Count > 1) {
Vector a = new Vector();
foreach (Particle p2 in particles) {
if (object.ReferenceEquals(p, p2)) { continue; }
Vector unit = p2.Location - p.Location;
float magnitude = (float)Math.Sqrt(
(unit.X * unit.X)
+ (unit.Y * unit.Y)
+ (unit.Z * unit.Z));
float factor = (GravitationalConstant * (
(p.Mass * p2.Mass) / (magnitude * magnitude * magnitude)
)) / p.Mass;
unit *= factor;
a += unit;
}
p.Velocity += a;
}
...
}
非弹性碰撞中的动量守恒
我的碰撞检测很原始。我只检测粒子的边界矩形是否与其他粒子相交。Z 轴是事后才考虑的,万一 X 和 Y 轴相交,我就会检查 Z 轴位置是否在 5 以内。我偷懒了,我能说什么呢。我并不太关心 Z 轴,但想将其包含在计算中。
无论如何,当两个粒子碰撞时,我会合并它们的颜色和质量。我稍微增加它们的大小。并合并它们的动量以找到结果粒子的速度。
private Particle Merge(Particle i, Particle j, Graphics g) {
...
Vector v = ((i.Velocity * i.Mass) + (j.Velocity * j.Mass));
v /= i.Mass + j.Mass;
...
}
演示应用程序
控件
有开始、停止和暂停模拟的菜单选项。暂停的键盘快捷键是 F5,开始的快捷键是 Ctrl+E(从 SQL 查询分析器执行 :)。你必须先启动模拟才能添加粒子,并且可以随时重新开始以重置所有内容。单击 PictureBox 中的任何位置都会在该位置创建一个粒子。复选框下方的文本框决定了新粒子将具有的属性。你可以设置其初始速度的 X、Y 和 Z 分量、质量以及像素大小。
按钮
文本框下的三个按钮,分别标为“B”、“M”和“S”,方便地将属性设置为预定义值。“B”创建一个大质量粒子,“M”创建一个中等质量粒子,“S”创建一个小质量粒子。“Background”按钮允许你更改 PictureBox 的背景颜色。
复选框
- Trace:切换跟踪。现在跟踪显示每个粒子的轨迹。
- Collisions:切换碰撞检测。
- Show Vel:以红色显示速度向量。
- Vel Box:显示速度向量的边界框。
- Show Acc:以粒子的颜色显示加速度向量。
- Acc Box:显示加速度向量的边界框。
速度和加速度向量的视觉表示被放大了不少,以便在我的工作尺度上可见。对于质量更大的例子,加速度向量线可能很长。我可能会考虑根据质量改变它的缩放比例,但很可能不会。
速度向量 | 速度框 | 加速度向量 | 加速度框 | 速度和加速度 |
| | | | |
粒子列表视图
最后,窗口底部的列表视图显示了粒子的一些属性。列表视图现在显示所有粒子,但仅在暂停时显示。暂停时,如果你在列表视图中选择粒子并按 DELETE
,它将删除所选粒子。
结论
我认为这个项目没什么可学的,除了 GDI+ 的一些简单用法,我想如果你不熟悉运算符重载,你可以在 Vector
类中找到一些有趣的东西。这只是一个有趣的实验项目,而且它看起来很酷。如果有一天我闲得无聊,我可能会考虑把它变成一个屏保。
历史
- 2008/07/24:根据 protix 和 Zimriel 的建议,我已更新计算以更准确地表示轨道。我通过绘制到图像然后替换 PictureBox 的图像(简易双缓冲)修复了非跟踪闪烁问题。跟踪也已重新完成。起初,我想用低 alpha 值的白色清除图像,以便之前的绘制能够淡出。但是,将 alpha 颜色绘制到整个图像需要太长时间。最后,我让每个粒子跟踪一些先前的位置,然后以递减的 alpha 值重新绘制它们。可能有更好的解决方案,但目前这个可以工作。