CGAL代码阅读跳坑指南

CGAL代码阅读跳坑指南

整体框架介绍

CGAL代码阅读跳坑指南_第1张图片

CGAL中的算法和数据结构由它们使用的对象类型和操作参数化。它们可以处理满足特定语法和语义需求的任何具体模板参数。为了避免长参数列表,参数类型被收集到一个单独的类中,称为CGAL中的traits类Concept是由一组需求定义的类型的抽象。概念是由一组需求定义的类型的抽象。如果任何具体类型满足与概念相对应的一组需求,则称为这个概念对应的Model。使用这个术语,我们可以说CGAL算法或数据结构带有一个traits概念,并且可以与这个概念的任何具体traits模型一起使用。

CGAL定义了geometry kernel的概念。理想情况下,这个概念的任何模型都可以与任何CGAL算法一起使用。当然,只有当一个算法或数据结构对其traits类的需求被核心概念所包含时,也就是说,如果一个算法或数据结构没有核心概念定义中未涵盖的特殊需求,这种情况才成立。目前,CGAL提供了一个基本geometry kernel的概念,它定义了各种几何对象,如点、线段、直线、圆和对它们的操作,以及另外两个概念,circular kernelspherical kernel。最后两个内核的目标是在平面上的圆和圆弧上指定一大组功能(circular kernel),并为生活在三维球体上的圆、圆弧(spherical kernel)指定类似的功能。

CGAL目前为CGAL 2D和3D内核概念提供了几个模型,为2D圆形和3D球形内核概念提供了一个模型。。它们再次被参数化,并且在几何对象的表示和所涉及类型的可交换性方面有所不同。在前四种情况下,内核由数字类型参数化,数字类型用于存储坐标,决定内核原语的基本算法。

在最后两种情况下,圆核和球核也由Algebraic Kernel参数化,这与代数基础一起,是CGAL中第三个明显的高阶泛化。CGAL中的代数基础是表示代数结构的一组概念,由传统代数中著名的对应概念驱动。代数基础决定每个代数结构的运算、它们的属性(例如,它们是精确的还是近似的)以及它们之间的互操作性。Algebraic Kernel负责为CGAL算法中使用的geometry kernel或特征类所需的代数操作提供抽象。目标是能够构造、比较和执行多项式方程的实根运算。根据用于确定根的多项式的变量数量,有不同的概念(目前有单变量和双变量代数核的概念),以及针对库的特定几何高层(如圆形和球形核)的专门概念。这些概念每个概念至少有一个模型。

CGAL中还有更多的互补层。最基本的层是配置层。此层负责根据安装期间运行的测试的结果设置配置标志。支持库层在支持库手册中有文档记录,其中包含处理CGAL中的可视化、数字类型、流和STL扩展等内容的包。

以上内容是对官方文档中介绍的翻译,见:https://doc.cgal.org/latest/Manual/devman_intro.html#secoverall_design

个人理解

Traits、Concept:都是用来定义一组满足特定语法和语义需求的概念(参数类型的集合);

Model:是对于这些参数类型的实现,对Kernel进行一定的封装;

Kernel:是最终概念具体实现的地方;

Algorithm:指的是利用概念提供的信息,完成对应的实现;

示例分析

下面以https://doc.cgal.org/latest/Convex_hull_2/classCGAL_1_1Convex__hull__traits__2.html为例验证上面的理解是不是有问题的,下面不考虑算法到底是怎么实现的。

Concepts

https://doc.cgal.org/latest/Convex_hull_2/classConvexHullTraits__2.html

定义中说明了convex hull and extreme point算法被参数化为Traits类,定义了算法需要的对象和判断的方法。其中类型主要包括:Point_2, Equal_2, Less_xy_2, Less_yx_2, Left_turn_2, Less_signed_distance_to_line_2, Less_rotate_ccw_2, Orientation_2;需要的方法有:拷贝构造函数,Equal_2 equal_2_object(), Less_xy_2 less_xy_2_object(), Less_yx_2 less_yx_2_object(), Less_signed_distance_to_line_2 less_signed_distance_to_line_2_object (), Less_rotate_ccw_2 less_rotate_ccw_2_object(), Left_turn_2 left_turn_2_object(), Orientation_2 orientation_2_object()

