Ideias, Regras, Simulação: Desenhando com Primitivas Gráficas (Círculos, Elipses e Polígonos)
Créditos para a imagem: Imagem criada pelo autor usando o programa Inkscape; ícones de Font Awesome.
Pré-Requisitos
O material de Ideias, Regras, Simulação promove aprendizado baseado em projetos (simulações interativas e jogos digitais) com recursos multimídia. Assim, ele é complementar ao material de Aprenda Programação, que introduz fundamentos e técnicas básicas de programação, com exemplos para JavaScript, Python, Lua e GDScript (para Godot Engine).
Para Ideias, Regras, Simulação, você precisará de um ambiente de desenvolvimento configurado para uma das linguagens anteriores:
- GDScript (Godot Engine);
- JavaScript. Em JavaScript, é preciso criar uma página HTML com um canvas;
- Python. Em Python, é necessário instalar PyGame. Instruções em texto estão disponíveis em Bibliotecas: Interface Gráfica com Thonny. Instruções em vídeo estão disponíveis em Python em linha de comando e usando o IDE Thonny, hospedado no YouTube;
- Lua. Em Lua, é necessário instalar LÖVE (Love2D). Instruções em vídeo estão disponíveis em Lua em linha de comando e usando o IDE ZeroBrane Studio, hospedado no YouTube.
JavaScript (para navegadores) e GDScript fornecem suporte para conteúdo multimídia.
Também estou considerando melhorar os editores online para programação dentro do website, em um estilo de curso interativo. Atualmente, a página de ferramentas fornece opções para:
Os editores possuem suporte para imagens em navegador.
Contudo, Python e Lua utilizam a sintaxe para JavaScript com canvas
.
Caso eu criasse uma abstração e adicionasse suporte para áudio, eles tornar-se-iam opções válidas (ao menos para primeiras atividades).
Contudo, convém configurar um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE), ou a combinação de editor de texto com interpretador assim que possível para programar em sua máquina com maior eficiência. Ambientes online são práticos, mas ambientes locais são mais potencialmente mais completos, eficientes, rápidos e personalizáveis. Caso queira tornar-se profissional, você precisará de um ambiente de desenvolvimento configurado. Quanto antes o fizer, melhor.
Versão em Vídeo (Em Progresso)
Versões em vídeo serão lançadas em breve no canal do autor no YouTube.
Documentação
Pratique consultar pela documentação:
- GDScript: documentação para a linguagem;
- JavaScript: documentação para a linguagem;
- Python: documentação para a linguagem e documentação para PyGame;
- Lua: documentação para a linguagem (Lua 5.1) e documentação para LÖVE.
Praticamente todos os links possuem um campo para buscas. Em Lua, a documentação está em uma única página. Você pode procurar por entradas usando o atalho Ctrl F (busca ou search).
Contornos e Preenchimentos
A representação para a simulação de lançamentos de moedas e dados usou linhas, arcos e retângulos para representar resultados. Embora simples, os gráficos cumpriram o papel de representar os lados da moeda ou as faces de um dado.
Contudo, os resultados seriam mais esteticamente agradáveis caso os desenhos tivessem um preenchimento colorido ao invés de serem meros contornos. Por exemplo, em editores gráficos como GIMP e Microsoft Paint fornecem uma ferramenta com ícone de balde (bucket fill) para preencher regiões delimitadas por contornos.
Você já pensou em como elas são criadas? Para implementar um bucket fill, seria necessário adicionar recursos para entrada de dados na janela. Isso é um pouco diferente do feito em Aprenda Programação: Entrada em Console (Terminal); logo, para um tópico mais simples, um recurso bucket fill é implementado em Aprofundamentos.
Por outro lado, existem outros algoritmos para preenchimento de formas. Assim, a partir deste tópico, polígonos com contornos e com preenchimentos serão parte dos recursos de desenho de Ideias, Regras, Simulação. De fato, uma das seções criará desenhos simples utilizando as primitivas gráficas.
A seção será o destino final deste tópico. Para chegar até ele, será necessário aprender a preencher círculos e polígonos. Para complementar as primitivas de desenho estudadas até aqui, pode-se também considerar o desenho de elipses.
Canvas em HTML para JavaScript
Como na Introdução de Ideias, Regras, Simulação, JavaScript requer um arquivo HTML auxiliar para a declaração do canvas.
O nome do arquivo HTML é de sua escolha (por exemplo, index.html
).
O exemplo a seguir assume que o arquivo JavaScript terá nome script.js
e que o canvas terá identificador (id
) canvas
.
Caso você altere os valores, lembre-se de modificá-los nos arquivos conforme necessário.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>www.FrancoGarcia.com</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div style="text-align: center;">
<canvas id="canvas"
width="1"
height="1"
style="border: 1px solid #000000;">
>
Acessibilidade: texto alternativo para conteúdo gráfico.
</canvas>
</div>
<!-- NOTA Atualizar com nome do arquivo JavaScript. -->
<script src="./script.js"></script>
</body>
</html>
O arquivo também mantém o lembrete sobre acessibilidade. Neste tópico, o conteúdo tornar-se-á ainda mais inacessível para pessoas com certos tipos de deficiência visual, devido a adição de conteúdo gráfico sem opções alternativas para comunicação do conteúdo.
No futuro, a intenção é abordar formas de tornar simulações mais acessíveis. Neste momento, este lembrete é apenas informativo, para conscientizar você da importância de acessibilidade.
Círculos
Círculos foram comentados rapidamente em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos). Na ocasião, criou-se círculos usando arcos. Ou seja, obteve-se apenas os contornos.
As próximas subseções apresentam como preencher círculos.
Preenchendo Círculos com Midpoint Circle Algorithm
Uma forma simples de preencher um círculo consiste em modificar o Midpoint Circle Algorithm implementado em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos).
Ao invés de desenhar os pontos (pixels) da extremidade, basta desenhar uma linha (segmento de reta) para conectar os pontos com mesma ordenada (valor de y
).
Ou seja, em franco_draw_circle()
, basta trocar a implementação original, que desenha um pixel por vez usando franco_draw_pixel()
com:
# 0 - 44
franco_draw_pixel(center_x + x, center_y - y, color)
# 45 - 89
franco_draw_pixel(center_x + y, center_y - x, color)
# 90 - 134
franco_draw_pixel(center_x - y, center_y - x, color)
# 135 - 179
franco_draw_pixel(center_x - x, center_y - y, color)
# 180 - 224
franco_draw_pixel(center_x - x, center_y + y, color)
# 225 - 269
franco_draw_pixel(center_x - y, center_y + x, color)
# 270 - 314
franco_draw_pixel(center_x + y, center_y + x, color)
# 315 - 359
franco_draw_pixel(center_x + x, center_y + y, color)
Para o desenho de uma linha conectando os pares com mesmo valor de y
usando franco_draw_line()
:
franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)
Os próximos blocos realizam as alterações.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func franco_draw_circle(center_x, center_y, radius, color):
var x = radius
var y = 0
var e = 3 - 2 * radius
while (x >= y):
franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)
if (e > 0):
# e = e + 2 * (5 - 2x + 2y)
e += 10 + 4 * (-x + y)
x -= 1
else:
# e = e + 2 * (3 + 2 * y)
e += 6 + 4 * y
y += 1
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function franco_draw_circle(center_x, center_y, radius, color) {
let x = radius
let y = 0
let e = 3 - 2 * radius
while (x >= y) {
franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)
if (e > 0) {
// e = e + 2 * (5 - 2x + 2y)
e += 10 + 4 * (-x + y)
--x
} else {
// e = e + 2 * (3 + 2 * y)
e += 6 + 4 * y
}
++y
}
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def franco_draw_circle(center_x, center_y, radius, color):
x = radius
y = 0
e = 3 - 2 * radius
while (x >= y):
franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)
if (e > 0):
# e = e + 2 * (5 - 2x + 2y)
e += 10 + 4 * (-x + y)
x -= 1
else:
# e = e + 2 * (3 + 2 * y)
e += 6 + 4 * y
y += 1
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
function franco_draw_circle(center_x, center_y, radius, color)
local x = radius
local y = 0
local e = 3 - 2 * radius
while (x >= y) do
franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)
if (e > 0) then
-- e = e + 2 * (5 - 2x + 2y)
e = e + 10 + 4 * (-x + y)
x = x - 1
else
-- e = e + 2 * (3 + 2 * y)
e = e + 6 + 4 * y
end
y = y + 1
end
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
end
O canvas
a seguir apresenta o resultado.
Nas versões em JavaScript com canvas
e Lua com LÖVE (Love2D), é possível notar que algumas das linhas são mais escuras que outras.
Isso ocorre porque elas foram desenhadas mais de uma vez.
Colorindo Círculos com Primitivas Gráficas
Interfaces de Programação de Aplicações (Application Programming Interfaces ou APIs) gráficas comumente fornecem uma primitiva para o desenho de círculos.
De fato, GDScript fornece draw_circle()
, JavaScript com canvas
permite colorir um arco, Python com PyGame possui pygame.draw.circle()
, e Lua com LÖVE provê love.graphics.circle()
.
Assim, elas serão usadas nos próximos exemplos ao invés de uma implementação própria.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
func franco_draw_circle(center_x, center_y, radius, color):
draw_circle(Vector2(center_x, center_y), radius, color)
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_circle(center_x, center_y, radius, color) {
context.fillStyle = color
context.beginPath()
context.arc(center_x, center_y,
radius,
0, 2 * Math.PI)
context.fill()
context.closePath()
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_circle(center_x, center_y, radius, color):
pygame.draw.circle(window, color, (center_x, center_y), radius)
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
function franco_draw_circle(center_x, center_y, radius, color)
love.graphics.setColor(color)
love.graphics.circle("fill", center_x, center_y, radius)
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
end
O canvas
a seguir apresenta o resultado.
Convém notar que a primitiva fornecida por PyGame para Python desenha o círculo completo.
Ou seja, ela não possui a limitação que o desenho de arcos usando pygame.gfxdraw.arc()
possuía.
Elipses
Para uma generalização de círculos, pode-se desenhar elipses. Uma elipse pode ser visualizada como um círculo deformado, que resulta em uma forma oval. Contudo, na realidade, um círculo é um caso particular de elipse.
A entrada da Wikipédia fornece a equações para o Plano Cartesiano e coordenas polares. Contudo, é hora de introduzir uma abordagem mais científica.
Desenhando Contornos de Elipses
Acadêmicos produzem conhecimento científico, que é comumente publicado como artigos em meios como revistas, periódicos, ou websites pessoais. Procurar por trabalhos de professoras ou professor, pesquisadores ou pesquisadores é uma boa opção para encontrar solução para problemas.
Para ilustrar o potencial da abordagem, a implementação do desenho do contorno de elipses seguirá o algoritmo de John Kennedy. O documento ilustra um exemplo de texto em formato de artigo; quando um artigo possui um algoritmo, é fácil segui-lo mesmo quando não se é uma ou um cientista.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const BLUE = Color.blue
func franco_draw_pixel(x, y, color):
draw_primitive(PoolVector2Array([Vector2(x, y)]),
PoolColorArray([color]),
PoolVector2Array())
func plot_4_ellipse_points(center_x, center_y, x, y, color):
franco_draw_pixel(center_x + x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y - y, color)
franco_draw_pixel(center_x + x, center_y - y, color)
func franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
var two_a_square = 2 * x_radius * x_radius
var two_b_square = 2 * y_radius * y_radius
var x = x_radius
var y = 0
var x_change = y_radius * y_radius * (1 - 2 * x_radius)
var y_change = x_radius * x_radius
var ellipse_error = 0
var stopping_x = two_b_square * x_radius
var stopping_y = 0
while (stopping_x >= stopping_y):
plot_4_ellipse_points(center_x, center_y, x, y, color)
y += 1
stopping_y += two_a_square
ellipse_error += y_change
y_change += two_a_square
if ((2 * ellipse_error + x_change) > 0):
x -= 1
stopping_x -= two_b_square
ellipse_error += x_change
x_change += two_b_square
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y):
plot_4_ellipse_points(center_x, center_y, x, y, color)
x += 1
stopping_x += two_b_square
ellipse_error += x_change
x_change += two_b_square
if ((2 * ellipse_error + y_change) > 0):
y -= 1
stopping_y -= two_a_square
ellipse_error += y_change
y_change += two_a_square
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const BLUE = "blue"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_pixel(x, y, color) {
context.fillStyle = color
context.fillRect(x, y, 1, 1)
}
function plot_4_ellipse_points(center_x, center_y, x, y, color) {
franco_draw_pixel(center_x + x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y - y, color)
franco_draw_pixel(center_x + x, center_y - y, color)
}
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color) {
let two_a_square = 2 * x_radius * x_radius
let two_b_square = 2 * y_radius * y_radius
let x = x_radius
let y = 0
let x_change = y_radius * y_radius * (1 - 2 * x_radius)
let y_change = x_radius * x_radius
let ellipse_error = 0
let stopping_x = two_b_square * x_radius
let stopping_y = 0
while (stopping_x >= stopping_y) {
plot_4_ellipse_points(center_x, center_y, x, y, color)
++y
stopping_y += two_a_square
ellipse_error += y_change
y_change += two_a_square
if ((2 * ellipse_error + x_change) > 0) {
--x
stopping_x -= two_b_square
ellipse_error += x_change
x_change += two_b_square
}
}
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y) {
plot_4_ellipse_points(center_x, center_y, x, y, color)
++x
stopping_x += two_b_square
ellipse_error += x_change
x_change += two_b_square
if ((2 * ellipse_error + y_change) > 0) {
--y
stopping_y -= two_a_square
ellipse_error += y_change
y_change += two_a_square
}
}
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
BLUE: Final = pygame.Color("blue")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_pixel(x, y, color):
window.set_at((x, y), color)
def plot_4_ellipse_points(center_x, center_y, x, y, color):
franco_draw_pixel(center_x + x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y - y, color)
franco_draw_pixel(center_x + x, center_y - y, color)
def franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
two_a_square = 2 * x_radius * x_radius
two_b_square = 2 * y_radius * y_radius
x = x_radius
y = 0
x_change = y_radius * y_radius * (1 - 2 * x_radius)
y_change = x_radius * x_radius
ellipse_error = 0
stopping_x = two_b_square * x_radius
stopping_y = 0
while (stopping_x >= stopping_y):
plot_4_ellipse_points(center_x, center_y, x, y, color)
y += 1
stopping_y += two_a_square
ellipse_error += y_change
y_change += two_a_square
if ((2 * ellipse_error + x_change) > 0):
x -= 1
stopping_x -= two_b_square
ellipse_error += x_change
x_change += two_b_square
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y):
plot_4_ellipse_points(center_x, center_y, x, y, color)
x += 1
stopping_x += two_b_square
ellipse_error += x_change
x_change += two_b_square
if ((2 * ellipse_error + y_change) > 0):
y -= 1
stopping_y -= two_a_square
ellipse_error += y_change
y_change += two_a_square
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
function franco_draw_pixel(x, y, color)
love.graphics.setColor(color)
love.graphics.points(x, y)
end
function plot_4_ellipse_points(center_x, center_y, x, y, color)
franco_draw_pixel(center_x + x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y - y, color)
franco_draw_pixel(center_x + x, center_y - y, color)
end
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color)
local two_a_square = 2 * x_radius * x_radius
local two_b_square = 2 * y_radius * y_radius
local x = x_radius
local y = 0
local x_change = y_radius * y_radius * (1 - 2 * x_radius)
local y_change = x_radius * x_radius
local ellipse_error = 0
local stopping_x = two_b_square * x_radius
local stopping_y = 0
while (stopping_x >= stopping_y) do
plot_4_ellipse_points(center_x, center_y, x, y, color)
y = y + 1
stopping_y = stopping_y + two_a_square
ellipse_error = ellipse_error + y_change
y_change = y_change + two_a_square
if ((2 * ellipse_error + x_change) > 0) then
x = x - 1
stopping_x = stopping_x - two_b_square
ellipse_error = ellipse_error + x_change
x_change = x_change + two_b_square
end
end
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y) do
plot_4_ellipse_points(center_x, center_y, x, y, color)
x = x + 1
stopping_x = stopping_x + two_b_square
ellipse_error = ellipse_error + x_change
x_change = x_change + two_b_square
if ((2 * ellipse_error + y_change) > 0) then
y = y - 1
stopping_y = stopping_y - two_a_square
ellipse_error = ellipse_error + y_change
y_change = y_change + two_a_square
end
end
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
end
O canvas
a seguir apresenta o resultado.
O algoritmo é similar ao de Bresenham para o desenho de círculos.
É interessante notar que uma elipse cujos dois raios (x_radius
e y_radius
) possuam a mesma medida forma um círculo.
De fato, um círculo é um caso particular de elipse.
Preenchendo Elipses
Como o algoritmo de Kennedy é similar ao de Bresenham, a implementação de preenchimento de elipses pode explorar a mesma estratégia usada para círculos: basta desenhar retas para pontos com mesmas ordenadas (valor de y
).
Ou seja, basta modificar plot_4_ellipse_lines()
que usava franco_draw_pixel()
:
func franco_draw_pixel(x, y, color):
draw_primitive(PoolVector2Array([Vector2(x, y)]),
PoolColorArray([color]),
PoolVector2Array())
func plot_4_ellipse_points(center_x, center_y, x, y, color):
franco_draw_pixel(center_x + x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y + y, color)
franco_draw_pixel(center_x - x, center_y - y, color)
franco_draw_pixel(center_x + x, center_y - y, color)
Para um novo plot_2_ellipse_lines()
que usará franco_draw_line()
para concluir a implementação.
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func plot_2_ellipse_lines(center_x, center_y, x, y, color):
franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
Em seguida, bastará atualizar as duas chamadas em franco_draw_ellipse()
.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const BLUE = Color.blue
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func plot_2_ellipse_lines(center_x, center_y, x, y, color):
franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
func franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
var two_a_square = 2 * x_radius * x_radius
var two_b_square = 2 * y_radius * y_radius
var x = x_radius
var y = 0
var x_change = y_radius * y_radius * (1 - 2 * x_radius)
var y_change = x_radius * x_radius
var ellipse_error = 0
var stopping_x = two_b_square * x_radius
var stopping_y = 0
while (stopping_x >= stopping_y):
plot_2_ellipse_lines(center_x, center_y, x, y, color)
y += 1
stopping_y += two_a_square
ellipse_error += y_change
y_change += two_a_square
if ((2 * ellipse_error + x_change) > 0):
x -= 1
stopping_x -= two_b_square
ellipse_error += x_change
x_change += two_b_square
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y):
plot_2_ellipse_lines(center_x, center_y, x, y, color)
x += 1
stopping_x += two_b_square
ellipse_error += x_change
x_change += two_b_square
if ((2 * ellipse_error + y_change) > 0):
y -= 1
stopping_y -= two_a_square
ellipse_error += y_change
y_change += two_a_square
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const BLUE = "blue"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function plot_2_ellipse_lines(center_x, center_y, x, y, color) {
franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
}
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color) {
let two_a_square = 2 * x_radius * x_radius
let two_b_square = 2 * y_radius * y_radius
let x = x_radius
let y = 0
let x_change = y_radius * y_radius * (1 - 2 * x_radius)
let y_change = x_radius * x_radius
let ellipse_error = 0
let stopping_x = two_b_square * x_radius
let stopping_y = 0
while (stopping_x >= stopping_y) {
plot_2_ellipse_lines(center_x, center_y, x, y, color)
++y
stopping_y += two_a_square
ellipse_error += y_change
y_change += two_a_square
if ((2 * ellipse_error + x_change) > 0) {
--x
stopping_x -= two_b_square
ellipse_error += x_change
x_change += two_b_square
}
}
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y) {
plot_2_ellipse_lines(center_x, center_y, x, y, color)
++x
stopping_x += two_b_square
ellipse_error += x_change
x_change += two_b_square
if ((2 * ellipse_error + y_change) > 0) {
--y
stopping_y -= two_a_square
ellipse_error += y_change
y_change += two_a_square
}
}
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
BLUE: Final = pygame.Color("blue")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def plot_2_ellipse_lines(center_x, center_y, x, y, color):
franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
def franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
two_a_square = 2 * x_radius * x_radius
two_b_square = 2 * y_radius * y_radius
x = x_radius
y = 0
x_change = y_radius * y_radius * (1 - 2 * x_radius)
y_change = x_radius * x_radius
ellipse_error = 0
stopping_x = two_b_square * x_radius
stopping_y = 0
while (stopping_x >= stopping_y):
plot_2_ellipse_lines(center_x, center_y, x, y, color)
y += 1
stopping_y += two_a_square
ellipse_error += y_change
y_change += two_a_square
if ((2 * ellipse_error + x_change) > 0):
x -= 1
stopping_x -= two_b_square
ellipse_error += x_change
x_change += two_b_square
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y):
plot_2_ellipse_lines(center_x, center_y, x, y, color)
x += 1
stopping_x += two_b_square
ellipse_error += x_change
x_change += two_b_square
if ((2 * ellipse_error + y_change) > 0):
y -= 1
stopping_y -= two_a_square
ellipse_error += y_change
y_change += two_a_square
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
function plot_2_ellipse_lines(center_x, center_y, x, y, color)
franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
end
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color)
local two_a_square = 2 * x_radius * x_radius
local two_b_square = 2 * y_radius * y_radius
local x = x_radius
local y = 0
local x_change = y_radius * y_radius * (1 - 2 * x_radius)
local y_change = x_radius * x_radius
local ellipse_error = 0
local stopping_x = two_b_square * x_radius
local stopping_y = 0
while (stopping_x >= stopping_y) do
plot_2_ellipse_lines(center_x, center_y, x, y, color)
y = y + 1
stopping_y = stopping_y + two_a_square
ellipse_error = ellipse_error + y_change
y_change = y_change + two_a_square
if ((2 * ellipse_error + x_change) > 0) then
x = x - 1
stopping_x = stopping_x - two_b_square
ellipse_error = ellipse_error + x_change
x_change = x_change + two_b_square
end
end
x = 0
y = y_radius
x_change = y_radius * y_radius
y_change = x_radius * x_radius * (1 - 2 * y_radius)
ellipse_error = 0
stopping_x = 0
stopping_y = two_a_square * y_radius
while (stopping_x <= stopping_y) do
plot_2_ellipse_lines(center_x, center_y, x, y, color)
x = x + 1
stopping_x = stopping_x + two_b_square
ellipse_error = ellipse_error + x_change
x_change = x_change + two_b_square
if ((2 * ellipse_error + y_change) > 0) then
y = y - 1
stopping_y = stopping_y - two_a_square
ellipse_error = ellipse_error + y_change
y_change = y_change + two_a_square
end
end
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE)
franco_draw_ellipse(160, 120, 60, 40, RED)
franco_draw_ellipse(160, 120, 30, 30, WHITE)
end
O canvas
a seguir apresenta o resultado.
A abordagem ilustra como é possível reaproveitar o conhecimento obtido de soluções anteriores para a resolução de novos problemas. Quanto maior seu repertório de problemas resolvidos, maior será o conhecimento que você terá para resolver novos problemas. Em outras palavras, resolver problemas cada vez mais complexos é uma excelente forma de tornar-se melhor em resolver problemas, e, conseqüentemente, em programar. Em suma, resolva problemas para resolver problemas.
Primitivas para Desenho de Elipses
JavaScript com canvas
fornece ellipse()
para o desenho de elipses.
Python com PyGame possui pygame.draw.ellipse()
para uma elipse em definida por retângulo, ou pygame.gfxdraw.ellipse()
e pygame.gfxdraw.filled_ellipse()
para uma elipse definida por raios.
Por simplicidade, a implementação escolherá a versão com raios.
Lua com LÖVE provê love.graphics.ellipse()
.
GDScript não fornece uma primitiva para desenho de elipses. Uma alternativa usando apenas recursos existentes consiste em aplicar uma matriz de transformação para modificar o círculo, como sugerido nesta resposta. O exemplo apresenta uma generalização da resposta, adaptando a equação de um círculo para uma equação de elipse.
Com alguns ajustes na equação do círculo, é possível observar que um círculo é uma elipse com .
Assim, a ideia é desenhar um círculo de raio unitário com centro na origem. A matriz de transformação aplicará uma operação de escala (para ajustar os tamanhos dos raios) e de translação (para a posição de centro desejada para a elipse), criando a elipse desejada. Ou seja, a elipse será criada como uma deformação do círculo original.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const BLUE = Color.blue
const POINT_COUNT = 30
func franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = false):
draw_set_transform(Vector2(center_x, center_y), 0, Vector2(x_radius, y_radius))
if (fill):
draw_circle(Vector2(0.0, 0.0), 1.0, color)
else:
draw_arc(Vector2(0.0, 0.0), 1.0, 0.0, 2.0 * PI, POINT_COUNT, color)
draw_set_transform(Vector2(0.0, 0.0), 0, Vector2(1.0, 1.0))
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE, true)
franco_draw_ellipse(160, 120, 60, 40, RED, true)
franco_draw_ellipse(160, 120, 30, 30, WHITE, true)
franco_draw_ellipse(160, 120, 30, 20, BLUE)
franco_draw_ellipse(160, 120, 15, 10, RED)
franco_draw_ellipse(160, 120, 7, 7, BLACK)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const BLUE = "blue"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = false) {
if (fill) {
context.fillStyle = color
} else {
context.strokeStyle = color
}
context.beginPath()
context.ellipse(center_x, center_y, x_radius, y_radius, 0.0, 0.0, 2.0 * Math.PI)
context.closePath()
if (fill) {
context.fill()
} else {
context.stroke()
}
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_ellipse(160, 120, 120, 80, BLUE, true)
franco_draw_ellipse(160, 120, 60, 40, RED, true)
franco_draw_ellipse(160, 120, 30, 30, WHITE, true)
franco_draw_ellipse(160, 120, 30, 20, BLUE)
franco_draw_ellipse(160, 120, 15, 10, RED)
franco_draw_ellipse(160, 120, 7, 7, BLACK)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import pygame.gfxdraw
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
BLUE: Final = pygame.Color("blue")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = False):
if (fill):
pygame.gfxdraw.filled_ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)
else:
pygame.gfxdraw.ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE, True)
franco_draw_ellipse(160, 120, 60, 40, RED, True)
franco_draw_ellipse(160, 120, 30, 30, WHITE, True)
franco_draw_ellipse(160, 120, 30, 20, BLUE)
franco_draw_ellipse(160, 120, 15, 10, RED)
franco_draw_ellipse(160, 120, 7, 7, BLACK)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local ELLIPSE_SEGMENTS = nil -- 30
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill)
local fill_string = "line"
if (fill) then
fill_string = "fill"
end
love.graphics.setColor(color)
love.graphics.ellipse(fill_string, center_x, center_y, x_radius, y_radius, ELLIPSE_SEGMENTS)
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_ellipse(160, 120, 120, 80, BLUE, true)
franco_draw_ellipse(160, 120, 60, 40, RED, true)
franco_draw_ellipse(160, 120, 30, 30, WHITE, true)
franco_draw_ellipse(160, 120, 30, 20, BLUE)
franco_draw_ellipse(160, 120, 15, 10, RED)
franco_draw_ellipse(160, 120, 7, 7, BLACK)
end
O canvas
a seguir apresenta o resultado.
A implementação define o parâmetro fill
como um valor lógico (booleano) para fornecer a opção de desenhar apenas o contorno (caso falso) ou preencher a elipse (caso verdadeiro).
Isso evita duplicar implementações.
Caso se optasse por dois procedimentos, um deles poderia chamar-se franco_draw_ellipse()
, franco_stroke_ellipse()
ou franco_outline_ellipse()
para o contorno; o outro poderia chamar-se franco_fill_ellipse()
para preenchimentos.
Polígonos
Pode-se desenhar polígonos como seqüencias de linhas, com feito para o desenho da coroa na simulação do lançamentos de moedas e dados.
Desenhando Contornos de Polígonos
Para generalizar o processo, é possível usar Listas (Lists) ou Vetores (Arrays) para armazenar vários valores de pontos, que serão conectadas com retas para formar o polígono. O uso de vetores e outras coleções em Ideias, Regras, Simulação será detalhado em tópicos futuros; para este momento, basta saber que uma variável que instancie um vetor pode armazenar vários valores, e cada um desses valores pode ser acessado usando um índice.
Em GDScript, Python e JavaScript, um vetor com tamanho
elementos possui o primeiro elemento na posição 0 e o último na posição tamanho - 1
.
Em Lua, o primeiro elemento está na posição 1 e o último está na posição tamanho
.
Assim, as implementações manipulam índices de forma um pouco diferente.
Em ambos os casos, o acesso ao elemento na posição indice
do vetor é feita usando-se nome_vetor[indice]
.
Assim, por exemplo, nome_vetor[2]
acessaria o terceiro valor armazenado em GDScript, Python e JavaScript.
Em Lua, nome_vetor[2]
acessaria o segundo valor armazenado no vetor.
Por fim, GDScript, Python e JavaScript usam colchetes para a declaração de vetores (ou listas, dependendo da linguagem).
Lua utiliza chaves (mais especificamente, Lua define uma tabela ou table
otimizada para o vetor).
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func franco_draw_polygon(points, color):
assert(points.size() >= 2)
var length = points.size()
for index in range(0, length - 1):
var current_point = points[index]
var next_point = points[index + 1]
franco_draw_line(current_point[0], current_point[1],
next_point[0], next_point[1],
color)
franco_draw_line(points[0][0], points[0][1],
points[length - 1][0], points[length - 1][1],
color)
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
# Triângulo
franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)
# Retângulo
franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)
# Losango
franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
[160, 10], [150, 70], [170, 90], [140, 230],
[180, 200], [210, 210], [250, 190], [300, 120],
[260, 80], [260, 10]
], BLUE)
# F
franco_draw_polygon([
[10, 105], [10, 220], [30, 220],
[30, 170], [60, 170], [60, 150],
[30, 150], [30, 130], [70, 130],
[70, 105],
], YELLOW)
# Estrela
franco_draw_polygon([
[80, 180], [50, 230], [120, 200],
[40, 200], [110, 230],
], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function franco_draw_polygon(points, color) {
console.assert(points.length >= 2)
let length = points.length
for (let index = 0; index < length - 1; ++index) {
let current_point = points[index]
let next_point = points[index + 1]
franco_draw_line(current_point[0], current_point[1],
next_point[0], next_point[1],
color)
}
franco_draw_line(points[0][0], points[0][1],
points[length - 1][0], points[length - 1][1],
color)
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
// Triângulo
franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)
// Retângulo
franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)
// Losango
franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)
// Polígono com pontos sortidos
franco_draw_polygon([
[160, 10], [150, 70], [170, 90], [140, 230],
[180, 200], [210, 210], [250, 190], [300, 120],
[260, 80], [260, 10]
], BLUE)
// F
franco_draw_polygon([
[10, 105], [10, 220], [30, 220],
[30, 170], [60, 170], [60, 150],
[30, 150], [30, 130], [70, 130],
[70, 105],
], YELLOW)
// Estrela
franco_draw_polygon([
[80, 180], [50, 230], [120, 200],
[40, 200], [110, 230],
], MAGENTA)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def franco_draw_polygon(points, color):
assert(len(points) >= 2)
length = len(points)
for index in range(0, length - 1):
current_point = points[index]
next_point = points[index + 1]
franco_draw_line(current_point[0], current_point[1],
next_point[0], next_point[1],
color)
franco_draw_line(points[0][0], points[0][1],
points[length - 1][0], points[length - 1][1],
color)
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
# Triângulo
franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)
# Retângulo
franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)
# Losango
franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
[160, 10], [150, 70], [170, 90], [140, 230],
[180, 200], [210, 210], [250, 190], [300, 120],
[260, 80], [260, 10]
], BLUE)
# F
franco_draw_polygon([
[10, 105], [10, 220], [30, 220],
[30, 170], [60, 170], [60, 150],
[30, 150], [30, 130], [70, 130],
[70, 105],
], YELLOW)
# Estrela
franco_draw_polygon([
[80, 180], [50, 230], [120, 200],
[40, 200], [110, 230],
], MAGENTA)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
function franco_draw_polygon(points, color)
assert(#points >= 2)
local length = #points
for index = 1, length - 1 do
local current_point = points[index]
local next_point = points[index + 1]
franco_draw_line(current_point[1], current_point[2],
next_point[1], next_point[2],
color)
end
franco_draw_line(points[1][1], points[1][2],
points[length][1], points[length][2],
color)
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
-- Triângulo
franco_draw_polygon({{10, 40}, {40, 40}, {40, 10}}, WHITE)
-- Retângulo
franco_draw_polygon({{50, 10}, {50, 80}, {140, 80}, {140, 10}}, RED)
-- Losango
franco_draw_polygon({{130, 100}, {150, 160}, {100, 160}, {80, 100}}, GREEN)
-- Polígono com pontos sortidos
franco_draw_polygon({
{160, 10}, {150, 70}, {170, 90}, {140, 230},
{180, 200}, {210, 210}, {250, 190}, {300, 120},
{260, 80}, {260, 10}
}, BLUE)
-- F
franco_draw_polygon({
{10, 105}, {10, 220}, {30, 220},
{30, 170}, {60, 170}, {60, 150},
{30, 150}, {30, 130}, {70, 130},
{70, 105},
}, YELLOW)
-- Estrela
franco_draw_polygon({
{80, 180}, {50, 230}, {120, 200},
{40, 200}, {110, 230},
}, MAGENTA)
end
O canvas
a seguir apresenta o resultado.
Simplificadamente, a estrutura de repetição em franco_draw_polygon()
desenha linhas conectando pares de pontos em posições contíguas (uma ao lado da outra).
O primeiro ponto é conectado ao segundo, o segundo ao terceiro, e assim por diante.
O laço termina no penúltimo ponto, que é conectado ao último.
A chamada de franco_draw_line()
fora do laço conecta o primeiro ponto ao último, para conectar o desenho.
Assim, para o uso correto do procedimento, deve-se fornecer ao menos dois pontos para a chamada (de preferência distintos, para o desenho de uma linha).
O uso de assert()
verifica o tamanho; caso o vetor tenha menos de dois valores, a chamada falha e apresenta um erro na versão de desenvolvimento.
Isso é chamado de asserção; asserções foram previamente comentadas em Aprenda Programação: Subrotinas (Funções e Procedimentos) e Aprenda Programação: Registros.
Uma asserção não serve para tratamento de erros em programas fornecidos para usuários finais; ela serve para informar o programador ou a programadora que ele ou ela cometeu um erro de implementação (que deve ser corrigido).
Convém notar que, dependendo dos pontos escolhidos, uma reta pode atravessar o desenho. A rigor, isso não formaria um polígono convexo. Em um polígono convexo, todos os ângulos internos são menores que 180°. Além de convexos, polígonos podem ser côncavos ou complexos. Um polígono côncavo pode ter ângulos internos maiores que 180°. A categoria de polígonos complexos inclui as outras duas. Arestas que cruzam o próprio polígono formariam um polígono complexo (mais especificamente, um self-intersecting polygon).
No exemplo criado, o triângulo, o retângulo e o losango são polígonos convexos. A letra F amarela e o polígono azul são côncavos. A estrela magenta é um polígono complexo.
Para usar franco_draw_polygon()
, a chamada define um vetor de vetores.
Cada ponto (par ordenado) é passado como um vetor.
Refatoração para Abstração de Dados: Criando um Tipo de Dados para Pontos
Para uma solução mais limpa, também seria possível definir um Registros (Structs ou Records) para definir um tipo ponto (Point
).
Embora GDScript, JavaScript e Python permitam criar classes do paradigma da Programação Orientada a Objetos (ou POO, do inglês, Object-Oriented Programming ou OOP), este tópico assumirá o uso de registros, como definidos em Aprenda Programação: Registros: tipos compostos para armazenamento de dados heterogêneos.
OOP será abordado no futuro, quando os tipos de dados requerem processamento mais complexo.
Um registro é um tipo criado pela programador ou pelo programador da aplicação. Ou seja, além de tipos fornecidos pela linguagem de programação, agora você poderá criar seu próprio.
O intuito é criar um novo tipo de dados que armazene dois valores: um valor inteiro ou real para x
, e um valor inteiro ou real para y
para formar um ponto bidimensional (2D).
Em alto nível, um registro Ponto
poderia ser descrito como a seguir na forma de pseudocódigo:
registro Ponto
x: real
y: real
fim
var ponto_a: Ponto
ponto_a.x = 10
ponto_a.y = 20
var ponto_b: Ponto
ponto_b.x = 20
ponto_b.y = 10
var delta_x: real
delta_x = ponto_a.x - ponto_b.x
Ou seja, a declaração de uma variável do tipo Ponto
criaria uma instância de um registro com duas variáveis (x
e y
) para as coordenadas.
Os próximos exemplos refatoram o código da seção anterior para incorporar um registro.
Assim, ao invés de vetores com pares de valores para cada coordenada do polígono, um polígono poderá ser composto por um vetor de variáveis do tipo Ponto
.
Por exemplo, ao invés de [10, 40]
, poder-se-ia criar um Ponto
com x
igual a 10 e y
igual a 40, e usá-lo como alternativa equivalente.
Para manter a nomenclatura em Inglês, Ponto
será traduzido para Point
.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta
class Point:
var x
var y
func _init(_x, _y):
self.x = _x
self.y = _y
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func franco_draw_polygon(points, color):
assert(points.size() >= 2)
var length = points.size()
for index in range(0, length - 1):
var current_point = points[index]
var next_point = points[index + 1]
franco_draw_line(current_point.x, current_point.y,
next_point.x, next_point.y,
color)
franco_draw_line(points[0].x, points[0].y,
points[length - 1].x, points[length - 1].y,
color)
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
# Triângulo
franco_draw_polygon([
Point.new(10, 40),
Point.new(40, 40),
Point.new(40, 10)
], WHITE)
# Retângulo
franco_draw_polygon([
Point.new(50, 10),
Point.new(50, 80),
Point.new(140, 80),
Point.new(140, 10),
Point.new(50, 10)
], RED)
# Losango
franco_draw_polygon([
Point.new(130, 100),
Point.new(150, 160),
Point.new(100, 160),
Point.new(80, 100)
], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
Point.new(160, 10), Point.new(150, 70), Point.new(170, 90), Point.new(140, 230),
Point.new(180, 200), Point.new(210, 210), Point.new(250, 190), Point.new(300, 120),
Point.new(260, 80), Point.new(260, 10), Point.new(160, 10)
], BLUE)
# F
franco_draw_polygon([
Point.new(10, 105), Point.new(10, 220), Point.new(30, 220),
Point.new(30, 170), Point.new(60, 170), Point.new(60, 150),
Point.new(30, 150), Point.new(30, 130), Point.new(70, 130),
Point.new(70, 105), Point.new(10, 105)
], YELLOW)
# Estrela
franco_draw_polygon([
Point.new(80, 180), Point.new(50, 230), Point.new(120, 200),
Point.new(40, 200), Point.new(110, 230), Point.new(80, 180)
], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function franco_draw_polygon(points, color) {
console.assert(points.length >= 2)
let length = points.length
for (let index = 0; index < length - 1; ++index) {
let current_point = points[index]
let next_point = points[index + 1]
franco_draw_line(current_point.x, current_point.y,
next_point.x, next_point.y,
color)
}
franco_draw_line(points[0].x, points[0].y,
points[length - 1].x, points[length - 1].y,
color)
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
// Triângulo
franco_draw_polygon([
new Point(10, 40),
new Point(40, 40),
new Point(40, 10)
], WHITE)
// Retângulo
franco_draw_polygon([
new Point(50, 10),
new Point(50, 80),
new Point(140, 80),
new Point(140, 10)
], RED)
// Losango
franco_draw_polygon([
new Point(130, 100),
new Point(150, 160),
new Point(100, 160),
new Point(80, 100)
], GREEN)
// Polígono com pontos sortidos
franco_draw_polygon([
new Point(160, 10), new Point(150, 70), new Point(170, 90), new Point(140, 230),
new Point(180, 200), new Point(210, 210), new Point(250, 190), new Point(300, 120),
new Point(260, 80), new Point(260, 10)
], BLUE)
// F
franco_draw_polygon([
new Point(10, 105), new Point(10, 220), new Point(30, 220),
new Point(30, 170), new Point(60, 170), new Point(60, 150),
new Point(30, 150), new Point(30, 130), new Point(70, 130),
new Point(70, 105),
], YELLOW)
// Estrela
franco_draw_polygon([
new Point(80, 180), new Point(50, 230), new Point(120, 200),
new Point(40, 200), new Point(110, 230),
], MAGENTA)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def franco_draw_polygon(points, color):
assert(len(points) >= 2)
length = len(points)
for index in range(0, length - 1):
current_point = points[index]
next_point = points[index + 1]
franco_draw_line(current_point.x, current_point.y,
next_point.x, next_point.y,
color)
franco_draw_line(points[0].x, points[0].y,
points[length - 1].x, points[length - 1].y,
color)
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
# Triângulo
franco_draw_polygon([
Point(10, 40),
Point(40, 40),
Point(40, 10)
], WHITE)
# Retângulo
franco_draw_polygon([
Point(50, 10),
Point(50, 80),
Point(140, 80),
Point(140, 10),
Point(50, 10)
], RED)
# Losango
franco_draw_polygon([
Point(130, 100),
Point(150, 160),
Point(100, 160),
Point(80, 100)
], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
Point(260, 80), Point(260, 10), Point(160, 10)
], BLUE)
# F
franco_draw_polygon([
Point(10, 105), Point(10, 220), Point(30, 220),
Point(30, 170), Point(60, 170), Point(60, 150),
Point(30, 150), Point(30, 130), Point(70, 130),
Point(70, 105), Point(10, 105)
], YELLOW)
# Estrela
franco_draw_polygon([
Point(80, 180), Point(50, 230), Point(120, 200),
Point(40, 200), Point(110, 230), Point(80, 180)
], MAGENTA)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}
function Point(x, y)
return {
x = x,
y = y
}
end
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
function franco_draw_polygon(points, color)
assert(#points >= 2)
local length = #points
for index = 1, length - 1 do
local current_point = points[index]
local next_point = points[index + 1]
franco_draw_line(current_point.x, current_point.y,
next_point.x, next_point.y,
color)
end
franco_draw_line(points[1].x, points[1].y,
points[length].x, points[length].y,
color)
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
-- Triângulo
franco_draw_polygon({
Point(10, 40),
Point(40, 40),
Point(40, 10)
}, WHITE)
-- Retângulo
franco_draw_polygon({
Point(50, 10),
Point(50, 80),
Point(140, 80),
Point(140, 10),
Point(50, 10)
}, RED)
-- Losango
franco_draw_polygon({
Point(130, 100),
Point(150, 160),
Point(100, 160),
Point(80, 100)
}, GREEN)
-- Polígono com pontos sortidos
franco_draw_polygon({
Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
Point(260, 80), Point(260, 10), Point(160, 10)
}, BLUE)
-- F
franco_draw_polygon({
Point(10, 105), Point(10, 220), Point(30, 220),
Point(30, 170), Point(60, 170), Point(60, 150),
Point(30, 150), Point(30, 130), Point(70, 130),
Point(70, 105), Point(10, 105)
}, YELLOW)
-- Estrela
franco_draw_polygon({
Point(80, 180), Point(50, 230), Point(120, 200),
Point(40, 200), Point(110, 230), Point(80, 180)
}, MAGENTA)
end
O canvas
a seguir apresenta o resultado.
A versão em GDScript usa uma classe interna (inner class).
A versão em Lua utiliza uma table
para evitar introduzir um modelo de OOP arbitrário, já que a linguagem não fornece construções específicas para registros ou classes.
O mesmo poderia ser feito em JavaScript, usando-se JavaScript Object.
Caso se desejasse, também poder-se-ia refatorar franco_draw_line()
para receber um Point
ao invés de dois números como coordenadas.
Fornecer alternativas para uso pode enriquecer uma API, tornando-a mais expressiva. A contrapartida é manter todas as versões atualizadas e funcionais. Uma forma elegante de minimizar o problema consiste em definir uma subrotina de nível mais baixo, e chamá-la por subrotinas similares (para abstração de nível mais alto).
Nessa abordagem, poder-se-ia definir um segundo procedimento que fosse uma variação usando pontos. O próximo exemplo ilustra a abordagem em GDScript.
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func franco_draw_line_from_points(point0, point1, color):
franco_draw_line(point0.x, point0.y, point1.x, point1.y, color)
func franco_draw_polygon(points, color):
assert(points.size() >= 2)
var length = points.size()
for index in range(0, length - 1):
var current_point = points[index]
var next_point = points[index + 1]
franco_draw_line_from_points(current_point, next_point, color)
franco_draw_line_from_points(points[0], points[length - 1], color)
franco_draw_line_from_points()
recebe dois pontos como parâmetros, e chama a função original franco_draw_line()
para o desenho da linha.
A versão modificada de franco_draw_polygon()
chama franco_draw_line_from_points()
para o desenho de cada linha do polígono.
Desde que os parâmetros fossem mantidos, a versão com pontos seria atualizada automaticamente sempre que a versão original fosse modificada, pois tudo que ela faz é chamar a original.
Primitivas para Desenho de Contornos de Polígonos
APIs gráficas comumente fornecem uma subrotina para a criação de polígonos usando uma seqüência de pontos.
GDScript fornece draw_polyline()
, Python com PyGame possui pygame.draw.polygon()
, e Lua com LÖVE provê love.graphics.polygon()
.
JavaScript não provê uma subrotina específica; no caso, deve-se desenhar as linhas individualmente.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta
func franco_draw_polygon(points, color):
assert(points.size() >= 2)
draw_polyline(PoolVector2Array(points), color)
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
# Triângulo
franco_draw_polygon([Vector2(10, 40), Vector2(40, 40), Vector2(40, 10), Vector2(10, 40)], WHITE)
# Retângulo
franco_draw_polygon([Vector2(50, 10), Vector2(50, 80), Vector2(140, 80), Vector2(140, 10), Vector2(50, 10)], RED)
# Losango
franco_draw_polygon([Vector2(130, 100), Vector2(150, 160), Vector2(100, 160), Vector2(80, 100), Vector2(130, 100)], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
Vector2(160, 10), Vector2(150, 70), Vector2(170, 90), Vector2(140, 230),
Vector2(180, 200), Vector2(210, 210), Vector2(250, 190), Vector2(300, 120),
Vector2(260, 80), Vector2(260, 10), Vector2(160, 10)
], BLUE)
# F
franco_draw_polygon([
Vector2(10, 105), Vector2(10, 220), Vector2(30, 220),
Vector2(30, 170), Vector2(60, 170), Vector2(60, 150),
Vector2(30, 150), Vector2(30, 130), Vector2(70, 130),
Vector2(70, 105), Vector2(10, 105)
], YELLOW)
# Estrela
franco_draw_polygon([
Vector2(80, 180), Vector2(50, 230), Vector2(120, 200),
Vector2(40, 200), Vector2(110, 230), Vector2(80, 180)
], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_polygon(points, color) {
console.assert(points.length >= 2)
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(points[0][0], points[0][1])
let length = points.length
for (let index = 1; index < length; ++index) {
let next_point = points[index]
context.lineTo(next_point[0], next_point[1])
}
context.closePath()
context.stroke()
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
// Triângulo
franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)
// Retângulo
franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)
// Losango
franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)
// Polígono com pontos sortidos
franco_draw_polygon([
[160, 10], [150, 70], [170, 90], [140, 230],
[180, 200], [210, 210], [250, 190], [300, 120],
[260, 80], [260, 10]
], BLUE)
// F
franco_draw_polygon([
[10, 105], [10, 220], [30, 220],
[30, 170], [60, 170], [60, 150],
[30, 150], [30, 130], [70, 130],
[70, 105],
], YELLOW)
// Estrela
franco_draw_polygon([
[80, 180], [50, 230], [120, 200],
[40, 200], [110, 230], [80, 180]
], MAGENTA)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_polygon(points, color):
assert(len(points) >= 2)
# 0: preencher polígono; > 0: espessura da linha.
pygame.draw.polygon(window, color, points, 1)
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
# Triângulo
franco_draw_polygon([(10, 40), (40, 40), (40, 10)], WHITE)
# Retângulo
franco_draw_polygon([(50, 10), (50, 80), (140, 80), (140, 10)], RED)
# Losango
franco_draw_polygon([(130, 100), (150, 160), (100, 160), (80, 100)], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
(160, 10), (150, 70), (170, 90), (140, 230),
(180, 200), (210, 210), (250, 190), (300, 120),
(260, 80), (260, 10)
], BLUE)
# F
franco_draw_polygon([
(10, 105), (10, 220), (30, 220),
(30, 170), (60, 170), (60, 150),
(30, 150), (30, 130), (70, 130),
(70, 105),
], YELLOW)
# Estrela
franco_draw_polygon([
(80, 180), (50, 230), (120, 200),
(40, 200), (110, 230), (80, 180)
], MAGENTA)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}
function franco_draw_polygon(points, color)
assert(#points >= 2)
love.graphics.setColor(color)
love.graphics.polygon("line", points)
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
-- Triângulo
franco_draw_polygon({10, 40, 40, 40, 40, 10}, WHITE)
-- Retângulo
franco_draw_polygon({50, 10, 50, 80, 140, 80, 140, 10}, RED)
-- Losango
franco_draw_polygon({130, 100, 150, 160, 100, 160, 80, 100}, GREEN)
-- Polígono com pontos sortidos
franco_draw_polygon({
160, 10, 150, 70, 170, 90, 140, 230,
180, 200, 210, 210, 250, 190, 300, 120,
260, 80, 260, 10
}, BLUE)
-- F
franco_draw_polygon({
10, 105, 10, 220, 30, 220,
30, 170, 60, 170, 60, 150,
30, 150, 30, 130, 70, 130,
70, 105,
}, YELLOW)
-- Estrela
-- NOTA LÖVE não desenha.
franco_draw_polygon({
80, 180, 50, 230, 120, 200,
40, 200, 110, 230, 80, 180
}, MAGENTA)
end
O canvas
a seguir apresenta o resultado.
Como esperado, o resultado é o mesmo. Afinal, trata-se uma refatoração (comumente chamada de extração de classe).
Em GDScript, deve-se repetir o primeiro ponto ao final do vetor usado como parâmetro para que draw_polyline()
complete o contorno polígono.
Em JavaScript, usa-se praticamente a mesma implementação anterior.
O código de franco_draw_line()
foi mesclado em franco_draw_polygon()
.
Em Lua com LÖVE, pode-se passar os pontos com as coordenadas alternadas diretamente na chamada, ou em um vetor (como table
).
Também deve-se notar que love.graphics.polygon()
não desenha polígonos complexos.
Preenchendo Polígonos
Definidos os contornos, é hora de preenchê-los para se criar polígonos coloridos. O algoritmo de preenchimento possivelmente será o mais complexo até este ponto de Ideias, Regras, Simulação. Para implementá-lo, será necessário usar operações de Listas (Lists) ou Vetores (Arrays), como inserção de novos valores e ordenação.
Assim, caso você esteja iniciando suas atividades de programação, pode ser interessante explorar as primitivas pré-definidas. Mais tarde, quando você tiver mais prática, você pode retornar ao algoritmo de preenchimento de polígonos.
Preenchendo Polígonos com Scanline Rendering (Scan-Line Algorithm)
Um dos algoritmos mais simples para preenchimento de polígonos chama-se scanline rendering, também conhecido como scan-line algorithm. Uma boa descrição pode ser encontrada nesta página (em Inglês).
A implementação deste tópico será um pouco mais simples e ineficiente, mantendo-se todos os pontos armazenados em um vetor. Ela segue os seguintes passos:
- Determinação de maior e menor valores da ordenada
y
do polígono. Os valores serão usados para preencher o polígono linha a linha; - Identificação do valor da abscissa
x
para o valor mínimo dey
. Ele será usado para percorrer as linhas durante o preenchimento; - Cálculo de coeficiente angular de retas. O coeficiente será usado para determinar pontos inclinados dentro do polígono;
- Determinação de segmentos de reta para desenho em cada
y
. Pontos serão pareados dois a dois. Desenhar-se-á linhas entre intervalos parax
de pares alternados. Isso permite colorir partes relevantes, assim como ignorar lacunas.
O próximo canvas
apresenta uma animação do preenchimento feito pelo algoritmo.
Para iniciá-la, pode-se usar o botão Iniciar Animação.
A velocidade de reprodução pode ser ajustada.
Simplificadamente, a implementação do algoritmo mapeia os segmentos de reta que formam o polígono (selecionando pares de pontos consecutivos).
Em seguida, inicializa-se uma instância de uma aresta Edge
para armazenar os valores mínimo e máximo de y
, o inverso (recíproca) do coeficiente angular, e o valor de x
para o qual y
é mínimo (para iniciar o desenho do segmento de reta).
Cada valor é armazenado de um vetor chamado edges
.
Após processar todos os pares, inicia-se o desenho do preenchimento. O código inicia-se na estrutura de repetição enquanto (while).
Mapeia-se cada edge
que deve ser desenhada para a linha (y
) atual, isto é, aquelas cujo valor mínimo da ordenada (minimum_y
) seja maior ou igual a y
e que (ao mesmo tempo) tenham valor máximo da ordenada (maximum_y
) menor que y
.
Cada valor inicial de x
é armazenado em um vetor (starting_x
).
Para o próximo passo, o valor inicial de x
para cada aresta é calculado usando-se o inverso do coeficiente angular.
Isso é similar ao que foi feito previamente para o desenho de linhas oblíquas em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos).
A ordenação de valores é feita para tratar o caso de linhas cruzarem-se ao longo do desenho (ao que pode ocorrer em polígonos complexos). Por fim, basta desenhar segmentos de reta alternados. O primeiro desenhado; o segundo é ignorado; o terceiro é desenhado; o quarto é ignorado; e assim por diante. Isso permite ignorar lacunas entre linhas do polígono.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta
class Point:
var x
var y
func _init(_x, _y):
self.x = _x
self.y = _y
class Edge:
var maximum_y
var minimum_y
var x
# m (coeficient angular)
var inverted_slope
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func franco_draw_polygon(points, color):
assert(points.size() >= 2)
var length = points.size()
var edges = []
var minimum_y = points[0].y
var maximum_y = points[0].y
for index in range(0, length):
var current_point = points[index]
var next_point
if (index < (length - 1)):
next_point = points[index + 1]
else:
next_point = points[0]
var x0 = current_point.x
var y0 = current_point.y
var x1 = next_point.x
var y1 = next_point.y
if (current_point.y <= next_point.y):
x0 = next_point.x
y0 = next_point.y
x1 = current_point.x
y1 = current_point.y
var edge = Edge.new()
if (y1 < y0):
edge.maximum_y = y0
edge.minimum_y = y1
edge.x = x1
else:
edge.maximum_y = y1
edge.minimum_y = y0
edge.x = x0
if (y0 != y1):
edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
else:
edge.inverted_slope = 0.0
edges.append(edge)
if (edge.minimum_y < minimum_y):
minimum_y = edge.minimum_y
if (edge.maximum_y > maximum_y):
maximum_y = edge.maximum_y
var PAINT_LAST_PIXEL = 1
var y = minimum_y
while (y < maximum_y):
var starting_x = []
for edge in edges:
if ((y >= edge.minimum_y) and (y < edge.maximum_y)):
starting_x.append(edge.x)
edge.x += edge.inverted_slope
starting_x.sort()
for index in range(0, starting_x.size() - 1, 2):
var x0 = starting_x[index] - PAINT_LAST_PIXEL
var x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
franco_draw_line(x0, y, x1, y, color)
y += 1
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
# Triângulo
franco_draw_polygon([
Point.new(10, 40),
Point.new(40, 40),
Point.new(40, 10)
], WHITE)
# Retângulo
franco_draw_polygon([
Point.new(50, 10),
Point.new(50, 80),
Point.new(140, 80),
Point.new(140, 10),
Point.new(50, 10)
], RED)
# Losango
franco_draw_polygon([
Point.new(130, 100),
Point.new(150, 160),
Point.new(100, 160),
Point.new(80, 100)
], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
Point.new(160, 10), Point.new(150, 70), Point.new(170, 90), Point.new(140, 230),
Point.new(180, 200), Point.new(210, 210), Point.new(250, 190), Point.new(300, 120),
Point.new(260, 80), Point.new(260, 10), Point.new(160, 10)
], BLUE)
# F
franco_draw_polygon([
Point.new(10, 105), Point.new(10, 220), Point.new(30, 220),
Point.new(30, 170), Point.new(60, 170), Point.new(60, 150),
Point.new(30, 150), Point.new(30, 130), Point.new(70, 130),
Point.new(70, 105), Point.new(10, 105)
], YELLOW)
# Estrela
franco_draw_polygon([
Point.new(80, 180), Point.new(50, 230), Point.new(120, 200),
Point.new(40, 200), Point.new(110, 230), Point.new(80, 180)
], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
class Edge {
constructor() {
this.maximum_y = null
this.minimum_y = null
this.x = null
// m (coeficient angular)
this.inverted_slope = null
}
}
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function franco_draw_polygon(points, color) {
console.assert(points.length >= 2)
var length = points.length
let edges = []
let minimum_y = points[0].y
let maximum_y = points[0].y
for (let index = 0; index < length; ++index) {
let current_point = points[index]
let next_point
if (index < (length - 1)) {
next_point = points[index + 1]
} else {
next_point = points[0]
}
let x0 = current_point.x
let y0 = current_point.y
let x1 = next_point.x
let y1 = next_point.y
if (current_point.y <= next_point.y) {
x0 = next_point.x
y0 = next_point.y
x1 = current_point.x
y1 = current_point.y
}
let edge = new Edge()
if (y1 < y0) {
edge.maximum_y = y0
edge.minimum_y = y1
edge.x = x1
} else {
edge.maximum_y = y1
edge.minimum_y = y0
edge.x = x0
}
if (y0 !== y1) {
edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
} else {
edge.inverted_slope = 0.0
}
edges.push(edge)
if (edge.minimum_y < minimum_y) {
minimum_y = edge.minimum_y
}
if (edge.maximum_y > maximum_y) {
maximum_y = edge.maximum_y
}
}
let PAINT_LAST_PIXEL = 1
let y = minimum_y
while (y < maximum_y) {
let starting_x = []
for (let edge of edges) {
if ((y >= edge.minimum_y) && (y < edge.maximum_y)) {
starting_x.push(edge.x)
edge.x += edge.inverted_slope
}
}
starting_x.sort(function(x, y) {
return x - y
})
for (let index = 0, end = starting_x.length - 1;
index <= end;
index += 2) {
let x0 = starting_x[index] - PAINT_LAST_PIXEL
let x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
franco_draw_line(x0, y, x1, y, color)
}
y += 1
}
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
// Triângulo
franco_draw_polygon([
new Point(10, 40),
new Point(40, 40),
new Point(40, 10)
], WHITE)
// Retângulo
franco_draw_polygon([
new Point(50, 10),
new Point(50, 80),
new Point(140, 80),
new Point(140, 10)
], RED)
// Losango
franco_draw_polygon([
new Point(130, 100),
new Point(150, 160),
new Point(100, 160),
new Point(80, 100)
], GREEN)
// Polígono com pontos sortidos
franco_draw_polygon([
new Point(160, 10), new Point(150, 70), new Point(170, 90), new Point(140, 230),
new Point(180, 200), new Point(210, 210), new Point(250, 190), new Point(300, 120),
new Point(260, 80), new Point(260, 10)
], BLUE)
// F
franco_draw_polygon([
new Point(10, 105), new Point(10, 220), new Point(30, 220),
new Point(30, 170), new Point(60, 170), new Point(60, 150),
new Point(30, 150), new Point(30, 130), new Point(70, 130),
new Point(70, 105),
], YELLOW)
// Estrela
franco_draw_polygon([
new Point(80, 180), new Point(50, 230), new Point(120, 200),
new Point(40, 200), new Point(110, 230),
], MAGENTA)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Edge:
def __init__(self):
self.maximum_y = None
self.minimum_y = None
self.x = None
# m (coeficient angular)
self.inverted_slope = None
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def franco_draw_polygon(points, color):
assert(len(points) >= 2)
length = len(points)
edges = []
minimum_y = points[0].y
maximum_y = points[0].y
for index in range(0, length):
current_point = points[index]
next_point = None
if (index < (length - 1)):
next_point = points[index + 1]
else:
next_point = points[0]
x0 = current_point.x
y0 = current_point.y
x1 = next_point.x
y1 = next_point.y
if (current_point.y <= next_point.y):
x0 = next_point.x
y0 = next_point.y
x1 = current_point.x
y1 = current_point.y
edge = Edge()
if (y1 < y0):
edge.maximum_y = y0
edge.minimum_y = y1
edge.x = x1
else:
edge.maximum_y = y1
edge.minimum_y = y0
edge.x = x0
if (y0 != y1):
edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
else:
edge.inverted_slope = 0.0
edges.append(edge)
if (edge.minimum_y < minimum_y):
minimum_y = edge.minimum_y
if (edge.maximum_y > maximum_y):
maximum_y = edge.maximum_y
PAINT_LAST_PIXEL = 1
y = minimum_y
while (y < maximum_y):
starting_x = []
for edge in edges:
if ((y >= edge.minimum_y) and (y < edge.maximum_y)):
starting_x.append(edge.x)
edge.x += edge.inverted_slope
starting_x.sort()
for index in range(0, len(starting_x) - 1, 2):
x0 = starting_x[index] - PAINT_LAST_PIXEL
x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
franco_draw_line(x0, y, x1, y, color)
y += 1
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
# Triângulo
franco_draw_polygon([
Point(10, 40),
Point(40, 40),
Point(40, 10)
], WHITE)
# Retângulo
franco_draw_polygon([
Point(50, 10),
Point(50, 80),
Point(140, 80),
Point(140, 10),
Point(50, 10)
], RED)
# Losango
franco_draw_polygon([
Point(130, 100),
Point(150, 160),
Point(100, 160),
Point(80, 100)
], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
Point(260, 80), Point(260, 10), Point(160, 10)
], BLUE)
# F
franco_draw_polygon([
Point(10, 105), Point(10, 220), Point(30, 220),
Point(30, 170), Point(60, 170), Point(60, 150),
Point(30, 150), Point(30, 130), Point(70, 130),
Point(70, 105), Point(10, 105)
], YELLOW)
# Estrela
franco_draw_polygon([
Point(80, 180), Point(50, 230), Point(120, 200),
Point(40, 200), Point(110, 230), Point(80, 180)
], MAGENTA)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}
function Point(x, y)
return {
x = x,
y = y
}
end
function Edge()
return {
maximum_y = nil,
minimum_y = nil,
x = nil,
-- m (coeficient angular)
inverted_slope = nil
}
end
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
function franco_draw_polygon(points, color)
assert(#points >= 2)
local length = #points
local edges = {}
local minimum_y = points[1].y
local maximum_y = points[1].y
for index = 1, length do
local current_point = points[index]
local next_point
if (index < length) then
next_point = points[index + 1]
else
next_point = points[1]
end
local x0 = current_point.x
local y0 = current_point.y
local x1 = next_point.x
local y1 = next_point.y
if (current_point.y <= next_point.y) then
x0 = next_point.x
y0 = next_point.y
x1 = current_point.x
y1 = current_point.y
end
local edge = Edge()
if (y1 < y0) then
edge.maximum_y = y0
edge.minimum_y = y1
edge.x = x1
else
edge.maximum_y = y1
edge.minimum_y = y0
edge.x = x0
end
if (y0 ~= y1) then
edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
else
edge.inverted_slope = 0.0
end
table.insert(edges, edge)
if (edge.minimum_y < minimum_y) then
minimum_y = edge.minimum_y
end
if (edge.maximum_y > maximum_y) then
maximum_y = edge.maximum_y
end
end
local PAINT_LAST_PIXEL = 1
local y = minimum_y
while (y < maximum_y) do
local starting_x = {}
for _, edge in ipairs(edges) do
if ((y >= edge.minimum_y) and (y < edge.maximum_y)) then
table.insert(starting_x, edge.x)
edge.x = edge.x + edge.inverted_slope
end
end
table.sort(starting_x)
for index = 1, #starting_x - 1, 2 do
local x0 = starting_x[index] - PAINT_LAST_PIXEL
local x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
franco_draw_line(x0, y, x1, y, color)
end
y = y + 1
end
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
-- Triângulo
franco_draw_polygon({
Point(10, 40),
Point(40, 40),
Point(40, 10)
}, WHITE)
-- Retângulo
franco_draw_polygon({
Point(50, 10),
Point(50, 80),
Point(140, 80),
Point(140, 10),
Point(50, 10)
}, RED)
-- Losango
franco_draw_polygon({
Point(130, 100),
Point(150, 160),
Point(100, 160),
Point(80, 100)
}, GREEN)
-- Polígono com pontos sortidos
franco_draw_polygon({
Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
Point(260, 80), Point(260, 10), Point(160, 10)
}, BLUE)
-- F
franco_draw_polygon({
Point(10, 105), Point(10, 220), Point(30, 220),
Point(30, 170), Point(60, 170), Point(60, 150),
Point(30, 150), Point(30, 130), Point(70, 130),
Point(70, 105), Point(10, 105)
}, YELLOW)
-- Estrela
franco_draw_polygon({
Point(80, 180), Point(50, 230), Point(120, 200),
Point(40, 200), Point(110, 230), Point(80, 180)
}, MAGENTA)
end
O canvas
a seguir apresenta o resultado.
Para uma implementação mais eficiente, poder-se-ia remover valores em edges
cujos valores máximos de maximum_y
superassem o valor atual y
(pois eles não seriam mais desenhados).
Da mesma forma, poder-se-ia começar a buscar por novos valores apenas para valores compatíveis de minimum_y
(pois valores menores não seria desenhados).
Preenchendo Polígonos com Primitivas Gráficas
Embora primitivas gráficas permitam preencher polígonos, muitas implementações de APIs restringem-se a polígonos convexos (por serem mais rápidos e simples de desenhar).
De fato, o preenchimento da estrela magenta falhará em alguns dos exemplos (GDScript, JavaScript com canvas
e Lua com LÖVE).
Ou seja, apenas a implementação em Python com PyGame preencherá a estrela corretamente (com a lacuna na parte central).
Posto o adendo, o código relevante é apresentado em franco_draw_polygon()
.
GDScript fornece draw_colored_polygon()
para o desenho de polígonos coloridos.
JavaScript com canvas
usa uma combinação de beginPath()
, moveTo()
, lineTo()
, closePath()
, e fill()
.
Python com PyGame fornece pygame.draw.polygon()
.
Lua com LÖVE provê love.graphics.polygon()
.
Em alguns casos, basta trocar o modo de desenho.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta
func franco_draw_polygon(points, color):
assert(points.size() >= 2)
draw_colored_polygon(PoolVector2Array(points), color)
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
# Triângulo
franco_draw_polygon([Vector2(10, 40), Vector2(40, 40), Vector2(40, 10), Vector2(10, 40)], WHITE)
# Retângulo
franco_draw_polygon([Vector2(50, 10), Vector2(50, 80), Vector2(140, 80), Vector2(140, 10), Vector2(50, 10)], RED)
# Losango
franco_draw_polygon([Vector2(130, 100), Vector2(150, 160), Vector2(100, 160), Vector2(80, 100), Vector2(130, 100)], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
Vector2(160, 10), Vector2(150, 70), Vector2(170, 90), Vector2(140, 230),
Vector2(180, 200), Vector2(210, 210), Vector2(250, 190), Vector2(300, 120),
Vector2(260, 80), Vector2(260, 10), Vector2(160, 10)
], BLUE)
# F
franco_draw_polygon([
Vector2(10, 105), Vector2(10, 220), Vector2(30, 220),
Vector2(30, 170), Vector2(60, 170), Vector2(60, 150),
Vector2(30, 150), Vector2(30, 130), Vector2(70, 130),
Vector2(70, 105), Vector2(10, 105)
], YELLOW)
# Estrela
# NOTA Godot não desenha.
franco_draw_polygon([
Vector2(80, 180), Vector2(50, 230), Vector2(120, 200),
Vector2(40, 200), Vector2(110, 230), Vector2(80, 180)
], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"
function franco_draw_polygon(points, color) {
console.assert(points.length >= 2)
// context.strokeStyle = color
context.fillStyle = color
context.beginPath()
context.moveTo(points[0][0], points[0][1])
let length = points.length
for (let index = 1; index < length; ++index) {
let next_point = points[index]
context.lineTo(next_point[0], next_point[1])
}
context.closePath()
context.fill()
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
// Triângulo
franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)
// Retângulo
franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)
// Losango
franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)
// Polígono com pontos sortidos
franco_draw_polygon([
[160, 10], [150, 70], [170, 90], [140, 230],
[180, 200], [210, 210], [250, 190], [300, 120],
[260, 80], [260, 10]
], BLUE)
// F
franco_draw_polygon([
[10, 105], [10, 220], [30, 220],
[30, 170], [60, 170], [60, 150],
[30, 150], [30, 130], [70, 130],
[70, 105],
], YELLOW)
// Estrela
// NOTA Canvas não preenche corretamente.
franco_draw_polygon([
[80, 180], [50, 230], [120, 200],
[40, 200], [110, 230], [80, 180]
], MAGENTA)
}
function main() {
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import math
import pygame
import sys
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
def franco_draw_polygon(points, color):
assert(len(points) >= 2)
# 0: preencher polígono; > 0: espessura da linha.
pygame.draw.polygon(window, color, points, 0)
def main():
pygame.display.set_caption("Olá, meu nome é Franco!")
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
# Triângulo
franco_draw_polygon([(10, 40), (40, 40), (40, 10)], WHITE)
# Retângulo
franco_draw_polygon([(50, 10), (50, 80), (140, 80), (140, 10)], RED)
# Losango
franco_draw_polygon([(130, 100), (150, 160), (100, 160), (80, 100)], GREEN)
# Polígono com pontos sortidos
franco_draw_polygon([
(160, 10), (150, 70), (170, 90), (140, 230),
(180, 200), (210, 210), (250, 190), (300, 120),
(260, 80), (260, 10)
], BLUE)
# F
franco_draw_polygon([
(10, 105), (10, 220), (30, 220),
(30, 170), (60, 170), (60, 150),
(30, 150), (30, 130), (70, 130),
(70, 105),
], YELLOW)
# Estrela
franco_draw_polygon([
(80, 180), (50, 230), (120, 200),
(40, 200), (110, 230), (80, 180)
], MAGENTA)
pygame.display.flip()
if (__name__ == "__main__"):
main()
io.stdout:setvbuf('no')
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}
function franco_draw_polygon(points, color)
assert(#points >= 2)
love.graphics.setColor(color)
love.graphics.polygon("fill", points)
end
function love.load()
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
-- Triângulo
franco_draw_polygon({10, 40, 40, 40, 40, 10}, WHITE)
-- Retângulo
franco_draw_polygon({50, 10, 50, 80, 140, 80, 140, 10}, RED)
-- Losango
franco_draw_polygon({130, 100, 150, 160, 100, 160, 80, 100}, GREEN)
-- Polígono com pontos sortidos
franco_draw_polygon({
160, 10, 150, 70, 170, 90, 140, 230,
180, 200, 210, 210, 250, 190, 300, 120,
260, 80, 260, 10
}, BLUE)
-- F
franco_draw_polygon({
10, 105, 10, 220, 30, 220,
30, 170, 60, 170, 60, 150,
30, 150, 30, 130, 70, 130,
70, 105,
}, YELLOW)
-- Estrela
-- NOTA LÖVE preenche incorretamente.
franco_draw_polygon({
80, 180, 50, 230, 120, 200,
40, 200, 110, 230, 80, 180
}, MAGENTA)
end
O canvas
a seguir apresenta o resultado.
Embora a restrição de preenchimento de polígonos complexos seja uma restrição, é possível dividir formas complexas em triângulos, o que permitiria o preenchimento correto. A técnica será abordada em LÖVE durante a criação de uma biblioteca de primitivas gráficas.
Desenhando com Primitivas Gráficas
Neste ponto, as primitivas gráficas para desenho incluem recursos para a criação de contornos e preenchimentos para pontos, linhas, polígonos (retângulos ou definidos por pontos) e elipses (elipses arbitrárias, círculos e arcos). As primitivas foram definidas neste tópico e em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos). Além disso, também é possível escrever texto, como feito em Introdução (Janela e Olá, mundo!).
Assim, agora é possível desenhar ilustrações semelhantes as que poderiam criadas com de editores de imagem raster simples (como Microsoft Paint). Ou seja, com observações atentas, pensando um pouco e com criatividade, já seria possível entender como um editor de imagem simples funciona, assim como pensar em como criar seu próprio.
De fato, pode-se combinar primitivas gráficas existentes para implementar recursos adicionais fornecidos pelo Microsoft Paint. Por exemplo:
- Alterando-se linhas contínuas e espaços, seria possível definir linhas tracejadas (ou pontilhadas);
- Desenhando pequenos círculos preenchidos ou pontos, seria possível criar a ferramenta de spray;
- Sobrepondo-se o desenho de figuras (ou desenhando um contorno por fora), seria possível criar bordas;
- Colorindo-se partes do desenho com a cor de fundo (por exemplo, branco), seria possível implementar uma borracha;
- Para a escrita de texto, pode-se usar as subrotinas apresentadas em tópicos anteriores;
- Desenhando pixels ao longo de um movimento do mouse, seria possível criar uma ferramenta de lápis ou pincel...
Dos itens mencionados, apenas o último requer funcionalidades ainda não abordadas em Ideias, Regras, Simulação. Tal desenho requereria o uso de entrada em tempo real via mouse, além de gerenciamento de cliques.
O momento de explorar a entrada aproxima-se; contudo, para evitar introduzir muitos outros conceitos em único tópico, o restante desta seção apresenta algumas ilustrações criadas usando as primitivas gráficas descritas até o momento. Antes disso, convém definir uma biblioteca própria para agrupar subrotinas para os desenhos.
Criação de Biblioteca para Desenhos com Primitivas Gráficas
Uma biblioteca em programação pode reunir definições para tipos de dados, valores, variáveis, e subrotinas para desempenhar processamentos arbitrários pré-definidos. Tanto para a continuidade deste tópico, quanto para tópicos futuros, seria conveniente agrupar todas as subrotinas criadas para desenho em uma biblioteca. Isso permitiria importar o código definido sempre que for necessário usá-lo, ao invés de duplicá-lo entre projetos. Isso promove boas práticas de programação como reuso de código; você pode aprender mais em Aprenda Programação: Bibliotecas.
Existem duas possibilidades para a criação da biblioteca para este tópico:
- Usar as primitivas fornecidas pelas APIs de cada biblioteca, framework ou motor usados;
- Usar as definições criadas ao longo dos tópicos pelo autor.
Para um código mais simples, conciso e eficiente, a biblioteca seguirá a primeira possibilidade. Contudo, caso se adotasse uma API comum para a biblioteca (no sentido de padronizar nomes e assinaturas para subrotinas, além de comportamentos, efeitos colaterais e resultados esperados), seria possível alterná-las. De fato, esse técnica foi apresentada em Bibliotecas como Abstração por Interfaces.
Como um projeto com bibliotecas pode utilizar múltiplos arquivos, os nomes para cada arquivo do exemplo a seguir será definido como franco_graphics.{EXTENSÃO}
.
Assim:
- GDScript:
franco_graphics.gd
; - JavaScript:
franco_graphics.js
oufranco_graphics.mjs
. A escolha do nome dependerá da abordagem adotada para a biblioteca (global ou módulo). Alguns interpretadores exigem o uso de.mjs
como extensão para módulos, mas outros não; - Python:
franco_graphics.py
; - Lua:
franco_graphics.lua
.
Como a versão em GDScript poderá ser feita usando um Node
convencional, não será mais necessário prefixar subrotinas com franco_
.
Assim, por exemplo, franco_draw_pixel()
passará a chamar-se draw_pixel()
.
Para evitar duplicações de código, cada subrotina de desenho define um parâmetro fill
para preenchimento.
O valor padrão será true
(verdadeiro) para criar um desenho preenchido.
Para desenhar apenas o contorno, basta passar o valor false
(falso).
extends Node
class_name FrancoGraphics
const POINT_COUNT = 100
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const CYAN = Color.cyan
const YELLOW = Color.yellow
const MAGENTA = Color.magenta
static func draw_pixel(drawable, x, y, color):
drawable.draw_primitive(PoolVector2Array([Vector2(x, y)]),
PoolColorArray([color]),
PoolVector2Array())
static func draw_line(drawable, x0, y0, x1, y1, color):
drawable.draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
static func draw_rectangle(drawable, x, y, width, height, color, fill = true):
drawable.draw_rect(Rect2(x, y, width, height), color, fill)
static func draw_square(drawable, x, y, side, color, fill = true):
draw_rectangle(drawable, x, y, side, side, color, fill)
static func draw_polygon(drawable, points, color, fill = true):
assert(points.size() >= 2)
if (fill):
drawable.draw_colored_polygon(PoolVector2Array(points), color)
else:
drawable.draw_polyline(PoolVector2Array(points), color)
static func draw_ellipse(drawable, center_x, center_y, x_radius, y_radius, color, fill = true):
drawable.draw_set_transform(Vector2(center_x, center_y), 0, Vector2(x_radius, y_radius))
if (fill):
drawable.draw_circle(Vector2(0.0, 0.0), 1.0, color)
else:
drawable.draw_arc(Vector2(0.0, 0.0), 1.0, 0.0, 2.0 * PI, POINT_COUNT, color)
drawable.draw_set_transform(Vector2(0.0, 0.0), 0, Vector2(1.0, 1.0))
static func draw_arc(drawable, center_x, center_y, radius, start_angle, end_angle, color, fill = true):
if (fill):
var center = Vector2(center_x, center_y)
# <https://docs.godotengine.org/en/latest/tutorials/2d/custom_drawing_in_2d.html>
var points_arc = PoolVector2Array()
points_arc.push_back(center)
for i in range(POINT_COUNT + 1):
var angle_point = start_angle + i * (end_angle - start_angle) / POINT_COUNT
points_arc.push_back(center + radius * Vector2(cos(angle_point), sin(angle_point)))
drawable.draw_colored_polygon(points_arc, color)
else:
drawable.draw_arc(Vector2(center_x, center_y),
radius,
start_angle, end_angle,
POINT_COUNT,
color)
static func draw_circle(drawable, center_x, center_y, radius, color, fill = true):
if (fill):
drawable.draw_circle(Vector2(center_x, center_y), radius, color)
else:
draw_arc(drawable, center_x, center_y, radius, 0.0, 2.0 * PI, color, false)
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const CYAN = "cyan"
const YELLOW = "yellow"
const MAGENTA = "magenta"
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
function fill_or_line_set_color(fill, color) {
if (fill) {
context.fillStyle = color
} else {
context.strokeStyle = color
}
}
function fill_or_line_draw(fill) {
if (fill) {
context.fill()
} else {
context.stroke()
}
}
function draw_pixel(x, y, color) {
context.fillStyle = color
context.fillRect(x, y, 1, 1)
}
function draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function draw_rectangle(x, y, width, height, color, fill = true) {
if (fill) {
context.fillStyle = color
context.fillRect(x, y, width, height)
} else {
context.strokeStyle = color
context.strokeRect(x, y, width, height)
}
}
function draw_square(x, y, side, color, fill = true) {
draw_rectangle(x, y, side, side, color, fill)
}
function draw_polygon(points, color, fill = true) {
console.assert(points.length >= 2)
fill_or_line_set_color(fill, color)
context.beginPath()
context.moveTo(points[0].x, points[0].y)
let length = points.length
for (let index = 1; index < length; ++index) {
let next_point = points[index]
context.lineTo(next_point.x, next_point.y)
}
context.closePath()
fill_or_line_draw(fill)
}
function draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = true) {
fill_or_line_set_color(fill, color)
context.beginPath()
context.ellipse(center_x, center_y, x_radius, y_radius, 0.0, 0.0, 2.0 * Math.PI)
context.closePath()
fill_or_line_draw(fill)
}
function draw_arc(center_x, center_y, radius, start_angle, end_angle, color, fill = true) {
fill_or_line_set_color(fill, color)
context.beginPath()
context.arc(center_x, center_y,
radius,
start_angle, end_angle)
if (!fill) {
context.stroke()
}
context.closePath()
if (fill) {
context.fill()
}
}
function draw_circle(center_x, center_y, radius, color, fill = true) {
draw_arc(center_x, center_y, radius, 0, 2 * Math.PI, color, fill)
}
export {
BLACK,
WHITE,
RED,
GREEN,
BLUE,
CYAN,
YELLOW,
MAGENTA,
Point,
canvas,
context,
draw_pixel,
draw_line,
draw_rectangle,
draw_square,
draw_polygon,
draw_ellipse,
draw_arc,
draw_circle,
}
import math
import pygame
import pygame.gfxdraw
from typing import Final
POINT_COUNT: Final = 100
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
CYAN: Final = pygame.Color("cyan")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")
window = None
def init(width, height):
global window
pygame.init()
window = pygame.display.set_mode((width, height))
return window
# 0: fill polygon; > 0: line width.
def fill_or_line(fill):
if (fill):
return 0
return 1
def draw_pixel(x, y, color):
window.set_at((x, y), color)
def draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def draw_rectangle(x, y, width, height, color, fill = True):
line_width = fill_or_line(fill)
pygame.draw.rect(window, color, (x, y, width, height), line_width)
def draw_square(x, y, side, color, fill = True):
draw_rectangle(x, y, side, side, color, fill)
def draw_polygon(points, color, fill = True):
assert(len(points) >= 2)
line_width = fill_or_line(fill)
pygame.draw.polygon(window, color, points, line_width)
def draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = True):
if (fill):
pygame.gfxdraw.filled_ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)
else:
pygame.gfxdraw.ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)
def draw_arc(center_x, center_y, radius, start_angle, end_angle, color, fill = True):
if (fill):
# pygame.gfxdraw.pie
# pygame.gfxdraw.filled_arc
center = (center_x, center_y)
points_arc = []
points_arc.append(center)
for i in range(POINT_COUNT + 1):
angle_point = start_angle + i * (end_angle - start_angle) / POINT_COUNT
points_arc.append((center_x + radius * math.cos(angle_point),
center_y + radius * math.sin(angle_point)))
draw_polygon(points_arc, color, True)
else:
pygame.gfxdraw.arc(window,
int(center_x), int(center_y),
int(radius),
int(math.degrees(start_angle)), int(math.degrees(end_angle)),
color)
def draw_circle(center_x, center_y, radius, color, fill = True):
line_width = fill_or_line(fill)
pygame.draw.circle(window, color, (center_x, center_y), radius, line_width)
local POINT_COUNT = 100
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local CYAN = {0.0, 1.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}
local function Point(x, y)
return {
x = x,
y = y
}
end
local function fill_or_line(fill)
if (not fill) then
return "line"
end
return "fill"
end
local function draw_pixel(x, y, color)
love.graphics.setColor(color)
love.graphics.points(x, y)
end
local function draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
-- NOTA Não é possível usar fill = fill or true, porque fill == false sempre
-- seria inicializado como true.
local function draw_rectangle(x, y, width, height, color, fill)
if (fill == nil) then
fill = true
end
love.graphics.setColor(color)
love.graphics.rectangle(fill_or_line(fill), x, y, width, height)
end
local function draw_square(x, y, side, color, fill)
draw_rectangle(x, y, side, side, color, fill)
end
local function draw_polygon(points, color, fill)
assert(#points >= 2)
if (fill == nil) then
fill = true
end
local points_array = {}
for _, point in ipairs(points) do
table.insert(points_array, point.x)
table.insert(points_array, point.y)
end
love.graphics.setColor(color)
if (not fill) then
love.graphics.polygon(fill_or_line(fill), points_array)
elseif (love.math.isConvex(points_array)) then
love.graphics.polygon("fill", points_array)
else
-- NOTA Polígono não pode interseccionar ele mesmo.
triangles = love.math.triangulate(points_array)
for _, triangle in ipairs(triangles) do
love.graphics.polygon("fill", triangle)
end
end
end
local function draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill)
if (fill == nil) then
fill = true
end
love.graphics.setColor(color)
love.graphics.ellipse(fill_or_line(fill), center_x, center_y, x_radius, y_radius, POINT_COUNT)
end
local function draw_arc(center_x, center_y, radius, start_angle, end_angle, color, fill)
if (fill == nil) then
fill = true
end
love.graphics.setColor(color)
love.graphics.arc(fill_or_line(fill), "open",
center_x, center_y,
radius,
start_angle, end_angle,
POINT_COUNT)
end
local function draw_circle(center_x, center_y, radius, color, fill)
if (fill == nil) then
fill = true
end
love.graphics.setColor(color)
love.graphics.circle(fill_or_line(fill), center_x, center_y, radius)
end
return {
BLACK = BLACK,
WHITE = WHITE,
RED = RED,
GREEN = GREEN,
BLUE = BLUE,
CYAN = CYAN,
YELLOW = YELLOW,
MAGENTA = MAGENTA,
Point = Point,
canvas = canvas,
context = context,
draw_pixel = draw_pixel,
draw_line = draw_line,
draw_rectangle = draw_rectangle,
draw_square = draw_square,
draw_polygon = draw_polygon,
draw_ellipse = draw_ellipse,
draw_arc = draw_arc,
draw_circle = draw_circle
}
Deve-se notar que nenhuma das implementações define um ponto de entrada. A biblioteca apenas fornecerá definições e código para desempenhar determinar tarefas. O código do programa será definido pelo arquivo que importar a biblioteca; assim, novos arquivos definirão a aplicação.
Essa não é a única abordagem possível. Estruturas de nível mais alto, como arcabouços (frameworks) ou motores (engines) podem definir o ponto de entrada na definição do código da biblioteca. Nesse caso, o código da aplicação em desenvolvimento deverá seguir as convenções do framework ou motor para a criação do programa. Isso ocorre em Lua com LÖVE e GDScript com Godot.
No mais, a maior parte do código reaproveita implementações de seções e tópicos anteriores, realizando os ajustes para alternar entre o desenho do contorno ou do preenchimento.
Algumas implementações definem uma função interna para ajuste de contorno ou preenchimento, para evitar duplicação de código. Embora isso seja uma boa prática em geral, no caso dos exemplos a escolha talvez torne o código mais complexo. De qualquer forma, como os exemplos têm propósito educativo, a refatoração do código comum ilustra a (boa) prática.
Além disso, cada linguagem de programação apresenta as próprias convenções para bibliotecas. Em alguns casos, podem existir várias alternativas. Você pode aprender mais sobre elas em Aprenda Programação: Bibliotecas.
Os próximos parágrafos descrevem particularidades de cada implementação. Para facilitar o uso, assume-se uma janela por programa. Assim, a variável usada para manipular a janela pode ser global. Para uma implementação mais genérica, poder-se-ia passar a janela (ou abstração para desenho na tela, como o contexto) como parâmetro -- como feito em GDScript.
Na versão em GDScript, o uso de class_name
permite definir um nome para a classe (o arquivo) gerado.
Esse nome poderá ser usado para a importação do código.
Para evitar a necessidade de instanciar um objeto da classe, declarou-se todas as subrotinas como static
.
Em POO, um método static
é um método de classe, isto é, que não depende de uma instância (um objeto) para uso.
Além disso, todas as subrotinas incluem um parâmetro drawable
, pois operações de desenho requerem um Node
que permita usar _draw()
(por exemplo, Control
).
Esse Node
precisará ser declarado no arquivo que chamar o código; para usá-lo, pode-se passar self
como parâmetro (isso será feito na próxima seção).
Alternativamente, poder-se-ia criar uma variável drawable
e um procedimento init()
, como feito em Python.
Nesse caso, nenhuma subrotina poderia ser static
, e seria necessário operar com uma instância de FrancoGraphics
, criada com FrancoGraphics.new()
.
Em Python, criou-se uma função init()
(de initialize ou inicializar) para inicializar a janela.
A função utiliza global window
para indicar que window
é a variável declarada fora da subrotina, com escopo global (para a biblioteca, chamada de módulo).
init()
será chamada no código da aplicação para a criação da janela de PyGame (que requer o tamanho).
Alternativamente, poder-se-ia fazer como em GDScript: cada subrotina poderia ter um parâmetro window
, fornecido pelo código que usasse a biblioteca.
Na versão em JavaScript, é importante notar o uso de export
ao final do arquivo para a exportação de declarações de variáveis, constantes, tipos de dados e subrotinas que se deseja fornecer na biblioteca.
Apenas recursos marcados com export
serão fornecidos para o código da aplicação.
Assim, definições internas (como fill_or_line_set_color()
e fill_or_line_draw()
) não precisam ser exportadas; elas são um mero detalhe de implementação interno.
Adicionalmente, poder-se-ia fazer como em GDScript: cada subrotina poderia ter um parâmetro window
, fornecido pelo código que usasse a biblioteca.
Algo similar ocorre em Lua: todas as definições são marcadas com local
.
Os recursos que se deseja exportar são retornados (return
) como uma table
ao final do arquivo.
LÖVE não requer um parâmetro para a janela porque assume a existência de uma única janela por aplicação.
Além disso, a implementação em Lua em LÖVE ilustra o uso de triangulação para decompor o desenho da letra F em triângulos.
Isso é feito usando love.math.triangulate()
.
Após a decomposição, cada triângulo é desenhando individualmente usando love.graphics.polygon()
usando uma estrutura de repetição.
Assim, do ponto de vista computacional, os recursos básicos estão implementados em todas as linguagens, e disponíveis em uma API pública (Public API). A API pode ser usada para a criação de desenhos. No futuro, ela poderia ser melhorada. Por exemplo, para desenhos mais sofisticados, seria interessante rotacionar as primitivas (por exemplo, para o desenho de elipses inclinadas); neste momento, isso só pode ser aproximado desenhando-se polígonos manualmente.
Do ponto de vista artístico, a qualidade dependerá das habilidades, criatividade, e senso estético da pessoa que criar os desenhos.
Exemplo de Como Usar as Bibliotecas
Após definidas, pode-se importar ou carregar bibliotecas toda vez que for necessário usar o código criado. Ou seja, não será mais necessário redigitar o código (ou copiar e colar implementações); doravante, bastará carregar as implementações dos arquivos definindo as bibliotecas.
Os próximos blocos de código carregam as bibliotecas para usá-las.
Assim, exceto para Lua com LÖVE (main.lua
), os nomes dos arquivos são de sua escolha.
Por exemplo:
- GDScript:
main.gd
,script.gd
, ou outro nome; - JavaScript:
main.js
,script.js
, ou outro nome; - Python:
main.js
,script.js
, ou outro nome; - Lua:
main.js
(obrigatório para o ponto de entrada).
Para usar a biblioteca, a implementação de cada linguagem deverá usar o respectivo franco_graphics.{EXTENSÃO}
.
Assim, por exemplo, a versão em Lua usará franco_graphics.lua
.
Em GDScript e JavaScript, o uso de bibliotecas será um pouco mais complexo que em Python e Lua.
Assim, se você preferir e achar difícil usar as biblioteca corretamente neste momento, você pode copiar e colar o código dos arquivos (GDScript) franco_graphics.gd
em main.gd
, e (JavaScript) franco_graphics.js
em main.js
.
Para usá-las corretamente, continue a ler o texto desta seção.
Para ilustrar o uso de cada subrotina, desenhos de contornos são feitos dentro (ou acima) da versão preenchida.
# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control
const TITLE = "Olá, meu nome é Franco!"
const WIDTH = 320
const HEIGHT = 240
func _ready():
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title(TITLE)
func _draw():
VisualServer.set_default_clear_color(FrancoGraphics.BLACK)
for x in range(0, 20, 2):
FrancoGraphics.draw_pixel(self, x, 10, FrancoGraphics.WHITE)
FrancoGraphics.draw_line(self, 30, 10, 60, 10, FrancoGraphics.BLUE)
FrancoGraphics.draw_rectangle(self, 10, 20, 100, 20, FrancoGraphics.RED)
FrancoGraphics.draw_rectangle(self, 12, 22, 96, 16, FrancoGraphics.WHITE, false)
FrancoGraphics.draw_square(self, 20, 50, 22, FrancoGraphics.GREEN)
FrancoGraphics.draw_square(self, 22, 52, 18, FrancoGraphics.BLACK, false)
var polygon = [
Vector2(10, 105), Vector2(10, 220), Vector2(30, 220),
Vector2(30, 170), Vector2(60, 170), Vector2(60, 150),
Vector2(30, 150), Vector2(30, 130), Vector2(70, 130),
Vector2(70, 105), Vector2(10, 105)
]
FrancoGraphics.draw_polygon(self, polygon, FrancoGraphics.BLUE)
FrancoGraphics.draw_polygon(self, polygon, FrancoGraphics.YELLOW, false)
FrancoGraphics.draw_ellipse(self, 160, 25, 15, 20, FrancoGraphics.BLUE)
FrancoGraphics.draw_ellipse(self, 160, 25, 10, 15, FrancoGraphics.GREEN, false)
FrancoGraphics.draw_arc(self, 160, 120, 20, 0.0, PI, FrancoGraphics.BLUE)
FrancoGraphics.draw_arc(self, 160, 120, 10, 0.0, PI, FrancoGraphics.CYAN, false)
FrancoGraphics.draw_circle(self, 160, 80, 20, FrancoGraphics.WHITE)
FrancoGraphics.draw_circle(self, 160, 80, 16, FrancoGraphics.RED, false)
import {
BLACK,
WHITE,
RED,
GREEN,
BLUE,
CYAN,
YELLOW,
MAGENTA,
Point,
canvas,
context,
draw_pixel,
draw_line,
draw_rectangle,
draw_square,
draw_polygon,
draw_ellipse,
draw_arc,
draw_circle,
} from "./franco_graphics.js"
const TITLE = "Olá, meu nome é Franco!"
const WIDTH = 320
const HEIGHT = 240
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
for (let x = 0; x < 20; x += 2) {
draw_pixel(x, 10, WHITE)
}
draw_line(30, 10, 60, 10, BLUE)
draw_rectangle(10, 20, 100, 20, RED)
draw_rectangle(12, 22, 96, 16, WHITE, false)
draw_square(20, 50, 22, GREEN)
draw_square(22, 52