Pular para o conteúdo principal

Aula 04 — Instanciamento e Transformações Geométricas 2D

Objetivos​

Ao final desta aula você deve ser capaz de:

  • Distinguir instanciamento de transformação geométrica, e explicar por que os aros da Aula 03 já eram instanciados sem usar nenhuma transformação.
  • Aplicar as fórmulas de translação, escala e rotação a um ponto, e prever o efeito visual de cada uma isoladamente.
  • Justificar por que a translação não pode ser escrita como uma matriz 2×2 multiplicando o ponto, e descrever como as coordenadas homogêneas resolvem esse problema.
  • Compor transformações por multiplicação de matrizes e prever, para uma ordem dada, se o resultado muda ao trocá-la.
  • Determinar sob que condição rotação e escala comutam, e construir um contraexemplo para quando elas não comutam.
  • Escalar ou rotacionar um objeto em torno de um ponto arbitrário, compondo translação, a transformação desejada e a translação inversa.
  • Usar glTranslatef, glRotatef e glScalef para posicionar, orientar e dimensionar objetos, e explicar por que a ordem das chamadas no código é o inverso da ordem em que elas afetam o vértice.
  • Isolar transformações com glPushMatrix/glPopMatrix, e diagnosticar o efeito de esquecer um glPopMatrix em um laço de instâncias.

Conteúdo​

Retomando: o que muda em relação à Aula 03​

A Atividade 1 da Aula 03 já era, tecnicamente, instanciamento: cinco cópias do mesmo círculo, em posições diferentes. Mas o deslocamento de cada cópia entrava direto na conta dos vértices — _desenhar_aro(cx, cy, raio, cor) somava o centro a cada glVertex2f. Nenhuma transformação geométrica foi usada porque, para uma única primitiva simples como um círculo, somar o centro aos vértices resolve o problema sozinho.

A última pergunta daquela atividade pedia para imaginar 500 aros em vez de cinco, e guardar a resposta. Aqui está o argumento: para uma forma de uma primitiva só, somar o centro continua funcionando em 500 cópias — é só alongar a lista. O que não escala é a técnica em si, para qualquer coisa mais complexa que um círculo: se o modelo tivesse dez primitivas — um carro com quatro rodas, por exemplo —, a posição, o ângulo e o tamanho de cada cópia teriam que ser somados e multiplicados em cada vértice de cada primitiva do modelo, escritos à mão, toda vez. É exatamente esse trabalho que uma transformação geométrica tira do seu código: o modelo é escrito uma única vez, em torno da própria origem, e uma matriz — que o OpenGL aplica a cada vértice sozinho, no hardware — cuida de onde, com que ângulo e em que tamanho cada cópia aparece.

EtapaO que fazQuem executa nos exemplos desta aula
InstanciamentoColoca uma cópia do modelo na cenaVocê, com uma lista de parâmetros por instância
TransformaçãoMove, gira e redimensiona o modeloO OpenGL — e você, com glTranslatef/glRotatef/glScalef
Recorte, Mapeamento, Rasterização(já vistos na Aula 03)O OpenGL

Instanciamento​

Um modelo é escrito uma vez, no seu próprio sistema de referência (o SRO da Aula 03), normalmente com a origem em um ponto conveniente do objeto — o centro, uma base, um canto. Instanciar é colocar uma cópia desse modelo na cena, em uma posição, orientação e tamanho escolhidos — é a etapa "SRO → SRU" da Figura 1 da Aula 03.

Cada instância é descrita por um pequeno conjunto de parâmetros de instanciamento: tipicamente uma posição (translação), um ângulo (rotação), uma escala e, quando fizer sentido, uma cor. Guardar essas instâncias como uma lista ou tabela — em vez de repetir o desenho inteiro para cada uma — é o que permite desenhar cinco aros ou quinhentos sem mudar a função que desenha um aro.

O que esta aula acrescenta é justamente a ferramenta que essa lista precisa: uma forma de posicionar, orientar e dimensionar o modelo sem recalcular seus vértices — a transformação geométrica.

Translação, Escala e Rotação​

Três painéis lado a lado, cada um com um triângulo escaleno tracejado (original) e um sólido (transformado), a partir de uma origem O comum. No painel Translação, uma seta rotulada T liga um vértice do triângulo original ao correspondente do transladado. No painel Escala, o vértice-pivô do triângulo coincide com a origem nos dois triângulos, com raios pontilhados saindo de O e passando pelos vértices correspondentes, rotulados Ex e Ey diferentes entre si — o triângulo maior está mais esticado que o menor. No painel Rotação, o vértice-pivô também coincide com a origem, e um arco com seta no sentido anti-horário liga a aresta original à aresta rotacionada, rotulado θ.
As três transformações elementares: translação desloca, escala e rotação partem da origem — o mesmo triângulo, três efeitos diferentes.

