If you're seeing this message, it means we're having trouble loading external resources on our website.

如果你被网页过滤器挡住,请确保域名*.kastatic.org*.kasandbox.org 没有被阻止.

主要内容

万有引力

物理学中最著名的力也许就是重力。在地球上,我们人类认为重力体现在一个苹果上,这个苹果砸中了艾萨克·牛顿的头部。重力意味着东西会向下落。但这只是我们体验到的重力。事实上,就像地球由于引力而把苹果拉向它一样,苹果也在拉着地球。问题是,地球压倒性的质量,让地球上所有其他物体的重力相互作用都显得微不足道。每一个有质量的物体都会对其他物体施加引力。我们可以用一个公式计算这些力的大小, 如下图所示:
两个球体之间的引力示意图
让我们一起更仔细地研究一下这个公式。
  • F 表示引力, 我们最终想要计算并传递到 applyForce() 函数的矢量。
  • G 表示 万有引力常量,在地球上等于 6.67428 x 10^-11 N·m^2/kg^2。如果你是艾萨克·牛顿或者阿尔伯特·爱因斯坦,这个数对你很重要。如果你是个 ProcessingJS 程序员,这个数并不重要。我们可以用它来使我们世界中的力变弱或变强。直接令这个数等于1或者忽略它也可以。
  • m1m2 分别表示物体 12 的质量。由牛顿第二定律 (F=MA) 可知,我们也可以选择忽略质量。毕竟我们在屏幕上画的形状并没有实际的物理质量。然而,如果我们保留这些值,我们可以创造出更有趣的模拟,其中“较大”的物体比起较小的物体具有更强的引力。
  • r^ 表示从物体 1 指向物体 2 的单位向量。我们稍后就可以看到,可以通过用两个物体的位置相减来计算这个方向向量。
  • r2 表示两个物体距离的平方。让我们多花点时间想一想这里。在公式上面的部分—Gm1m2—数值越大,力也就越大。更大的质量对应更大的力。 更大的 G 也对应更大的力。现在,我们要除一个数,这时的情况是相反的。力的强弱和距离的平方成反比例。一个物体的距离 越远,力就 越弱; 距离 越近,力也就 越强
希望我们现在理解了公式。我们看完了示意图,并分析了公式中的每一个部分。现在我们该想想如果将数学转化为 ProcessingJS 的代码。我们先假设:
我们有两个物体:
  1. 每个物体的位置用 PVector 表示:location1location2
  2. 每个物体各有一个数值表示质量:mass1mass2
  3. 用数值变量 G 表示万有引力常量。
基于以上假设,我们可以算出重力,用 PVector force 表示。我们分两步来求重力。首先,算出公式中的力的方向r^。然后,将质量和距离带入,算出力的大小。
还记得我们是怎样让物体向鼠标加速移动的吗?在这里我们将用相同的方法。
向量是两点之间的差。要得到一个从圆指向鼠标的向量,我们只需用一个点减去另一个点:
var dir = PVector.sub(mouse, location);
在这里,物体 1 对物体 2 施加的引力方向为:
var dir = PVector.sub(location1, location2);
别忘了我们要得到的是一个单位向量,一个只告诉我们方向的向量,所以我们需要在减去位置后标准化这个向量:
dir.normalize();
好的,我们已经知道了力的方向。现在我们只需要计算向量的大小,并相应地缩放向量。
var m = (G * mass1 * mass2) / (distance * distance);
dir.mult(m);
唯一的问题是我们不知道距离。Gmass1mass2 都是给定的,但是在运行上面的代码之前,我们需要计算距离。我们不是有个从一个位置指向另一个位置的向量吗?那个向量的长度不就是两个物体之间的距离吗?
如果我们只加一行代码,然后在标准化之前存储向量的大小,我们就可以得到距离。
// 从一个物体指向另一个物体的向量
var force = PVector.sub(location1, location2);

// 向量的长度(大小)就是两个物体间的距离。
var distance = force.mag();

// 用引力的公式来计算力的大小。
var strength = (G * mass1 * mass2) / (distance * distance);

// 标准化表示力的向量,并将其缩放到合适的大小。
force.normalize();
force.mult(strength);
注意我还将 PVector “dir” 重命名为 “force”。毕竟,当我们完成了所有计算,最开始定义的 PVector 就是我们想要得到的表示力的向量。
既然我们已经想出了计算引力(模拟重力)的数学公式和代码,我们需要关注一下如何在实际的 ProcessingJS 程序中,应用这种技术。在本节的前面,我们创建了一个简单的 Mover 对象(object)—这个对象具有 PVector的位置(location),速度(velocity),加速度(acceleration)和 applyForce()函数。让我们把这个类(class)放到程序中:
  • 一个单独的 Mover 对象。
  • 一个单独的 Attractor 对象(具有固定位置的新对象类型)。
如图所示,Mover 对象会受到 Attractor 对象的吸引力。
我们先建一个简单的 Attractor 对象—给它一个位置和一个质量,以及一个显示自身的方法(将质量与大小捆绑在一起)。
var Attractor = function() {
    this.position = new PVector(width/2, height/2);
    this.mass = 20;
    this.G = 1;
    this.dragOffset = new PVector(0, 0);
    this.dragging = false;
    this.rollover = false;
};

// 显示方法
Attractor.prototype.display = function() {
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    fill(175, 175, 175, 200);
    ellipse(this.position.x, this.position.y, this.mass*2, this.mass*2);
};
定义了上面的对象,我们可以创建 Attractor 对象类型的一个实例。
var mover = new Mover();
var attractor = new Attractor();

