Sobre

Objetivos

Pretende-se que os alunos dominem efetivamente os conceitos e técnicas fundamentais da computação gráfica 2D e 3D.

Para os gráficos 2D são desenvolvidos temas como primitivas de desenho procedimental e gráficos vetoriais, transformações e técnicas de animação. Nos gráficos 3D são tratados os grafos de cena, pontos de vista, geometria e aspeto, transformações, iluminação e animação.

Os conceitos e técnicas são transmitidos no contexto da web, usando as tecnologias mais recentes deste domínio.

Programa

Capítulo Sumário

Introdução

Sobre a Computação Gráfica.

Gráficos 2D

Introdução 2D, Geometria, Transformações.

Aspeto, Exemplos, Revisões.

Gráficos 3D

Introdução 3D, Geometria.

Transformações, Aspeto.

Exemplos 3D.

Animação

Animação por Tempo e por Passos.

Exemplos & Aplicações

Gerador SVG em javascript.

Sistemas-L (C2D).

Animação 2D (C2D + SVG).

Geometria, Aspeto, Animação (X3D).

Avaliação

  • A avaliação de Computação Gráfica tem uma componente teórica e uma componente prática.

  • A componente teórica pode ser realizada por exame, ou por três testes.

  • A componente prática consiste na realização de quatro trabalhos dentro do processo e condições indicadas a seguir.

  • Os exames correspondem apenas à componente teórica da avaliação.

  • Se reprovar no exame normal pode fazer o exame de recurso.

Em casos individuais que o docente ache necessário, pode haver uma prova oral.
Veja as datas das provas nos anúncios do moodle.

Componente Teórica

  • A componente teórica vale 60% da nota final (12 valores).

  • Cada teste vale 20% da nota final (4 valores).

  • Pode realizar os testes que desejar e também os exames (normal e de recurso).

  • A nota final desta componente é a combinação mais vantajosa que resulta dos testes e dos exames.

  • Exemplo 01. Testes: 03, 03, NA; Exame: 11; Nota final: 11.

  • Exemplo 02. Testes: 04, 03, 03; Exame: 08; Nota final: 10.

  • Exemplo 03. Testes: 04, NA, 04; Exame: NA; Nota final: 08.

Componente Prática

  • A componente prática vale 40% da nota final (8 valores).

  • Cada trabalho conta 10% da nota final (2 valores).

  • Cada trabalho corresponde a uma das tecnologias: SVG, C2D, X3D e 3JS.

  • A componente prática não tem «recurso».

  • A classificação de cada trabalho é calculada segundo os Critérios de Avaliação dos Trabalhos Práticos apresentados mais abaixo.

Todos os trabalhos terão de ser entregues até à data do «Exame Normal».
Tabela 1. Critérios de Avaliação dos Trabalhos Práticos
Critério Descrição Peso

formatação

encoding, comportamento, etc.

4%

fidelidade

em relação ao tema e à proposta.

6%

organização/separação

ficheiros externos

10%

organização/abstrações

aplicação de elementos comuns, eliminação de elementos repetidos.

10%

organização/grafo de cena

hierarquia de objetos gráficos, espaços do objeto/mundo.

10%

modelação/geometria e aspeto básicos

quadrados, caixas, círculos, esferas, etc.

5%

modelação/transformações

translação, rotação, escala, composição

10%

modelação/geometria construída

caminhos, segmentos, geometrias por faces, extrusões.

15%

modelação/aspeto construído

gradientes, mosaicos, mapas de cores, mapas UV.

15%

modelação/animação

tweens, por passos, eventos internos (por exemplo, colisões).

15%

Note bem o peso de cada componente da avaliação!

Bibliografia

  • João Madeiras Pereira, João Brisson, António Coelho, Alfredo Ferreira, Mário Rui Gomes - Introdução à Computação Gráfica (2019).

  • Marschner, S., Shirley, P. - Fundamentals of Computer Graphics (2016).

  • Hughes J.F., et al. - Computer graphics. Principles and practice (2014).

  • Ammeraal, L., Zhang, K. - Computer Graphics for Java Programmers (2007).

Recursos

Introdução

Conceitos Fundamentais

Definição e Disciplinas Relacionadas.

Computação Gráfica

É o estudo e aplicação das técnicas de modelação, processamento e construção de objetos gráficos em computadores.

Visão Artificial

É o oposto da computação gráfica: procura reconstruir um modelo (virtual) a partir de imagens reais.

Programação de Jogos

É uma aplicação da computação gráfica: usa um modelo (virtual) onde o utilizador interage.

Processamento de Imagem

Tem técnicas para:

  1. Melhoramento de imagem (equalização do histograma, etc).

  2. Compressão de imagem.

  3. Deteção de features: arestas, super-pixeis, etc..

  4. Resolver crimes (CSI).

Álgebra Linear

Proporciona as bases formais e numéricas para a CG.

Conceitos Principais da Computação Gráfica

Modelador

Especifica o modelo gráfico.

Construtor

Transforma o modelo gráfico numa imagem.

Grafo de Cena

Estrutura de Dados que define e organiza o modelo gráfico.

Os Principais Espaços (ou Referenciais) da Computação Gráfica

Processo da Computação Gráfica

A modelação consiste em especificar o modelo gráfico que define a imagem.

Modelação

Espaço do Modelo

É um espaço 2D ou 3D onde é definido o modelo, juntando e organizando vários objetos gráficos — nos espaços 3D os problemas são mais complexos e tratados de forma significativamente diferente dos espaços 2D.

Objetos Gráficos

Entidades geométricas (linhas, superfícies) ou luzes, textos, imagens.

Representações Matemáticas

Permitem definir e transformar os objetos gráficos.

Vistas

Mostram um modelo numa perspetiva específica e enquadram o passo da construção — Os modelos 2D têm vistas relativamente simples. Para modelos 3D é necessário ter em inúmeras propriedades que envolvem a câmara, a projeção, a posição e orientação da vista ou o campo de profundidade.

Construção (ou Rendering) é o processo computacional que transforma um modelo gráfico numa imagem exibida num dispositivo físico.

Construção

Modo Retido

O modelo é definido e depois a imagem é construída.

Modo Imediato

Os objetos são imediatamente desenhados.

O Grafo de Cena é a estrutura de dados que define o modelo gráfico.

Além do «simples» desenho de objetos, interessa definir uma cena onde os objetos são colocados e geridos. O grafo de cena organiza os vários objetos gráficos numa única estrutura de dados que «alimenta» a rotina de rendering.

A programação gráfica consiste em:

  • Construir o Grafo de Cena:

    • Colocar e Ligar objetos gráficos.

    • Definir vistas.

  • Evoluir o Grafo de Cena:

    • Remover e/ou Acrescentar novos objetos.

    • Alterar atributos (posições, …).

Outros Aspetos

Problemas

Oclusão, Posicionamento de objetos, Aspeto de materiais, etc.

Hardware

Ecrãs, Impressoras, Plotters, Projetores holográficos, etc.

Animação

Filmes, Jogos, Simulações, etc.

Interação

Teclados e Ratos, Rede, Interfaces Gráficos, etc.

Aplicações

Filmes

image

Jogos

image

Visualização

image

Simulação

image

Estatística

010-INTRO_fig_05.png

Medicina

image

Computação Gráfica na Web

A Computação Gráfica na web proporciona:

  • Normas Abertas com ecossistema enorme:

    • Aplicações, Ferramentas.

    • Informação, Comunidade.

  • Paradigmas modernos de descrição de dados e de programação.

  • Suporte para gráficos 2D (contexto 2d e svg) e 3D (x3dom e contexto webgl).

Web 2D

Os elementos canvas e svg:

  • São elementos HTML para gráficos 2D (e 3D).

  • Proporcionam ferramentas para modelar:

    • Figuras Geométricas, Texto, Imagens.

    • Transparências, Gradientes, Transformações, Glifos.

Web 3D

  • O elemento x3d é usado para integrar conteúdo 3D diretamente num documento html, sem extensões.

  • A biblioteca threejs usa o elemento canvas com contexto webgl permite o rendering de gráficos 2D e 3D, sem extensões.

Exemplos elementares

Elemento canvas com contexto 2D (C2D)
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Exemplo C2D</title>
    <script>
function main() {
  let gc = document
    .getElementById("acanvas")  (1)
    .getContext("2d");          (2)
  gc.fillStyle = "steelblue";   (3)
  gc.fillRect(0,0,256,256);     (4)
}
    </script>
  </head>
  <body onload="main();">   (5)
    <canvas id="acanvas"    (6)
      width="256"
      height="256" />
  </body>
</html>
1 Variável javascript que representa o elemento HTML identificado ("acanvas").
2 O contexto gráfico é um objeto com atributos e métodos de desenho.
3 Definir a cor da tinta como «azul».
4 Pintar um retângulo.
5 "main();" é a função que corre quando o browser «carrega» o documento.
6 "<canvas>" é o tipo de elemento HTML que proporciona um contexto gráfico.
Elemento SVG
<!DOCTYPE svg PUBLIC                                    (1)
  "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg                                                    (2)
    xmlns="http://www.w3.org/2000/svg"                  (3)
    xmlns:xlink="http://www.w3.org/1999/xlink"
    width="512" height="512">
  <path                                                 (4)
    stroke="black"
    fill="none"
    d=" M 0 50
        Q 50 0 100 50
        Q 50 100 0 50
        M 25 40
        L 75 60
        L 75 40
        L 25 60
        L 25 40" />
</svg>
1 Identificação do tipo de documento XML. Neste caso, SVG.
2 Um modelo/uma cena 2D
3 Validação de conteúdos SVG.
4 Desenhar um «caminho».
Elemento X3D
<!doctype html>
<html>

<head>
  <meta encoding="utf-8">
  <title>Exemplo X3D</title>
  <script src="x3dom-full.js"></script>
  <link rel="stylesheet" href="x3dom.css">
</head>

<body>
  <x3d width="512px" height="512px">
    <scene>                         (1)
      <shape>                       (2)
        <appearance>
          <material
            diffuseColor="orange">  (3)
          </material>
        </appearance>
        <box size="2 2 2">          (4)
        </box>
      </shape>
    </scene>
  </x3d>
</body>
</html>
1 Uma cena 3D…​
2 …​ com um «objeto gráfico» …​
3 …​ «pintado» de laranja …​
4 …​ e com a forma de uma caixa.
Biblioteca threejs (3JS)
<!doctype html>
<html>
  <head>
    <meta charset = "utf-8" />
    <title>Exemplo Three.js</title>
    <script src = "three.js"></script>
    <script type = "text/javascript" >
function main() {
  let renderer = new THREE.WebGLRenderer();
  renderer.setSize(500,500);
  document.body.appendChild(renderer.domElement);

  let scene = new THREE.Scene();                      (1)

  let camera = new THREE.PerspectiveCamera(           (2)
    35,       // abertura
    500/500,  // proporção largura/altura
    0.1,      // corte perto
    10000     // corte longe
    );
  camera.position.set( -2.5, 0, 20 );
  //camera.position.set( 0, 0, 0 );
  camera.lookAt( scene.position );

  let geometry = new THREE.BoxGeometry( 5, 5, 5 );    (3)
  let material = new THREE.MeshLambertMaterial(
    {color: 0x00FF00 } );                             (4)
  let mesh = new THREE.Mesh( geometry, material );    (5)
  scene.add( mesh );                                  (6)

  let light = new THREE.PointLight( 0xFFFF00 );       (7)
  light.position.set( -15, 10, 10 );
  scene.add( light );                                 (8)

  renderer.render( scene, camera );
}
    </script>
  </head>
  <body onload = "main();">
  </body>
</html>
1 Um modelo/cena 3D…​
2 …​ visto da perspetiva desta câmara.
3 Uma caixa…​
4 …​ pintada de verde
5 …​ é um «objeto gráfico».
6 …​ acrescentado ao modelo.
7 Uma fonte de luz…​
8 …​ para iluminar o modelo.

Exercícios

Programação

  1. Funções geradoras

    1. Escreva uma função repete(x, n) que devolve um array com n cópias de x.

    2. Escreva uma função aleatorios(n) que devolve um array com n números aleatórios.

    3. Escreva uma função intervalo(a, b) que devolve um array com os números inteiros entre a e b incluindo ambos os extremos. Se b < a o resultado deve ser a lista vazia [].

    4. Escreva uma função linspace(a, b, n) que enche um array com n números reais (float) entre a e b, igualmente espaçados. Por exemplo, linspace(0, 1, 3) devolve [0.0, 0.5, 1.0].

  2. Filtros

    1. Escreva uma função pares(x) que tem como argumento um array x de números inteiros e que devolve um array apenas com os números pares. Por exemplo pares([1, 2, 4, 5, 2, 3]) devolve [2, 4, 2].

    2. Escreva uma função positivos(x) que tem como argumento um array x de números reais e que devolve um array apenas com os números positivos. Por exemplo positivos([1, -2.5, 0.4, 0.0, -1.5, 2, 2.3]) devolve [1, 0.4, 2, 2.3].

    3. Escreva uma função limite_sup(x, a) que tem como argumentos um array x de números reais e um valor a e que devolve um array apenas com os números menores ou iguais que o valor a. Por exemplo limite_sup([1, -2.5, 0.4, 0.0, -1.5, 2, 2.3], 0.4) devolve [-2.5, 0.4, 0.0, -1.5].

    4. Escreva uma função filtro(f, x) que tem como argumentos uma função f: float -> boolean e um array x de números reais e que devolve um array apenas com os números xi de x tais que f(xi) === true. Por exemplo filtro(x => x % 2 === 0, [1, 2, 4, 5, 2, 3]) devolve [2, 4, 2]. Quais das alíneas acima consegue tornar a resolver usando esta função?

  3. Mapas

    1. Escreva uma função dobro(x) que tem como argumento um array x de números reais e que devolve um array com os dobros desses números. Por exemplo dobro([1, 2.1, 4, 5, -2, 3]) devolve [2, 4.2, 8, 10, -4, 6].

    2. Escreva uma função quadrado(x) que tem como argumento um array x de números reais e que devolve um array com os quadrados desses números. Por exemplo quadrado([1, -2.5, 0.4]) devolve [1, 6.25, 0.16].

    3. Escreva uma função unicos(x) que tem como argumento um array x de números reais e que devolve um array sem valores repetidos. Por exemplo quadrado([1, -2.5, 1]) devolve [1, -2.5].

    4. Escreva uma função crescente(x) que tem como argumento um array x de números reais e que devolve um array com os valores por ordem crescente. Por exemplo crescente([1, -2.5, 1]) devolve [-2.5, 1, 1].

    5. Escreva uma função estender(x, n) que tem como argumento um array x de números reais e um valor inteiro n e que devolve um array exatamente de comprimento n. Se o comprimento de x for menor que n devem ser acrescentados zeros suficientes. Se o comprimento de x for maior que n os valores a mais são descartados. Por exemplo estender([1, -2.5], 4) devolve [1, -2.5, 0, 0], e estender([1, 6.25, 0.16], 2) devolve [1, 6.25].

    6. Escreva uma função mapa(f, x) que tem como argumentos uma função f: float -> float e um array x de números reais e que devolve um array com números yi = f(xi) em que x = [ ..., xi, ...] +`. Por exemplo `+mapa(x => 2 * x, [1, 2.1, 4, 5, -2, 3]) devolve [2, 4.2, 8, 10, -4, 6]. Quais das alíneas acima consegue tornar a resolver usando esta função?

  4. Misturas

    1. Escreva uma função inverte(x) que tem como argumentos o array x de números reais e que devolve um array com os valores de x por ordem inversa (do último para o primeiro).

    2. Assegure-se que x = cadeia(cabeca(n, x), cauda(n, x)) para qualquer x e qualquer n, em que:

      1. A função cadeia(x, y) tem como argumentos dois array x, y de números reais e devolve um array com os valores de x seguidos pelos valores de y. Por exemplo cadeia([1, 2.1, 4], [5, -2, 3]) devolve [1, 2.1, 4, 5, -2, 3].

      2. A função cabeca(n, x) tem como argumentos o int n e o array x de números reais e devolve o array dos primeiros n valores de x.

      3. A função cauda(n, x) tem como argumentos o int n e o array x de números reais e devolve o array com os valores de x a partir do n-ésimo elemento.

    3. Escreva uma função somar(x, y) que tem como argumentos dois array x, y de números reais e que devolve um array com os valores de x somados aos valores de y pela mesma ordem. Se os argumentos tiverem comprimentos diferentes o resultado deve ser a lista vazia: []. Por exemplo somar([1, 2.1, 4], [5, -2, 3]) devolve [6, 0.1, 7] e somar([1, 2], [3]) devolve [].

    4. Escreva uma função emparelhar(x, y) que tem como argumentos dois array x, y de números reais e que devolve um array com objetos {x: xi, y: yi} onde xi, yi estão nas mesmas posições de x, y. Se os argumentos tiverem comprimentos diferentes o resultado deve ser a lista vazia: []. Por exemplo emparelhar([1, 2.1, 4], [5, -2, 3]) devolve [{x: 1, y: 5}, {x: 2.1, y: -2}, {x: 4, y: 3}] e emparelhar([1, 2], [3]) devolve []. Consegue usar a função mapa do exercício anterior para resolver esta alínea?

  5. Agrupamentos

    1. Escreva uma função conta(x) que tem como argumento um array x de números reais e que devolve o comprimento do array. Por exemplo conta([1, 2, 3, 4]) devolve 4.

    2. Escreva uma função soma(x) que tem como argumento um array x de números reais e que devolve a soma desses números. Por exemplo soma([1, 2, 3, 4]) devolve 10.

    3. Escreva uma função media(x) que tem como argumento um array x de números reais e que devolve a média desses números. Por exemplo media([1, 2, 3, 4]) devolve 2.5.

    4. Escreva uma função max(x) que tem como argumento um array x de números reais e que devolve o maior desses números. Por exemplo max([1, 2, 3, 4]) devolve 4.

    5. Escreva uma função min(x) que tem como argumento um array x de números reais e que devolve o menor desses números. Por exemplo min([1, 2, 3, 4]) devolve 1.

    6. Escreva uma função stats(x) que tem como argumento um array x de números reais e que devolve um sumário estatístico desses valores: um objeto com atributos count, mean, stdev, min, max. O atributo count é o comprimento de x, os valores de mean, min, max resultam das alíneas anteriores e stdev (o desvio padrão) pode ser calculado pela fórmula \(\sqrt{\frac{1}{n - 1}\sum_{i=1}^{n} (x_i - m)^2}\) onde \(n\) é a dimensão do vetor e \(m\) a sua média. Por exemplo stats([1, 2, 3, 4]) devolve {count: 4, mean: 2.5, stdev: 1.291, min: 1, max: 4}.

    7. Escreva uma função dot(x, y) que tem como argumentos dois array x, y de números reais e que devolve o produto interno dos vetores \(x\) e \(y\). Se os argumentos tiverem comprimentos diferentes o resultado deve ser a lista vazia: []. Por exemplo dot([1, 2, 3], [4, 5, 6]) devolve 32. Torne a resolver esta alínea usando as alíneas e exercícios anteriores.

    8. Escreva uma função norma(x) que tem como argumento um array x de números reais e que devolve a norma do vetor \(x\). Por exemplo norma([1, 1]) devolve 1.4142135624. Lembre-se que a norma de um vetor \(x = (x_1, \ldots, x_n)\) é \[\lVert x \rVert = \sqrt{\sum_{i=1}^{n} x_i^2} = \sqrt{x \cdot x}.\]

