进入CGAL的世界

进入CGAL的世界由四个小的主题组成:定义点和线段,以及对他们的简单操作,(这里要有一个重要的认识,就是计算机中的浮点数的使用会导致精度问题,这个也是计算机图形学的一个重要的问题);第二部分使用一个典型的CGAL函数,计算二维的凸包;第三部分介绍了一个特征(traits)类;第四部分定义了concept以及model的概念。

1.顶点和线段

#include <iostream>
#include <CGAL/Simple_cartesian.h>
typedef CGAL::Simple_cartesian<double> 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 collinear\n";
    break;
  case CGAL::LEFT_TURN:
    std::cout << "make a left turn\n";
    break;
  case CGAL::RIGHT_TURN:
    std::cout << "make a right turn\n";
    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 <iostream>
#include <CGAL/Simple_cartesian.h>
typedef CGAL::Simple_cartesian<double> 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) ? "collinear\n" : "not collinear\n");
  }
  //结果是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) ? "collinear\n" : "not collinear\n");
  }
  //结果是not collinear
  {
    Point_2 p(0,0), q(1, 1), r(2, 2);
    std::cout << (CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
  }
  //结果是collinear
  return 0;
}
#include <iostream>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <sstream>
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) ? "collinear\n" : "not collinear\n");
  }
  //结果是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) ? "collinear\n" : "not collinear\n");
  }
  //结果是collinear
  {
    q = CGAL::midpoint(p,r);
    std::cout << (CGAL::collinear(p,q,r) ? "collinear\n" : "not collinear\n");
  }
  //结果是collinear
  return 0;
}

以上的两段代码就揭示了计算机几何处理中的一个重要的问题:就是浮点数的使用所导致的精度问题。在第一段代码中,由于舍入(rounding)误差的存在,分数没有在计算机中表示成完整精度的double,这样一来,在判定线性性是,行列式的计算会趋于零担不会是零,从而判定为非线性。为了解决这个问题,cgal设计了另一个精确计算的核(kernel) Exact_predicates_exact_constructions_kernel。
再看第二段代码的第一块内容,这个示例仍然被判定为非线性,这是因为在这个过程中,计算机是存在着一个转换的。点的位置在代码中表示为一个text,被计算机读入时会转换为float的形式,在计算时又会被转换为任意精度的有理数,这个有理数在浮点精度上是精确的,之后的位数是不精确的,就会导致这样的结果。而第二块代码是直接从输入流(文件)中读取的数据,这个数据是以string的格式读取的,会被精确的表达为text的数据。第三块中的中点是计算机计算所得,同样也是精确的。
通过上面的两个示例,基本上可以理解计算机几何中的精度问题了。精确计算,通常是最符合直觉的计算,但是,对于计算机而言,进行精确的计算并不像想象中的那样简单。此外,它会给计算机带来额外的内存以及算力上的消耗,所以并不一定要在所有的应用中进行精确的计算!

2.计算序列点的凸包

接下来的代码是计算一个点序列的凸包,原始的输入为一个点数组,输出为凸包的一个点数组

#include <iostream>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_2.h>
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 <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_2.h>
#include <vector>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
typedef std::vector<Point_2> 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 <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 CGAL::Exact_predicates_inexact_constructions_kernel K3;
typedef CGAL::Projection_traits_yz_3<K3> 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()的函数原型,这个原型可以通过查看函数定义的方式查看

template<class InputIterator , class OutputIterator , class Traits >
OutputIterator
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进行修改即可实现。

4.概念和模型

概念是指对某种类型的的一组要求,它要有怎样的数据组织形式,有哪些成员函数等,模型是指具现了这些要求的一种类
例如

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

这里的T就是一个概念,对于任何一个定义了拷贝函数的类,都是它的一个模型,又如

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

这里的T也是一个概念,对于一个定义了(重载了)运算符<的类,都是它的一个模型。
这里的概念和模型实际上是对模板编程的一个解释,也是为了更好的理解CGAL中的函数。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值