準備工作
判斷點在有向線段的左側
可以通過叉積判斷,如下爲k在有向線段ab的左側代碼描述:
double multiply(Point a, Point b, Point k)
{
double x1 = b.x-a.x;
double y1 = b.y-a.y;
double x2 = k.x-a.x;
double y2 = k.y-a.y;
return x1*y2-x2*y1;
}
bool toLeft(Point a, Point b, Point k)
{
return multiply(a,b,k)>0;
}
判斷點在三角形的內部
給三角形abc定義一定的次序,按照一般習慣,假設abc是逆時針的,則判斷k是否在三角形內部,只需要判斷k是否在有向線段ab,bc,ac的左側:
bool inTriangle(Point a, Point b, Point c, Point k)
{
bool abLeft = toLeft(a,b,k);
bool bcLeft = toLeft(b,c,k);
bool caLeft = toLeft(c,a,k);
return (abLeft==bcLeft)&&(bcLeft==caLeft);
}
幾種典型的算法
極點算法
凸包上的頂點稱爲極點,極點有一個特性,總可以找到過極點的一條直線使得其他所有的頂點,在這個直線的一側。所以極點不可能在某一個頂點三角形的內部,則可以在初始化時,標示所有的頂點爲極點,然後遍歷所有的頂點組成的三角形,排除掉三角形內部的頂點,則剩下的頂點則爲凸包的極點。該算法時間複雜度爲O(N^4),算法描述如下:
極邊算法
凸包上的邊稱爲極邊,所有的頂點都在極邊的一側,所以可以遍歷所有的邊,檢查它是否爲極邊,算法時間複雜度爲O(N^3),算法描述如下:
GiftWrapping算法
兩個相鄰的極邊之間有一個共同的極點,所以一條極邊的尾端也是另一條極邊的頂端。如果已知一個極點,則可以尋找以該極點作爲頂端的極邊的尾端極點。方法是任取一個點作爲候選點,如果下一個點在已知點與候選點組成的有向線段的右端,則把這個點作爲候選點,這樣不斷的更新。因爲最下的點肯定是一個極點,所以可把最下點作爲初始點。算法的複雜度爲O(N*W)(W是凸包的邊數),算法描述如下:
Graham Scan算法
算法需要藉助一次排序,和兩個棧:
下圖描述了整個流程:
opengl實現
geometry.h文件:
#include <GL/glut.h>
#include <vector>
#include <algorithm>
#include <stack>
using namespace std;
class Point
{
public:
Point(){};
Point(double a,double b,double c):x(a),y(b),z(c),extreme(true){};
public:
double x;
double y;
double z; //平面凸包,此項爲0
bool extreme; //EE算法中用到,標識該點是否爲極點
pair<double,double> ref; //Graham Scan算法中,排序的參考點
bool operator < (const Point &a);
};
class Edge
{
public :
Edge(Point a,Point b):s(a),e(b){};
Point s,e;
};
enum PLOTMODE
{
EP=0,
EE,
GW,
GS
};
double multiply(Point a, Point b, Point c);
bool toLeft(Point a, Point b, Point c);
bool inTriangle(Point a, Point b, Point c, Point k);
void EPAlgorithm(vector<Point> &list);
vector<Edge> EEAlgorithm(vector<Point> list);
vector<Edge> GWAlgorithm(vector<Point> list);
stack<Point> GSAlgorithm(vector<Point>list);
geometry.cpp文件:
#include "Geometry.h"
bool Point::operator<(const Point &a)
{
Point p = Point(ref.first,ref.second,0);
return toLeft(p,*this,a);
}
double multiply(Point a, Point b, Point k)
{
double x1 = b.x-a.x;
double y1 = b.y-a.y;
double x2 = k.x-a.x;
double y2 = k.y-a.y;
return x1*y2-x2*y1;
}
bool toLeft(Point a, Point b, Point k)
{
return multiply(a,b,k)>0;
}
bool inTriangle(Point a, Point b, Point c, Point k)
{
bool abLeft = toLeft(a,b,k);
bool bcLeft = toLeft(b,c,k);
bool caLeft = toLeft(c,a,k);
return (abLeft==bcLeft)&&(bcLeft==caLeft);
}
//極點算法
void EPAlgorithm(vector<Point> &list)
{
//三重循環遍歷所有三角形
for(int i=0;i<list.size();i++)
{
for(int j=i+1;j<list.size();j++)
{
for(int k=j+1;k<list.size();k++)
{
for(int m=0;m<list.size();m++)
{
if(!list[m].extreme)
continue;
if(m==i||m==j||m==k)
continue;
if(inTriangle(list[i],list[j],list[k],list[m]))
list[m].extreme = false;
}
}
}
}
}
//極邊算法
vector<Edge> EEAlgorithm(vector<Point> list)
{
vector<Edge> res;
//二重循環遍歷所有邊
for(int i=0;i<list.size();i++)
{
for(int j=i+1;j<list.size();j++)
{
bool left = true, right = true;
for(int k=0;k<list.size();k++)
{
if(k!=i&&k!=j)
(toLeft(list[i],list[j],list[k])>0?left:right) = false;
}
if(left|right)
res.push_back(Edge(list[i],list[j]));
}
}
return res;
}
//GiftWrapping算法
vector<Edge> GWAlgorithm(vector<Point> list)
{
vector<Point> listCopy = list;
vector<Edge> res;
if(listCopy.size()<=2)
return res;
int ltl = 0;
//找出lowest-then-leftest的點
for(int i=1;i<listCopy.size();i++)
{
if(listCopy[i].y<listCopy[ltl].y||(listCopy[i].y==listCopy[ltl].y&&listCopy[i].x<listCopy[ltl].x))
ltl = i;
}
int p = ltl;
//找出下一條極邊
while(1)
{
int q = -1;
for(int i=0;i<listCopy.size();i++)
{
if(i!=p&&(q<0||!toLeft(listCopy[p],listCopy[q],listCopy[i])))
q = i;
}
res.push_back(Edge(listCopy[p],listCopy[q]));
if(q==ltl)
break;
p = q;
}
return res;
}
Point getPoint(stack<Point>s, int num)
{
stack<Point> temp = s;
for(int i=0;i<num;i++)
{
temp.pop();
}
return temp.top();
}
stack<Point> GSAlgorithm(vector<Point>list)
{
vector<Point> listCopy = list;
stack<Point> S,T;
if(listCopy.size()<3)
return S;
int ltl = 0;
//找出lowest-then-leftest的點
for(int i=1;i<listCopy.size();i++)
{
if(listCopy[i].y<listCopy[ltl].y||(listCopy[i].y==listCopy[ltl].y&&listCopy[i].x<listCopy[ltl].x))
ltl = i;
}
//給所有定點附加ref屬性
for(int i=0;i<listCopy.size();i++)
{
listCopy[i].ref = pair<double,double>(listCopy[ltl].x,listCopy[ltl].y);
}
S.push(listCopy[ltl]);
listCopy.erase(listCopy.begin()+ltl);
//對定點進行排序
sort(listCopy.begin(),listCopy.end());
//構造初始的S和T
S.push(listCopy[0]);
for(int i = listCopy.size()-1;i>=1;i--)
{
T.push(listCopy[i]);
}
while(!T.empty())
{
while(!toLeft(getPoint(S,1),getPoint(S,0),getPoint(T,0)))
{
S.pop();
}
S.push(getPoint(T,0));
T.pop();
}
return S;
}
convexHull.cpp文件:
#include <iostream>
#include <vector>
#include <GL/glut.h>
#include "Geometry.h"
using namespace std;
GLsizei width = 600,height = 600;
vector<Point> list;
PLOTMODE mode ;
void init()
{
glClearColor(0.0f,0.0f,0.0,1.0f);
glViewport(0,0,width,height);
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0,width,0,height);
}
//選擇菜單
void selectMenu(GLint option)
{
switch (option)
{
case 1:
list.clear();
glutPostRedisplay();
break;
case 2:
mode = EP;
glutPostRedisplay();
break;
case 3:
mode = EE;
glutPostRedisplay();
break;
case 4:
mode = GW;
glutPostRedisplay();
break;
case 5:
mode = GS;
glutPostRedisplay();
break;
default:
break;
}
}
void plotPoints(vector<Point> list)
{
glBegin(GL_POINTS);
for(int i=0;i<list.size();i++)
{
glVertex2i(list[i].x,list[i].y);
}
glEnd();
}
void plotEP(vector<Point>list)
{
EPAlgorithm(list);
glPointSize(3.0);
glBegin(GL_POINTS);
for(int i=0;i<list.size();i++)
{
if(list[i].extreme)
glVertex2d(list[i].x,list[i].y);
}
glEnd();
glPointSize(1.0);
}
void plotEE(vector<Point>list)
{
vector<Edge> edge = EEAlgorithm(list);
glBegin(GL_LINES);
for(int i=0;i<edge.size();i++)
{
glVertex2d(edge[i].s.x,edge[i].s.y);
glVertex2d(edge[i].e.x,edge[i].e.y);
}
glEnd();
}
void plotGW(vector<Point>list)
{
vector<Edge> edge = GWAlgorithm(list);
glBegin(GL_LINES);
for(int i=0;i<edge.size();i++)
{
glVertex2d(edge[i].s.x,edge[i].s.y);
glVertex2d(edge[i].e.x,edge[i].e.y);
}
glEnd();
}
void plotGS(vector<Point> list)
{
stack<Point> s = GSAlgorithm(list);
glBegin(GL_LINE_LOOP);
{
while (!s.empty())
{
glVertex2d(s.top().x,s.top().y);
s.pop();
}
}
glEnd();
}
void displayFunc()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0,0.0,0.0);
glPointSize(1.0);
plotPoints(list);
cout<<mode<<endl;
switch (mode)
{
case EE:
plotEE(list);
break;
case EP:
plotEP(list);
break;
case GW:
plotGW(list);
break;
case GS:
plotGS(list);
break;
default:
break;
}
glFlush();
}
void reshapeFunc(GLint newWidth,GLint newHeight)
{
glViewport(0,0,newWidth,newHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,GLdouble(newWidth),0.0,GLdouble(newHeight));
width = newWidth;
height = newHeight;
}
void mouseFunc(GLint button, GLint action, GLint x,GLint y)
{
if(button==GLUT_LEFT_BUTTON&&action==GLUT_DOWN)
{
list.push_back(Point(x,height-y,0));
glutPostRedisplay();
}
}
int main(int argc,char* argv[])
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
glutInitWindowPosition(100,100);
glutInitWindowSize(width,height);
glutCreateWindow("Convex Hull");
init();
glutCreateMenu(selectMenu);
glutAddMenuEntry("清除點",1);
glutAddMenuEntry("極點算法",2);
glutAddMenuEntry("極邊算法",3);
glutAddMenuEntry("GiftWrapping算法",4);
glutAddMenuEntry("Graham Scan算法",5);
glutAttachMenu(GLUT_RIGHT_BUTTON);
glutDisplayFunc(displayFunc);
glutReshapeFunc(reshapeFunc);
glutMouseFunc(mouseFunc);
glutMainLoop();
}