GEOMETRIA COMPUTACIONAL – ALGORITMO DE CYRUS-BECK
Visualizado 1492 vezes.
Neste 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.
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:
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.
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,
Substituindo P(t) pela equação paramétrica da reta que contem o segmento de reta e explicitando o parâmetro t, obteremos sucessivamente:
e, definindo D=P1-P0,
Para ser possível calcular t é necessário que:
- 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.
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).
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.
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#include //********* 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=;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=; 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[].x = 50; P.pt[].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[].x = 50; P.pt[].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(); }#include #include
No hay comentarios:
Publicar un comentario