本章项目是使用参数方程实现的,这些方程将曲线上的点的坐标表示为一个或多个变量的函数,而这些变量被称为参数。把参数值代入方程,以计算构成繁花曲线的点的坐标,再将这些坐标交给模块turtle以绘制相应的曲线。
为帮助读者理解参数方程的工作原理,先来看一个简单的示例:表示圆的方程。假设一个圆的半径为 r ,其圆心位于二维平面坐标系的原点,那么这个圆将由所有这样的点组成:其坐标 x 和 y 满足方程 x 2 + y 2 = r 2 。然而,这个方程并非参数方程,因为参数方程是基于其他变量(参数)的变化获得所有可能的 x 和 y 值。
来看下面的方程:
x = r cos ( θ )
y = r sin ( θ )
这两个方程就是前述圆的参数化表示,其中的参数 θ 为从原点到点( x , y )的线段与 x 轴正方向的夹角。这些方程计算得到的( x , y )都满足前面的方程 x 2 + y 2 = r 2 。将 θ 从0逐渐变为2π时,这些方程生成的坐标 x 和 y 对应的点便构成了圆,如图2.2所示。
别忘了,这两个方程只适用于以坐标系原点为圆心的圆。通过将圆心从点(0, 0)平移到点( a , b ),可将圆放到平面的任何地方,因此更通用的参数方程为 x = a + r cos ( θ )和 y = b + r sin ( θ )。
万花尺轨迹的参数方程和圆的参数方程差别不大,因为从本质上说,绘制万花尺轨迹不过是绘制两个相切的圆。图2.3展示了万花尺轨迹的数学模型。这个模型中没有齿轮,因为在万花尺玩具中,齿轮的唯一用途是防止滑动,而在数学建模这样的理想世界中,根本不用担心滑动的问题。
图2.2 使用参数方程定义一个圆
图2.3 万花尺轨迹的数学模型
在图2.3中, C 为小圆的圆心, P 为笔尖,而 θ 为从原点到 C 的线段与 x 轴正方向的夹角。大圆的半径为 R ,小圆的半径为 r ,用变量 k 表示这两个半径的比值,如下所示:
线段 PC 的长度 为笔尖与小圆圆心的距离。用变量 l 表示 与小圆半径的比值,如下所示:
现在可以使用这些变量来创建两个参数方程,用于表示在大圆内侧滚动小圆时,得到的点 P (笔尖)的运动轨迹,如下所示:
注意
这些曲线被称为内摆线。虽然这些方程看起来有点复杂,但推导过程非常简单。如果读者想研究相关的数学知识,可参阅与万花尺(spirograph)相关的文献。
图2.4显示了使用上述参数方程绘制的一条曲线。为绘制这条曲线,将 R 、 r 和 l 分别设置成了220、65和0.8。通过将这3个参数设置为不同的值,再逐渐增大夹角 θ ,可生成无数条神奇的繁花曲线。
图2.4 一条示例曲线
至此,余下的唯一任务是确定在什么情况下停止绘画,因为要绘制完整的繁花曲线,可能需要让小圆沿大圆内侧转很多圈。要确定何时停止,需计算万花尺轨迹的周期性(即小圆沿大圆内侧绕多少圈后绘制的曲线将是重复的),可先查看内圆半径和外圆半径的比值:
再对这个分数进行约分,方法是将分子和分母都除以它们的最大公约数(Greastest Common Divisor,GCD)。这样得到的分子就能指出需要沿大圆内侧绕多少圈才能绘制出完整的曲线。例如,在图2.4中,代入 r 和 R 的值可得:
这表明小圆沿大圆内侧绕13圈后,绘制的曲线将重复,即确定了整条繁花曲线的形状;分母44为小圆绕其圆心旋转的圈数。如果数一数图2.4所示图形中的花瓣数量,将发现正好是44个。
将表示半径比值的分数约分后,便可计算在绘制繁花曲线的过程中参数 θ 的取值范围[0, 2π r ],这样就能知道在何时该停止绘制。在图2.4中,将在 θ 为2π×13=26π时停止绘制。如果不知道 θ 为多少后停止绘制,将一直循环绘制,导致不必要的重复。
Python模块turtle没有提供绘制曲线的方法,因此这里将通过在不同的点之间绘制线段来生成繁花曲线,而这些点是使用前面讨论的参数方程计算得到的。只要在从一个点移到另一个点时,角度 θ 的变化足够小,最终绘制出的图形看起来就像曲线。
为证明这一点,下面的程序使用模块turtle绘制了一个圆。这个程序使用了表示圆的基本参数方程 x = a + r cos ( θ )和 y = b + r sin ( θ )来确定圆上的点,再使用线段将相邻的点连接起来。严格地说,这个程序实际上绘制的是一个 N 边形,但由于角度参数 θ 的递增量很小,因此 N 将非常大,使这个多边形看起来像圆。请输入下面的代码,将其保存为文件drawcircle.py,再在Python中运行。
import math
import turtle
# 使用模块turtle绘制圆
def drawCircleTurtle(x, y, r):
# 移到起点
turtle.up()
❶ turtle.setpos(x + r, y)
turtle.down()
# 绘制圆
❷ for i in range(0, 365, 5):
❸ a = math.radians(i)
❹ turtle.setpos(x + r*math.cos(a), y + r*math.sin(a))
❺ drawCircleTurtle(100, 100, 50)
turtle.mainloop()
这里定义了函数drawCircleTurtle(),其参数为要绘制的圆的圆心坐标(x, y)及其半径r。这个函数首先将海龟移到圆上的第一个点,即与圆的水平轴相交的右边那个点:(x+r, y) ❶。函数调用up()是为了避免在移到起点的过程中进行绘画。然后使用range (0, 365, 5)开启了一个循环,它以步长5将变量i从0递增到360❷。变量i为角度参数,将被传递给圆的参数方程,但这样做之前,先将其单位从度转换成了弧度❸。大多数计算机程序都要求以弧度为单位来执行基于角度的计算。
接下来,使用两个参数方程计算下一个点的坐标,并相应地设置海龟的位置❹。这将绘制一条线段,其起点为海龟的上一个位置,终点为计算得到的新位置。
定义好绘制圆的函数后,调用它来实际绘制圆❺。由于每次只将角度参数增加了5°,因此通过绘制线段得到的图形看起来像圆。调用turtle.mainloop(),可避免tkinter窗口被关闭,这样能够展示绘制结果。
现在可以开始绘制繁花曲线了,为此将使用前面演示的海龟绘图法,唯一不同的地方是用来计算坐标的参数方程。