原文链接:https://doc.cgal.org/latest/Manual/tutorial_hello_world.html

前言

这个教程的目标读者是:了解C++并有基础几何学算法知识的CGAL新手。第一部分展示了如何定义点(point)和线段(segment)的类,以及如何在它们上应用 predicate。这个部分还提到了,使用浮点数作为坐标时存在的一些严重的问题。在第二部分中,您将遇到一个典型的 CGAL 函数,它计算 2D 凸包。第三部分展示了我们对于 Traits 类的含义。第四部分解释了 conceptmodel 的意思。

1. 三个点,一个线段

在第一个示例中,我们将演示如何构造一些点和线段,并对它们执行一些基本操作。

所有 CGAL 头文件都在目录 include/CGAL 中。所有 CGAL 类和函数都在命名空间CGAL中。类以大写字母开头,全局函数以小写字母开头,常量全部大写。对象的维度用后缀表示。

几何图元,如点类型,在一个 kernel 中定义。我们为第一个示例选择的 kernel 使用双精度浮点数作为点的笛卡尔坐标。

下面代码可以看到三点方向测试的predicate。还可以看到距离计算和中间点计算的constructions。所谓的 predicate 具有一组离散的可能结果。而所谓的 constructions 会产生一个数字或其他几何实体。

文件 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;
}

阅读代码,我们会预期它将打印三次“collinear”(共线)。但实际上它将输出如下结果:

not collinear
not collinear
collinear

这是因为这些分数并没有表示为双精度数。在共线测试时,计算3x3矩阵的行列式的值接近0但是不等于0,因此得出的结果是“非共线”。

类似,有时实际上是“左转”,但由于行列式计算期间的舍入误差,会得出“共线”或者“右转”的结果。

如果你必须要确保你的数字被视为全精度,你可以选择使用 exact predicates 和 extract constructions 的kernel。

文件 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

在第一个代码块中,结果仍然是“不共线”,原因很简单,你看到的文本坐标变成了浮点数。当它们变成任意精度有理数时,它们事实上表示浮点数,而不是文本!

而第二个代码块就不一样,它对应于从文件中读取数字。它是直接从字符串构造任意精度有理数,所以它们准确地表示文本。

在第三个代码块中你会看到,被 “中间点construction” 所构造的值,是精确的。正如这个 kernel 类型的名字所暗示的那样。

在许多情况下,您将拥有“精确的”浮点数,因为它们是由某些应用程序计算的或从传感器获得的。它们不是字符串“0.1”或动态计算为“1.0/10.0”,而是一个全精度浮点数。如果将它们输入到不进行 construction 的算法时,则可以使用提供精确 predicate 但不精确 construction 的内核。我们将在下一节中看到的凸包算法就是一个例子。输出是输入的子集,算法只执行坐标比较和方向测试。

乍一看,kernel 执行精确的 predicate 和 construction 似乎是完美的选择,但如果考虑性能要求或有限的内存资源,那他们就并非完美的选择。此外,对于许多算法来说,进行精确的 construction 是无关紧要的。例如,通过“将边缘折叠到边缘的中点来迭代收缩边缘”的表面网格简化算法。

大多数CGAL的package都解释了它们应该使用哪种kernel,或支持哪种kernel。

2. 点序列的凸包

本节中的所有示例都计算一组2D点的凸包。我们展示的算法将“表示点范围的开始/结束迭代器对”作为输入,并将凸包上的点写入输出迭代器。

2.1 使用原生数组里的点计算凸包

在第一个示例中,我们将一个包含五个点的数组作为输入。由于这些点的凸包是输入的子集,因此可以再提供一个相同大小的数组来存储结果。

文件 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 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;
}

译者注:本地跑了一遍代码,输出的结果如下
3 points on the convex hull:
0 0
10 0
10 10

我们在上一节中已经看到 CGAL 有多个kernel。由于凸包算法仅执行对输入点的坐标比较和方向测试,因此我们可以选择提供精确 predicates 但不提供精确 constructions 的 kernel。

凸包函数接受三个参数,输入的起点和末尾的指针,以及接收结果的数组的起点指针。该函数返回的指针是最后写入的凸包点后面的位置,因此指针间的距离表示了凸包上有多少点。