Model

CGAL::Convex_hull_constructive_traits_2
CGAL::Convex_hull_traits_2
CGAL::Convex_hull_traits_adapter_2
CGAL::Projection_traits_xy_3
CGAL::Projection_traits_yz_3 
CGAL::Projection_traits_xz_3

代码示例(仅使用kernel)

#include 
#include 
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
int main()
{
  CGAL::set_ascii_mode(std::cin);
  CGAL::set_ascii_mode(std::cout);
  std::istream_iterator< Point_2 >  in_start( std::cin );
  std::istream_iterator< Point_2 >  in_end;
  std::ostream_iterator< Point_2 >  out( std::cout, "\n" );
  CGAL::ch_graham_andrew( in_start, in_end, out );
  return 0;
}

ch_graham_andrew提供了两个接口,如下:

// With out traits
template 
inline
OutputIterator
ch_graham_andrew( InputIterator  first,
                  InputIterator  last,
                  OutputIterator result );
// With traits
template 
OutputIterator
ch_graham_andrew( InputIterator  first,
                  InputIterator  last,
                  OutputIterator result,
                  const Traits&  ch_traits );

那么就需要详细看下Kernel的实现,此处直看偏特化为true的情况:

class Epick
  : public Filtered_kernel_adaptor<
               Type_equality_wrapper< Simple_cartesian::Base::Type, Epick >, true >
{};

typedef Epick Exact_predicates_inexact_constructions_kernel;

首先看一下最内层Simple_cartesian::Base::Type对应的是:Cartesian_base_no_ref_count,于是得到

class Epick
  : public Filtered_kernel_adaptor<
               Type_equality_wrapper< Cartesian_base_no_ref_count, Epick >, true >
{};

Epick最后的基类是:Cartesian_base,该基类定义了Point_2, Vector_2等数据结构,如下 :

typedef PointC2                             Point_2;
typedef VectorC2                            Vector_2;
typedef DirectionC2                         Direction_2;
/// ...

PointC2Vector2DirectionC2等是可以找到具体实现的。其他的概念中对应的类型和函数的实现见:

template < typename FT_, typename Kernel_ >
struct Cartesian_base_no_ref_count
  : public Cartesian_base< Kernel_, FT_ >
{
    typedef FT_                                           RT;
    typedef FT_                                           FT;

    // The mechanism that allows to specify reference-counting or not.
    template < typename T >
    struct Handle { typedef T   type; };

    template < typename Kernel2 >
    struct Base { typedef Cartesian_base_no_ref_count  Type; };

    typedef Kernel_ K;
#define CGAL_Kernel_pred(Y,Z) typedef CartesianKernelFunctors::Y Y; \
                              Y Z() const { return Y(); }
#define CGAL_Kernel_cons(Y,Z) CGAL_Kernel_pred(Y,Z)

#include  
};

#include 定义了概念中对应的函数,类型,具体如下:

CGAL_Kernel_pred(Less_signed_distance_to_line_2,
                 less_signed_distance_to_line_2_object)
CGAL_Kernel_pred(Equal_2,
		 equal_2_object)
CGAL_Kernel_pred(Less_xy_2,
		 less_xy_2_object)
CGAL_Kernel_pred(Less_yx_2,
		 less_yx_2_object)
CGAL_Kernel_pred(Less_signed_distance_to_line_2,
                 less_signed_distance_to_line_2_object)
CGAL_Kernel_pred(Less_rotate_ccw_2,
		 less_rotate_ccw_2_object)
CGAL_Kernel_pred(Left_turn_2,
                 left_turn_2_object)
CGAL_Kernel_pred_RT(Orientation_2,
		    orientation_2_object)
// ...

上面的定义都可以通过下面的宏进行展开:

#define CGAL_Kernel_pred(Y,Z) typedef CartesianKernelFunctors::Y Y; \
                              Y Z() const { return Y(); }

CartesianKernelFunctors中定义了各种概念上具体的Functor类型,这些Functor提供了一些辅助的几何关系计算的功能,同时这些类型的实现是由Cartesian_base中对应的类型组装而成的。