Geometria

  1. Encontre as coordenadas dos vértices dos seguintes triângulos:

    Triângulo 01

    Triângulo 02

    Triângulo 03

    Triângulo 04

Aplicações

  1. Indique três aplicações (programas) que usam Computação Gráfica 2D.

  2. Indique (procurando online, se necessário) cinco filmes que aplicaram Computação Gráfica (procure incluir também filmes 2D!)

  3. Identifique as áreas dos seguintes problemas/tarefas

    1. Localizar pontos brilhantes numa mamografia

    2. Construir um modelo 3D de um prédio a partir de fotografias

    3. Mostrar uma simulação do sistema solar com o sol e os oito planetas em movimento

    4. Reconhecer a região cerebral numa ressonância magnética e mostrar um modelo 3D do cérebro

    5. Usar computadores para gerar a cena de uma colisão entre automóveis

    6. Fazer a identificação automática de uma pessoa a partir de uma fotografia

Gráficos 2D

Conceitos Fundamentais 2D

Amadeu de Souza Cardoso - Clown
Figura 1. Amadeu de Souza Cardoso - Clown, Cavalo, Salamandra

Na computação gráfica 2D, tanto os espaços dos objetos (onde são definidos os objetos gráficos) como o espaço do modelo (onde é definido o modelo) são 2D (isto é, planos).

Modelação

O processo de modelação é relativamente simples:

  1. Cada objeto gráfico é definido (pela a sua geometria e outras propriedades não-geométricas, como a cor) no seu próprio espaço do objeto.

  2. O modelo é constituído juntando e organizando os vários objetos gráficos já definidos.

Modelação de Objetos Gráficos

Geometria (forma)

A geometria de um objeto gráfico (2D ou 3D) é geralmente obtida com:

  • Objetos básicos como retas, quadrados, círculos, caminhos ou cubos, cilindros, reticulados, etc.

  • Textos.

  • Imagens.

Transformações

As transformações são usadas para

  • Construir objetos gráficos compostos a partir de outros, mais simples.

  • Posicionar cada objeto gráfico (definido no seu próprio espaço do objeto) no espaço do modelo.

  • Modificar a geometria dos objetos com:

    • Rotações.

    • Escalas.

    • Translações.

    • e outras operações.

Aspeto (propriedades não geométricas)

Além da construção da geometria (isto é, da forma) também são usadas propriedades que dizem respeito ao aspeto:

  • Cores e transparências.

  • Texturas.

  • Estilos de linhas.

Geometria 2D

A geometria dos objetos gráficos usa linhas e outros conjuntos de pontos, como polígonos ou elipses.

O problema que se coloca aqui é saber como representar essas linhas, conjuntos de pontos, etc.

A representação dos objetos gráficos assenta na aplicação da matemática, em particular da Álgebra Linear e da Geometria Analítica, que proporcionam as definições formais (como ponto, plano, etc) e propriedades relevantes.

Neste capítulo vamos explorar a representação matemática das entidades geométricas:

  • Sistemas de coordenadas (ou Referenciais).

  • Equações para retas, circunferências e elipses.

  • Equações paramétricas.

  • Caminhos e formas «irregulares».

Referenciais

Um referencial é a ferramenta matemática que permite representar numericamente espaços geométricos.

Uma vez escolhido um referencial, cada ponto do espaço fica identificado por uma lista de números: as coordenadas desse ponto.

Num espaço 2D as coordenadas têm duas componentes, \((x, y)\) e no espaços 3D têm três componentes \((x, y, z)\).

Podemos escolher diferentes referenciais para tratar problemas diferentes. Dois dos referenciais mais comuns são:

  • O referencial cartesiano, normalmente usado na aulas de matemática.

  • O referencial do ecrã, normalmente usado em dispositivos gráficos;

Referenciais

Geometrias Primitivas

Matematicamente, um objeto gráfico, como uma linha reta ou uma circunferência, é um conjunto de pontos.

Ingenuamente, poderíamos pensar em usar (digamos) listas com esses pontos. O problema é que mesmo os objetos gráficos mais simples são formados por infinitos pontos (quantos pontos existem numa linha?).

A solução para representar objetos gráficos consistem em usar equações.

Uma equação tem uma quantidade finita de informação que define um conjunto infinito de pontos.

Por exemplo \((x - 2)^2 + (y + 1)^2 = 9\) é um texto finito que define os infinitos pontos da circunferência de raio \(3\) centrada em \((2, -1)\).

Equações Comuns
  • Equação da Reta: \(Ax + By + C = 0\) ou \(X = P + \lambda v\)

  • Equação da Circunferência: \(x^2 + y^2 = R^2\)

  • Equação da Elipse: \(\left(\frac{x - x_0}{a}\right)^2 + \left(\frac{y - y_0}{b}\right)^2 = 1\)

Questões
  1. Quantos números são usados para representar uma reta? Quantos pontos estão nessa reta?

  2. A equação da circunferência dada não é a mais geral. O que «falta»?

  3. Porque não se usa, para as retas, a equação mais comum \(y = mx + b\) ?

  4. Ainda para a equação da reta, como é que se obtém a equação da reta que passa em dois pontos dados?

As equações dadas anteriormente não são a forma mais prática de trabalhar com Objetos Gráficos para efeitos da Computação Gráfica.

Como obter 100 pontos da circunferência \(x^2 + y^2 = 3\)?

Com equações paramétricas, que indicam explicitamente as coordenadas \(x, y\) dos pontos do objeto.

  • Para a reta:

    \[X = P + \lambda v \Rightarrow (x,y) = (P_x, P_y) + t (v_x, v_y) \Rightarrow \left\lbrace \begin{aligned} x &= P_x + t v_x \\ y &= P_y + t v_y \end{aligned} \right.\]
  • Para a circunferência:

    \[x^2 + y^2 = R^2 \Rightarrow \left\lbrace \begin{aligned} x &= R \cos t \\ y &= R \sin t \end{aligned} \right.\]
  • Para a elipse:

    \[\left (\frac{x - x_0}{a}\right)^2 + \left(\frac{y - y_0}{b}\right)^2 = 1 \Rightarrow \left\lbrace \begin{aligned} x &= x_0 + a \cos t \\ y &= y_0 + b \sin t \end{aligned} \right.\]
Equações Paramétricas e Geometria

As equações paramétricas usam um parâmetro (nestes exemplos, a variável \(t\)) que imaginamos que está a percorrer um certo intervalo (digamos \(\left\lbrack 0, 2\pi\right\rbrack\)).

Conforme \(t\) vai tomando diferentes valores nesse intervalo, vamos também obtendo diferentes valores das coordenadas \(x,y\) dos pontos que formam a geometria do objeto gráfico.

A equação paramétrica da reta tem um problema: O que acontece se \(B = 0\)?

Caminhos

Uma Geometria Difícil de Representar com Equações

Caminho.svg

As equações permitem representar eficientemente formas «regulares», mas nem sempre existe uma forma «regular» adequada ao objeto que se pretende construir…​
Os caminhos permitem definir formas «irregulares» que podem ser tratadas como «Objetos Básicos».

Pensamos num caminho como (o resultado d)as operações que fazemos com um lápis numa folha de papel:

  • «traçar» um contorno;

  • «pintar» o interior dum contorno;

Traçar um Contorno

Um contorno é formado por por vários segmentos. Cada segmento é traçado a partir do fim do segmento anterior, como um lápis numa folha de papel.

Tipos de segmentos curvos

Tipos de Caminhos

Há quatro tipos básicos de segmentos:

  • salto: o lápis salta para uma certa posição.

  • linha: o lápis desenha uma reta até uma certa posição.

  • curva quadrática: o lápis desenha uma curva, controlada por um ponto, até uma certa posição.

  • curva cúbica: o lápis desenha uma curva, controlada por dois pontos, até uma certa posição.

Exemplo da construção do contorno dum caminho, segmento-a-segmento

Exemplo de Construção de Caminho

"Pintar" Caminhos

«Pintar» um caminho (isto é, encher a zona delimitada pelo caminho) é uma tarefa surpreendentemente difícil de «programar».

O problema pode ser formulado da seguinte forma:

Dada um retângulo com um caminho fechado lá dentro, como determinar se um dado ponto está dentro ou fora da região delimitada pelo caminho?

É usado um de dois algoritmos (regras) para encher um caminho:

  • A regra par-ímpar é mais simples e por isso menos «controlável».

  • A regra não-zero é mais complexa do que a regra par-ímpar mas permite tirar partido da orientação dos segmentos para definir o interior do caminho.

Ambas as regras funcionam com base no seguinte princípio:

  1. O retângulo é «varrido de cima para baixo» por linhas que «andam» da esquerda para a direita.

  2. Em cada uma dessas linhas são calculados os pontos que intersectam o caminho.

  3. É aplicada uma das regras para determinar quais segmentos limitados por esses pontos são interiores e quais são exteriores ao caminho.

Tabela 2. Regras Par-Ímpar e Não-Zero para detetar o interior de um contorno.
A regra par-ímpar A regra não-zero

Os segmentos do caminho têm de ser orientados.

Os pontos de interseção são numerados da esquerda para a direita (a começar em zero).

Os pontos de interseção são:

  • Positivos (azuis) se o segmento vem pela direita da linha.

  • Negativos (vermelhos) se o segmento vem pela esquerda da linha.

Os segmentos interiores estão entre um ponto par (azul) e um ponto ímpar (vermelho).

Os segmentos interiores estão entre pontos com total acumulado não zero (estritamente positivo ou negativo).

Regra Par Ímpar

Regra Não Zero

Explore o código SVG sobre caminhos em

Caminhos

Aspeto 2D

A apresentação de um objeto gráfico, além da forma (definida pela geometria) também depende de outras propriedades visuais:

  • cores, traços e tintas;

  • textos e fontes;

Cores

A especificação numérica de uma cor depende de um espaço numérico. Conforme a aplicação do modelo gráfico, certos espaços são mais adequados que outros:

  • Para ecrãs, onde a cor é emitida, a escolha mais comum é um espaço RGB onde cada cor fica definida por três componentes (r,g,b) correspondentes a vermelho, verde e azul.

  • Para impressão, onde a cor é refletida, usam-se espaços CMY (cião, magenta e amarelo) ou CMYK (cião, magenta, amarelo e preto). A adição do preto resulta da dificuldade de se produzir economicamente o preto com as restantes componentes.

  • Para certas aplicações a escolha de uma cor em termos de RGB ou CMY (definidos em função do hardware) pode iludir a perceção humana; Um espaço de cor definido em termos de perceção humana é o HSL (de hue=tom, saturation=saturação e luminosity=luminosidade).

No contexto da web o espaço de cor normal é o RGB; Há imensas formas de especificar concretamente uma cor mas a mais comum é da forma #rrggbb em que rr, gg e bb são números entre 0 e 255 representados em notação hexadecimal. Por exemplo:

cor código hexadecimal exemplo

cião

#00FFFF

magenta

#FF00FF

amarelo

#FFFF00

Além disso, através do CSS, também estão disponível cores por nome (ver as tabelas de cores na norma w3 ou na wikipedia).

Traços

Os traços (isto é, as propriedades visuais das linhas) são inesperadamente complexos.

Uma linha desenhada num dispositivo não é um objeto abstrato (como uma linha matemática, de espessura 0). Na prática as linhas têm formas específicas.

Os traços são definidos por

  • uma espessura;

  • um tracejado;

  • as formas das extremidades;

  • o estilo da junções com outros traços;

  • o controlo das junções;

Exemplo Traços

Tintas

A tinta define como uma forma é pintada. Há várias formas de pintar uma geometria:

  • usando uma cor sólida;

  • usando um mosaico (uma imagem repetida);

  • usando um gradiente de cores (uma variação suave entre cores);

Uma tinta pode ser aplicada apenas ao contorno (stroke) ou encher (fill) o interior da geometria:

image

Mosaicos

O uso de mosaicos para traçar ou encher o contorno é semelhante à aplicação de uma cor sólida, com a particularidade de que a «cor» é um padrão repetido:

image

Gradientes de Cores

Os gradientes mais simples são lineares: uma cor inicial vai mudando suavemente ao longo de uma linha, até chegar a uma cor final. Também podemos definir versões um pouco mais complexas, com várias cores:

image