Seja P=(x,y)P = (x, y) um ponto do modelo.

Translação por T=(Tx,Ty)T = (T_x, T_y) soma um deslocamento a cada coordenada:

P′=(x+Tx, y+Ty)P' = (x + T_x,\ y + T_y)

Escala por (Ex,Ey)(E_x, E_y), em torno da origem, multiplica cada eixo por um fator independente:

P′=(Ex⋅x, Ey⋅y)P' = (E_x \cdot x,\ E_y \cdot y)

Um fator maior que 1 amplia; entre 0 e 1 reduz; negativo espelha o eixo correspondente. Quando Ex≠EyE_x \neq E_y, a forma muda de proporção — é o que a Figura 1 mostra no painel do meio. Repare que a escala, do jeito escrito acima, sempre acontece em torno da origem do sistema de referência: um ponto que já está em (0,0)(0,0) não se move, e quanto mais longe da origem, maior o deslocamento.

Rotação por um ângulo θ\theta, também em torno da origem, gira o ponto mantendo sua distância a ela:

P′=(xcos⁡θ−ysin⁡θ,  xsin⁡θ+ycos⁡θ)P' = (x \cos\theta - y \sin\theta,\ \ x \sin\theta + y \cos\theta)

Por convenção — a mesma que o OpenGL adota, e que a Aula 05 vai batizar de regra da mão direita —, ângulo positivo gira no sentido anti-horário, como no painel da direita da Figura 1.

E se o pivô não for a origem?

As fórmulas de escala e rotação acima só valem em torno da origem. Para escalar ou rotacionar em torno de um ponto arbitrário C=(Cx,Cy)C = (C_x, C_y) — o centro de um objeto, por exemplo —, a técnica é compor três passos: translade CC até a origem, aplique a escala ou a rotação, e translade de volta. A seção seguinte mostra por que essa composição pode ser escrita como um único produto de matrizes.

Exemplo 1 — as três fórmulas, escritas à mão​

Janela do exemplo Transformações 2D no modo de translação. Sobre eixos cartesianos, o triângulo original aparece em cinza e sua cópia azul está deslocada três unidades à direita e duas unidades para cima.
Translação em transformacoes2d.py: o triângulo azul resulta de aplicar T=(3, 2) ao triângulo original, desenhado em cinza.
Janela do exemplo Transformações 2D no modo de rotação. Sobre eixos cartesianos, o triângulo original aparece em cinza e sua cópia azul está rotacionada quarenta graus no sentido anti-horário em torno da origem.
Rotação no mesmo programa: o triângulo azul resulta de girar o original 40° no sentido anti-horário, em torno da origem.

As teclas T, E e R alternam a fórmula ativa; o título da janela mostra os parâmetros correntes, e a tecla P imprime no terminal as coordenadas dos três vértices, antes e depois da transformação.

📥 Baixe o arquivo completo: transformacoes2d.py

transformacoes2d.py (trecho principal)
def transladar(p, tx, ty):
x, y = p
return (x + tx, y + ty)


def escalar(p, ex, ey):
x, y = p
return (ex * x, ey * y)


def rotacionar(p, angulo_graus):
x, y = p
a = math.radians(angulo_graus)
cos_a, sin_a = math.cos(a), math.sin(a)
return (x * cos_a - y * sin_a, x * sin_a + y * cos_a)

O que observar. As teclas T, E e R alternam qual transformação está ativa; +/- mudam o parâmetro dela (deslocamento, fator de escala ou ângulo). O triângulo claro é sempre o original; o escuro é o resultado das funções acima, recalculado a cada quadro — sem nenhuma chamada gl* de transformação ainda. É a mesma disciplina do mapeamento2d.py da Aula 03: antes de deixar o OpenGL fazer a conta, é preciso ver a conta.

Coordenadas Homogêneas​

As três fórmulas acima têm um problema de forma: escala e rotação são multiplicações por (x,y)(x, y), mas translação é uma soma. Isso impede escrever as três como a mesma operação — uma matriz 2×2 multiplicando o ponto — e portanto impede compor translação com as outras duas por simples multiplicação de matrizes.

A saída é acrescentar uma terceira coordenada, sempre igual a 1, e usar matrizes 3×3: as coordenadas homogêneas. Um ponto (x,y)(x, y) passa a ser escrito (x,y,1)(x, y, 1), e as três transformações viram matrizes que multiplicam esse vetor:

T(Tx,Ty)=[10Tx01Ty001]E(Ex,Ey)=[Ex000Ey0001]R(θ)=[cos⁡θ−sin⁡θ0sin⁡θcos⁡θ0001]T(T_x, T_y) = \begin{bmatrix} 1 & 0 & T_x \\ 0 & 1 & T_y \\ 0 & 0 & 1 \end{bmatrix} \qquad E(E_x, E_y) = \begin{bmatrix} E_x & 0 & 0 \\ 0 & E_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \qquad R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}

