"""A câmera sintética: posição, alvo e vetor *up* definem o que se vê. Aula 05 — Câmera Sintética e Projeção em Perspectiva. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python camera_sintetica.py A cena (um chão em grade e alguns blocos) é sempre a mesma — o que muda de uma tecla para outra é só a chamada a ``gluLookAt``. Compare os quatro enquadramentos: nenhum vértice da cena foi tocado, só a posição e a orientação do observador. Teclas: 1 2 3 4 alterna entre os quatro pontos de vista Esc fecha O título da janela mostra o olho, o alvo e o *up* correntes. Repare que ``gluLookAt`` mexe na matriz **GL_MODELVIEW** — a mesma matriz que, a partir da Aula 06, também vai carregar as transformações dos objetos (``glTranslatef``, ``glRotatef``). Aqui ela carrega só a câmera: por isso ``gluLookAt`` é a primeira chamada depois do ``glLoadIdentity()``, antes de qualquer vértice ser emitido. """ 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 # Cada ponto de vista é (olho, alvo, up). O alvo é sempre o centro da cena; # o que muda é de onde e de que ângulo o observador olha para ele. PONTOS_DE_VISTA = [ ((0.0, 3.0, 9.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)), # 1: frontal, um pouco alta ((9.0, 2.0, 0.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)), # 2: lateral ((0.0, 9.0, 0.01), (0.0, 0.0, 0.0), (0.0, 0.0, -1.0)), # 3: vista de pássaro ((-3.0, 0.6, 3.0), (1.0, 0.5, -1.0), (0.0, 1.0, 0.0)), # 4: rente ao chão, de esguelha ] # Blocos da cena: (cx, cy, cz, semi-lado, cor). Posição é parâmetro da # função de desenho, não transformação — mesmo padrão da Atividade 1 da # Aula 03: nenhuma chamada gl* de transformação geométrica é usada aqui. BLOCOS = [ (-2.0, 0.5, -1.5, 0.5, (0.86, 0.35, 0.35)), (1.8, 0.4, -2.2, 0.4, (0.35, 0.65, 0.86)), (0.2, 0.6, 1.6, 0.6, (0.45, 0.80, 0.45)), (2.6, 0.3, 1.2, 0.3, (0.86, 0.75, 0.30)), ] TAMANHO_CHAO = 8.0 PASSO_GRADE = 1.0 class CameraSintetica(QOpenGLWidget): """Cena fixa; a câmera troca de posição e orientação por tecla.""" def __init__(self, parent=None): super().__init__(parent) self.indice_vista = 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: dpr = self.devicePixelRatioF() fb_w = max(1, round(w * dpr)) fb_h = max(1, round(h * dpr)) glViewport(0, 0, fb_w, fb_h) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(50.0, fb_w / float(fb_h), 0.1, 100.0) glMatrixMode(GL_MODELVIEW) def paintGL(self) -> None: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() olho, alvo, up = PONTOS_DE_VISTA[self.indice_vista] gluLookAt(*olho, *alvo, *up) self._desenhar_chao() for cx, cy, cz, s, cor in BLOCOS: self._desenhar_bloco(cx, cy, cz, s, cor) self._atualizar_titulo(olho, alvo, up) @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: """Um cubo de centro (cx, cy, cz) e semi-lado s — vértices puros.""" 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)), # +Z ((x1, y0, z0), (x0, y0, z0), (x0, y1, z0), (x1, y1, z0)), # -Z ((x0, y1, z1), (x1, y1, z1), (x1, y1, z0), (x0, y1, z0)), # +Y ((x0, y0, z0), (x1, y0, z0), (x1, y0, z1), (x0, y0, z1)), # -Y ((x1, y0, z1), (x1, y0, z0), (x1, y1, z0), (x1, y1, z1)), # +X ((x0, y0, z0), (x0, y0, z1), (x0, y1, z1), (x0, y1, z0)), # -X ] glColor3f(*cor) glBegin(GL_QUADS) for face in faces: for v in face: glVertex3f(*v) glEnd() def _atualizar_titulo(self, olho, alvo, up) -> None: self.setWindowTitle( f"Câmera sintética [{self.indice_vista + 1}/4] — " f"olho={olho} alvo={alvo} up={up}" ) def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() return for i, k in enumerate( (Qt.Key.Key_1, Qt.Key.Key_2, Qt.Key.Key_3, Qt.Key.Key_4) ): if tecla == k: self.indice_vista = i self.update() return 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 = CameraSintetica() janela.setWindowTitle("Câmera sintética") janela.resize(800, 600) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())