A forma de definir gradientes «multi-coloridos» consiste em associar cores a pontos de paragem. Um ponto de paragem fica definido por um certo offset (deslocamento) do caminho entre o ponto mais à esquerda e o ponto mais à direita:

  • o ponto mais à esquerda tem offset 0;

  • o ponto mais à direita tem offset 1;

  • pontos intermédios têm offset entre 0 e 1. Por exemplo, um ponto exatamente a meio tem offset 0.5 e um ponto a um quarto «do caminho» tem offset 0.25;

Por exemplo, o gradiente acima está definido com:

  1. um ponto de paragem com offset 0.00 e cor khaki:

  2. um ponto de paragem com offset 0.20 e cor steelblue:

  3. um ponto de paragem com offset 0.75 e cor crimson:

  4. um ponto de paragem com offset 1.00 e cor darkseagreen:

Outra propriedade que controla a aplicação dos gradientes é a repetição, que em geral tem três formas:

image

Finalmente, um gradiente também pode ser circular (em vez de linear):

image

Texto

Texto e Fonte

O uso de textos envolve selecionar uma fonte e desenhar o texto de acordo com essa fonte.

  • Um texto é um tipo especial de objeto geométrico, definido por uma sequência de carateres (letras, dígitos, etc);

  • Uma fonte define os desenhos dos carateres;

Letras, Dígitos, Glifos

Tecnicamente, glifo é o termo que designa, em conjunto:

  • As letras (a é β etc).

  • Os dígitos (0 1 9 etc).

  • Símbolos de pontuação (! , . etc).

  • As ligaturas (Æ Œ fi etc).

  • Outros símbolos (± ∫ → etc).

As propriedades das fontes estão definidas na norma CSS Fonts Module Level 3 e incluem, entre outras:

Tabela 3. Principais atributos das fontes
atributo variantes

família (family)

serif, sans-serif, monospace

estilo (style)

normal, italic

espessura (weight)

normal, bold, bolder, lighter

tamanho (size)

small, medium, large

Transformações 2D

Como já vimos, o Processo da Computação Gráfica determina que cada cena deve ser construida seguindo os seguintes passos:

  1. Construção de cada Objeto Gráfico nos respetivo Espaço do Objeto;

  2. Construção do Modelo, no Espaço do Mundo, juntando os vários Objetos Gráficos do ponto anterior;

  3. Construção (Rendering) final, no Espaço do Dispositivo, com base no Modelo do ponto anterior e das definições da Vista;

O último passo, do Rendering, assenta num conjunto de transformações e outras operações (por exemplo, no clipping) que, normalmente, são automaticamente tratadas pelo sistema gráfico em que se está a trabalhar.

Falta esclarecer:

  • Como é construído cada Objeto Gráfico no seu espaço;

  • Como esse objeto é «transportado» para o Espaço do Mundo;

As Transformações são as principais ferramentas nestes passos. Neste capítulo vamos ilustrar o papel que desempenham.

Transformações: Representação matricial e Casos Especiais

Uma transformação é uma função que aplica pontos em pontos:

\[(x_0,y_0) \stackrel{T}{\to} (x_1,y_1)\]

Para a computação gráfica interessam apenas alguns tipos de transformações. Os tipos principais de transformações são:

Translação

\((x_1, y_1)\) resulta de mover \((x_0, y_0)\) segundo uma certa direção;

Rotação

\((x_1, y_1)\) resulta de rodar \((x_0, y_0)\) um certo ângulo em torno da origem;

Escala

\((x_1, y_1)\) resulta de aumentar ou reduzir \((x_0, y_0)\) por um certo fator;

Composição

\((x_1, y_1)\) resulta de aplicar a \((x_0, y_0)\) uma sequência de transformações;

Adicionalmente ainda podem ser usadas reflexões (em relação a um certo eixo) e deslizamentos (numa certa direção).

A escolha destes tipos de transformações assenta nas seguintes razões:

  • são suficientemente expressivos para a maior parte das necessidades da computação gráfica;

  • são intuitivamente acessíveis;

  • são numericamente eficientes;

A eficiência numérica assenta na forma como os cálculos são efetuados: as coordenadas transformadas \((x_1,y_1)\) resultam de multiplicar as coordenadas originais \((x_0,y_0)\)por uma matriz de transformação:

\[\begin{bmatrix} x_1 \\ y_1 \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_0 \\ y_0 \\ 1 \end{bmatrix}\]

Os valores concretos dos parâmetros \(a,b,c,d,e,f\) dependem da transformação concreta que se pretende aplicar às coordenadas originais.

Nas páginas seguintes vamos ver como esses valores também dependem do tipo de transformação.

Translações

Exemplo de uma Translação

Exemplo Translação

Uma translação consiste em mover as coordenadas originais \((x_0, y_0)\) segundo um certo vetor \((dx, dy)\).

Isto é, as coordenadas transformadas são obtidas por \( (x_1, y_1) = (x_0, y_0) + (dx, dy) \)

A matriz de translação é

\[ \begin{aligned} T(dx,dy) &= \begin{bmatrix} 1 & 0 & dx \\ 0 & 1 & dy \\ 0 & 0 & 1 \end{bmatrix} \end{aligned}\]

e temos

\[\begin{aligned}(x_1,y_1,1) &= T(dx,dy) \begin{bmatrix} x_0\\ y_0 \\ 1 \end{bmatrix}\end{aligned}\]

Rotações

Exemplo de uma Rotação

Exemplo Rotação

Uma rotação consiste em rodar as coordenadas originais \((x_0, y_0)\) segundo um certo ângulo \(\alpha\) em torno da origem \((0,0)\).

A matriz de rotação é

\[ \begin{aligned} R(\alpha) &= \begin{bmatrix} \cos\alpha & \sin\alpha & 0 \\ -\sin\alpha & \cos\alpha & 0 \\ 0 & 0 & 1 \end{bmatrix} \end{aligned}\]

e temos

\[\begin{aligned}(x_1,y_1,1) &= R(\alpha) \begin{bmatrix} x_0\\ y_0 \\ 1 \end{bmatrix}\end{aligned}\]

Escalas

Exemplo de uma Escala

Exemplo Escala

Uma escala consiste em encolher ou esticar as coordenadas originais \((x_0, y_0)\) segundo um certo fator \((sx, sy)\).

A matriz de escala é

\[ \begin{aligned} S(sx,sy) &= \begin{bmatrix} sx & 0 & 0 \\ 0 & sy & 0 \\ 0 & 0 & 1 \end{bmatrix} \end{aligned}\]

e temos

\[\begin{aligned}(x_1,y_1,1) &= S(sx,sy) \begin{bmatrix} x_0\\ y_0 \\ 1 \end{bmatrix}\end{aligned}\]

Composição

Exemplo de uma Composição

Exemplo Composição

Nem sempre as transformações básicas são suficientes para construir os objetos gráficos pretendidos.

Nesse caso é necessário compor as transformações básicas de forma a obter-se o efeito pretendido.

Motivação da composição ilustrada acima

Supondo que se pretende escalar e rodar um certo objeto gráfico, sem o deslocar da posição em que está. O problema está na rotação, quo o feita em torno da origem do referencial, e não em torno do «centro do objeto».

A solução consiste em compor várias transformações de forma a obter-se o efeito pretendido. Geometricamente:

  1. Colocamos o referencial no «centro» do objeto, com uma translação.

  2. Com o referencial no «centro» do objeto, fazemos as operações de escala e de rotação.

  3. Repomos o referencial na posição em que estava inicialmente, de novo com uma translação.

Explore o código SVG sobre transformações em

Transformações

Exercícios 2D

Reveja a matéria de Álgebra Linear e de Geometria Analítica.

Geometria 2D

  1. Considere a equação paramétrica da reta, \(X = P + \lambda v\). Suponha que são dados dois pontos, \(A = (a_x, a_y)\) e \(B=(b_x, b_y)\). Determine os valores dos parâmetros \(P, v\) da reta que passa em \(A, B\) e tais que \(A = P + \lambda v\) quando \(\lambda = 0\) e \(B = P + \lambda v\) quando \(\lambda = 1\).

  2. Escreva uma expressão matemática para definir:

    1. Os segmentos de reta \((0,0) - (0,1)\), \((0,0) - (1,0)\) e \((0,0) - (1,1)\).

    2. O triângulo de vértices \((0,0) - (0,1) - (1,0)\).

    3. O quadrado \((0,0) - (1,0) - (1,1) - (0,1)\).

    4. O interior do quadrado e triângulo anteriores.

  3. Encontre os valores dos parâmetros da equação paramétrica:

    1. Da reta:

      1. Que passa nos pontos \((0,0)\) e \((0,1)\).

      2. Que passa nos pontos \((0,0)\) e \((1,0)\).

      3. Que passa nos pontos \((0,0)\) e \((1,1)\).

    2. Da reta com equação algébrica:

      1. \(2x + 3y - 4 = 0\).

      2. \(3x - 2y - 4 = 0\).

      3. \(4x + 6y - 8 = 0\).

    3. Da circunferência:

      1. Centrada em \((0,1)\) e de raio \(2\).

      2. Centrada em \((1,0)\) e de raio \(2\).

      3. Centrada em \((1,1)\) e de raio \(2\).

      4. Centrada em \((0,1)\) e que passa no ponto \((0,0)\).

      5. Centrada em \((1,0)\) e que passa no ponto \((0,0)\).

      6. Centrada em \((1,1)\) e que passa no ponto \((0,0)\).

Programação 2D

  1. Calcule os parâmetros A, B, C da equação algébrica da reta (\(Ax + By + C = 0\)), dadas as coordenadas (x1, y1), (x2, y2) de dois pontos:

    1. Implemente a função eqna_s(x1, y1, x2, y2) que devolve um objeto com atributos {A, B, C}.

    2. Em que casos é que as entradas x1, y1, x2, y2 não definem uma reta?

      No seu código detete esse caso e devolva null.

    3. Em que casos é que A1, B1, C1 define a mesma reta que A2, B2, C2?

      Implemente uma função equala_ss(A1, B1, C1, A2, B2, C2) que devolve true se os parâmetros definem a mesma reta e false caso contrário.

  2. Calcule os parâmetros P, v da equação paramétrica da reta (\(X = P + \lambda v\)), dadas as coordenadas (x1, y1), (x2, y2) de dois pontos:

    1. Implemente a função eqnp_s(x1, y1, x2, y2) que devolve um objeto com atributos {P, v}.

    2. Em que casos é que as entradas x1, y1, x2, y2 não definem uma reta?

      No seu código detete esse caso e devolva null.

    3. Em que casos é que P1, v1 define a mesma reta que P2, v2?

      Implemente uma função equalp_ss(P1, v1, P2, v2) que devolve true se os parâmetros definem a mesma reta e false caso contrário.

  3. Implemente uma função dot(x1, y1, x2, y2) para calcular o produto interno e use-a para implementar funções para calcular:

    1. A distância entre dois pontos, dist_pp.

    2. A distância de um ponto a uma reta, dist_ps.

    3. O «reflexo» de um ponto por uma reta, mirror_ps.

    4. O «reflexo» de uma reta por outra reta, mirror_ss.

    5. Se duas retas são perpendiculares, are_perp.

  4. Pode gerar pontos de uma circunferência usando uma equação paramétrica.

    1. Implemente a função points_c(x1, y1, r, n) que gera uma lista com n pontos equidistantes da circunferência de centro x1, y1 e raio r.

    2. Use a função points_c para estimar a distância de uma circunferência a:

      1. Um ponto: edist_cp.

      2. Uma reta: edist_cl.

      3. Outra circunferência: edist_cc.

  5. Pode gerar pontos de um segmento de reta usando os dois extremos do segmento e uma equação paramétrica.

    1. Implemente a função points_s(x1, y1, x2, y2, n) que gera uma lista com n pontos equidistantes do segmento limitado pelos pontos (x1, y1) e (x2, y2). Sugestão: Use a equação vetorial da reta:

      \[(x, y) = \lambda ( x_2 - x_1, y_2 - y_1 ) + (x_1, y_1)\]
    2. Estime a distância de um segmento de reta a:

      1. Um ponto: edist_sp.

      2. Outro segmento de reta: edist_ss.

      3. Uma circunferência: edist_sc. Suponha que a circunferência:

        1. está definida por centro e raio.

        2. está aproximada por um conjunto de pontos.

  6. A forma de representar as coordenadas dos pontos usada acima é muito «trapalhona». Em vez de usar x1, y1 (ou outros indíces) use listas, P1 = [x1, y1], para representar pontos. Atualize todas as funções anteriores. Por exemplo, x = dot(P, Q).

  7. Pode obter formas simples usando apenas conjuntos de pontos para definir o contorno do objeto gráfico. Implemente:

    1. A função join(A, B) em que A e B são listas de pontos e que devolve a lista que resulta de acrescentar os pontos de B a seguir aos pontos de A.

    2. A função frame(A) que calcula os cantos superior esquerdo e inferior direito dos pontos em A, isto ém a moldura para os pontos de A.

    3. A função min_circ(A) que calcula a menor circunferência que contém todos os pontos de A. Resolva em duas versões: uma que devolve o centro e o raio da circunferência e outra que devolve uma aproximação com n pontos.

  8. Pode transformar pontos usando as operações da álgebra linear.

    1. Implemente a função [x1, y1, z1] = dot_mv(a,b,c,d,e,f,g,h,i,x0,y0,z0) que calcula

      \[\begin{bmatrix} x_1 \\ y_1 \\ z_Y \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix} \begin{bmatrix} x_0 \\ y_0 \\ z_0 \end{bmatrix}.\]
    2. A forma de representar matrizes e vetores na alínea anterior é muito «trapalhona». Represente os vetores por listas v = [x, y, z] e as matrizes por listas de listas:

      A = [[a, b, c], [d, e, f], [g, h, i]]

      Note que A[0] é a primeira linha de A.

    3. Implemente funções para gerar matrizes e calcular operações comuns:

      1. A matriz zeros(n,m) tem n linhas, m colunas e todas as entradas são 0.

      2. Na matriz ones(n, m) tem n linhas, m colunas e todas as entradas são 1.

      3. A matriz eye(n) é a matriz identidade de ordem n.

      4. A função t(A) é a matriz transposta de A.

      5. A função sum_mm(A, B) soma as matrizes A e B. Se as dimensões forem incompatíveis, devolve null.

      6. A função dot_mm(A, B) multiplica as matrizes A e B. Se as dimensões forem incompatíveis, devolve null.

      7. A função translate(dx, dy) devolve a matriz da translação por (dx, dy).

      8. A função rotate(alpha) devolve a matriz da rotação por alpha radianos.

      9. A função scale(sx, sy) devolve a matriz da escala por (sx, sy).

  9. Use a biblioteca de funções que definiu acima para construir e transformar (aproximações) de circunferências e de segmentos. Visualize as formas originais (a verde) e transformadas (a vermelho) nos sistemas C2D e SVG.

  10. Implemente a regra par-ímpar de enchimento de formas. Suponha que F é um conjunto de pontos que aproxima uma figura 2D:

    1. Defina a função sort_h(F) que devolve os pontos de F ordenados da esquerda para a direita (isto é, primeiro os pontos com menor coordenada x).

    2. Defina a função strip_h(F, y0, e) que devolve os pontos de F que estão numa faixa horizontal, isto é os pontos de F cuja coordenada y é tal que \(\left| y - y_0 \right| \leq e\). Se não existirem pontos assim em F, devolve a lista vazia.

    3. Defina a função first_left(F) que devolve o ponto mais à esquerda de F. Se F for uma lista vazia devolve null.

    4. Defina a função next_right(F, x0) que devolve o ponto de F que, de todos os pontos de F com coordenada x maior que x0, é o que tem menor coordenada x. Se não existir tal ponto devolve null.

    5. Use as funções sort_h, strip_h, first_left e next_right para definir a função fill_evenodd(F, …​), que implementa a regra par-ímpar por faixas horizontais.

      O resultado deve ser uma lista com os segmentos horizontais interiores, sendo cada segmento definido por dois pontos.

      Considere ainda que argumentos são úteis e/ou necessário e como detetar e assinalar potenciais erros.

  11. Visualize o preenchimento de formas pela regra par-ímpar usando vermelho para a forma e verde para o interior. Resolva esta alínea para os sistemas C2D e SVG.

  12. Visualize o interior da forma usando os sistemas C2D e SVG com:

    1. Um gradiente horizontal com várias cores (pelo menos 3).

    2. Um gradiente vertical com várias cores (pelo menos 3).

    3. Um padrão xadrez.

