如何在C ++中正确三角剖分多边形

8
我正在进行物体三角测量(最终,我想实现Delaunay三角测量,但在合法化边缘之前,三角测量无法正常工作,因此我想先专注于简单的三角测量)。我在下面包含了相关代码。我正在实现类似于Mark de Berg等人在“计算几何:算法与应用第三版”中描述的三角测量方法。下面是伪代码(如果需要,我将删除它): Delaunay Pseudocode 注意:我通过创建一个边框三角形来修改了伪代码,而不是使用“P中字典序最高的点”,因为我不太确定如何定义p-1和p-2,因为教科书上说要“符号化”定义它们,而不是定义确切的单位(公平地说,我可能误解了它的意思)。此外,合法化还不是我的代码的一部分(尚未),因为这对于Delaunay三角测量是必要的,但我想确保简单的三角测量按预期工作。

The issue is that I get some triangulations such as these triangulations where the blue lines are from the original polygon.

Some of these lines don't get added because they are part of the triangles of points p0, p1, and p2 which I don't add in the findSmallest method. Yet, if I add those triangles as well, I get something like this:like this (Note p0, p1, and p2 are beyond the scope of the picture). Some of the lines from the original polygon (this time in green) still aren't added to the triangulation. I'm not sure where I'm going wrong.

I hope the code is clear but I'm going to explain some of the methods/structs just in case:

TriPoint

is a child of the Point struct.

p0, p1, p2

are three points form a bounding triangle around the polygon. I got the idea from this post.

contains(Point p) 

returns true if a point is within the triangle or on one of the edges.

findCommonTriangle(TriPoint *a, TriPoint *b, Triangle *t) 