Multiplicar qualquer uma dessas matrizes por (x,y,1)T(x, y, 1)^T reproduz exatamente as fórmulas da seção anterior — confira com T(Tx,Ty)T(T_x, T_y):

[10Tx01Ty001][xy1]=[x+Txy+Ty1]\begin{bmatrix} 1 & 0 & T_x \\ 0 & 1 & T_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} x + T_x \\ y + T_y \\ 1 \end{bmatrix}

O ganho não é só estético. Com as três transformações no mesmo formato, compor duas ou mais delas é multiplicar suas matrizes, e o resultado é uma única matriz que já carrega o efeito combinado — em vez de aplicar três fórmulas em sequência, uma a uma, a cada vértice.

Escala ou rotação em torno de um ponto arbitrário, agora como matrizes

A ideia da nota anterior — translada até a origem, transforma, translada de volta — vira um produto de três matrizes. Para escalar em torno de C=(Cx,Cy)C = (C_x, C_y):

M=T(Cx,Cy)⋅E(Ex,Ey)⋅T(−Cx,−Cy)M = T(C_x, C_y) \cdot E(E_x, E_y) \cdot T(-C_x, -C_y)

Um exemplo numérico confere a ideia: pivô C=(5,5)C = (5, 5), escala Ex=Ey=2E_x = E_y = 2, ponto P=(6,5)P = (6, 5) — um passo à direita do pivô. Translada para a origem: (1,0)(1, 0). Escala: (2,0)(2, 0). Translada de volta: (7,5)(7, 5). O próprio pivô, testado do mesmo jeito, sai intacto: (5,5)→(0,0)→(0,0)→(5,5)(5,5) \to (0,0) \to (0,0) \to (5,5). É exatamente o comportamento esperado de "escalar em torno de um ponto": esse ponto não se move, e o que estava a um passo dele passa a estar a dois.

Composição de Transformações e Comutatividade​

Multiplicação de matrizes não é comutativa: A⋅BA \cdot B e B⋅AB \cdot A são, em geral, matrizes diferentes. Como compor transformações é multiplicar suas matrizes, a ordem em que você compõe translação, escala e rotação importa — trocá-la pode produzir um resultado diferente.

Duas fileiras de três painéis cada, todas com o mesmo triângulo retângulo fino. Na fileira de cima, rotulada translação depois escala, o painel 1 mostra o triângulo original próximo da origem; o painel 2 mostra o mesmo triângulo deslocado; o painel 3 mostra o triângulo deslocado e então esticado, com uma marca vertical rotulada x igual a 8 no vértice mais à esquerda. Na fileira de baixo, rotulada escala depois translação, o painel 1 é idêntico ao da fileira de cima; o painel 2 mostra o triângulo esticado a partir da origem; o painel 3 mostra o triângulo esticado e então deslocado, com uma marca vertical rotulada x igual a 4. Abaixo das duas fileiras, uma nota indica que a mesma translação e a mesma escala, em ordens diferentes, terminam em posições finais diferentes.
Mesmos T e S nas duas fileiras — só a ordem muda, e a posição final não é a mesma: o vértice mais à esquerda termina em x=8 numa fileira e em x=4 na outra.

A Figura 4 mostra por quê, com números conferíveis: o triângulo (0,0)(0,0)–(2,0)(2,0)–(0,1)(0,1), transladado por T=(4,0)T = (4, 0) e depois escalado por Ex=2, Ey=1E_x = 2,\ E_y = 1, termina com o vértice mais à esquerda em x=8x = 8 — a translação também foi escalada, porque aconteceu antes. Na ordem inversa — escalar primeiro, transladar depois —, o mesmo TT e o mesmo EE terminam em x=4x = 4: a translação não sofreu a escala, porque veio por último.

Em termos de matrizes, "T depois S" e "S depois T" são os produtos S⋅TS \cdot T e T⋅ST \cdot S — nessa ordem, porque uma matriz de transformação multiplica o vetor de coluna pela esquerda, e a transformação mais à direita do produto é a que age primeiro sobre o ponto. Os dois produtos são matrizes diferentes sempre que T≠0T \neq 0 e EE não for a identidade — é por isso que a Figura 4 dá resultados diferentes.

Rotação e escala comutam — mas só quando a escala é uniforme

É comum ouvir que "translação não comuta com escala nem com rotação, mas escala e rotação comutam entre si". A primeira parte é sempre verdadeira. A segunda só vale quando a escala é uniforme (Ex=EyE_x = E_y): nesse caso a matriz de escala é um múltiplo da identidade, e um múltiplo da identidade comuta com qualquer matriz.

