Boolean operations on polygons, referred to as Boolean set operations, constitute fundamen-tal tasks in computational geometry. These operations are ubiquitous in computer graphics, computer-aided design and manufacturing (CAD/CAM), electronic design automation, and many more domains. Unfortunately, input data of such operations, namely a set of one or more polygons, used in real-world applications is occasionally corrupted, as it originates from measuring devices that are susceptible to noise and physical disturbances. In some other cases, it contains many de-generacies, which either disable computations based on fixed-precision arithmetic or further slow down computation using exact geometric computation. Chapter 8 introduces a package that sup-ports Boolean set operations on point sets bounded by x-monotone segments in two-dimensional Euclidean space. These operations expect each input point set to meet a specific set of require-ments. Naturally, passing point sets that fail to meet these requirements as input to a Boolean set operation must be avoided.
The polygon repairing problem: Given a sequence of x-monotone segments that compose a closed curve, which represents the boundary of a point set, subdivide the point set into as few as possible simple point sets, each bounded by a counterclockwise-oriented boundary comprising x-monotone segments that are pairwise disjoint in their interior.
A simple point set is topologically equivalent to a disc, and has a well-defined interior and exterior. Refer to Chapter 8 for the exact definition of this and related terms.
Section 5.6 lists the function template polygon_orientation(), which determines the orien-tation of the boundary of a point set represented by a sequence of x-monotone segments that compose a closed curve. It computes the correct orientation only if the boundary curve does not overlap or cross itself. It can be improved to handle self-overlapping boundaries, but heavier machinery is required to properly handle self-crossing boundaries.
Automatically “fixing” corrupted data, that is, converting invalid point sets, the interiors and exteriors of which are not well defined, to valid ones, is not a simple task. As a matter of fact, it is impossible to come up with a procedure that yields the desired results at all times. Con-sider, for example, the self-intersecting polygon
depicted to the right. It is uncertain which points are contained in the point set and which ones are not, but including the green small triangle on the right and excluding the red small triangle on the left is a good guess. Winding numbers appear to be useful in such cases. The winding number of a point is the number of counterclockwise cycles the oriented boundary makes around the point. It is common to use one of the following three heuristics (although one could come up with more options): (i) Consider a point included in the point set only if its winding number is nonzero. (ii) Consider a point included in the point set only if its winding number is greater than zero. (iii) Consider a point included in the point set only if its winding number is odd. Applying option (ii) results in the guess above, but there is no guarantee that it is the desired result, and so applying any specific option may work well on a number of input cases, but fail on others.
Arrangements can be used to compute the winding numbers of all points in the plane efficiently as follows. We use an arrangement-with-history data structure where each face is extended with an integer that stores the winding number of every point on the face. The use of Arrangement_with_
history_2enables the retrieval of all curve segments that induce a given edge of the arrangement.
We apply a depth-first search (DFS) traversal5 on all the arrangement faces, starting from the unbounded face, and update the winding-number counters of each face as we visit new faces.
Figure 6.2 contains a sketch of the process, where we apply option (iii) above on a star self-intersecting polygon.
We start with the unbounded face. We maintain a counter of the number of counterclockwise cycles the oriented boundary makes. The counter is initialized with zero. Indeed, the winding
5The Boost Graph Library, Bgl, for example, can be employed for this task.
b a
Fig. 6.2: (a) A self-crossing polygon given by {a, b, c, d, e}. (b) The arrangement data structure constructed from the polygon edges. (c) The arrangement data structure with updated face winding-numbers. (d) The resulting polygons.
number of the unbounded face is zero. When we cross a halfedge e moving from the face f incident to e to the face f incident to the twin of e, we subtract the number of curve segments inducing e such that f lies to their left, and add the number of remaining curve segments inducing e, that is, the number of curve segments inducing e such that f lies to their left.
p1
In non-degenerate cases the net absolute contribution of a single edge is 1. However, in degenerate cases, where overlaps occur, the net absolute contribution of a single edge can be any other integer. It is, for example, 2 for every edge out of the four in the figure to the right, as every edge is induced by two curve segments oriented in the same direction. Thus, the winding number of the single bounded face is 2. The functor template Winding_number<Arrangement>listed below, and defined in the header file Winding_number.h, computes the winding number of the faces of a given
arrangement. When it is instantiated, the template parameter Arrangement must be substituted with an instance of the Arrangement_with_history_2 class template. In addition, the type Curve_2nested in the traits class Arrangement::Traits must be convertible to the nested type X_monotone_curve_2, as the operator of the functor Compare_endpoints_xy_2 applied to an inducing curve segment c accepts only a curve segment of the latter type, while the type of the inducing curve segments in an arrangement-with-history is Curve_2; see Line22in the code excerpt below. Moreover, the inducing curve segments must be x-monotone, and thus have a well-defined direction (either left to right or right to left). This allows comparing the direction of an inducing curve segment with the direction of the halfedge induced by it; see Line 35 in the code excerpt below.
6 template <typename Arrangement> class Winding_number { 7 private :
8 Arrangement& _arr ;
9 typename Arrangement : : Traits_2 : : Compare_endpoints_xy_2 _cmp_endpoints ; 10
11 // The Boolean f l a g i n d i c a t e s whether the face has been discovered already 12 // during the t r a v e r s a l . The i n t e g r a l f i e l d s t o r e s the winding number .
13 typedef std : : pair<bool , int> Data ;
14
15 public :
16 Winding_number( Arrangement& a r r ) : _arr( a r r )
17 {
18 // I n i t i a l i z e the winding numbers o f a l l f a c e s . 19 typename Arrangement : : Face_iterator f i ;
20 for ( f i = _arr . faces_begin ( ) ; f i != _arr . faces_end ( ) ; ++f i ) 21 f i−>set_data(Data( false , 0));
22 _cmp_endpoints = _arr . t r a i t s ()−>compare_endpoints_xy_2_object( ) ;
23 propagate_face( _arr . unbounded_face ( ) , 0 ) ; // compute the winding numbers
24 }
25
26 private :
27 // Count the net change t o t he winding number when c r o s s i n g a h a l f e d g e . 28 int count (typename Arrangement : : Halfedge_handle he )
29 {
30 bool l 2 r = he−>direction () == CGAL: :ARR_LEFT_TO_RIGHT;
31 typename Arrangement : : Originating_curve_iterator o c i t ; 32 int num = 0 ;
33 for ( o c i t = _arr . originating_curves_begin( he ) ;
34 o c i t != _arr . originating_curves_end ( he ) ; ++o c i t )
35 ( l 2 r == (_cmp_endpoints(∗ ocit ) == CGAL: :SMALLER)) ? ++num : −−num;
36 return num;
37 }
38
39 // Traverse a l l f a c e s neighboring the given face and compute the 40 // winding numbers o f a l l f a c e s while t r a v e r s i n g t he arrangement . 41 void propagate_face(typename Arrangement : : Face_handle fh , int num)
42 {
43 i f ( fh−>data ( ) . f i r s t ) return ; 44 fh−>set_data(Data(true , num) ) ; 45
46 // Traverse the inner boundary ( h o l e s ) . 47 typename Arrangement : : Hole_iterator h i t ;
48 for ( h i t = fh−>holes_begin ( ) ; hit != fh−>holes_end ( ) ; ++hit ) { 49 typename Arrangement : : Ccb_halfedge_circulator cch = ∗hit ;
50 do {
51 typename Arrangement : : Face_handle inner_face = cch−>twin()−>face ( ) ; 52 i f ( inner_face == cch−>face ()) continue; // discard antenas 53 propagate_face( inner_face , num + count ( cch−>twin ( ) ) ) ;
54 } while (++cch != ∗hit ) ;
55 }
56
57 // Traverse the outer boundary . 58 i f ( fh−>is_unbounded()) return ;
59 typename Arrangement : : Ccb_halfedge_circulator cco = fh−>outer_ccb ( ) ;
60 do {
61 typename Arrangement : : Face_handle outer_face = cco−>twin()−>face ( ) ; 62 propagate_face( outer_face , num + count ( cco−>twin ( ) ) ) ;
63 } while (++cco != fh−>outer_ccb ( ) ) ;
64 }
65 } ;
The application presented in this section attempts to “fix” invalid representations of point sets.
It is centered around a generic implementation of the following algorithm: The input point set is represented as a sequence of x-monotone segments that compose a closed curve. The program constructs an arrangement that represents the input point set. It calculates the winding numbers
associated with each face of the arrangement. Next, it excludes points the winding numbers of which are even (applying the third heuristic above). Finally, it returns the resulting point sets represented by their counterclockwise-oriented boundaries.
The function template polygon_repairing(begin, end, container, traits) accepts a range of x-monotone segments that compose a closed curve. The input range [begin, end) and the input traits objects must meet the same conditions as the corresponding arguments of the polygon_orientation(begin, end, traits)function template listed on Page 122. That is, the target point of every segment must be equal to the source point of the next segment in the range.
The target point of the last segment must be equal to the source point of the first segment. The type of the input iterators that define the input range must model the conceptBidirectionalIterator.6 The type of the traits object must model the concept ArrangementDirectionalXMonotoneTraits_
2. The value type of the input-iterator type must be convertible to the type Curve_2 nested in the traits class Traits, as only this type of curve can be inserted into an arrangement ob-ject the type of which is an instance of the Arrangement_with_history_2 class template. The container argument is used to obtain the results. It is a container of point sets the interior and exterior of each of which are well-defined. Each point set is represented by a container of x-monotone curve segments. The function template is listed below, and defined in the header file polygon_repairing.h.
#include <u t i l i t y >
#include <CGAL/ b a s i c . h>
#include <CGAL/Arrangement_with_history_2. h>
#include <CGAL/Arr_extended_dcel . h>
#include "Winding_number. h"
template <typename Traits , typename Input_iterator , typename Container>
void polygon_repairing ( Input_iterator begin , Input_iterator end , Container& r es , const Tr a its& t r a i t s ) {
// Each face i s extended with a pair o f a Boolean f l a g and an i n t e g r a l // f i e l d : The former i n d i c a t e s whether the face has been discovered // already during the t r a v e r s a l . The l a t t e r s t o r e s the winding number .
typedef std : : pair<bool , int> Data ;
typedef CGAL: : Arr_face_extended_dcel<Traits , Data> Dcel ;
typedef CGAL: : Arrangement_with_history_2<Traits , Dcel> Arrangement ; Arrangement a r r(& t r a i t s ) ;
i n s e r t ( arr , begin , end ) ;
Winding_number<Arrangement> winding_number ( a r r ) ; typename Arrangement : : Face_iterator f i ;
for ( f i = a r r . faces_begin ( ) ; f i != a r r . faces_end ( ) ; ++f i ) { i f ( ( f i−>data ( ) . second % 2) == 0) continue;
CGAL_assertion( ! f i−>is_unbounded( ) ) ; typename Container : : value_type polygon ;
typename Arrangement : : Ccb_halfedge_circulator cco = f i−>outer_ccb ( ) ; do polygon . push_back ( cco−>curve ( ) ) ;
while (++cco != f i−>outer_ccb ( ) ) ; r e s . push_back ( polygon ) ;
6See http://www.sgi.com/tech/stl/for a complete specification of the SGI Stl, where the concept Bidirec-tionalIterator is defined, among others.
} }
Like the polygon-orientation example presented in Section 5.6, the application comes with a main() function that extracts an ordered sequence of point set boundary vertices from an input file. It constructs a list of line segments that compose the boundary of the polygon as follows. The first segment connects the first point to the second; the second segment connects the second point to the third; and so on; finally, the last segment connects the last and the first points. The function then invokes an instance of the polygon_repairing() function template above, passing the range of segments as input. The sequence of segments are guaranteed to be consistently oriented, as they are constructed from the ordered sequence of points.
// F i l e : polygon_repairing
#include<l i s t >
#include <CGAL/Exact_predicates_exact_constructions_kernel . h>
#include <CGAL/Arr_segment_traits_2 . h>
#include <CGAL/Arrangement_2 . h>
#include "read_objects . h"
#include "polygon_repairing . h"
typedef CGAL: : Exact_predicates_exact_constructions_kernel Kernel ; typedef CGAL: : Arr_segment_traits_2<Kernel> Tr a its ;
typedef Tr a its : : Point_2 Point ;
typedef Tr a its : : X_monotone_curve_2 Segment ;
int main( int argc , char∗ argv [ ] ) {
std : : l i s t <Point> po ints ;
const char∗ filename = ( argc > 1) ? argv [ 1 ] : "polygon . dat" ; read_objects<Point>(filename , std : : back_inserter ( po ints ) ) ; CGAL_assertion( po ints . s i z e ( ) >= 3 ) ;
std : : l i s t <Segment> segments ;
std : : l i s t <Point >:: const_iterator i t = points . begin ( ) ; const Point& f i r s t _ p o i n t = ∗ i t++;
const Point∗ prev_point = &first_point ; while ( i t != po ints . end ( ) ) {
const Point& point = ∗ i t++;
segments . push_back (Segment(∗prev_point , point ) ) ; prev_point = &point ;
}
segments . push_back( Segment(∗prev_point , first_point ) ) ; std : : l i s t <std : : l i s t <Segment> > polygons ;
polygon_repairing ( segments . begin ( ) , segments . end ( ) , polygons , Tr a its ( ) ) ; std : : l i s t <std : : l i s t <Segment> >:: co nst_iter a to r p i t ;
for ( p i t = polygons . begin ( ) ; p i t != polygons . end ( ) ; ++p i t ) { std : : copy ( pit−>begin () , pit−>end () ,
std : : ostream_iterator<Segment>(std : : cout , "\n" ) ) ; std : : cout << std : : endl ;
}
return 0 ;
As mentioned above, using winding numbers is a heuristic ap-proach. Consider, for example, the self-intersecting polygon de-picted at the top of the figure to the right. The polygon boundary induces two abutting faces with identical (odd and positive) wind-ing numbers. Notice the delicate difference between the polygons in the figure. A small closed portion of the boundary of the top polygon winds once in counterclockwise orientation, i.e., p4, p5, p6, and once again in clockwise orientation, i.e., p7, p8, p9. Naturally, any test involving winding numbers, and in particular the three heuristics described on Page 152, cannot distinguish between the two faces, and thus both faces will either be considered as part of the final point set or be altogether dismissed.
The program listed in this section suffers from a few drawbacks.
First, it results in pairs of abutting polygons when fed with poly-gons such as the one depicted at the top of the figure to the right.
Each such pair can be unified into a single polygon by removing their common edge, thereby sim-plifying the result. Secondly, the directions of the line segments that comprise the output-polygon boundaries are not consistent when fed with polygons such as the one depicted at the bottom of the figure. Finally, the output polygon that results in this case is invalid with respect to the strict definition of valid polygons as stated in Chapter 8.
Try: Enhance the program as follows:
1. Unify abutting polygons by removing their common edge thereby simplifying the result.
2. Reverse the direction of the line segments that comprise the output-polygon boundaries as necessary so that the boundaries are consistently counterclockwise oriented.
In Exercise 8.1 you are asked to further enhance the program and eliminate the last drawback.
6.6 Bibliographic Notes and Remarks
The overlay of two arrangements with m and n vertices, respectively, can be computed using a plane-sweep algorithm in O(k log(m + n)) time, where k is the complexity of the resulting arrangement. The operation is described by de Berg et al. [45, Section 2.3], including details regarding the construction of a Dcel that represents the resulting arrangement.
The need to overlay two or more arrangements is common to many applications. As mentioned in the beginning of this chapter, computing the overlay of two planar arrangements is useful for supporting Boolean set operations on polygons or general polygons as discussed in Chapter 8; see also e.g., [19, 23].
The overlay of two-dimensional arrangements induced by geodesic arcs embedded on the unit sphere (see Section 11.1 and [75, 76]) is a fundamental operation in the computation of Minkowski sums of convex polytopes in R3 [20], which in turn is used in assembly partitioning of bounded polyhedra inR3 [72]. Here, the overlay operation is directly applied to two additional variants of such arrangements. While arrangements induced by geodesic arcs embedded on the unit sphere are not covered in this book, it is possible to replace one such arrangement with six (planar) arrangements induced by line segments. These arrangements are embedded on the six planes underlying the six faces of the unit cube (a parallel-axis cube circumscribing the unit sphere) and stitched properly at the edges of the cube [71].
Overlaying two-dimensional arrangements that represent partial envelopes of surfaces inR3 is the central part of the merge step of the recursive divide-and-conquer algorithm that computes the envelope of the surfaces; see Section 10.3. The overlay operation is also used in the construction of two-dimensional Voronoi diagrams via the construction of envelopes of surfaces inR3[191,192].
Computing the overlay of simply connected planar subdivisions optimally can be done in
linear time in the number of features in the overlaid arrangement [67]. It is based on efficient convex subdivision. A convex arrangement, for example, is simply connected. Many applications would benefit from an efficient implementation of an operation that computes the overlay of such restricted arrangements; see Exercise6.2.
The implementation of the overlay operation provided by the 2D Arrangements package utilizes the plane sweep algorithmic framework and employs a dedicated visitor, which resembles the visitor design-pattern [83, Chapter 5]. Arrangement observers follow a mechanism that resembles the observer design-pattern [83, Chapter 5].
Consistent representations of the boundary and interior of three-dimensional point sets are required by applications ranging from interactive visualization to finite element analysis. Hoff-mann defines a set of polygons in R3 to be consistent if the union of the polygons is a closed 2-manifold [119]. Automatically constructing a consistent representation from arbitrary data [165]
is naturally more complicated in 3-space than in 2-space. Polygon repairing, also referred to as (bad) polygon repairing, is offered to various extents by various software tools.
The 2D Arrangements package brings together generic programming and design patterns. It is implemented by generic components and other advanced C++ features. One example is the policy-clone idiom used to instantiate a Dcel class with many different possible traits types without ad hoc limitations on the type of the Dcel classes; see Section 6.4. More information on such techniques can be found in [7].
6.7 Exercises
6.1 Develop a program that defines an extended arrangement induced by line segments, such that (i) each halfedge is extended with a real number that represents the length of the curve associated with the halfedge, (ii) each face is extended with a real number that represents the area of the face, and (iii) each vertex is extended with a real number that represents the distance of the point associated with the vertex from the origin. The program constructs the arrangement induced by an input set of line segments. Then, it traverses all the cells of the arrangement and calculates the values of all the fields that extend the cells. Finally, it prints out the arrangement data including the extended values using an output formatter (see Section6.2.3) that you are required to provide.
6.2 [Project] Develop a generic function called overlay_connected() that computes the over-lay of simply connected arrangements of line segments, as described in [67]. Compare the execution time of the newly developed overlay function and the existing one on arrangements of various sizes.
6.3 [Project] Chapter 8 introduces the notion of general polygons, namely point sets the bound-aries of which comprise general x-monotone curves. The polygon repairing application pre-sented in Section6.5 repairs only (linear) polygons, the boundaries of which comprise line segments. With a small change to the main function the application can be extended to support general polygons the boundaries of which are still x-monotone but not necessarily line segments. However, the types Curve_2 and X_monotone_curve_2 nested in the traits class must remain convertible to one another. The type X_monotone_curve_2 must be con-vertible to the Curve_2 type, because only curves of type Curve_2 can be inserted into an arrangement object the type of which is an instance of the Arrangement_with_history_2 class template. The type Curve_2 must be convertible to the X_monotone_curve_2 type, because the operator of the functor Compare_endpoints_xy_2 accepts only curves of the X_monotone_curve_2type.
Augment the functor template Winding_number and the polygon_repairing() function template to repair general polygons the boundaries of which comprise line segments and
Augment the functor template Winding_number and the polygon_repairing() function template to repair general polygons the boundaries of which comprise line segments and