Modelação 2D

  1. Escreva um programa que use o C2D para desenhar um caminho entre os pontos de coordenadas (0,0) e (64,64) que tenha um arco de raio 16.

  2. Escreva um programa javascript para o C2D que desenhe um círculo amarelo no meio de um retângulo 100x100 azul. Defina a mesma imagem com um documento SVG.

  3. Procure na internet: Qual é o intervalo dos comprimentos de onda das cores visíveis?

  4. Marque os seguintes pontos num referencial cartesiano e num referencial de ecrã (desenhados numa folha de papel): (1, 3), (-2, 1.5), (0, -2), (0, 0), (2, 1), (-1.5, 1.5), (3, -0.5)

  5. Escreva um programa javascript para o C2D, e um documento SVG para desenhar:

    1. Uma meia lua:

      Meia Lua

    2. Um quadrado centrado e rodado 45º.

    3. Um tabuleiro de xadrez.

    4. Um tabuleiro tri-colorido. Generalize para uma função que desenhe um tabuleiro n-colorido.

    5. O símbolo oriental Yin-Yang:

      Yin-Yang

    6. Um retângulo pintado com um gradiente radial acíclico.

    7. Um retângulo pintado com um gradiente radial cíclico.

    8. Um octógono. Generalize para um n-ágono.

    9. Uma estrela de cinco pontas. Generalize para n pontas.

    10. Desenhe a bandeira da união europeia.

  6. Implemente uma função javascript para ajudar a desenhar o gráfico de uma função: grafico(f, n, a, b) devolve n pontos (xi, f(xi)) do gráfico da função f : float → float, com os xi igualmente espaçados no intervalo [a, b]. Implemente funções para adaptar as listas produzidas por grafico(…​) a caminhos C2D e SVG.

  7. Implemente uma pequena biblioteca gráfica 2D.

    • Suponha que cada objeto gráfico é definido por:

      • Um caminho, isto é uma lista de pontos x, y.

      • Uma transformação com uma translação, uma rotação e uma escala.

      • Propriedades de aspeto (stroke e fill).

      • Descendentes: uma lista de objetos gráficos.

    • Implemente uma função para desenhar estes objetos gráficos num C2D e para produzir um elemento SVG.

Gráficos 3D

Conceitos Fundamentais 3D

MC Escher - A Strange World
Figura 2. MC Escher - A Strange World

A Computação Gráfica 3D trata o problema da visualização a 2D de modelos 3D.

Um modelo 3D é composto por objetos gráficos, fontes de luz e câmaras

Um sistema gráfico 3D tem de resolver os muitos problemas derivados da representação dos objetos, das transformações, da organização e do rendering da cena.

Funções de um sistema gráfico 3D
  • Definir a geometria dos objetos gráficos.

  • Aplicar transformações geométricas, aos objetos e às câmaras para os localizar e posicionar.

  • Definir o aspeto (usando Texturas e Materiais) dos objetos;

  • iluminar a cena.

  • Definir a vista (usando propriedades como projeção, localização, atitude, abertura, etc) do modelo.

Geometria 3D

Aproximação da Geometria do Rosto Humano.

image

Tipos de Geometrias

Técnicas para definir geometrias 3D.

Para aproximar a geometria de um objeto gráfico ideal podem usar-se:

Formas Básicas

São proporcionadas «diretamente» pelo sistema gráfico.

Por exemplo: Caixas, Esferas, etc.

Geometrias Parametrizadas

Certas formas podem ser definidas por um pequeno conjunto de parâmetros e objetos auxiliares.

Por exemplo: Um cilindro é o rasto que uma circunferência deixa quando se desloca ao longo de um segmento de reta perpendicular ao plano da circunferência.

Conjuntos de Faces

Formas ainda mais irregulares têm de ser construídas definido individualmente as coordenadas dos vértices de uma rede de polígonos.

Por exemplo: Um rosto, o relevo de um terreno.

Além destas formas de construção de geometrias «estáticas» também são usadas técnicas que deformam a geometria inicial e que envolvem «esqueletos» e/ou «morphing». Estes técnicas não constam do programa deste curso.

Geometria Construída

Tabela 4. Modelo de um barco, com geometrias construídas.
Exemplo X3D Exemplo 3JS

image

image

Resultado final, usando o X3D.

Resultado final, usando o 3JS.

Além das formas básicas, em geral estão disponíveis outras métodos para definir a geometria dos objetos gráficos.

Os métodos principais para construir geometrias são:

  • As geometrias parametrizadas; em particular, extrusões.

  • Os conjuntos de faces.

Exemplo/Aplicação da construção de geometrias.

Vamos ilustrar estes dois métodos construindo um objeto que usa ambos. O casco de um barco tem três partes:

  • A proa (bow), usando um conjunto de faces.

  • A quilha (keel), com uma extrusão.

  • A popa (stern), transformando a proa.

Extrusões
Peças de Alumínio feitas por Extrusão (fonte: Artigo «Extrusion» na Wikipedia)

Peças de Alumínio feitas por Extrusão

Tabela 5. Efeito de transportar a secção ao longo da espinha.
A secção…​ …​transportada ao longo da espinha…​ …​gera uma geometria 3D.

image

image

image

Exemplos de extrusões.
  • [2D] Um segmento é o rasto dum ponto deixa quando se movimenta «a direito».

  • [2D] Um quadrado é o rasto dum segmento quando se movimenta «a direito»

  • [2D] Um círculo é o rasto dum segmento quando roda em torno dum ponto.

  • [3D] Um cubo é o rasto dum quadrado ao longo dum segmento.

  • [3D] Um cilindro é o rasto duma circunferência ao longo dum segmento.

Parâmetros das extrusões.

Uma extrusão é definida por:

A secção

Uma superfície 2D assente no plano Y=0.

Por exemplo, uma circunferência, um disco, um «L».

A espinha

Uma sequência de pontos no espaço 3D.

Parâmetros adicionais

Parâmetros específicos de cada sistema gráfico, que controlam pormenores do rendering desta geometria.

Por exemplo, no sistema X3D as extrusões podem ter rotações e escalas definidas ao longo da espinha.

Estes parâmetros definem uma geometria de acordo com o seguinte processo:

  1. A secção, que é definida no plano Y=0 é «transportada» para o primeiro ponto da espinha e orientada para o ponto seguinte.

  2. Da mesma forma, a secção é colocada no segundo ponto da espinha.

  3. Os vértices correspondentes da primeira e da segunda secções são ligados, formando um quadrilátero entre cada par de vértices.

  4. Este processo é depois repetido para o resto dos pontos da espinha, resultando numa superfície de extrusão («arrasto») ao longo da espinha.

Tabela 6. A quilha, em código.
Versão X3D Versão 3JS
<extrusion id="keel"
  convex="false"
  crossSection="    (1)
    0.9   -1.0
    0.9    0.5
    0.0    0.9
   -0.9    0.5
   -0.9   -1.0
   -1.0   -1.0
   -1.0    0.5
    0.0    1.0
    1.0    0.5
    1.0   -1.0
    0.9   -1.0"
  spine="           (2)
    0.0   -1.0    0.0
    0.0   -0.9    0.0
    0.0   -0.5    0.0
    0.0    0.0    0.0
    0.0    0.5    0.0
    0.0    0.9    0.0
    0.0    1.0    0.0"
  scale="           (3)
    1.00    1.00
    1.10    1.00
    1.20    1.00
    1.25    1.00
    1.20    1.00
    1.10    1.00
    1.00    1.00">
</extrusion>
1 A secção é um caminho 2D.
2 A espinha é um caminho 3D.
3 No X3D podem ser aplicadas transformações em cada passo da espinha.
let cross_section_points = [        (1)
  new THREE.Vector2( 0.9, -1.),
  new THREE.Vector2( 0.9, 0.5),
  new THREE.Vector2( 0.0, 0.9),
  new THREE.Vector2( -.9, 0.5),
  new THREE.Vector2( -.9, -1.),
  new THREE.Vector2( -1., -1.),
  new THREE.Vector2( -1., 0.5),
  new THREE.Vector2( 0.0, 1.0),
  new THREE.Vector2( 1.0, 0.5),
  new THREE.Vector2( 1.0, -1.),
  new THREE.Vector2( 0.9, -1.) ];
let cross_section = \               (2)
  new THREE.Shape(cross_section_points);
let spine_points = [                (3)
  new THREE.Vector3(0, -1, 0),
  new THREE.Vector3(0, 1, 0) ];
let spine = \                       (4)
  new THREE.CatmullRomCurve3(spine_points);
let parameters = {
  steps         : 2,
  extrudePath   : spine };
let keel = \                        (5)
  new THREE.ExtrudeGeometry(
    cross_section,
    parameters );
1 Um conjunto de pontos 2D…​
2 …​ para definir a secção.
3 Um conjunto de pontos 3D…​
4 …​ para definir a espinha.
5 A extrusão é definida pela secção e pela espinha.

No X3D as extrusões podem ser parametrizadas com escalas e com rotações.

No 3JS não são suportados parâmetros adicionais.

Conjuntos de Faces
Aproximações de uma geometria por conjuntos de faces (fonte: Artigo «Computer Graphics (computer science)» na Wikipédia)

Aproximações de uma geometria por conjuntos de faces

Tabela 7. Uma geometria definida por faces.
A versão final da proa, um conjunto de faces. A proa em wireframe, com faces poligonais. A proa em wireframe, com faces triangulares.

image

image

image

O X3D aceita faces poligonais.

No 3JS as faces são triângulos.

Formas pouco regulares (por exemplo, um rosto ou um terreno) têm de ser construídas definido individualmente as coordenadas dos vértices e ligando esses vértices para formar faces (triângulos ou quadriláteros).

Parâmetros dos conjuntos de faces.

Um conjunto de faces é definido por:

Os vértices

Um ponto no espaço 3D, dado pelas suas coordenadas x, y, z.

Em geral os vértices são «arrumados» numa lista e referenciados pelo seu índice nessa lista.

As faces

Uma lista de três (ou, no caso do X3D, mais) vértices.

Normalmente é usado o índice na lista dos vértices, em vez das coordenadas.

Regra da Mão Direita.

Um aspeto importante na definição de uma face é a ordem em que os (índices dos) vértices são dados.

Dados três pontos no espaço 3D, A, B, C, a face «para cima» é definida pela regra da mão direita. Se os pontos forem dados pela ordem A, C, B então a face «para cima» é oposta.

Quando esta regra é esquecida o resultado típico são «faces invisíveis».

Tabela 8. A proa, em código.
Versão X3D Versão 3JS
<indexedfaceset id="bow"
  coordIndex="              (1)
    0   1   8   9   0   -1
    1   2   7   8   1   -1
    2   3   6   7   2   -1
    3   4   5   6   3   -1
    0   10  11  1   0   -1
    10  4   3   11  10  -1
    11  3   2   11      -1
    11  2   1   11      -1
    5   6   13  12  5   -1
    6   7   13  6       -1
    7   8   13  7       -1
    9   8   13  12  9   -1
    0   9   12  10  0   -1
    5   4   10  12  5   -1">
  <coordinate point="         (2)
    0.9     0.0     -1.
    0.9     0.0     0.5
    0.0     0.0     0.9
    -.9     0.0     0.5
    -.9     0.0     -1.
    -1.     0.0     -1.
    -1.     0.0     0.5
    0.0     0.0     1.0
    1.0     0.0     0.5
    1.0     0.0     -1.
    0.0     1.9     -1.
    0.0     1.9     -.5
    0.0     2.0     -1.
    0.0     2.0     -.5">
  </coordinate>
</indexedfaceset>
1 As faces. Cada face é uma lista de índices que termina em -1.
2 Os vértices. Cada vértice é um ponto 3D, definido por coordenadas xyz.
let coordinates = [                   (1)
    new THREE.Vector3(0.9, 0.0, -1.),
    new THREE.Vector3(0.9, 0.0, 0.5),
    new THREE.Vector3(0.0, 0.0, 0.9),
    new THREE.Vector3(-.9, 0.0, 0.5),
    new THREE.Vector3(-.9, 0.0, -1.),
    new THREE.Vector3(-1., 0.0, -1.),
    new THREE.Vector3(-1., 0.0, 0.5),
    new THREE.Vector3(0.0, 0.0, 1.0),
    new THREE.Vector3(1.0, 0.0, 0.5),
    new THREE.Vector3(1.0, 0.0, -1.),
    new THREE.Vector3(0.0, 1.9, -1.),
    new THREE.Vector3(0.0, 1.9, -.5),
    new THREE.Vector3(0.0, 2.0, -1.),
    new THREE.Vector3(0.0, 2.0, -.5) ];
let faces = [                       (2)
    //
    new THREE.Face3(0, 1, 8),
    new THREE.Face3(0, 8, 9),
    new THREE.Face3(1, 2, 7),
    new THREE.Face3(1, 7, 8),
    new THREE.Face3(2, 3, 7),
    new THREE.Face3(3, 6, 7),
    new THREE.Face3(3, 4, 6),
    new THREE.Face3(4, 5, 6 ),
    //
    new THREE.Face3(0, 10, 11),
    new THREE.Face3(0, 11, 1),
    new THREE.Face3(4, 3, 11),
    new THREE.Face3(4, 11, 10),
    new THREE.Face3(3, 2, 11),
    new THREE.Face3(2, 1, 11),
    //
    new THREE.Face3(5, 13, 6),
    new THREE.Face3(5, 12, 13),
    new THREE.Face3(7, 6, 13),
    new THREE.Face3(8, 7, 13),
    new THREE.Face3(9, 8, 13),
    new THREE.Face3(9, 13, 12 ),
    //
    new THREE.Face3(0, 9, 10),
    new THREE.Face3(9, 12, 10 ),
    new THREE.Face3(5, 4, 10),
    new THREE.Face3(5, 10, 12 ) ];
let bow = new THREE.Geometry();
bow.vertices = coordinates;
bow.faces = faces;
bow.computeFaceNormals(); // get the right «outside"
bow.computeBoundingSphere(); // to help the rendering system
1 Os vértices: pontos 3D definidos por coordenadas xyz.
2 As faces no 3JS são triângulos. Cada face é uma lista de três índices.
Listas de vértices, índices e faces.

Nos exemplos acima as faces são listas de índices. Mais concretamente, em ambos os exemplos:

  1. É dada uma lista de pontos 3D.

    No X3D é o sub-elemento <coordinate> e no 3JS é o Array coordinates.

  2. Cada face é definida por um certo número de índices.

    No caso do X3D uma face pode ter vários elementos, pelo que se usa o valor -1 para marcar o fim de uma face e o início da seguinte. As faces da geometria são definidas pelo atributo coordIndex.

    No caso do 3JS as faces são triângulos, definidos por três índices.

Aspeto 3D

Efeito da Iluminação na Perceção de Um Objeto

image

A luz é refletida segundo um certo ângulo tangente à superfície do objeto no ponto onde incide.

  • O ângulo é determinado pelas normais (que dependem da geometria do objeto).

  • As cores somam a cor própria do objeto à cor da luz incidente.

  • Os reflexos definem quanta luz incidente é refletida pelo objeto.

O aspeto percecionado de um objeto depende:

  • Da textura desse objeto.

  • Da iluminação da cena.

Parâmetros das texturas.