Quando Ex≠EyE_x \neq E_y — como no painel "Escala" da Figura 1, ou em qualquer objeto esticado de forma diferente em cada eixo —, rotação e escala não comutam: rotacionar e depois esticar produz uma forma diferente de esticar e depois rotacionar, porque a direção em que o "esticão" age muda com a rotação. Vale conferir com um exemplo pequeno antes de assumir a generalização como regra.

Exemplo 2 — a mesma translação e a mesma escala, em ordens diferentes​

Janela de fundo escuro dividida ao meio por uma linha vertical. Na metade esquerda, um triângulo é desenhado após translação seguida de escala; na metade direita, o mesmo triângulo é desenhado após escala seguida de translação, em uma posição visivelmente diferente. O título da janela identifica qual ordem cada metade usa.
Saída de composicao2d.py. Os mesmos T e E de cada lado; só a ordem de aplicação muda, e o triângulo da direita fica visivelmente mais perto da origem que o da esquerda.

📥 Baixe o arquivo completo: composicao2d.py

composicao2d.py (trecho principal)
def matriz_translacao(tx, ty):
return [[1, 0, tx], [0, 1, ty], [0, 0, 1]]


def matriz_escala(ex, ey):
return [[ex, 0, 0], [0, ey, 0], [0, 0, 1]]


def multiplicar(a, b):
"""Produto de duas matrizes 3x3, na ordem A * B."""
return [[sum(a[i][k] * b[k][j] for k in range(3)) for j in range(3)]
for i in range(3)]


def aplicar(matriz, ponto):
x, y = ponto
xh, yh, _ = (sum(matriz[i][j] * v for j, v in enumerate((x, y, 1)))
for i in range(3))
return (xh, yh)


T = matriz_translacao(4.0, 0.0)
E = matriz_escala(2.0, 1.0)

# "T depois E": T age primeiro sobre o ponto (matriz mais à direita).
translada_depois_escala = multiplicar(E, T)
# "E depois T": E age primeiro sobre o ponto.
escala_depois_translada = multiplicar(T, E)

O que observar. As duas metades da janela desenham o mesmo triângulo com o mesmo TT e o mesmo EE; só a ordem do produto de matrizes muda entre multiplicar(E, T) e multiplicar(T, E). A tecla Espaço imprime, no terminal, as duas matrizes finais lado a lado — elas não são iguais, e essa é a prova de que a composição não é comutativa, sem depender de olhar só o desenho. A projeção enquadra simultaneamente a origem e o intervalo horizontal ocupado pelos dois resultados (0≤x≤120 \le x \le 12), mesmo quando a janela muda de proporção.

Transformações Geométricas em OpenGL​

O OpenGL não tem uma função "componha estas três matrizes": ele mantém uma matriz corrente (GL_MODELVIEW, a mesma que a Aula 03 usava só com a identidade) e cada chamada de transformação multiplica essa matriz pela matriz correspondente, à direita — exatamente o produto que a seção anterior descreveu.

FunçãoEquivale aParâmetros
glTranslatef(tx, ty, tz)T(tx,ty)T(t_x, t_y)Deslocamento em cada eixo (tz = 0 em 2D)
glRotatef(ang, x, y, z)R(θ)R(\theta)Ângulo em graus; eixo de rotação — (0, 0, 1) em 2D
glScalef(ex, ey, ez)E(ex,ey)E(e_x, e_y)Fator em cada eixo (ez = 1 em 2D)
A ordem no código é o inverso da ordem em que o vértice é afetado

Como cada chamada multiplica a matriz corrente pela direita, e um vértice é transformado por essa matriz no momento em que é emitido, a transformação escrita por último no código é a que age primeiro sobre o vértice. Chamando, nessa ordem,

glTranslatef(cx, cy, 0);
glRotatef(angulo, 0, 0, 1);
glScalef(escala, escala, 1);
glVertex2f(x, y);

o vértice é primeiro escalado, depois rotacionado, e só então transladado — exatamente a ordem intuitiva para posicionar um objeto (ajustar o tamanho, depois a orientação, depois o lugar). É a mesma conta da seção anterior, T⋅R⋅ET \cdot R \cdot E, só que a ordem em que você a lê no código é o espelho da ordem em que ela age no vértice. Esquecer isso é a causa mais comum de um objeto aparecer na escala ou no ângulo certos, mas no lugar errado.

Isolando transformações: glPushMatrix e glPopMatrix​

Se você transladar a matriz corrente para desenhar um objeto e não desfizer essa translação antes do próximo, ela acumula: o segundo objeto herda o deslocamento do primeiro, o terceiro herda os dois, e assim por diante. Para desenhar várias instâncias independentes — a lista de parâmetros da seção Instanciamento —, cada uma precisa começar da mesma matriz-base.

