"""Atividade 2 — Recorte de polígonos por Sutherland-Hodgman (ponto de partida). Aula 03 — Visualização 2D. Execute com: python poligono2d_base.py O programa já roda: desenha a janela de recorte tracejada e o polígono original em cinza. O que falta é o algoritmo — as quatro funções abaixo estão pela metade, e enquanto elas não funcionarem nenhum contorno vermelho aparece. Cada TODO corresponde a um passo do roteiro publicado na página da aula. Teclas: 0 polígono original, sem recorte 1 2 3 4 resultado após 1, 2, 3 ou 4 passagens O mostra ou esconde o polígono original P preenche o resultado com GL_POLYGON V marca os vértices do resultado 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 MUNDO = 35.0 JANELA = (-25.0, 25.0, -18.0, 18.0) # (x_min, x_max, y_min, y_max) # Um "colchete" deitado: dois braços horizontais ligados por uma espinha # vertical à direita. Repare onde a espinha está em relação à janela — o # roteiro volta a esse detalhe no fim. POLIGONO = [ (32.0, -24.0), (32.0, 24.0), (-40.0, 24.0), (-40.0, 8.0), (27.0, 8.0), (27.0, -8.0), (-40.0, -8.0), (-40.0, -24.0), ] BORDAS = ("direita", "superior", "esquerda", "inferior") # --------------------------------------------------------------------------- # O algoritmo — é isto que você vai escrever # --------------------------------------------------------------------------- def dentro(ponto: tuple[float, float], borda: str, janela: tuple[float, float, float, float]) -> bool: """O ponto está do lado de dentro do semiplano desta borda? Atenção: "dentro" aqui é em relação a UMA borda, não à janela inteira. Um ponto pode estar dentro em relação à borda direita e fora em relação à superior — e é justamente isso que permite tratar uma borda por vez. """ x_min, x_max, y_min, y_max = janela x, y = ponto # TODO 2 — quatro comparações, uma por borda. A da esquerda está pronta # como modelo; complete as outras três. if borda == "esquerda": return x >= x_min if borda == "direita": return True # TODO if borda == "inferior": return True # TODO return True # superior — TODO def intersecao(p1: tuple[float, float], p2: tuple[float, float], borda: str, janela: tuple[float, float, float, float]) -> tuple[float, float]: """Onde o segmento p1-p2 cruza a reta que contém esta borda.""" x_min, x_max, y_min, y_max = janela x1, y1 = p1 x2, y2 = p2 # TODO 3 — para uma borda VERTICAL, x é conhecido e falta descobrir y; # para uma HORIZONTAL, o contrário. As duas fórmulas estão na página da # aula, na seção "Sutherland-Hodgman, uma borda por vez". # # Antes de escrever: esta função pode ser chamada com um segmento # paralelo à borda? Releia quem a chama, no TODO 4, antes de responder — # a resposta decide se você precisa de um `if` protegendo a divisão. return p1 def recortar_borda(poligono: list[tuple[float, float]], borda: str, janela: tuple[float, float, float, float], ) -> list[tuple[float, float]]: """Uma passagem: recorta o polígono contra UMA borda.""" saida: list[tuple[float, float]] = [] for k in range(len(poligono)): anterior = poligono[k - 1] # em k = 0 isto é o ÚLTIMO vértice: corrente = poligono[k] # é o que fecha a volta sem caso especial anterior_dentro = dentro(anterior, borda, janela) corrente_dentro = dentro(corrente, borda, janela) # TODO 4 — os quatro casos. Para cada aresta (anterior → corrente), # decida o que entra em `saida`, e em que ORDEM: # # anterior corrente o que sai # -------- -------- --------- # dentro dentro ? # dentro fora ? # fora dentro ? # fora fora ? # # Duas dicas que evitam o erro mais comum: o vértice emitido é sempre # o CORRENTE, nunca o anterior; e em um dos quatro casos saem DOIS # pontos. return saida def recortar_poligono(poligono: list[tuple[float, float]], janela: tuple[float, float, float, float], passagens: int = 4, ) -> list[tuple[float, float]]: """As quatro passagens em sequência. ``passagens`` existe para que as teclas 1 a 4 mostrem os estados intermediários — use isso para depurar. """ resultado = list(poligono) # TODO 5 — encadeie as passagens: a saída de uma é a entrada da seguinte. # Use `BORDAS[:passagens]`. Pense também no que fazer se uma passagem # devolver lista vazia. # Enquanto o TODO acima não for feito, devolver lista vazia mantém a tela # sem nenhum contorno vermelho — assim o primeiro vermelho que aparecer # será mesmo resultado do seu código. Apague esta linha ao implementar. return [] # --------------------------------------------------------------------------- class Poligono2DBase(QOpenGLWidget): def __init__(self, parent=None): super().__init__(parent) self.passagens = 4 self.mostrar_original = True self.preencher = False self.mostrar_vertices = False self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self._atualizar_titulo() def initializeGL(self) -> None: glClearColor(0.10, 0.11, 0.13, 1.0) 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() if w <= h: gluOrtho2D(-MUNDO, MUNDO, -MUNDO * h / w, MUNDO * h / w) else: gluOrtho2D(-MUNDO * w / h, MUNDO * w / h, -MUNDO, MUNDO) glMatrixMode(GL_MODELVIEW) glLoadIdentity() def paintGL(self) -> None: glClear(GL_COLOR_BUFFER_BIT) self._desenhar_janela() if self.mostrar_original: glColor3f(0.34, 0.37, 0.44) glLineWidth(1.5) glBegin(GL_LINE_LOOP) for x, y in POLIGONO: glVertex2f(x, y) glEnd() recortado = recortar_poligono(POLIGONO, JANELA, self.passagens) if not recortado: return if self.preencher: # Antes de apertar `P`, releia o que a Aula 02 diz sobre o que # GL_POLYGON garante — e depois compare com o que você vê. glColor3f(0.45, 0.18, 0.18) glBegin(GL_POLYGON) for x, y in recortado: glVertex2f(x, y) glEnd() glColor3f(0.95, 0.35, 0.35) glLineWidth(4.0) glBegin(GL_LINE_LOOP) for x, y in recortado: glVertex2f(x, y) glEnd() if self.mostrar_vertices: glColor3f(0.95, 0.95, 0.98) glPointSize(8.0) glBegin(GL_POINTS) for x, y in recortado: glVertex2f(x, y) glEnd() @staticmethod def _desenhar_janela() -> None: x_min, x_max, y_min, y_max = JANELA glColor3f(0.55, 0.60, 0.70) glLineWidth(2.0) glEnable(GL_LINE_STIPPLE) glLineStipple(3, 0xAAAA) glBegin(GL_LINE_LOOP) glVertex2f(x_min, y_min) glVertex2f(x_max, y_min) glVertex2f(x_max, y_max) glVertex2f(x_min, y_max) glEnd() glDisable(GL_LINE_STIPPLE) def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() return elif Qt.Key.Key_0 <= tecla <= Qt.Key.Key_4: self.passagens = tecla - Qt.Key.Key_0 elif tecla == Qt.Key.Key_O: self.mostrar_original = not self.mostrar_original elif tecla == Qt.Key.Key_P: self.preencher = not self.preencher elif tecla == Qt.Key.Key_V: self.mostrar_vertices = not self.mostrar_vertices else: return self._atualizar_titulo() self.update() def _atualizar_titulo(self) -> None: """O título é o painel de instrumentos do programa. A contagem de vértices é o checkpoint do roteiro, então ela precisa estar visível já na abertura — e não só depois da primeira tecla. """ etapa = ("original (sem recorte)" if self.passagens == 0 else f"após {self.passagens} passagem(ns) — última: " f"{BORDAS[self.passagens - 1]}") recortado = recortar_poligono(POLIGONO, JANELA, self.passagens) self.setWindowTitle(f"Recorte de polígonos — {etapa} — " f"{len(recortado)} vértices") 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 = Poligono2DBase() janela.setWindowTitle("Atividade 2 — Recorte de polígonos") janela.resize(760, 560) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())