2.2 使用Vector里的点计算凸包

在第二个示例中,我们将原生数组换成STL中的std::vector

文件 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 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));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;
}

我们在vector中放一些点,调用std::vector类的push_back()方法。

然后我们调用凸包函数。前两个参数points.begin()points.end()迭代器,它们是指针的泛化:它们可以被引用和递增。凸包函数是泛化的,因为它能将任何可以引用和递增的内容作为输入。

第三个参数是结果被写入的地方。在前面的示例中,我们提供了一个指向已分配内存的指针。这种指针的泛化输出迭代器——它允许递增和分配一个值给引用的迭代器。在这个例子中,我们从一个根据需要增长的空vector开始。因此,我们不能简单地将result.begin()传递进去,而是用辅助函数std::back_inserter(result)生成输出迭代器。此输出迭代器在递增时不执行任何操作,只会调用result.push_back(..)进行分配。

如果您了解 STL(标准模板库),那么上面的内容就非常有意义,因为这是 STL 将算法与容器解耦的方式。如果你不了解 STL,可以先熟悉一下它的基本思想。

3. 关于 Kernels 和 Traits 类

likeconvex_hull_2() 这样的函数可以使用的点的类型并不是限定的,但是需要满足必要条件。这一节将说明我们要怎样表示这种必要条件

如果您查看函数convex_hull_2()的手册页和其他 2D 凸包算法,您会发现它们有两个版本。到目前为止我们看到的示例中,该函数采用两个迭代器来表示输入点的范围,以及一个输出迭代器来将结果写入。第二个版本有一个额外的模板参数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 这个 concept 的文档中。

这些类型被重新分组的原因很简单。不这么做的话,就会需要一个相当长的函数模板,以及一个更长的函数调用:

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);

那么,就有两个明显的问题:
什么可以用作这个模板参数的参数?
说到底我们为什么要有模板参数呢?

为了回答第一个问题,any model of the CGAL concept Kernel provides what is required by the concept ConvexHullTraits_2.

至于第二个问题。考虑一下,我们想要计算投影到yz平面上的 3D 点的凸包的应用程序。那么使用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;
}

另一个例子是“用户定义的点类型”,或者来自第三方库而不是 CGAL 的点类型。将点类型与该点类型所需的 predicate 放在类里,就可以对这些点使用 convex_hull_2() 了。

最后,让我们解释一下为什么要将 traits 对象传递给凸包函数。它将允许使用更通用的投影 traits 对象来存储状态,例如,如果投影平面由一个方向给出,就可以用 Projection_traits_yz_3

4. Concepts 与 Models

在上一节中,我们写道: any model of the CGAL concept Kernel provides what is required by the concept ConvexHullTraits_2。

concept 是对类型的一组要求,也就是说,它具有某些嵌套类型、某些成员函数或带有某些以该类型为对象的函数。conceptmodel 是满足这个 concept 要求的类。

我们来看看下面的函数。

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

这个函数仅在类型Toperator<(..)被定义时才会编译。这样,我们就说该类型必须是LessThanComparable的模型。

CGAL and the Boost Graph Library 这个package里的 HalfedgeListGraph 是一个需要自由函数(free function)的例子。为了成为HalfedgeListGraph一个类的模型,G必须有一个全局函数halfedges(const G&)

InputIterator 是一个需要 trait 类的例子。对于 InputIterator 的模型,一个 std::iterator_traits 的特化必须存在(或通用模板必须适用)。

5. 延伸阅读

我们还推荐 Addison-Wesley 的 Nicolai M. Josuttis 编写的标准教科书《The C++ Standard Library, A Tutorial and Reference》,或 Matthew H. Austern 编写的《Generic Programming and the STL》,其中有他对 conceptsmodels 的理解。

CGAL 的其他资源是剩余的教程,和https://www.cgal.org/上的用户支持页面。

