.. _sec-Animacao: Gerando animações ================= Para gerar animações por computador precisamos gerar novos desenhos, cada um pouco diferente do anterior, tipicamente a uma taxa de cerca de 30 quadros por segundo. Assim, conseguimos criar a ilusão de que os objetos da cena estão se movendo. Imagine por exemplo um objeto se movendo no canvas com velocidade constante que, ao se chocar com uma das paredes, tem seu movimento refletido, como ilustrado abaixo: .. raw:: html Para gerar esse efeito, podemos calcular a nova posição do objeto a cada instante. Embora essa seja uma solução bem natural, o mesmo efeito pode ser alcançado movendo o canvas. Como assim, movendo o canvas!? A gente pode aplicar transformações geométricas sobre as coordenadas do canvas, usando o canvas como se fosse uma "janela" ou "camera virtual" que podemos apontar para diferentes regiões do espaço. Nessa aula vamos explorar essas duas soluções. Animação -------- Para nossa primeira solução, vamos gerar uma animação simplesmente redesenhando o objeto a cada novo quadro, em uma nova posição, atualizada aplicando uma velocidade horizontal ``velX`` e uma velocidade vertical ``velY``. Toda vez que o objeto toca em uma borda do canvas (de dimensão ``(width, height)``), sua velocidade (horizontal e/ou vertical) é **refletida**. A seguinte função ``redesenhe`` aplica essa atualização sobre a posição ``(posX, posY)`` do objeto. .. code:: JavaScript //================================================================== /** * redesenha o canvas a cada intervalo * @param {number} maxp */ function redesenhe( ) { // atualiza a cena, ou seja, a posição do objeto posX = posX + velX; posY = posY + velY; if (posX < 0) { velX *= -1; posX = -posX; } if (posX >= width) { velX *= -1; posX = width - (posX-width); } if (posY < 0) { velY *= -1; posY = -posY; } if (posY >= height) { velY *= -1; posY = height - (posY-height); } // ciclo da animação: limpa a tela e desenhe ctx.clearRect( 0, 0, width, height); // desenha um quadrado verde ctx.fillStyle = "green" desenheFillRect(posX-tam, posY-tam, tam*2, tam*2); // requisita o próximo redesenho window.requestAnimationFrame(redesenhe); }; A função ``redesenhe`` é um exemplo típico de função para animação, constituída pelas seguintes etapas: * atualiza a cena, nesse caso recalculando a nova posição do objeto; * limpa o canvas usando :code:`ctx.clearRect(0, 0, width, height)`; * gera um novo desenho, atualizado; e * usa a função :code:`window.requestAnimationFrame(redesenhe)` para chamar a mesma função ``redesenhe`` no próximo instante de `"adequado" `__. Há outras formas gerar animações, por exemplo definindo explicitamente o intervalo de tempo para chamar a função de redesenho. Nesse curso vamos utilizar a função :code:`requestAnimationFrame`, que procura gerar uma animação suave e eficiente, para gerar animações. A janela abaixo contém o código desse exemplo, que você pode acessar no JSitor pelo link ``__. .. raw:: html Transformações geométricas: translação -------------------------------------- Nessa aula vamos apenas introduzir algumas transformações básicas e a ideia de composição de transformações geométricas. Vamos voltar a esse tópico em aulas futuras. Para introduzir esses fundamentos, vamos por enquanto utilizar apenas 3 transformações básicas: translação, rotação e escala. A Figura :numref:`fig-translacao` ilustra como uma translação podem ser aplicadas no canvas. .. figure:: ./figuras/a06/canvas_grid_translate.png :alt: Efeito de uma translação do canvas :name: fig-translacao :width: 40.0% :align: center Efeitos de uma translação do canvas. `Fonte: Developer Mozilla `__. Uma **translação** pode ser aplicada pela função ``translate(x,y)``. O resultado de uma translação por :math:`(x, y)` é como se a origem do canvas fosse movida para :math:`(x, y)`. Animação usando translações do canvas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A função ``translate`` aplica uma translação sobre canvas como um todo, ou seja, todos os desenhos **após** um ``translate(dx, dy)`` serão deslocados por ``(dx,dy)``. Lembre-se da nossa função ``redesenhe`` anterior que a atualização do objeto basicamente calcula sua nova posição. Ao invés de desenhar o objeto nessa nova posição, podemos criar uma função ``desenhaQuadrado`` que **sempre** desenha o quadrado ao redor da origem (ponto (0, 0)), como abaixo: .. code:: JavaScript /** * desenha um quadrado ao redor de (0, 0) * @param {number} lado */ function desenheQuadrado(lado=40) { let l2 = Math.floor(lado/2); let x = -l2; let y = -l2; let quad = new Path2D(); quad.moveTo( x, y); quad.lineTo( x+lado, y); quad.lineTo( x+lado, y+lado); quad.lineTo( x, y+lado); quad.closePath(); ctx.fill(quad); } Observe que o canto superior esquerdo de um quadrado desenhado pela função ``desenheQuadrado`` sempre tem coordenadas negativas. Podemos usar a função ``translate`` para desenhar o quadrado ao redor de ``(posX, posY)`` modificando a função ``redesenhe`` como a seguir: .. code:: JavaScript /** * redesenha o canvas a cada intervalo * @param {number} maxp */ function redesenhe( ) { // atualiza a cena, ou seja, a posição do objeto posX = posX + velX; posY = posY + velY; if (posX < 0) { velX *= -1; posX = -posX; } if (posX >= width) { velX *= -1; posX = width - (posX-width); } if (posY < 0) { velY *= -1; posY = -posY; } if (posY >= height) { velY *= -1; posY = height - (posY-height); } console.log(`Nova pos: (${posX}, ${posY}) `) // ciclo da animação: limpa a tela e desenhe ctx.clearRect( 0, 0, width, height); // desenha um quadrado verde ctx.fillStyle = "green" ctx.save(); ctx.translate(posX, posY); desenheQuadrado(20); ctx.restore(); // requisita o próximo redesenho requestAnimationFrame(redesenhe); }; Observe que, a cada novo quadro, a nova translação deve ser aplicada sobre o canvas original e não sobre a última translação. A chamada ``ctx.save()`` armazena o estado do canvas **antes** de aplicar a translação e a chamada ``ctx.restore()`` restaura esse estado, evitando assim a composição de translações. A janela abaixo contém o código do exemplo de animação, semelhante a anterior, mas agora usando ``translate``, que você pode acessar no JSitor pelo link ``__. .. raw:: html Rotação e escala ----------------- Uma **rotação** pode ser aplicada pela função ``rotate( a )``. Essa função aplica uma rotação por um ângulo :math:`\alpha` (em radianos) sobre os eixos ao redor da origem, como ilustrado na Figura :numref:`fig-rotacao`. .. figure:: ./figuras/a06/canvas_grid_rotate.png :alt: Efeito de uma rotação do canvas :name: fig-rotacao :width: 40.0% :align: center Efeitos de uma rotação do canvas. `Fonte: Developer Mozilla `__. Uma mudança de escala por ser aplicada pela função ``scale(sx, sy)``. Os valores de :math:`(sx, sy)` podem ser reais menores que 1.0. Por exemplo, para diminuir o tamanho pela metade, podemos usar ``scale(0.5, 0.5)``. Já para aumentar o tamanho do desenho, por exemplo dobrar o tamanho, podemos usar ``scale(2.0, 2.0)``. Observe também que podemos esticar ou encolher o desenho arbitrariamente aplicando fatores de escala diferentes aos eixos horizontal e vertical. Composição de transformações ---------------------------- Imagine agora que temos uma função ``desenheQuadrado`` como abaixo, que desenha um quadrado de lado 50 centrado na origem. .. code:: JavaScript /** * desenha um quadrado ao redor de (0, 0) * @param {number} lado */ function desenheQuadrado(lado=50) { let l2 = Math.floor(lado/2); let x = -l2; let y = -l2; let quad = new Path2D(); quad.moveTo( x, y); quad.lineTo( x+lado, y); quad.lineTo( x+lado, y+lado); quad.lineTo( x, y+lado); quad.closePath(); ctx.fill(quad); } Usando transformações de rotação, translação e escala, como podemos gerar a Figura :numref:`fig-exemplo-transformacoes` abaixo, formado por um quadrado azul de lado 100, rodado de :math:`45^o`, sob um quadrado vermelho de lado 50? .. figure:: figuras/a06/transformacoes.png :alt: Desenho formado a partir de transformações do quadrado :width: 40% :name: fig-exemplo-transformacoes :align: center Exemplo de quadrados gerados a partir de transformações de translação, rotação e escala de um quadrado padrão. Procure resolver esse problema no papel **antes** de prosseguir sua leitura. Uma forma natural de pensar sobre a solução é começando pelo quadrado azul. Podemos rodar o quadrado de :math:`45^o`, ajustar a escala multiplicando por um fator de 2.0 e transladar o resultado até a posição desejada, no centro do canvas. O código poderia corresponder a algo como: .. code:: JavaScript desenheQuadrado(); ctx.rotate(45.0 * Math.PI / 180.0 ); // 45o em radianos ctx.scale(2.0, 2.0); ctx.translate(width/2, height/2); Embora natural, o resultado desse trecho não gera a figura desejada, exibida na Figura :numref:`fig-exemplo-transformacoes`, devido a ordem das transformações estar "invertida" com relação ao canvas. Ou seja, o canvas precisa primeiro ser transladado, etc, para que o desenho seja gerado já sobre o canvas "transformado". A função ``desenhe`` a seguir cria o efeito desejado. .. code:: JavaScript function desenhe( ) { let w2 = Math.floor(width/2); let h2 = Math.floor(height/2); ctx.save(); ctx.translate(w2, h2); // translada para o centro do canvas ctx.rotate(45.0 * Math.PI / 180); // roda de 45 ctx.scale(2.0, 2.0) // escala * 2.0 ctx.fillStyle = 'blue'; desenheQuadrado(); ctx.restore(); ctx.save(); ctx.translate(w2, h2); ctx.fillStyle = 'red'; desenheQuadrado(); ctx.restore(); }; A janela abaixo contém o código do exemplo de composição de transformações, que você pode acessar no JSitor pelo link ``__. .. raw:: html Onde estamos e para onde vamos? ------------------------------- Vimos que a animação pode ser criada usando função ``window.requestAnimationFrame`` para chamar a função responsável pelo redesenho do canvas. Tipicamente essa funçãod deve limpar o canvas antes de redesenhar, atualizar os valores para o desenho antes de gerar o novo desenho. A última chamada da função de redesenho deve ser a ``resquestAnimationFrame``. Vimos também que dois tipos comuns para aplicar transformações aos desenhos. Uma é aplicando as transformações diretamente e outro por meio de composições de transformações primitivas como translação, rotação e escala. A composição é bastante utilizada pois nos permite simplificar as funções de desenho, considerando sempre a origem e uma orientação e escala padrão. Os recursos de interação e animação que vimos até aqui são, embora tipicos de outros sistemas gráficos, específicos do HTML e do canvas. A partir das próximas aulas vamos começar a tratar de outros fundamentos de programação gráfica, iniciando com uma visão da API WebGL. Exercícios ---------- 1. Modifique os exemplos de animação para incluir os seguintes controles: - tecla 'd' para incrementar a velocidade velX - tecla 'a' para decrementar a velocidade velX - tecla 'w' para incrementar a velocidade velY - tecla 's' para decrementar a velocidade velY - slider para controlar o tamanho do objeto, que pode variar de 10 a 100 com passo 10. 2. Modifique os exemplos de animação para incluir outros objetos, com outros tamanhos e cores. 3. Modifique o exemplo de composição de transformações para fazer o quadrado azul rotacionar no sentido anti-horário com velocidade angular constante. 4. A animação do quadrado foi simplificada e apresenta o seguinte artefato: é possível ver parte do quadrado "sumindo" atrás de algumas bordas do canvas. Aumente o tamanho do quadrado para que esse efeito seja mais notável. Altere o programa para que o quadrado seja refletido assim que um de seus lados toque uma borda do canvas. 5. Crie um botão de ``Pausa`` que interrompe a animação ao ser clicado e que retoma a animação ao ser clicado novamente. Inclusive, altere o valor do botão para ``Execute``, para reniciar a animação. Para saber mais --------------- * Capítulo 3 do livro "Interactive Computer Graphics", 8a edição, de Edward Angel e Dave Shreiner. * `Animação básica `__. * `Transformações no canvas `__.