"""Recorte de retas pelo algoritmo de Cohen-Sutherland. Aula 03 — Visualização 2D: recorte. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python recorte2d.py O OpenGL recorta sozinho tudo o que cai fora da window — este exemplo refaz o trabalho à mão, numa window de recorte MENOR que a área visível, para que o resultado do algoritmo possa ser visto ao lado da geometria original. Teclas: O mostra ou esconde os segmentos originais C liga ou desliga o recorte K imprime no terminal os códigos de região de cada segmento Esc fecha Ideias para experimentar: acrescente segmentos à lista ``SEGMENTOS``; mude ``JANELA`` e observe quais casos deixam de ser triviais; conte quantas iterações o laço faz para cada segmento. """ 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 = 50.0 JANELA = (-25.0, 25.0, -18.0, 18.0) # (x_min, x_max, y_min, y_max) # Segmentos escolhidos para cobrir os casos previstos pelo algoritmo. SEGMENTOS = [ ((-15.0, -8.0), (12.0, 10.0)), # inteiro dentro: aceitação trivial ((-45.0, 30.0), (-32.0, 42.0)), # inteiro fora, à esquerda e acima ((-40.0, 0.0), (0.0, 6.0)), # entra por uma borda ((-45.0, -30.0), (45.0, 30.0)), # atravessa de lado a lado ((10.0, 24.0), (34.0, 8.0)), # corta o canto superior direito ((-48.0, 24.0), (48.0, 24.0)), # passa acima sem cruzar ((0.0, -45.0), (0.0, 45.0)), # vertical: dx = 0 ] # --- códigos de região ----------------------------------------------------- # Quatro bits, do mais para o menos significativo: acima, abaixo, direita, # esquerda. A ordem em si é arbitrária — o que não é arbitrário é usá-la de # forma consistente entre a codificação e os testes. ACIMA = 0b1000 ABAIXO = 0b0100 DIREITA = 0b0010 ESQUERDA = 0b0001 def codigo_regiao(x: float, y: float, janela: tuple[float, float, float, float]) -> int: """Classifica um ponto em relação à window, em 4 bits.""" x_min, x_max, y_min, y_max = janela codigo = 0 if x < x_min: codigo |= ESQUERDA elif x > x_max: codigo |= DIREITA if y < y_min: codigo |= ABAIXO elif y > y_max: codigo |= ACIMA return codigo def recortar(p1: tuple[float, float], p2: tuple[float, float], janela: tuple[float, float, float, float], ) -> tuple[tuple[float, float], tuple[float, float]] | None: """Cohen-Sutherland: devolve o segmento recortado, ou ``None``. O laço tem no máximo quatro voltas úteis: cada volta troca um extremo de fora por uma interseção com uma borda, e há quatro bordas. """ x1, y1 = p1 x2, y2 = p2 x_min, x_max, y_min, y_max = janela c1 = codigo_regiao(x1, y1, janela) c2 = codigo_regiao(x2, y2, janela) while True: if c1 | c2 == 0: # Nenhum bit ligado nos dois extremos: ACEITAÇÃO TRIVIAL. return (x1, y1), (x2, y2) if c1 & c2 != 0: # Há um bit ligado nos DOIS extremos, isto é, ambos estão do # mesmo lado de uma mesma borda: REJEIÇÃO TRIVIAL. return None # Caso geral: escolhe um extremo que esteja fora e o substitui pela # interseção da reta com a borda correspondente. fora = c1 if c1 != 0 else c2 if fora & ACIMA: x = x1 + (x2 - x1) * (y_max - y1) / (y2 - y1) y = y_max elif fora & ABAIXO: x = x1 + (x2 - x1) * (y_min - y1) / (y2 - y1) y = y_min elif fora & DIREITA: y = y1 + (y2 - y1) * (x_max - x1) / (x2 - x1) x = x_max else: # fora & ESQUERDA y = y1 + (y2 - y1) * (x_min - x1) / (x2 - x1) x = x_min # As divisões acima nunca dividem por zero: um segmento vertical # (x1 == x2) jamais tem os bits DIREITA ou ESQUERDA ligados sem que # ambos os extremos os tenham — e nesse caso a rejeição trivial já # teria devolvido None. O mesmo vale, por simetria, para o horizontal. if fora == c1: x1, y1 = x, y c1 = codigo_regiao(x1, y1, janela) else: x2, y2 = x, y c2 = codigo_regiao(x2, y2, janela) class Recorte2D(QOpenGLWidget): """Desenha os segmentos originais e o resultado do recorte.""" def __init__(self, parent=None): super().__init__(parent) self.mostrar_originais = True self.recortar_ligado = True self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) 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_originais: glColor3f(0.34, 0.37, 0.44) glLineWidth(1.5) glBegin(GL_LINES) for p1, p2 in SEGMENTOS: glVertex2f(*p1) glVertex2f(*p2) glEnd() if self.recortar_ligado: glColor3f(0.95, 0.35, 0.35) glLineWidth(4.0) glBegin(GL_LINES) for p1, p2 in SEGMENTOS: recortado = recortar(p1, p2, JANELA) if recortado is not None: a, b = recortado glVertex2f(*a) glVertex2f(*b) glEnd() @staticmethod def _desenhar_janela() -> None: x_min, x_max, y_min, y_max = JANELA # Prolongamento das quatro bordas: é o que divide o plano nas nove # regiões dos códigos de Cohen-Sutherland. glColor3f(0.20, 0.22, 0.28) glLineWidth(1.0) glBegin(GL_LINES) for x in (x_min, x_max): glVertex2f(x, -MUNDO * 2) glVertex2f(x, MUNDO * 2) for y in (y_min, y_max): glVertex2f(-MUNDO * 2, y) glVertex2f(MUNDO * 2, y) glEnd() # A window de recorte propriamente dita, tracejada. 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 tecla == Qt.Key.Key_O: self.mostrar_originais = not self.mostrar_originais elif tecla == Qt.Key.Key_C: self.recortar_ligado = not self.recortar_ligado elif tecla == Qt.Key.Key_K: self._relatar_codigos() return else: return self.update() @staticmethod def _relatar_codigos() -> None: print("\n segmento cod(P1) cod(P2) decisão") print(" " + "-" * 62) for p1, p2 in SEGMENTOS: c1 = codigo_regiao(*p1, JANELA) c2 = codigo_regiao(*p2, JANELA) if c1 | c2 == 0: decisao = "aceitação trivial" elif c1 & c2 != 0: decisao = "rejeição trivial" else: decisao = "calcula interseção" rotulo = f"({p1[0]:6.1f},{p1[1]:6.1f}) → ({p2[0]:6.1f},{p2[1]:6.1f})" print(f" {rotulo} {c1:04b} {c2:04b} {decisao}") def main() -> int: print(__doc__) fmt = QSurfaceFormat() fmt.setVersion(2, 1) fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile) fmt.setDepthBufferSize(0) QSurfaceFormat.setDefaultFormat(fmt) app = QApplication(sys.argv) janela = Recorte2D() janela.setWindowTitle("Recorte — Cohen-Sutherland") janela.resize(760, 560) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())