【13】processing-平面(中文)

PShape

Daniel Shiffman

(本教程的源代码在处理示例中。在处理IDE中选择文件→示例,然后选择主题→创建形状。)

使用处理编程时,首先要学习的是如何在屏幕上绘制“原始”形状:矩形、椭圆、直线、三角形等。

rect(x,y,w,h);
ellipse(x,y,w,h);
line(x1,y1,x2,y2);
triangle(x1,y1,x2,y2,x3,y3);

更高级的绘图选项是使用beginShape()和endShape()指定自定义多边形的顶点

 

beginShape();
  vertex(x1,y1);
  vertex(x2,y2);
  vertex(x3,y3);
  vertex(x4,y4);
  // 等;
endShape();

通过将一组绘图函数组合在一起,甚至可以将它们组织到一个类中,可以构建更复杂的形状。

 

class MyWackyShape {
  //一些变量
  //构造器
  //一些功能
  //显示形状!
  void display() {
    rect(x,y,w,h);
    ellipse(x,y,w,h);
    beginShape();
    vertex(x1,y1);
    vertex(x2,y2);
    vertex(x3,y3);
    vertex(x4,y4);
    // 等等;
    endShape();
  }
}

这一切都很好,会让你走得很远。只有知道上面的情况,你才能画出很少的东西。然而,还有另一个步骤。在某些情况下,这一步骤可以提高渲染速度,并为代码PShape提供更高级的组织模型。

 

PShape是用于存储形状的数据类型。这些形状可以是由自定义几何图形生成的形状,也可以是从外部文件(如SVG)加载的形状。

 

原始形状

让我们从使用PShape的一个最简单的例子开始。下面是一个简单的处理draw()方法,它在鼠标后面绘制一个矩形。

 

void draw() {
  background(51);
  stroke(255);
  fill(127);
  rect(mouseX,mouseY,100,50);
}

基本的东西。如果这是我们所有的代码,不一定有一个很好的理由使用PShape,但我们将继续推进,并制作一个PShape矩形无论如何作为演示。我们的目标是要有一个变量来存储该变量的颜色和维度,允许我们的draw函数看起来像这样。

 

void draw() {
  background(51);
  shape(rectangle);
}

这个“矩形”是什么?这是个假象。

PShape rectangle;

要初始化PShape,请使用createShape()方法。createShape()的第一个参数是一个常量,该常量指定要生成的PShape的类型。正如我们将在本教程中看到的,有很多选项:椭圆、矩形、圆弧、三角形、球体、长方体、直线、组等等。在这里,我们创建一个基本形状,一个矩形,所以我们使用常量“RECT”,后面的参数是形状的(x,y)位置以及它的尺寸(宽度,高度)。所以现在我们的setup()应该是:

 

PShape rectangle;
void setup() {
  size(640,360,P2D);
  rectangle = createShape(RECT,mouseX,mouseY,100,50);
}

不过,有个大问题。是的,我们希望矩形显示在鼠标位置,但是当草图首先开始mouseX时,mouseY设置为零。在我们循环draw()之前,mouseX和mouseY是不会复活的。重要的是要记住,当使用PShape时,我们真正要做的是配置相对于原点(0,0)的几何体。然后使用translate()、rotate()等变换在屏幕上移动该形状通常是有利的

 

PShape rectangle;
void setup() {
  size(640,360,P2D);
  rectangle = createShape(RECT,0,0,100,50);
}

如果我们想从中心画出矩形:

PShape rectangle;
void setup() {
  size(640,360,P2D);
  rectangle = createShape(RECT,-50,-25,100,50);
}

然后我们可以根据鼠标移动它。

void draw() {
  background(51);
  translate(mouseX,mouseY);
  shape(rectangle);
}

PShape对象的一个优点是,除了几何图形外,它还可以存储颜色信息。创建形状以更改其填充或笔划后,请使用setFill()、setStroke()和setStrokeWeight()等方法。

void setup() {
  size(640,360,P2D);
  rectangle = createShape(RECT,-50,-25,100,50);
  rectangle.setStroke(color(255));
  rectangle.setStrokeWeight(4);
  rectangle.setFill(color(127));
}

如果要动态更改形状的颜色,也可以在draw()期间调用这些方法。

 

