Curso rapidíssimo de Processing
Primeira parte: bolas
1. Instale: http://processing.org/download/.
2. Crie uma pasta processing, ao lado da sua pasta sources (a dos programas C). 3. Abra o Processing.
4. Na janelita de edição que aparece vamos escrever um programa, para desenhar um círculo vermelho inscrito num quadrado preto.
5. Regras básicas:
a. A sintaxe do Processing baseia-‐se na do Java, a qual se baseia na do C. Portanto, estamos em casa!
b. Nos programas mais simples, há apenas duas funções: setup, que é chamada uma vez no início do programa, e draw, que é chamada repetidamente até mandarmos parar o programa.
c. Usaremos variáveis “globais”, declaradas fora das funções, para as grandezas, cuja “vida” de prolonga para além das chamadas das funções. 6. No nosso caso, para ilustrar, queremos uma variável (global) para representar a
cor preta e outra para representar a cor vermelha:
color black = color(0, 0, 0); color red = color(255, 0, 0);
7. O quadrado onde queremos inscrever o círculo vai ser a janela. Para definir o tamanho da janela, temos a função size. Neste caso, tudo o que a função setup tem para fazer é fixar o tamanho da janela:
void setup() { size(480, 480); }
8. Para desenhar um círculo, usamos a função ellipse (!!!), a qual tem quatro argumentos: as coordenadas do centro nos dois primeiros, a largura no terceiro e a altura no quarto. Quando uma figura é desenhada, o seu interior sai com a cor que tiver sido fixada mais recentemente através da função fill. Antes de desenhar, limpamos o fundo, “pintando-‐o” de uma cor pré-‐determinada, par apagar algum desenho que exista anteriormente. Logo, a função draw fica assim: void draw() { background(black); fill(red); ellipse(240, 240, 480, 480); }
9. O programa completo é a concatenação destas três partes: a declaração e inicialização das variáveis globais, a função setup e a função draw.
10. Guarde o programa na sua pasta processing. Espreite para ver o que lá aparece. 11. Corra o programa. Que tal?
12. Agora quanto ao estilo: usar aquelas constantes arbitrárias, 480, 240, espalhadas no programa, não fica bem. O melhor é fixar as dimensões da janela por meio de constantes e depois exprimir os cálculos em termos dessas constantes, ou equivalente. Observe, primeiro a declaração das constantes:
final int h_side = 480; final int v_side = 480;
13. Usamos estes valores como argumentos da função size:
void setup() {
size(h_side, v_side); }
14. Na função ellipse, o melhor é recorrer às variáveis de ambiente width e height que dão a largura e a algura da janela (as quais terão sido fixadas através da função size):
void draw() {
background(black); fill(red);
ellipse(width/2, height/2, width, height); }
15. Guarde também esta versão.
16. Se quisermos fazer experiências com janelas de outros tamanhos, porventura com janelas retangulares com lados de comprimentos diferentes, basta mudar os valores das constantes.
17. Agora experimentemos uma variante: fazer o círculo começar vazio e crescer até ao máximo, depois diminuir até desaparecer, depois aumentar até ao máximo e depois diminuir, e assim sucessivamente.
18. Precisamos de uma variável, ou de uma expressão, para usar na largura e na altura da elipse, em vez da constante 480, agora representada pela variável width. Da primeira vez que o círculo é desenhado, a largura deve ser 0; da segunda, 1; depois 2, 3, ..., 479, 480, 479, 478, ..., 3, 2 ,1, 0, 1, 2, etc. Se tivermos uma variável inteira que conte o número de vezes que a função draw foi
chamada, “basta” transformar esse valor no correspondente valor da sequência, assim, por exemplo:
int diameter(int x){
int result = x % (width*2); if (result > width)
result = width*2 - result; return result;
}
19. Precisamos de uma variável global para contar as chamadas da função draw:
int n_draws = 0;
20. Usamos esta variável para calcular o diâmetro, na função draw e depois de desenhar, incrementamos:
void draw() { background(black); fill(red); int d = diameter(n_draws); ellipse(width/2, height/2, d, d); n_draws++; }
21. Guarde noutro ficheiro e experimente.
22. A função fill preenche o interior da figura que for desenhada a seguir com a cor indicada no argumento. O contorno da figura é desenhado com a cor que tiver sido fixada através da função stroke. Por exemplo, para desenhar os círculos vermelhos com contorno branco, faz-‐se assim:
void draw() { background(black); fill(red); stroke(white); int d = diameter(n_draws); ellipse(width/2, height/2, d, d); n_draws++; }
23. Mais interessante será desenharmos vários círculos, em vez de apenas 1. Para isso precisamos de um array de círculos e para ter um array de círculos
precisamos antes de um tipo Circle para os círculos. Na nossa aplicação, um círculo é descrito pela sua cor, pelas coordenadas do centro e pelo diâmetro. Observe: class Circle { color c; int x; int y; int d; }
24. Para inicializarmos os membros desta classe, usaremos um construtor. Observe: class Circle { color c; int x; int y; int d;
Circle (color new_c, int new_x, int new_y, int new_d){ c = new_c; x = new_x; y = new_y; d = new_d; } }
25. Note que o construtor vem definido dentro da classe.
26. Algures no programa haverá declarações de variáveis globais assim:
Circle c1 = new Circle (red, h_side/2, v_side/2, h_side);
27. Ao longo da vida de um círculo, a cor não muda, o centro também não, mas o diâmetro muda. Por isso, a classe deve ter um método para modificar o valor do diâmetro:
class Circle { //...
void set_diameter(int new_d){ d = new_d;
} }
28. Repare que o método set_diameter também vem definido dentro da classe, e a atua sobre os membros, neste caso o membro d.
29. Agora, quando quisermos que o diâmetro do círculo c1 passe a valer 100, por exemplo, programaremos assim:
c1.set_diameter(100);
30. Analogamente, para desenhar o círculo, usaremos um outro método na classe: class Circle { //... void draw(){ fill(c); stroke(c); ellipse(x, y, d, d); } }
31. A função draw para um programa equivalente ao nosso programa original, mas agora escrito usando a classe Circle, fica assim:
void draw() { background(black); int d = diameter(n_draws); c1.set_diameter(d); c1.draw(); n_draws++; }
32. Não confunda as duas funções draw: uma é a “velha” função do Processing; a outra é um método da classe Circle.
33. O próximo exercício é desenhar quatro círculos, um vermelho, um verde, um azul e um branco, cada um numa quarta parte da janela, todos crescendo e
encolhendo ao mesmo tempo. Como a janela é quadrada, com lado 480, cada um dos quartos é um quadrado de lado 240.
34. Precisamos de quatro círculos:
Circle c1; Circle c2; Circle c3; Circle c4;
35. Não vamos usar estes círculos um a um. Vamos sim metê-‐los num array. Observe. Primeiro as cores:
color black = color(0, 0, 0);
color white = color(255, 255, 255); color red = color(255, 0, 0);
color green = color(0, 255, 0); color blue = color(0, 0, 255);
36. Agora os inicializamos os círculos, um a um, logo na declaração:
Circle c1 = new Circle (red, h_side/4, v_side/4, h_side/2); Circle c2 = new Circle (green, h_side/4, 3*v_side/4, h_side/2); Circle c3 = new Circle (blue, 3*h_side/4, v_side/4, h_side/2); Circle c4 = new Circle (white, 3*h_side/4, 3*v_side/4, h_side/2);
37. Declaramos um array de círculos e inicializamo-‐lo logo:
Circle[] circles = {c1, c2, c3, c4};
38. A anterior função diameter calcula o diâmetro do círculo em função do número de vezes que o círculo já foi desenhado, assumindo que o diâmetro máximo é igual à largura da janela. Agora, cada círculo cresce e diminui num quarto da janela, e não na janela toda. Logo, precisamos de uma função mais geral, em que o diâmetro máximo é parametrizado:
int diameter(int x, int d_max){ int result = x % (d_max*2); if (result > d_max)
result = d_max*2 - result; return result;
}
39. Para desenhar os quatro círculos do array, fazendo-‐os crescer e encolher, usamos um ciclo for, na função draw:
void draw() {
background(black); for (Circle c: circles) {
int d = diameter(n_draws, width/2); c.set_diameter(d); c.draw(); } n_draws++; }
40. Este ciclo for é muito castiço, mas podíamos ter programado à moda antiga:
void draw() {
background(black);
for (int i = 0; i < 4; i++) {
int d = diameter(n_draws, width/2); circles[i].set_diameter(d); circles[i].draw(); } n_draws++; }
41. Quatro círculos já não é mau mas queremos mais! O exercício seguinte continua a ser com bolas, mas agora queremos começar com zero bolas e fazer aparecer uma nova bola a cada clique, com centro na posição do clique. Para começar, todas as bolas são vermelhas com diâmetro máximo igual a metade da largura da janela.
42. Como agora as bolas crescem e encolhem cada uma por si, o tamanho do diâmetro não é função do número de vezes que a função draw foi chamada mas sim do número de vezes que a bola já foi desenhada. Logo, esse número deve ser um membro da classe. Antecipando o caso em possamos ter bolas com
tamanhos máximos diferentes, aproveitamos para acrescentar um membro para registar o tamanho máximo:
class Circle { color c; int x; int y; int d; int d_max; int n;
Circle (color new_c, int new_x, int new_y, int new_d){ c = new_c; x = new_x; y = new_y; d = new_d; d_max = new_d; n = 0; } void draw(){ fill(c); stroke(c); ellipse(x, y, d, d); n++; }
void set_diameter(int new_d){ d = new_d;
} }
43. Na verdade, não é esta função set_diameter que nos faz falta neste problema, pois o que queremos é que cada círculo calcule o seu diâmetro
automaticamente, a partir de n e d_max. Usaremos um outro método para isso: class Circle { // ... void set_diameter(){ d = diameter(n, d_max); }
}
44. Já agora, repare que temos dois métodos com o mesmo nome (o que faz algum sentido, pois fazem mais ou menos a mesma coisa) e que se distinguem pela lista de argumentos.
45. Agora o número de círculo não é fixo. O array começa vazio e de cada vez que há um clique, um novo círculo é acrescentado ao array. Eis a declaração:
final int max_circles = 64;
Circle[] circles = new Circle[max_circles]; int n_circles = 0;
46. A função mousePressed é chamada de cada vez que há um clique: o que lá programarmos será executado em resposta a cada clique. Neste caso, o que queremos fazer é criar acrescentar um novo círculo ao array:
void mousePressed(){
if (n_circles < max_circles)
circles[n_circles++] = new Circle(red, mouseX, mouseY, width/2); }
47. Antes de desenhar cada círculo, atualizamos o seu diâmetro:
void draw() {
background(black);
for (int i = 0; i < n_circles; i++) { circles[i].set_diameter(); circles[i].draw(); } }
48. Note que aqui não podemos usar aquele ciclo for castiço pois nem todos os elementos do array estão preenchidos.
49. Nos exercícios que temos estado a estudar, os círculos não mudam de sítio, mudam apenas de tamanho. Vejamos agora um caso em que os círculos se mudam de sítio, isto é, em que o centro se desloca. Queremos um programa que crie um círculo no local do clique e que depois esse círculo “caia”, deslocando-‐se verticalmente até sair da janela. Para ser mais variado, o diâmetro do círculo será um número aleatório entre 10 e 100 (inclusive) e o a velocidade vertical será um número aleatório entre 1 e 4 (inclusive). Note bem: se a velocidade vertical for dy, então da próxima vez o círculo deve ser desenhado dx pontos mais abaixo. Precisamos de constantes para estas quatro grandezas:
final int dy_min = 1; // minimum vertical speed final int dy_max = 4; // maximum vertical speed final int d_min = 10; // mininum diameter
final int d_max = 100; // maximum diameter
50. Neste exemplo, cada círculo é caracterizado pela cor, pelas coordenada do centro, pelo diâmetro e pela velocidade vertical.
color c; int x; int y; int d; int dy;
Circle(color new_c, int new_x, int new_y, int new_d, int new_dy) { c = new_c; x = new_x; y = new_y; d = new_d; dy = new_dy; } void draw() { fill(c); stroke(c); ellipse(x, y, d, d); } }
51. Precisamos agora de um método para mover um círculo: class Circle { // ... void move() { y += dy; } }
52. A função mousePressed é análoga à anterior:
void mousePressed() {
if (n_circles < max_circles) {
int dx = (int) random(dy_min, dy_max+1); int d = (int) random(d_min, d_max+1);
circles[n_circles++] = new Circle(red, mouseX, mouseY, d, dx); }
53. Repare na forma de usar a função random.
54. Neste exemplo, introduzimos a função update para concentrar todos os cálculos que determinam a forma no novo desenho, antes de cada chamada da funçãoo draw:
void update() {
for (int i = 0; i < n_circles; i++) {
circles[i].move(); }
}
55. Eis a função draw:
void draw() {
background(black); update();
for (int i = 0; i < n_circles; i++) {
circles[i].draw(); }
}
56. O programa produz o efeito visual pretendido, mas podemos notar que as bolas que já saíram da janela continuam a ser desenhadas ainda que não se vejam. Isto é, o computador gasta energia com cálculos desnecessários. Seria melhor retirar do array os círculos que já saíram da janela. O próximo exercício é para ilustrar esta situação.
57. Queremos agora um programa que crie bolas à velocidade de tantas por segundo, bolas essas que entram por cima e vão caindo até baixo. A cor, a coordenada x do centro, o diâmetro e a velocidade de queda são escolhidas aleatoriamente, entre limites dados.
58. Eis uma função para geral uma cor aleatória:
color random_color(){
int r = (int) random(0, 256); int g = (int) random(0, 256); int b = (int) random(0, 256); return color(r, g, b);
}
59. Neste exercício os círculos são como os do exercício anterior, com cor,
coordenadas do centro, diâmetro e velocidade de queda. Precisamos apenas de mais um método para decidir se um círculo é visível (ainda que parcialmente):
class Circle { // ...
boolean visible(){
return y + d/2 >= 0 && y - d/2 <= height; }
}
60. Para criar um círculo aleatório, colocado sobre o lado de cima da janela, podemos usar a seguinte função:
Circle random_circle(){
int x = (int) random(0, width+1);
int dy = (int) random(dy_min, dy_max+1); int d = (int) random(d_min, d_max); color c = random_color();
return new Circle(c, x, -d/2, d, dy); }
61. Periodicamente, precisamos de retirar do array os círculos que deixarem de ser visíveis:
int prune_invisibles(Circle[] c, int n){ int result = 0;
for (int i = 0; i < n; i++) if (c[i].visible())
c[result++] = c[i]; return result;
}
62. Outra operação sobre o array dos círculos, a realizar antes de desenhar, é mover cada um deles:
void move_all(Circle[] c, int n){ for (int i = 0; i < n; i++) c[i].move();
}
63. A frequência de criação de círculos estará fixada numa constante:
final float frequency = 4; // max number of balls created per second
64. Assim, só devemos criar novos círculos quando o número de círculos já criados for menor do que o tempo decorrido vezes a frequência. Logo, precisamos também de uma variável para o número de círculos criados:
int n_balls = 0;
65. A função update, chamada antes de fazer o desenho, move os círculos, remove os que ficam invisíveis e cria um novo círculo se for altura de o fazer:
void update(){
move_all(circles, n_circles);
n_circles = prune_invisibles(circles, n_circles); if (n_balls < millis() / 1000 * frequency && n_circles < max_circles){ circles[n_circles++] = random_circle(); n_balls++; } }
66. A função draw é como de costume e a função setup também.
Segunda parte: mais funções
67. Até agora, só desenhamos bolas, usando a função ellipse. Para desenhar outras figuras básicas, temos as seguintes funções:
a. arc() b. line() c. point() d. quad() e. rect() f. triangle()
g. beginShape(), endShape(), vertex()
Terceira parte: exercícios com bandeiras
68. Escreva programas para desenhar as bandeiras da França, da Suíça, da Suécia, da Noruega, da África do Sul, do Reino Unido, da República Checa e outras formadas só por zonas triangulares ou quadrangulares. Ou então invente uma nova
bandeira nacional, só usando o verde e vermelho, mas numa disposição original. Escolha aquela de que gostar mais, e submeta na tarefa A.
69. Escreva um programa para desenhar uma estrela de cinco pontas no meio de uma janela retangular. Use uma classe Star, análoga a Circle, para este efeito. Submeta na tarefa B
70. Acrescente à classe Star uma operação para rodar uma estrela de um ângulo dado. Use-‐a para animar uma estrela, fazendo-‐a rodar. Submeta na tarefa C. 71. Repita o exercício anterior para várias estrelas, rodando umas para a esquerda
outras para a direita. Submeta na tarefa D.
72. Escreva programas para desenhar as bandeiras dos Estados Unidos, da China, da de Somália, de Cuba, e outras que usam estrelas. Escolha uma delas e submeta na tarefa E.
73. Escreva um programa para desenhar o logótipo da Universidade do Algarve. Submeta na tarefa F.
74. Idem, com uma animação original. Submeta na tarefa G.
75. E, para terminar, um clássico: escreva um programa para desenhar um relógio analógico, com um design original seu! Submeta na tarefa H.
76. Update: como há um exemplo na documentação que é precisamente um relógio analógico, acrescentamos mais uma tarefa, para fechar a nossa cadeira em beleza: uma animação do bubblesort ou de um outro algoritmo de ordenação à escolha. Submeta na tarefa I.