CGAL Tutorial
CGAL Tutorial
CGAL Tutorial
with
Preface
C GAL is the Computational Geometry Algorithms Library, written in C++. It is developped by a consortium of seven sites: Utrecht University (The Netherlands), ETH Zurich (Switzerland), Free University of
Berlin (Germany), I NRIA Sophia-Antipolis (France), Martin-Luther-Universitat Halle-Wittenberg (Germany),
Max-Planck Institute for Computer Science, Saarbrucken (Germany), R ISC Linz (Austria) and Tel-Aviv
University (Israel). More information about the project can be found on the C GAL home page at URL
http://www.cs.uu.nl/CGAL/.
This document is acompanied with a number of example source files. The example files mentioned in the text
refer to these source files. They can be found in the C GAL distribution in the directory examples/Getting started.
Authors
Geert-Jan Giezeman, Remco Veltkamp, Wieger Wesselink
Department of Computer Science
Utrecht University, The Netherlands
Acknowledgement
This work is supported by the Esprit IV Project No. 21957 (CGAL) and by the Esprit IV Project No. 28155
(GALIA).
ii
Contents
1 Introduction
1.1
Overview of C GAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Robustness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3
Generality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4
Efficiency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5
Ease of use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.6
2 Elementaries
2.1
2.2
2.1.1
Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2.1
Orientation of points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2.2
Inside circle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3
2.4
Naming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3 Arithmetics
11
3.1
3.2
Coordinate representation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.3
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.4
4 Stepping through
4.1
Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.1.1
4.2
15
Circulators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.2.1
21
5.1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.2
Bounding boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.3
Intersection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.4
Boolean operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.5
6 Triangulations
29
6.1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2
Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.3
Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.4
6.5
Delaunay triangulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.6
7 Convex Hulls
7.1
37
An example program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
7.1.1
Our problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
8.2
8.3
8.4
iv
Chapter 1
Introduction
The C GAL library is a C++ library that contains primitives, data structures, and algorithms for computational
geometry. The goal of this document is to teach you how to use the C GAL library. It contains information about
how to use primitives, datastructures, and algorithms, and contains also example programs. This document
should be used together with the C GAL Reference Manual. For downloading and installing C GAL, see the
C GAL Installation Guide.
This chapter gives a short overview of the C GAL library. Besides mentioning the purpose and the intended
users, this chapter will give you some background information about the project behind C GAL. Chapters 2 to 8
introduce several aspects of the C GAL library.
This document was written with the assumption that you are familiar with the C++ language. To assist the Cprogrammer, we have tried to explain some typical C++ features in Appendix A. Some C++ notions are explained
whenever they are encountered. This is done to such an extent that it should be possible to use the library
without relying on a C++ textbook. To really learn the C++ language we recommend an elementary book, for
example [Lippman 98].
with knowledge of computational geometry who want to use geometric algorithms in application research areas.
There are developers working in other research areas and companies who want to use C GAL in, possibly commercial, applications. All these groups of users have rather different demands. To please all of them, the C GAL
library has to fulfill a number of design goals. The most important of these are robustness, generality, efficiency
and ease of use. Of course it isnt easy to combine these in one library. Below we will describe briefly what has
been done to achieve these goals.
1.2 Robustness
Especially in the field of computational geometry, robustness of a software library is of vital importance. In
geometric algorithms many decisions are based on geometric predicates. If these predicates are not computed
exactly (for example due to round-off errors), the algorithm may easily give incorrect results. For some algorithms, strategies exist to deal with inexact predicates. However, in general this is very difficult to achieve.
Therefore, in C GAL we use the following rule: a correct result of an algorithm can only be guaranteed if geometric predicates are evaluated exactly. The most natural way of obtaining exact predicates is to choose an
appropriate number type for doing computations. As a consequence of this, in C GAL there is a strong emphasis
on the specification of algorithms. It should always be clear for which inputs and for which number types a
correct result is guaranteed. Of course the user is always free to use fast but imprecise number types like floats
or doubles. This should not cause the algorithm to break down, although it could occasionally lead to incorrect
results. The above discussion should be seen separate from dealing with degenerate cases.
1.3 Generality
The applications of the C GAL library will be very heterogeneous, with very different requirements. To make
the library as general as possible, C++ templates (parameterized data types and functions) are heavily used.
This enables the user to choose an appropriate number type for doing computations. For example, if speed is
important, computations can be done with floats or doubles. On the other hand, if reliability is more important,
computations can be done with arbitrary precision rational numbers. Furthermore, the user can choose the
representation type of points (i.e. Cartesian or homogeneous coordinates). And to some extent it is even possible
to replace a C GAL data type with a user defined one (see Chapter 8).
1.4 Efficiency
A computational geometry library must be efficient to be really useful. Whenever possible the most efficient
version of an algorithm is used. Clearly, a library algorithm cannot be the best solution for every application.
Therefore, sometimes multiple versions of an algorithm are supplied. For example, this will be the case if
dealing with degenerate cases is expensive, or when for a specific number type a more efficient algorithm exists
(in which case it will be implemented as a C++ template specialization). Another (C++ level) decision that has been
made in favor of efficiency is that geometric objects do not share a common base class with virtual methods.
However, this can be simulated through the use of CGAL_Object.
of the compiler or during low level debugging), but the absence of templates on the source code level makes
it definitely easier to start using C GAL. Developing computational geometry applications is in general very
difficult because of problems with inaccuracies and degeneracies. In C GAL these problems are largely overcome
by the strong support for computing with exact number types and the existence of algorithms that can deal with
degeneracies. Furthermore, the algorithms in the library contain many pre and postcondition checks. These
checks are performed by default, which can be a great help when debugging an application. By setting a
compiler flag these checks can be turned off to gain execution speed. To facilitate using C GAL with existing
code, C GAL types and algorithms are placed in namespace CGAL. All macro names, which can not be put in
a namespace, are prefixed with CGAL . Another point where the library can make the users life easier is the
consistent use of the iterator concept of the C++ Standard Template Library (see also Appendix A).
Chapter 2
Elementaries
2.1 Points and Vectors
Example file: examples/Getting started/basic.C
Points and vectors are about the most basic elements in geometry. The Hello, Geo program of this document
shows some operations that can be done on them.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include
#include
#include
#include
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/Vector_2.h>
<iostream>
main()
{
Point p1(1.0, -1.0), p2(4.0, 3.0), p3;
Vector v1(-1, 10);
Vector v2(p2-p1);
v1 = v1 + v2;
p3 = p2 + v1*2;
std::cout << "Vector v2 has coordinates: ("
<< v2.x() <<", "<<v2.y() <<")\n";
std::cout << "Point p3 has coordinates: ("
<< p3.x() <<", "<<p3.y() <<")\n";
}
When we compile and run the program, the output is:
Vector v2 has coordinates: (3, 4)
Point p3 has coordinates: (8, 31)
Before we have a look at the operations on points and vectors, we consider the structure of the program. First
there are include files. The first include file is tutorial.h. This header file contains some definitions that
make our example programs easier to read. In Section 3.2 we will show what it contains. Next we include
some header files that define the C GAL points and vectors. As we want to do some output, we also include the
standard C++ IO. The order of inclusion sometimes is important in C GAL. We always include tutorial.h as
the first file. When we explain the contents of this file, well explain why.
In line 8, three two-dimensional points are declared. The first two are initialised with x and y values. The third
is not initialised. In the next two lines, two vectors are declared. The first vector is initialised in the same way
as the points. The second vector is initialised with the difference of two points.
5
In lines 11 and 12 we see some operations on vectors and points. Two vectors can be added, and a new vector
results. A vector can be multiplied with a number. A vector can be added to a point, resulting in another point.
In lines 13 to 16 we print the coordinates of the computed vector and point to standard output. The x and y
coordinates are doubles.
2.2 Predicates
As we have access to the coordinates, we can do anything with points what we could possibly want to do. But
usually we dont want to work on such a low level. C GAL provides predicates that work on a higher level.
An important predicate is about the orientation of points. Three points may make a left or a right turn or they
may lie on a line. Figure 2.1 shows the possible orientations of three points, p1, p2 and p3.
In the following program the user is repeatedly prompted to give 3 points. The predicate is used to decode in
what orientation they are.
1
2
3
4
5
6
7
8
9
10
#include
#include
#include
#include
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/predicates_on_points_2.h>
<iostream>
using std::cout;
using std::cin;
main()
{
6
p3
p3
p2
p2
p1
p2
p3
p1
p3
p2
p1
p1
CGAL LEFTTURN
CGAL COLLINEAR
CGAL RIGHTTURN
Another predicate decides if a test point lies inside a circle going through three points. If three points do not lie
on a line, there is exactly one circle that passes through them. Figure 2.2 shows a circle through three points p1,
p2 and p3 and three test points.
on
in
p3
out
p1
p2
#include
#include
#include
#include
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/predicates_on_points_2.h>
<assert.h>
main()
{
Point p1(0, -5), p2(3, -4), p3(4, 3), in(-1, 4), out(5, -1), on(0, 5);
CGAL::Bounded_side inside, onside, outside;
inside = CGAL::side_of_bounded_circle(p1, p2, p3, in);
outside = CGAL::side_of_bounded_circle(p1, p2, p3, out);
onside = CGAL::side_of_bounded_circle(p1, p2, p3, on);
assert(inside == CGAL::ON_BOUNDED_SIDE);
assert(outside == CGAL::ON_UNBOUNDED_SIDE);
assert(onside == CGAL::ON_BOUNDARY);
}
8
The names of the predicate and of the constants may seem long. This is due to the fact that there is another
predicate which does almost the same thing, but considers the circle as oriented (see the reference manual for
details). In order to avoid confusion between those two predicates and their return values, shorter names like
inside or which side were not used. C GAL places clarity before brevity in its naming.
In this section we will compute the centre of mass of a number of point masses. This can be done by taking the
weighted sum of a number of vectors:
ni=1 mi~vi
(2.1)
ni=1 mi
This formula describes the position of a point mass in terms of a vector. We would expect that it is given as a
point. The vector gives the position relative to a fixed, chosen point: the origin.1
In the program below we use conversion between points and vectors in both ways. This occurs in the function
centre of mass. The program starts with the inclusion of header files.
1
2
3
4
#include
#include
#include
#include
"tutorial.h"
<iostream.h>
<CGAL/Point_2.h>
<CGAL/Vector_2.h>
A struct is defined that defines a point mass which has fields for the position and the mass. One constructor is
defined, which initialises the position and the mass. This is done by the code after the colon.
5
6
7
8
9
struct Point_mass {
Point_2 pos;
double mass;
Point_mass(const Point_2 & p, double m): pos(p), mass(m) {}
};
The actual computation is done in the function centre of mass. In a loop we compute the numerator and
denominator of equation (2.1). Those sums are collected in the variables sumv and sumw. As the formula is
written in terms of vector additions and multiplications (with a number), we have to convert the data points to
vectors first, which is done by subtracting the origin. Finally, we convert the resulting vector sumv/sumw back
to a point by adding the origin to it.
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
main()
{
const int N = 4;
Point_mass points[N] = {
Point_mass(Point_2(3,4), 1),
Point_mass(Point_2(-3,5), 1),
Point_mass(Point_2(2.1,0), 10),
Point_mass(Point_2(7,-12), 1)
};
Point_2 centre = centre_of_mass(points, points+N);
cout << "The centre of mass is: ("
<< centre.x() <<", "<< centre.y() <<")\n";
}
This program passes parameters in a way that may seem strange at first sight. Here we pass two pointers, one
to the start of the array and one pointing just after the array. A more common way is to give the number of
arguments as second parameter. This way of passing parameters is more in line with the practice of the standard
template library (STL). We will tell more about STL in Section 4.1.1, where we will also come back to the
example above.
2.4 Naming
In order to make it easier to remember what kind of entity a particular name refers to, C GAL has a naming
convention.
All globally visible names are in namespace CGAL. This means that, for instance, you will have to refer
to CGAL::ORIGIN, not to ORIGIN. In the text, we will omit the namespace, but in the code, we will use
it. The few macros that exist in C GAL all start with a CGAL prefix.
If a name is made of several words, those words are separated by underscores. For example, side of
bounded circle.
All types (classes and enums) start with one uppercase letter and are all lowercase for the rest. Examples
are Bounded side and Point 2.
Functions, whether member functions or global funcions, are all lowercase. Examples are side of
bounded circle(...) and Point 2::x().
Constants and enum values are all uppercase. For instance ON BOUNDED SIDE and Triangulation
2::EDGE.
10
Chapter 3
Arithmetics
3.1 Number types and exact arithmetic
Until now everything was based on doubles. Coordinates were stored as doubles and computations were done
on doubles. On page 8 we saw how this can lead to problems. Round-off errors may cause a wrong decision to
be made.
The C GAL library itself does not favour doubles over other number types. The decision to use doubles is not
taken in the C GAL library, but in the header file tutorial.h. In C GAL all geometric classes are parameterised
by number type.
The problem with floating point types is that their operations are inexact. The C++ language also has number
types like int and long where computations are done exact (as long as there is no overflow). Alas, those integer
number types have their drawbacks, one of which is that they have no division operator with the nice property
that a (b/a) b for all a and b. For example, 100 (99/100) equals 0. Still, we can use integer types as a
basis for exact computation, by representing numbers as rationals with an integer numerator and denominator.
There can be two good reasons for choosing this representation. Your application may use a number type where
division is an expensive operation (compared to multiplication). Or you may want to use exact arithmetic based
on integers. In this case, the most common choice is to use an integer class that can deal with arbitrarily large
numbers, since types like long are bound to overflow. An example of such a class is the L EDA type integer
[Mehlhorn & al. 98]. This type can be used in CGAL programs as leda integer, by including the header file
CGAL/leda integer.h, which adapts the L EDA integers to the requirements that C GAL imposes on its number
types. The precise requirements for using a number type as a parameter are described in the C GAL Reference
Manual. Another class for arbitrary precision integer arithmetic is the gmp z type (Gnu Multiple Precision Z)
[Granlund 96]. The type Gmpz is a wrapper class around this type. Note that C GAL only provides wrappers
for L EDA and Gnu number types. If you want to use them, you need to have L EDA or GMP installed on your
system.
The example with the points shows a peculiarity when we switch from one number type to another. When
we have a number type that supports division, the most natural representation of a point uses two numbers
(Cartesian representation).1 But when we have an integer number type, we need three numbers (homogeneous
representation). This fact, that the representation of a geometric object depends on the underlying number type,
occurs frequently. It is the reason why the parameterisation by number type takes place in two stages.
First, there is the representation class. This is a class that decides which representation is chosen by the different
geometric objects. Currently there are only two possibilities; either Cartesian or Homogeneous.
These classes are parameterised by number types. In the case of Cartesian this number type should provide
a division operator that behaves in the appropriate way. The language-defined number types float and double
are commonly used. But there are also libraries that provide number types that can be used here. For example,
L EDA [Mehlhorn & al. 98] supplies the number types rational and real.
The number type is a template parameter of the representation class, and the representation class is a template
parameter of the geometric object class. Readers not familiar with the C++ concept of templates can just follow
the examples below. Here is how we can declare points based on C++ doubles, L EDA rationals and L EDA reals:
1
2
3
4
5
6
7
8
9
10
11
#include
#include
#include
#include
<CGAL/Cartesian.h>
<CGAL/Point_2.h>
<CGAL/leda_rational.h>
<CGAL/leda_real.h>
using CGAL::Cartesian;
using CGAL::Point_2;
Point_2< Cartesian <double> > pd1;
Point_2< Cartesian <leda_rational> > pd2;
Point_2< Cartesian <leda_real> > pd3;
And here is how we can declare a point based on integers. We define a point with the built-in long, a point with
L EDAs integer, one with a Gmpzs integer and one with double as number type.
1
2
3
4
5
6
7
8
9
10
11
12
#include
#include
#include
#include
<CGAL/Homogeneous.h>
<CGAL/Point_2.h>
<CGAL/leda_integer.h>
<CGAL/Gmpz.h>
using CGAL::Homogeneous;
using CGAL::Point_2;
Point_2<
Point_2<
Point_2<
Point_2<
Homogeneous
Homogeneous
Homogeneous
Homogeneous
The order in which the include files appear is important in C GAL. The files Cartesian.h and Homogeneous.h
must be included before any other C GAL include files. If they are both included, the order in which this is
done does not matter. But if you include Point 2.h before Cartesian.h, the preprocessor should give an error
message.
There is another point to note, which is not specific to C GAL, but is a peculiarity of C++ syntax of nested
templates. Note that we use a lot of spaces in the declarations above. Most of them are not necessary, except the
one between the two > brackets. Without a space, the lexical analyser would interpret >> as a right shift token
instead of two closing brackets, which results in compilation errors.
As you can see there were good reasons to hide the complete names of the types in the header file tutorial.h.
We advise you to use typedefs to get shorter names. For instance:
1 Users
12
1
2
3
4
5
6
7
#include <CGAL/Cartesian.h>
#include <CGAL/Point_2.h>
#include <CGAL/Line_2.h>
typedef CGAL::Cartesian<double> Rep_class;
typedef CGAL::Point_2<Rep_class> Point_2;
typedef CGAL::Line_2<Rep_class> Line_2;
This kind of definitions can also be found in the header file tutorial.h.
3.3 Example
Example file: examples/Getting started/exact orientation.C
Example file: examples/Getting started/exact orientation gmpz.C
We return to the example of section 2.2.1. There we encountered a round-off error which led to a wrong decision.
Now we will use exact arithmetic to solve this problem.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include
#include
#include
#include
<CGAL/Homogeneous.h>
<CGAL/Point_2.h>
<CGAL/predicates_on_points_2.h>
<iostream>
using std::cout;
typedef CGAL::Homogeneous<long> Rep_class;
typedef CGAL::Point_2<Rep_class> Point;
main()
{
Point p1(0, 0), p2(3, 17, 10), p3(9, 51, 10);
switch (CGAL::orientation(p1,p2,p3)) {
case CGAL::LEFTTURN:
cout << "Left turn.";
break;
case CGAL::RIGHTTURN:
cout << "Right turn.";
break;
case CGAL::COLLINEAR:
cout << "The three points lie on a line.";
break;
}
cout << "\n";
}
We used the built-in type long and not an arbitrary precision integer as number type. This ensures that the code
compiles on all systems, without the need for additional libraries besides C GAL. In this case, where we know
that we only compute with small integers, there is no problem. In real code this would usually not be the case.
type makes the library easier to use. For example, plug in leda real and you dont have robustness problems
any longer.
Moreover, there are many geometric algorithms and there are many fields in which they can be used. If there
would have been a single number type that would suit everybodys needs perfectly, we would have chosen it.
But alas, there are tradeoffs, both from the implementation side and from the user side.
For some applications it is very important not to loose any precision during the computation. Others may
not care so much about that, but may be more interested in the speed with which the results are computed
(perhaps their input data is based on measurements with a large error). In the latter case, built-in floating
point types are a good candidate. In the first case, some high precision or exact number type libraries may
be a better choice.
Implementing an algorithm is easier when exact arithmetic can be assumed. Otherwise it is often necessary to very carefully analyse an algorithm to see what major consequences a minor round-off error may
have. The algorithm may have to be adapted to be more robust under such circumstances. This may also
mean that the efficiency gained by using a number type with faster operations is lost through the more
complicated algorithm.
An exactness problem may be inherent to the input of the problem. That is, a small perturbation of the
input would lead to a (radically) different output. In this case, the algorithm is allowed to give the output
belonging to the disturbed output when plugging in an inexact number type. For example, if a point
lies (almost) on the boundary of a polygon, asking whether it lies inside or outside may give the wrong
answer. This type of behaviour should always be expected by the user when inexact number types are
used.
On the other hand, the exactness problem may be caused by the chosen implementation. This case should
be carefully documented. We can elaborate on the previous example. Suppose we use the following
method to decide if a point lies inside a polygon: cast a ray from the point in some arbitrary direction
and count the number of times that an edge of the polygon is crossed. If this number is odd, the point
lies inside, otherwise outside. Now, if the ray passes (almost) through a polygon edge endpoint, inexact
computation may miss it (or count it twice), leading to a wrong result, even though the point lies well
inside or outside the polygon.
Another aspect is the availability of operations on number types. For example, if the length of a vector
is computed, a square root operation is needed. For a number type like int this is not available. Even the
finite precision double is not closed under the square root operation. Although there are number types
(e.g. L EDAs real) that can compute exactly with of square roots, exact arithmetic may become infeasible
when more operations (like sine or natural logarithm) are needed.
Which of the above considerations are important depends on the particular algorithm, the particular number
type, and the particular application. The good thing about our solution is that it makes it very easy to switch
between number types, so testing what number type is best suited is little work. Normally, all one has to do is
change one or two typedefs placed in a strategical header file and recompile.
14
Chapter 4
Stepping through
4.1 Iterators
In Section 2.3 we discussed code to compute the centre of mass of a number of point masses. We wrote the
code in a somewhat peculiar way, to make it easier to generalise. In this section we will explain what we mean
by that. We will introduce the concept of iterators. Readers familiar with iterators and the C++ standard template
library wont find anything new here, and can skip to the next section.
A set of objects can be stored in different ways; in an array, a list, a tree or other containers. Often, it is not very
important to an algorithm how the objects are stored. But if we implement the algorithm in a routine it seems
that we have to make a choice how to pass the objects to the routine. If we choose to pass them as a list and the
caller has them stored in an array, the caller first has to create a list and copy the objects into it. Of course, it
is possible to implement the algorithm for various containers. However, besides being tedious and error prone,
this approach works only when the type of containers needed is known beforehand and the number of different
types is not too large.
The iterator concept helps in those cases. Instead of passing a container to a routine, we pass iterators. Now,
what is an iterator? An iterator is some kind of pointer to an object in a container. When we say some kind
of pointer we mean that an iterator must satisfy a number of requirements. Any object that satisfies these
requirements is an iterator. In this sense, an iterator is a concept rather than a language element. It must be
possible to go to the next element (advance the iterator) and to get to the object to which the iterator points
(dereferencing). Furthermore, the syntax used to advance and dereference must be the same as with normal
pointers. So, if it is an iterator, we can advance it by ++it or it++ and dereference it by it.
Now, to be usable in this framework, every container must have associated iterators that iterate over the elements
of the container. For arrays the iterators are pointers. For other containers, it should be possible to get iterators
to the first and last element by means of member functions begin() and end(). More precisely, the end() function
gives an iterator that points one position beyond the last element. Because of the precise syntactic and semantic
rules that an iterator must obey, it is now possible to write just one implementation that works for all iterators.
We repeat here the example of computing the centre of mass of a set of point masses. There are no new C GAL
calls, but this time we use three different data structures to store the points: an array, a vector, and a list.
The array is a built-in construct of C++, the vector (a dynamic array) and the list are data types of the Standard
Template Library [Musser & al. 96]. STL is part of the C++ standard and free implementations of it are available,
see [STL]. Modern versions of compilers should be able to handle it. You will need to have STL installed in
order to be able to compile the example.
We start with including header files and making some typedefs. The include files vector and list are part of
STL.
15
1
2
3
4
5
6
7
8
9
10
#include
#include
#include
#include
#include
#include
<CGAL/Cartesian.h>
<CGAL/Point_2.h>
<CGAL/Vector_2.h>
<iostream>
<vector>
<list>
12
13
14
15
16
17
struct Point_mass {
Point_2 pos;
double mass;
Point_mass(const Point_2 & p, double m): pos(p), mass(m) {}
Point_mass() {}
};
In the main routine below, three containers are declared: points1 (an array), points2 (a vector) and points3 (a
list). The vector and the list both take a template parameter that indicates what type of values are stored in the
container. The argument Point mass is written after the class name, in brackets < >. The array is initialised in a
standard way. Both the vector and the list are initialised with the elements of the array. This is the first use of
iterators: a pointer to the first element as begin iterator and a pointer past the last argument as the end iterator.
Remember that in C and C++, a pointer is allowed to point one element past an array.
The three calls to the routine centre of mass take a begin and end iterator as arguments. We already saw in
Section 2.3 how to supply the array iterators. Both the vector and the list class supply their begin and end
iterator by functions begin() and end(). This is in accordance to the requirements of STL.
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Now lets direct our attention to the implementation of centre of mass(). (This function is described last, but
should actually be defined before the main function in the complete program text.) Although a user of C GAL
does not need to know the following, it may still be interesting to see it. Since the iterator types of the vector
and list may be different from each other and from pointer to Point mass, how is it possible that we have one
function that works for arguments of different types ?
We see that the centre of mass() routine below is almost the same as the original one in Section 2.3:
19
20
21
22
23
24
25
26
27
28
29
4.2 Circulators
Example file: examples/Getting started/circulate.C
For inherently circular structures, such as polygons, C GAL provides so-called circulators. Circulators are similar
to iterators, but there is no past-the-end value, because of the circularity. A container providing circulators has
no end() method, only a begin() method. For a circulator cir, the range [cir, cir) denotes the sequence of
all elements in the data structure. By contrast, for iterators this range would be empty. For a circulator cir,
cir==NULL tests whether the data structure is empty or not.
The following example shows a typical use of a circulator. The program reads a polygon from a file, circulates
over the edges, and sums their lengths to compute the perimeter.
1
2
3
#include "tutorial.h"
#include <CGAL/Polygon_2.h>
#include <fstream>
17
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
In the previous section we computed the centre of mass of a number of point masses, by stepping through the
container of the point masses with an iterator. In this section we compute the centre of mass of a (filled) polygon,
by stepping through the container of the vertices with a circulator.
The formula for the center of mass of a (filled) polygon is similar to, but slightly different from, the formula for
the centroid of point masses. For a polygon in the plane, with at least three vertices v1 , . . . , vn , vn+1 = v1 , the
18
(4.1)
with ai = xi yi+1 xi+1 yi . To code this formula we could step through the vertices with an iterator, but we see that
we have to address the starting vertex v1 a second time as vn+1 . For such applications a circulator is particularly
useful:
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
19
20
Chapter 5
The primitive objects in the kernel that have a position and have limited extent (point, segment, triangle and
tetrahedron, iso-rectangle, and circle, but for example not vector and line), have a member function bbox(),
which returns a bounding box of type Bbox 2. This is illustrated for triangles in the program below, see also
Figure 5.1. Lines 8 and 9 declare two triangles, in lines 12 and 13 their bounding boxes are taken.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include
#include
#include
#include
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/Triangle_2.h>
<CGAL/Bbox_2.h>
main()
{
Triangle t1(Point(-5.2,1.7), Point(-7.1,-6.3), Point(-0.9,-2.3));
Triangle t2(Point(-2.8,-4.5), Point(4.5,-1.1), Point(2.4,-7.6));
Triangle t3(Point(5.5,8.8), Point(-7.7,8.3), Point(1.3,2.9));
Bbox bb1 = t1.bbox();
Bbox bb2 = t2.bbox();
Bbox bb12, bb3;
std::cout << "Bounding box 1: " << bb1
<< "\n and bounding box 2: "<< bb2 << std::endl;
if ( !CGAL::do_overlap(bb1, bb2) )
std::cout << "do not ";
std::cout << "overlap." << std::endl;
21
t3
t1
t2
5.3 Intersection
Example file: examples/Getting started/inters check.C
Example file: examples/Getting started/inters comp.C
If two bounding boxes of objects intersect, it may be necessary to test whether the objects themselves actually
intersect. Checking for intersection is often easier than actually computing the intersection result. Therefore,
C GAL provides the function do intersect(obj1, obj2), which merely tests for intersection and returns a bool.
The parameters obj1 and obj2 can have various types. For two-dimensional kernel objects for example, they can
be Point 2, Line 2, Ray 2, Segment 2, Triangle 2, and Iso rectangle. Internally, function do intersect() uses
the bounding box overlap test.
22
The following little program shows the testing for intersection of two triangles.
intersections.h must be included.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include
#include
#include
#include
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/Triangle_2.h>
<CGAL/intersections.h>
main()
{
Triangle t1(Point(-5.2,1.7), Point(-7.1,-6.3), Point(-0.9,-2.3));
Triangle t2(Point(-4.8,-4.5), Point(4.5,-1.1), Point(2.4,-7.6));
std::cout << "Triangle 1:\n" << t1 <<
"\nand triangle 2:\n" << t2 << std::endl;
if ( ! CGAL::do_intersect(t1,t2))
std::cout << "do not intersect" << std::endl;
else
std::cout << "do intersect" << std::endl;
}
In order to actually compute the intersection of two objects, use the function intersection(obj1, obj2). If obj1
and obj2 are both triangles, then depending on their coordinates, the intersection result can be empty, a point,
a segment, a triangle, or a polygon. Because the result type is not known in advance, the return type of the
function is Object. An object of this class can represent an object of arbitrary type. In order to identify the true
type of such an object, the function assign() should be used. assign(spec obj, generic object), returns true and
assigns generic obj to spec obj if they have the same type, and returns false otherwise.
A typical use is to test all possible types such a generic object can have in a particular situation, and to handle
these cases appropriately. This is illustrated in the following piece of code. Two triangles are first tested for
intersection (line 15). If they do intersect, the actual intersection is computed (line 19), which returns a generic
object. All possible types in this situation (point, segment, triangle, and polygon) are subsequently tested
(lines 25-35).
Note that if the result is a polygon, it is returned as a vector of points, rather than as a Polygon 2. This is because
the polygon is a templated data structure in the C GAL basic library, and the do intersect() function of the kernel
is kept independent from the basic library. To get the result as a polygon, use the boolean operations functions
from the basic library, see the next section.
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
main()
{
Triangle_2 t1(Point_2(2,6), Point_2(-6,5), Point_2(-2,-7));
Triangle_2 t2(Point_2(6,0), Point_2(-6,0), Point_2(2,-5));
cout << "The intersection of triangle 1:\n" << t1;
cout << "\nand triangle 2:\n" << t2 << "\n is ";
if ( ! CGAL::do_intersect(t1,t2) ) {
cout << "empty" << endl;
}
else {
CGAL::Object result = CGAL::intersection(t1,t2);
Point_2 point;
Segment_2 segment;
Triangle_2 triangle;
vector<Point_2> polypoint;
// not a Polygon_2 !
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
if (CGAL::assign(point, result)) {
cout << "point." << endl;
} else
if (CGAL::assign(segment, result)) {
cout << "segment." << endl;
} else
if (CGAL::assign(triangle, result)) {
cout << "triangle." << endl;
} else
if (CGAL::assign(polypoint, result)) {
cout << "a polygon." << endl;
} else
cout << "unknown!" << endl;
}
}
Alternatively, the intersection function can be called first, and the function result.is empty() can be used to test
if the intersection is empty.
For a number of combinations of objects, the intersection has only a single result, see the previous section.
However, the result of the intersection of two polygons is unknown in advance. Therefore, the result is not a
single Object, but a list of generic objects. To take the intersection of two polygons, they must be simple, and
oriented counterclockwise. A third parameter, an iterator, must be specified to indicate where the result should
be put. A typical use is illustrated in the next piece of code, which computes the area of overlap of two polygons.
The function only takes the intersection, and passes the result to another funciton. The iterator for the result is
here the back inserter of a list, see line 67, which makes that the intersection objects are added at the end of the
list.
63
64
65
66
67
68
69
48
49
50
51
52
53
54
55
24
56
57
58
59
60
61
62
71
72
73
74
75
76
77
78
79
80
81
82
83
84
The above functions can be used to perform the approximate matching of polygons. In general, matching an object B onto an object A means finding a transformation T such that T (B) and A are as similar
as possible. For polygons, a possible measure of similarity is the area of overlap area(A T (B)), which
should be maximized. Alternatively, a possible measure of dissimilarity is the area of symmetric difference
area(A T (B)) + area(T (B) A), which should be minimized. Naturally, the transformation that gives the
maximal overlap also gives the minimal symmetric difference.
Here we restrict the type of transformations to translations only. The translation that moves the centroid of B to
the centroid of A can be used as an approximation of the optimal translation. The program below computes this
approximate transformation, and the resulting areas of overlap and symmetric difference.
The program first reads two polygons from a file (lines 95-97), tests for emptiness with the member function
is empty(), for simplicity with is simple(), and the orientation with orientation(). When necessary, the orientation is reversed to make them counterclockwise (which is a precondition for the boolean operations), with the
member function reverse().
25
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
26
144
145
146
147
148
149
150
151
Computing the area of overlap and symmetric difference have been done in the previous section, and computing
the centroid of a polygon has been done in Section 4.2.1. In order to transform polygon B such that its centroid
is moved to the centroid of A, we first subtract the two centroids to obtain the translation vector, see line 135.
The next line instantiates an object of type Transformation 2, which implements an affine transformation. The
constructor transl(TRANSLATION, vec) tells that the transformation object transl is a translation over vector
vec. Other special transformation can be constructed similarly; to define an arbitrary affine transformation,
all the transformation matrix coefficients can be given explicitly. The transformation object has a function
operator Transformation 2() for C GAL kernel primitives such as Point 2, so that a transformation t can be
applied to a point p as t(p). However, there is no function operator () defined for a polygon, which is not a
kernel type. Instead, there is a global function transform() which returns the image of the input polygon under
the transformation, see line 138. The centroids are computed, and the polygons are translated over the difference
vector. Before and after the translation, the areas of overlap and symmetric difference are computed and printed
on standard output.
At the end of the program, the convexity of the polygons is tested (line 145), using the member function is
convex(). Surprisingly, for convex polygons, the resulting area of overlap is at least 9/25 of the maximum
[Berg & al. 97], and the area of symmetric difference is at most 11/4 of the minimum [Alt & al. 96]!
27
28
Chapter 6
Triangulations
6.1 Introduction
Apart from elementary geometric objects such as points and lines from the kernel, C GAL offers a number of
geometric datastructures, for example polygons (introduced in the previous chapter) and triangulations. Triangulations of point sets are used in many areas, for example numerical analysis (finite elements), computer aided
design (meshes), and geographic information systems (triangulated irregular networks). In this chapter we will
consider triangulations in the plane.
6.2 Construction
Example file: examples/Getting started/triangulation1.C
Given a triangulation tr, a point pt can be inserted with tr.insert(pt). If point pt coincides with an already existing
vertex, the triangulation is not changed. If it lies on an edge, the two adjacent faces are split into two new faces.
If it lies inside a face, the face is split into three new faces. And if it lies outside the current triangulation, pt is
connected to the visible points on the convex hull in order to form new faces. This is illustrated in Figure 6.1.
The order of insertion is important, because edges are not removed.
A whole range of points can be inserted with tr.insert(first,last), where first and last are input iterators. This
is illustrated in the program below. First an empty triangulation is created in line 25. Then points1, an array
of points with length numPoints1 is inserted. The iterator points1 refers to the first, and points1+numPoints
points past the last element of the array. The corresponding range [points1, points1+numPoints) is inserted into
Triangulation of 4 points
the triangulation with tr.insert(points1, points1+numPoints), see line 25. This results in the left triangulation
of Figure 6.1. Line 26 inserts a single point, which happens to be an internal point, resulting in the middle
triangulation of Figure 6.1. Line 27 inserts an exterior point, giving the right triangulation of Figure 6.1. Finally,
an STL vector points4 of points is inserted. An STL vector is a sequence container that linearly stores objects
of a single type, see Chapter 4. The first element of point4 is pointed to by the iterator point4.begin() and the
past-the-end iterator is point4.end().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include
#include
#include
#include
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/Triangulation_2.h>
<vector>
main()
{
const int numPoints1 = 4;
static Point points1[numPoints1] = {
Point(0.4, 1),
Point(1, 0.3),
Point(0.0, -0.9),
Point(-1, 0)
};
Point point2(0.0, 0.0);
Point point3(-1,1);
std::vector<Point> points4(3);
points4[0] = Point(1, 0.9);
points4[1] = Point(1.4, -0.3);
points4[2] = Point(0.6, 0);
Triangulation tr;
tr.insert(points1, points1+numPoints1);
tr.insert(point2);
tr.insert(point3);
tr.insert(points4.begin(),points4.end());
//
//
//
//
insert
insert
insert
insert
array of Point-s
interior Point
exterior Point
vector of Point-s
6.3 Access
Vertices
A triangulation is stored as a collection of vertices and faces. A vertex is an object type that is defined locally
within the triangulation class: Triangulation 2::Vertex. Vertices are created automatically when a point is
inserted. The point associated with a vertex v can be accessed with the method v.point(), which returns a
Point 2. Each triangulation has a special vertex at infinity, see Figure 6.2.
All the finite vertices can be accessed through a vertex iterator, defined within the triangulation class:
Triangulation 2::Vertex iterator. The value type of a vertex iterator is Vertex, i.e. dereferencing a vertex
30
// access vertex
// advance the iterator
// print vertex point
Faces
A face is an object type that is defined locally within the triangulation class: Triangulation 2::Face. Faces
are created automatically when a point is inserted. A face f has three vertices: f.vertex(0), f.vertex(1), and
f.vertex(2). These methods return a Vertex handle, a sort of pointer to the vertex. Dereferencing the handle
yields the vertex itself. Conversely, if v is a handle of a vertex of f , then f.index(v) returns the vertex index of v
in f .
The vertices with indices 0, 1, 2 are ordered counterclockwise. In order to facilitate taking the next vertex in
counterclockwise or clockwise order, faces have the member functions ccw(i) which returns i + 1 modulo 3, and
cw(i), which returns i + 2 modulo 3.
Each face has three neighbors: f.neighbor(0), f.neighbor(1), and f.neighbor(2). These methods return a Face
handle, a sort of pointer to the face. Conversely, if nb is a handle of a neighboring face of f , then f.index(nb)
returns the index of nb in f , either 0, 1, or 2. The neighbor with index i is always opposite the vertex with index
i, see Figure 6.3.
Note that each face has three neighbors. An interior triangle with an edge on the convex hull has a neighboring
face that has an infinite vertex, and lies outside the convex hull. To test if a handle v refers to an infinite vertex,
the triangulation has the method is infinite(v). Similarly, is infinite(f) tests if a face handle f refers to a face
with an infinite vertex.
Analogous to the vertex iterator, there is a face iterator to address all finite faces: Triangulation 2::Face
iterator. As an example, the following piece of code looks at all faces, inspect all three neighbors, and prints
the number of infinite neighbors.
31
f.vertex(0)
f.neighbor(2)
f.neighbor(1)
f.vertex(1)
f.vertex(2)
f.neighbor(0)
Figure 6.3: Relation between indices, vertices, and neighbors of face f.
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Edges
Note that edges are not explicitly stored in the triangulation. However, an edge is identified by a pair of a
face handle and an index, where the index denotes the edge between the face and the neighbor of that index,
see Figure 6.4. The triangulation provides an iterator for edges, The value type of the iterator is Edge, i.e.
dereferencing an edge iterator yields an object of type Edge. In fact an edge is an STL pair of a face handle
and an integer: pair<Face handle, int>. Given an object edge of type Edge, the face handle is accessed with
edge.first, and the index value with edge.second.
32
e = <f,i>
vertex(i)
neighbor(i)
In the following example program, we construct a triangulation, and test for all edges with vertices edgev1 and
edgev2 if the opposite vertices lie within the smallest circle passing through the edge vertices. The triangulation
is constructed with random points, generated by the random point generator declared in line 18. Points are
successively taken from the generator in line 23, and inserted into the triangulation.
The edges of the resulting triangulation are accessed through the begin and past-the-end edge iterators declared
in lines 26 and 27. In the while loop the iterator is dereferenced, giving an edge. An edge is a pair of a face handle
and an index. The handle (face) is taken as the first element from the pair (line 33), the second element is the
index (nbIndex) of the neighbor (neighbor) that is the other adjacent face (line 35). This index is also the index
of the opposite vertex (line 41), so the edge vertices have indices face->cw(nbIndex) and face->ccw(nbIndex).
The neighbor index of neighbor referring to face is neighbor->index(face), so the second opposite vertex is the
vertex with the same index (line 42), see also Figure 6.5.
edgev1 =
vertex(face >cw(nbIndex))
Circle 2(edgev1 >point(),
edgev2 >point())
edge
opposite1 =
vertex(nbIndex))
face
neighbor =
face >neighbor(nbIndex)
opposite2 =
vertex(neighbor >index(face))
edgev2 =
vertex(face >ccw(nbIndex))
Figure 6.5: Relation between faces, neighbor, vertices, and indices in the example program.
33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include
#include
#include
#include
#include
#include
typedef
typedef
typedef
typedef
typedef
typedef
"tutorial.h"
<CGAL/Point_2.h>
<CGAL/Circle_2.h>
<CGAL/Triangulation_2.h>
<CGAL/point_generators_2.h>
<iostream>
Triangulation::Edge_iterator Edge_iterator;
Triangulation::Face Face;
Triangulation::Edge Edge;
Triangulation::Vertex Vertex;
Face::Face_handle Face_handle;
Vertex::Vertex_handle Vertex_handle;
main()
{
const int numPoints = 50;
CGAL::Random_points_in_square_2<Point> g(100.0); // random points generator
Triangulation tr;
// empty triangulation
// construct a triangulation
for (int i=0; i<numPoints; ++i) {
tr.insert( *g++ );
}
Edge_iterator it = tr.edges_begin(),
beyond = tr.edges_end();
// take edge
// advance iterator
34
55
56
57
58
59
60
61
62
63
64
Triangulation of 4 points
Figure 6.6: Difference between insertion into a normal and a Delaunay triangulation.
35
36
Chapter 7
Convex Hulls
The convex hull is one of the most familiar concepts in computational geometry. Nevertheless, at the risk of
being boring, well give a definition of a convex object and of a convex hull.
Definition 7.1 An object O is convex if for any two points p1 and p2 that are part of O, all points on the line
segment between p1 and p2 are also part of O.
Definition 7.2 The convex hull of a set of objects is the smallest convex object that contains all objects of the
set.
Those definitions hold in arbitrary dimensions and for arbitrary objects. In this chapter we have a look at a
special case, the convex hull of a set of points in the plane. Below we see an example of the convex hull of six
points.
a set of points
This chapter describes the basic way to use the convex hull routines. A more advanced use is possible, giving
more flexibility in the input. This is described in Chapter 8.
37
The following program shows how we can compute the convex hull of those six points in C GAL. The program
has more than thirty lines, but most of them have to do with defining the points and writing them to standard
output. Only two are directly related to computing the convex hull. In the second line we include the header file
where the convex hull algorithms are declared. In the second line of the main procedure we call the convex hull
function. This function takes a set of points as input and computes the convex polygon that is the convex hull
of those points.
The convex hull function, in its simplest form, takes three parameters. The first two are input iterators that are
the begin and end iterator of the sequence of input points. The third parameter is an output iterator. The resulting
convex hull polygon is represented by its sequence of vertices in counterclockwise order, not as an object of
type Polygon 2. The output iterator tells the function where it should write those vertices to. After each write,
the function increments the output iterator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include
#include
#include
#include
"tutorial.h"
<CGAL/convex_hull_2.h>
<list.h>
<iostream.h>
We can substitute the main function of the previous function by the one below.
26
27
28
29
30
31
void main()
{
Point_2 out[IN_COUNT], *beyond;
beyond = CGAL::convex_hull_points_2(in, in+IN_COUNT, out);
write(out, beyond);
}
39
40
Chapter 8
struct Special_point {
Special_point() {}
Special_point(int x, int y) : pt(x, y), next_on_hull(0) {}
CGAL::Point_2<Rep_class> pt;
Special_point *next_on_hull;
};
The input to our problem will be an array of those points.
Now the question is, how can we use our convex hull algorithm to compute the convex hull of those special
points. And also, how can we use the result of the computation to fill in the pointers.
1 Another
possibility is to inherit from CGAL::Point 2<Rep class>. You can rewrite this example in such a way as an exercise.
41
struct Special_point_traits {
typedef Special_point * Point_2;
typedef Special_less_xy Less_xy;
typedef Special_less_yx Less_yx;
typedef Special_right_of_line Right_of_line;
typedef Special_leftturn Leftturn;
Less_xy get_less_xy_object() const
{ return Less_xy(); }
Less_yx get_less_yx_object() const
{ return Less_yx(); }
Right_of_line get_right_of_line_object(
const Point_2& p, const Point_2& q) const
{ return Right_of_line( p, q); }
Leftturn get_leftturn_object() const
{ return Leftturn(); }
Special_point_traits() {}
};
Of course, we are not ready yet. The traits class uses a lot of typedefs. Except the first one, they are defined in
terms of unknown types. As we will see below, those types are function objects, that is, types with an operator()
2 If the special points were stored in a different container than an array, we could have used the iterator type of that container instead of
pointers.
3 The full list serves also for other convex hull algorithm implementations. That is why it contains superfluous elements for our purpose.
42
defined. Every function object typedef is accompanied by a function that constructs such an object. That are the
four get member functions.
struct Special_less_xy {
bool operator()(Special_point *p, Special_point *q) const
{ return CGAL::lexicographically_xy_smaller(p->pt, q->pt); }
};
The following function object is implemented in the same way. The function object Special right of line deserves more attention. This function object has an operator() that takes one Special point pointer as argument
and returns if this point lies to the right of a line. This line is specified at construction time of the objectj by two
point parameters.
The implementation makes use of a function object, CGAL::r Right of line in line 38, which is a predicate
object in the C GAL kernel (this type is parametrised by the representation class).4 So, here we do not only
define a function object, we also see how to use it. In the constructor of Special right of line in line 33 and
34 we call the constructor of CGAL::r Right of line. In the operator() function of Special right of line in
line 35 and 36 we call the operator() of CGAL::r Right of line ( rol(r->pt) ). All those different meanings of
parentheses may be confusing at first sight, but there is positive evidence that people can get used to it.
32
33
34
35
36
37
38
39
struct Special_right_of_line {
Special_right_of_line(Special_point *p, Special_point *q)
: rol(p->pt, q->pt) {}
bool operator()(Special_point *r) const
{ return rol(r->pt);}
private:
CGAL::r_Right_of_line<Rep_class> rol;
};
Finally, the last function object defines an operator() function that decides if three points define a left turn or
something else. The C GAL kernel has a predicate that computes just this. All we need to do is call this function
with the C GAL points stored inside the Special point to which the pointers refer.
41
42
43
44
struct Special_leftturn {
bool operator()(Special_point *p, Special_point *q, Special_point *r)const
{ return CGAL::leftturn(p->pt, q->pt, r->pt); }
};
43
By now we have seen the most important parts of the program. Below we give the full listing. There are still a
lot of details that we did not give in the preceding discussion. Those details do not bring anything new.
We compute with longs, a choice only meant for tu-toy-rial programs (where general availability and ease of
compilation is more important than correctness). In line 73 we choose to store the pointers to the points in an
STL vector (this is a rather arbitrary choice). The linking of the convex hull points in a chain was not described.
This is done in lines 77 to 97. It is not very interesting and documented in comments only.
The main program initialises the vector of pointers first (line 101105). Then comes the call of the convex hull
routine. The fourth argument is the interesting one. It is an object of class Special point traits, which is created
by calling the default constructor. After that the internal pointers of the points are set and the points on the
convex hull are printed to standard output.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
#include
#include
#include
#include
#include
#include
<CGAL/Homogeneous.h>
<CGAL/convex_hull_2.h>
<CGAL/Point_2.h>
<CGAL/predicates_on_points_2.h>
<CGAL/predicate_objects_on_points_2.h>
<vector.h>
<iostream.h>
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
};
struct Special_point_traits {
typedef Special_point * Point_2;
typedef Special_less_xy Less_xy;
typedef Special_less_yx Less_yx;
typedef Special_right_of_line Right_of_line;
typedef Special_leftturn Leftturn;
Less_xy get_less_xy_object() const
{ return Less_xy(); }
Less_yx get_less_yx_object() const
{ return Less_yx(); }
Right_of_line get_right_of_line_object(
const Point_2& p, const Point_2& q) const
{ return Right_of_line( p, q); }
Leftturn get_leftturn_object() const
{ return Leftturn(); }
Special_point_traits() {}
};
static Special_point in[IN_COUNT] = {
Special_point(0, 0),
Special_point(3, 4),
Special_point(0, 10),
Special_point(11, 12),
Special_point(7, 1),
Special_point(6, 5),
};
typedef vector<Special_point *> Pointer_collection;
// Link the points of the convex hull together, given a vector of pointers
// to the points on the convex hull.
Special_point *
link(Pointer_collection &c)
{
Pointer_collection::iterator cur, prev;
cur = c.begin();
// return NULL if there are no points.
if (cur == c.end())
return 0;
// prev and cur iterate over all CH points. prev lags one behind.
// Every time we set the next pointer in prev to cur.
prev = cur;
++ cur;
while (cur != c.end()) {
(*prev)->next_on_hull = *cur;
prev = cur;
++cur;
}
// Close the chain.
(*prev)->next_on_hull = c.front();
return *prev;
}
45
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
void main()
{
// Initialise a vector with pointers to the input points.
Pointer_collection pointers(IN_COUNT), out;
for (int i=0; i<IN_COUNT; i++)
pointers[i] = in+i;
// Compute the convex hull of the pointers.
CGAL::convex_hull_points_2(
pointers.begin(),
pointers.end(),
back_inserter(out),
Special_point_traits());
// Link the points of the convex hull together.
Special_point *first, *cur;
cur = first = link(out);
// Print all points of the convex hull.
if (first != 0)
do {
cout << cur->pt << \n;
cur = cur->next_on_hull;
} while (cur != first);
}
46
Appendix A
class int_stack {
public:
int_stack() ;
int_stack(int_list il) ;
bool
is_empty() const ;
int
top() const ;
void
push(int i) ;
void
pop() ;
};
47
The class is named int stack and models the well known stack of integers. On the first two lines there are two
member functions with the same name as the class. Those special functions are called constructors and play a
part in the declaration of variables of this class. The next four lines contain normal member functions. The type
bool is a boolean type with values true and false. It is a built-in type like char or int, although that is not yet
supported by all compilers. The type int list is supposed to be defined elsewhere.
Constructors give the possibility to initialise an object during declaration. In this case there are two constructors.
This gives the following possibilities to declare a variable of type int stack, where we assume that ilist is a
variable of type int list.
int_stack s1;
int_stack s2(ilist);
In the first declaration the first constructor is used. There are no parameters; the stack will start empty. In the
second declaration a list is given as parameter. This could be a list of initial values of the stack. Note that when
no parameters are required, no parentheses should be used. Those two ways of declaring can be compared to:
int i1;
int i2 = 42;
Except for the two explicitly mentioned constructors, a third one can be used:
int_stack s3(s2);
This constructor initialises s3 as a copy of s2. In C++ such a copy constructor exists for each class, that is why it
is not mentioned explicitly in the class descriptions.
After the declaration of an object, the member functions for that object can be called. A member function is
called for an object by placing the function name behind the object name, separated by a dot. For instance:
if (!s2.is_empty())
s1.push( s2.top() );
Here we check if the second stack contains an element and, if so, put the top element also on stack s1.
Now lets look more closely to the declarations of the member functions. The methods is empty and top take no
parameters. They return whether the stack is empty and the value of the top element respectively. The keyword
const after the declarations means that the object (s2 in the example) does not change because of this call.1
The method push puts an integer on the top of the stack. The method pop removes the top value from the stack.
Note that those declarations are not followed by const, because in this case the stack is altered.
Objects of a class can be assigned to variables of that class. Because this is always possible, this is not mentioned
for every individual class.
s1 = s2;
s2 = int_stack(ilist);
In the second line a constructor is used in the same way as a constant of a standard type (compare: i2 = 42;). In
fact, a temporary object of type int stack is created and assigned to s2. The syntax for this use of a constructor
differs from the syntax used for declaration; in the former case the arguments follow the class name, in the latter
they follow the variable name.
What apparently is missing in the class definition, is a description of how the data are stored. The implementor
of the class must decide how to do this, but for the user of the class this is not interesting. C++ has a mechanism
to hide those details from the user of a class. Users of a class may only access a class via its public interface.
The class may have some extra, private members, which are only accessible to himself. In general, we do not
document those private members. So, in the int stack case, we do not document how we store the data. Of
course, if you want to define your own classes, you have to know about this. But this is beyond the scope of this
introduction.
1 Actually, the object may change internally, but this is invisible when using only the public interface to the class. This allows for caching
things for more complicated objects.
48
1
2
3
4
5
int_stack *is_pt;
is_pt = new int_stack;
delete is_pt;
is_pt = new int_stack[11];
delete [] is_pt;
Which can be compared with the C code:
1
2
3
4
5
int_stack *is_pt;
is_pt = (int_stack *) malloc(sizeof(int_stack));
free(is_pt);
is_pt = (int_stack *) malloc(11*sizeof(int_stack));
free(is_pt);
As can be seen, the syntax of new is somewhat simpler than the syntax of malloc. More important though is that
new always calls a constructor for every object that is created, so that every object is initialised in a valid state.
The two different forms of delete sometimes cause confusion. Remember: when square brackets are used with
new, square brackets should be used with delete.2 So, even when an object is created as
is_pt = new int_stack[1];
it should be deleted with
delete [] is_pt;
A.2.4 Namespaces
Namespaces are a mechanism for grouping declarations together in one scope. The user of CGAL will encounter
two namespaces frequently: std and CGAL. The first holds all the functions, constants, variables and so on of
the standard C++ library. Everything supplied by the CGAL library is in namespace CGAL.
Names that are in a namespace can be accessed in two ways. One is to qualify the name by the namespace that
it belongs to. For instance, the name cout in namespace std may be referred to as std::cout. If a name is used
often in a file, the repetition of the qualification may become tedious. In that case, a using declaration may be
helpful. After declaring using std::cout;, the name cout may be used without qualification.
1
2
3
4
5
6
#include <iostream>
50
C++ defines a new model for doing input and output. A problem with C style IO is that it is not very well
extendible. The new C++ model is based on streams. Characters stream out of input stream into your program,
or they stream out of your program to output streams. A stream can be a file, standard input, standard output or
other things.
In the example below we see how it is possible to read and write integers and floats. We read from standard
input, which is always represented by the stream cin. We write to standard output, represented by the stream
cout. There is a third stream, cerr, which represents the standard error output.
Values are read from a stream into variables. We separate the input stream and the variables with right shift
operators. The direction of the arrows can be memorized by thinking of the data flowing from the stream to the
variables. The number of variables is arbitrary. In the example below there are two: an integer and a float.
To write data to an output stream, the left shift operator should be put between the output stream and the data.
Again, one or more data values can be written in a single line. In the example four types are written: a float, an
integer, character strings and characters (end of line characters, in this case).
1
2
3
4
5
6
7
8
9
10
#include <iostream>
main()
{
int i;
float f;
std::cin >> i >> f;
std::cout << "The float read was: " << f << \n;
std::cout << "The int read was: " << i << \n;
}
We can compile and run this example.
% CC basic_io.C -o basic_io
% basic_io
42 3.1842
The float read was: 3.1842
The int read was: 42
User defined types are usually read and written in the same way as built-in types (the writer of the class should
provide the necessary routines). In the next example we suppose that in header file Segment.h a type Segment
is declared, together with appropriate routines for input and output. In this case we read and write from file. In
order to do this we need to include the header file fstream.h. A file from which we want to read should be
declared as an ifstream. We can initialise an ifstream object with the name of the file. Likewise, a file to which
we want to write must be declared as an ofstream and can be initialised with the file name.
In line 10 we check if the input stream is ok. If some error occurs during reading, an error flag is set in the
stream. So, if the file segin contains no valid segment as first item, we skip the writing to segout.
1
2
3
4
5
6
7
8
9
10
11
12
#include <fstream.h>
#include <Segment.h>
main()
{
Segment seg;
ifstream fin("segin");
ofstream fout("segout");
fin >> seg;
if (fin.good())
fout << seg;
}
Formatting.
51
A.2.6 Templates
In section A.1.1 we showed the interface for a stack of integers. Now, what would the interface for a stack of
floats look like? A good guess is:
1
2
3
4
5
6
7
class float_stack {
float_stack() ;
bool
is_empty() const ;
float
top() const ;
void
push(float i) ;
void
pop() ;
};
The two interfaces look very much alike. We had to invent a different name for the class (two classes with the
same name are not allowed), the top member returns a float instead of an int and the push member expects a
float as parameter. If we wanted to have stacks for more types, this same pattern would arise again. All those
classes would have the same interface (and implementation), after substitution of the int type for another. It
would be nice if we could write the code once.
C++ has a mechanism to deal with this: templates. A templated class is a class that can be parametrized with
different types. Those types can be user defined (classes) or built-in (int, char, double . . . ). This is how we
would write the interface declaration of the templated stack class. We write the name of the type parameter (T)
between < > brackets.
1
2
3
4
5
6
7
class stack<T> {
bool
T
void
void
stack() ;
is_empty() const ;
top() const ;
push(T i) ;
pop() ;
};
A templated class is not a complete type. You still have to fill in the dots, that is, choose a type for the type
parameter(s). Here is how you could use such a templated stack class. The only place where you have to do
something new is when you declare an object of a templated class. Here we show how to make and manipulate
a stack of customers, where customer is supposed to be a user defined type.
1
2
3
4
5
6
7
8
9
10
11
52
The vectors of STL are much like the built-in array types. There are two major differences.
The number of elements is part of the vector.
A vector can be resized. Elements can be added and deleted.
First we compare the declaration of a vector to the declaration of an array. We declare a container of 10 elements
of type T, where T can be a built-in type (like int) or a user defined type (some struct or class).
T t_array[10];
vector<T> t_vec(10);
There are two variants for declaring a vector. We can omit the number of elements, in which case we get a vector
with zero elements. We can also add an argument of type T. Then all elements of the vector will be initialised
with this value instead of with some default value.
vector<float> fvec1(3, 9.781);
vector<float> fvec2;
fvec2 = fvec1;
In the example above we also see that we can assign a vector to another vector. This means copying all elements.
In this case, fvec2 will also contain three elements with value 9.781 after the assignment. Note that assignment
is not possible with built-in arrays.
Now we come to a more elaborate example. This example illustrates three new aspects of vectors.
There is the method push back, which appends an element to the vector.
The method size returns the number of elements of the vector.
Access to individual elements of a vector is done in the same way as access to elements of an array.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
using std::vector;
main()
{
vector<float> fvec;
fvec.push_back(3.14159265358979323846);
fvec.push_back(2.7182818284590452354);
vector<int> ivec(fvec.size());
int i;
for (i=0; i<ivec.size(); i++)
ivec[i] = (int) fvec[i];
std::cout << ivec[0] << << ivec[1] <<\n;
}
An STL list is a doubly connected list of elements. STL lists can contain elements. We can go forward and
backward in a list and insert and erase elements anywhere in a list.
The declaration of lists resembles the declaration of vectors. Here are three declarations of list, one containing
7 int values, an empty list of a user defined type T and one containing 9 NULL pointers to type T.
53
1
2
3
4
5
6
7
8
#include <list>
using std::list;
struct T {/* anything here */};
list<int> ilist(7);
list<T> tlist;
list<T*> tplist(9, 0);
Now we come to the access aspect of lists. How do we refer to a particular position in the list? We need some
kind of pointer when we move in the list or when we want to indicate what element must be erased or where
we want to insert an element. The STL has a special concept for this sort of thing: iterators. Iterators are a
generalisation of pointers. That is, a lot of operations can be performed on iterators that are syntactically and
semantically similar to operations on pointers. Every data structure of STL has an associated iterator type. In
the following example we will highlight the most common usage of both lists and iterators.
First, lets define a function that removes all the occurrences of the value 2 from a list of integers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <list>
using std::list;
void remove_2(list<int> &l)
{
typename list<int>::iterator x, cur(l.begin());
while (cur != l.end()) {
if (*cur == 2) {
x = cur;
++cur;
l.erase(x);
} else {
++cur;
}
}
}
The list is passed by reference to the function. This implies that the changes that occur in this routine affect the
list that was passed as argument, not just a copy of it. In the function we first declare two iterators, x and cur.
The syntax for getting the type of the iterator that is associated with the list of ints may seem strange at first
sight:
list<int>::iterator
The double colon indicates that the type iterator is defined inside the (parametrised) class list<int>. The variable
cur is initialised at declaration with the begin iterator of the list. The member function begin returns an iterator
which points to the first element of the list. Also, there is a member function end() which returns an iterator that
points just beyond the end of the list. Here, the iterator cur will pass over the list, from the first position until it
has passed the last element. We step forward by doing ++cur. At every step, we check if the value to which the
iterator points (*cur) is equal to 2. If this is the case, we save the iterator in a temporary variable, advance the
iterator and remove the retained element from the list. This is done by means of the method erase.
By now, it should be clear why we called iterators a generalisation of pointers. They will advance to the next
element by applying the ++ operator (in the same way the operator can be applied to move backward) and
the operator gives access to the value to which the iterator points, just like we can manipulate with a pointer
in an array. This syntax is shared by all iterators.
The next routine inserts a new element containing the value 7 after each element in the list.
16
17
18
19
20
21
{
list<int>::iterator cur(l.begin());
while (cur != l.end())
l.insert(++cur, 7);
}
The method insert inserts a new list element before the the element to which its first argument is pointing. Here,
the iterator is first moved to the next element (because the prefix increment operator is used) before the new
element is inserted. Note that this routine also inserts a new element after the last list element, because it inserts
an element before the end iterator, which points one position beyond the last element.
Then we show how to count all occurrences of the value 7.
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int main()
{
list<int> l;
for (int i=-1; i<4; i++)
l.push_back(i);
// l contains -1 0 1 2 3
remove_2(l);
// l contains -1 0 1 3
insert_7(l);
// l contains -1 7 0 7 1 7 3 7
l.pop_front();
// l contains 7 0 7 1 7 3 7
return count_7(l);
}
The main routine calls the routines that were described above. The only method that are new are push back and
pop front. Just like for a vector, push back inserts a value at the end of the list. The method pop front removes
the first element of a list.
Lists have a number of methods which we did not yet mention. clear() removes all elements of the list. This
routine, although mandated by the C++ standard, is not yet available in older implementations. size() gives the
number of elements in the list.
55
The vector interface that we described in section A.3.1 was not the whole story. Vectors can also be accessed by
means of iterators, in the same as lists can. In particular, a vector has begin and end methods that return iterators
to the first and past the last element of the vector.
We introduce the notion of vector iterators here because they are useful in the CGAL library. Whenever a
collection of objects is expected as input to a function, this collection should normally be supplied by means of
a begin and an end iterator. We illustrate this in the following example. Here we use the function copy, which
is part of STL. It copies a collection of objects from one place to another. The first two parameters are two
iterators that indicate the range of values that must be copied. We see from the three calls of copy that we can
use all kinds of iterators here: list iterators, vector iterators and pointers in an array. The last parameter indicates
where the values must be copied to. We wont explain the details here. Let it suffice to say that with the syntax
below the values are appended to the back of a list or vector respectively.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include
#include
#include
#include
<list>
<vector>
<algobase.h>
<iterator.h>
main()
{
double d_array[2] = { 1.3, 1.2};
std::vector<double> d_vec(3, 0.5);
d_vec[2] = 1.4;
std::list<double> d_list;
copy(d_vec.begin(), d_vec.end(), back_inserter(d_list));
// d_list: 0.5, 0.5, 1.4 ;
copy(d_array, d_array+2, back_inserter(d_list));
// d_list: 0.5, 0.5, 1.4, 1.3, 1.2 ;
d_vec.clear();
// d_vec: ;
copy(d_list.begin(), d_list.end(), back_inserter(d_vec));
// d_vec: 0.5, 0.5, 1.4, 1.3, 1.2 ;
}
56
Bibliography
[Alt & al. 96] H. Alt, U. Fuchs, G. Rote, G. Weber. Matching Convex Shapes with Respect to the Symmetric Difference. In Proceedings of the 4th Annual European Symposium on Algorithms, Lecture Notes in
Computer Science, Vol. 1148, pages 320-333. Springer, 1996.
[de Berg & al. 97] M. de Berg, M. van Kreveld, M. Overmars, O. Schwarzkopf. Computational Geometry,
Algorithms and Applications. Springer, 1997, ISBN 3-540-61270-X.
[Berg & al. 97] M. de Berg, O. Devillers, M. van Kreveld, O. Schwarzkopf, and M. Teillaud. Computing the
maximum overlap of two convex polygons under translations. In Proceedings 7th Annual International
Symposium on Algorithms and Computation, pages 126135, 1996.
[Fabri & al. 98] A. Fabri, G.-J. Giezeman, L. Kettner, S. Schirra, and S. Schonherr. On the Design of CGAL,
the Computational Geometry Algorithms Library. Research Report MPI-I-1-98, Max-Planck-Institut fur
Informatik, Saarbrucken, Germany, 1998 To appear in Trends in Software.
[Fabri & al. 96] A. Fabri, G.J. Giezeman, L. Kettner, S. Schirra, and S. Schonherr. The CGAL Kernel: A
Basis for Geometric Computation. In Proceedings of the 1st ACM Workshop on Applied Computational
Geometry, Lecture Notes in Computer Science, Vol. 1148. Springer, 1996.
[Granlund 96] T. Granlund. The Gnu Multiple Precision Arithmetics Library 2.0.2, 1996. http://www.nada.
kth.se/tege/gmp/.
[Lippman 98] S. B. Lippman, J. Lajoie. C++ primer, 3d ed. Addison Wesley Longman, 1998.
[Mehlhorn & al. 98] K. Mehlhorn, S. Naher, M. Seel and C. Uhrig. The L EDA user manual (version 3.6).
http://www.mpi-sb.mpg.de/LEDA/www/MANUAL/MANUAL.html
[Musser & al. 96] D. R. Musser and A. Saini. STL tutorial & reference guide: C++ programming with the
standard template library. Addison-Wesley, 1996.
[Myers 95] Nathan C. Myers. Traits: a New and Useful Template Technique. C++ Report, 1995.
[Overmars 96] M. H. Overmars. Designing the Computational Geometry Algorithms Library CGAL. In
Proc. of the 1st ACM Workshop on Applied Computational Geometry, Lecture Notes in Computer Science,
Vol. 1148. Springer, 1996.
[Schirra 96] S. Schirra. Designing a Computational Geometry Algorithms Library. In Lecture Notes for
Advanced School on Algorithmic Foundations of Geographic Information Systems, CISM, Udine, September
16-20, 1996.
[STL] Standard Template Library. URL ftp://butler.hpl.hp.com/stl/. URL http://www.sgi.com/
Technology/STL/.
[Stroustrup 97] B. Stroustrup. The C++ Programming Language, 3d edition. Addison-Wesley, 1997.
57