官方文档:CGAL帮助文档Hello World
原文链接 ,不定时更新,此文档为第一版,之后不再同步。
QQ交流群:1097346536
geometric primitives
(几何图元)被定义在kernel
(内核)中,使用几何图元之前要指定内核。此处先使用double内核,可以理解为使用double作为坐标的数值类型,内核将在后面介绍。
#include
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_2 Point_2;
typedef Kernel::Segment_2 Segment_2;
Point_2 p(1,1), q(10,10); //点
Segment_2 s(p,q); //线段
Point_2 p(1,1), q(10,10), m(5, 9); //点
Segment_2 s(p,q); //线段
// 两点距离
CGAL::squared_distance(p,q);
//点到线段距离的平方
CGAL::squared_distance(s,m);
//计算中点
CGAL::midpoint(p,q);
//三点的方向
switch (CGAL::orientation(p,q,m)){
case CGAL::COLLINEAR: //共线
std::cout << "are collinear\n";
break;
case CGAL::LEFT_TURN: //p->q, q->m时向左转
std::cout << "make a left turn\n";
break;
case CGAL::RIGHT_TURN: //p->q, q->m时向右转
std::cout << "make a right turn\n";
break;
}
计算几何的核心是计算精度问题,而Kernel正是代表这个程序如何去对待精度问题。它可以是
double
:使用double精度Exact_predicates_exact_constructions_kernel
:精确谓词,且精确构造Exact_predicates_inexact_constructions_kernel
:精确谓词、但不精确构造要解释内核,就要先看一个计算几何中的一个大坑:精度问题。
「案例一」如果是自己写的几何算法,我们一般使用float或者double类型,这里以double为例,讨论精度问题。
#include
#include
typedef CGAL::Simple_cartesian<double> Kernel; //使用double作为坐标的类型
typedef Kernel::Point_2 Point_2; //定义一个double类型的二维点
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;
}
这是由于使用double所导致的(在计算的途中,由于精度的问题,可能会得到意想不到的结果)。如果要保证精度,可以使用内核Exact_predicates_exact_constructions_kernel
。
「案例二」使用内核Exact_predicates_exact_constructions_kernel
,即精确谓词、精确构造的内核
#include
#include
#include
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; //精确谓词、精确构造的内核
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;
}
在许多情况中,就浮点数而言,它们是“精确的”,即它们是由某些应用程序计算或从传感器中获得的,是全精度的浮点数。它们不是文本0.1或动态计算"1.0/10.0"所得的(如果是这种方式得到的结果,可能不是精确的)。
凸包算法仅仅比较坐标的数值和进行方向测试,故在凸包算法中(计算一个点集的凸包),输出的结果即是点集的子集,是原来的坐标值,而并不会重新构造出新的点。此类应用场景,即可使用精确谓词,但不精确构造的内核Exact_predicates_inexact_constructions_kernel
。
「案例三」2D凸包算法示例
#include
#include
#include
typedef CGAL::Exact_predicates_inexact_constructions_kernel K; //精确谓词,但不精确构造的内核
typedef K::Point_2 Point_2;
int main()
{
// 5个点
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];
// 计算2D凸包
///参数:输入的起点、终点指针、凸包结果的指针数组
///返回值:The function returns the pointer into the result array just behind the last convex hull point written,
///so the pointer difference tells us how many points are on the convex hull.
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;
}
「案例四」说到2D凸包算法,这里展示另一种写法,使用vector存点集,在vector中计算凸包
#include
#include
#include
typedef CGAL::Exact_predicates_inexact_constructions_kernel K; //精确谓词,但不精确构造的内核
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));
//2D凸包
///第一二个参数:两个迭代器,表示输入的点集
///第三个参数:计算结果所插入的位置
CGAL::convex_hull_2(points.begin(), points.end(), std::back_inserter(result));
std::cout << result.size() << " points on the convex hull" << std::endl;
for(int i = 0, size = result.size(); i < size; ++i)
{
std::cout << result.at(i) << std::endl;
}
return 0;
}
「引言」convex_hull_2()函数有两种版本
template <class ForwardIterator, class OutputIterator>
inline
OutputIterator
convex_hull_2(ForwardIterator first, ForwardIterator last,
OutputIterator result )
template <class InputIterator, class OutputIterator, class Traits>
inline
OutputIterator
convex_hull_2(InputIterator first, InputIterator last,
OutputIterator result, const Traits& ch_traits)
第二个版本即是CGAL泛型编程的体现,Traits中定义了用户所使用类型的一些特征,让convex_hull_2()
函数支持任意点类型,也可支持多种凸包算法。
「举例」以经典的凸包算法Graham/Andrew Scan
为例,该算法先从左到右对点进行排序,然后从排序结果中逐个拿点构造凸包
(1)Traits类型
因此,若要完成凸包计算,必须要知道三个内容:点的类型、这个点类型的排序方式、三点的方向计算。而为了避免参数太多、太长,则将这些参数都定义在一个特征类中,即Traits类型中(CGAL每个Kernel中都有定义好的Traits类型)
(2)验证上面的说法convex_hull_2()
函数中用到了一个核心函数ch_graham_andrew()
,具体定义如下:
template <class InputIterator, class OutputIterator, class Point_2, class Less_xy_2, class Left_turn_2, class Equal_2>
OutputIterator
ch_graham_andrew( InputIterator first,
InputIterator beyond,
OutputIterator result);
可以看到,如果要完成convex_hull_2()
,必须要提供以下嵌套类型:
Traits::Point_2
:自定义类型Traits::Less_xy_2
:点排序Traits::Left_turn_2
:方向测试Traits::Equal_2
:相等判断而对于CGAL的每个模型,都有定义好的Traits。我们举个例子,来实现convex_hull_2()
算法:
#include
#include
#include
#include
#include
typedef CGAL::Exact_predicates_inexact_constructions_kernel K3; //精确谓词、不精确构造的Kernel
typedef CGAL::Projection_traits_yz_3<K3> K; //将3D投影到yz面的Traits
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;
}