Geometria Computacional

Geometria Computacional – Algoritmo de Cyrus-Beck

SNAGHTML1059a8fNeste artigo, vou apresentar uma introdução ao algoritmo de Cyrus-Back para o campo da geometria computacional.

Os cientistas Cyrus e Beck seguiram uma aproximação ao problema do recorte de segmentos de reta no qual conceberam um algoritmo que permite o recorte de um segmento de reta por qualquer polígono convexo, em 2D, ou de um segmento de reta em 3D por qualquer poliedro convexo.

Consideremos a representação paramétrica de um segmento de reta. Como pode observar-se na figura abaixo, existem sempre 4 valores de t que resultam da interseção da reta contendo o segmento do reta a recortar com as 4 arestas do retângulo de recorte.

Baixe o código fonte do projeto aqui: Login to view.

image

Recorte paramétrico: pontos de interseção da reta contendo o segmento de reta a recortar com o retângulo de recorte.

Através da classificação de cada um dos pontos de interseção é possível saber quais os valores de t dos vértices do segmento recortado, caso existam (no exemplo da figura seriam os vértices correspondentes a t2 e t3).

 

Interseção entre linhas

Considere-se novamente a representação paramétrica de um segmento de reta:

image

O polígono de recorte é definido pela enumeração dos seus lados (ou vértices) segundo a convenção da circulação direta (sentido contrário ao dos ponteiros do relógio). Para cada aresta define-se a normal, Ni, orientada para o exterior do polígono.

Seja “Pei” um ponto arbitrário pertencente a uma aresta do polígono de recorte. Como pode observar-se na figura abaixo, podemos definir três vectores que partem desse ponto, um deles seguindo a fronteira e os outros dois para o espaço interior e para o espaço exterior ao polígono convexo de recorte.

image

Algoritmo de Cyrus-Beck: produtos internos dos três vectores com a normal exterior ao polígono de recorte.

O produto interno da normal à aresta do polígono com cada um destes três vectores é, respectivamente, nulo, negativo e positivo. O sinal do produto interno permite determinar se um ponto do segmento de reta coincide com a aresta ou é um ponto interior ou exterior do polígono de recorte.

Para determinar o ponto de interseção do segmento de reta com a aresta que estamos a considerar é necessário obter o valor do parâmetro no ponto de interseção. Como vimos atrás, no ponto de interseção o produto interno da normal à aresta com o vector que une o ponto arbitrário “Pei” com o ponto de interseção é nula, ou seja,

image

Substituindo P(t) pela equação paramétrica da reta que contem o segmento de reta e explicitando o parâmetro t, obteremos sucessivamente:

image

e, definindo D=P1-P0,

image

Para ser possível calcular t é necessário que:

  • Ni 0, o que é sempre verdadeiro;

  • P1 e P0 não sejam coincidentes, o que é verdadeiro para qualquer segmento de reta de comprimento não nulo;

  • Ni * D ≠ 0, o que aconteceria se o segmento de reta a recortar fosse paralelo à aresta do polígono;

Não existe interseção se o valor de t for inferior a 0 ou superior a 1.

Tendo sido obtido um modo de calcular se um ponto está no exterior ou no interior do polígono convexo de recorte e um modo de calcular o valor de t na interseção está em condições de descrever o algoritmo paramétrico.

 

Descrição do Algoritmo

O algoritmo de Cyrus-Beck aplica-se sucessivamente a cada uma das arestas do polígono convexo. Para cada uma das arestas, escolhe-se, por exemplo, o primeiro vértice como “Pei”, calcula-se a normal exterior, Ni e o valor de t no ponto de interseção.

É ainda necessário identificar se o primeiro vértice do segmento de reta está no exterior e o 2º no interior, situação em que a interseção é definida como Potencialmente de Entrada, PE, ou vice-versa em que a interseção é definida como Potencialmente de Saída, PS.

Para efetuar essa identificação poderia classificar-se cada vértice do segmento de reta, tal como acontecia no algoritmo da Cohen-Sutherland. No entanto, é possível efetuar a classificação com base no ângulo existente entre o vector P0P1 e o vector Ni. Se o ângulo for inferior a 90º trata-se de uma interseção do tipo PS, se não, será do tipo PE. Esta informação está contida no sinal do produto interno entre aqueles dois vectores e que é necessário para o cálculo do próprio valor de t.

image

Estando calculados todos os valores t dos pontos de interseção, e devidamente classificados como sendo do tipo PE ou PS, estamos em condições de identificar exatamente os valores de t dos vértices do segmento de reta já recortado.