glPushMatrix() empilha uma cópia da matriz corrente; glPopMatrix() descarta o topo da pilha e volta à cópia empilhada. Desenhar uma instância vira, então, um empilha–transforma–desenha–desempilha:

Uma instância por vez: empilhar antes de transformar e desempilhar depois de desenhar garante que a transformação de uma instância nunca vaze para a próxima.

glLoadIdentity() é diferente dos dois: em vez de empilhar ou desempilhar, ele substitui a matriz corrente pela identidade — é o que os exemplos já usavam no resizeGL desde a Aula 02, para começar cada projeção do zero. A pilha de glPushMatrix/glPopMatrix guarda estados para retomar depois; glLoadIdentity descarta o estado corrente.

Exemplo 3 — várias instâncias, uma pilha só​

Janela do exemplo Pilha de matrizes com sete estrelas de cinco pontas. As estrelas têm cores, posições, rotações e tamanhos diferentes, embora todas sejam instâncias do mesmo modelo local.
Saída de pilha_instancias2d.py. A função que desenha a estrela não muda; a lista INSTANCIAS é a única coisa que determina onde, com que ângulo e em que tamanho cada cópia aparece.

📥 Baixe o arquivo completo: pilha_instancias2d.py

pilha_instancias2d.py (trecho principal)
@staticmethod
def _desenhar_estrela(cor) -> None:
"""Uma estrela de cinco pontas, sempre centrada na própria origem."""
glColor3f(*cor)
glBegin(GL_LINE_LOOP)
for k in range(10):
raio = RAIO_EXTERNO if k % 2 == 0 else RAIO_INTERNO
ang = math.pi / 2 + k * math.pi / 5
glVertex2f(raio * math.cos(ang), raio * math.sin(ang))
glEnd()

def paintGL(self) -> None:
glClear(GL_COLOR_BUFFER_BIT)
for cx, cy, angulo, escala, cor in INSTANCIAS:
glPushMatrix()
glTranslatef(cx, cy, 0.0)
glRotatef(angulo, 0.0, 0.0, 1.0)
glScalef(escala, escala, 1.0)
self._desenhar_estrela(cor)
glPopMatrix()

O que observar. _desenhar_estrela não recebe posição, ângulo nem escala — só a cor. Ela não sabe, e não precisa saber, onde vai parar: isso é inteiramente responsabilidade da matriz corrente no momento em que é chamada. Comente a linha glPopMatrix() e rode de novo: as instâncias passam a se acumular umas sobre as outras, cada vez mais deslocadas — é o vazamento que a nota acima descreveu, agora visível na tela.


Atividades de Laboratório​

Aquecimento​

Antes da atividade, use os três exemplos da aula para experimentar:

  1. Em transformacoes2d.py, ative a rotação (R) e aumente o ângulo até passar de 90°. Em que quadrante vai parar o vértice que começou no eixo xx positivo? Confira com a fórmula, no papel, antes de olhar a tela.
  2. Ainda em transformacoes2d.py, ative a escala (E) e leve ExE_x a um valor negativo. O que acontece com o triângulo? Relacione com o que a fórmula P′=(Ex⋅x, Ey⋅y)P' = (E_x \cdot x,\ E_y \cdot y) prevê para xx negativo.
  3. Em composicao2d.py, pressione Espaço e compare as duas matrizes impressas no terminal. Multiplique-as à mão por um ponto simples, como (1,0)(1, 0), e confira que bate com o que a tela mostra.
  4. Em pilha_instancias2d.py, comente o glPopMatrix() do laço principal e rode de novo. Depois de entender o efeito, desfaça o comentário — você vai precisar do glPopMatrix() funcionando na atividade.
  5. Ainda em pilha_instancias2d.py, troque a ordem de glRotatef/glScalef no meio do laço (deixe glTranslatef onde está). O visual muda? Por quê — pense em quais das três transformações comutam entre si quando a escala é uniforme.

Atividade — Aros Olímpicos com a Pilha de Matrizes​

Janela retangular de fundo branco contendo os cinco anéis olímpicos desenhados apenas com contorno espesso, sem preenchimento. Na fileira de cima, da esquerda para a direita, um anel azul, um preto e um vermelho, encostando-se pelas bordas. Na fileira de baixo, deslocados para baixo e posicionados nos vãos entre os de cima, um anel amarelo e um verde, que cruzam os anéis superiores.
Alvo visual da atividade: o MESMO desenho da Atividade 1 da Aula 03 — Figura 14 de lá —, agora produzido com glPushMatrix/glTranslatef/glPopMatrix em vez de vértices deslocados à mão.

Objetivo. Refazer os cinco aros olímpicos da Aula 03, mas desenhando um único aro na origem e usando a pilha de matrizes para posicionar as cinco cópias — sem que nenhuma coordenada de centro entre na conta de nenhum vértice.

