【26】processing-分析(中文)

程序的解剖学

J David Eisenberg

 

许多处理教程集中在语言可以做什么 (改变颜色,绘制形状,创建对象数组) 以及哪些函数调用可以让你完成这些任务。为了编写一个正在处理的程序,你需要知道这些事情。

 

这些教程没有解决其中的一个难题: 你如何分析一个问题并将其分解成计算机可以做的步骤?在本教程中,我将向您展示我在编写函数以绘制规则多边形和星形图形时的想法。这是一个很好的选择,因为任务不是很大,不能让你集中精力,但这也不是一个完全微不足道的问题。

 

请记住,您在这里看到的是我特定的思维过程和编程风格。有许多不同的方法和风格。当你继续编程时,你会发现你自己的。通过查看 openProcessing.org 程序的源代码,您还可以看到其他人的编程风格 (尽管不是他们的思维过程!)。

 

绘制规则多边形

你不会想到没有蓝图就建一所房子,你也不应该想到没有某种计划就写一个程序。由于处理是一种视觉语言,所以在我接近键盘之前,我总是必须勾勒出我想要的结果。这就是我开始的地方。

 

第一步: 纸上规划

第一步是绘制一个粗略的图表,以挖掘规则多边形如何工作的旧记忆。六边形是我画的第一个。正如你从我在六边形内画的小环中看到的,所有切片的中心角度加起来是一个完整的圆,或者 360 °,多边形的 “半径” 是从中心到每个顶点的一条线。因此,每条半径线之间的角度除以边数 360 °。

 

我只需要知道任务是什么,一个手绘的图表就能完成这项工作。我不需要在绘图程序中生成图表。

 

编程提示: 当你做你的计划时,远离电脑去做。如果你坐在电脑前,屏幕会低语,“看着我!看着我!”键盘会低声说“ 打字!键入一些东西!”去你的厨房桌子。

第二步: 一些基本的三角学

所以,如果你有一个长度 r 的线,以 θ (θ) 的角度从 (0,0) 开始,它的坐标是什么?如果你知道一点三角学,答案是线的端点在 (r cos θ,r sin θ)。如果你不知道三角学,看看这个教程 (三角学的一般和非常简单的介绍),这个教程 (面向处理),以及学习处理第 13 章中的这个例子。在下图中,角度是顺时针绘制的,这就是它们在处理中的测量方式。

 

第三步: 设计决策

看起来这是一个从 0 到 n (边数) 的 for 循环的工作,计算每个顶点的点并绘制连接它们的线。在每一步,我们绘制的角度增加了 360 °/n。

 

绘制一组线的问题是它们只是线 -- 你没有得到一个你可以填充的真实形状,比如 rect() 或三角形 ()。幸运的是,处理允许您使用 beginShape() 、顶点 () 和 endShape() 函数创建自己的形状。BeginShape () 参考页面上的第一个示例是要遵循的模型。因此,下一步的设计决策是将多边形作为真实的形状。

 

由于您可能希望在程序中绘制许多多边形,因此使用多边形 () 函数是有意义的。它需要什么参数?想到四个: 边数、中心 x 和 y 坐标以及半径。这是代码。我在 setup() 函数中写了几个不同的对多边形 () 的调用。虽然我计算了以度为单位的角度,以弧度为单位的正弦和余弦测量角度,但是我必须使用弧度 () 函数进行转换。

 

 

void setup() {
  size(300, 300);
  background(255);
  noFill();
  polygon(3, 50, 75, 50);
  polygon(4, 170, 75, 50);
  fill(255, 204, 255);
  stroke(128, 0, 128);
  polygon(5, 50, 180, 50);
  noFill();
  stroke(0);
  polygon(6, 170, 180, 50);
}
void polygon(int n, float cx, float cy, float r) {
  float angle = 360.0 / n;
  beginShape();
  for (int i = 0; i < n; i++) {
    vertex(cx + r * cos(radians(angle * i)),
      cy + r * sin(radians(angle * i)));
  }
  endShape(CLOSE);
}

