[CGAL手册]Hello World

关键词

  • Kernel
  • 几何图元:点Point、分段Segment
  • 谓词:方向orientation
  • 构造:距离squared_distance、中点midpoint
  • 特征Traits
  • 概念concept
  • 模型model

概念:类型要求,例如:InputIterator
模型:满足某一概念的一个类型,例如Point_2
特征类:表征一组模型,例如KernelConvexHullTraits_2
谓词:具有一组离散的可能结果
构造:产生一个新的实体(数/几何实体)


这个教程适合CGAL新手(知道C++和一些几何算法基本知识)。
第一部分展示如何定义点和线段的类型,如何应用一些几何谓词。本节也可以让我们认识到使用浮点数作为坐标存在严重问题。
第二部分说明一个经典的 CGAL 函数:计算 2D 凸包。
第三部分说明Traits是啥意思。
第四部分解释conceptmodel的概念。

1. 三个点、一个线段

CGAL头文件都在include/CGAL目录,类型和函数都定义在CGAL命名空间下。类型以大写字母开头,全局函数用小写字母,常量全大写,维度作为后缀,如:
Point
squared_distance
COLLINEAR
Point_2

几何图元定义在Kernel中,如:
CGAL::Simple_cartesian<double>表示使用双精度double浮点数的笛卡尔坐标

谓词(具有一组离散的可能结果),如:
orientation获得三个点的方向

构造(产生一个数字或另一个几何实体),如:
squared_distance图元距离
midpoint中点

Kernel_23/points_and_segment.cpp

#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;
  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;
}

用浮点数表示几何图形可能不太准确:
Kernel_23/surprising.cpp

#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");
  }
  {
    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;
}

结果可能是这样:

not collinear
not collinear
collinear

这是因为这些分数不能用双精度数字表示,通过计算3x3矩阵行列式来判断三点是否共线并不可行,因为它们的行列式逼近但不等于0。

有时候不共线的点可能也因为舍入误差被判断为共线了。

如果你想要精度完整,你可以选择一个使用精准谓词和构造的CGAL内核。

Kernel_23/exact.cpp

#include <iostream>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <sstream>
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;
}
not collinear
collinear
collinear

结果可能还是不尽人意。

这是因为第一段代码中的数还是由浮点数表示的(编译结果)。

第二段代码就不一样了,内核直接从文本读取一个数,这样就准确了。

第三段代码表示构造过程也是精确的,就如内核类型描述的那样。

有时候我们会从一些程序或传感器中获得“精确”的浮点数。它们不是字符串“0.1”或“1.0/10.0”一样的算式,而是一个全精度的浮点数。如果他们只需要谓词而不需要构造的算法,则可以选用提供精确谓词,但不提供精确构造的内核。例如下一节的凸包算法,输出是输入的子集,算法只比较坐标和方向测试。

感觉用精准谓词和构造内核很完美,但它并不一定适合要求性能和资源占用的场景。并且有些算法不需要精确地构造,例如网格简化算法中通过将边缘折叠到中点来收缩边缘的过程。

大多数CGAL包都会说明他们应该使用或支持哪些内核。

2. 点序列凸包

3. 关于核和特征类型

本节我们展示如何表达必要要求,以便convex_hull_2()函数可以应用到任意点类型。

如果你翻阅手册,你会发现conver_hull_2等凸包算法有两个版本。我们刚才看到的示例中,它采用两个输入迭代器表示输入,一个输出迭代器表示输出。第二个版本有一个附加模板参数Traits,和该类型的形参。

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就来了,函数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>
OutputIterator
ch_graham_andrew( InputIterator  first,
                  InputIterator  beyond,
                  OutputIterator result);

这里有两问题:啥可以作为模板参数?我们为啥要模板参数?

为了回答这俩问题,概念Kernel的任何模型都提供了概念ConvexHullTraits_2

对于第二个问题,我们想象一个场景:我们想要计算3D点云投影到YZ平面的凸包。我们搞个Projection_traits_yz_3类型,从前面示例改的:
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 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;
}

另一个示例说明用户自定义的点类型和第三方库来的点类型。将点类型和点类型所需的谓词放在类的范围内,我们就可以使用这些点来运行convex_hull_2()

额~ 简单了,是吧~

最后,我们解释下为什么要将一个Traits对象传递个凸包函数?它可以让我们使用一个更通用的投影特征对象来存储状态,例如:如果投影面由一个方向表示,它可以硬编码到Projection_traits_yz_3中。

4. 概念和模型

在上一节中,我们写到:概念Kernel的任何模型都提供了概念ConvexHullTraits_2

概念是对类型的一组要求,即它们具有某些嵌套类型、某些成员函数或以它们作为参数的自由函数。

自由?

概念的模型指满足概念要求的类。

例如这个函数:

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

如果我们想要用类型C来实例化这个函数,则需要这个类型必须具备复制构造函数,换句话说,就是类型C是CopyConstructible的一个模型。单例就不满足此要求。

另一个例子:

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

此时,类型T是LessThanComparable的一个模型。

HalfedgeListGraph是另一个例子。如果类型T想要成为HalfedgeListGraph的一个模型,则要求类型T拥有全局函数halfedges(const T&)

InputIterator是另一个例子。要成为InputIterator的一个模型,必须存在std::iterator_traits的特化(或通用模板可以被应用)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值