分选点:凹多边形

时间:2014-10-02 14:22:17

标签: c++ geometry

我有一套点,我试图按照ccw顺序排序或从他们的角度按顺序排序。我希望点的排序方式可以形成一个多边形,在其区域或交叉点没有分割。这很难,因为在大多数情况下,它将是一个凹多边形。

point centroid;
int main( int argc, char** argv )
{
  // I read a set of points into a struct point array: points[n]

  // Find centroid
  double sx = 0; double sy = 0;
  for (int i = 0; i < n; i++)
  {
    sx += points[i].x;
    sy += points[i].y;
  }
  centroid.x = sx/n;
  centroid.y = sy/n;

  // sort points using in polar order using centroid as reference
  std::qsort(&points, n, sizeof(point), polarOrder);
}

// -1 ccw, 1 cw, 0 collinear
int orientation(point a, point b, point c)
{
   double area2 = (b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x);
   if      (area2 < 0) return -1;
   else if (area2 > 0) return +1;
   else                return  0;
}

// compare other points relative to polar angle they make with this point
// (where the polar angle is between 0 and 2pi)
int polarOrder(const void *vp1, const void *vp2)
{
  point *p1 = (point *)vp1;
  point *p2 = (point *)vp2;

  // translation
  double dx1 = p1->x - centroid.x;
  double dy1 = p1->y - centroid.y;

  double dx2 = p2->x - centroid.x;
  double dy2 = p2->y - centroid.y;

  if      (dy1 >= 0 && dy2 < 0) { return -1; }  // p1 above and p2 below
  else if (dy2 >= 0 && dy1 < 0) { return  1; }  // p1 below and p2 above
  else if (dy1 == 0 && dy2 ==0) {               // 3-collinear and horizontal
      if      (dx1 >= 0 && dx2 < 0) { return -1; }
      else if (dx2 >= 0 && dx1 < 0) { return  1; }
      else                          { return  0; }
  }
  else return -orientation(centroid,*p1,*p2);   // both above or below
}

看起来这些点被准确地分类(粉红色),直到它们“洞穴”#34;在这种情况下,算法跳过这些点然后继续..任何人都可以指向正确的方向对点进行排序,以便它们形成我正在寻找的多边形? 原点图 - 蓝色,粉红点 - 排序 enter image description here

点列表:http://pastebin.com/N0Wdn2sm(您可以忽略第3个组件,因为所有这些点都位于同一平面上。)

3 个答案:

答案 0 :(得分:1)

首先,我在centroid中将main声明为局部变量。但是在polarOrder内,您还访问了一些centroid变量。

根据您发布的代码判断,第二个centroid是一个文件范围变量,您从未将其初始化为任何特定值。因此,你的比较函数会产生毫无意义的结果。

代码中的第二个奇怪细节是,如果两个点都高于或低于此值,则会return -orientation(centroid,*p1,*p2)。由于orientation为CCW返回-1,为CW提交+1,因此它应该只是return orientation(centroid,*p1,*p2)。为什么你觉得有必要否定orientation的结果?

答案 1 :(得分:1)

原始点不会出现凸多边形,因此只需围绕固定质心按角度排序它们就不一定会产生干净的多边形。这是一个非常重要的问题,您可能需要研究Delaunay triangulation和/或gift wrapping算法,尽管两者都必须进行修改,因为多边形是凹的。答案here是修改凹面多边形礼品包装算法的一个有趣例子。还有一个名为PCL的C ++库可以满足您的需求。

但是......如果你真的想进行极地排序,你的排序功能似乎比必要的更复杂。我会先使用atan2进行排序,然后在必要时获得所需的结果后再对其进行优化。以下是使用lambda函数的示例:

#include <algorithm>
#include <math.h>
#include <vector>

int main()
{
    struct point
    {
        double x;
        double y;
    };

    std::vector< point > points;
    point centroid;

    // fill in your data...

    auto sort_predicate = [&centroid] (const point& a, const point& b) -> bool {
        return atan2 (a.x - centroid.x, a.y - centroid.y) <
                atan2 (b.x - centroid.x, b.y - centroid.y);
    };

    std::sort (points.begin(), points.end(), sort_predicate);
}

答案 2 :(得分:1)

下面的代码(对不起,C而不是C ++)按照您的意愿正确排序atan2

您的代码的问题可能是它尝试使用被比较的两个向量之间的夹角。这注定要失败。该数组不是圆形的。它有第一个和最后一个元素。关于质心,对阵列进行排序需要总极性顺序:角度范围,使得每个点对应于唯一的角度而不管另一个点。角度是总极性顺序,并将它们作为标量进行比较,提供了排序比较功能。

通过这种方式,您提出的算法可以保证生成星形折线。它可能在不同的半径之间疯狂地振荡(......你的数据会做什么!这就是你所说的&#34;陷入其中?如果是这样,它是你的算法和数据的一个特征,而不是一个实现错误),对应于完全相同角度的点可能会产生重合的边(直接位于彼此的顶部),但边缘不会交叉。

我相信你选择质心作为极坐标原点足以保证连接上面生成的折线的末端会产生一个完整的star-shaped polygon,但是,我没有证据。

使用Excel绘制结果

注意你可以从质心所在的近径向边缘猜测!这是&#34;星形&#34;我在上面提到过。

enter image description here

为了说明这是一个真正的星形多边形,这里是放大到令人困惑的左下角:

enter image description here

如果你想要一个更好的多边形&#34;从某种意义上说,你需要一个发烧友(可能是很多发烧友)算法,例如基于Delaunay三角测量的其他人已经提到过。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

struct point {
  double x, y;
};

void print(FILE *f, struct point *p) {
  fprintf(f, "%f,%f\n", p->x, p->y);
}

// Return polar angle of p with respect to origin o
double to_angle(const struct point *p, const struct point *o) {
  return atan2(p->y - o->y, p->x - o->x);
}

void find_centroid(struct point *c, struct point *pts, int n_pts) {
  double x = 0, y = 0;
  for (int i = 0; i < n_pts; i++) {
    x += pts[i].x;
    y += pts[i].y;
  }
  c->x = x / n_pts;
  c->y = y / n_pts;
}

static struct point centroid[1];

int by_polar_angle(const void *va, const void *vb) {
  double theta_a = to_angle(va, centroid);
  double theta_b = to_angle(vb, centroid);
  return theta_a < theta_b ? -1 : theta_a > theta_b ? 1 : 0;
}

void sort_by_polar_angle(struct point *pts, int n_pts) {
  find_centroid(centroid, pts, n_pts);
  qsort(pts, n_pts, sizeof pts[0], by_polar_angle);
}

int main(void) {
  FILE *f = fopen("data.txt", "r");
  if (!f) return 1;
  struct point pts[10000];
  int n_pts, n_read;
  for (n_pts = 0; 
       (n_read = fscanf(f, "%lf%lf%*f", &pts[n_pts].x, &pts[n_pts].y)) != EOF;
       ++n_pts)
    if (n_read != 2) return 2;
  fclose(f);
  sort_by_polar_angle(pts, n_pts);
  for (int i = 0; i < n_pts; i++)
    print(stdout, pts + i);
  return 0;
}