CGAL 5.4 - 2D Polygons: User Manual

此处的多边形是由闭合的线段连接起来的。该package由一些针对多边形的常用算法。对于其中一些算法,多边形必须是简单的,也就是说,多边形的边之间除了相邻的边之外不存在相交的情况。

目前支持的算法有:

  • 找到最右边,最左边,最上边,最下边的顶点;
  • 计算带符号面积;
  • 判断多边形是不是简单的simple;
  • 判断多边形是不是凸多边形;
  • 判断多边形的朝向(顶点是顺时针的还是逆时针的);只会考虑简单多边形
  • 判断一个点是不是位于多边形的内部;
  • 此处的所有的算法,都是输入前向迭代器进行计算的。 Polygon_2 用来表示多边形。

    算法详解(补充项)

    关于Cartesian坐标系的kernel内部的计算逻辑大多位于: CGAL-5.4\include\CGAL\Cartesian\function_objects.h

    Polygon_2位于头文件: CGAL-5.4\include\CGAL\Polygon_2.h

    最右边的顶点

    Polygon_2.right_vertex会调用到下面的函数。

    template <typename Traits>
    class Compare_vertices {
        typedef typename Traits::Less_xy_2 Less_xy_2;
        typedef typename Traits::Point_2 Point_2;
        Less_xy_2 less;
    public:
        Compare_vertices(Less_xy_2 less) : less(less) {}
        // `Point_like` derives from `Point_2`
        template <typename Point_like>
        bool operator()(const Point_like& p1, const Point_like& p2) {
            return less(Point_2(p1), Point_2(p2));
    }; // end Compare_vertice
    template <class ForwardIterator, class PolygonTraits>
    ForwardIterator right_vertex_2(ForwardIterator first,
                                        ForwardIterator last,
                                        const PolygonTraits &traits)
        CGAL_polygon_precondition(first != last);
        internal::Polygon_2::Compare_vertices<PolygonTraits>
            less(traits.less_xy_2_object());
        return std::max_element(first, last, less);
    

    即,该函数的重要实现是internal::Polygon_2::Compare_verticesless_xy_2_object返回的是LessXY_2函数对象,这个函数先进行x比较,如果第一个参数的x小于第二个参数,返回true,第二个大于第一个,返回false,如果相等的时候比较y,第一个小于第二个的时候返回true。

    其他最左边,最上边,最下边的顶点处理方式类似。

    计算带符号面积

    假设顶点是v0, v1, v2, ..., vn。那么对应的伪代码如下:

    polygon_area(polygon):
    	if polygon.n_vertices < 3:
    		return 0
    	first = v0
    	second = v1
    	third = v1
    	while ++v1 != last:
    		result += compute_area_2(first, second, third)
    		second = third
    	return result
    

    Compute area的具体实现如下:

      template <typename K>
      class Compute_area_2
        typedef typename K::FT                FT;
        typedef typename K::Iso_rectangle_2   Iso_rectangle_2;
        typedef typename K::Triangle_2        Triangle_2;
        typedef typename K::Point_2           Point_2;
      public:
        typedef FT               result_type;
        result_type
        operator()( const Point_2& p, const Point_2& q, const Point_2& r ) const
          FT v1x = q.x() - p.x();
          FT v1y = q.y() - p.y();
          FT v2x = r.x() - p.x();
          FT v2y = r.y() - p.y();
          // 向量叉乘为平行四边形面积,三角形面积需要除以2
          return determinant(v1x, v1y, v2x, v2y)/2;
        result_type
        operator()( const Iso_rectangle_2& r ) const
        { return (r.xmax()-r.xmin()) * (r.ymax()-r.ymin()); }
        result_type
        operator()( const Triangle_2& t ) const
        { return t.area(); }
    

    判断多边形是不是简单的simple

    代码片段位于CGAL-5.4\include\CGAL\Polygon_2\Polygon_2_simplicity.h : is_simple_polygon

    具体代码实现如下:

    template <class Iterator, class PolygonTraits>
    bool is_simple_polygon(Iterator points_begin, Iterator points_end,
                           const PolygonTraits& polygon_traits)
        typedef Iterator ForwardIterator;
        typedef i_polygon::Vertex_data<ForwardIterator, PolygonTraits> Vertex_data;
        typedef std::set<i_polygon::Vertex_index,
                         i_polygon::Less_segments<Vertex_data> >       Tree;
        // A temporary fix as the sweep in some cases doesn't discover vertices with degree > 2
        // Todo: fix the sweep code
        std::vector<typename PolygonTraits::Point_2> points(points_begin,points_end);
        std::sort(points.begin(), points.end(), polygon_traits.less_xy_2_object());
        typename std::vector<typename PolygonTraits::Point_2>::iterator
                                      succ(points.begin()) , it(succ++);
        for(;succ != points.end(); ++it,++succ){
          if(*it == *succ){
            return false;
        // end of fix
        Vertex_data   vertex_data(points_begin, points_end, polygon_traits);
        Tree tree(&vertex_data);
        vertex_data.init(&tree);
        vertex_data.sweep(&tree);
        return vertex_data.is_simple_result;
    

    这个函数里面分两部分逻辑,第一部分是为了补充sweep的不足,处理顶点的度大于2的情况(即,有3条及以上的边共享顶点的情况)。对应的逻辑的伪代码如下:

    sort points by Less_xy_2
    for pt1 in points[0...n-2]
    	if pt1 equals pt1.nextpt()
    		return false
    

    第二部分的主要逻辑位于sweep函数中。其中用到了如下kernel中的函数:

  • orientation_2(p,q,r): 如果r位于直线pq的左边返回CGAL::LEFT_TURN,在右边返回CGAL::RIGHT_TURN,否则共线,返回CGAL::COLLINEAR
  • sweep函数如下:

    template <class ForwardIterator, class PolygonTraits>
    void Vertex_data<ForwardIterator, PolygonTraits>::
    sweep(Tree *tree)
        if (this->m_size < 3)
                return;
        bool succes = true;
        // 从多边形最左边的顶点开始依次往右进行遍历
        for (Index_t i=0; i< this->m_size; ++i) {
            Vertex_index cur = index_at_rank(Vertex_order(i));
                Vertex_index prev_vt = prev(cur), next_vt = next(cur);
                if (ordered_left_to_right(cur, next_vt)) {
                    if (ordered_left_to_right(cur, prev_vt))
    	                // 最左边的一个顶点必然会走insertion逻辑
    	                // 以逆时针的顺序插入prev_vt和cur到tree中
                        succes = insertion_event(tree, prev_vt, cur, next_vt);
                        succes = replacement_event(tree, prev_vt, cur);
                } else {
                    if (ordered_left_to_right(cur, prev_vt))
                        succes = replacement_event(tree, cur, prev_vt);
                        succes = deletion_event(tree, prev_vt, cur);
                if (!succes)
                    break;
        if (!succes)
                this->is_simple_result = false;
    

    该函数的实现采用的是sweepline方法实现的。TODO:具体实现细节。

    判断多边形是不是凸多边形

    实现函数位于:CGAL-.4\include\CGAL\Polygon_2\Polygon_2_algorithms_impl.h : is_convex_2

    函数的实现原理比较简单。关键代码如下:

    // 此处不考虑共线点和重复点的存在
      bool HasClockwiseTriples = false; // 记录是否存在连续的三个点是顺时针的
      bool HasCounterClockwiseTriples = false; // 记录是否存在连续的三个点是逆时针的
      bool Order = less_xy_2(*previous, *current); // 前一个点和当前点的顺序是从左往右,还是从右往左
      int NumOrderChanges = 0;
      switch_orient:
        // 判断连续三个点的顺序
        switch (orientation(*previous, *current, *next)) {
          case CLOCKWISE:
            HasClockwiseTriples = true;
            break;
          case COUNTERCLOCKWISE:
            HasCounterClockwiseTriples = true;
            break;
          case ZERO:
            if(equal(*current, *next)) {
              if(next == first) {
                first = current;
              ++next;
              if (next == last)
                next = first;
              goto switch_orient;
            break;
        bool NewOrder = less_xy_2(*current, *next);
        if (Order != NewOrder) NumOrderChanges++;
    	// 从左往右变化为从右往左,或从右往左变化为从左往右,最多出现两次
        if (NumOrderChanges > 2) {
    #ifdef CGAL_POLYGON_DEBUG
    std::cout << "too many order changes: not convex!" << std::endl;
    #endif
          return false;
    	// 如果同时出现逆时针和顺时针场景,则返回false
        if (HasClockwiseTriples && HasCounterClockwiseTriples) {
    #ifdef CGAL_POLYGON_DEBUG
    std::cout << "polygon not locally convex!" << std::endl;
    #endif
          return false;
        previous = current;
        current = next;
        ++next;
        if (next == last) next = first;
        Order = NewOrder;
      while (previous != first);
      return true;
    

    判断多边形的朝向

    实现见:CGAL-5.4\include\CGAL\Polygon_2\Polygon_2_algorithms_impl.h : orientation_2

    找到最左边的顶点,然后判断该点前一个点,和后一个点的朝向,如果是逆时针,那么多边形朝向就是逆时针,如果是顺时针,多边形朝向就是顺时针。

    判断一个点是不是位于多边形的内部

    实现见:CGAL-5.4\include\CGAL\Polygon_2\Polygon_2_algorithms_impl.h : bounded_side_2

    具体思路是遍历多边形的线段(current,next),并判断射线{(t, point.y()) | t >= point.x()}是不是与线段相交。如果是奇数次相交,那么位于多边形内部。如果是偶数次相交位于多边形外部。具体实现参见源代码。

    作者: grassofsky

    出处: http://www.cnblogs.com/grass-and-moon

    本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(grass-of-sky@163.com)咨询.