draw = function() {
    background(50, 50, 50);

    attractor.display();
    mover.update();
    mover.display();
};
这是一个很好的结构:一个带有 MoverAttractor 对象的主程序。最后一个难题是如何让一个物体吸引另一个物体。我们如何让这两个物体对话呢?
在架构上,有很多方法可以做到这一点。下面列出了一些方法。
任务函数
1. 一个既接收 Attractor又接收 Mover 的函数:attraction(a, m);
2. Attractor 对象的一个接收 Mover的函数:a.attract(m);
3. Mover 对象的一个接收 Attractor 的函数:mover.attractedTo(a);
4. Attractor 对象的既接收一个 Mover 又返回一个 PVector(也就是吸引力)的函数。吸引力接下来被传递到 MoverapplyForce() 函数。var f = a.calculateAttraction(m); mover.applyForce(f);
我们可以先了解如何让对象之间对话的一系列选项,你也可能会对上面的每种方法有不同的看法。我们先丢弃第一个,因为一个面向对象的方法比起一个与 MoverAttractor 对象都无关的任意函数,是一个更好的选择。无论你选择选项 2 还是选项 3,区别就在是“ Attractor 吸引 Mover ”还是“ Mover 被 Attractor 所吸引”。选项 4 似乎是最合适的,至少就我们在这门课学到的知识而言。毕竟,我们花了很多时间来研究 applyForce() 这个方法,我认为如果我们继续使用相同的方法,我们能更好地理解例子。
换句话说,我们已经有了:
var f = new PVector(0.1, 0); // 新创造出的力
mover.applyForce(f);
我们现在将有:
var f = a.calculateAttraction(m); // 两个物体之间的吸引力
mover.applyForce(f);
所以我们的 draw() 函数可以写成:
draw = function() {
    background(50, 50, 50);

    // 计算并应用吸引力
    var f = a.calculateAttraction(m);
    mover.applyForce(f);

    attractor.display();
    mover.update();
    mover.display();
};
我们就快成功了。既然我们决定将 calculateAttraction() 方法放入 Attractor 对象类型,我们需要把这个函数写出来。这个函数需要接收一个 Mover 对象并返回一个 PVector。函数应该里面有什么?所有我们想出来的关于引力的数学计算!
Attractor.prototype.calculateAttraction = function(mover) {

    // 力的方向如何?
    var force = PVector.sub(this.position, mover.position);    
    var distance = force.mag();
    force.normalize();

    // 力的大小等于多少?
    var strength = (this.G * this.mass * mover.mass) / (distance * distance);
    force.mult(strength);

    // 返回这个力,才能用到它!
    return force;
};
我们完成了。算是吧。几乎完成了。我们需要解决一个小问题。让我们再看一遍上面的代码。看到除法的符号了吗,那个斜杠?无论何时,只要我们看到除号,我们就需要问自己这样一个问题:如果这个距离碰巧是一个非常非常小的数字,或者(甚至更糟)是零,会发生什么??!我们知道我们不能用一个数字除以0,如果我们用一个数字除以0.0001,就同等于把这个数字乘以10000!是的,这是现实世界的引力公式,但我们不生活在现实世界里。我们生活在 ProcessingJS世界 里。在 ProcessingJS 的世界中,mover 可能会非常非常接近 attractor,并且这个力可能变得非常强以至于 mover 会直接飞离屏幕显示范围。有了这个公式,我们就可以更实际地限制距离的范围了。也许,无论 Mover 实际上在哪里,我们都不应该认为它距离 attractor 小于 5 像素或大于 25 像素。
distance = constrain(distance, 5, 25);
出于同样的原因,我们需要限制最小距离,我们也可以限制最大范围。毕竟,如果 mover 距离 attractor 500 像素(也不是不合理),我们将用力的大小除以250,000。这个力可能会变得很弱,几乎就像我们没有施加它一样。
现在,你可以自己决定你想要什么样的行为。但如果你说,“我想要看起来合理的吸引力,这个力不会离奇地变弱或变强”,那么限制距离是一个很好的技巧。
现在让我们把所有写过的部分放在一个程序中。 Mover 对象类型完全没有改变,但是现在我们的程序包含了一个 Attractor 对象,以及将它们联系在一起的代码。我们还向程序中添加了代码,来用鼠标控制 attractor,以便更容易观察到效果。
当然,就像我们在摩擦力和拉力的例子那样, 我们也可以利用数组来包括更多的Mover对象。 我们对程序的主要更改是调整 Mover 对象以接受质量, x,y (像我们过去那样), 随机初始化的Mover对象数组,并在数组上方循环以计算每个对象的引力:
var movers = [];
var attractor = new Attractor();

for (var i = 0; i < 10; i++) {
    movers[i] = new Mover(random(0.1, 2), random(width), random(height));
}

draw = function() {
    background(50, 50, 50);

    attractor.display();
    for (var i = 0; i < movers.length; i++) {
        var force = attractor.calculateAttraction(movers[i]);
        movers[i].applyForce(force);

        movers[i].update();
        movers[i].display();
    }
};

“自然模拟”系列课程是由 Daniel Shiffman 的 "编程的本质" 衍生而来,基于 知识共享 著名-非商用性 3.0 本地化许可协议

想加入讨论吗?

尚无帖子。
你会英语吗?单击此处查看更多可汗学院英文版的讨论.