opengl:凸包算法

準備工作

判斷點在有向線段的左側

可以通過叉積判斷,如下爲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();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章