向前两步,后退一步

程序工作正常,所以是时候看看是否有可以添加或更改的内容了。首先,三角形和五角大楼似乎有点错误; 它们通常是向上而不是向侧面画的。它们看起来奇怪的原因是第一个顶点 (在 0 ° 处) 指向右边而不是直线向上。最好有一个额外的参数来给出第一个顶点的起始角度。(另一种解决方案是让事情保持原样,让程序员使用旋转 () [参见本教程],但我做出了使用额外参数的设计决定。)角度应该以度或弧度为单位?答案: 弧度,以便与处理所做的其他一切保持一致。

 

我的下一个想法是,能够为多边形指定宽度和高度会很好,就像您使用椭圆 () 或矩形 () 一样。我已经知道公式是什么,但是我想画一幅画来检查一下。作为一个初步的实验,我试着用量角器和直尺把五角大楼画成一个正方形,最后在左边画了一幅可怕的画。为什么两边的长度不相等?我意识到我试图让这幅画符合我的先入之见,而不是做一幅准确的画,看看它把我引向了哪里。右边的图纸做得更仔细。经过一点思考,我意识到五角大楼不会完全适合这个正方形,因为角度不是 90 度的倍数。正多边形适合一个圆,而不是一个正方形!

 

嗯,这是一个死胡同。这种事情总是发生在编程中,所以我没有花太多时间担心它。是时候采取另一种方法了。因为我没有绘制椭圆的准确方法,所以我不得不以不同的方式思考这个问题。假设你在一块正方形的橡胶板上画了一个圆圈,然后你把它拉伸,这样它的宽度是原来的两倍,但是高度是一样的。圆上的点的垂直位置没有改变,但是水平位置现在离中心的距离是过去的两倍。如果你垂直拉伸纸张,同样的想法也适用。下面的粗略图似乎证实了这一点,所以是时候重写多边形 () 函数了。

 

 

 

void setup() {
  size(300, 300);
  background(255);
  noFill();
  polygon(3, 50, 75, 100, 100, -PI / 2.0); // -90 度
  polygon(4, 170, 75, 50, 125, -PI / 4.0); // -45度
  fill(255, 204, 255);
  stroke(128, 0, 128);
  polygon(5, 50, 200, 75, 50, -PI / 2.0); // -90度
  noFill();
  stroke(0);
  polygon(6, 170, 200, 50, 100, 0);
  stroke(128);
  //绘制封闭的椭圆以确保我们做得正确
  ellipse(50, 75, 100, 100);
  ellipse(170, 75, 50, 125);
  ellipse(50, 200, 75, 50);
  ellipse(170, 200, 50, 100);
}
void polygon(int n, float cx, float cy, float w, float h, float startAngle) {
  float angle = TWO_PI/ n;
  //水平 “半径” 是宽度的一半,
//垂直 “半径” 是高度的一半
  w = w / 2.0;
  h = h / 2.0;
  beginShape();
  for (int i = 0; i < n; i++) {
    vertex(cx + w * cos(startAngle + angle * i), 
      cy + h * sin(startAngle + angle * i));
  }
  endShape(CLOSE);
}

因为一切都是弧度,我现在用 π 和 TWO_PI (2 π) 来描述角度,因为 2 π 弧度等于 360 °。除了 setup() 中的代码来测试新功能之外,我绘制了与多边形具有相同中心、宽度和高度的椭圆,以确保顶点在适当的区域内。

 

参数太多

我现在有一个更灵活的绘制多边形的函数,但是它是以更多参数为代价的。如果能够画出普通的情况 (宽度和高度相等,起始角度为零),而不必指定所有这些参数,那就太好了。解决方案是使函数过载。在处理中,您可以有两个同名的函数,只要它们具有不同数量的参数和/或参数类型。这方面的一个例子是 Processing 的 stroke() 函数,它被重载了,这样你就可以用一个数字来调用它,三个数字来表示颜色,或者四个数字来表示颜色的不透明度。处理查看您提供的参数数量,并确定要调用哪个版本的 stroke()。

 

