openGl學習之判定點在三角形的內部還是外部

#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <gl/glut.h>
#include <gl/GLU.h>
#include <Windows.h>

using namespace std;

int screenWidth=640;
int screenHeight=480;

typedef struct
{
	float x;
	float y;
}Point;

typedef struct
{
	float x;
	float y;
}Vec;

void init()
{
	glClear(GL_COLOR_BUFFER_BIT);
	glClearColor(0.0,0.0,0.0,1.0);
	glMatrixMode(GL_PROJECTION);
	gluOrtho2D(0.0,screenWidth,0.0,screenHeight);
	glPointSize(10);
}

float CrossProduct(Point a,Point b,Point c)
{
	return abs((b.x - a.x)*((screenHeight-c.y) - (screenHeight-a.y)) - ((screenHeight-b.y) - (screenHeight-a.y))*(c.x - a.x));

}


int calc(Point a,Point b,Point c,Point p)
{
	float area_1 = CrossProduct(a,b,c)*0.5;
	cout<<"the area is "<<area_1<<endl;
	float area_2 = CrossProduct(a,b,p)*0.5;
	float area_3 = CrossProduct(a,c,p)*0.5;
	float area_4 = CrossProduct(b,c,p)*0.5;


	if (abs(area_1 - (area_2+area_3+area_4))<1e-6)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}



void display(int x,int y)
{
	
	glBegin(GL_POINTS);
		glVertex2f(x,y);
	glEnd();
	//glFlush();
}



void selectFont(int size, int charset, const char* face) 
{
	HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,
		charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
	HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
	DeleteObject(hOldFont);
}

void drawString(const char *str)
{
	static int isFirstCall = 1;
	static GLuint lists;
	 selectFont(48, ANSI_CHARSET, "Comic Sans MS");
	if (isFirstCall)
	{
		isFirstCall = 0;
		lists = glGenLists(128);
		wglUseFontBitmaps(wglGetCurrentDC(),0,128,lists);
	}
	for (;*str!='\0';str++)
	{
		glCallList(lists+(*str));
	}
}

int k = 0;
Point p[8];
bool flag = true;
void mymouse(int button,int state,int x,int y)
{
	
	if (state == GLUT_DOWN)
	{
		if (button == GLUT_LEFT_BUTTON)
		{
			display(x,screenHeight - y);
		}
		glFlush();
	}
	else if(button == GLUT_RIGHT_BUTTON)
	{
		glClear(GL_COLOR_BUFFER_BIT);
		glClearColor(0.0f,0.0f,0.0f,0.0f);
		glRasterPos2f(0,0);
		drawString("Right Button clear screen!");
		glFlush();
	}
	//p = new Point[k+2];
	p[k].x = x;
	p[k].y = y;
	k++;
	cout<<"k is :"<<k<<endl;
	if (k == 6)
	{
		
		glBegin(GL_LINE_LOOP);
			for (int i=0;i<6;i+=2)
			{
				glVertex2f(p[i].x,screenHeight - p[i].y);
			}
		glEnd();
		glFlush();
	}
	if (k==8)
	{
#if 0
		cout<<"result is :"<<calc(p[0],p[2],p[4],p[6])<<endl;
#endif
		if (calc(p[0],p[2],p[4],p[6]))//如果判定點在三角形的內部,則下面的點開始用紅色的點進行繪製
		{
			glColor3f(1.0,0.0,0.0);
		}
		else
		{
			glColor3f(0.0,1.0,0.0);//如果判定點在三角形的內部,則下面的點開始用綠色的點進行繪製
		}
	}
	
	
	if (k==8)
	{
		k = 0;
	}
}

void myDisplay()
{
	drawString("Right Button clear screen!");
	glFlush();
}


int main(int argc,char **argv)
{
	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowPosition(200,200);
	glutInitWindowSize(screenWidth,screenHeight);
	glutCreateWindow("hello");
	init();
	glutDisplayFunc(myDisplay);
	glutMouseFunc(mymouse);
	glutMainLoop();
	return 0;
}

判斷點在三角形的內部還是外部有很多的方法

這裏給出的代碼是最簡單的秋三角形面積的方法

下面介紹一下大致有幾種方法:

轉載自http://www.cnblogs.com/cgwolver/archive/2008/07/31/1257611.html

