"""Composição de transformações: a ordem importa. Aula 04 — Instanciamento e Transformações Geométricas 2D. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python composicao2d.py O MESMO triângulo, a MESMA translação T=(4,0) e a MESMA escala Ex=2, Ey=1 são aplicados nas duas metades da janela — só a ORDEM muda. À esquerda, "T depois E" (translada, depois escala); à direita, "E depois T" (escala, depois translada). Os dois triângulos claros e tracejados são o original, sem nenhuma transformação; os sólidos e azuis são o resultado de cada composição. Em termos de matrizes, "T depois E" é o produto E * T (a matriz mais à direita, T, age primeiro sobre o ponto), e "E depois T" é T * E. Multiplicar matrizes não é comutativo, e os dois produtos são diferentes. Teclas: Espaço imprime as duas matrizes finais no terminal Esc fecha """ from __future__ import annotations import sys from PySide6.QtCore import Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtWidgets import QApplication from PySide6.QtOpenGLWidgets import QOpenGLWidget from OpenGL.GL import * from OpenGL.GLU import gluOrtho2D CENTRO_MUNDO = (6.0, 1.0) # centro da janela de mundo de cada metade MEIA_LARGURA_MINIMA = 6.5 # enquadra a origem e os resultados (x = 0..12) MEIA_ALTURA_MINIMA = 4.0 TRIANGULO_BASE = [(0.0, 0.0), (2.0, 0.0), (0.0, 1.0)] 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) class Composicao2D(QOpenGLWidget): """Duas metades, dois produtos de matrizes diferentes.""" def __init__(self, parent=None): super().__init__(parent) self._largura, self._altura = 1, 1 def initializeGL(self) -> None: glClearColor(0.10, 0.11, 0.13, 1.0) def resizeGL(self, w: int, h: int) -> None: # O viewport de cada metade é definido no paintGL, não aqui — como # em janela_viewport.py (Aula 03): a projeção muda de quadrante # para quadrante, então não há um único glViewport "da janela". self._largura, self._altura = max(1, w), max(1, h) def _desenhar_metade(self, x0: int, largura: int, altura: int, matriz) -> None: glViewport(x0, 0, largura, altura) aspecto = largura / float(altura) glMatrixMode(GL_PROJECTION) glLoadIdentity() cx, cy = CENTRO_MUNDO # Mantém pelo menos x = -0,5..12,5 visível. Em janelas estreitas, # aumenta a faixa vertical em vez de recortar um dos resultados. meia_h = max(MEIA_ALTURA_MINIMA, MEIA_LARGURA_MINIMA / aspecto) gluOrtho2D(cx - meia_h * aspecto, cx + meia_h * aspecto, cy - meia_h, cy + meia_h) glMatrixMode(GL_MODELVIEW) glLoadIdentity() # eixos, para dar referência de onde fica a origem glColor3f(0.30, 0.32, 0.38) glLineWidth(1.0) glBegin(GL_LINES) glVertex2f(cx - meia_h * aspecto, 0.0) glVertex2f(cx + meia_h * aspecto, 0.0) glVertex2f(0.0, cy - meia_h) glVertex2f(0.0, cy + meia_h) glEnd() # original, sem transformação nenhuma glColor3f(0.55, 0.58, 0.65) glLineWidth(1.5) glEnable(GL_LINE_STIPPLE) glLineStipple(1, 0x00FF) glBegin(GL_LINE_LOOP) for p in TRIANGULO_BASE: glVertex2f(*p) glEnd() glDisable(GL_LINE_STIPPLE) # resultado da composição desta metade glColor3f(0.35, 0.55, 0.85) glBegin(GL_TRIANGLES) for p in TRIANGULO_BASE: glVertex2f(*aplicar(matriz, p)) glEnd() glColor3f(0.65, 0.78, 1.0) glLineWidth(2.5) glBegin(GL_LINE_LOOP) for p in TRIANGULO_BASE: glVertex2f(*aplicar(matriz, p)) glEnd() def paintGL(self) -> None: # glClear varre o framebuffer inteiro (Aula 03): uma chamada só, # antes de configurar o primeiro viewport, basta para as duas # metades. glClear(GL_COLOR_BUFFER_BIT) meia_largura = self._largura // 2 self._desenhar_metade(0, meia_largura, self._altura, translada_depois_escala) self._desenhar_metade(meia_largura, self._largura - meia_largura, self._altura, escala_depois_translada) @staticmethod def _imprimir_matrizes() -> None: print("T depois E (matriz E * T):") for linha in translada_depois_escala: print(" ", [round(v, 2) for v in linha]) print("E depois T (matriz T * E):") for linha in escala_depois_translada: print(" ", [round(v, 2) for v in linha]) def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() elif tecla == Qt.Key.Key_Space: self._imprimir_matrizes() def main() -> int: fmt = QSurfaceFormat() fmt.setVersion(2, 1) fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile) fmt.setDepthBufferSize(0) QSurfaceFormat.setDefaultFormat(fmt) app = QApplication(sys.argv) janela = Composicao2D() janela.setWindowTitle( "Composição: esquerda = T depois E | direita = E depois T") janela.resize(900, 480) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())