以下是要添加到上一个示例中的代码。当你给多边形 () 只有四个数字时,它将调用以下函数,该函数调用宽度和高度等于所需半径两倍的函数的 “大” 版本,以及零的起始角度。

 

 

void polygon(int n, float cx, float cy, float r) {
  polygon(n, cx, cy, r * 2.0, r * 2.0, 0.0);
}

这里有一些代码来测试重载函数。

 

 

void setup() {
  size(300, 300);
  background(255);
  smooth();
  
  noFill();
  polygon(3, 70, 75, 50); // 使用默认值
  polygon(4, 170, 75, 25);
  
  stroke(128);
  // 绘制封闭的椭圆以确保我们做得正确
  ellipse(70, 75, 100, 100);
  ellipse(170, 75, 50, 50);
}

安全计算

如果有人试图绘制一个有 2 面、 1 面或更糟糕的是 0 面的多边形,会发生什么?前两个将生成一条线和一个点,但是第三个将在试图计算角度时导致零误差除法。负数会发生什么?由于少于三边的多边形没有多大意义,所以我将多边形 () 函数的主体包装在 if 语句中。现在,当有人指定两个或更少的边时,该函数将不会绘制任何内容。

空多边形 (int n,浮动 cx,浮动 cy,浮动 w,浮动 h,浮动星盘) {

 

if (n > 2) {
    float angle = TWO_PI/ n;
    .
    .
    beginShape()
    .
    .
    endShape(CLOSE);
  }
}

画星

现在我对多边形 () 函数很满意,是时候继续绘制星星了。从一些玩粗糙的草图,我认为通过找出多边形的所有对角线相交的位置来绘制恒星是可能的。

 

 

我在这里看到了两个问题。首先,找到两条线的交点是很多计算。不是特别难计算,但是很多,当你有垂直线时,它会变得很棘手。第二,我不能有四边或三边箭头形状; 没有足够的对角线。

 

然后,我有了另一个想法。我不能告诉你这个想法是从哪里来的,也不能告诉你我是如何得到它的; 它只是击中了我。这是我认为无法教授的过程的一部分。这是一个想法: 如果你用饼干面团切下了一个多边形,然后你把它推到两边,形成一个星星形状,会怎么样?这是一种适用于正方形和三角形的方法。

 

当你推到两边时,你把它们推到中点,这样你就可以得到一个很好的对称饼干。从那里开始,这并不是一个很大的飞跃: “如果我在多边形的每一部分的一半处有一个更短的半径呢?”

 

这个代码很容易写。我需要一个额外的参数: 小半径与大半径的比例。在下面的代码中,if 语句控制是使用短半径还是长半径。我还放入了一个重载版本,它绘制了一个宽度和高度相等且起始角度为零的恒星。为了测试,我将短半径定为长半径的一半。

 

 

void setup() {
  size(300, 300);
  background(255);
  smooth();
  noFill();
  star(3, 60, 75, 100, 100, -PI / 2.0, 0.50); // -90 度
  star(4, 170, 75, 25, 0.50);  //使用更简单的呼叫
  fill(255, 204, 255);
  stroke(128, 0, 128);
  star(5, 60, 200, 75, 50, -PI / 2.0, 0.50); // -90 度
  noFill();
  stroke(0);
  star(6, 170, 200, 50, 100, 0, 0.50);
  stroke(128);
  
  // 绘制封闭的椭圆以确保我们做得正确
  ellipse(60, 75, 100, 100);
  ellipse(170, 75, 50, 50);
  ellipse(60, 200, 75, 50);
  ellipse(170, 200, 50, 100);
}
void star(int n, float cx, float cy, float r, float proportion) {
  star(n, cx, cy, 2.0 * r, 2.0 * r, 0.0, proportion);
}
void star(int n, float cx, float cy, float w, float h,
  float startAngle, float proportion) {
  if (n > 2) {
    float angle = TWO_PI/ (2 *n);  // 两倍的侧边
    float dw; // 绘制宽度
    float dh; // 绘制高度
    
    w = w / 2.0;
    h = h / 2.0;
    
    beginShape();
    for (int i = 0; i < 2 * n; i++) {
      dw = w;
      dh = h;
      if (i % 2 == 1) { //对于奇数顶点,使用短半径
      
        dw = w * proportion;
        dh = h * proportion;
      }
      vertex(cx + dw * cos(startAngle + angle * i),
        cy + dh * sin(startAngle + angle * i));
    }
    endShape(CLOSE);
  }
}