returns the triangle incident to t along edge ab. (I'm not using Edges to calculate the triangulation so I decided to get the incident triangle this way).

isOnTriangle(Point s)

is called on a Triangle abc, and returns 1 if the point is on edge ab, 2 if the pointis on edge bc, 3 if the point is on edge cd. If it is within the triangle, it returns 0.

The code for the triangulation itself is located below:

    #include <GL\glew.h>
#include <GL\freeglut.h>
#include <iostream>

#include <array>
#include <vector>
#include "predicates.h"

struct Point {
    float x, y;
    Point() { }
    Point(float a, float b) {
        x = a;
        y = b;
    }
};

struct Triangle;
struct Triangulation;
std::vector<Triangulation *> triangulations;

struct TriPoint : Point {
    std::vector<Triangle *> triangles;
    TriPoint() { };
    int index;
    TriPoint(Point a) {
        x = a.x;
        y = a.y;
    }
    TriPoint(float x, float y) : Point(x, y) {};
    void removeTriangle(Triangle *t) {
        for (size_t i = 0; i < triangles.size(); i++) {
            if (triangles[i] == t) {
                triangles.erase(triangles.begin() + i);
            }
        }
    }
    void addTriangle(Triangle *t) {
        triangles.push_back(t);
    }
};

double pointInLine(Point *a, Point *b, Point *p) {
    REAL *A, *B, *P;
    A = new REAL[2];
    B = new REAL[2];
    P = new REAL[2];
    A[0] = a->x;
    A[1] = a->y;
    B[0] = b->x;
    B[1] = b->y;
    P[0] = p->x;
    P[1] = p->y;
    double orient = orient2d(A, B, P);
    delete(A);
    delete(B);
    delete(P);
    return orient;
}

struct Triangle {
    TriPoint *a, *b, *c;
    std::vector<Triangle *> children;
    Triangle() { };
    Triangle(TriPoint *x, TriPoint *y, TriPoint *z) {
        a = x;
        b = y;
        c = z;
        orientTri();
        x->addTriangle(this);
        y->addTriangle(this);
        z->addTriangle(this);
    }
    bool hasChildren() {
        return children.size() != 0;
    }
    void draw() {
        glBegin(GL_LINE_STRIP);
        glVertex2f(a->x, a->y);
        glVertex2f(b->x, b->y);
        glVertex2f(c->x, c->y);
        glVertex2f(a->x, a->y);
        glEnd();
    }
    bool contains(Point s) {
        float as_x = s.x - a->x;
        float as_y = s.y - a->y;

        bool s_ab = (b->x - a->x)*as_y - (b->y - a->y)*as_x > 0;
        if ((c->x - a->x)*as_y - (c->y - a->y)*as_x > 0 == s_ab) return false;
        if ((c->x - b->x)*(s.y - b->y) - (c->y - b->y)*(s.x - b->x) > 0 != s_ab) return false;
        return true;
    }
    int isOnTriangle(Point p) {
        //Return -1 if outside
        //Returns 1 if on AB
        //Returns 2 if on BC
        //Returns 3 if on CA
        //Returns 4 if on B
        //Returns 5 if on C
        //Returns 6 if on A
        double res1 = pointInLine(b, a, &p);
        double res2 = pointInLine(c, b, &p);
        double res3 = pointInLine(a, c, &p);

        /*If triangles are counter-clockwise oriented then a point is inside
        the triangle if the three 'res' are < 0, at left of each oriented edge
        */
        if (res1 > 0 || res2 > 0 || res3 > 0)
            return -1; //outside
        if (res1 < 0) {
            if (res2 < 0) {
                if (res3 < 0) {
                    return 0; //inside
                } else { //res3 == 0
                    return 3; //on edge3
                }
            } else { //res2 == 0
                if (res3 == 0) {
                    return 5; //is point shared by edge2 and edge3
                }
                return 2; //on edge2
            }
        } else { //res1 == 0
            if (res2 == 0) {
                return 4; //is point shared by edge1 and edge2
            } else if (res3 == 0) {
                return 6; //is point shared by edge1 and 3
            }
            return 1; //on edge 1
        }
    }

    TriPoint *getThirdPoint(TriPoint *x, TriPoint *y) {
        if (a != x && a != y)
            return a;
        if (b != x && b != y)
            return b;
        return c;
    }
    bool hasPoint(TriPoint *p) {
        return a == p || b == p || c == p;
    }
    void orientTri() {
        REAL *A, *B, *C;
        A = new REAL[2];
        B = new REAL[2];
        C = new REAL[2];
        A[0] = a->x;
        A[1] = a->y;
        B[0] = b->x;
        B[1] = b->y;
        C[0] = c->x;
        C[1] = c->y;
        double orientation = orient2d(A, B, C);
        if (orientation < 0) {
            TriPoint *temp = a;
            a = b;
            b = temp;
        }
        delete(A);
        delete(B);
        delete(C);
    }
};

struct Poly {
    std::vector<Point> points;
    bool selected = false;
};


Triangle *findCommonTriangle(TriPoint *a, TriPoint *b, Triangle *t) {
    //Returns a triangle shared by a and b incident to t
    for (Triangle *aTri : a->triangles) {
        for (Triangle *bTri : b->triangles) {
            if (aTri == bTri && aTri != t) {
                return aTri;
            }
        }
    }
    return NULL;
}

struct Triangulation {
    std::vector<Point> points;
    std::vector<Triangle *> triangles;
    float xMin = 9999;
    float xMax = 0;
    float yMin;
    float yMax;
    Triangulation() { };
    Triangulation(Poly p) {
        points = p.points;
        sort();
        triangulate();
    }
    void draw() {
        for (Triangle *t : triangles) {
            t->draw();
        }
    }
    void sort() {
        //Sort by y-value in ascending order.
        //If y-values are equal, sort by x in ascending order.
        for (size_t i = 0; i < points.size() - 1; i++) {
            if (points[i].x < xMin) {
                xMin = points[i].x;
            }
            if (points[i].x > xMax) {
                xMax = points[i].x;
            }
            int index = i;
            for (size_t j = i; j < points.size(); j++) {
                if (points[index].y > points[j].y) {
                    index = j;
                } else if (points[index].y == points[j].y) {
                    if (points[index].x > points[j].x) {
                        index = j;
                    }
                }
            }
            std::swap(points[i], points[index]);
        }
        yMin = points[0].y;
        yMax = points[points.size() - 1].y;
        std::random_shuffle(points.begin(), points.end());
    }
    void triangulate() {
        Triangle *root;
        float dx = xMax - xMin;
        float dy = yMax - yMin;
        float deltaMax = std::max(dx, dy);
        float midx = (xMin + xMax) / 2.f;
        float midy = (yMin + yMax) / 2.f;

        TriPoint *p0;
        TriPoint *p1;
        TriPoint *p2;
        p0 = new TriPoint(midx - 2 * deltaMax, midy - deltaMax);
        p1 = new TriPoint(midx, midy + 2 * deltaMax);
        p2 = new TriPoint(midx + 2 * deltaMax, midy - deltaMax);
        p0->index = 0;
        p1->index = -1;
        p2->index = -2;

        root = new Triangle(p0, p1, p2);
        for (size_t i = 0; i < points.size(); i++) {
            TriPoint *p = new TriPoint(points[i]);
            p->index = i + 1;
            Triangle *temp = root;
            double in;
            while (temp->hasChildren()) {
                for (size_t j = 0; j < temp->children.size(); j++) {
                    in = temp->children[j]->isOnTriangle(points[i]);
                    if (in >= 0) {
                        temp = temp->children[j];
                        break;
                    }
                }
            }

            in = temp->isOnTriangle(points[i]);
            if (in > 0 ) { //Boundary
                if (in == 1) { //AB
                    Triangle *other = findCommonTriangle(temp->a, temp->b, temp);
                    TriPoint *l = other->getThirdPoint(temp->a, temp->b);
                    l->removeTriangle(other);
                    temp->a->removeTriangle(other);
                    temp->b->removeTriangle(other);
                    temp->a->removeTriangle(temp);
                    temp->b->removeTriangle(temp);
                    temp->c->removeTriangle(temp);
                    Triangle *n1 = new Triangle(temp->a, p, temp->c);
                    Triangle *n2 = new Triangle(temp->b, temp->c, p);
                    Triangle *n3 = new Triangle(temp->a, l, p);
                    Triangle *n4 = new Triangle(temp->b, p, l);

                    temp->children.push_back(n1);
                    temp->children.push_back(n2);
                    other->children.push_back(n3);
                    other->children.push_back(n4);
                } else if (in == 2) { //BC
                    Triangle *other = findCommonTriangle(temp->b, temp->c, temp);
                    TriPoint *l = other->getThirdPoint(temp->b, temp->c);
                    l->removeTriangle(other);
                    temp->b->removeTriangle(other);
                    temp->c->removeTriangle(other);
                    temp->a->removeTriangle(temp);
                    temp->b->removeTriangle(temp);
                    temp->c->removeTriangle(temp);
                    Triangle *n1 = new Triangle(temp->a, p, temp->c);
                    Triangle *n2 = new Triangle(temp->b, temp->a, p);
                    Triangle *n3 = new Triangle(temp->c, p, l);
                    Triangle *n4 = new Triangle(temp->b, l, p);

                    temp->children.push_back(n1);
                    temp->children.push_back(n2);
                    other->children.push_back(n3);
                    other->children.push_back(n4);
                } else if (in == 3) { //CA
                    Triangle *other = findCommonTriangle(temp->a, temp->c, temp);
                    TriPoint *l = other->getThirdPoint(temp->a, temp->c);
                    l->removeTriangle(other);
                    temp->a->removeTriangle(other);
                    temp->c->removeTriangle(other);
                    temp->a->removeTriangle(temp);
                    temp->b->removeTriangle(temp);
                    temp->c->removeTriangle(temp);

                    Triangle *n1 = new Triangle(temp->b, temp->c, p);
                    Triangle *n2 = new Triangle(temp->a, temp->b, p);
                    Triangle *n3 = new Triangle(temp->c, l, p);
                    Triangle *n4 = new Triangle(temp->a, p, l);

                    temp->children.push_back(n1);
                    temp->children.push_back(n2);
                    other->children.push_back(n3);
                    other->children.push_back(n4);
                }
            } else { //Point is inside of triangle
                Triangle *t1 = new Triangle(temp->a, temp->b, p);
                Triangle *t2 = new Triangle(temp->b, temp->c, p);
                Triangle *t3 = new Triangle(temp->c, temp->a, p);

                temp->a->removeTriangle(temp);
                temp->b->removeTriangle(temp);
                temp->c->removeTriangle(temp);

                temp->children.push_back(t1);
                temp->children.push_back(t2);
                temp->children.push_back(t3);
            }
        } //Triangulation done
        findSmallest(root, p0, p1, p2);
        triangulations.push_back(this);
    }

    void findSmallest(Triangle *root, TriPoint *p0, TriPoint *p1, TriPoint *p2) {
        bool include = true; //Controls drawing triangles with p0, p1, and p2
        if (root->hasChildren()) {
            for (Triangle *t : root->children) {
                findSmallest(t, p0, p1, p2);
            }
        } else {
            int i0 = root->hasPoint(p0);
            int i1 = root->hasPoint(p1);
            int i2 = root->hasPoint(p2);
            if ((!i0 && !i1 && !i2) || include) {
                triangles.push_back(root);
            }
        }
    }
};

Poly polygon;

void changeViewPort(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT), -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.375, 0.375, 0.0);
}