Identifica-se a interseção do tipo PE que corresponde ao maior t e a interseção do tipo PS que corresponde ao menor t e comparam-se os dois valores de t.

Se o valor de t da interseção do tipo PE for superior ao valor de t da interseção do tipo PS, então todo o segmento de reta pode ser rejeitado.

Se o valor de t da interseção do tipo PE for inferior ao valor de t da interseção do tipo PS, então usam-se os dois valores para obter as coordenadas dos vértices do segmento de reta recortado.

A figura abaixo apresenta um polígono convexo de recorte com 5 lados e um segmento de reta a recortar (P0P1).

image

Algoritmo de Cyrius-Beck: segmento de reta a recortar com identificação do tipo (PE ou PS) das suas intersecções com o polígono de recorte.

Do cálculo das intersecções resultam 4 valores de t, sendo dois do tipo PE e dois do tipo PS. Como o segmento de reta é paralelo ao lado inferior direito do polígono, o 5º valor de t não foi calculado uma vez que Ni * D = 0.

Escolhendo-se a interseção do tipo PE com maior valor de t e a interseção do tipo PS com o menor valor de t obtêm-se os valores paramétricos de cada um dos vértices do segmento recortado.

Considere-se agora o exemplo apresentado pela figura abaixo. Aplicando o algoritmo, são calculados 5 valores de t dos quais só são classificados os três valores representados na figura uma vez que os restantes têm valores de t inferiores a 0 ou superiores a 1.

image

Algoritmo de Cyrus-Beck: segmento de reta rejeitado (o maior t das intersecções de tipo PE é superior ao menor t de tipo PS).

Como pode observar-se, a interseção do tipo PS tem um valor de t inferior ao maior valor de t de uma interseção do tipo PE pelo que se conclui que o segmento de reta deve ser rejeitado.

Pseudocódigo (Algoritmo)

Pré-calcular Ni e selecionar “Pei” para cada aresta.

Para cada segmento de reta

Se P1 = P0 então

Segmento de reta degenerado: recortar como ponto

te = 0; tl= 1;

Para cada aresta de recorte

Se Ni . D ¹ 0 então /* ignorar arestas paralelas ao segmento */

calcular t; /* de interseção do segmento com aresta */

usar sinal de Ni . D para caracterizar PE ou PS;

Se PE então tE = max(tE, t);

Se PS então tS = min(tS, t);

FimSe

Se tE > tS então devolve nulo;

Senão devolve [P(tE), P(tS)];

Código em C usando OpenGL

#include <windows.h>   
#include <gl/Gl.h>
#include <gl/glut.h>
#include <iostream>

//********* Estrutura de Dados

struct GLintPoint
{ GLint x,y;
};

struct GLfloatPoint
{ GLfloat x,y;
};

const int MAX = 100;

class GLintPointArray
{ 
public:  
  int num;
  GLintPoint pt[MAX];
};

class GLfloatPointArray
{
public:
  int num;
  GLfloatPoint pt[MAX];
};

//********** Rotinas de suporte
typedef GLfloat colorType[3];

void drawDot (GLint x, GLint y, GLfloat r, GLfloat g, GLfloat b)
{ glColor3f(r,g,b);

  glBegin (GL_POINTS);
  glVertex2i (x,y);
  glEnd();
}

void drawIntPolygon (GLintPointArray P, colorType c)
{ glColor3fv (c);

  glBegin(GL_LINE_LOOP);
    for (int i=0;i<P.num;i++)

	glVertex2i (P.pt[i].x,P.pt[i].y);

  glEnd();
}

void drawFloatPolygon (GLfloatPointArray P, colorType c)
{ glColor3fv (c);

  glBegin(GL_LINE_LOOP);
   for (int i=0; i < P.num; i++)

     glVertex2f (P.pt[i].x,P.pt[i].y);

  glEnd();
}

//**************** Inicialização do programa 
 void myInit(void)
 {
    glClearColor(0.0,0.0,0.0,0.0);  // Coloca fundo branco

	glColor3f (0.0f,0.0f,0.0f);    
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity();

    gluOrtho2D(0.0, 640.0, 0.0, 480.0);
}

//**************  Subrotinas de desenho.


const int MaxN = 100;
typedef GLfloatPoint Normals[MaxN];


void buildintPolygon (GLintPointArray &P)
{ P.num = 4; 
  P.pt[0].x = 50; P.pt[0].y =  100; 
  P.pt[1].x = 150; P.pt[1].y = 200; 
  P.pt[2].x = 100; P.pt[2].y = 300; 
  P.pt[3].x = 10; P.pt[3].y =  200; 
}