出了什么问题?

 

当我运行这个程序时,我吓坏了。一切看起来都很棒,除了三面星。为什么我没有从中得到一颗星星?代码看起来确实是正确的,所以我决定看看如果我手工绘制图表会发生什么。我用我的量角器测量了角度,我用黑色画了长度为 2英寸的长半径线,用红色画了长度为 1英寸的短半径线。果然,恰好短半径线的端点正好在主三角形的线上。该程序正在绘制一颗星星,侧面被零推入。

 

当我想知道为什么会发生这种情况时,我记得线之间的夹角余弦,60 ° (π/3) 达到 0.5,我强烈怀疑这与此有关。为了测试我的假设,我改变了平方,使用 45 ° (π/4) 的余弦比例,五角大楼使用 36 ° (π/5) 的余弦,六边形到余弦为 30 ° (π/6)。果然,他们都没有推出。

 

所以,如果你画一个有 n 条边的星星,并且你将短半径与长半径的比例设置为 cos(π/n),你会得到一个非星星!我仍然不能写一个数学证明,但这是一个有趣的结果。我不认为写测试程序的兼职是浪费时间; 我确实学到了一些新的有趣的东西,而且将来可能会有用。

 

编程挑战: 如果你将比例设置为大于 “非明星” 比例会发生什么?尝试一下,找出答案。

当然,获得三面星的方法是将比例设置为小于 0.5 的金额,我在下面的设置代码中做了这一点,结果要好得多。我还改变了其他星星的比例,只是为了看看它们会是什么样子。

 

 

void setup(){
  size(300, 300);
  background(255);
  noFill();
  star(3, 60, 75, 100, 100, -PI / 2.0, 0.3); // -90 度
  star(4, 170, 75, 25, 0.5);  // 使用更简单的呼叫
  fill(255, 204, 255);
  stroke(128, 0, 128);
  star(5, 60, 200, 75, 50, -PI / 2.0, 0.75); // -90 度
  noFill();
  stroke(0);
  star(6, 170, 200, 50, 100, 0, 0.4);
  stroke(128);
  
  // 绘制封闭的椭圆以确保我们做得正确
  ellipse(60, 75, 100, 100);
  ellipse(170, 75, 50, 50);
  ellipse(60, 200, 75, 50);
  ellipse(170, 200, 50, 100);
}
void star(int n, float cx, float cy, float r, float proportion) {
  star(n, cx, cy, 2.0 * r, 2.0 * r, 0.0, proportion);
}
void star(int n, float cx, float cy, float w, float h,
  float startAngle, float proportion) {
    
  if (n > 2) {
    float angle = TWO_PI/ (2 *n);  //两倍的侧边
    float dw; // 绘制宽度
    float dh; //绘制高度
    
    w = w / 2.0;
    h = h / 2.0;
    
    beginShape();
    for (int i = 0; i < 2 * n; i++) {
      dw = w;
      dh = h;
      if (i % 2 == 1) {
        dw = w * proportion;
        dh = h * proportion;
      }
      vertex(cx + dw * cos(startAngle + angle * i),
        cy + dh * sin(startAngle + angle * i));
    }
    endShape(CLOSE);
  }
}

使用函数

 