翻译CGAL官方文档教程:Hello World相关推荐

  1. 翻译Houdini官方文档:PDG教程之HDA处理器

    官方文档:PDG Tutorial 3 HDA Processor Part 1 关于这个教程 你将会学到什么 步骤 0 - 拷贝所需的HDA文件 步骤 1 - 观察HDA 步骤 2 - 设置PDG输 ...

  2. Pytorch 官方文档教程整理 (一)

    Pytorch 官方文档教程整理 (一) 对应官方的 Instuction to Pytorch 前半部分 运行的Python版本:3.9.12 所使用的库: numpy 1.23.0 pandas ...

  3. 翻译HoudiniEngine官方文档:PDG

    官方文档:<Houdini Engine 3.6: PDG> 介绍 PDG 是一套用于对任务进行分配与管理的程序化架构. 使用 Houdini Engine,PDG的功能可以轻松地在其他应 ...

  4. 翻译Houdini官方文档:PDG/TOPs介绍

    官方文档:Introduction to TOPs 总览 使用TOPs工作 TOP节点UI Processors(处理器) Mappers partitions(分割) Schedulers(调度器) ...

  5. SAP help使用和下载官方文档教程

    基本是图,流量党别点(土豪的话当我在fp) 为什么有这个教程? 因为我去了几次SAP的官网都不知道怎么用,因此决定好好学习,然后就学了个大概. 第一画面可以直接去SAP官网就成 或者 https:// ...

  6. 第三期_Metasploit 介绍《Metasploit Unleashed Simplified Chinese version(Metasploit官方文档教程中文版)》

    翻译者说明1:本文为Metasploit Unleashed中文版翻译.原文链接:https://www.offensive-security.com/metasploit-unleashed/ 翻译 ...

  7. [翻译]ElasticSearch官方文档-安装

    本文翻译自:www.elastic.co/guide/en/el- 本文是Elasticsearch的入门文档,将会介绍ElasticSearch在不同环境下的安装. 安装 Elasticsearch ...

  8. [翻译]ElasticSearch官方文档-执行查询和过滤操作

    本文翻译自:www.elastic.co/guide/en/el- 本文是Elasticsearch的入门文档,将会介绍ElasticSearch中的查询操作和过滤操作. 执行查询 现在我们已经看到了 ...

  9. 第四期_Metasploit 基础(六)Meterprete《Metasploit Unleashed Simplified Chinese version(Metasploit官方文档教程中文版)》

    翻译者说明1:本文为Metasploit Unleashed中文版翻译.原文链接:https://www.offensive-security.com/metasploit-unleashed/ 翻译 ...

最新文章

  1. 2021年普高考成绩查询,山东2021年高考成绩改为6月26日前公布
  2. 结构控制Switch Case
  3. Hyperopt官方中文文档导读
  4. C# WebAPI设置跨域
  5. java akka_Akka系列(九):Akka分布式之Akka Remote
  6. WebForm 使用点滴。。。。
  7. 双时隙的工作原理_OFDM调制技术原理是什么 OFDM调制实现原理介绍【图文】
  8. openerp child_of操作符深度解析
  9. JAVA读锁不使用效果一样_Java使用读写锁替代同步锁
  10. java wait 释放_Java:wait()从同步块释放锁
  11. html5触摸界面设计与开发_原生APP的开发步骤主要分为哪些?
  12. Java 浅析内部类
  13. java从入门到精通 答案_JAVA从入门到精通习题
  14. mysql无法加载主类_错误: 找不到或无法加载主类 JDBC
  15. idea下载postgresql的驱动失败Failed to download ,报connect timed out的解决方法
  16. 网络高清监控摄像头如何安装(图文方法+模拟像机)
  17. 数据分析精选案例:3行代码上榜Kaggle学生评估赛
  18. Renesas:初步使用CS+ for CC 的注意事项
  19. AI 入行那些事儿(9)人工智能对人类社会的影响
  20. 用C语言散列表实现电话薄

热门文章

  1. sql多表联查,索引
  2. android述职报告关于责任的话,关于责任的一句话语录
  3. wps或者excel中如何将两个单元格或者数据合并;wps或者excel中如何快速填充格式
  4. 五年制计算机专业代码,45个五年制的大学专业,除了医学专业,有些专业也要学五年...
  5. getClass和.class作用
  6. Java--比较日期大小
  7. Java2Word 使用_如何用Java2Word改写已有word表格中的数据
  8. 小白to大神 vim学习笔记
  9. win10鼠标不受控制乱动_电脑鼠标灵敏度怎么调?一般人不会用!
  10. docker集群(3):集群常用命令