void buildfloatPolygon (GLfloatPointArray &P)
{ P.num = 4;

  P.pt[0].x = 50; P.pt[0].y = 100; 
  P.pt[1].x = 150; P.pt[1].y = 200;

  P.pt[2].x = 100; P.pt[2].y = 300;

  P.pt[3].x = 10; P.pt[3].y = 200; 
}

float DotProduct (GLfloatPoint v1, GLfloatPoint v2)
{ 
   return v1.x*v2.x + v1.y*v2.y;
}


void CalcNormals (GLfloatPointArray p, Normals & n)
{ int i,j,k;

  GLfloatPoint v;

  for (i = 0; i < p.num; i++)
  { j = (i+1)%p.num;

    k = (i+2)%p.num;
    n[i].x = -(p.pt[j].y - p.pt[i].y)/(p.pt[j].x - p.pt[i].x);

    n[i].y = 1.0; 
    v.x = p.pt[k].x - p.pt[i].x;

    v.y = p.pt[k].y - p.pt[i].y;

    if (DotProduct (n[i],v) > 0)    // 

    { n[i].x *= -1;
      n[i].y  = -1;
    }
  }
}

    

void CBClip (GLfloatPoint p1, GLfloatPoint p2, Normals n, GLfloatPointArray p, 
             bool & visible, GLfloatPoint & rp, GLfloatPoint & q)
{ float t1,t2,t,numer,den;

  GLfloatPoint dirV,F;          // Vetores
  int i;

  t1 = 0.0;

  t2 = 1.0;
    // Calcula a direção do vetor.
  dirV.x = p2.x - p1.x;

  dirV.y = p2.y - p1.y;

  visible = true;
  i = 0;

  while ( (i < p.num) && visible)
  { F.x = p1.x - p.pt[i].x;

    F.y = p1.y - p.pt[i].y;

    numer  = DotProduct (n[i],F);
    den   =  DotProduct (n[i],dirV);

    if (den == 0.0)          
    { if (numer > 0.0)

         visible = false;   
    }
    else 
    { t = -(numer/den);

      if (den < 0.0)        
      { if (t <= 1.0)

          if (t > t1)
             t1 = t;
      }

      else if ( t >= 0.0)    
      { if (t < t2)

          t2 = t;
      }
    }
    i++;
  }
  if ( t1 <= t2)
  { rp.x = p1.x + t1*dirV.x;

    rp.y = p1.y + t1*dirV.y;

    q.x = p1.x + t2*dirV.x;

    q.y = p1.y + t2*dirV.y;
  }

  else
   visible = false;
}



//******************* Exibe na tela **********************
void myDisplay(void)
{     
  GLfloatPointArray Poly;

  colorType C = {1.0f,0.0f,1.0f};
  bool visible;

  GLfloatPoint p1 = {40,20},p2 = {200,400},cp1,cp2;

  Normals Nor;

  glClear(GL_COLOR_BUFFER_BIT);     // Limpa a tela 
  
  // 1. Cria o polígono
  buildfloatPolygon(Poly);

  drawFloatPolygon (Poly,C);
  
  // 2. Cria as linhas de interseção
  glColor3f (0.0f,3.0f,0.0f);

  glBegin(GL_LINES);
    glVertex2f (40,20);
    glVertex2f (200,400);

  glEnd();

  // 3. Corta a linha
  CalcNormals (Poly,Nor);
  CBClip (p1,p2,Nor,Poly,visible,cp1,cp2);

  if (visible)
  {	  
	  glColor3f (1.0f,1.0f,0.0f);

	  glBegin(GL_LINES);
	  glVertex2f (cp1.x,cp1.y);

	  glVertex2f (cp2.x,cp2.y);
	  glEnd();
  }

  glFlush();
}

//******************* Principal **********************
// Rotinas do OpenGL
void main(int argc, char** argv)
{

  glutInit(&argc, argv);          
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); 
  glutInitWindowSize(640,480);     
  glutInitWindowPosition(100, 150); 
  glutCreateWindow("Cyrus-Beck"); 
  glutDisplayFunc(myDisplay);     
  myInit();          
  glutMainLoop();      
}

Espero que tenha gostado do artigo. Qualquer coisa entre em contato!

Márcio Pulcinelli @ OminaVincit!

Referências:

Computational Geometry in C, Joseph O’Rourke – Cambridge University Press (1998).

A polyhedron representation for computer vision, B. Baumgart, AFIPS Conf. Proc(1975).

Computational Geometry: an Introduction, F. P. Preparata e M. I. Shamos, Springer-Verlag, New York (1985).

Finding the convex hull of a simple polygon, R. L. Graham e F. F. Yao (1983).