void render() {
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(2.5);
    changeViewPort(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));

    glColor3f(0, 0, 1); //Blue
    glBegin(GL_LINE_STRIP);
    for (size_t j = 0; j < polygon.points.size(); j++) {
        std::vector<Point> ps = polygon.points;
        Point p1 = ps[j];
        glVertex2i(p1.x, p1.y);
    }
    glVertex2i(polygon.points[0].x, polygon.points[0].y);
    glEnd();

    glColor3f(1, 0, 1);
    for (Triangulation *t : triangulations) {
        t->draw();
    }

    glutSwapBuffers();
}

int main(int argc, char* argv[]) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Stack Overflow Question");
    glutReshapeFunc(changeViewPort);
    glutDisplayFunc(render);

    exactinit();

    polygon.points.push_back(*new Point(300.0f, 300.0f));
    polygon.points.push_back(*new Point(300.0f, 400.0f));
    polygon.points.push_back(*new Point(400.0f, 400.0f));
    polygon.points.push_back(*new Point(400.0f, 300.0f));
    Triangulation t = *(new Triangulation(polygon));

    glutMainLoop();
    return 0;
}

Note: predicates.cpp and predicates.h were created using code from here.


@Ripi2,这个问题现在应该已经解决了。谢谢。 - Toj19
1
你正在实现什么样的算法?是 Ear Clipping 还是更复杂的算法? - doron
1
@Toj19 你正在尝试实现什么算法?为什么要在排序后对点进行洗牌?三角化方法中那个硬编码值(20)是什么意思? - Sergio Monteleone
@SergioMonteleone 我正在尝试按照我提到的教科书中定义的算法来实现。我考虑是否应该包含教科书中的伪代码,但由于版权问题或其他原因,我不确定是否应该这样做。至于洗牌,它建议在添加点之前创建点的随机排列。我尝试了有和没有洗牌两种方法,从我所能看到的结果来看,它们很相似,所以我可能会删除洗牌,除非我看到显着的差异。20是来自另一篇帖子,关于如何在整个多边形周围创建一个三角形。 - Toj19
关于“符号化的p0,p1,p2”,Shewchuk使用了一个“幽灵顶点”。请参见Lecture Notes on Delaunay Mesh Generation中的图3.6。与使用大边界三角形相比,这个幽灵顶点需要特殊处理,但可以节省一些内存并避免事先知道边界框。 - Ripi2
显示剩余4条评论
2个回答

