"""As três transformações geométricas 3D, agora com glTranslatef/glRotatef/glScalef. Aula 06 — Transformações Geométricas 3D e Navegação. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python tg3d_cubo.py Um cubo de referência (aramado, cinza claro) fica sempre na origem. Um segundo cubo (aramado, azul) é o resultado de aplicar UMA das três transformações — a mesma disciplina da Aula 04, agora em três eixos: as matrizes passam a ser 4x4 (translação e escala ganham um terceiro eixo; a rotação passa a precisar de um EIXO, não só de um ângulo, porque em 3D existem infinitos eixos de rotação possíveis, não só o implícito eixo z das rotações 2D). A transformação não é calculada à mão (como em transformacoes2d.py, Aula 04): aqui é a própria OpenGL que faz a conta, dentro de um glPushMatrix()/glPopMatrix() — exatamente como pilha_instancias2d.py (Aula 04), só que glTranslatef/glScalef agora recebem Tz/Ez, e glRotatef recebe um eixo (x, y, z) além do ângulo. Teclas: T E R escolhe qual transformação está ativa X Y Z (só no modo R) escolhe o eixo de rotação setas muda os parâmetros da transformação ativa 0 zera todos os parâmetros Esc fecha O título da janela mostra o modo corrente, o eixo (se for rotação) e os valores dos parâmetros. """ 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 gluPerspective, gluLookAt PASSO_T = 0.3 PASSO_E = 0.1 PASSO_ANGULO = 5.0 # Câmera fixa (a mesma câmera da Aula 05, sem navegação): só as # transformações do cubo azul mudam nesta aula. OLHO = (5.0, 3.5, 6.0) ALVO = (0.0, 0.0, 0.0) UP = (0.0, 1.0, 0.0) TAMANHO_CHAO = 6.0 PASSO_GRADE = 1.0 def _desenhar_cubo_aramado(semi_lado: float) -> None: """Um cubo de semi-lado `semi_lado`, centrado na PRÓPRIA origem. Wireframe (arestas apenas) — a técnica de modelagem mais simples desta aula: uma lista de vértices e a ligação entre eles, sem preencher faces. A função não recebe posição, ângulo nem escala: é a matriz GL_MODELVIEW corrente, no momento em que ela é chamada, que decide onde, como e em que tamanho o cubo aparece. """ s = semi_lado vertices = [ (-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s), # face -Z (-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s), # face +Z ] arestas = [ (0, 1), (1, 2), (2, 3), (3, 0), # -Z (4, 5), (5, 6), (6, 7), (7, 4), # +Z (0, 4), (1, 5), (2, 6), (3, 7), # ligações entre as duas faces ] glBegin(GL_LINES) for a, b in arestas: glVertex3f(*vertices[a]) glVertex3f(*vertices[b]) glEnd() class TG3DCubo(QOpenGLWidget): """Um cubo de referência na origem; um cubo transformado ao lado.""" def __init__(self, parent=None): super().__init__(parent) self.modo = "T" self.eixo = "Y" self.tx, self.ty, self.tz = 0.0, 0.0, 0.0 self.ex, self.ey, self.ez = 1.0, 1.0, 1.0 self.angulo = 0.0 self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) def initializeGL(self) -> None: glClearColor(0.10, 0.11, 0.13, 1.0) glEnable(GL_DEPTH_TEST) def resizeGL(self, w: int, h: int) -> None: w, h = max(1, w), max(1, h) glViewport(0, 0, w, h) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, w / float(h), 0.1, 100.0) glMatrixMode(GL_MODELVIEW) @staticmethod def _desenhar_chao() -> None: glColor3f(0.30, 0.32, 0.38) glLineWidth(1.0) glBegin(GL_LINES) v = -TAMANHO_CHAO while v <= TAMANHO_CHAO: glVertex3f(v, 0.0, -TAMANHO_CHAO) glVertex3f(v, 0.0, TAMANHO_CHAO) glVertex3f(-TAMANHO_CHAO, 0.0, v) glVertex3f(TAMANHO_CHAO, 0.0, v) v += PASSO_GRADE glEnd() def paintGL(self) -> None: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() gluLookAt(*OLHO, *ALVO, *UP) self._desenhar_chao() # cubo de referência: sempre na origem, sem transformação nenhuma glColor3f(0.55, 0.58, 0.65) glLineWidth(1.5) glPushMatrix() glTranslatef(0.0, 1.0, 0.0) _desenhar_cubo_aramado(0.6) glPopMatrix() # cubo transformado: a matriz corrente aplica só o modo ativo glColor3f(0.35, 0.65, 0.95) glLineWidth(2.5) glPushMatrix() glTranslatef(0.0, 1.0, 0.0) # mesma base do cubo de referência if self.modo == "T": glTranslatef(self.tx, self.ty, self.tz) elif self.modo == "E": glScalef(self.ex, self.ey, self.ez) else: eixo_vetor = {"X": (1, 0, 0), "Y": (0, 1, 0), "Z": (0, 0, 1)}[self.eixo] glRotatef(self.angulo, *eixo_vetor) _desenhar_cubo_aramado(0.6) glPopMatrix() self._atualizar_titulo() def _atualizar_titulo(self) -> None: if self.modo == "T": info = f"T=({self.tx:.1f}, {self.ty:.1f}, {self.tz:.1f})" elif self.modo == "E": info = f"E=({self.ex:.2f}, {self.ey:.2f}, {self.ez:.2f})" else: info = f"eixo={self.eixo} ângulo={self.angulo:.0f}°" self.setWindowTitle(f"TG 3D — modo {self.modo} — {info}") def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() return elif tecla == Qt.Key.Key_T: self.modo = "T" elif tecla == Qt.Key.Key_E: self.modo = "E" elif tecla == Qt.Key.Key_R: self.modo = "R" elif tecla in (Qt.Key.Key_X, Qt.Key.Key_Y, Qt.Key.Key_Z) and self.modo == "R": self.eixo = {Qt.Key.Key_X: "X", Qt.Key.Key_Y: "Y", Qt.Key.Key_Z: "Z"}[tecla] elif tecla == Qt.Key.Key_0: self.tx = self.ty = self.tz = 0.0 self.ex = self.ey = self.ez = 1.0 self.angulo = 0.0 elif tecla in (Qt.Key.Key_Left, Qt.Key.Key_Right): sinal = -1.0 if tecla == Qt.Key.Key_Left else 1.0 if self.modo == "T": self.tx += sinal * PASSO_T elif self.modo == "E": self.ex += sinal * PASSO_E else: self.angulo += sinal * PASSO_ANGULO elif tecla in (Qt.Key.Key_Up, Qt.Key.Key_Down): sinal = 1.0 if tecla == Qt.Key.Key_Up else -1.0 if self.modo == "T": self.ty += sinal * PASSO_T elif self.modo == "E": self.ey += sinal * PASSO_E else: self.angulo += sinal * PASSO_ANGULO elif tecla in (Qt.Key.Key_PageUp, Qt.Key.Key_PageDown): sinal = 1.0 if tecla == Qt.Key.Key_PageUp else -1.0 if self.modo == "T": self.tz += sinal * PASSO_T elif self.modo == "E": self.ez += sinal * PASSO_E else: return self.update() def main() -> int: fmt = QSurfaceFormat() fmt.setVersion(2, 1) fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile) fmt.setDepthBufferSize(24) QSurfaceFormat.setDefaultFormat(fmt) app = QApplication(sys.argv) janela = TG3DCubo() janela.setWindowTitle("Transformações Geométricas 3D") janela.resize(800, 600) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())