6.程序化的形状(Procedural Shapes) #Programming Design System
到目前为止,我们自定义形状的代码还是非常繁琐。我们通过逐行输入顶点函数来手动创建形状,用这种方法碰到更加复杂的形状就会爆炸,根据我们的书名,这种方法不太适合。现在我们知道了beginShape()
的用法,接下来我们要用for
循环和sin()
和cos()
函数以程序化的方式绘制自定义形状。
正弦和余弦
多年来,我已经看到了许多同学面对sin
和cos
函数苦苦挣扎。原因也很容易理解:这些词看起来相当吓人和抽象,尤其是当你自认为不擅长数学的时候。这既不幸,又没必要。不幸的是,这两个函数是大多是程序设计的基本组成部分,并且对它们充分理解能够解决你许多视觉上的问题。没有必要是因为它们并不难学。即使你对本章内容一点也不了解,也可以通过记住两行相同的代码开始。
正弦和余弦能让我们在圆周上找到任意的位置。它们通过将角度转换为单位圆的x
坐标和y
坐标来实现。然后再把这些值乘以实际圆得半径来按比例放大。三角函数按照咱们高中学的知识sin
就是对边比邻边,cos
就是斜边比邻边。
在P5中,这些函数称为sin()
和cos()
。它们接受一个以弧度制表示的参数,并返回一个在-1到1 之间的值。下面的两行代码演示了如何获取这些值并将它们乘以实际圆的半径。记住这两行代码,它们非常重要。
1 | var x = cos(RADIANS) * RADIUS; |
为了说明这点,请看下面的示例。我们用相同的代码在大圆圆周的330°的位置绘制小圆。
1 | translate(width/2, height/2); |
sin()
和cos()
函数为那些围绕着一个中心点并且没有重复轮廓的图形(正多边形)提供了一种绘制的思路。
for循环
尽管整本书都在使用for
循环,但还是要在这里介绍一下for
循环的基本功能。for
循环允许我们通过递增或者递减的方式改变一个通常为i
变量,知道变量不满足表达式,循环才会停止。在下面的例子中,我们初始化i
的值为0
,只要我们变量的值小于10
,就会进行迭代,并且每次迭代之后变量将递增1
。最终这个循环会执行十次,变量会从0
递增到9
,在画布上绘制十个矩形。
1 | for(var i = 0; i < 10; i++) { |
不幸的是,所有这些矩形的位置和大小相同,rect()
一次又一次的接受同样的参数,所有矩形都重叠在一起。i
在这个时候就能发挥用处了,每次循环的时候i
都会发生变化,通过i
就能使矩形产生变化。下面的示例用i
将十个矩形放置在x
轴上,每个矩形相聚一个像素。
尽管你现在可能还不能马上理解,但这在绘制图形时是一项重要的技能。因为i
在每次迭代的时候都会递增,所以可以用作画布上图形分布的标量。例如,如果我们想让矩形彼此相邻放置,则可以把大于矩形宽度的值乘以i
。
1 | for(var i = 0; i < 10; i++) { |
我们可以用相同的方法绘制自定义形状。我们在循环在beginShape()
和endShape()
函数之间添加顶点。在下面的示例中,我们用这个方法在画布中间添加十个随机顶点。
1 | translate(width/2, height/2); |
结合上面说的方法
我们把上面随机放置的顶点改为沿着圆周按顺序的顶点。这个时候我们之前记住的两行代码就派上了用处。但我们这次传递给sin()
和cos()
的角度是把圆周分为i
份得来的。最终我们得到了一个正十边形。
1 | translate(width/2, height/2); |
通过更矮迭代次数和顶点之间的角度,可以绘制所有基本形状。下面的代码在顶部添加了一些变量,来根据顶点数自动计算间距。更改numVertices
变量,将会出现另一个形状。
1 | var numVertices = 3; // or 4 or 30 |
你可能会说:“太好了,我们发明了新的基本形状函数”。其实我们用这个方法可以绘制更加复杂的形状。我们来看几个例子,它们都用相同的sin()
和cos()
公式来绘制不同类型的形状。我们将从下面的波浪形圆圈开始,每个顶点的半径都是随机的,看起来就像是手工绘制的。
1 | translate(width/2, height/2); |
通过在每个顶点的高低半径交替,可以创建出下面的星形。使用不同的参数,或用rotate()
函数改变星形的方向,我们可以很容易的改变星形的样式。
1 | translate(width/2, height/2); |
这是用 quadraticVertex()
函数创建的花朵, quadraticVertex()
所有的控制点和顶点的位置都是通过sin()
和cos()
生成。使用贝塞尔曲线时,请记住以vertex()
函数调用开始。
1 | //自动计算间距 |
您会发现自己只需要使用一种循环功能。如下创建两个形状:第一个使用sin()
,第二个使用cos()
(如下面的代码所示)。
1 | strokeWeight(20); |
在设计过程中,可以使用正弦和余弦来创建一系列不同的形状。在约瑟夫·穆勒·布罗克曼(JosefMüller-Brockmann)的设计中,一系列指数增长的圆弧围绕着画布的左下角旋转。
约瑟夫·米勒-布罗克曼的贝多芬海报
Sediment Mars是莎拉·哈拉切(Sarah Hallacher)和亚历山德拉·维拉米尔(Alessandra Villaamil)创作的一系列海报。通过sin()
和cos()
函数,生成一个椭圆形,然后通过添加随机值来让他失真。
莎拉·哈拉切(Sarah Hallacher)和亚历山德拉·维拉米尔(Alessandra Villaamil)的Sediment Mars
Generative Play项目是Adria Navarro的卡片游戏,它使用程序绘图来创建各种各样的生成角色。这些角色的身体是使用sin()
和cos()
创建的。
Adria Navarro的生成游戏
本章介绍了一种与传统设计过程有本质不同的设计方法。我们没有一个一个去绘制形状,而是编写算法来替我们完成这些任务。使用循环来绘制图形是一种强大的方式,因为它能让程序员用更少的代码做更多的事情,从而减轻了手工绘制每个对象的痛苦。这同时也是程序设计中最难的部分,因为设计人员需要花更多的时间将系统提炼成代码,而且他们不能像传统设计工具那样轻易的操纵单个形状。美国计算机科学家唐纳德·努斯(Donald Knuth)称这是从设计到元设计的过渡:
“元设计(Meta-design)比设计困难得多;解释如何画比画东西难得多。[…]然而,一旦我们成功地解释了如何以足够通用的方式绘制事物,那么在不同情况下,相同的解释将适用于所有形状。因此花时间制定描述如何绘制是值得的。”
Donald Knuth (1986), The Metafont Book
这也是本书的主要论点,当设计师学会了系统的思考设计过程,并且用软件实现这些系统时,他们能做出以前无法完成的工作。
作业
尝试使用本章介绍的技术绘制所有基本形状。然后继续生成其他类型的形状。你可以random()
用来操纵形状轮廓吗?可以使用贝塞尔曲线代替简单顶点吗?
你可以把作业发在下面评论区中。
本文译自Programming Design Systems
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。