4

你的代码相当不够优化,但现在并不重要(毕竟你正在学习,对吧?)。我将专注于三角测量问题。

EDITED: You initialize yMin and yMax members of Triangulation at sort() and use them later for the "big enclosing" triangle. If you decide not to use "sort()" you'll use initialized values. Put some defaults on it.

Sorting the points is not needed for building the triangulation. You use it just to find the BB, too much effort, and at the end shuffle them, again too much effort.

The main issue (in your first post, before you edited it) I see is the way of finding if a point is inside a triangle, on its boundary, or outside of it.
Triangle::isOnTriangle() is horrible. You calculate several crossproducts and return '0' (inside triangle) is none of them equals '0'.
You may argue that you know in advance that the point is not outside because you tested it before by Triangle::contains(), but this function is also horrible, not that much though.

The best (or at least easiest and most used) way to find the relative position of a point to a line is

res = (y2 - y1)*(px - x1) - (x2 - x1)*(py - y1)

res is a positive value if {px,py} is at right of {x1,y1} to {x2,y2} line. Negative if at left and zero if it's on the line.
Two important things here:

  • a) Swapping the direction (i.e. the line is {x2,y2} to {x1,y1}) changes the sign of res.
  • b) Telling if res is really zero is not easy due to numerical issues, as with any other float-precision representation.

For a) you must be sure that all of triangles have the same orientation (or the former expression will be used wrongly). You can take extra-care on the order of points you pass to the triangle ctor. Or you can add a "orientTri" function that sets them. Currently your bounding triangle is clockwise-order. The most common order (also used in OpenGL) is counter-clockwise; but you can choose the one you like, just be aware of it.

For b) the comparison of a float with '0' is not a good idea. In some scenaries you can use if std::abs(value) < someEpsilon. But specially with triangulations that's not enough. Classroom Examples of Robustness Problems in Geometric Computations explains very well why your calculations must be "robust". Shewchuk Robust Predicates is a very good solution.

Once you have those two subjects solved the problem of "point in triangle" can be handled as this:

double pointInLine(line *L, point *p)
{
    //returns positive, negative or exactly zero value
    return robustCalculus(L, p);
}

int pointInTriangle(triangle *t, point *p)
{
    double res1 = pointInLine(t->edge1, p);
    double res2 = pointInLine(t->edge2, p);
    double res3 = pointInLine(t->edge3, p);

    /*If triangles are counter-clockwise oriented then a point is inside
      the triangle if the three 'res' are < 0, at left of each oriented edge
    */

    if ( res1 > 0 || res2 > 0 || res3 > 0 )
        return -1; //outside
    if ( res1 < 0 )
        if ( res2 < 0 )
            if ( res3 < 0 )
                 return 0; //inside
            else //res3 == 0
                 return 3; //on edge3
        else //res2 == 0
        {    if ( res3 == 0 )
                 return 5; //is point shared by edge2 and edge3
             return 2; //on edge2
        }
    else
       ... test for other edges or points
}

