Hello World 之 CGAL

CGAL有神祕的面紗,讓我不斷想看清其真面目。開始吧!html

 

Three Points and One Segment

第一個例子是建立3個點和一條線段,而且在其上進行一些操做。ios

全部的CGAL頭文件都在CGAL目錄下。全部的CGAL類和函數都在CGAL的命名空間。類以大寫字母開頭,常量全大寫,全局函數名小寫。對象的空間維度由後綴給出。算法

幾何元,如點,在一個kernel中定義。第一個例子中咱們選擇的kernel採用double精度的浮點數做爲笛卡爾空間座標。編程

另外,咱們有predicate(斷言),如位置測試斷言,咱們有construction(構建),如距離和中點的計算,都是construction。一個predicate的結果是一個離散集,一個construction產生一個值,也可能產生一個新的幾何實體。數組

File Kernel_23/points_and_segment.cpp函數

#include <iostream>
#include <CGAL/Simple_cartesian.h>
 
typedef Kernel::Point_2 Point_2;
typedef Kernel::Segment_2 Segment_2;
 
int main()
{
Point_2 p(1,1), q(10,10);
 
std::cout << "p = " << p << std::endl;
std::cout << "q = " << q.x() << " " << q.y() << std::endl;
 
std::cout << "sqdist(p,q) = "
<< CGAL::squared_distance(p,q) << std::endl;
 
Segment_2 s(p,q);
Point_2 m(5, 9);
 
std::cout << "m = " << m << std::endl;
std::cout << "sqdist(Segment_2(p,q), m) = "
<< CGAL::squared_distance(s,m) << std::endl;
 
std::cout << "p, q, and m ";
switch (CGAL::orientation(p,q,m)){
std::cout << "are collinear\n";
break;
std::cout << "make a left turn\n";
break;
std::cout << "make a right turn\n";
break;
}
 
std::cout << " midpoint(p,q) = " << CGAL::midpoint(p,q) << std::endl;
return 0;
}
 
下面採用浮點數進行的幾何運行讓咱們吃驚。

File Kernel_23/surprising.cpp測試

#include <iostream>
#include <CGAL/Simple_cartesian.h>
 
typedef Kernel::Point_2 Point_2;
 
