可以通过叉积判断,如下为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),算法描述如下:
两个相邻的极边之间有一个共同的极点,所以一条极边的尾端也是另一条极边的顶端。如果已知一个极点,则可以寻找以该极点作为顶端的极边的尾端极点。方法是任取一个点作为候选点,如果下一个点在已知点与候选点组成的有向线段的右端,则把这个点作为候选点,这样不断的更新。因为最下的点肯定是一个极点,所以可把最下点作为初始点。算法的复杂度为O(N*W)(W是凸包的边数),算法描述如下:
算法需要借助一次排序,和两个栈:
下图描述了整个流程:
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();
}