template 
inline
OutputIterator
ch_graham_andrew( InputIterator  first,
                  InputIterator  last,
                  OutputIterator result )
{ 
    typedef std::iterator_traits   ITraits;
    typedef typename ITraits::value_type          value_type;  // Kernel::Point_2
    typedef CGAL::Kernel_traits       KTraits;     // 
    typedef typename KTraits::Kernel              Kernel;      // Kernel
    return ch_graham_andrew( first, last, result, Kernel()); 
}

算法内部中利用了kernel中依据概念提供的数据结构和辅助函数。

代码示例(使用Traits)

对应的示例代码如下:

#include 
#include 
#include 
#include 
#include 
#include 

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
typedef CGAL::Convex_hull_traits_adapter_2::type > Convex_hull_traits_2;
int main()
{
  std::vector points = { Point_2(10,0),
                                  Point_2(3,4),
                                  Point_2(0,0),
                                  Point_2(10,10),
                                  Point_2(2,6) };
  
  std::vector indices(points.size()), out;
  std::iota(indices.begin(), indices.end(),0);
  
  CGAL::convex_hull_2(indices.begin(), indices.end(), std::back_inserter(out),
                      Convex_hull_traits_2(CGAL::make_property_map(points)));
  for( std::size_t i : out){
    std::cout << "points[" << i << "] = " << points[i] << std::endl;
  }
  return 0;
}

先来认识下:CGAL::make_property_map

template 
struct Pointer_property_map{
  typedef boost::iterator_property_map< T*,
                              boost::typed_identity_property_map,
                              T,
                              T&> type; ///< mutable `LvaluePropertyMap`
  typedef boost::iterator_property_map< const T*,
                              boost::typed_identity_property_map,
                              T,
                              const T&> const_type; ///< non-mutable `LvaluePropertyMap`
};

template 
inline
typename Pointer_property_map::type
make_property_map(T* pointer)
{
  return typename Pointer_property_map::type(pointer);
}

可见最终会导向:boost::iterator_property_map。简单来讲就是用来将随机访问的迭代器转换成LvaluePropertyMap,该类型提供了operator[],和get()函数来访问对象。进一步了解可以查找:Boost Property Map Library。

接着看一下:Convex_hull_traits_2(CGAL::make_property_map(points))就是构建了一个traits类。这个traits内部直接实现了concepts中给出的需求,如下:

template
class Convex_hull_traits_adapter_2:public Base_traits{
	typedef typename boost::property_traits::key_type Point_2;
    struct Less_xy_2 : public Base_traits::Less_xy_2{
        Less_xy_2(const PointPropertyMap& ppmap,const typename Base_traits::Less_xy_2& base):
        Base_traits::Less_xy_2(base),ppmap_(ppmap){}
        const PointPropertyMap& ppmap_;
        bool operator()(Arg_type p,Arg_type q) const {
            return static_cast(this)->operator()(get(ppmap_,p),get(ppmap_,q));
        }
    };

    struct Equal_2 : public Base_traits::Equal_2{ ... }
    //...
    Equal_2 equal_2_object () const {return Equal_2(ppmap_,static_cast(this)->equal_2_object() );}
    Left_turn_2 left_turn_2_object () const {return Left_turn_2(ppmap_,static_cast(this)->left_turn_2_object() );}
    //...

这里的Base_traits就是对应上面示例中的的Kernel。以Less_xy_2为例,operator()最后调用的是:

static_cast(this)->operator()(get(ppmap_,p),get(ppmap_,q));

这里就有完美的切换到了Kernel的实现中到了。

But,为什么么要这么设计,这么实现呢?这个我就不懂了。。,希望大家一起探索下。。

小结

针对相关代码的阅读,可以先理解concept中对应的数据类型和函数。然后再看对应的算法实现。看算法实现的时候,就不用过多关注kernel中和concept相关的内容了,更多的关注算法实现的过程即可。不然代码跳来跳去很快就会晕晕乎乎了。

你可能感兴趣的:(CGAL代码阅读跳坑指南)