As texturas (ou materiais) são propriedades do objeto que contribuem para a sua perceção.

Definem como o objeto é «pintado» e como transforma a luz.

Cores (difusão: diffuse)

São as cores próprias do objeto: luz refletida = cor do objeto + luz incidente.

Por exemplo, um cubo laranja difunde a cor laranja e um cubo verde difunde verde.

Reflexos (specular)

Determinam a quantidade de luz refletida: luz refletida = reflexo * luz incidente.

Por exemplo, um cubo brilhante reflete quase toda a luz que recebe, enquanto que um cubo baço quase não reflete a luz que recebe.

Normais (normals)

Dependem da geometria do objeto e determinam a direção da luz refletida: ângulo da luz refletida = normal + ângulo da luz incidente.

Por exemplo, num cubo liso as normais são perpendiculares às faces mas num cubo rugoso as normais têm desvios da perpendicular.

Tipos de iluminação.

A iluminação resulta de um conjunto de propriedades do modelo (da cena) que contribuem para a perceção dos vários objetos nesse modelo.

Fontes de Luz

Definem propriedades como «cor emitida», «atenuação», etc e, em geral, incluem os seguintes tipos: ambiente, ponto de luz, foco, direcional.

Luz Ambiente

Incide igualmente em todos os objetos gráficos, independentemente da posição ou pose,

Ponto de Luz

Emite luz em todas as direções, a partir de uma certa posição (por exemplo, uma lâmpada numa sala),

Foco

Emite um cone de luz (com uma certa orientação) a partir de uma certa posição (por exemplo, um projetor numa secretária).

Direcional

Emite luz em «raios paralelos» a uma certa orientação (por exemplo, o sol a iluminar uma cena na superfície da terra).

Texturas

Modelo da Terra, com Iluminação

image

Vejamos o efeito que cada caraterística das texturas tem na perceção (e realismo) do objeto seguindo um exemplo: o planeta terra.

A geometria da terra é (aproximadamente) uma esfera, e vamos supor que na cena há um ponto de luz razoavelmente afastado, de forma a simular o sol.

Mapas de Cores (difusão: diffuse)

São as cores «próprias» do objeto: luz refletida = cor do objeto + luz incidente

Para pintar «num passo» todas as cores da superfície da esfera usa-se uma textura para a difusão das cores.
Mapa de Difusão Efeito do Mapa

image

image

A esfera fica pintada com as cores pretendidas mas falta realismo à imagem.

Por exemplo, as superfícies líquidas devem refletir muito mais luz do que as massa de terra.

Mapas de Reflexos (specular)

Determinam a quantidade de luz refletida: luz refletida = reflexo * luz incidente

Para definir «num passo» os reflexos na superfície da esfera usa-se uma textura para a reflexão das cores. Nestas texturas branco significa «reflete 100%», preto significa «reflete 0%» e tons intermédios refletem percentagens intermédias.

A principal avanço em relação aos mapas de difusão «simples» nota-se nas zonas que devem brilhar.

Por exemplo, nas zonas com água (rios, lagos, mares, oceanos) pode ver-se o reflexo do «sol» enquanto que nas zonas «de terra» não há grandes diferenças.

Tabela 9. Efeito do Mapa de Reflexos
Mapa de Reflexos Efeito do Mapa

image

image

Tabela 10. Pormenor do Efeito do Mapa de Reflexos
Sem Mapa de Reflexos Efeito do Mapa

image

image

As superfícies planas, como os rios, lagos, mares, estão mais reais do que antes.

Mas ainda falta realçar o efeito do relevo nas sombras.

Mapas de Normais (normals)

Determinam a direção da luz refletida: ângulo da luz refletida = normal + ângulo da luz incidente

Para definir «num passo» as normais à superfície da esfera usa-se uma textura para as normais.
Representação das normais por uma imagem.

Uma normal é um vetor no espaço 3D e, como tal, tem três componentes xyz.

Uma cor também tem três componentes, RGB, e nestes mapas uma cor RGB representa um vetor xyz associando x = R, y = G, z = B, sendo x positivo «para norte», y positivo «para leste» e z «para fora».

Um Mapa de Normais para a Terra

image

Tabela 11. Pormenor da Aplicação do Mapa de Normais
Sem Mapa de Normais Efeito do Mapa de Normais

image

image

A principal diferença em relação ao resultado anterior está nas zonas rugosas.

Por exemplo, nas zonas montanhosas podem ver-se as sombras como o efeito do relevo.

Pode ver o resultado final, com difusão, reflexos e normais aqui.

Mapas UV

Um «dado», com as texturas aplicadas por faces

image

Tabela 12. Texturas aplicadas «à bruta» não funcionam como esperado
Textura de Difusão Geometria Efeito «Ingénuo»

image

image

image

Temos uma textura de difusão…​

…​e uma geometria.

O que pode correr mal?

Controlo de aplicação das texturas

As texturas definidas por imagens são uma forma conveniente de refinar o aspeto de um objeto gráfico.

Para funcionar corretamente, é preciso controlar como cada sector da textura é associado a cada face da geometria do objeto.

Termos e processo dos mapas UV

Resumidamente, o processo para aplicar uma textura a uma geometria consiste em:

  1. Definir vértices xyz e as faces da geometria.

  2. Definir pontos uv e os setores da textura.

  3. Aplicar cada setor da textura a uma face da geometria.

Especificamente:

Os vértices

Têm coordenadas xyz e estão na geometria 3D.

As faces

São as superfícies da geometria 3D e definem-se com listas de índices de vértices.

Os pontos

Têm coordenadas uv e estão na textura 2D.

Os setores

São «pedaços» da textura 2D e definem-se com listas de índices dos pontos.

A aplicação de mapas UV é adequado a geometrias definidas por conjunto de faces onde os vértices e as faces estão explicitamente indicados.
Exemplo 1. Processo para o mapa UV do «dado»

O processo para ilustrar a aplicação dos mapas UV é o seguinte:

  1. Definir os vértices e as faces da geometria 3D.

  2. Definir os pontos e os setores da textura 2D.

Por convenção, o primeiro setor é aplicado à primeira face, etc.
Tabela 13. Vértices e faces da geometria 3D
Coordenadas dos Vértices XYZ do Cubo Faces do Cubo
-1 -1 -1 // vértice xyz #0
-1 -1 +1 // vértice xyz #1
+1 -1 +1 // vértice xyz #2
+1 -1 -1 // vértice xyz #3

-1 +1 -1 // vértice xyz #4
-1 +1 +1 // vértice xyz #5
+1 +1 +1 // vértice xyz #6
+1 +1 -1 // vértice xyz #7
0 3 2 1 // face #0
0 1 5 4 // face #1
1 2 6 5 // face #2
2 3 7 6 // face #3
3 0 4 7 // face #4
5 6 7 4 // face #5

A «marcação» dos pontos na textura consiste apenas em indicar as coordenadas (x,y) dos pontos de interesse.

Porém, dado que x e y já se referem a coordenadas no espaço 3D, para evitar confusões é costume representar o eixo horizontal da textura por u (ou, no caso do x3d, por t) e o eixo vertical por v (respetivamente, s).

Tabela 14. Pontos UV da textura 2D
Pontos UV na Textura de Difusão Coordenadas dos Pontos UV

image

0.00 0.00 // ponto uv #00
0.25 0.00 // ponto uv #01
0.00 0.33 // ponto uv #02
0.25 0.33 // ponto uv #03
0.50 0.33 // ponto uv #04
0.75 0.33 // ponto uv #05
1.00 0.33 // ponto uv #06
0.00 0.66 // ponto uv #07
0.25 0.66 // ponto uv #08
0.50 0.66 // ponto uv #09
0.75 0.66 // ponto uv #10
1.00 0.66 // ponto uv #11
0.00 1.00 // ponto uv #12
0.25 1.00 // ponto uv #13
Setores da textura 2D
7   8   3   2   // setor #0 para a face #0
11  6   5   10  // setor #1 para a face #1
2   3   1   0   // setor #2 para a face #2
3   8   9   4   // setor #3 para a face #3
8   7   12  13  // setor #4 para a face #4
5   4   9   10  // setor #5 para a face #5
Regra da mão direita

Na definição dos setores, 7 8 3 2 não é o mesmo que 7 8 2 3.

Tem de usar a regra da mão direita para se determinar o lado da face como uma página numa folha de papel.

Transformações 3D

Investigação da perspetiva (gravura de Albrecht Dürer)

image

As transformações colocam os objetos gráficos no espaço e alteram a sua forma, tamanho e posição.

Além disso também são usadas pelos sistemas gráficos para produzir a vista 2D do modelo 3D.

Tipos de transformações

Normalmente são usados dois tipos de transformações nos sistemas gráficos 3D:

Transformações afins

São geralmente usadas no espaço do mundo ou do objeto e generalizam as transformações dos sistemas gráficos 2D. São usadas para posicionar, rodar e escalar os objetos gráficos.

Projeções

Estão associadas a câmaras e são usadas para definir a vista da cena, isto é, a transformação espaço 3D do modeloespaço 2D do dispositivo.

Vistas, câmaras e parâmetros

Uma vista de uma cena é obtida fazendo uma certa projeção do modelo, precisamente da mesma forma que uma câmara fotográfica produz uma imagem 2D de um ambiente 3D.

A projeção que define a vista é normalmente designada câmara e depende de um conjunto de parâmetros. Os mais comuns são:

Tipo

Tipo da projeção (ortográfica, projetiva, etc depende do sistema gráfico).

Abertura (FOV)

Ângulo de captura (depende do tipo de câmara).

Proporção (Ratio, Aspect)

Relação entre a altura e a largura (depende do tipo de câmara).

Perto (Near)

Distância do plano de corte «perto».

Longe (Far)

Distância do plano de corte «longe».

Exercícios 3D

Geometria 3D

  1. Vetor Perpendicular 1. Dados dois vetores 3D \(u = (u_x, u_y, u_z), v = (v_x, v_y, v_z)\), como é que encontra um terceiro vetor \(w=(w_x, w_y, w_z)\) que é perpendicular a ambos?

  2. Vetor Perpendicular 2. Como é que adapta a resolução do exercício anterior para o caso em que, em vez de dois vetores, são dados três pontos 3D, \(A=(A_x, A_y, A_z), B=(B_x, B_y, B_z), C=(C_x, C_y, C_z)\)?

Programação 3D

  1. Distância. Implemente uma função dist(p, q) que calcula a distância entre dois pontos. Suponha que os pontos são definidos por objetos {x, y, z}.

  2. Produto Externo. Implemente a função outer(u, v) que calcula o produto externo entre dois vetores. Supondo que \(w = u \otimes v\), então as componentes de \(w\) podem ser calculadas por

    \[w_x \mathbf{x} + w_y \mathbf{y} + w_z \mathbf{z} = \begin{vmatrix} \mathbf{x} & \mathbf{y} & \mathbf{z} \cr u_x & u_y & u_z \cr v_x & v_y & v_z \end{vmatrix}\]
Conjuntos de Pontos
  1. Centro. Implemente uma função center(points) que calcula o centro de um array de pontos.

  2. Próximo. Implemente a função nearest(target, points) em que target é um ponto 3D e points é um array de pontos 3D e que encontra o elemento de points que está mais próximo de target. A sua função deve devolver um objeto com atributos {d, p} em que d é a distância e p o ponto mais próximo.

  3. Afastado. Implemente a função farthest(target, points) em que target é um ponto 3D e points é um array de pontos 3D e que encontra o elemento de points que está mais afastado de target. Resolva de forma semelhante à alínea anterior.

  4. «Mais Isolado». Implemente a função loneliest(points) em que points é um array de pontos 3D e que encontra o ponto «mais isolado» de um conjunto de pontos. O ponto «mais isolado» é o que está mais distante do centro.

  5. «Mais Central». Implemente a função centralest(points) em que points é um array de pontos 3D e que encontra o ponto «mais central» de um conjunto de pontos. O ponto «mais central» é o que está mais perto do centro.

  6. Canto Superior Direito Anterior. Implemente a função corner_trf(points) (trf: top right front) em que points é um array de pontos 3D e que encontra o «canto superior direito anterior» (SDA) de um conjunto de pontos. As coordenadas do SDA são os valores máximos nos eixos x, y e z. Por exemplo:

let points =  [
    {x: -1, y: 2, z: 0},
    {x: 20, y: 1, z: 3}
];

{x: 20, y: 2, z: 3} === corner_trf(points);
O SDA não tem de ser um dos pontos dados.
  1. Canto Inferior Esquerdo Posterior. Implemente a função corner_llb(points) (llb: lower left back) nos mesmos moldes do exercício anterior, substituindo o máximo pelo mínimo.

  2. Pontos Mais Afastados. Implemente a função most_apart(points) em que points é um array de pontos 3D e que encontra os dois elementos de points que estão mais afastados um do outro. Isto é, se p, q forem esses pontos então, dados quaisquer dois pontos a e b em points, dist(a,b) <= dist(p,q).

  3. Pontos Menos Afastados. Implemente a função least_apart(points) em que points é um array de pontos 3D e que encontra os dois elementos distintos de points que estão menos afastados um do outro. Isto é, se p, q forem esses pontos então, dados quaisquer dois pontos a e b em points, dist(a,b) >= dist(p,q).

Processamento de Extrusões

Na construção de uma extrusão a secção percorre a espinha. Suponha que:

  • A secção é definida por um array de pontos 2D {x, z} (note que é usado z e não y).

  • A espinha é definida por um array de vetores 3D {x, y, z}.

Estes dois parâmetros definem os vértices da geometria obtida por extrusão. Mais concretamente, cada ponto p:{x, z} da secção e cada vetor v:{x, y, z} da espinha definem o vértice q = {x: p.x + v.x, y: v.y, z: p.z + v.z} da extrusão.

Por exemplo, dados

let seccao = [
    {x: 0.0, z: 0.0},
    {x: 1.0, z: 0.0},
    {x: 0.0, z: 1.0} ];

let espinha = [
    {x: 0.0, y: 0.0, z: 0.0},
    {x: 0.0, y: 1.0, z: 0.0},
    {x: 1.0, y: 2.0, z: -1.0} ];

o ponto seccao[2] === {x: 0.0, z: 1.0} e o vetor espinha[1] === {x: 0.0, y: 1.0, z: 0.0} definem o vértice {x: 0.0, y: 1.0, z: 1.0}.

  1. Escreva a função vertex(i, j, section, spine) que devolve as coordenadas xyz do vértice definido pelo i-ésimo ponto da secção e pelo j-ésimo passo da espinha.

    Por exemplo, usando as variáveis acima, vertex(1, 2, seccao, espinha) é o ponto 3D {x: 2.0, y: 2.0, z: -1.0}, que resulta do ponto {x: 1.0, z: 0.0} e do vetor {x: 1.0, y: 2.0, z: -1.0}.

    Assegure-se que i e j são índices válidos. Se não o forem a função deve devolver null.

Outros Casos
  1. Grelha de elevação em 3JS. A biblioteca 3JS não tem um construtor «direto» equivalente à ElevationGrid do X3D. Estude este elemento, determine três parâmetros que mais contribuem para esta geometria e faça uma implementação para o 3JS.

