65.9K
CodeProject 正在变化。 阅读更多。
Home

玩转重力

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (70投票s)

2007年7月21日

CPOL

4分钟阅读

viewsIcon

158407

downloadIcon

3509

一个重力模拟粒子系统

引言

这篇文章是一个很好的例子,说明了程序员无聊时会做什么。代码本身没什么激动人心的,也许是 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 值重新绘制它们。可能有更好的解决方案,但目前这个可以工作。
© . All rights reserved.