int main()
{
{
Point_2 p(0, 0.3), q(1, 0.6), r(2, 0.9);
std::cout << ( CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
}
{
Point_2 p(0, 1.0/3.0), q(1, 2.0/3.0), r(2, 1);
std::cout << ( CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
}
{
Point_2 p(0,0), q(1, 1), r(2, 2);
std::cout << ( CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
}
return 0;
}

若是隻從代碼上看,咱們會發現前兩種狀況也應當是共線的,但實際的結果是:ui

not collinear
not collinear
collinear
由於分數做爲雙精度數是不可被描述的,共線測試內部的計算是一個3X3行列式(determinant),它能夠獲得近似值,但不能獲得偏差爲0的精確值。因此得出前兩種狀況爲不花線的結論。
其餘的predicate也會有一樣的問題,如 CGAL::orientation(p,q,m)運算可能會因爲舍入偏差,可能得出不一樣實際的結論。
若是你須要使數被全精度解析,你能夠使用精確斷言和精確構建的CGAL kernel。 

File Kernel_23/exact.cpp編碼

#include <iostream>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <sstream>
 
typedef Kernel::Point_2 Point_2;
 
int main()
{
Point_2 p(0, 0.3), q, r(2, 0.9);
{
q = Point_2(1, 0.6);
std::cout << ( CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
}
 
{
std::istringstream input( "0 0.3 1 0.6 2 0.9");
input >> p >> q >> r;
std::cout << ( CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
}
 
{
q = CGAL::midpoint(p,r);
std::cout << ( CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
}
 
return 0;
}

這裏的結果仍然讓咱們吃驚:spa

not collinear
collinear
collinear

第一個結果仍然是錯的,緣由與前面相同,它們仍然是浮點運算。第二個結果不一樣,它由字符串生成(construct),則精確地表明瞭字符串所表示的數。第三個結果經過構建(construct)中點獲得第三個點,構建操做是精確的,因此結果也是正確的。

在不少狀況下,你操做「精確」浮點數據,認爲它們是由應用計算獲得或由傳感器獲得的。它們不是象「0.1」這樣的字符串,也不是象"1.0/10.0"這樣動態( on the fly)生成的,它是一個全精度的浮點數。若是它們只是被傳遞入某個算法而且沒有構建(construct)操做時,你能夠使用支持精確斷言(predicate)和非精確構建(construct)的kernel。這樣的例子包括下一節咱們看到的「凸包」算法。它的輸出是輸入的一個子集,這個算法只進行座標比較和位置測試。

因爲高精度的計算須要消耗比普通計算多的資源,內存、時間等,因此使用時須要考慮。

大部分CGAL包說明它們須要或支持哪一種kernel。

The Convex Hull of a Sequence of Points

 本節全部例子都是凸包算法。輸入一個點序列,輸出全部凸包邊界上的點序列。

下面的例子輸入和輸出的都是一個座標數組。

File Convex_hull_2/array_convex_hull_2.cpp

#include <iostream>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_2.h>
 
typedef K::Point_2 Point_2;
 
int main()
{
Point_2 points[5] = { Point_2(0,0), Point_2(10,0), Point_2(10,10), Point_2(6,5), Point_2(4,1) };
Point_2 result[5];
 
Point_2 *ptr = CGAL::convex_hull_2( points, points+5, result );
std::cout << ptr - result << " points on the convex hull:" << std::endl;
for(int i = 0; i < ptr - result; i++){
std::cout << result[i] << std::endl;
}
return 0;
}
如同上節所說,咱們採用了精確斷言和非精確構建的kernel來生成程序。
下面的例子則採用了標準庫中的vector類來進行輸入和輸出。

File Convex_hull_2/vector_convex_hull_2.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_2.h>
 
#include <vector>
 
typedef K::Point_2 Point_2;
typedef std::vector<Point_2> Points;
 
int main()
{
Points points, result;
points.push_back(Point_2(0,0));
points.push_back(Point_2(10,0));
points.push_back(Point_2(10,10));
points.push_back(Point_2(6,5));
points.push_back(Point_2(4,1));
 
 
CGAL::convex_hull_2( points.begin(), points.end(), std::back_inserter(result) );
std::cout << result.size() << " points on the convex hull" << std::endl;
return 0;
}

About Kernels and Traits Classes

 本節簡介traits的內涵和意義.

在上個例子中,若是咱們閱讀convex_hull_2()的手冊時,會發現它及其餘2D convex_hull_2()算法都有兩個版本。其中一個版本包含了traits參數。

template<class InputIterator , class OutputIterator , class Traits >
InputIterator beyond,
OutputIterator result,
const Traits & ch_traits)

什麼緣由要咱們使用traits呢?泛型編程須要使用抽象元語對算法進行抽象,而在實現中將元語變爲實際的類型和算法。那麼convex hull算法須要哪些元語(primitive)呢?最簡單的"Graham/Andrew Scan"算法過程是:(1)將全部輸入的點進行從左到右排序;(2)從左向右順序加入,逐步造成convex hull。這個過程(ch_graham_andrew())須要的元語包括:

  • Traits::Point_2
  • Traits::Less_xy_2
  • Traits::Left_turn_2
  • Traits::Equal_2

能夠看出,Left_turn_2負責位置測試,Less_xy_2用於點的排序(這些類型須要知足的要求在概論念ConvexHullTraits_2中進行詳細說明)。

從泛型概念的要求出發,這些元語須要抽象造成模板,因此下面是新的算法形式:

template <class InputIterator, class OutputIterator, class Point_2, class Less_xy_2, class Left_turn_2, class Equal_2>
InputIterator beyond,
OutputIterator result);
每一種類型必須對模板中的全部類型進行定義。能夠看出,這個模板參數有一點複雜。
有兩個問題須要咱們回答:(1)哪些類型須要進入模板參數列表?(2)咱們爲何要用這些模板參數?
對第一個問題:ConvexHullTraits_2所要求的任何模型,這些模型由CGAL概念Kernel提供。
對第二個問題:若是咱們未來須要計算投影到yz平面上的的3D點集的convex hull時,咱們設計一個新的traits——Projection_traits_yz_3,這樣前面的例子就不須要進行大的修改。

File Convex_hull_2/convex_hull_yz.cpp

#include <iostream>
#include <iterator>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Projection_traits_yz_3.h>
#include <CGAL/convex_hull_2.h>
 
typedef K::Point_2 Point_2;
 
int main()
{
std::istream_iterator< Point_2 > input_begin( std::cin );
std::istream_iterator< Point_2 > input_end;
std::ostream_iterator< Point_2 > output( std::cout, "\n" );
CGAL::convex_hull_2( input_begin, input_end, output, K() );
return 0;
}
另外一個例子是關於使用已經定義的空間點類型,或者來自非CGAL庫中的點類型,將這些點類型及其相應的斷言(predicates)加入類範圍,而後你就能夠基於新的點類型運行convex_hull_2。
最後,爲何須要將一個traits對象做爲參數傳入該方法呢?主要緣由在於咱們能夠用一個更加通常的投影特徵對象(projection trait)來保存狀態。例子:若是這個投影平面是由一個向量給出的方向,並且是經過硬編碼的方式加入Projection_traits_yz_3。
 

Concepts and Models

一個概念(concept)是一個類型的一個需求集(requirment set),它包括一些內嵌的類型,成員函數或一些處理該類型自由函數。

一個概念的模型(model of a concept)是一個用於實現概念全部需求的一個類。

下面有一個函數:

template <typename T>
T
duplicate(T t)
{
return t;
}

若是你用一個類C來實例化該函數,則C必須提供一個複製構造函數(copy constructor),咱們稱類C必須是「可複製構造的」(CopyConstructible)。

另外一個例子:

template <typename T>
T& std::min( const T& a, const T& b)
{
return (a<b)?a:b;
}

這個函數只有在類型T的operator<(..)有定義時才能編譯。咱們稱類C必須是「小於關係可比較的」(LessThanComparable)

關於自由函數的一個例子:CGAL包和Boost Graph庫中的HalfedgeListGraph概念。若是一個類G要成爲HalfedgeListGraph的一個模型,必須有一個全局函數halfedges(const G&)處理該類。

關於帶有traits需求的概念的一個例子是InputIterator。對於一個InputIterator的模型,一個特化的std::iterator_traits類必須存在(或其通用的模板必須可用)。

Further Reading

閱讀:

 "The C++ Standard Library, A Tutorial and Reference" by Nicolai M. Josuttis from Addison-Wesley, or "Generic Programming and the STL" by Matthew H. Austern for the STL and its notion of concepts and models.

Other resources for CGAL are the rest of the tutorials and the user support page at https://www.cgal.org/.

相關文章
相關標籤/搜索