Oh!Coder

Coding Life

有趣的Processing-曲线

| Comments

之前的学习中,我们已经学过Processing中的基本图形。今天我们将学习Processing中的曲线。Processing提供了三种常见的曲线,分别是弧线,样条曲线以及贝塞尔曲线。下面,我们就分为三部分来一起学习一下这三种曲线。

弧线

首先,我们一起来看看弧线。为了方便我们画弧线,Processing为我们提供了一个名为arc()的方法。我们还是结合具体的例子来看看如何使用。

打开PDE编辑器,新建一个项目窗口,如下图所示。

processing-new-window

图片来源:Processing新项目窗口

将如下代码填入其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口大小
  size(300, 200);
  // 设置窗口背景色
  background(255);
  // 将矩形的坐标点设置为中心位置
  rectMode(CENTER);
  // 设置矩形边框颜色
  stroke(128);
  // 依次画出四个矩形
  rect(35, 35, 50, 50);
  rect(105, 35, 50, 50);
  rect(175, 35, 50, 50);
  rect(105, 105, 100, 50);
  // 设置弧线线条颜色
  stroke(0);
  // 依次在上方矩形中画四段弧线
  arc(35, 35, 50, 50, 0, PI / 2.0);
  arc(105, 35, 50, 50, -PI, 0);
  arc(175, 35, 50, 50, -PI / 6, PI / 6);
  arc(105, 105, 100, 50, PI / 2, 3 * PI / 2);
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码
}

完成后,点击项目窗口中,菜单栏里的三角形按钮,如下图所示。

processing-window-menu

图片来源:Processing新项目窗口中的菜单栏

如果一切顺利,运行结果大致如下图所示。

processing-fun-curves-1

图片来源:Processing程序执行结果

这段示例中,出现了一个新方法,就是上面我们提到的那个arc()。这个方法有六个参数,不过,你不用担心,这六个参数很容易理解。如果你还记得之前如何画椭圆,那么对于理解弧线就非常有帮助。arc(x, y, width, height, start, stop)方法,参数从左向右,每两个参数算起,最左边两个是弧线的坐标位置,紧接着两个参数是弧线外围的宽高,这里我们通过画矩形图来进行显示,最后两个参数是弧线的起始点和终止点。起始点默认在圆形正东方位,方向为顺时针旋转一圈为180度,同样,逆时针旋转一圈为-180度。起始点和终止点都以弧度值为单位,常量PI是180度。

上例中,之所以画出的矩形,目的是想帮助大家理解弧线。只要搞明白arc()方法中的六个参数,基本上就可以理解弧线的画法了。

样条曲线

了解了弧线的画法,现在让我们来看看样条曲线如何画?跟弧线类似,样条曲线也有专门的方法供我们使用,方法名为curve(cpx1, cpy1, x1, y1, x2, y2, cpx2, cpy2)。与arc()不同,curve()方法有八个参数,每两个参数算起,从左向右,依次为控制点1,起始点,终止点,控制点2。cpx1是参数名control point x1的简写,类似的cpx2是第二个控制点。需要注意一点,起始点和终止点才是样条曲线的开始和结束,控制点不是。

说了这么多,我们还是举例子进行说明。首先,还是新建一个项目窗口,将如下代码示例填入其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口大小
  size(200, 200);
  // 设置窗口背景色
  background(255);
  // 设置样条曲线颜色为黑色
  stroke(0);
  // 画样条曲线
  curve(40, 40, 80, 60, 100, 100, 60, 120);
  // 隐藏外边框
  noStroke();
  // 设置填充色
  fill(255, 0, 0);
  // 画直径为3的圆形
  ellipse(40, 40, 3, 3);
  // 设置填充色
  fill(0, 0, 255, 192);
  // 画直径为3的圆形
  ellipse(100, 100, 3, 3);
  ellipse(80, 60, 3, 3);
  // 设置填充色
  fill(255, 0, 0);
  // 画直径为3的圆形
  ellipse(60, 120, 3, 3);
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码。
}