Ponto de partida. O programa já abre a janela e configura a projeção; _desenhar_aro está pronta e desenha um aro centrado na origem; a lista AROS e o paintGL estão incompletos.

📥 Baixe o arquivo completo: aros2d_matrizes_base.py

Guarde o arquivo da Aula 03 ao lado deste

A comparação entre aros2d_solucao.py (Aula 03) e a sua solução aqui é o próprio objetivo da atividade. Duas diferenças devem saltar aos olhos: _desenhar_aro perde os parâmetros cx, cy — ela não precisa mais saber onde vai parar —, e o paintGL ganha um glPushMatrix/glPopMatrix por aro, onde antes havia só uma chamada de função.

Roteiro​

Passo 1 — Confira _desenhar_aro. Ela já está pronta neste arquivo, e é diferente da versão da Aula 03: não recebe cx, cy, só raio e cor. Leia-a e confirme que todo vértice que ela emite está em torno de (0,0)(0, 0).

Passo 2 — Preencha AROS. Reaproveite as cinco cores, os cinco raios e as cinco posições que você já validou na Aula 03 — mas agora cada posição é só o centro (cx, cy), sem entrar em nenhuma conta de vértice.

Passo 3 — Escreva o laço de instâncias no paintGL. Para cada (cx, cy, cor) de AROS: empilhe a matriz, translade para (cx, cy), desenhe o aro, desempilhe. Releia o Exemplo 3 se travar na ordem das chamadas.

Antes de rodar: o que acontece se você desenhar o aro antes de chamar glTranslatef, em vez de depois? E se esquecer o glPushMatrix — só o glPopMatrix? Preveja os dois casos, teste os dois de propósito, e depois volte à versão correta.

Passo 4 — Confronte com a Aula 03. Rode os dois arquivos lado a lado. Os aros devem ficar pixel a pixel iguais aos da Figura 14 da Aula 03 — a técnica mudou, o resultado não.

Passo 5 — Teste o esquecimento de propósito. Comente o glPopMatrix() de dentro do laço e rode de novo. Descreva o que acontece e por que — a resposta é a mesma da nota da seção Isolando transformações, agora causada por você.

Checklist de conclusão​

  • Os cinco aros aparecem, nas cinco cores corretas, no mesmo layout da Aula 03.
  • _desenhar_aro não recebe cx nem cy — nenhum centro é somado a nenhum vértice.
  • Cada aro tem seu próprio glPushMatrix()/glPopMatrix().
  • Removido o glPopMatrix() de teste do Passo 5 antes de entregar.
  • Redimensionar a janela não deforma os aros nem os corta.

Para responder​

Registre as respostas em um comentário no topo do arquivo:

  1. Compare, linha a linha, o paintGL desta versão com o da Aula 03. Qual das duas você acha mais fácil de estender para 500 aros em posições aleatórias, e por quê — a resposta não é só "porque dá menos código".
  2. No Passo 3, o que aconteceu quando você desenhou o aro antes de chamar glTranslatef? Explique com a regra "a última chamada antes do desenho age primeiro sobre o vértice".
  3. Se cada aro também devesse ter um tamanho diferente (não só uma posição), que chamada você acrescentaria ao laço, e em que ordem em relação a glTranslatef? (Não precisa implementar — só justificar a ordem.)

Atividade — Quatro Composições 2D​

Objetivo. Construir uma cena dividida pelos eixos xx e yy e posicionar um objeto em cada quadrante, usando somente a matriz GL_MODELVIEW para transladar, rotacionar ou escalar os modelos. Cada composição deve ser isolada com glPushMatrix()/glPopMatrix().

Esta atividade adapta para PySide6+PyOpenGL o exercício originalmente proposto em OpenGL+IUP. Os modelos já estão definidos em coordenadas locais; você não deve alterar nem recalcular seus vértices para obter o resultado.

📥 Baixe o arquivo-base: t2_transformacoes2d_base.py

Resultado a construir​

Cena 2D dividida pelos eixos verdes, com um triângulo branco no quadrante superior esquerdo, uma casa no superior direito, um quadrado amarelo no inferior esquerdo e um losango verde no inferior direito.
Resultado de referência da atividade: cada modelo ocupa um quadrante após a composição das transformações 2D.

Use a Figura 8 como referência visual para a organização da cena. Pequenas diferenças de posição são aceitáveis, desde que os objetos permaneçam nos quadrantes indicados e respeitem as transformações exigidas.

