进入CGAL的世界由四个小的主题组成:定义点和线段,以及对他们的简单 *** 作,(这里要有一个重要的认识,就是计算机中的浮点数的使用会导致精度问题,这个也是计算机图形学的一个重要的问题);第二部分使用一个典型的CGAL函数,计算二维的凸包;第三部分介绍了一个特征(traits)类;第四部分定义了concept以及model的概念。
1.顶点和线段#include#include typedef CGAL::Simple_cartesian Kernel; 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; //实际的结果显示这里是把s当成了一个顶点在处理 std::cout << "p, q, and m "; switch (CGAL::orientation(p,q,m)){ case CGAL::COLLINEAR: std::cout << "are collinearn"; break; case CGAL::LEFT_TURN: std::cout << "make a left turnn"; break; case CGAL::RIGHT_TURN: std::cout << "make a right turnn"; break; } //没错,这段代码就是在计算三个点是左旋,共线,还是右旋的,估计是计算向量叉乘的方式来实现的 std::cout << " midpoint(p,q) = " << CGAL::midpoint(p,q) << std::endl; //计算两个顶点的中点 return 0; }
需要注意的地方:
- CGAL使用的命名空间为CGAL,如果代码中无using namespace CGAL的话,需要使用前缀CGAL::来调用CGAL中的成员和函数。
- CGAL中的类(例如Kernel)首字母大写,函数(例如sqdist())小写,常数(例如CGAL::COLLINEAR)全部大写,对象的维度(例如Point_2二维顶点)以后缀的方式体现。
接下来的两段代码显示了浮点数的使用造成的有悖于常识(精确的数学计算)的结果:
#include#include typedef CGAL::Simple_cartesian Kernel; 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) ? "collinearn" : "not collinearn"); } //结果是not collinear { Point_2 p(0, 1.0/3.0), q(1, 2.0/3.0), r(2, 1); std::cout << (CGAL::collinear(p,q,r) ? "collinearn" : "not collinearn"); } //结果是not collinear { Point_2 p(0,0), q(1, 1), r(2, 2); std::cout << (CGAL::collinear(p,q,r) ? "collinearn" : "not collinearn"); } //结果是collinear return 0; }
#include#include #include typedef CGAL::Exact_predicates_exact_constructions_kernel 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) ? "collinearn" : "not collinearn"); } //结果是not collinear { std::istringstream input("0 0.3 1 0.6 2 0.9"); input >> p >> q >> r; std::cout << (CGAL::collinear(p,q,r) ? "collinearn" : "not collinearn"); } //结果是collinear { q = CGAL::midpoint(p,r); std::cout << (CGAL::collinear(p,q,r) ? "collinearn" : "not collinearn"); } //结果是collinear return 0; }
以上的两段代码就揭示了计算机几何处理中的一个重要的问题:就是浮点数的使用所导致的精度问题。在第一段代码中,由于舍入(rounding)误差的存在,分数没有在计算机中表示成完整精度的double,这样一来,在判定线性性是,行列式的计算会趋于零担不会是零,从而判定为非线性。为了解决这个问题,cgal设计了另一个精确计算的核(kernel) Exact_predicates_exact_constructions_kernel。
再看第二段代码的第一块内容,这个示例仍然被判定为非线性,这是因为在这个过程中,计算机是存在着一个转换的。点的位置在代码中表示为一个text,被计算机读入时会转换为float的形式,在计算时又会被转换为任意精度的有理数,这个有理数在浮点精度上是精确的,之后的位数是不精确的,就会导致这样的结果。而第二块代码是直接从输入流(文件)中读取的数据,这个数据是以string的格式读取的,会被精确的表达为text的数据。第三块中的中点是计算机计算所得,同样也是精确的。
通过上面的两个示例,基本上可以理解计算机几何中的精度问题了。精确计算,通常是最符合直觉的计算,但是,对于计算机而言,进行精确的计算并不像想象中的那样简单。此外,它会给计算机带来额外的内存以及算力上的消耗,所以并不一定要在所有的应用中进行精确的计算!
接下来的代码是计算一个点序列的凸包,原始的输入为一个点数组,输出为凸包的一个点数组
#include#include #include typedef CGAL::Exact_predicates_inexact_constructions_kernel K; 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; }
计算凸包的函数的第一个参数为点序列的起始点的位置,第二个参数为点序列的终止点的位置,第三个为存储输出的数组的起始位置,这个函数返回值指向最后写入的顶点的数组位置,所以ptr - result的值为凸包中的顶点个数。
#include#include #include typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef K::Point_2 Point_2; typedef std::vector Points; //使用stl中的vector进行存储 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; }
这段代码的基本上和上一段代码的意思是差不多的,只不过,使用了stl中的vevtor来存储输入和输出。需要注意的是第三个参数,这里不能直接用result.begin(),而是使用了一个由辅助函数std::back_inserter()产生的输出迭代器。这个是stl库关于算法与容器解耦思想的一个体现。
3.关于核以及特质类#include#include #include #include #include typedef CGAL::Exact_predicates_inexact_constructions_kernel K3; typedef CGAL::Projection_traits_yz_3 K; 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; }
这段代码示意了如何去定义一个特质(traits)类,其中k()就是一个特质类,这个特性是专门为了模板编程而设计的。由于笔者对模板编程的理解尚浅,只能提一些简单的理解。
注意,这段代码的输入是键入顶点的形式,输出为打印的形式,函数计算的是三维的点投影到二维yz平面上的凸包的计算。输入的结尾应以ctrl+Z结束。例如可以键入:
0 0 0 0 10 0 0 10 10 0 6 5 0 4 1
ctrl+z,再enter即可输入计算。
回到对特质(traits)类的解读,展示convex_hull_2()的函数原型,这个原型可以通过查看函数定义的方式查看
templateOutputIterator convex_hull_2(InputIterator first, InputIterator beyond, OutputIterator result, const Traits & ch_traits)
要了解特质类是如何起作用的,那么首先要了解计算凸包的算法。考虑其中的一种可能是最为高效的简单算法,即“Graham/Andrew Scan”。该算法首先从左到右对点进行排序,然后通过从排序列表中一个接一个地添加一个点来逐步构建凸包。要做到这一点,它至少必须知道某种点类型,它应该知道如何对这些点进行排序,并且它必须能够评估三元组点的方向。那么对于,这个算法而言,它所提供的特质应当包括
- Traits::Point_2
- Traits::Less_xy_2
- Traits::Left_turn_2
- Triats::Equal_2
其中Point_2指的是算法处理的数据的形式,Less_xy_2和Equal_2是对顶点进行排序的依据,Left_turn_2是确定三个顶点方向的方法。
那么这里产生了两个问题:什么可以作为这个模板参数的参数?为什么要使用这个模板参数?
对于第一个问题,那就是需要了解这个模板参数都需要对哪些参数和方法进行定义,完整的定义了这些需要的参数即可成为该模板参数的参数。
对于第二个问题,最开始的那段代码就给出了原因,可以最大程度的复用代码。例如要在三维点的yz平面上计算凸包,只需要对traits进行修改即可实现。
概念是指对某种类型的的一组要求,它要有怎样的数据组织形式,有哪些成员函数等,模型是指具现了这些要求的一种类
例如
templateT duplicate(T t) { return t; }
这里的T就是一个概念,对于任何一个定义了拷贝函数的类,都是它的一个模型,又如
templateT& std::min(const T& a, const T& b) { return (a 这里的T也是一个概念,对于一个定义了(重载了)运算符<的类,都是它的一个模型。
这里的概念和模型实际上是对模板编程的一个解释,也是为了更好的理解CGAL中的函数。欢迎分享,转载请注明来源:内存溢出
评论列表(0条)