完成之后,编译并运行上述代码。如果一切顺利,运行结果大致如下。

processing-fun-curves-2

图片来源:Processing程序执行结果

图中可以看出,连接曲线的上端是起始点,下端为终止点。曲线左侧,上面那点为控制点1,下面那点为控制点2。这四点有这样一种特点。起始点那一点的曲线切线,平行于控制点1与终止点之间的连线。终止点那一点的曲线切线,平行于起始点与控制点2之间的连线。通过这个特点,我们更容易了解样条曲线。目前,我们只是画了其中的一段。如果我们想连续画多段,应该怎样画呢?

下面就让我们看看,怎样连续画多个样条曲线。我们还是通过例子来学习。

还是新建一个项目窗口。将如下代码填入其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 定义多个样条曲线坐标点。
// 这里我们用一个整形数组来记录多个坐标点,
// 每两个整数为一个坐标点
int[] coords = {
  40, 40, 80, 60, 100, 100, 60, 120, 50, 150
};
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口大小
  size(200, 200);
  // 设置背景色
  background(255);
  // 关闭填充色
  noFill();
  // 设置外边框颜色
  stroke(0);
  // 开始画图
  beginShape();
  // 设置控制点1
  curveVertex(40, 40);
  // 设置起始点。
  // 与控制点1坐标相同。
  curveVertex(40, 40);
  curveVertex(80, 60);
  curveVertex(100, 100);
  curveVertex(60, 120);
  // 设置终止点
  // 与控制点2坐标相同。
  curveVertex(50, 150);
  // 设置控制点2
  curveVertex(50, 150);
  // 终止画图
  endShape();
  // 设置填充色
  fill(255, 0, 0);
  // 关闭外边框
  noStroke();
  // 根据样条曲线坐标点。
  // 每两个整数为一组坐标,画出直径为3的圆形
  for (int i = 0; i < coords.length; i += 2) {
    ellipse(coords[i], coords[i + 1], 3, 3);
  }
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码,开心不?
}

编译并运行上述代码,如果一切顺利,运行结果将与下图类似。

processing-fun-curves-3

图片来源:Processing新项目窗口

这段示例中,出现了三个新面孔。其中两个是beginShape()方法和endShape()方法,这两个方法通常来说是要成对使用的,中间是要画的点,后面我们在学习贝塞尔曲线的时候,还会遇到这两个方法。第三个方法是curveVertex(),这个方法是用来画样条曲线中的点的。虽然这里用了“画”这个字眼,但其实用设置可能更合适,因为这些点并不能显示出来,所以在示例的最后,我们用了一个for循环,将所设置的点,用ellipse()方法画成小圆点显示了出来。这里要注意一点的是,我们在画这条曲线的时候,起始点和第一个控制点是重合的,同样的,结束点和第二个控制点也是重合的。

贝塞尔曲线

最后,让我们来看看贝塞尔曲线。Processing提供的画贝塞尔曲线的方法也有八个参数,这一点与画样条曲线的方法参数一样,但很显然,方法名肯定是不一样的,画贝塞尔曲线的方法名为bezier(x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2),从参数名可以猜出,起始点和终止点以及两个控制点的位置,与样条曲线方法curve(cpx1, cpy1, x1, y1, x2, y2, cpx2, cpy2)的参数位置正好相反。那它们的区别在哪呢?它们的区别在于画线的规则不一样。贝塞尔曲线要比样条曲线复杂一些。

下面我们还是通过一个例子来看看贝塞尔曲线的特点。

还是新建一个项目窗口。先填入下面这几行代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口大小
  size(150, 150);
  // 设置背景色
  background(255);
  // 以贝塞尔曲线的起始点为坐标,
  // 画一个圆圈作为标示。
  ellipse(50, 75, 5, 5);
  // 以贝塞尔曲线的终止点为坐标,
  // 画一个圆圈作为标示。
  ellipse(100, 75, 5, 5);
  // 设置填充色
  fill(255, 0, 0);
  // 以贝塞尔曲线的控制点1为坐标,
  // 画一个红色圆点作为标示
  ellipse(25, 25, 5, 5);
  // 以贝塞尔曲线的控制点2为坐标,
  // 画一个红色圆点作为标示
  ellipse(125, 25, 5, 5);
  // 关闭填充色
  noFill();
  // 设置外边框颜色为黑色
  stroke(0);
  // 画出贝塞尔曲线
  bezier(50, 75, 25, 25, 125, 25, 100, 75);
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码
}