Modelação 3D

  1. Desenhar um referencial. Faça um documento X3D e um programa 3JS que desenhem um referencial XYZ. Assegure-se que os eixos estão corretamente orientados e use o seguinte esquema de cores: X:red; Y:green; Z:blue.

  2. Desenhar uma pirâmide. Faça um documento X3D e um programa 3JS que desenhem uma pirâmide em wireframe.

  3. Desenhar um frustum. Um frustum (veja também o artigo na wikipédia) é um poliedro (isto é, um sólido com todas as faces planas) com duas das faces quadradas paralelas (o topo e a base). Faça um documento X3D e um programa 3JS que desenhem um frustum em wireframe em que a base é um quadrado de lado 2, o topo um quadrado de lado 1 e com altura 1.

  4. Desenhar um octaedro.

    Octaedro

    Um octaedro é um dos cinco sólido platónicos (veja o artigo na wikipédia); tem oito faces triangulares e seis vértices:

    \[(0,0,-1), (-1,0,0), (0,-1,0), (1,0,0), (0,1,0), (0,0,1)\]

    Faça um documento X3D e um programa 3JS que desenhem um octaedro em wireframe.

  5. Desenhar um icosaedro.

    Icosaedro

    Um icosaedro é um dos cinco sólido platónicos (veja o artigo na wikipédia) e tem os vértices seguintes:

    \[\begin{aligned} && (0,\phi,1) && (0,\phi,-1) \cr && (-\phi,1,0) && (\phi,1,0) \cr (1,0,\phi) && (1,0,-\phi) && (-1,0,-\phi) && (-1,0,\phi) \cr && (-\phi,-1,0) && (\phi,-1,0) \cr && (0,-\phi,-1) && (0,-\phi,1) \end{aligned}\]

    onde \(\phi = \frac{\sqrt{5} + 1}{2}\). Faça um documento X3D e um programa 3JS que desenhem um icosaedro em wireframe.

  6. Pintar uma pirâmide. Faça um documento X3D e um programa 3JS que desenhem uma pirâmide com as texturas de difusão, brilho e normais obtidas de imagens feitas ou encontradas por si. Seja realista.

  7. Pintar uma tenda. Os vértices de uma tenda estão dados abaixo

    \[\begin{aligned} (0.0, 1.5, 0.0)\cr (-.7, 1.0, 0.7) && (0.7, 1.0, 0.7)\cr (0.7, 1.0, -.7) && (-.7, 1.0, -.7)\cr (-1., 0.0, 1.0) && (1.0, 0.0, 1.0)\cr (1.0, 0.0, -1.) && (-1., 0.0, -1.) \end{aligned}\]

    Faça um documento X3D e um programa 3JS que desenhem uma tenda pintada com texturas feitas (ou encontradas) por si. Seja realista.

Animação

Conceitos Fundamentais da Animação

Estroboscópio
Figura 3. Estroboscópio

Com animação uma cena estática ganha vida e torna-se mais informativa e interessante. Isto é:

Animação = Imagem x Tempo

Historicamente, a animação computacional descende do cinema e dos desenhos animados, onde uma sequência de imagens estáticas (fotogramas, em inglês frames) é apresentada em rápida sucessão.

Animação por Fotogramas

Uma animação por fotogramas define um conjunto fixo de imagens (os fotogramas) que são «projetadas» em sequência, com uma certa frequência («fotogramas por segundo», FPS).

O limiar humano de perceção anda perto dos 24FPS.

Se os fotogramas forem substituídas a um ritmo de 24 por segundo, a maioria das pessoas não distingue a passagem de um fotograma para o seguinte e interpreta «o que está a ver» como um movimento fluído.

Na realidade, 24 fotogramas por segundo está demasiado perto do limiar de perceção e a esta frequência muitas pessoas apercebem-se da substituição dos fotogramas.

Os primeiros filmes foram filmados e projetados entre 16FPS e 24FPS.

A frequência «padrão» 24FPS foi adotado no início da computação gráfica. Atualmente as placas gráficas facilmente proporcionam 60+FPS.

Figuras em Vaso Funerário

3º Milénio AEC

Limitações da Animação por Fotogramas

Uma animação por fotogramas é feita para uma certa frequência e não resulta bem a frequências diferentes.

Além disso, dispositivos diferentes, bem como «cargas» diferentes, proporcionam frequências diferentes.

A animação por tempo resolve o problema das diferenças de frequências na animação por fotogramas.

Animação por Tempo

Deste ponto em diante a matéria de Computação Gráfica muda substancialmente.

O foco deixa os sistemas gráficos específicos e passa para a representação e para o processamento de modelos gráficos.

Certifique-se que está confortável a programar.

Tabela 15. Interpolação de Valores Intermédios entre Duas Chaves
Chaves Valores Interpolados
chave[0]: {
    t : 0.5,
    parâmetros: {  x : 1.00  }
}
chave[1]:  {
    t : 2.5,
    parâmetros: {  x : 2.00  }
}

image

As chaves definem os pontos «vermelhos»

A interpolação calcula os pontos «verdes»

A animação por tempo resolve o problema das diferenças de frequências que a animação por fotogramas coloca.

Processo da Animação por Tempo
  1. São definidas chaves (momentos, keys) específicas.

    1. A cada chave são associados valores de parâmetros que definem um único fotograma.

  2. Durante a animação:

    1. Em cada momento os valores dos parâmetros são recalculados entre chaves consecutivas.

    2. O modelo gráfico é atualizado com os novos valores dos parâmetros e construído/desenhado de novo.

Tweens

Os tweens (contração do inglês «in-between») assentam uma técnica simples mas flexível e eficiente para calcular os valores intermédios entre um valor inicial \(x_0\) e um valor final \(x_1\) durante um certo intervalo de tempo \([t_0, t_1\)].
Fórmula para tweens lineares
\[x_t = \frac{(t_1 - t)x_0 + (t - t_0)x_1}{t_1 - t_0}\]
Um tween atualiza, ao longo de um certo tempo \(d\), um valor \(x(t)\) que varia entre \(x_0\) e \(x_1\).

Os parâmetros fundamentais de um tween são:

Valor Inicial

Em que valor começa a variação.

Valor Final

Em que valor termina a variação.

Duração

Quanto tempo demora a variação do valor inicial para o final.

Os tweens são usados para fazer uma animação com um modelo parametrizado.
Exemplo 2. Tween Linear

A forma mais simples de tween é linear:

\[x_t = \frac{(t_1 - t)x_0 + (t - t_0)x_1}{t_1 - t_0}\]

Para animações com «objetos naturais» este tween não funciona bem porque produz movimentos uniformes, com velocidade constante.

O resultado é semelhante ao movimento dos robots nos filmes antigos, ou de segunda categoria e também de alguns estilos de dança.

Os movimentos naturais não são uniformes. Alguns aceleram no início e travam no fim. Outros têm uma fase de «ganhar balanço». As variantes são muitas.
Easing

A aceleração de um tween é designada por easing e, em geral, tem as algumas variantes bem definidas.

As variantes mais comuns de easing são:

ease-in

Início gradual.

ease-out

Fim gradual.

ease-in-out

Início e fim graduais.

Definir Tweens com a Biblioteca Tween.js

Os tween da biblioteca Tween.js são bastante flexíveis e facilitam imenso a implementação das animações.

Os tweens desta biblioteca:

  1. São construídos com um valor inicial, um valor final e uma duração.

  2. Aceitam uma grande variedade de easings.

  3. Podem ser operados de várias formas:

    • yoyo: depois de atingir o valor final, o tween volta ao inicial.

    • repetir: o tween é repetido um certo número de vezes.

    • encadear: aplicar um segundo tween após a conclusão do primeiro.

    • etc.

Exemplo 3. Tweens e o Ciclo de Animação

Os ciclos de animação e de uso dos tweens têm essencialmente a mesma estrutura e podem ser facilmente unificados.

Ciclo de Animação Ciclo dos Tweens
m = initial_model()
while True:
    m.update()
    m.render()
t = Tween(x0, x1, d)
t.start()
while True:
    t.update()
    x = t.value()
    ...
Programação Web e Ciclos Infinitos

No contexto da programação na Web, os ciclos infinitos devem ser implementados com a função requestAnimationFrame.

Ciclo da Animação + tween na Web
function step(model) {
    model.parameter_1 = tween.parameter_1;  (1)
    ...
    draw(model);                            (2)
    TWEEN.update();                         (3)
    requestAnimationFrame(function() {      (4)
        step(model);
    });
}

let model = init_model();                   (5)
let tween = new TWEEN.Tween( ... ) ...;     (6)
tween.start();                              (7)
step(model);                         (8)
1 Atualiza os parâmetros do modelo com valores no tween.
2 Constrói o modelo atualizado.
3 Atualiza o sistema de tweens.
4 Próximo passo no ciclo de animação.
5 Inicialização do modelo.
6 Configuração do tween.
7 Início do tween.
8 Início do ciclo de animação.
Exemplo 4. Animação com um tween
Código
function draw(context, model) {
    context.fillStyle = "darkblue";
    context.fillRect(0, 0, 250, 60);
    context.fillStyle = "crimson";
    context.fillRect(model.x, 10, 40, 40);
}

function update(context, model) {
    draw(context, model);
    requestAnimationFrame(function() {
        update(context, model);
    });
    TWEEN.update();
}

function show_tween() {
    let context = document
        .getElementById("AnimCanvas")
        .getContext("2d");
    let model = {x: 10};
    let tween = new TWEEN.Tween(model)
        .to({ x: 200 }, 2000)
        .easing(TWEEN.Easing.Cubic.InOut)
        .yoyo(true)
        .repeat(Infinity);
    tween.start();
    requestAnimationFrame(function() {
        update(context, model);
    });
}

Animação por Passos

Embora flexíveis, nos tweens todos os valores estão determinados antes da animação começar. Uma vez iniciado, o tween vai seguir um «percurso» fixo, previsível.
Quando a animação dum objeto depende doutros objetos ou eventos, é necessário ir além dos tweens.

Por exemplo:

  • Com movimentos baseados na física é preciso tratar da aceleração, colisões com outros objetos, etc.

  • Os comportamentos reativos («seguir X», «olhar para Y», etc) dependem dos valores de outros objetos.

  • Os objetos controlados por interação (do teclado, rato, etc) dependem de valores externos ao próprio modelo.

A estrutura básica da animação por tempo, com modelos parametrizados e o ciclo

m = initial_model()     #   INICIALIZAÇÃO DO MODELO
while True:             #   CICLO DE ANIMAÇÃO
    m.update()          #       ATUALIZAR OS PARÂMETROS
    m.render()          #       CONSTRUIR/DESENHAR A IMAGEM

é válida em geral: a animação resulta da variação dos parâmetros calculada na atualização, quer esta variação resulte de tweens ou de outros cálculos.

Quando o tempo decorrido entre dois fotogramas não é constante, a atualização dos parâmetros do modelo tem de adaptar-se a essa variação.

Atualização Dinâmica

Atualização Dinâmica

Os tweens fazem automaticamente esta adaptação: O «valor» do tween é atualizado «internamente».

Para animações gerais, é necessário proporcionar, ao ciclo de animação, informação sobre o tempo: A forma universal e prática para lidar com a variação do tempo no ciclo da animação consiste em passar informação temporal para guiar a atualização dos parâmetros do modelo.

Revisitar os Ciclos de Animação
window.requestAnimationFrame(callback)

A função indicada como argumento (callback) é «chamada» cada vez que o sistema (por exemplo, o browser) está pronto para iniciar um novo passo.

Quando o callback é «chamado» recebe m um único argumento, timestamp que assinala esse preciso momento.

Exemplo 5. Animação dum elemento deste documento

Elemento animado: <span id="counter"></span>

function draw_model(canvas, model) {                (1)
    canvas.innerHTML = `Count: ${model.count}; ` +
        `Time: ${Math.round(model.time * 0.001)}s; ` +
        `Elapsed: ${Math.round(model.elapsed)}ms; ` +
        `FPS: ${Math.round(1000 * model.count \ model.time)}.`; // \ == divisão :(
}
//
let canvas = document.getElementById("counter");
let model = { count: 0, time: 0, elapsed: 0 };      (2)
let start = performance.now();                      (3)
let previous = performance.now();                   (4)
animation_step = function (timestamp) {
    let progress = timestamp - start;               (5)
    let elapsed = timestamp - previous;             (6)
    previous = timestamp;                           (7)
    model.count += 1;                               (8)
    model.time = progress;
    model.elapsed = elapsed;
    draw_model(canvas, model);                      (9)
    requestAnimationFrame(animation_step);          (10)
}
//
//      START THE ANIMATION CYCLE
requestAnimationFrame(animation_step);              (11)
//
1 Função para desenhar o modelo tendo em conta os valores dos parâmetros.
2 Inicialização do modelo.
3 Registo do momento início da animação.
4 Registo do momento passo anterior.
5 Cálculo da duração desde o início da animação.
6 Cálculo da duração desde o passo anterior.
7 Atualização do momento passo anterior.
8 Cálculo dos parâmetros do modelo.
9 Desenho do modelo.
10 Próximo passo da animação.
11 Início da animação.
Exemplo 6. Movimentos Baseados na Física

Para ilustrar uma aplicação da animação geral usamos uma (simples) simulação física do movimento uniformemente acelerado.

As leis do movimento (de Newton) definem o estado de uma partícula em termos de posição \(p\), velocidade \(v\) e aceleração \(a\) e como variam a posição e a velocidade em função da posição, velocidade, aceleração e do tempo decorrido \(\delta_t\):

\[\left\lbrace \begin{aligned} v &\gets v + a \delta_t \cr p &\gets p + v \delta_t \end{aligned} \right.\]
let model = {                               (1)
    x: 64, y: 64,           //  POSITION
    vx: 0, vy: 0,           //  VELOCITY
    ax: 0, ay: 0,           //  ACCELERATION
    r: 16,                  //  RADIUS
    min_x: 0, max_x: 256,   //  BOUNDS: X
    min_y: 0, max_y: 256,   //  BOUNDS: Y
    G: 0.25E-3, K: 0.8      //  PHYSICS CONSTANTS
};

// Record time of the animation start
let start = performance.now();
// Record time of the previous step
let previous = performance.now();

physdemo_step = function (timestamp) {      (2)
    let dt = timestamp - previous;          (3)
    previous = timestamp;

    if (timestamp - start < 1000.0) {       (4)
        model.ax = 2.0 * model.G;
    } else {
        model.ax = 0.0;
    }
    model.ay = model.G;

    model.vx = model.vx + model.ax * dt;    (5)
    model.vy = model.vy + model.ay * dt;

    model.x = model.x + model.vx * dt;      (6)
    model.y = model.y + model.vy * dt;

    if (model.x - model.r < model.min_x) {  (7)
        model.vx = -model.K * model.vx;
        model.x = model.min_x + model.r; }
    if (model.x + model.r > model.max_x) {
        model.vx = -model.K * model.vx;
        model.x = model.max_x - model.r; }
    if (model.y - model.r < model.min_y) {
        model.vy = -model.K * model.vy;
        model.y = model.min_y + model.r; }
    if (model.y + model.r > model.max_y) {
        model.vy = -model.K * model.vy;
        model.y = model.max_y - model.r; }

    draw_physmodel(context, model);         (8)

    requestAnimationFrame(physdemo_step);   (9)
}
1 Um modelo simples com leis da física.
2 Passo do modelo.
3 Tempo decorrido desde o passo anterior.
4 Atualização da aceleração.
5 Atualização da velocidade.
6 Atualização da posição.
7 Tratamento das colisões.
8 Desenhar o modelo.
9 Passo seguinte.

Exercícios Animação

Exceto quando indicado o contrário, cada exercício é para ser resolvido em SVG, C2D, X3D e 3JS.

  1. Percursos Lineares Uniformes. Desenhe uma forma simples (por exemplo, uma bola ou uma esfera azul) e desloque-a:

    1. 200 pixeis para a direita.

    2. Num retângulo com 200 pixeis de largura e 100 de altura.

    3. Num caminho poligonal com \(n\) pontos.

  2. Percursos Curvos. Como é que representa percursos «curvos»? Por exemplo, percursos circulares ou em onda. Leve a forma do anterior a percorrer um percurso em forma de:

    • Círculo.

    • Seno (ondas).

    • Curva \(x^3\).

    • Um caminho poligonal com \(n\) pontos, definido por uma equação paramétrica.

  3. Percursos Compostos. Como é que representa percursos definidos por várias «partes"? Por exemplo, um percurso como ilustrado na figura seguinte:

  4. Percursos Acelerados. Repita o exercício dos percursos compostos, mas a forma:

    1. Acelera no início de cada segmento e trava no fim.

    2. Acelera no início do percurso e trava no fim.

