"""Atividade — Cena Navegável 3D. Aula 05 — Câmera Sintética e Projeção em Perspectiva. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python cena_navegavel_base.py A cena (chão em grade + blocos) já está modelada, com vértices puros — nenhuma chamada gl* de transformação geométrica é usada aqui (essa ferramenta chega na Aula 06). O que falta é a CÂMERA navegável: o arquivo abre com uma vista provisória do chão e sem alternar projeção. Complete os TODOs para navegar pela cena e comparar as duas projeções. Teclas (depois de completos os TODOs): W S anda para frente / para trás A D desloca lateralmente (strafe), sem girar setas esquerda/direita gira o ângulo de visada (yaw) O alterna entre projeção ortográfica e perspectiva Esc fecha """ from __future__ import annotations import math 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 # --- constantes de experimentação -------------------------------------- PASSO_MOVIMENTO = 0.3 PASSO_ROTACAO = math.radians(4.0) FOVY = 55.0 ORTHO_META_ALTURA = 4.0 UP = (0.0, 1.0, 0.0) TAMANHO_CHAO = 10.0 PASSO_GRADE = 1.0 # Cena: (cx, cy, cz, semi-lado, cor). Posição é parâmetro da função de # desenho, como na Atividade 1 da Aula 03 — não é transformação geométrica. # # TODO 1: preencha com pelo menos QUATRO blocos, em posições e # profundidades diferentes, seguindo o layout relativo da Figura 5 (sem # copiar coordenadas — a figura não dá números de propósito). BLOCOS = [ # (cx, cy, cz, semi-lado, cor) ] class CenaNavegavel(QOpenGLWidget): """Chão e blocos fixos; o observador anda e gira sobre o plano xz.""" def __init__(self, parent=None): super().__init__(parent) self.eye = [0.0, 1.0, 6.0] self.yaw = 0.0 # 0 rad = olhando para -Z self.ortografica = False self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self._largura, self._altura = 1, 1 # -- ciclo de vida ----------------------------------------------------- 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: dpr = self.devicePixelRatioF() self._largura = max(1, round(w * dpr)) self._altura = max(1, round(h * dpr)) glViewport(0, 0, self._largura, self._altura) # -- geometria da câmera ------------------------------------------- def _direcao_frente(self) -> tuple[float, float, float]: """Vetor unitário para onde o observador olha, a partir de self.yaw. Em yaw = 0 o observador olha para -Z (o padrão de gluLookAt sem rotação nenhuma). Girar yaw deve girar essa direção no plano xz, mantendo o comprimento 1 — é uma rotação em torno do eixo Y. """ # TODO 2: substitua a linha abaixo pela direção correta em função # de self.yaw. Consulte "Girando a mira" no roteiro da atividade. return (0.0, 0.0, -1.0) def _direcao_lateral(self) -> tuple[float, float, float]: """Vetor unitário 'para a direita' do observador, no plano xz. Perpendicular a `_direcao_frente()` e a UP — mesma ideia do produto vetorial usado em zoom_pan_3d.py, só que agora a direção da frente muda a cada quadro em vez de ficar fixa. """ # TODO 3: calcule a partir de self._direcao_frente() e de UP. return (1.0, 0.0, 0.0) # -- desenho ------------------------------------------------------- def paintGL(self) -> None: self._configurar_projecao() glMatrixMode(GL_MODELVIEW) glLoadIdentity() # Visualização provisória: deixa o chão visível antes dos TODOs. # TODO 4: substitua este alvo fixo por self.eye deslocado por # self._direcao_frente(), mantendo self.eye e UP na chamada. alvo_inicial = (0.0, 0.0, 0.0) gluLookAt(*self.eye, *alvo_inicial, *UP) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) self._desenhar_chao() for cx, cy, cz, s, cor in BLOCOS: self._desenhar_bloco(cx, cy, cz, s, cor) self.setWindowTitle( f"Cena navegável — {'ortográfica' if self.ortografica else 'perspectiva'}" f" — eye=({self.eye[0]:.1f}, {self.eye[1]:.1f}, {self.eye[2]:.1f})" f" yaw={math.degrees(self.yaw):.0f}°" ) def _configurar_projecao(self) -> None: glMatrixMode(GL_PROJECTION) glLoadIdentity() aspecto = self._largura / float(self._altura) # TODO 5: se self.ortografica for True, chame glOrtho com uma # meia-altura ORTHO_META_ALTURA (meia-largura = meia-altura * # aspecto) e planos near/far 0.1 e 100.0; senão, chame # gluPerspective(FOVY, aspecto, 0.1, 100.0). gluPerspective(FOVY, aspecto, 0.1, 100.0) @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() @staticmethod def _desenhar_bloco(cx: float, cy: float, cz: float, s: float, cor) -> None: x0, x1 = cx - s, cx + s y0, y1 = cy - s, cy + s z0, z1 = cz - s, cz + s faces = [ ((x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)), ((x1, y0, z0), (x0, y0, z0), (x0, y1, z0), (x1, y1, z0)), ((x0, y1, z1), (x1, y1, z1), (x1, y1, z0), (x0, y1, z0)), ((x0, y0, z0), (x1, y0, z0), (x1, y0, z1), (x0, y0, z1)), ((x1, y0, z1), (x1, y0, z0), (x1, y1, z0), (x1, y1, z1)), ((x0, y0, z0), (x0, y0, z1), (x0, y1, z1), (x0, y1, z0)), ] glColor3f(*cor) glBegin(GL_QUADS) for face in faces: for v in face: glVertex3f(*v) glEnd() # -- interação ------------------------------------------------------- def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() return elif tecla == Qt.Key.Key_O: self.ortografica = not self.ortografica elif tecla == Qt.Key.Key_Left: self.yaw -= PASSO_ROTACAO elif tecla == Qt.Key.Key_Right: self.yaw += PASSO_ROTACAO elif tecla in (Qt.Key.Key_W, Qt.Key.Key_S, Qt.Key.Key_A, Qt.Key.Key_D): # TODO 6: use self._direcao_frente() e self._direcao_lateral() # para deslocar self.eye: W/S andam para frente/trás; A/D fazem # strafe lateral, sem alterar self.yaw. PASSO_MOVIMENTO é o # tamanho do passo. pass 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 = CenaNavegavel() janela.setWindowTitle("Cena navegável 3D") janela.resize(900, 640) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())