CGAL代碼閱讀跳坑指南
整體框架介紹
CGAL中的算法和數據結構由它們使用的對象類型和操作參數化。它們可以處理滿足特定語法和語義需求的任何具體模板參數。為了避免長參數列表,參數類型被收集到一個單獨的類中,稱為CGAL中的traits類。Concept是由一組需求定義的類型的抽象。概念是由一組需求定義的類型的抽象。如果任何具體類型滿足與概念相對應的一組需求,則稱為這個概念對應的Model。使用這個術語,我們可以說CGAL算法或數據結構帶有一個traits概念,並且可以與這個概念的任何具體traits模型一起使用。
CGAL定義了geometry kernel的概念。理想情況下,這個概念的任何模型都可以與任何CGAL算法一起使用。當然,只有當一個算法或數據結構對其traits類的需求被核心概念所包含時,也就是說,如果一個算法或數據結構沒有核心概念定義中未涵蓋的特殊需求,這種情況才成立。目前,CGAL提供了一個基本geometry kernel的概念,它定義了各種幾何對象,如點、線段、直線、圓和對它們的操作,以及另外兩個概念,circular kernel和spherical 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<R>
CGAL::Convex_hull_traits_adapter_2<R>
CGAL::Projection_traits_xy_3<K>
CGAL::Projection_traits_yz_3 <K>
CGAL::Projection_traits_xz_3<K>
代碼示例(僅使用kernel)
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/ch_graham_andrew.h>
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 <class InputIterator, class OutputIterator>
inline
OutputIterator
ch_graham_andrew( InputIterator first,
InputIterator last,
OutputIterator result );
// With traits
template <class InputIterator, class OutputIterator, class Traits>
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<double>::Base<Epick>::Type, Epick >, true >
{};
typedef Epick Exact_predicates_inexact_constructions_kernel;
首先看一下最內層Simple_cartesian<double>::Base<Epick>::Type
對應的是:Cartesian_base_no_ref_count<double,Epick>
,於是得到
class Epick
: public Filtered_kernel_adaptor<
Type_equality_wrapper< Cartesian_base_no_ref_count<double,Epick>, Epick >, true >
{};
Epick
最后的基類是:Cartesian_base
,該基類定義了Point_2, Vector_2等數據結構,如下 :
typedef PointC2<Kernel> Point_2;
typedef VectorC2<Kernel> Vector_2;
typedef DirectionC2<Kernel> Direction_2;
/// ...
PointC2
,Vector2
,DirectionC2
等是可以找到具體實現的。其他的概念中對應的類型和函數的實現見:
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<FT_, Kernel2> Type; };
typedef Kernel_ K;
#define CGAL_Kernel_pred(Y,Z) typedef CartesianKernelFunctors::Y<K> Y; \
Y Z() const { return Y(); }
#define CGAL_Kernel_cons(Y,Z) CGAL_Kernel_pred(Y,Z)
#include <CGAL/Kernel/interface_macros.h>
};
#include <CGAL/Kernel/interface_macros.h>
定義了概念中對應的函數,類型,具體如下:
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<K> Y; \
Y Z() const { return Y(); }
CartesianKernelFunctors
中定義了各種概念上具體的Functor類型,這些Functor提供了一些輔助的幾何關系計算的功能,同時這些類型的實現是由Cartesian_base
中對應的類型組裝而成的。
template <class InputIterator, class OutputIterator>
inline
OutputIterator
ch_graham_andrew( InputIterator first,
InputIterator last,
OutputIterator result )
{
typedef std::iterator_traits<InputIterator> ITraits;
typedef typename ITraits::value_type value_type; // Kernel::Point_2
typedef CGAL::Kernel_traits<value_type> KTraits; //
typedef typename KTraits::Kernel Kernel; // Kernel
return ch_graham_andrew( first, last, result, Kernel());
}
算法內部中利用了kernel中依據概念提供的數據結構和輔助函數。
代碼示例(使用Traits)
對應的示例代碼如下:
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_2.h>
#include <CGAL/Convex_hull_traits_adapter_2.h>
#include <CGAL/property_map.h>
#include <vector>
#include <numeric>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
typedef CGAL::Convex_hull_traits_adapter_2<K,
CGAL::Pointer_property_map<Point_2>::type > Convex_hull_traits_2;
int main()
{
std::vector<Point_2> points = { Point_2(10,0),
Point_2(3,4),
Point_2(0,0),
Point_2(10,10),
Point_2(2,6) };
std::vector<std::size_t> 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 <class T>
struct Pointer_property_map{
typedef boost::iterator_property_map< T*,
boost::typed_identity_property_map<std::size_t>,
T,
T&> type; ///< mutable `LvaluePropertyMap`
typedef boost::iterator_property_map< const T*,
boost::typed_identity_property_map<std::size_t>,
T,
const T&> const_type; ///< non-mutable `LvaluePropertyMap`
};
template <class T>
inline
typename Pointer_property_map<T>::type
make_property_map(T* pointer)
{
return typename Pointer_property_map<T>::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 Base_traits,class PointPropertyMap>
class Convex_hull_traits_adapter_2:public Base_traits{
typedef typename boost::property_traits<PointPropertyMap>::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<const typename Base_traits::Less_xy_2*>(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<const Gt*>(this)->equal_2_object() );}
Left_turn_2 left_turn_2_object () const {return Left_turn_2(ppmap_,static_cast<const Gt*>(this)->left_turn_2_object() );}
//...
這里的Base_traits
就是對應上面示例中的的Kernel
。以Less_xy_2
為例,operator()
最后調用的是:
static_cast<const typename Base_traits::Less_xy_2*>(this)->operator()(get(ppmap_,p),get(ppmap_,q));
這里就有完美的切換到了Kernel
的實現中到了。
But,為什么么要這么設計,這么實現呢?這個我就不懂了。。,希望大家一起探索下。。
小結
針對相關代碼的閱讀,可以先理解concept中對應的數據類型和函數。然后再看對應的算法實現。看算法實現的時候,就不用過多關注kernel中和concept相關的內容了,更多的關注算法實現的過程即可。不然代碼跳來跳去很快就會暈暈乎乎了。