Exemplos

Comportamento: Seguidor

Enki Bilal - Desenho Original
Figura 4. Enki Bilal - Desenho Original
Exemplo de Animação: Comportamento «Seguidor»

Neste exemplo vai ver:

  1. Uma animação por Interpolação.

  2. Uma animação por Passos.

  3. Um «comportamento» de um objeto gráfico: Seguidor.

Neste modelo criamos um líder (leader) que percorre os quatro cantos da imagem num ciclo infinito e um seguidor (follower) que «tenta» chegar à posição do líder. Veja o resultado aqui.

Como a animação do líder depende apenas do tempo, pode ser implementada com um tween.

Já a animação do seguidor é sensível à posição do líder, pelo que tem de ser implementada por passos.

Especificação do Modelo
  • A imagem é um quadrado de lado 1.0, com o canto superior esquerdo na origem do referencial (de ecrã).

  • O líder (vermelho) e o seguidor (azul) são quadrados de lado 0.1 e a «âncora» desses objetos é o centro do quadrado.

  • O líder percorre o seguinte «circuito», com cada «troço» a durar `1.5`segundos:

    • (0.2, 0.2), perto do CSE.

    • (0.2, 0.8), perto do CIE.

    • (0.8, 0.8), perto do CID.

    • (0.8, 0.2), perto do CSD.

  • Para tornar a animação um pouco mais «natural», o easing do movimento do líder é elástico no início e no fim.

O modelo é um objeto javascript com a seguinte estrutura:

    let model = {
        background: {
            color: "khaki",
        },
        leader: {
            x: 0.2,
            y: 0.2,
            color: "crimson",
        },
        follower: {
            x: 0.8,
            y: 0.2,
            speed: 0.005,
            color: "steelblue",
        },
        last_timestamp: performance.now(),
        frame_count: 0,
    }
Construção: Desenhar o modelo é uma tarefa simples
function render(model) {
    this.enter(0, 0,                                    (1)
        this.canvas.width, this.canvas.height, 0);      (3)

    this.message(`FRAME COUNT: ${model.frame_count}`);  (2)

    this.fillStyle = model.background.color;            (4)
    this.fillRect(0, 0, 1, 1);                          (4)

    this.fillStyle = model.leader.color;
    this.fillRect(
        model.leader.x - 0.05, model.leader.y - 0.05,
        0.1, 0.1);

    this.fillStyle = model.follower.color;
    this.fillRect(
        model.follower.x - 0.05, model.follower.y - 0.05,
        0.1, 0.1);

    this.leave();                                       (3)
}
1 this refere-se ao contexto gráfico 2D de um elemento canvas do documento.
2 O método message limita-se a escrever um texto num elemento do documento html (exercício).
3 Os métodos enter e leave já foram previamente explicados, comas Transformações.
4 Os restantes métodos são standard do C2D.
De seguida vai ser analisada a função dos atributos e como são atualizados.
Animação do Líder

O líder percorre os quatro cantos da imagem num ciclo infinito.

Esta animação é «independente» do estado do restante modelo. Portanto pode ser implementada por um tween.

Tween para o movimento do líder
let down = new TWEEN.Tween(model.leader)        (1)
        .to({y: 0.8}, 1500)
        .easing(TWEEN.Easing.Elastic.InOut);
let right = new TWEEN.Tween(model.leader)       (1)
        .to({x: 0.8}, 1500)
        .easing(TWEEN.Easing.Elastic.InOut);
let up = new TWEEN.Tween(model.leader)          (1)
        .to({y: 0.2}, 1500)
        .easing(TWEEN.Easing.Elastic.InOut);
let left = new TWEEN.Tween(model.leader)        (2)
        .to({x: 0.2}, 1500)
        .easing(TWEEN.Easing.Elastic.InOut);
//
down.chain(right);                              (2)
right.chain(up);
up.chain(left);
left.chain(down);
//
...                                             (3)
//
down.start();                                   (4)
requestAnimationFrame(animation_step)           (4)
1 Definir cada troço do percurso.
2 Encadear os troços.
3 Tratar de outros assuntos anted de começar o ciclo de animação.
4 Inicial o tween e o ciclo de animação.
Durante o ciclo de animação os parâmetros controlados pelo interpolador têm de ser atualizados. Para esse efeito, basta uma única instrução TWEEN.update() na função de atualização do modelo.
Atualização dos tweens com o modelo.
function update(model) {
    // SOME TIME MANAGEMENT STUFF
    // ...
    TWEEN.update();     // UPDATE TWEEN'ED PARAMETERS
    // UPDATE OTHER PARAMETERS
    // ...
    return model;
}
Animação do Seguidor

O seguidor aproxima-se gradualmente da posição do líder, a partir da posição em que está.

Como atualizar a posição do seguidor?

Geometricamente, seja \(P\) a posição \((x, y)\) do seguidor e \(Q\) a posição \((x, y)\) do líder. Então \(d = Q - P\) é o vetor que «transporta» \(P\) para \(Q\). Estas operações podem ser facilmente implementadas na função de atualização do modelo.

Atualização da posição do seguidor - versão 1 «Ingénua»
function update(model) {
    // ...
    let dx = model.leader.x - model.follower.x;
    let dy = model.leader.y - model.follower.y;
    //
    model.follower.x += dx;
    model.follower.y += dy;
    // ...
    return model;
}
Há dois problemas evidentes nesta versão
  1. Não se adapta à duração dos passos

  2. O seguidor salta imediatamente para a posição do líder, sem se observar o efeito pretendido, de «aproximação gradual».

Portanto a atualização dos parâmetros da posição do seguidor tem de ser mais sofisticada. Para esse efeito o atributo leader.speed serve para atenuar o movimento do seguidor:

Atualização da posição do seguidor - versão 2 «Atenuada»
function update(model) {
    //
    // UPDATE TIME STUFF
    //
    let timestamp = performance.now();
    let dt = timestamp - model.last_timestamp;
    model.last_timestamp = timestamp;
    // ...
    let dx = model.follower.speed * (model.leader.x - model.follower.x);
    let dy = model.follower.speed * (model.leader.y - model.follower.y);
    //
    model.follower.x += dx * dt;
    model.follower.y += dy * dt;
    // ...
    return model;
}
Juntar Todas as Peças

Falta iniciar a animação e detalhar o passo de animação.

Estrutura Completa do Código
function init_model() {}                (1)
function render(model) {}               (2)
function enter(x, y, sx, sy, a) {}      (3)
function leave() {}                     (3)

function update(model) {                (4)
    let timestamp = performance.now();  (5)
    let dt = timestamp - model.last_timestamp;
    model.last_timestamp = timestamp;
    //
    model.frame_count += 1;             (6)
    //
    ...                                 (7)
    ...                                 (8)

    return model;
}
//
function animate(gc) {                  (9)
    let model = init_model();
    function animation_step(timestep) {
        model = update(model);
        gc.render(model);
        requestAnimationFrame(animation_step);
    };

    down.start();
    requestAnimationFrame(animation_step);
}

function main() {   (10)
    console.log("I'm alive!");

    let gc = document.getElementById("canvas").getContext("2d");
    gc.render = render;
    gc.enter = enter;
    gc.leave = leave;
    let message = document.getElementById("message")
    gc.message = (text) => { message.innerHTML = text; }
    let width = 256;
    let height = 256;
    gc.canvas.width = width;
    gc.canvas.height = height;
    //
    animate(gc);
}
1 Inicialização do modelo
2 Desenhar/Construir o modelo
3 Transformações
4 Atualização por passos do modelo
5 Registos do tempo
6 Contador de ciclos
7 Atualizar parâmetros dos tweens (posição do líder)
8 Atualizar parâmetros por passos (posição do seguidor)
9 Ciclo de animação
10 Ligação ao dom, extensão do context, início da animação
Desafios
  1. Torne o movimento do seguidor mais natural usando equações da física e controlando o movimento com acelerações.

  2. Faça o mesmo para o movimento do líder. Deve continuar a usar tweens, mas controlar a aceleração em vez da posição. O movimento deve continuar a seguir os quatro cantos da imagem.

  3. Defina um atributo para controlar a «sensibilidade» do seguidor. Um seguidor mais «sensível» reage com maior intensidade do que outro menos «sensível».

  4. Crie um modelo com n seguidores, em posições e com sensibilidades aleatórias. Experimente vários valores do atributo n.

  5. Detete colisões:

    1. Se dois seguidores colidirem (por exemplo, a distância entre eles é inferior a c = 0.05) simule um efeito de «ricochete». Experimente outros valores do atributo c.

    2. Adicionalmente, da colisão resulta um terceiro (novo) seguidor de cor aleatória (procriação de seguidores).

  6. Continue o exercício anterior mas, adicionalmente, cada seguidor «morre» se não se aproximar do líder menos de d = 0.1 durante k = 100 passos consecutivos. Experimente outros valores para os atributos d e k.

  7. Continue o exercício anterior mas, adicionalmente, anime a cor de cada seguidor, aproximando-a de cinzento neutro #C0C0C0 conforme envelhece mas, se ficar perto do líder, rejuvenesce.

Gerador SVG

Negreiros - Corvos de Lisvoa
Figura 5. Negreiros - Corvos de Lisboa
Gerador de svg em JavaScript

Podemos ultrapassar algumas limitações do sistema svg, principalmente no que diz respeito a objetos gráficos com repetições (por exemplo, um tabuleiro de xadrez) e aos aspetos dinâmicos (por exemplo, a animação), usando as funções do dom do html e JavaScript.

O objetivo deste exemplo é mostrar como criar um objeto svg usando o JavaScript no browser. Pode ver o resultado aqui.

É necessário:
  1. Um documento html com um elemento identificado.

  2. Um modelo da cena.

  3. Uma função JavaScript que compile o modelo para svg.

Versão mínimo do documento HTML
<!DOCTYPE html>
<html lang="pt">

<head>
    <meta charset="utf8">
    <script src="script/svg-generator.js">  (1)
    </script>
</head>

<body onload='main();'>                     (2)
    <div id = "svg-container"></div>        (3)
</body>
</html>
1 O ficheiro/biblioteca JavaScript com funções para aceder ao dom e compilar o modelo da cena para svg.
2 Inicia o programa.
3 Elemento no dom do documento html onde vai ser construído o svg.
Especificação do Grafo de Cena (isto é, do Modelo)

Um documento svg é uma árvore em que os nós têm um tipo (tag), atributos (attributes) e filhos (children). O tipo é apenas uma string, os atributos são um mapa (e.g. um objeto JavaScript) e os filhos são uma lista de nós.

Outra forma simples de especificar o modelo é usando um objeto JavaScript (e.g. JSON)
let model = {
    tag: 'svg',                                 (1)
    attributes: {                               (2)
        width: 256,
        height: 256 },
    children: [
        {   tag: 'rect',                        (3)
            attributes: {
                x: 0,
                y: 0,
                width: 256,
                height: 256,
                fill: 'crimson' } },            (4)
        {
            tag: 'circle',
            attributes: {
                cx: 128,
                cy: 128,
                r: 68,
                style: 'fill:darkolivegreen;'   (5)
} } ] };
1 A raíz do modelo é um elemento de tipo svg.
2 Ainda na raíz, especificam-se os atributos que definem o tamanho da cena.
3 Os filhos do nó raíz são os objetos gráficos que formam a cena.
4 Uma forma de definir a cor do interior deste objeto.
5 Uma forma diferente de definir a cor do interior.
O modelo acima define o seguinte elemento svg:
<svg                            (1)
    width= "256"                (2)
    height="256">
    <rect                       (3)
        x="0"
        y="0"
        width="256"
        height="256"
        fill="crimson"></rect>
    <circle
        cx="128"
        cy="128"
        r="68"
        style="fill:darkolivegreen;"></circle>
</svg>
1 Este é o valor de model.tag.
2 Resulta de model.attributes.width.
3 Resulta de model.children[0].tag.
Compilar um Elemento svg

É necessário compilar (i.e. transformar) um modelo como o exemplo acima para um elemento adequado para injetar no dom do documento html.

/**
 * Creates an svg element.
 *
 * The element is defined by an object with attributes:
 *
 * - tag: The element's tag (eg 'rect', 'svg', 'path').
 * - attributes (optional): The element's attributes (e.g 'x', 'fill').
 * - children (optional): list of sub-elements specifications.
 *
 * @param {object} spec - the specification of the svg element.
 */
function svg(spec) {
    let element = document.createElementNS(
        'http://www.w3.org/2000/svg',
        spec.tag);                                  (1)
    if (spec.hasOwnProperty('attributes')) {        (2)
        for (let attr in spec.attributes) {
            element.setAttributeNS(null,
                attr, spec.attributes[attr]);       (3)
        }
    }
    if (spec.hasOwnProperty('children')) {          (4)
        for (let child_spec of spec.children) {
            let child = svg(child_spec);            (5)
            element.appendChild(child);             (6)
        }

    }
    return element;                                 (7)
}
1 Sintaxe para criar corretamente um elemento svg. Consulte a documentação sobre createElementNS em mdn.
2 Aplicação dos atributos deste elemento.
3 Sintaxe para aplicar atributos. Consulte a documentação sobre setAttributeNS em mdn.
4 Construção dos filhos deste elemento.
5 Um filho é, ainda, um elemento svg. Portanto este passo é recursivo.
6 Acrescentar o nó filho. Consulte a documentação sobre appendChild em mdn.
7 That’s all, folks.
Aceder ao dom do documento html

Esta parte consiste essencialmente em ligar as várias partes ilustradas acima

let svg_container = document.getElementById('svg-container');   (1)
let svg_element = svg(model);                                   (2)
svg_container.appendChild(svg_element);                         (3)
1 Obter o elemento svg-container do dom do documento html.
2 Compilar o modelo para um elemento svg.
3 Injetar o elemento svg no dom do documento html.
Exercícios
  1. Neste exemplo não foi mostrada a totalidade do ficheiro/biblioteca svg-generator.js porque é um exercício.

  2. Use esta base para criar um elemento svg um pouco mais sofisticado. Por exemplo, um tabuleiro de xadrez.

  3. Continue a tornar o elemento svg mais rico, fazendo animações. Anime não só parâmetros de posição/tamanho/rotação mas também parâmetros de aspeto. Faça uma geometria deformável, por exemplo um triângulo que evolui para um quadrado, depois para um pentágono, etc.

C2D com SVG

Objetivo

Animação de um Camião

C2D

SVG

Grafo de Cena
scene(x,a):
    ground:  rect, saddlebrown
    truck(x, a):
        weel_1: circle, black
        weel_2: circle, black
        weel_3: circle, black
        base: rect, olive
        cabin_box: path, darkorange
        cabin_glass: path, skyblue
        cargo(a):
            0: circle, steelblue
            1: rect, steelblue

C2D

Levantamento do Problema
Desenhar Figuras
shape

Definir Formas.

aspect

Traçar o Contorno e Pintar o Interior.

transform

Aplicar Translações, Escalas e Rotações.

children

Construir Figuras Complexas com outras mais simples.

let figure = { shape, aspect, transform, children };
Animar Figuras

Pretendemos uma representação das figuras (e da cena) que seja simultâneamente simples, intuitiva, flexível e eficiente.

let figure = {
    shape: unit_circle(32),
    aspect: { fill: 'orange', stroke: 'blue' },
    transform: {
        x: 50, y: 50,
        sx: 15, sy: 15,
        a: 0.0 },
    children: [ {
        transform: {
            x: 2, y: 0,
            sx: 0.25, sy: 0.25,
            a: 0.0 },
        shape: unit_circle(32),
        aspect: {
            fill: 'crimson',
            stroke: 'green' }
        } ]
}
Definir Formas

Já temos instruções para desenhar caminhos arbitrários: .beginPath(), .closePath(), .moveTo(), .lineTo(), .quadraticCurveTo(), .bezierCurveTo().