最后,为了在测试之外的其他地方使用这些函数,我决定编写一个程序,随机生成多边形和星星。窗口是 300 乘 300,星星或多边形的半径为 24 点,所以我有六行五列 (额外的点用于间距)。还记得我说过,知道会创造一颗星星的比例 “可能会在未来变得有用” 吗?嗯,它们不仅对这个项目有用 -- 它们至关重要。当我生成一颗星星时,我需要确保它真的有一个星星的形状,所以我必须保持短半径与长半径的比例小于 π 的余弦除以边数。

 

以下是 setup() 和 draw() 的代码:

 

 

void setup() {
  size(300, 300);
  background(255);
  frameRate(6);
  rectMode(CENTER);
}
void draw() {
  // 选择随机笔画颜色
  int r = int(random(0, 255));
  int g = int(random(0, 255));
  int b = int(random(0, 255));
  // 填充不透明度
  int opacity = int(random(100, 255));
  int nSides = int(random(3, 9));
  // 确定中心 x 和 y 坐标
  int cx = 25 + 50 * int(random(0, 6));
  int cy = 25 + 50 * int(random(0, 6));
 //如果随机数 (0 或 1) 为 0,绘制多边形;
//否则,画一颗星星
  boolean isPolygon = int(random(2)) == 0;
  // 对于恒星,你需要长短半径的比例
  float proportion;
  stroke(255); // 擦除此区域中的任何先前绘图
  fill(255);
  rect(cx, cy, 50, 50); 
  stroke(r, g, b);
  fill(r, g, b, opacity);
  if (isPolygon) {
    polygon(nSides, cx, cy, 24);
  } else {
    proportion = random(0.2, 0.8) * cos(PI / nSides);
    star(nSides, cx, cy, 24, proportion);
  }
}
void polygon(int n, float cx, float cy, float r) {
  float angle = 360.0 / n;
  beginShape();
  for (int i = 0; i < n; i++) {
    vertex(cx + r * cos(radians(angle * i)),
      cy + r * sin(radians(angle * i)));
  }
  endShape(CLOSE);
}
void star(int n, float cx, float cy, float r, float proportion) {
  star(n, cx, cy, 2.0 * r, 2.0 * r, 0.0, proportion);
}
void star(int n, float cx, float cy, float w, float h,
  float startAngle, float proportion) {
  if (n > 2) {
    float angle = TWO_PI/ (2 *n);  // 两倍的侧边
    float dw; //绘制宽度
    float dh; // 绘制高度
    
    w = w / 2.0;
    h = h / 2.0;
    
    beginShape();
    for (int i = 0; i < 2 * n; i++)
    {
      dw = w;
      dh = h;
      if (i % 2 == 1) // 对于奇数顶点,使用短半径
      {
        dw = w * proportion;
        dh = h * proportion;
      }
      vertex(cx + dw * cos(startAngle + angle * i),
        cy + dh * sin(startAngle + angle * i));
    }
    endShape(CLOSE);
  }
}

多边形和星星作为对象

现在我有了多边形和星星的工作函数,制作一个多边形和星星类可能会很有用,这样我就可以把它们当作对象。我将使用的方法大致相同; 我将从简单的测试用例开始,逐步构建类,最后在一个成熟的程序中使用它们。这里有一个关于处理对象的教程。

 

总结

本教程向您展示了您从未在书中看到的东西。在一本书中,所有的图表都是完美的。你看到一个示例程序,它只是工作,它产生了华丽的结果。公平地说,作者不能向你展示他们的思维过程; 否则,他们的书会是十倍。事实上,我没有包括所有的版本,其中一个错误的括号或一个被遗忘的对弧度 () 的调用使我的草图爆炸成一团不可理解的线。但是我们所有人,知名作者,编写这些教程的人,以及刚开始的程序员,都经历了同样的设计、试错和开发过程。我想让你至少看一次这个过程,因为我们都在一起。

 

您可以从本教程下载文件。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值