QuadranteObjetoTransformações exigidas
1º — superior direitoCasaOrigem local p1p_1 posicionada em (5,5)(5,5); posição estática
2º — superior esquerdoTriângulo retânguloCentralizado aproximadamente em (−50,50)(-50,50) e rotacionado 45°45°
3º — inferior esquerdoQuadradoEscala uniforme 3×3× e alinhamento na parte superior esquerda
4º — inferior direitoLosangoTranslação (−30,−50)(-30,-50) aplicada primeiro e rotação de 90°90° aplicada depois
Por que a ordem do losango é parte do enunciado

Uma translação isolada para (−30,−50)(-30,-50) terminaria no terceiro quadrante. Nesta adaptação, o losango deve ser primeiro transladado e depois rotacionado 90°90° em torno da origem do SRU. A rotação leva seu centro de (−30,−50)(-30,-50) para (50,−30)(50,-30), no quarto quadrante. Em termos de matrizes, P′=R(90°) T(−30,−50) PP' = R(90°)\,T(-30,-50)\,P; no código OpenGL, isso significa chamar glRotatef antes de glTranslatef, pois a chamada mais próxima do desenho afeta os vértices primeiro.

Roteiro​

Passo 1 — Leia os modelos locais. Confira desenhar_casa, desenhar_triangulo_retangulo, desenhar_quadrado e desenhar_losango. Nenhuma dessas funções recebe posição, ângulo ou escala. Essa separação é intencional: as funções definem como o modelo é, enquanto a matriz corrente determina onde e como a instância aparece.

Passo 2 — Posicione a casa. No primeiro quadrante, isole a matriz e use uma única translação para colocar o ponto p1=(0,0)p_1=(0,0) da casa em (5,5)(5,5). A casa não recebe rotação nem escala.

Passo 3 — Componha o triângulo. No segundo quadrante, faça o triângulo girar 45°45° em torno da própria origem e, depois, posicione-o aproximadamente no centro (−50,50)(-50,50). Escreva as chamadas na ordem necessária para que a rotação afete o modelo antes da translação.

Passo 4 — Componha o quadrado. No terceiro quadrante, amplie o quadrado uniformemente por um fator 3 e, depois, posicione seu centro em (−85,−15)(-85,-15). Com esses valores, o quadrado de lado 10 passa a ter lado 30 e fica alinhado às bordas superior e esquerda do quadrante.

Passo 5 — Resolva o losango pela ordem das matrizes. No quarto quadrante, faça a translação (−30,−50)(-30,-50) agir primeiro e a rotação de 90°90° agir depois. Antes de executar, calcule no papel o destino do centro:

R(90°)[−30−501]=[50−301].R(90°)\begin{bmatrix}-30\\-50\\1\end{bmatrix} =\begin{bmatrix}50\\-30\\1\end{bmatrix}.

Passo 6 — Teste o isolamento. Comente temporariamente um dos glPopMatrix(). Observe quais objetos passam a herdar a transformação anterior e explique por que o vazamento começa naquele ponto. Restaure o glPopMatrix() antes de entregar.

Checklist de conclusão​

  • Os quatro objetos aparecem nos quadrantes indicados.
  • Nenhum vértice dos modelos foi alterado para posicionar um objeto.
  • Cada objeto está isolado por glPushMatrix()/glPopMatrix().
  • O triângulo está rotacionado 45°45° e o quadrado tem escala 3×3×.
  • O losango usa exatamente a translação (−30,−50)(-30,-50) e a rotação 90°90° na ordem solicitada.
  • Redimensionar a janela preserva as proporções e mantém os quatro quadrantes visíveis.

Para responder​

Registre as respostas em um comentário no topo do arquivo:

  1. Qual é a ordem das chamadas glTranslatef e glRotatef no código do triângulo, e por que ela produz rotação local antes do posicionamento?
  2. Por que o quadrado precisa ser escalado antes de ser transladado se a posição (−85,−15)(-85,-15) representa o centro final desejado?
  3. O losango recebe uma translação com xx negativo, mas termina com xx positivo. Demonstre esse resultado com a matriz de rotação de 90°90°.
  4. O que mudaria se rotação e translação do losango fossem invertidas?

Exercícios​

Questões dissertativas​

Q1

Os aros da Aula 03 já eram instâncias de um mesmo modelo, mas não usavam nenhuma transformação geométrica. Explique por que isso foi possível ali, e por que a técnica deixa de bastar para um modelo com várias primitivas.

Q2

Por que a translação não pode ser escrita como uma matriz 2x2 multiplicando o ponto (x, y), e como as coordenadas homogêneas resolvem isso?

Q3Difícil

Um objeto é transladado por T=(4,0) e depois escalado por Ex=2, Ey=1 (nessa ordem). Escalado por Ex=2, Ey=1 e depois transladado por T=(4,0) (na ordem oposta). Mostre, com um ponto de sua escolha, que os dois resultados são diferentes, e explique a causa em uma frase.

Q4