方法1:三個Perplane的方法

           設AB,BC,AC邊上的垂直平面爲Perplane[3],垂直朝向內側的法向爲n[3]

          1)先根據任意兩邊叉出法向N

               N = AB.CrossProduct(AC); 

               N.Normalize();

               D = A.DotProduct( N );

          2)如果P在三角形所在平面之外,可直接判定不在平面之內( 假定方程爲 ax+by+cz+d = 0 )

               if( P.DotProduct( N ) + D > 0 ) return false; 

          3)然後法向和各邊叉出垂直平面的法向

               n[0] = N.CrossProduct(AB); //朝向內側

               n[0].Normalize();

               Perplane[0].dist = A.DotProduct(n[0]);

               Perplane[0].normal = n[0];

               同樣方法求得Perplane[1],Perlane[2];

          3)因爲三個Perplane都朝向三角形內側,P在三角形內的條件是同時在三個Perplane前面;如果給定點P在任意一個垂直平面之後,那麼可判定P在三角形外部

               for( int i = 0;i<3;j++ )

               {

                    if( P.DotProduct( Perplane[i].normal ) + Perplane[i].dist < 0 )

                         return false;

               }

               return true;//如果P沒有在任意一條邊的外面,可判斷定在三角形之內,當然包括在邊上的情況

 

方法2:三個部分面積與總面積相等的方法

 

          S(PAB) + S(PAC) + S( PBC) = S(ABC) 則判定在三角形之內

          用矢量代數方法計算三角形的面積爲

               S = 1/2*|a|*|b|*sin(theta)

                  = 1/2*|a|*|b|*sqrt(1-cos^2(theta))

                  = 1/2*|a|*|b|*sqrt(1- (a.DotProduct(b)/(|a|*|b|))^2);

 

               另一種計算面積的方法是 S = 1/2*|a.CrossProduct(b)|

 

               比較一下,發現後者的精確度和效率都高於前者,因爲前者需要開方和求矢量長度,矢量長度相當於一次點乘,三個點乘加一個開方,顯然不如

               後者一次叉乘加一次矢量長度(注,一次叉乘計算相當於2次點乘,一次矢量長度計算相當於一次點乘),後者又對又快。 

                 

               S(ABC)  = AB.CrossProduct(AC);//*0.5;

               S(PAB)  = PA.CrossProduct(PB);//*0.5;

               S(PBC)  = PB.CrossProduct(PC);//*0.5;

               S(PAC)  = PC.CrossProduct(PA);//*0.5;

 

               if( S(PAB) + S(PBC) + S(PAC) == S(ABC)  )

                    return true;

               return false;

          

        另一種計算三角形面積的矢量方法是 1/2*a.CrossProdcuct(b) ,CrossProduct = ( y1*z2 - y2*z1 , x1*z2 - x2*z1, x1*y2 - x2*z1 )

               可以看到CrossProduct 的計算要比DotProduct多3個乘法計算,效率沒有上面的方法高


方法3:三個向量歸一化後相加爲0

 

        這個方法很怪異,發現自http://flipcode.spaces.live.com/blog/cns!8e578e7901a88369!903.entry 下面的一個回帖

               
               

          如上圖三角形ABC,P爲AB外側一點,N1,N2,N3 分別爲BP,AP,CP的歸一化矢量;NM爲N1,N2夾角的角平分線

          可以看出角A-P-B是三角形內角,必然小於180度,那麼角N1-P-N2等於A-P-B;NM是N1-P-N2的角平分線,那麼角B-P-N等於角N-P-A,而CPN必然小於其中一個,

          即小於180/2 = 90度。結論是角N1,N2的合矢量方向與N3的夾角爲銳角。所以N1,N2,N3的合向量模大於1.

          這裏注意,N3不一定在N1,N2之間,不能假定N2-P-N3 和N3-P-N1這兩個角一定是銳角

          同樣可以推導出如果P在三角形內,N1+N2+N3必然小於0;若N1+N2+N3 = 0則P在三角形的邊上。

          有沒有更簡單的推導方法?

          

          這個方法看起來很精巧,但是善於優化的朋友會立刻發現,三個矢量歸一化,需要三個開方。迭代式開方太慢了,而快速開方有的時候又不滿足精度要求。

                  

 方法4:重心座標之和爲1

 

         {

               BaryCenter = ( S(PAB)/S(PABC),S(PBC)/S(PABC),S(PAC)/S(PABC)) // 點P在三角形內的重心座標

          

               if( BaryCenter.x + BaryCenter.y + BaryCenter.z >0.f )

                    return false

               return true;

          }

 

          其中S(PAB),S(ABC),S(PBC),S(PBC) 用上述的方法二種提到的計算三角形面積方法計算。 

 

綜合比較

     方法1必須求叉乘,雖然可以通過首先排除不在平面內的點,但是後面仍要求三個叉乘和3個點乘(當然還可排除法優化)

     方法2看起來之需要求4個點乘,如果用叉乘方法計算面積,可能會導致效率低下

     方法3是看起來是最精巧的方法,但是效率也不能保證...3個開方

     方法4和方法2的效率差不多


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章