Para desenhar diretamente com estas instruções, o caminho «é» um fragmento de código, pouco flexível.

Podemos representar o contorno de uma figura por um conjunto de segmentos de reta, ou seja, uma lista de pontos (x,y) no plano.

let quadrado = [
    {x: 1, y:1},
    {x: 1, y:-1},
    {x: -1, y:-1},
    {x: -1, y:1}
];

Contornos mais complexos usam mais segmentos e contornos curvos podem ser aproximadas por muitos segmentos. A lista de pontos pode ser gerada por uma função. Por exemplo unit_circle(n), nagon(n), star(n,r)

Agora precisamos de uma função que leia a lista de pontos e desenhe o contorno, com as instruções beginPath, closePath, moveTo, lineTo.

Função para desenhar um contorno arbitrário.
function draw_shape(points) {
    this.beginPath();
    let p0 = points[0];
    this.moveTo(p0.x, p0.y);
    for (let p of points) {
        this.lineTo(p.x, p.y);
    }
    this.lineTo(p0.x, p0.y);
    this.closePath();
}

Aos passarmos de caminhos definidas por funções para caminhos definidas por dados as figuras passam a ser estruturas de dados que podem ser processadas.

Por exemplo, uma figura pode ficar «deformada» por uma «colisão».

Traçar e Pintar

A representação de traços e de tintas é baseada em dados (.fillStyle, .strokeStyle) e em instruções (.fill(), .stroke()).

Nem sempre pretendemos aplicar o traço ou encher o interior.

Os «objetos» do javascript têm um método de «auto-inspeção», .hasOwnProperty(), que permite testar se um atributo está, ou não, presente no objeto.

Usamos esse método para fazer uma aplicação adaptativa do aspeto da figura.

Fragmento de código adaptativo para o aspeto.
let a = figure.aspect;
// «Smart» Fill
if (a.hasOwnProperty('fill')) {
    this.fillStyle = a.fill;
    this.fill();
}
// «Smart» Stroke
if (a.hasOwnProperty('stroke')) {
    this.strokeStyle = a.stroke;
    this.stroke();
}
Transformar

As transformações mais comuns são as translações, escalas e rotações. Aplicadas sequencialmente resolvem os problemas de orientação, tamanho e posição das figuras.

Como vimos, a aplicação de transformações não é comutativa: rodar primeiro e depois deslocar não produz o mesmo resultado que deslocar primeiro e depois rodar.

Uma das sequências de transformações mais comum é Translate, Scale, Rotate, usada para «entrar» no espaço da figura ascendente.

Funções para «entrar» e «sair» no espaço/referencial de um sub-objeto.
function enter(x, y, sx, sy, a) {
    this.save();
    this.translate(x, y);
    this.scale(sx, sy);
    this.rotate(a);
}

function leave() {
    this.restore();
}

O uso das instruções .save(), .restore() permitem delimitar a aplicação das transformações.

Compor

Precisamos de representar figuras complexas, compostas por sub-figuras.

Em particular, queremos que as figuras «filhas» sejam transformadas juntamente com a figura «mãe».

Um forma de representar as figuras «filhas» consiste em definir uma lista de figuras:

let figure = {
    ...
    children: [ child_1, child_2, ... ]
}
Juntar Tudo
Função para desenhar um objeto gráfico, incluindo sub-objetos.
//
function draw(figure) {
    //
    //      TRANSFORM
    let t = figure.transform;
    //
    // HACK to side-track scale on lineWidth;
    let save_lineWidth = this.lineWidth;
    let scale_lw = 0.5 * (t.sx + t.sy);
    this.lineWidth = this.lineWidth / scale_lw;
    //
    //      Apply TRANSFORM
    this.enter(t.x, t.y, t.sx, t.sy, t.a);
        //
        //      Recursively, DRAW CHILDREN
        if (figure.hasOwnProperty('children')) {
            for (let child of figure.children) {
                this.draw(child);
            }
        }
        //      DRAW SHAPE
        if (figure.hasOwnProperty('shape')) {
            this.draw_shape(figure.shape);
        }
    //      Reset TRANSFORM
    this.leave();
    // Reset lineWidth HACK
    this.lineWidth = save_lineWidth;
    //
    //      ASPECT
    if (figure.hasOwnProperty('aspect')) {
        let a = figure.aspect;
        // ================ Smart Fill
        if (a.hasOwnProperty('fill')) {
            this.fillStyle = a.fill;
            this.fill();
        }
        // ================ Smart Stroke
        if (a.hasOwnProperty('stroke')) {
            this.strokeStyle = a.stroke;
            this.stroke();
        }
    }
}
//
Animação

Pretendemos usar a biblioteca tween.js para tratar as animações.

O processo para definir uma animação consiste em dois passos:

  1. Definir um modelo parametrizado.

  2. Associar o tempo decorrido a valores dos parâmetros.

No grafo de cena, focamo-nos apenas no camião (truck).

Modelo do camião
let lo_circle = unit_circle(25); let rect = unit_rect();
//
let weel_1 = {
    shape: lo_circle,
    aspect: {fill: 'black'},
    transform: {x: 0.75, y: 0.5, sx: 0.2, sy: 0.2, a: 0}, }
let weel_2 = {
    shape: lo_circle,
    aspect: {fill: 'black'},
    transform: {x: -0.7, y: 0.5, sx: 0.2, sy: 0.2, a: 0}, }
let weel_3 = {
    shape: lo_circle,
    aspect: {fill: 'black'},
    transform: {x: -0.25, y: 0.5, sx: 0.2, sy: 0.2, a: 0}, }
let base = {
    shape: rect,
    aspect: {fill: 'olive'},
    transform: {x: 0, y:0.3, sx: 1.0, sy: 0.075, a: 0}, }
let cabin_box = {
    shape: [
        {x: 0.0, y: 0.0},  {x: 1.0, y: 0.0},
        {x: 1.0, y: 1.0},  {x: -1.0, y: 1.0},
        {x: -1.0, y:-1.0}, {x: 0.0, y:-1.0} ],
    aspect: {fill: 'darkorange'},
    transform: {x: 0.5, y: -0.17, sx: 0.4, sy: 0.4, a: 0}, }
let cabin_glass_pts = unit_sector(16, -0.5 * Math.PI);
    cabin_glass_pts.push({x:0.0, y: 0.0});
let cabin_glass = {
    shape: cabin_glass_pts,
    aspect: { fill: 'skyblue', stroke: 'black'},
    transform: {
        x: 0.5, y: -0.17,
        sx: 0.4, sy: 0.4,
        a: 0.0}, }
let cargo = {
    transform: {x: -0.9, y: 0.1, sx: 1.0, sy: 1.0, a: 0.0},
    children: [ {
            shape: lo_circle,
            aspect: {fill: 'steelblue', stroke: 'black'},
            transform: {x: 0, y: 0, sx: 0.1, sy: 0.1, a: 0},
        }, {
            shape: rect,
            aspect: {fill: 'steelblue'},
            transform: {
                x: 0.2, y: -0.25,
                sx: 0.75, sy: 0.25,
                a: 0.0}, }], }
let truck  = {
    transform: {
        x: 100.0, y: 70.0,
        sx: 70.0, sy: 70.0,
        a: 0.0},
    children: [
        base,
        cabin_box, cabin_glass,
        weel_1, weel_2, weel_3,
        cargo],
    cargo: cargo, }
Exemplo 7. Associar Tempo e Parâmetros

A animação tem duas partes:

  1. O camião desloca-se para a direita.

  2. A caixa do contentor roda para a esquerda e volta à posição inicial.

Definimos cada fase isoladamente e formamos uma cadeia com ambas.

Código para a animação.
let truck_anim = {
    from: { x: truck.transform.x, },
    target: { x: 400, },
    duration: 2000,
    easing: TWEEN.Easing.Quadratic.InOut,
    update_func: function (step) {
        gc.clear();
        truck.transform.x = step.x;
        gc.draw(ground);
        gc.draw(truck); }, }
let cargo_anim = {
    from: { a: truck.cargo.transform.a, },
    target: { a: -0.25 * Math.PI, },
    duration: 500,
    easing: TWEEN.Easing.Quadratic.InOut,
    update_func:  function (step) {
        gc.clear();
        truck.cargo.transform.a = step.a;
        gc.draw(ground);
        gc.draw(truck); }, }
let truck_tween = make_tween(truck_anim);
let cargo_tween = make_tween(cargo_anim)
        .yoyo(true)
        .repeat(1);
truck_tween.chain(cargo_tween).start();

SVG

camião
Figura 6. O modelo inicial em SVG
Levantamento do Problema
Desenhar Figuras
shape

Definir Formas.

aspect

Traçar o Contorno e Pintar o Interior.

transform

Aplicar Translações, Escalas e Rotações.

children

Construir Figuras Complexas com outras mais simples.

Todas estas caraterísticas são «nativas» do SVG.

Animar Figuras
  • Definir o Modelo Parametrizado.

  • Associar Tempo e Parâmetros.

Definir o Modelo Parametrizado

O SVG define modelos essencialmente estáticos, com animações «nativas» muito simples.

Cada documento HTML define uma API (o «DOM») que permite programaticamente (isto é, num programa javascript) aceder aos elementos desse documento.

Este acesso é concretizado, por exemplo, pelo método document.getElementById( ... ).

A afirmação acima aplica-se quer a sub-elementos SVG quer a documentos externos importados via object e proporciona uma forma para «parametrizar» modelos SVG.

Documento HTML
Fragmento HTML com elementos para «alojar» as animações.
<canvas id="C2DAnim" width="512" height="136"></canvas>
<object id="SVGAnim" data="anim.svg" type="image/svg+xml"></object>
Instruções javascript
Acesso aos sub-objetos gráficos do documento SVG.
let elem = document
  .getElementById("SVGModel")
  .contentDocument;
let truck = elem
  .getElementById("truck");
truck.setAttribute(
  "transform",
  "translate(10, 10)");

Para parametrizar documentos SVG é necessário:

  1. Identificar os sub-elementos a parametrizar, usando o atributo id="...".

  2. Aceder ao sub-elemento, através, por exemplo, do método .getElementById("...").

  3. No caso de documentos SVG «externos», pode ser necessário inserir o modelo através de um elemento object ou embed.

Transformações
Para ler e definir programaticamente transformações de elementos SVG o processo é um pouco mais complicado do que devia.
Ler .transform (incompleto!)
function get_transform(elem) {
    let item = elem.transform.baseVal.getItem(0)
    let m = item.matrix;
    return {
        x: m.e, y: m.f,
        sx: m.a, sy: m.d,
        a: item.angle
    }
}
Definir .transform
function set_transform(elem, t) {
    let item = elem.transform.baseVal.getItem(0);
    let m = item.matrix;
    if (t.hasOwnProperty('x')) m.e = t.x;
    if (t.hasOwnProperty('y')) m.f = t.y;
    if (t.hasOwnProperty('sx')) m.a = t.sx;
    if (t.hasOwnProperty('sy')) m.d = t.sy;
}

Para definirmos a rotação, que funciona em relação a um centro não evidente, é necessária uma técnica um pouco diferente e ad-hoc:

Definir rotações para o SVG.
element.setAttribute('transform',
    `translate(DX,DY)rotate(ANGLE)`);
Animação

A animação para o exemplo SVG tem a mesma estrutura que foi usada para o exemplo CanvasRenderingContext2D.

Basta, portanto, alterar a parte que diz respeito à atualização do modelo.

Com estas funções podemos associar o tempo e os parâmetros de forma muito semelhante ao que fizemos no caso do CanvasRenderingContext2D.

Associar Tempo e Parâmetros
let truck_anim = {
    from: { x: get_transform(truck).x, },
    target: { x: 400, },
    duration: 2000,
    easing: TWEEN.Easing.Quadratic.InOut,
    update_func: function (step) {
        set_transform(truck, {x: step.x});
    }, }
let cargo_t0 = get_transform(cargo);
let cargo_anim = { from: { a: cargo_t0.a, },
    target: { a: -0.25 * 180, },
    duration: 500,
    easing: TWEEN.Easing.Quadratic.InOut,
    update_func:  function (step) {
        cargo.setAttribute(
            'transform',
            `translate(-0.9,0.1)rotate(${step.a})`); },
}

let truck_tween = make_tween(truck_anim);
let cargo_tween = make_tween(cargo_anim)
        .yoyo(true)
        .repeat(1);
truck_tween.chain(cargo_tween).start();

Cenário 2D Animado

O Problema da Animação ficou dividido em duas partes:

  1. Desenhar Figuras.

  2. Animar Figuras.

Para Desenhar o Modelo (e as figuras que o formam) de forma parametrizada usamos atributos para definir:

  • Formas (shape).

  • Aspeto (aspect).

  • Transformações (transform).

  • Descendentes (children).

Sistemas Gráficos diferentes permitem concretizar estes atributos de formas diferentes.

Nestes exemplos, um modelo foi definido usando CanvasRenderingContext2D e outro por um documento «standalone" SVG, incluído num documento HTML.

Para Animar o Modelo (isto é, as figuras que o formam) definimos:

  • Os Parâmetros do Modelo.

  • A Associação do Tempo e dos Parâmetros.

Nestes exemplos, a Associação entre o Tempo e os Parâmetros foi feita por Tweens.

Desafios

  1. Implemente a função make_tween.

  2. Melhorar a construção de figuras. Em shape permitir segmentos quadráticos e cúbicos.

  3. Usar um único modelo para criar e animar SVG e C2D.

  4. Difícil. Fazer uma animação com física e colisões. Fazer com que o camião derrube uma parede.

  5. Faça um modelo simples (por exemplo, uma bola a saltitar no chão) e visualize animações desse modelo nos quatro sistemas gráficos: C2D, SVG, X3D e 3JS.

Anexos

Documento SVG
Documento anim.svg
<!DOCTYPE svg PUBLIC
    "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="512" height="136"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <rect id="urect"
            x = "-1" y = "-1"
            width = "2" height = "2"></rect>
        <circle id="ucirc"
            cx = "0" cy = "0" r = "1"></circle>
    </defs>
    <g id="ground"
        transform="translate(0,124)scale(512,10)">
        <use xlink:href="#urect"
            transform="translate(0,0)scale(1,1)"
            fill="saddlebrown" />
    </g>
    <g id="truck"
        transform="translate(100,70)scale(70,70)">

        <use xlink:href="#urect"
            id = "t_base"
            transform="translate(0,0.3)scale(1,0.075)"
            fill="olive" />

        <path id="t_cabin_box"
            transform="translate(0.5, -0.17)scale(0.4,0.4)"
            fill="darkorange"
            d=" M 0 0
                L 1 0
                L 1 1
                L -1 1
                L -1 -1
                L 0 -1
                Z " />

        <path id="t_cabin_glass"
            fill = "skyblue" stroke="black"
            stroke-width="0.05"
            transform="translate(0.5,-0.17)scale(0.4,0.4)"
            d=" M 0 0
                L 1 0
                A 1 1 0 0 0 0 -1
                L 0 0 " />

        <use id="t_weel_1"
             fill="black"
             transform="translate(0.75, 0.5)scale(0.2,0.2)"
             xlink:href="#ucirc" />

        <use id="t_weel_2"
             fill="black"
             transform="translate(-0.7, 0.5)scale(0.2,0.2)"
             xlink:href="#ucirc" />

        <use id="t_weel_3"
             fill="black"
             transform="translate(-0.25, 0.5)scale(0.2,0.2)"
             xlink:href="#ucirc" />

        <g id="cargo"
            transform="translate(-0.9,0.1)scale(1,1)">
            <use xlink:href="#ucirc"
                transform="scale(0.1,0.1)"
                stroke-width="0.25"
                fill="steelblue" stroke="black" />
            <use id="t_cargo" xlink:href="#urect"
                transform="translate(0.2,-0.25)scale(0.75,0.25)"
                fill="steelblue" />
        </g>
    </g>
</svg>