主要内容
向量简介
此课程是关于观察我们周边的事物并找出用代码巧妙地模拟这个世界的方法。我们先从基本的物理开始 — 苹果为何从树上掉下来,钟摆如何在空中摇晃,地球如何围绕着太阳,等等。我们在此介绍的一切都需要用到编写动作最基本的构件 — 向量。于是我们就从这儿开始讲。
我们在这里想要的是欧氏向量(以希腊数学家欧几里得的名字命名的,也称为几何向量)。当你在此课程中看到 “向量” 这个词的时候,你可以假定它指的就是欧氏向量,定义为 既有大小又有方向的实体。
向量通常会被画成箭头;箭头所指的是它的方向,而箭头本身的长度表明它的大小。
在以上图像中,向量被画为从点 A 至点 B 的箭头,可以当作如何从 A 移动到 B 的说明。
为何用向量?
在我们深入讨论向量的细节之前,让我们来看一个基础的程序,以展示为什么要在意向量。如果你已经看过了我们可汗学院的 JS 入门课程,你可能在之前已经了解了如何编写简单的弹球程序。
在以上的例子中,我们有一个很简单的世界 — 一个空白的画布上有一个原型(一个 ”球“)到处跑。这个球有一些属性,在代码中表达为变量。
位置 | 速度 |
---|---|
x 和 y | xSpeed 和 ySpeed |
在更复杂的程序中,我们可以想象有更多变量:
加速度 | 目标位置 | 风 | 摩擦 |
---|---|---|---|
xacceleration 和 yacceleration | xtarget 和 ytarget | xwind 和 ywind | xfriction 和 yfriction |
现在可以更清楚地看到这个世界里的每个概念(风、位置、加速度等等)都需要两个变量。而这只是个二维的空间。在一个三维的空间里,我们需要
x
、y
、z
、xSpeed
、ySpeed
、zSpeed
,以此类推。如果我们可以简化代码而用更少的变量岂不是更好?
与其:
var x = 5;
var y = 10;
var xSpeed;
var ySpeed;
我们不如使用两个变量,每个变量是一个类似向量的对象,具有二维信息:
var position;
var speed;
跨出使用向量的第一步并不能让我们做出任何新的东西。仅仅用类似于向量的对象作为变量不会魔术般的让你的程序模拟真实物理。但是,它们会简化你的代码并提供一套函数,帮助完成在编写动作时会反复出现的运算。
作为向量的简单介绍,我们只会使用二维空间。所有这些例子都可以比较容易地被扩展到三维(而我们用的对象 —
PVector
— 也允许三维)。但是,从二维开始更容易。用 PVector
编程
理解向量的一种方法是将其视为两点之间的差距。想象一下你在指路时会如何告诉别人从一点走到另一点。
以下是一些变量加上可能的注释:
| (-15, 3) | 朝西走十五步;转身并朝北走三步。 |
| (3, 4) | 朝东走三步;转身并朝北走四步。 |
| (2, -1) | 朝东走两步;转身并朝南走一步。 |
你可能在编写动态物体时已经做过类似的操作。在每一个动画帧中(也就是说 ProcessingJS 的 draw() 循环中的一次循环),你使屏幕上的每一个对象在水平和竖直方向上移动一定距离来产生动画效果。
对于每帧:
新的位置 = 当前的位置加上速度
如果速度是个向量(两个点之间的差距),那么位置是什么?它也是向量吗?严格来说,某人可以争辩说位置不是个向量,因为它不是描述如何从一点移动到另一点的 — 它仅仅描述空间中的一个具体的点。
尽管如此,另一种描述位置的方法是从原点到这个位置的路径。这也就是说一个位置可以表达为一个代表某位置与原点之间的距离的向量。
让我们验证一下例子中数据所代表的位置和速度。在弹球的例子中,如下:
位置 | 速度 |
---|---|
x 和 y | xSpeed 和 ySpeed |
请注意我们是如何为两者存储同样的数据 — 两个浮点数,一个
x
和一个 y
。如果我们要自己编写一个向量类,我们会从相当简单的东西开始:var Vector = function(x, y) {
this.x = x;
this.y = y;
};
基本来说,一个
PVector
就是同时存储两个值(或者三个,就像我们在三维的例子中将看到)的一种方便的办法。而这 …
var x = 100;
var y = 100;
var xSpeed = 1;
var ySpeed = 3.3;
就变成 …
var position = new PVector(100,100);
var velocity = new PVector(1,3.3);
既然我们现在已有两个向量对象(位置与速度),我们就可以执行动作的运算法 —位置 = 位置 + 速度。在例子 1.1 中,没有向量的运算方式如下:
x = x + xSpeed;
y = y + ySpeed;
理所应当地, 我们应该可以把将以上语句重写为:
position = position + velocity;
然而,在 Javascript 中,加号 + 是预留给基本数据类型(数值、字符串)专用的。在某些编程语言中,运算符可以被 ”重载“,但 Javascript 中不行。幸运的是,
PVector
对象提供很多常见数学运算的方法,例如 add()
(加)。向量加法
在我们继续学习
PVector
对象与它的 add()
方法之前,让我们用数学和物理课本中的标记法看一下向量加法。向量通常会写为黑体字或者上面有箭头。对于本课程来说,为了更好地分辨 向量 与 标量 (标量 指的是单一数值,例如整数或浮点数),我们会用箭头标注:
- 向量:u, with, vector, on top
- 标量:x
假如有以下这两个向量:
每个向量都有两个分量,一个
x
和一个 y
。要将两个向量相加,我们只要将两个 x
和两个 y
相加。换句话说,
可以被写为:
然后,把
u
和 v
换成图 1.6 中的数值,得到:也就是说:
最后,我们把它写为向量:
既然我们理解了如何将两个向量相加,我们可以看看加法在
PVector
对象中是怎样执行的。让我们写一个叫 add()
的方法,接受另外一个 PVector
对象为参数,只是简单地将 x 与 y 分量相加。var Vector = function(x, y) {
this.x = x;
this.y = y;
};
Vector.prototype.add = function(v) {
this.y = this.y + v.y;
this.x = this.x + v.x;
};
现在我们看到了
add()
在 PVector
中是如何写的,我们可以回到弹球的例子,为它的 位置 + 速度 的运算法实现向量相加:position.add(velocity);
而我们现在就可以用
PVector
对象重写弹球的例子了!请看一看代码并注意与之前的差别。我们应该注意到以上用向量编程的过渡过程中的一个重要方面。虽然我们在用
PVector
对象来描述两个值 — 位置的 x 和 y 以及速度的 x 和 y — 我们经常还要单独参考每个 PVector
的 x 和 y 分量。当我们在 ProcessingJS 中绘制对象时,不能这样写:ellipse(position, 16, 16);
ellipse()
函数不接受 PVector
为参数。一个椭圆形只能用两个标量值绘制,一个 x 座标和一个 y 座标。因此我们要用面向对象的点号记法在这个 PVector
对象中找出 x 和 y 分量:ellipse(position.x, position.y, 16, 16);
测试圆形是否达到窗口的边缘时会遇到同一个问题,而我们必须获取两个向量,
position
和 velocity
,的独立分量。if ((position.x > width) || (position.x < 0)) {
velocity.x = velocity.x * -1;
}
现在,你可能会有点儿失望。毕竟,这次改成用向量一开始看起来好像让代码变得更复杂。虽然这是个完全合理且正确的评论,但我们要知道现在我们还没有完全利用到向量编程的精髓。看一个简单的弹球并只执行向量加法只是第一步。
当我们进一步接触到有多个对象以及多个力(我们很快就会介绍到)的更复杂的世界中,
PVector
的好处会变得更明显。加油!“自然模拟”系列课程是由 Daniel Shiffman 的 "编程的本质" 衍生而来,基于 知识共享 著名-非商用性 3.0 本地化许可协议。