主要内容
牛顿运动定律
在最后一节的最后一个示例中,我们看到了如何基于从屏幕上的圆指向鼠标位置的矢量来计算动态加速度。由此产生的运动类似于圆和鼠标之间的磁力吸引,好像有一定的力量把圆圈拉向鼠标。
在本节中,我们将正式确定我们对力的概念及其与加速度的关系的理解。到最后,我们的目标是了解如何让多个物体在屏幕上移动,并对各种环境力量做出反应。
在开始研究模拟代码中的力的实际现实之前,让我们从概念上看看在现实世界中作为一种力意味着什么。就像 "矢量" 这个词一样, "力" 经常被用来指各种各样的东西。这可以表明一种强大的强度, 比如在”她用巨大的力量推巨石“ 或 "他言辞有力" 中。我们所关心的 (力)force 的定义要正式得多, 来自艾萨克·牛顿的运动定律:
力是导致有质量的物体加速的矢量。
这里的好消息是, 我们认识到定义的第一部分 : 力是矢量 。谢天谢地, 我们刚刚花了整整一节课来学习什么是矢量, 以及如何用
PVector
来编程!让我们来看看牛顿的运动三定律,这与力的概念有关。
牛顿第一定律
牛顿的第一定律通常被描述为:
静止物体停留在静止状态,运动中的物体保持运动。
然而, 这缺少了与力有关的一个重要因素。我们可以扩写它:
静止物体静止不动,运动中的物体保持恒定的速度和方向,除非受到不平衡的力的作用。
当牛顿出现的时候,盛行的运动理论——亚里士多德提出的——已经有将近两千年的历史了。它指出,如果一个物体在移动,需要某种力量来保持它的移动。除非那个移动的东西正在被推或拉,否则它只会减速或停止。对吗?
当然, 这不是对的。在没有任何力量的情况下, 不需要任何力量来保持物体的移动。由于空气阻力 (力),在地球大气层中抛掷的物体 (如球) 在运动中会减速。只有在没有任何力的情况下,或者如果作用在物体上的力相互抵消,即净力加起来为零,物体的速度才会保持不变。这通常被称为 平衡状态。一旦空气阻力的力等于重力, 落球将达到一个终端速度 (保持不变)。
在 ProcessingJS 世界中,我们可以重述牛顿的第一定律, 如下所示:
如果物体处于平衡状态,其
PVector
速度将保持不变。暂时跳过牛顿的第二定律 (可以说是我们的目的最重要的定律),让我们先讨论第三定律。
牛顿第三定律
这项定律通常规定如下:
每一个作用力,都有一个等大和相反的反作用力。
这项法则的表述方式经常造成一些混乱。首先, 听起来一种力量会引起另一种力量。是的,如果你推某人,有人可能会主动决定把你推回去。但这不是我们用牛顿第三定律谈论的行动和反应。
假设你靠在墙上。这堵墙并没有主动决定向你倒在一起。没有 "起始" 的力量。你的推动只是包括两种力量,被称为 "作用力/反作用力"。
陈述这一定律的一个更好的方式可能是:
力总是成对发生。这两种力量的强度相等,但方向相反。
现在,这仍然会造成混乱,因为听起来像这些力量总是会相互抵消。事实并非如此。记住,力作用于不同的物体。而仅仅因为这两种力是相等的,并不意味着运动是相等的 (也不意味着物体会停止运动)。
试着推一辆固定的卡车。虽然卡车比你强大得多,但与移动的卡车不同,固定卡车永远不会压倒你,让你向后飞。你对它施加的力量与你的手被卡车施加的力是相等的,也是相反的。结果取决于其他各种因素。如果是结冰的下坡上的一辆小卡车,你可能就能让它移动。另一方面,如果是土路上的一辆非常大的卡车,你用力推它 (甚至可能是跑着推),你的手可能会受伤。
如果你穿着溜冰鞋推一辆卡车呢?
让我们为ProcessingJS重申牛顿第三定律:
如果我们计算对象 a 在对象 b 上的
PVector
力,我们还必须对对象 a 施加力--PVector.mult(f,-1);
——b也对a 施加力。我们将看到,在 ProcessingJS编程的世界里,我们并不总是要忠于上面的内容。有时,例如在物体之间的引力情况下,我们会想要模拟相等和相反的力。其他时候,比如我们只是说,“嘿,环境中有一些风”,我们不会费心模拟一个物体在空气中施加的力量。事实上,我们根本不是在模拟空气!记住,我们只是从自然界的物理学中汲取灵感,而不是以完美的精度模拟一切。
牛顿第二定律
现在我们看看对 ProcessingJS 程序员最重要的定律。
这项定律一般规定如下:
力等于质量乘以加速度。
或:
为什么这对我们来说是最重要的定律?好吧, 让我们用另一种方式来写。
加速度与力成正比,与质量成反比。这意味着,如果你被推,你被推得越用力,你就会移动得越快 (加速)。你越大,移动的速度就越慢。
重量与质量
物体的 质量 是对物体中物质量 (以公斤为单位) 的度量。
重量 虽然经常被误认为质量, 但从技术上讲,它是物体上的重力。从牛顿的第二定律中,我们可以计算为质量乘以重力加速度 (
w = m * g
)。重量是以牛顿为单位来衡量的。密度定义为单位体积的质量的量 (例如,克/立方厘米)。
请注意,地球上质量为一公斤的物体在月球上的质量仍是一公斤。不过, 它的重量只有六分之一。
现在,在ProcessingJS的世界里,什么是质量呢?我们不是在处理像素吗?要从一个更简单的地方开始,让我们说,在我们假装像素世界中,我们所有的对象都有一个质量等于1.
F/1 = F
。因此:物体的加速度等于力。这是个好消息。毕竟, 我们在 矢量 部分看到,加速度是控制我们的对象在屏幕上的运动的关键。位置由速度调整,速度由加速度调整。加速是这一切开始的地方。现在我们了解到, 力 才是一切真正开始的地方。
让我们用我们学到的东西来建立在我们的
移动者
对象上,它目前有位置、速度和加速度。现在我们的目标是能够向这个对象添加力量, 也许说:mover.applyForce(wind);
或者:
mover.applyForce(gravity);
那里的风和重力都是
PVector
。根据牛顿第二定律,我们可以实现此函数,如下所示:Mover.prototype.applyForce = function(force) {
this.acceleration = force;
};
力的积累
这看起来相当不错。毕竟, 加速度=力 是牛顿第二定律 (无质量) 的直译。不过,这里有一个相当大的问题。让我们回到我们要完成的任务: 在屏幕上创建一个响应风和重力的移动对象。
mover.applyForce(wind);
mover.applyForce(gravity);
mover.update();
mover.display();
好吧,让我们把自己当作电脑。首先,我们用wind(风)来调用
applyForce()
。现在,Mover
的加速度现在被分配为 PVector
风。接下来,我们用gravity(重力)来调用 applyForce()
。现在, Mover
的加速度被设置为重力的PVector
。第三步,我们调用 update()
。在 update()
中会发生什么?加速度被添加到速度中。velocity.add(acceleration);
我们的程序中不会有任何错误, 但仔细看看!我们有一个大问题。当加速度被添加到速度时, 它的值是多少?它等于重力。风被排除在外了!如果我们多次调用
applyForce()
,它将覆盖以前的每个调用。我们将如何处理不止一种力量?这里事情的真相是,我们刚才是从牛顿第二定律的简化陈述开始的。下面是一种更准确的说法:
净力等于质量乘以加速度。
或者, 加速度等于 所有力的和 除以质量 。这完全说得通。毕竟, 正如我们在牛顿第一定律中看到的, 如果所有的力加起来都是零, 一个物体就会经历一个平衡状态 (即没有加速度)。我们通过一个被称为 力的累计积累的过程来实现这一点。这其实很简单;我们要做的就是把所有的力量加起来。在任何给定的时刻, 可能会有1、2、6、12或303个力。只要我们的对象知道如何积累它们, 有多少力量对其采取行动并不重要。
让我们修改
applyForce()
方法,以便将每个新力添加到加速度中,并将它们积累:Mover.prototype.applyForce = function(force) {
this.acceleration.add(force);
};
现在,我们还没有完成。力的积累还有一块。由于我们在任何给定时刻将所有力相加,因此在调用
update()
之前,我们必须确保清除加速度 (即将其设置为零)。让我们考虑风一会儿。有时风很大,有时很弱,有时根本没有风。在任何给定的时刻,可能会有巨大的大风,比如,当用户按住鼠标时:if (mouseIsPressed) {
var wind = new PVector(0.5, 0);
mover.applyForce(wind);
}
当用户释放鼠标时,风就会停止,根据牛顿的第一定律,物体将继续以恒定的速度移动。然而,如果我们忘记将加速重置为零,风仍将有效。更糟糕的是, 它将从以前的框架中增加自身的量,因为我们正在积累力量!
在我们的模拟中,加速度没有内存;它只是根据时刻存在的环境力量计算的。这与位置不同,例如,位置必须记住对象在上一帧中的位置,才能正确移动到下一帧。
实现清除每个帧的加速度的最简单方法是在 update() 结束时将 PVector 乘以0。
Mover.prototype.update = function() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.acceleration.mult(0);
};
处理质量
在将力整合到
Mover
对象和看例题之前,我们还需增加一小步。 毕竟,牛顿的第二定律实际是 F, with, vector, on top, equals, M, A, with, vector, on top,而不是 A, with, vector, on top, equals, F, with, vector, on top。 加入对象质量就像添加一个新属性一样简单, 但是因为情况较为复杂,我们需要在这里多花一些时间。度量单位
现在我们正在引入质量,快速记下测量单位是很重要的。在现实世界中,事物是以特定的单位来衡量的。我们说两个物体相距3米,棒球以每小时90英里的速度移动,或者这个保龄球的质量是6公斤。正如我们将在本课程后面看到的,有时我们会希望考虑到现实世界中的单位。但是,在本节中,我们将在很大程度上忽略它们。
我们这里的测量单位是像素 (“这两个圆相距100像素”) 和动画帧 (“这个圆以每个帧2个像素的速率移动”)。在质量的情况下,没有任何单位的测量来使用。我们只用编数字。在本例中,我们任意选取数字10。没有测量单位,你喜欢什么单位都可以随心所欲,比如 “1 酸奶盖” 或 “1 片腌黄瓜。”
为了演示,我们将物体质量与物体大小捆绑在一起--因此,如果物体的质量为 10,我们可能会绘制一个半径为10的圆。这使得在我们的程序中很容易地想象物体的质量,并了解质量的影响。然而,在现实世界中,大小并不明确表示质量。一个小金属球由于密度较高, 质量可能比一个大气球大得多。
质量是标量 (浮点数),而不是矢量,因为它只是一个描述物体中物质量的数字。我们可以只针对事物,并将形状的面积计算为其质量,但更简单的一开始就说: “嘿,这个物体的质量是...... 嗯,我不知道...... 10 怎么样?”
var Mover = function() {
this.mass = 10;
this.position = new PVector(random(width), random(height));
this.velocity = new PVector(0, 0);
this.acceleration = new PVector(0, 0);
};
这不是很好,因为只有当我们有了质量不同的物体,事情才会变得有趣,但它会给我们一个好的开端。质量从哪里来?我们把牛顿第二定律应用于物体。
Mover.prototype.applyForce = function(force) {
force.div(this.mass);
this.acceleration.add(force);
};
同样,尽管我们的代码看起来相当合理,但我们这里有一个相当大的问题。考虑下面的场景,两个
Mover
对象,都被风力吹走。var m1 = new Mover();
var m2 = new Mover();
var wind = new PVector(1, 0);
m1.applyForce(wind);
m2.applyForce(wind);
再次,让我们变成电脑。
m1.applyForce()
接收风力 (1, 0),将其除以质量 (10), 并将其添加到加速度。代码 | wind |
---|---|
var wind = new PVector(1, 0); | (1, 0) |
m1.applyForce(wind) | (0.1, 0) |
OK。移动到对象
m2
。它也接收风力——(1,0)。等。等一下。风力的值是多少?仔细看看, 风力现在实际上是 (0.1, 0)!你还记得这个关于处理对象的小插曲吗?
在 JavaScript 中,包含对象(如
PVector
)的变量实际上在内存中持有指向该对象的指针。当您将对象传递给函数时,您不是在传递一个副本--你传递的是原始指针。因此,如果一个函数对该对象进行了更改 (就像它在这里通过除以质量所做的那样),那么该对象就会永久更改! 这就是为什么这里的事情出了问题。我们不希望
m2
接收由除以m1
质量的力。我们希望它在最初的状态—(1,0) 中接受这种力量。因此,我们必须保全变量,在将其除以质量之前,先复制一份 PVector f
。 幸运的是,
PVector
对象有一种方便的方法来制作副本——get()
。 get()
返回具有相同数据的新 PVector
对象。因此,我们可以将 applyForce()
修改如下:Mover.prototype.applyForce = function(force) {
var f = force.get();
f.div(this.mass);
this.acceleration.add(f);
};
或者,我们可以使用
div()
的静态版本重写方法,使用从上一节中学到的有关静态函数的内容:Mover.prototype.applyForce = function(force) {
var f = PVector.div(force, this.mass);
this.acceleration.add(f);
};
重要的是找到一种方法 不 影响原始力矢量, 以便它可以应用于多个
Mover
对象。制造力
我们知道什么是力 (矢量), 我们知道如何将力施加到物体上 (将其除以质量并将其添加到物体的加速度矢量)。我们缺了什么?好吧, 我们还没有指出当初是如何获得一个力的。力是从哪里来的?
在本节中, 我们将介绍在我们的 ProcessingJS世界中创建力的两种方法:
- 制造一个力! 毕竟, 你是程序员, 你的世界的创造者。你没有理由不能仅仅组成一股力量并应用它。
- 模拟一个力! 是的, 力存在于现实世界中。而物理教科书中往往包含这些力的公式。我们可以采用这些公式, 将它们转换为源代码, 并在 ProcessingJS 中对真实世界的力进行建模。
组成力最简单的方法就是只挑一个数字。让我们从模拟风的想法开始。一个指向右边、相当弱的风力该如何制造呢?假设
Mover
对象 m
,我们的代码将如下所示:var wind = new PVector(0.01, 0);
m.applyForce(wind);
这个结果并不是很有趣,但这是一个很好的起点。我们创建一个
PVector
对象,初始化它,并将其传递到一个对象(这反过来将应用于它自己的加速度)。如果我们想有两种力量,也许是风和重力 (更强一点, 指向下方),我们可以写下:var wind = new PVector(0.01, 0);
var gravity = new PVector(0, 0.1);
m.applyForce(wind);
m.applyForce(gravity);
现在有了两种力,指向不同的方向,大小不同,都适用于物体
m
。我们开始有所进展了。现在已经在 ProcessingJS 中为我们的对象构建了一个世界,一个它们实际上可以响应的环境。下面是我们的程序,当我们把所有的东西都放在一起:
哇!我们已经明白了很多,但现在我们可以做更多。继续下去——学会使用力!
“自然模拟”系列课程是由 Daniel Shiffman 的 "编程的本质" 衍生而来,基于 知识共享 著名-非商用性 3.0 本地化许可协议。