void draw() {
  background(51);
  translate(mouseX, mouseY);
  rectangle.setFill(color(map(mouseX, 0, width, 0, 255)));
  shape(rectangle);
}

需要注意的是,与fill()和stroke()不同,必须将全色作为参数传递。i、 e.对于红色填充,不要说“setFill(255,0,0)”,而是说“setFill(color(255,0,0))”。此外,set fill()和set stroke()可以使用布尔参数(例如setFill(false))来打开或关闭给定顶点的填充或笔划,也可以使用整数(例如setFill(i,color(255,0,0))来设置特定顶点的填充或笔划。

 

请参阅示例“PrimitivePShape”(在“文件”—“示例”—“主题”—“创建形状”下),以获取演示上述所有代码的示例。

 

自定义形状

pshape也可以配置为自定义顶点。您可能以前做过,没有PShape,只使用beginShape()和endShape()。例如,假设你想在处理过程中画一颗星星。您可能有以下代码:

void draw() {
  background(51);
  translate(mouseX, mouseY);
  fill(102);
  stroke(255);
  strokeWeight(2);
  beginShape();
  vertex(0, -50);
  vertex(14, -20);
  vertex(47, -15);
  vertex(23, 7);
  vertex(29, 40);
  vertex(0, 25);
  vertex(-29, 40);
  vertex(-23, 7);
  vertex(-47, -15);
  vertex(-14, -20);
  endShape(CLOSE);
}

在这里,与前面的示例一样,我们的目标是在draw()中将形状本身绘制为对象。

void draw() {
  background(51);
  translate(mouseX, mouseY);
  shape(star);
}

要使用自定义顶点生成PShape,必须首先调用createShape()来生成shape对象,然后对该对象调beginShape()和endShape():

PShape star;
void setup() {
  size(640,360,P2D);
  star = createShape();       // 首先创建形状
  star.beginShape();          // 现在调用beginShape();
  // 所有的顶点信息都在这里。
  star.endShape(CLOSE);       // 现在调用endShape(CLOSE);
}

然后,可以通过调用新PShape对象“star”上的函数来指定所有顶点(和颜色)。请注意,这里不需要setFill()和setStroke(),只有在最初创建形状后选择更改颜色。

 

void setup() {
  size(640,360,P2D);
  // 首先创建形状
  star = createShape();
  star.beginShape();
  // 可以设置填充和笔划
  star.fill(102);
  star.stroke(255);
  star.strokeWeight(2);
  // 这里,我们对一系列顶点进行硬编码
  star.vertex(0, -50);
  star.vertex(14, -20);
  star.vertex(47, -15);
  star.vertex(23, 7);
  star.vertex(29, 40);
  star.vertex(0, 25);
  star.vertex(-29, 40);
  star.vertex(-23, 7);
  star.vertex(-47, -15);
  star.vertex(-14, -20);
  star.endShape(CLOSE);
}

请参阅示例“PolygonPShape”(在“文件>示例>主题>创建形状”下),以获取演示上述所有代码的示例。

 

更多形状

如前所述,使用PShape的一个原因是帮助您组织几何图形。然而,还有另一个原因。假设您有一个星型类,其中有一个display()函数,如下所示:

void display() {
  pushMatrix();
  translate(x, y);
  fill(102);
  stroke(255);
  strokeWeight(2);
  beginShape();
  vertex(0, -50);
  vertex(14, -20);
  vertex(47, -15);
  vertex(23, 7);
  vertex(29, 40);
  vertex(0, 25);
  vertex(-29, 40);
  vertex(-23, 7);
  vertex(-47, -15);
  vertex(-14, -20);
  endShape(CLOSE);
  popMatrix();
}

在draw()中,您将遍历一个星形对象数组,显示每个对象。

 

void draw() {
  background(51);
  for (int i = 0; i < stars.length; i++) {
    stars[i].display();
  }
}

试着画500颗左右的星星,你的素描可能会跑得很慢,大约每秒10帧。这是因为这种画法,通常被称为“立即”模式,要求渲染器每次通过绘制每个星星来计算几何体。但这有必要吗?毕竟,一次又一次是同一颗星星。使用PShape可以让处理基本上“记住”恒星的几何结构。绘制存储的几何体(在较低级别的OpenGL语法中称为“顶点缓冲对象”)称为“保留”模式,速度要快得多。这500颗恒星可以用PShape代替以60 FPS的速度轻松渲染。这可以通过将PShape变量作为Star类的一部分来实现。

class Star {
  PShape s;
  float x, y;
//然后需要在构造函数中初始化该PShape。这可以直接做,就在课堂上。
Star() {
    // 首先创建形状
    s = createShape();
    s.beginShape();
    // 可以设置填充和笔划
    s.fill(102);
    s.stroke(255);
    s.strokeWeight(2);
    // Here, we are hardcoding a series of vertices
    s.vertex(0, -50);
    s.vertex(14, -20);
    s.vertex(47, -15);
    s.vertex(23, 7);
    s.vertex(29, 40);
    s.vertex(0, 25);
    s.vertex(-29, 40);
    s.vertex(-23, 7);
    s.vertex(-47, -15);
    s.vertex(-14, -20);
    s.endShape(CLOSE);
  }

如果每个对象本身都有自己的几何体(通过算法生成),则此方法是有意义的。但是,如果每个对象都显示相同的PShape,那么在构造函数本身中传递对PShape的引用可能更有意义。让我们来看看这是如何工作的。假设我们创建了一个名为“Polygon”的泛型类,它引用了一个PShape(在display方法中绘制)。

class Polygon {
  PShape s;
  void display() {
    shape(s);
  }
}

在前面的例子中,形状是在对象的构造函数中创建的。在这里,我们将演示一种不同的方法来编写通过参数设置形状的构造函数。

Polygon(PShape s_) {
    s = s_;
  }

但是,要使其工作,必须在创建对象时传入PShape。下面是在带有setup()的主选项卡中的情况:

Polygon poly;                   // 多边形物体
void setup() {
  size(640,360,P2D);
  PShape star = createShape();  // 首先我们要做一个
  star.beginShape();
  star.noStroke();
  star.fill(0, 127);
  star.vertex(0, -50);
  star.vertex(14, -20);
  star.vertex(47, -15);
  star.vertex(23, 7);
  star.vertex(29, 40);
  star.vertex(0, 25);
  star.vertex(-29, 40);
  star.vertex(-23, 7);
  star.vertex(-47, -15);
  star.vertex(-14, -20);
  star.endShape(CLOSE);
  
  // 通过传入对PShape的引用来生成多边形对象
  poly = new Polygon(star);
}

这是一种非常灵活的方法。例如,如果有一个PShape对象数组,则可以使用随机PShape创建新的多边形对象。下面是一个简单的实现。

 

 

ArrayList polygons;
PShape[] shapes = new PShape[2];                 // An array of PShapes
void setup() {
  size(640, 360, P2D);
  smooth();
  shapes[0] = createShape(ELLIPSE,0,0,100,100);  // Two PShapes
  shapes[1] = createShape(RECT,0,0,100,100);
  polygons = new ArrayList();
  for (int i = 0; i < 25; i++) {
    int selection = int(random(shapes.length));  // Pick a random index
    Polygon p = new Polygon(shapes[selection]);  // Use corresponding PShape to create Polygon
    polygons.add(p);
  }
}

对于完整的示例,请查看“polygonpshapeop1”、“polygonpshapeop2”和“polygonpshapeop3”(在“文件→示例→主题→创建形状”下)。

 

更多自定义形状

pshape支持在即时模式下可以绘制的所有相同类型的形状。这些包括:点、线、三角形、三角形扇形、三角形条、四边形和四边形条。例如,如果要创建四边形条带,可以说:


/

PShape s = createShape();
s.beginShape(QUAD_STRIP);

如果未指定模式,则该形状可以是任何不规则多边形,如我们在上一个星形示例中看到的。通过不关闭形状,PShape也可以是路径。下面是一个将正弦波作为PShape对象的路径示例。

 

PShape path = createShape();
  path.beginShape();
  float x = 0;
  //将路径计算为正弦波
  for (float a = 0; a < TWO_PI; a += 0.1) {
    path.vertex(x,sin(a)*100);
    x+= 5;
  }
  // 如果你想让形状成为路径,就不要“关闭”它
  path.endShape();

有关完整实现,请参见示例“PathPShape”。

 

pshape还包括begincoutor()和endContour()方法。这些方法允许您从另一个形状中剪切一个形状,并在其中创建带有孔的形状。想想画一个字母的轮廓,比如P(用于处理)。P的轮廓可以绘制为一系列顶点,但是要从P的中间敲出相反的形状,需要开始/结束轮廓并绘制内部的路径。下面是一个简单的例子,它在一个外部正方形的内部绘制一个内部正方形。

 

// 成形
PShape s = createShape();
s.beginShape();
// 外形外部
s.vertex(-100,-100);
s.vertex(100,-100);
s.vertex(100,100);
s.vertex(-100,100);
s.vertex(-100,-100);
// 形状内部
s.beginContour();
s.vertex(-10,-10);
s.vertex(-10,10);
s.vertex(10,10);
s.vertex(10,-10);
s.endContour();
// 成品形状
s.endShape();

请注意,定义负形状的顶点必须与外部形状的方向相反。首先按顺时针顺序为外部形状绘制顶点,然后为内部形状逆时针绘制顶点。

 

有关完整实现,请参见示例“BeginedConteur”(在“文件→示例→主题→创建形状”下)。

 

PShape群

PShape的另一个便利之处是能够对形状进行分组。例如,如果你想从一组圆、矩形和自定义多边形中创建一个外星生物呢。如果头部是圆形,身体是矩形,你可能会认为你需要:

 

PShape head = createShape(ELLIPSE,0,0,50,50);
PShape body = createShape(RECT,0,50,50,100);
shape(head);
shape(body);

当然这是可行的,但如果你能把头部和身体组合成一个形状,那就方便多了。有了一个“小组”,你就可以了。

 

// 生成父形状
PShape alien = createShape(GROUP);
// 做两个形状
PShape head = createShape(ELLIPSE, 0, 0, 50, 50);
PShape body = createShape(RECT, 0, 50, 50, 100);
// 将两个“子”形状添加到父组
alien.addChild(head);
alien.addChild(body);
// 绘制组
translate(width/2, height/2);
shape(alien);

有关演示将基本形状、自定义多边形和路径组合在一起的PShape的完整示例,请参见“GroupPShape”(在“文件>示例>主题>创建形状”下)。

 

PShape组允许您构建复杂的形状层次结构。这又允许您通过在父级调用相应的方法来设置子形状的颜色和属性。类似地,通过在层次结构的给定级别调用转换函数,只会影响下面的形状。

 

加载外部形状文件

pshape还支持加载形状文件,例如SVG(用于2D形状)或OBJ(用于3D形状)。这可以通过loadShape()方法实现。

PShape svg;
void setup() {
  size(640, 360, P2D);
  svg = loadShape("star.svg");
}
void draw() {
  background(255);
  shape(svg);
}

实时操作PShape的顶点

在使用PShape大约五分钟后,不可避免地会出现一个问题:“如果我想晃动所有顶点,该怎么办?”PShape允许您通过getVertex()和setVertex()方法动态访问和更改顶点。

 

要在PShape的顶点上迭代,可以从0循环到顶点总数(getVertexCount())。假设一个PShape“s”,这看起来像:

 

for (int i = 0; i < s.getVertexCount(); i++) {
}

//可以使用getVertex()将顶点检索为PVector对象。
for (int i = 0; i < s.getVertexCount(); i++) {
  PVector v = s.getVertex(i);
}

//然后,可以通过操纵PVector并使用setVertex()设置新值来移动该顶点。
for (int i = 0; i < s.getVertexCount(); i++) {
  PVector v = s.getVertex(i);
  v.x += random(-1,1);
  v.y += random(-1,1);
  s.setVertex(i,v.x,v.y);
}

有关使用Perlin噪波摆动多边形顶点的示例,请参见“WigglePShape”(在“文件”—“示例”—“主题”—“创建形状”下)。

 

3D和纹理

本教程主要介绍在二维图形环境中使用PShape对象的基础知识。但是,PShape也提供了P3D教程中描述的所有3D功能。主要区别在于,在三维中,顶点是用x、y和z坐标指定的。此外,PShape对象可以使用setTexture()函数自动生成纹理。对于像球体这样的复杂形状,这使事情变得很容易。

PImage img;
PShape globe;
 
void setup() {
  // 加载图像
  img = loadImage("earth.jpg");
  globe = createShape(SPHERE, 50);
  // 使用图像自动对形状进行纹理处理
  globe.setTexture(img);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值