Descreva, em termos de matrizes, como escalar um objeto em torno do seu próprio centro C=(Cx,Cy) em vez de em torno da origem — e diga o que acontece com C nessa composição.

Q5

Sob que condição rotação e escala comutam? Dê um exemplo em que elas comutam e um contraexemplo em que não comutam.

Q6

Em um laço que desenha N instâncias de um mesmo modelo com glPushMatrix/glTranslatef/.../glPopMatrix, o que acontece se o glPopMatrix de dentro do laço for removido? E se, em vez disso, for o glPushMatrix que faltar?

Questões objetivas​

Quiz7 questões

1. Um ponto P = (3, 2) é transladado por T = (-1, 4). Qual é P'?

  • a)(2, 6)
  • b)(-3, 8)
  • c)(4, -2)
  • d)(2, -6)
  • e)(3, 2)

2. Um ponto P = (2, 0) é rotacionado 90° (sentido anti-horário, em torno da origem). Qual é P'?

  • a)(0, 2)
  • b)(0, -2)
  • c)(-2, 0)
  • d)(2, 0) — não muda
  • e)(1.4, 1.4), aproximadamente

3. Um objeto é transladado por T=(6,0) e depois escalado por Ex=Ey=3 (nessa ordem, em torno da origem). Para que ponto vai o vértice que começa em (0,0)?

  • a)(18, 0)
  • b)(6, 0)
  • c)(2, 0)
  • d)(0, 0)
  • e)(3, 0)

4. No código glTranslatef(cx, cy, 0); glRotatef(ang, 0, 0, 1); glVertex2f(x, y);, qual transformação afeta o vértice primeiro?

  • a)glTranslatef, porque é a primeira linha do código
  • b)glRotatef, porque é a última chamada antes do vértice
  • c)As duas ao mesmo tempo, porque OpenGL combina tudo em uma única operação
  • d)Depende de qual matriz está ativa (GL_MODELVIEW ou GL_PROJECTION)
  • e)Nenhuma: glVertex2f não é afetado por chamadas anteriores no mesmo quadro

5. Em qual das situações abaixo rotação e escala comutam (dão o mesmo resultado nas duas ordens)?

  • a)Sempre, para quaisquer Ex e Ey
  • b)Nunca, a menos que o ângulo seja 0°
  • c)Quando Ex = Ey (escala uniforme)
  • d)Quando o ângulo é múltiplo de 180°, qualquer que seja a escala
  • e)Só quando a rotação é aplicada em torno de um ponto diferente da origem

6. Um laço desenha 5 instâncias de um modelo com glPushMatrix() / glTranslatef(...) / desenhar() / glPopMatrix() a cada volta. O glPopMatrix() da terceira volta é removido por engano. O que se vê na tela?

  • a)As 5 instâncias aparecem normalmente: glPopMatrix só afeta o desempenho, não o resultado
  • b)As instâncias 1 e 2 aparecem corretas; a partir da 3ª, cada uma acumula o deslocamento das anteriores
  • c)Nenhuma instância aparece, porque a pilha de matrizes trava
  • d)Só a instância 3 desaparece; as outras quatro continuam corretas
  • e)O programa encerra com um erro antes de desenhar qualquer coisa

7. Por que a técnica de somar o centro (cx, cy) a cada vértice — usada nos aros da Aula 03 — deixa de ser prática para um modelo com dezenas de primitivas, mesmo continuando matematicamente correta?

  • a)Porque a soma deixa de ser uma operação válida em modelos grandes
  • b)Porque o OpenGL limita o número de primitivas por objeto
  • c)Porque a soma precisaria ser escrita em cada vértice de cada primitiva do modelo, toda vez que ele for instanciado em um lugar novo
  • d)Porque só glTranslatef aceita valores maiores que uma unidade
  • e)Não deixa de ser prática: é sempre preferível a usar transformações

Referências​

Principais (essenciais)​

  • AZEVEDO, E.; CONCI, A.; LETA, F. R. Computação Gráfica. Rio de Janeiro: Elsevier, 2003–2008. 2 v. (004.92 A994c)
  • FOLEY, J. D. et al. Computer Graphics: Principles and Practice. 3. ed. Addison-Wesley, 2013. (004.92 C738)
  • HEARN, D.; BAKER, M. P.; CARITHERS, W. R. Computer Graphics with OpenGL. 4. ed. Pearson Prentice Hall, 2011. (004.92 H436co)
  • The Khronos Group. OpenGL 2.1 Reference Pages — glTranslate, glRotate, glScale. Disponível em: registry.khronos.org/OpenGL-Refpages/gl2.1
  • The Khronos Group. OpenGL 2.1 Reference Pages — glPushMatrix, glPopMatrix. Disponível em: registry.khronos.org/OpenGL-Refpages/gl2.1

Aprofundamento (opcionais)​