2.1 二维向量绘图
二维世界如同一页纸或者计算机屏幕一样扁平。在数学语言中,这种扁平的二维空间称为平面。二维平面中的物体有长度和宽度两个维度,但没有第三个维度——高度。同样,可以用两个信息来描述二维世界中的具体位置:垂直位置和水平位置。为了描述点在平面中的位置,你需要一个参考点。这个特殊的点称为原点。图2-1展示了这种关系。
图2-1 参照原点定位一个具体的点
虽然有很多点可供选择,但必须确定其中一个作为原点。为便于区分,我们选择×点作为原点(如图2-1所示)。可以从原点开始画一个箭头(像图2-1中的那条实线)表示它和另一个点的相对位置。
二维向量(two-dimensional vector)是平面上相对于原点的一个点。换言之,可以把一个向量想象成平面上的一个直线箭头,从原点指向一个具体的点(见图2-2)。
图2-2 箭头指向相对于原点的一个具体的点
从本章开始,我们将用箭头和点来表示向量,因为可以用它们画出更多有意思的图。如果将图2-2中的点连接起来,会得到一只恐龙,如图2-3所示。
图2-3 连接平面中的点来绘图
计算机中呈现的二维或三维图形,无论是这只简单的恐龙还是皮克斯动画长片,都通过连接一些点或者向量来显示期望的形状。要想创建图形,就需要把向量放在正确的位置上,因而需要精准地测量。接下来看看如何测量平面中的向量。
2.1.1 如何表示二维向量
可以使用尺子测量一维世界中的对象,比如一个物体的长度。要测量二维对象,我们需要两把尺子。这些尺子就是坐标轴,它们彼此垂直,相交于原点。借助坐标轴来绘图,恐龙就有了上下左右之分。如图2-4所示,水平的轴称为轴,垂直的轴称为轴。
图2-4 基于轴和轴绘制的恐龙
借助坐标轴确定方向,我们可以说:“有4个点在原点的右上方。”但这还不够,还需要进一步量化数据。在尺子上,有刻度来展示测量到多少单位。与之类似,在二维平面中,可以添加垂直于轴线的网格线,以展示点相对于轴线的位置。按惯例,我们把原点放在轴和轴的刻度0处(见图2-5)。
图2-5 网格线让我们可以测量点与坐标轴的相对位置
通过网格线,可以精准测量平面中的向量。举个例子,在图2-5中,恐龙的尾巴尖对应轴上的+6、轴上的+4。目前你可以认为这些数值代表厘米、英寸、像素或者其他任何长度单位。除非有特定的场景,一般情况下我们不使用单位。
数6和4分别是点的坐标和坐标,帮助我们定位具体的点。一般将二维坐标写成坐标和坐标的有序数对(或者叫元组),比如(6, 4)。图2-6展示了如何通过3种方式来描述同一个向量。
图2-6 描述同一个向量的3种方式
通过形如(-3, 4.5)的坐标,可以在平面中定位表示它们的点或者箭头。到达该坐标在平面上对应点的路径是:从原点开始,向左移动3个网格(因为坐标是-3)的距离,然后向上移动4个半网格(坐标是4.5)的距离。这个点并不在两条网格线的交点上,但这无关紧要,因为任何实数组成的坐标都能确定平面中的一个点。相应地,箭头是从原点到该位置的直线路径,指向左上方(或者说西北方)。尝试自己练习一下这个例子吧!
2.1.2 用Python绘制二维图形
当你在屏幕上绘制图形时,是在二维平面上工作的。屏幕上的像素就是二维平面上的可用点,其坐标用整数(而非实数)表示,而且像素相互独立。尽管如此,大多数图形库允许使用浮点数作为坐标值,并会将图形上的点自动转换为屏幕上的像素。
要在屏幕上绘制图形,我们有大量的语言和库可供选择:OpenGL、CSS、SVG,等等。具体到Python中,有Pillow和Turtle这样的库,非常适合用向量来进行绘图。本章会使用一些自定义函数来辅助绘图,这些函数是基于一个名为Matplotlib的Python库之上构建的。这些工具函数可以让我们专注于绘图本身。在掌握了这些工具函数后,就可以推此及彼,很容易地学会任何其他相关的Python库。
这里最重要的函数是draw
,它的输入是几何图形类的实例,以及其他影响绘制效果的关键字参数。表2-1列出了用于绘制各种几何对象的Python类。
表2-1 一些表示几何图形的Python类,可用于draw
函数
你可以在源代码文件vector_drawing.py里找到这些函数。本章最后会对它们的具体实现进行说明。
注意 从本章开始,源代码目录下有对应的notebook来记录如何顺次运行所有代码,其中包括如何从
vector_drawing
模块导入所需的方法。如果你对此并不了解,可以参考附录A来配置Python和Jupyter。
使用这些绘图函数,可以把图2-5中表示恐龙轮廓的点画出来。
from vector_drawing import *
dino_vectors = [(6,4), (3,1), (1,2), (-1,5), (-2,5), (-3,4), (-4,4),
# 这里插入剩余的16个向量
]
draw(
Points(*dino_vectors)
)
这里没有给出完整的dino_vectors
列表,但只要提供完整的向量集合,这段代码就可以绘制出如图2-7所示的点(也与图2-5相符)。
图2-7 用Python中的draw
函数绘制恐龙的轮廓点
下一步,可以把点连接起来。先来尝试连接恐龙尾巴上的点(6, 4)和点(3, 1),通过如下方式调用draw
函数来实现图2-8中的效果。
draw(
Points(*dino_vectors),
Segment((6,4),(3,1))
)
图2-8 将恐龙轮廓点中的(6, 4)和(3, 1)连接而成的线段
这条线段实际上是由点(6, 4)和点(3, 1)以及位于它们之间的所有点组成的集合。draw
函数会将线段上的所有像素设置为蓝色。Segment
类提供的抽象能力很实用,使用它再绘制20条线段,就得到了恐龙的完整轮廓(见图2-9)。
图2-9 通过21次函数调用,得到21条线段,就形成了恐龙的轮廓
只要提供所有向量,就可以勾勒出想要绘制的二维图形。手动计算所有的坐标是很乏味的,所以接下来研究如何通过向量运算来自动计算坐标值。
2.1.3 练习
练习2.1:恐龙脚趾尖上的点的坐标和坐标是什么?
解:(-1, -4)
练习2.2:在平面上画出点(2, -2)和与之对应的箭头。
解:用平面上的点和箭头表示(2, -2)如图2-10所示。
图2-10 表示(2, -2)的点和箭头
练习2.3:通过观察恐龙各点的位置,推断
dino_vectors
列表未包含的其余向量。例如,列表已经包含了恐龙尾巴尖上的点(6, 4),但不包含恐龙鼻子上的点(-5, 3)。完成后,dino_vectors
列表中应该有由21个坐标对表示的向量。解:恐龙轮廓的完整向量列表如下。
dino_vectors = [(6,4), (3,1), (1,2), (-1,5), (-2,5), (-3,4), (-4,4), (-5,3), (-5,2), (-2,2), (-5,1), (-4,0), (-2,1), (-1,0), (0,-3), (-1,-4), (1,-4), (2,-3), (1,-2), (3,-1), (5,1) ]
练习2.4:构建一个以
dino_vectors
为顶点的Polygon
对象,画出将每个点相连的恐龙图像(见图2-11)。解:
draw( Points(*dino_vectors), Polygon(*dino_vectors) )
图2-11 绘制呈多边形的恐龙轮廓
练习2.5:当坐标在-10到10的范围内时,使用
draw
函数绘制表示向量(x, x**2)
的点。解:当坐标为-10到10的整数时,为函数绘制出的图表如图2-12所示。
图2-12 上的点
为了绘制这个图表,我给
draw
函数传递了两个额外的关键字参数。grid
参数为(1, 10),表示每隔1个单位绘制垂直网格线,以及每隔10个单位绘制水平网格线。nice_aspect_ratio
参数设置为False
,表示轴和轴的比例不必相同。draw( Points(*[(x,x**2) for x in range(-10,11)]), grid=(1,10), nice_aspect_ratio=False )