完成之后,编译并运行上述代码示例。如果一切顺利,运行结果大致如下。

processing-fun-curves-4

图片来源:Processing新项目窗口

以此为例,我们简答介绍贝塞尔曲线的原理。与样条曲线不同,这条贝塞尔曲线的形成,可以看作是先将两个控制点相连,构成线段1,然后将左上方的控制点1和左下方的起始点相连,构成线段2,再将右上方的控制点2与右下方的终止点相连,构成线段3。取线段1与线段2的中点,相连构成线段5,再取线段1与线段3的中点,相连构成线段6,再取线段5与线段6的中点,此点即为贝塞尔曲线上的一个切点。以此类推,不断的求相邻线段的中点,然后连线,其切点即为贝塞尔曲线的切点,无数的切点连接起来,最终形成光滑的贝塞尔曲线。

processing-fun-curves-5

图片来源:Processing官网

为了便于理解上面的文字描述,可参照上图理解。

对于贝塞尔曲线来说,分为一次,二次,三次等等。Processing中所给出的,可简单理解成三次贝塞尔曲线,更多关于贝塞尔曲线的介绍,可参照wikipedia

上面的示例,我们只画了一条贝塞尔曲线。那么,如果我们想画多条,应该如何实现呢?

好,我们还是用示例来说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 环境初始化。
// 此方法在软件启动时,被系统调用一次。
void setup() {
  // 设置窗口大小
  size(200, 200);
  // 设置背景色
  background(255);
  // 开始画图
  beginShape();
  // 设置起始点
  // 注意:此点必须要首先设置,
  // 因为此点相当于贝塞尔曲线的起始点,
  // 从方法bezierVertex()的参数中可以看出,
  // 只有六个参数,三组坐标点,少了起始点。
  // 这里调用vertex()方法,相当于补上了起始点。
  // 若却少此点,程序会出错,贝塞尔曲线将无法画出。
  vertex(30, 70);
  // 画贝塞尔曲线余下各点。
  bezierVertex(25, 25, 100, 50, 50, 100);
  bezierVertex(20, 130, 75, 140, 120, 120);
  // 终止画图
  endShape();
}
// 画图。
// 此方法被系统默认循环调用。
void draw(){
  // 此处无代码
}

编译并运行此示例代码,运行结果大致如下图所示。

processing-fun-curves-6

图片来源:Processing程序执行结果

上述示例代码可以看到,程序中出现了两个新面孔,一个是vertex()方法,此方法传入两个整形参数,用于设置一个坐标点。另一个新面孔,就是bezierVertex(cpx1, cpy1, cpx2, cpy2, x, y)方法,从参数名称可以看出,少了一组坐标点,这也是为什么要首先调用vertex()方法的原因。这里你又看到了beginShape()方法和endShape()方法。在学习样条曲线的时候,我们也曾经碰到过。

总结

这次我们一口气学了三种曲线,分别是弧线,样条曲线以及贝塞尔曲线,难度以此递增,但灵活度也是依次递增。画弧线,只需要arc()方法即可,画样条曲线,需要调用curve()方法,画多条样条曲线,则需要调用curveVertex()方法,外加配合beginShape()方法和endShape()方法使用。画贝塞尔曲线,则需要调用bezier()方法,画多条贝塞尔曲线,则需要调用bezierVertex()方法,和样条曲线一样,还需要配合beginShape()方法和endShape()方法一起使用。

下期预告

从基本图形到画曲线,已经学了不少如何画不同的图形,但可惜都是静止的。如果想让其动起来,应该如何实现呢?嗯,下一次,我们来一些学习一下,如何让静止的图形动起来吧!

Comments