Voronoi Basic Tutorial

In this tutorial we will cover the basic usage of the Boost.Polygon Voronoi library that should be enough for the 95% of cases. Below we will discuss the following topics:

In the example that goes through this tutorial (voronoi_basic_tutorial.cpp) we are going to construct the Voronoi diagram of a few points and segments. On the image below one may see the corresponding rendered Voronoi graph. The primary Voronoi edges are marked with the black color, non-primary with green, input geometries have blue color. In case you forgot, we split each input segment onto three sites (segment itself and both endpoints), edges that go between those sites are considered to be non-primary.



And before you proceed don't forget to:

#include "boost/polygon/voronoi.hpp"
using boost::polygon;

Preparing Input Geometries

Below is the example of how the user provided point and segment classes might look like:

struct Point {
  int a;
  int b;
  Point (int x, int y) : a(x), b(y) {}
};


struct Segment {

  Point p0;
  Point p1;
  Segment (int x1, int y1, int x2, int y2) : p0(x1, y1), p1(x2, y2) {}
};

As we are going to use the default routines defined in the voronoi.hpp header to construct the Voronoi diagram, we are required to map our point and segment classes to the corresponding Boost.Polygon concepts:

template <>
struct geometry_concept<Point> { typedef point_concept type; };

  
template <>
struct point_traits<Point> {
  typedef int coordinate_type;
   

  static inline coordinate_type get(const Point& point, orientation_2d orient) {
    return
(orient == HORIZONTAL) ? point.a : point.b;
  }
};


template <>
struct geometry_concept<Segment> { typedef segment_concept type; };

template <>
struct point_traits<Segment> {
  typedef int coordinate_type;
  typedef Point point_type;
    
  static inline coordinate_type get(const Segment& segment, direction_1d dir) {
    return
dir.to_int() ? segment.p1() : segment.p0();
  }
};


It's also possible to use the native Boost.Polygon types as point_data and segment_data, that won't require the above mapping.

So once we are done we can create the sample input:

std::vector<Point> points;
points.push_back(Point(0, 0));
points.push_back(Point(1, 6));
std::vector<Segment> segments;
segments.push_back(Segment(-4, 5, 5, -1));
segments.push_back(Segment(3, -11, 13, -1));

Construction of the Voronoi Diagram

At this point we are ready to construct the Voronoi diagram:

voronoi_diagram<double> vd;
construct_voronoi(points.begin(), points.end(), segments.begin(), segments.end(), &vd);

So brief, isn't that awesome!

Traversing Voronoi Graph

Voronoi graph traversal is the basic operation one would like to do once the Voronoi diagram is constructed. There are three ways to do that and we are going to cover all of them:
This should give a very nice idea on how to do the Voronoi diagram traversal. Notice that while the output from the first two methods should be the same, it wouldn't for the third one. The reason is that in the last case we will iterate only once through the edges with a single finite endpoint and will skip all the edges with no finite endpoints.

Associating User Data with Voronoi Primitives

A few simple cases of associating the user data with the Voronoi primitives are following:
We will consider the first example and will associate the total number of incident edges with each cell.
Note: Each Voronoi primitive contains mutable color member, that allows to use it for the graph algorithms or associate user data via array indices.

for (voronoi_diagram<double>::const_cell_iterator it = vd.cells().begin();
     it != vd.cells().end(); ++it) {
  const voronoi_diagram<double>::cell_type &cell = *it;
  const voronoi_diagram<double>::edge_type *edge = cell.incident_edge();
  std::size_t count = 0;
  do {
    ++count;
    edge = edge->next();
  } while (edge != cell.incident_edge());
  cell.color(count);
}

Rendering Voronoi Diagram

There are two main issues that don't allow to strictly render the resulting Voronoi diagram using such rendering tools as OpenGL or DirectX. Those are:
Note: This would be the issues not only for rendering tools. Basically every task that requires diagram to be represented as a set of finite segments will fall into this category.

All the above functionality is already implemented in the Voronoi utils. Before clipping an edge we are going to define the clipping rectangle. It is practical to choose it so that it wraps all the input geometries plus some offset.

bounding_rectangle<double> bbox;
for (std::vector<Point>::iterator it = points.begin(); it != points.end(); ++it)
  bbox.update(it->a, it->b);
for (std::vector<Segment>::iterator it = segments.begin(); it != segments.end(); ++it) {
  bbox.update(it->p0.a, it->p0.b);
  bbox.update(it->p1.a, it->p1.b);
}
// Add 10% offset to the bounding rectangle.
bbox = voronoi_utils<double>::scale(bbox, 1.1);


Now lets consider that we have the 3rd party library function that draws segments using the following prototype:

void draw_segment(double x1, double y1, double x2, double y2);

Then the function that renders our diagram edges will have the following implementation:

void render_diagram(const voronoi_diagram<double> &vd,
                    const voronoi_utils<double>::brect_type &bbox) {
  const std::size_t VISITED = 1;
  for (voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin();
       it != vd.edges().end(); ++it) {
    // We use color to mark visited edges.
    it->color(VISITED);
    // Don't render the same edge twice.
    if (it->twin()->color()) continue;
    voronoi_utils<double>::point_set_type polyline;
    if (it->is_linear())
      voronoi_utils<double>::clip(*it, bbox, polyline);
    else
      // Parabolic edges are always finite.
      voronoi_utils<double>::discretize(*it, 1E-1, polyline);
    // Note: discretized edges may also lie outside of the bbox.
    // So user might do additional clipping before rendering each such edge. 
    for (std::size_t i = 1; i < polyline.size(); ++i)
      draw_segment(polyline[i-1].x(), polyline[i-1].y(),
                   polyline[i].x(), polyline[i].y());
  }
}

voronoi_visualizer.cpp contains a simple fully featured implementation of the Voronoi diagram renderer using the Qt libraries. It was used to generate all the .png drawings under the ./libs/polygon/example directory.

I hope the reader managed to get to this point and found the basic tutorial to be useful (in the end it's not so basic). Worth to notice that construction of the Voronoi diagram takes only two lines of code, everything else is about initializing input data structures, traversing Voronoi graph, associating data with the diagram primitives and using the Voronoi utilities. In the default mode the Voronoi diagram operates with the signed int (32-bit) input coordinate type and double (64-bit) output coordinate type. In the advanced Voronoi tutorial we explain why this is enough for the 95% of cases and how to expand the algorithm coordinate types for the other 5%.

Copyright: Copyright © Andrii Sydorchuk 2010-2012.
License: Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)