Desenhando curvas - I
Agora que já sabemos desenhar um polígonos com beginShape()
e endShape()
ou endShape(CLOSE)
podemos experimentar formas curvas.
Sumário
Curvas Bezier com bezierVertex()
- Exemplo 1: Comportamento inesperado
- Exemplo 2: Fechando a curva corretamente
- Exemplo 3: Curva aberta
- Exemplo 4: Curva aberta usando diferentes pontos
- Exemplo 5: Usando
endShape(CLOSE)
Simulando arcos e filetes com Bezier
Extra: Um testador de curvas interativo
Curvas Bezier com bezierVertex()
As famosas curvas Bezier levam o nome de Pierre Bézier, que as desenvolveu em seus trabalhos na década de 1960 na indústria automotiva, elas descrevem curvas a partir das coordenadas de pontos, ou âncoras, que delimitam o início e o fim de uma curva, mas também precisam de “pontos de controle” que em geral ficam fora da curva, mas controlam o seu comportamento.
Você pode usar um ou mais vértices Bezier entre o beginShape()
e o endShape()
, e ela pode ser aberta ou fechada (com endShape(CLOSE)
), mas antes de cada bezierVertex()
é preciso que haja algum outro vértice, um ponto âncora, que marca o início e que pode ser feito com vertex()
, como neste exemplo a seguir.
No bezierVertex()
propriamente dito, os quatro primeiros argumentos são as cordenadas de dois pontos de controle e os últimos dois são as coordenadas do vértice (que pode servir de âncora inicial para um próximo vértice Bezier).
beginShape()
vertex(100, 50) # 0: âncora inicial
bezierVertex(150, 150, # 1: primeiro ponto de controle do primeiro vértice
250, 100, # 2: segundo ponto de controle do primeiro vértice
250, 200), # 3: vértice final da primeira curva, âncora da segunda
bezierVertex(150, 250, # 4: primeiro ponto de controle do segundo vértice
50, 200, # 5: segundo ponto de controle do segundo vértice
50, 100) # 6: segundo vértice bezier (final)
endShape(),
Código completo para reproduzir a imagem acima
def setup(): size(300, 300) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() vertex(100, 50) bezierVertex(150, 150, 250, 100, 250, 200), bezierVertex(150, 250, 50, 200, 50, 100) endShape() pontos = [ (100, 50), (150, 150), (250, 100), (250, 200), (150, 250), (50, 200), (50, 100), ] strokeWeight(1) for i, ponto in enumerate(pontos): x, y = ponto fill(255) ellipse(x, y, 5, 5) t = "{}: {:3}, {:3}".format(i, x, y) text(t, x+5, y-5)
Curvas com curveVertex()
Agora que já sabemos iterar por uma estrutura de dados, e como usar as coordenadas das tuplas para desenhar um polígono, podemos experimentar a mesma estratégia com outros tipos de vértice.
Vejamos agora o curveVertex()
, uma forma de descrever curvas que não tem os pontos de controle como as Bezier, mas tem a curiosa propriedade dos pontos/vértices serem influenciados pelos pontos que vem antes e depois deles.
Vamos iterar por uma estrutura de dados, e usar as coordenadas de tuplas, da mesma forma que fizemos para desenhar um polígono, só que desta vez vamos experimentar essa estratégia com outros tipos de vértice, os vértices de curva, que acabamos de mencionar. Considere esta lista de pontos:
pontos = [
(100, 50),
(150, 100),
(250, 100),
(250, 200),
(150, 200),
(50, 200),
(50, 100),
]
Exemplo 1: Comportamento inesperado
Se chamarmos uma vez curveVertex()
para cada vértice dentro de um contexto de beginShape()
e endShape(CLOSE)
obteremos o seguinte resultado, esquisito (estou aqui omitindo parte do código que controla os atributos gráficos e mostra os texto com os índices dos pontos):
beginShape()
for x, y in pontos:
curveVertex(x, y)
endShape(CLOSE)
Código completo para reproduzir a imagem acima
pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100), ] def setup(): size(300, 300) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() for x, y in pontos: curveVertex(x, y) endShape(CLOSE) strokeWeight(1) for i, ponto in enumerate(pontos): x, y = ponto fill(255) ellipse(x, y, 5, 5) text(i, x+5, y-5)
Exemplo 2: Fechando a curva corretamente
Para obter o resultado esperado (ou, caro leitor, pelo menos o que eu esperava) temos que acrescentar uma chamada com as coordenadas do último vértice antes do primeiro, e do primeiro e segundo vértices depois do último! Diga lá se não é estranho isso!
curveVertex(pontos[-1][0], pontos[-1][1])
for x, y in pontos:
curveVertex(x, y)
curveVertex(pontos[0][0], pontos[0][1])
curveVertex(pontos[1][0], pontos[1][1])
endShape(CLOSE)
Código completo para reproduzir a imagem acima
pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100), ] def setup(): size(300, 300) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() curveVertex(pontos[-1][0], pontos[-1][1]) for x, y in pontos: curveVertex(x, y) curveVertex(pontos[0][0], pontos[0][1]) curveVertex(pontos[1][0], pontos[1][1]) endShape(CLOSE) strokeWeight(1) for i, ponto in enumerate(pontos): x, y = ponto fill(255) ellipse(x, y, 5, 5) text(i, x+ 5, y - 5)
Exemplo 3: Curva aberta
É possível fazer uma curva aberta com os mesmo pontos e a mesma influência do último ponto no primeiro, e do primeiro no último, omitindo o CLOSE
:
curveVertex(pontos[-1][0], pontos[-1][1])
for x, y in pontos:
curveVertex(x, y)
curveVertex(pontos[0][0], pontos[0][1])
endShape()
Código completo para reproduzir a imagem acima
pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100), ] def setup(): size(600, 600) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() curveVertex(pontos[-1][0], pontos[-1][1]) for x, y in pontos: curveVertex(x, y) curveVertex(pontos[0][0], pontos[0][1]) curveVertex(pontos[1][0], pontos[1][1]) pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100), ]
Exemplo 4: Curva aberta usando diferentes pontos
Agora se não queremos essa influência da curva fechada, é preciso repetir o primeiro e o último vértice.
beginShape()
curveVertex(pontos[0][0], pontos[0][1])
for x, y in pontos:
curveVertex(x, y)
curveVertex(pontos[-1][0], pontos[-1][1])
endShape()
Código completo para reproduzir a imagem acima
pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100), ] def setup(): size(600, 600) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() curveVertex(pontos[0][0], pontos[0][1]) for x, y in pontos: curveVertex(x, y) curveVertex(pontos[-1][0], pontos[-1][1]) endShape() strokeWeight(1) for i, ponto in enumerate(pontos): x, y = ponto fill(255) ellipse(x, y, 5, 5) text(i, x+5, y-5)
Exemplo 5: Usando endShape(CLOSE)
Veja como ficaria acrescentando-se o CLOSE
em endShape(CLOSE)
Código completo para reproduzir a imagem acima
pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100), ] def setup(): size(600, 600) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() curveVertex(pontos[0][0], pontos[0][1]) for x, y in pontos: curveVertex(x, y) curveVertex(pontos[-1][0], pontos[-1][1]) endShape(CLOSE) strokeWeight(1) for i, ponto in enumerate(pontos): x, y = ponto fill(255) ellipse(x, y, 5, 5) text(i, x+5, y-5)
Assuntos relacionados
EXTRA: Um testador de curvas interativo
Desafio: Você conseguiria escrever o código que permite testar as curvas arrastando os pontos com o mouse?
Resposta: Usando a mesma estratégia de "arrastar círculos".
arrastando = None pontos = [ (100, 50), (150, 100), (250, 100), (250, 200), (150, 200), (50, 200), (50, 100)] def setup(): size(300, 300) def draw(): background(100) strokeWeight(3) stroke(0) noFill() beginShape() global pontos global arrastando curveVertex(pontos[-1][0], pontos[-1][1]) for x, y in pontos: curveVertex(x, y) curveVertex(pontos[0][0], pontos[0][1]) endShape(CLOSE) strokeWeight(1) for i, ponto in enumerate(pontos): x, y = ponto if i == arrastando: fill(200, 0, 0) else: fill(255) ellipse(x, y, 5, 5) t = "{}: {:03}, {:03}".format(i, x, y) text(t, x + 5, y - 5) def mousePressed(): # quando um botão do mouse é apertado global arrastando for i, ponto in enumerate(pontos): x, y = ponto dist_mouse_ponto = dist(mouseX, mouseY, x, y) if dist_mouse_ponto < 10: arrastando = i break # encerra o laço def mouseReleased(): # quando um botão do mouse é solto global arrastando arrastando = None def mouseDragged(): # quando o mouse é movido apertado global pontos global arrastando if arrastando is not None: x, y = pontos[arrastando] x += mouseX - pmouseX y += mouseY - pmouseY pontos[arrastando] = (x, y)