"""Navegação do OBJETO (não da câmera), com glTranslatef/glRotatef acumulados. Aula 06 — Transformações Geométricas 3D e Navegação. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python piramide_navegavel.py Na Aula 05, quem se movia era o observador (``gluLookAt``); os objetos ficavam parados, com vértices puros. Aqui é o oposto: a câmera é FIXA, e é a pirâmide que translada e gira, através de glTranslatef/glRotatef — exatamente as funções de TG 3D revisadas em tg3d_cubo.py, agora acumuladas quadro a quadro em vez de recalculadas do zero a cada tecla. A pirâmide é definida por uma lista de vértices (``VERTICES``) e uma tabela de faces (``FACES``, cada face uma tupla de índices em ``VERTICES``) e desenhada em wireframe — um detalhe de implementação, não o foco desta aula. Teclas: setas esquerda/direita gira a pirâmide em torno do eixo Y (guinada) setas cima/baixo gira a pirâmide em torno do eixo X (arfagem) W A S D translada a pirâmide no plano xz 0 volta à posição e ângulo iniciais 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 gluPerspective, gluLookAt PASSO_MOVIMENTO = 0.15 PASSO_ROTACAO = 4.0 # Câmera fixa: nesta aula quem navega é o objeto, não o observador. OLHO = (0.0, 3.5, 7.0) ALVO = (0.0, 0.5, 0.0) UP = (0.0, 1.0, 0.0) TAMANHO_CHAO = 6.0 PASSO_GRADE = 1.0 # --- definição da pirâmide: lista de vértices + tabela de faces -------- # Base quadrada no plano xz, ápice em +y. Cada face é uma tupla de # ÍNDICES em VERTICES — a mesma face nunca repete as coordenadas de um # vértice compartilhado. ALTURA = 1.4 MEIO_LADO = 0.9 VERTICES = [ (0.0, ALTURA, 0.0), # 0: ápice (-MEIO_LADO, 0.0, -MEIO_LADO), # 1: base, -x -z (MEIO_LADO, 0.0, -MEIO_LADO), # 2: base, +x -z (MEIO_LADO, 0.0, MEIO_LADO), # 3: base, +x +z (-MEIO_LADO, 0.0, MEIO_LADO), # 4: base, -x +z ] FACES = [ (0, 1, 2), # lateral -z (0, 2, 3), # lateral +x (0, 3, 4), # lateral +z (0, 4, 1), # lateral -x (1, 2, 3, 4), # base ] def desenhar_piramide_aramada() -> None: """Desenha VERTICES/FACES em wireframe: um GL_LINE_LOOP por face. Repare que cada aresta entre duas faces laterais é desenhada duas vezes (uma por face) — é o preço de wireframe-por-face, mais simples de escrever do que uma tabela de arestas sem repetição. A Aula 07 volta a este mesmo VERTICES/FACES para preencher em vez de contornar. """ for face in FACES: glBegin(GL_LINE_LOOP) for indice in face: glVertex3f(*VERTICES[indice]) glEnd() class PiramideNavegavel(QOpenGLWidget): """Câmera parada; a pirâmide translada e gira sob controle do teclado.""" def __init__(self, parent=None): super().__init__(parent) self.pos = [0.0, 0.0, 0.0] self.rot_x = 0.0 self.rot_y = 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(50.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) # câmera fixa, primeira chamada self._desenhar_chao() glColor3f(0.90, 0.65, 0.30) glLineWidth(2.0) glPushMatrix() glTranslatef(*self.pos) glRotatef(self.rot_y, 0.0, 1.0, 0.0) # guinada: em torno de Y glRotatef(self.rot_x, 1.0, 0.0, 0.0) # arfagem: em torno de X desenhar_piramide_aramada() glPopMatrix() self.setWindowTitle( f"Pirâmide navegável — pos=({self.pos[0]:.1f}, {self.pos[1]:.1f}, " f"{self.pos[2]:.1f}) rot_x={self.rot_x:.0f}° rot_y={self.rot_y:.0f}°" ) def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() return elif tecla == Qt.Key.Key_0: self.pos = [0.0, 0.0, 0.0] self.rot_x = self.rot_y = 0.0 elif tecla == Qt.Key.Key_Left: self.rot_y -= PASSO_ROTACAO elif tecla == Qt.Key.Key_Right: self.rot_y += PASSO_ROTACAO elif tecla == Qt.Key.Key_Up: self.rot_x -= PASSO_ROTACAO elif tecla == Qt.Key.Key_Down: self.rot_x += PASSO_ROTACAO elif tecla == Qt.Key.Key_W: self.pos[2] -= PASSO_MOVIMENTO elif tecla == Qt.Key.Key_S: self.pos[2] += PASSO_MOVIMENTO elif tecla == Qt.Key.Key_A: self.pos[0] -= PASSO_MOVIMENTO elif tecla == Qt.Key.Key_D: self.pos[0] += PASSO_MOVIMENTO 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 = PiramideNavegavel() janela.setWindowTitle("Pirâmide navegável") janela.resize(800, 600) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())