For the rest of the process of triangulation some advices:

Because you want a Delaunay triangulation, every time you add a new point you must check the "inCircle" condition (no other triangle whose circumcircle contains this new point). It can be done as shown in the book or in the links I posted. Again, you need robust predicates.

Shuffling the order of insertion of the points may improve performance for locating the triangle where the new point lies. This is may be true depending on the method used for the locating part. You use a hierarchy of triangles, so if the data is sorted or not does not affect.
By the way, maintaining the hierarchy structure for each triangle added/removed/changed is expensive in CPU and RAM. Perhaps you may find another ways later, when you get experience with meshing.

With no hierarchy, the "walk to point" method (google for it) seems faster with a randomized input. But using a cache (the last triangle built) is far more efficent.

Good luck with meshing. It's hard to begin with and to debug, the devil lives in the details.


我想问一下,为什么在排序方法中初始化yMin或yMax可能会导致失败?实际上,在排序后调用的三角剖分方法中才使用它们。而且我不确定排序是否必要,所以除非我看到显着的改进(我还没有看到),否则我可能会将其删除。接下来,我将调整我的方法以确定一个点是否在三角形内,谢谢! - Toj19
你关于yMin/yMax是正确的。我已经编辑过我的回答。 - Ripi2

0
除了Ripi2的回答中已经指出的问题外,我想提出以下建议:
1. random_shuffle:
我看到你没有初始化随机生成器(在主函数中调用srand)。这意味着你将始终使用相同的伪随机序列,因此多次执行程序将导致完全相同的结果。我测试了你的代码,洗牌确实对代码产生了影响。正确初始化随机生成器后,你可以看到三角剖分每次都会改变,产生不同的三角形。
2. 比较:
在你的代码中,你通过比较指针来比较点和三角形。这意味着,例如,只有当两个点在内存中完全相同的结构时,它们才相等。具有相同坐标的两个点结构将被视为不同的点。我不确定这是否是你想要的结果,所以建议你考虑一下。
3. 多边形周围的三角形:

除了硬编码的值(20)之外,我不明白为什么这段代码应该会产生一个有效的三角剖分。您在多边形上创建了几个三角形,但是它们全部共享三角形外面的那3个固定点之一。

这里是一张图片,将硬编码参数减少到2,以适应所有三角形的视口:

triangulation1

相同的代码,不同的点顺序(在使用time(0)初始化srand之后):

triangulation2

我无法访问算法的伪代码,但我建议你编辑回答并简要描述它,以便更清晰地阐述。

祝你实现好运 :)


我添加了伪代码(如果需要,我可以删除它)。我更担心的是确保多边形正确地三角化并包含所有边缘。我计划稍后处理外部点。例如,如果给定的叶节点有2个点,分别为p0、p1或p2,则我可以确定我不需要将其包含在内。如果三角形中只有一个点是这三个点之一,则我必须更加小心,以确保相关的边缘被包括在内。如果我能让它正常工作,我就会满意,但这取决于正确的三角剖分开始。 - Toj19
关于复制点,这是个坏主意,因为你会得到长度为0的边和三角形连接混乱的一团。如果你发现有重复的点,就跳过它。 - Ripi2
@Toj19:具有p0、p1、p2作为一个或两个点的三角形与其余三角形完全相同处理。没有缩短器。在显示阶段只需跳过它们即可。 - Ripi2
@Ripi2 不显示这些三角形依赖于三角剖分的正确性。我发现即使在使用Shewchuk的orient2d()之后,我的三角剖分结果基本相同。我正在更新我的原始帖子中的最小和完整示例,加入新的代码。我认为我的错误可能部分是在orientTri()中定向三角形时出现的。此外,我在(b, a, p)上调用orient2d()来确定p相对于线段AB的位置(其他两个线段类似),我不确定这是否正确,但如果是的话,res1、res2和res3都应该是正数。 - Toj19
@Toj19。我告诉过你,网格处理一开始就很难。现在你已经纠正了代码的一些部分,我的最佳建议是使用调试器逐步查看发生了什么。例如,错误的赋值、错误的符号、混乱的层次结构、某些顶点丢失等等。 - Ripi2
@Ripi2 好的。感谢你的帮助! - Toj19

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接