Showing posts with label CGAL. Show all posts
Showing posts with label CGAL. Show all posts

Saturday, December 19, 2015

The Dangers of Super Smart Compilers

For the first time today, I ran an optimized DSF render using RenderFarm (the internal tool we use to make the global scenery) compiled by Clang.
The result was a segfault, which was a little bit surprising (and very disheartening) because the non-optimized debug build worked perfectly, and the optimized build works perfectly when compiled by GCC. When -O0 revealed no bug (meaning the bug wasn’t some #if DEV code) it was time for a “what did the optimizer do this time session.”
After a lot of printf and trial and error, it became clear that the optimizer had simply skipped an entire block of code that went roughly like this:
for(vector<mesh_mash_vertex_t>::iterator pts = 
   ioBorder.vertices.begin(); pts != 
   ioBorder.vertices.end(); ++pts)
if(pts->buddy == NULL)
{
   /* do really important stuff */
}
The really important stuff was being skipped, and as it turns out, it was really important.
So…WTF? Well, buddy isn’t a pointer - it’s a smart handle, so operator== isn’t a pointer compare it’s code. We can go look at that code, let’s see what’s in it.
The handle turns out to just be a wrapper around a pointer - it’s operator* returns *m_ptr. Operator== is defined out of line and has a case specifically designed to make comparison-with-null work.
  template < class DSC, bool Const >
  inline
  bool operator==(const CC_iterator<DSC, Const> &rhs,
                  Nullptr_t CGAL_assertion_code(n))
  {
    CGAL_assertion( n == NULL);
    return &*rhs == NULL;
  }
Of course, Clang is way smarter than I am, and it actually has commentary about this very line of code!
Reference cannot be bound to dereferenced null pointer in well-defined C++ code; comparison may be assumed to always evaluate to false.
Oh @#. Well, there’s our problem. This operator==, like plenty of other semi-legit code, is “unpacking” the handle wrapper by using &* to get a bare pointer to the thing being wrapped. In practice, the & and * cancel each other out and you get the bare pointer that is secretly inside whatever you’re working with.
Except that Clang is sooooo clever. It goes “hrm - if &*rhs == NULL then what was *rhs? It’s a NULL reference (because rhs is NULL and we dereferenced it). And since NULL objects by reference are illegal, this must never have happened - our code is in undefined behavior land as soon as *rhs runs.
Since our code is in undefined behavior land (if and only if *rhs is a “null object” if such a thing exists, which it doesn’t) then the compiler can do whatever it wants!
If *rhs is not a NULL object, &*rhs won’t ever equal NULL, and the result is false. So if one side of the case returns false and the other side is undefined, we can just rewrite the whole function.
  template < class DSC, bool Const >
  inline
  bool operator==(const CC_iterator<DSC, Const> &rhs,
                  Nullptr_t CGAL_assertion_code(n))
  {
    return false; /* there I fixed it! */
  }
and that is exactly what Clang does. Thus if(pts->buddy == NULL) turns into if(false) and my important stuff never runs.
The short term “fix” (and I use the term loosely) is to do this:
for(vector<mesh_mash_vertex_t>::iterator pts = 
   ioBorder.vertices.begin(); pts != 
   ioBorder.vertices.end(); ++pts)
if(pts->buddy == CDT::Vertex_handle())
{
   /* do really important stuff */
}
Now we have operator== between two handles:
  template < class DSC, bool Const1, bool Const2 >
  inline
  bool operator!=(const CC_iterator<DSC, Const1> &rhs,
                  const CC_iterator<DSC, Const2> &lhs)
  {
    return &*rhs != &*lhs;
  }
This one is also doing illegal undefined stuff (&* on a null ptr = bad) but Clang can’t tell in advance that this is bad, so the optimizer doesn’t hammer our code. Instead it shortens this to a pointer compare and we win.
Newer versions of CGAL* have fixed this by taking advantage of the fact that a custom operator->() returns the bare pointer underneath the iterator, avoiding the illegal null reference case. (This technique doesn’t work in the general case, but the CGAL template is specialized for a particular iterator.)
In Clang’s defense, the execution time of the program was faster until it segfaulted!
  • You can make fun of me for not updating to the latest version of every library every time it comes out, but given the time it takes to update libraries on 3 or 4 compilers/build systems and then deal with the chain of dependencies if they don’t all work together, you’ll have to forgive me for choosing to get real work done instead.

Thursday, February 26, 2015

The Ambiguously Overloaded Operator

It's been a while since we've had a good CGAL error. Went to upgrade to CGAL 4.5.2 and got this:
/Volumes/RAID/code/xptools/src/XESCore/MapPolygon.cpp:22:0 /Volumes/RAID/code/xptools/src/XESCore/MapPolygon.cpp:22: error: ambiguous overload for 'operator+=' in '* extent += ((const CGAL::Point_2 > >, true> >*)circ.CGAL::_HalfedgeDS_facet_const_circ > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::Halfedge, CGAL::I_Filtered_const_iterator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >, std::allocator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face > > >, CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::_Is_valid_halfedge, CGAL::internal::In_place_list_iterator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >, std::allocator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face > > >, CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::Halfedge, int, std::bidirectional_iterator_tag>, CGAL::Bidirectional_circulator_tag>::.CGAL::I_Filtered_const_iterator::operator-> [with CIterator_ = CGAL::internal::In_place_list_const_iterator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >, std::allocator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face > > >, Filter_ = CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::_Is_valid_halfedge, MIterator_ = CGAL::internal::In_place_list_iterator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >, std::allocator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face > > >, Value_ = CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::Halfedge, Diff_ = int, Category_ = std::bidirectional_iterator_tag]()->CGAL::Arrangement_on_surface_2::Halfedge::source [with GeomTraits_ = CGAL::Gps_segment_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, TopTraits_ = CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> >]().CGAL::I_Filtered_const_iterator::operator-> [with CIterator_ = CGAL::internal::In_place_list_const_iterator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >, std::allocator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face > > >, Filter_ = CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::_Is_concrete_vertex, MIterator_ = CGAL::internal::In_place_list_iterator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >, std::allocator > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face > > >, Value_ = CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::Vertex, Diff_ = int, Category_ = std::bidirectional_iterator_tag]()->CGAL::Arrangement_on_surface_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, CGAL::Arr_extended_dcel > >, true>, std::vector > >, true> >, std::allocator > >, true> > > >, CGAL::Arr_consolidated_curve_data_traits_2 > >, true> >, int> >, GIS_vertex_data, GIS_halfedge_data, GIS_face_data, CGAL::Arr_vertex_base > >, true> > >, CGAL::Arr_halfedge_base > >, true> >, CGAL::_Unique_list > >, CGAL::Gps_face_base> > >::Vertex::.CGAL::Arr_vertex > >, true> > >, GIS_vertex_data>, CGAL::Arr_extended_halfedge > >, true> >, CGAL::_Unique_list > >, GIS_halfedge_data>, CGAL::Arr_extended_face >::.CGAL::Arr_extended_vertex > >, true> > >, GIS_vertex_data>::.CGAL::Arr_vertex_base::point [with Point_ = CGAL::Point_2 > >, true> >]())->CGAL::Point_2::bbox [with R_ = CGAL::Filtered_kernel > >, true>]()'
I am pretty sure the actual problem is trivial and obvious, but it'll be easier to fix if I ignore the error message.

Thursday, March 14, 2013

How to Jam an Arrangement_2 into a General_polygon_set_2

I spent about three hours yesterday tracking down a weird bug in CGAL - I have code that builds a general polygon set out of an arrangement, exports the polygons, and weirdly the polygons had duplicate points.  This is an impossibility for a valid arrangement.

To my annoyance, I discovered today as I went to write the bug up that I knew about this bug...over three years ago. :-(  I get annoyed when I search for the answer to an obscure OpenGL problem and find my own post (e.g. I'm not going to find anything I didn't already know), but it's even more annoying to waste hours on the bug and then have that happen.

Basically if you are going to build a general polygon set by providing a pre-built arrangement, there are two things you must do:
  • Remove redundant edges - the GPS code assumes that the arrangement doesn't have needless edges (which will screw up traversal).  Fortunately, the GPS code has a utility to do this, which I just call.
  • Then you have to ensure that the direction of the underlying curves along the various edges are consistent - that is, for a given counter-clockwise boundary, every underlying curve goes either with or against the edge.
(After redundant edge removal, the arrangement will contain no antennas, so it will always be possible to get consistency on both sides of a CCB.)

I wrote code to enforce this second condition by flipping the curve of any halfedge where (1) the curve goes against the halfedge and (2) the halfedge is adjacent to the "contained" side of the map.

With this, polygon set operations work on arbitrary map input.

Why Did You Try This?

Forcing pre-made arrangements into polygon sets requires sub-classing the general polygon set template instantiation to get direect access to things like the arrangement, and it's not particularly safe.  It also requires your arrangement to have the containment flag on the face data mixed in.  Why go to the trouble?  I did this for two reasons:
  • Sometimes the polygonal set data I want to process came from an arrangement, and that arrangement is fairly huge.  Having to construct the arrangement out of polygons the normal way requires geometry tests - topology data would be lost and rediscovered.  For big maps this is really performance-painful.
  • I have some operations that work on arrangements that are precursors to boolean sets. For example, the airport surface area data are fundamentally polygon sets (e.g. in the set is the airport surface area) but some of the constructive processing (e.g. simplifying the contour) run on arrangements.
When an arrangement is turned into a polygon set, one of the results is a rather drastic map cleaning.  Since the polygon set cares only about containment (what points are in, what are out), random features like roads tend to get blown away.

Monday, August 29, 2011

merge_edge - Fixed, Sort of.

A while ago I wrote that you can't use CGAL's merge_edge if the two half-edges run in opposite X directions.  It turns out this isn't entirely true; as of CGAL 3.4 (yes, it's been a while since we went to latest) you can merge if you're very careful.

The issue is that CGAL caches the direction of a half-edge and doesn't invalidate the cache when you merge the edge.  Since it is replacing the curve of one of the two edges (the other is deleted) the cache could get out of sync with the curve, which causes chaos.

The work-around requires that you know which of two edges is going to be saved.  If you pass two halfedges h1 and h2 such that h1's target is h2's source (and that point is the vertex to be removed) then h1 and its twin are kept (and represent the merged edge) and h2 (and its twin) are deleted.

If h1 has the same direction as the new curve, you can simply merge h1,h2.  But if they run in opposite directions this means that h1 and h2 must not have the same direction (because two same-direction curves add up to the same direction curve).  If h1 does not match the curve then h2 does . If h2 matches the curve then h2's twin matches the curve's opposite.  Therefore the wokr-around when the curve and h1 don't match is to merge h2's twin, h1's twin with the curve reversed.

Thursday, May 26, 2011

Mesh Simplification Part III - Simplifying A Triangulation

Previously I suggested using a Delaunay triangulation as a spatial index to find "squatters" when simplifying an arrangement. If we want to simplify a triangulation itself, then the triangulation is the spatial index.

Consider a constrained Delaunay triangulation, where our arrangement edges have been replaced with triangulation constraints, and there are no free vertices (nor are there vertices in the triangulation that don't have at least one incident constraint).

We can now use an idea from the previously referenced paper: given a pair of constraints forming a curve pqr that we want to simplify into pr, if there exist any vertices that might be on the edge or in the interior of triangle pqr, then they must be adjacent to q in the triangulation (and between pq and pr on the accute side of pqr).

This means that we can simply circulate vertex q to search for squatters. The triangulation is the index.

Why does this work? Well, consider the case where there exists a vertex X inside triangle PQR that is not adjacent to Q. You can't have free vertices in a triangulation; the act of triangulating out X is going to create at least one link between Q and X; the only way that this will not happen is if there is already some other point inside PQR that is closer to Q than X (and in that case, we fail for that other point).

The triangulation also has the nice property that when we remove a vertex X, we can reconsider its adjacent vertices to see if X was a squatter of those other vertices. This works because if X is a squatter of Q and there are no other squatters (thus removing X "unlocks" Q) then X and Q must be connected.

Implementation Notes

In my case I have one other implementation consideration besides the usual requirements: in my case, I have a many-to-many link between vertices in my original arrangement and vertices in my triangulation. Some triangulation vertices will not have original nodes because they represent subdivision of the triangulation to improve elevation data. And some original arrangement vertices will not be in the triangulation due to simplification of the triangulation's constraints.

The problem is: how do we work backward from a triangulation triangle to an original arrangement face? Given a triangle with a constraint on one side, we need to figure out what arrangement halfedge(s) it links to.

In order to keep this "back-link" unambiguous, we cannot remove all of the degree 2 vertices from a poly-line of original edge segments. We need to leave at least one "poly-line interior" vertex in place to disambiguate two paths between vertices in the arrangement. (This case happens a lot when we have closed loops.)

In practice, we could never remove the poly-line interior vertices from all paths anyway (because they would collapse to zero paths) but in practice, we don't want to remove them from any poly-line because it makes resolving the original arrangement face more difficult.

Mesh Simplification Part II - Arrangement Simplification

In my previous post, I suggested that we can iteratively simplify an arrangement if we can test a node's degree, the pre-existence of the simplifying edge we want to replace it with, and confirm that there are no "squatting" vertices inside the triangle formed by the two old edges and the one new one.

To simplify an arrangement, therefore, what we really need is a good spatial index to make searching for squatters fast.

Previously I had used a quadtree-like structure, but I seem to be getting better results using a Delaunay triangulation. (This idea is based on the CGAL point_set_2 class).
  • We insert every vertex of our arrangement into a Delaunay triangulation.
  • When we want to check for squatters, we find the minimum circle enclosing the triangle pqr (where pqr is the curve pair we want to simplify to pr) and search the triangulation for nodes inside the circle.
To search the Delaunay triangulation for nodes within a fixed distance of point P, we first insert P (if it isn't already present) and then do a search (depth or breadth-search) from P outward based on vertex adjacency. When done, we remove P if it wasn't already part of the triangulation.

Implementation Details

For my implementation using arrangements, there are a few quirks:
  • I use my own point set; CGAL's point set uses a stack-based depth-first search that tends to flood the stack for large data sets.
  • I do not re-queue previously "blocked" points as squatters are removed. This would be a nice feature to add at some point (but is not easily added with a mere index).
  • I abuse CGAL's "merge_edge" routine to do the simplification. Edge merge was meant for collinear curves; in my case I pre-ensure that it is a safe operation. The advantage of using merge_edge vs. actually inserting the new edges and removing the old ones is speed and stability: no faces are created or removed, thus face data stays stable, and no geometry tests are needed to determine what holes go in what face, etc.
  • Because I am edge-merging, I can't merge two edges that have opposite x-monotone "direction" - thus some details won't get simplified. This is a limitation of CGAL's arrangement interface.
Here's why the last point happens: CGAL caches the "direction" (left to right or right to left) of its X-Monotone curves on the half-edge itself. Since merge assumes that we aren't moving the point-set that is the curve, but rather glue-ing two curves together in-place, it assumes that the merged half-edge direction cannot have changed. Thus it does not recalculate the direction flag.

Since the method recycles two of the four half-edges in the merge, if the first half of the curve points in the opposite direction of the merged curve, the merge is changing the half-edge's direction.

Could this case happen if the merged edge had the same path as the original two edges? No. In order for the direction to change, the two underlying curves cannot be summed to a single curve that is still x-monotone, which is a requirement for CGAL's arrangement.

Mesh Simplification Part I - It's All About Squatters

I've been working on map simplification for a few days now - it seems like I periodically have to revisit this problem. After working on simplification yet again, I realized that the problem statement is even simpler than I realized.

Given an arrangement (that is, a set of line segments and possibly free points such that line segments don't cross or end in each other's interiors) we can iteratively simplify the map by replacing adjacent pairs of line segments with a "short-cut" (e.g. replace line segments pq and qr with pr) given the following conditions:
  1. The degree of vertex q is 2 (e.g. only pq and qr emerge from q).
  2. Line segment pr is not already in the arrangement.
  3. If p, q, and r are not collinear are no points in the interior of triangle pqr (nor directly between p and r). By definition there can't be any points on pq and qr.
Test 3 - the test for "squatters" (that is, points in the interior or on the border of triangle PQR) is the key. If any points exist inside PQR then:
  • There is some kind of island geometry (or free vertex) and it will be on the wrong side of pqr after simplification, or
  • The geometry "connects" to the world outside pr and pr will intersect at least one segment.
Both cases require us to not simplify.

Given this, we can build an iterative algorithm for simplifying a mesh:
  • Put every vertex that passes these tests into a queue, based on the error introduced by removing it.
  • Remove the first vertex.
  • Requeue neighboring vertices based on changed error metrics.
Note that a vertex that may have been "stuck" before may now be removable, if one of the "squatters" from test 3 was previously removed.

The Zone Is Not What We Want

Previously I had coded similar logic via a zone visiting calculation - that is, finding every face, line and point that the edge pr would intersect. This had a few problems:
  1. Arrangement zone calculations are really expensive. Given a simple polygon with X sides, we may have to do as many as X zone calculations (if any vertex is eligible for removal) and the zone calculation iterates the polygon boundary. Thus we have an O(N^2) calculation, which is really painful for large polygons made of a large number of small sides. (Sadly, that is precisely what my data tends to be.)
  2. The zone calculation is wrong; even if we don't crash into anything while computing the zone, if the zone has holes that would be on inside of triangle PQR then we still should not be simplifying. So we would have to iterate over holes as well as calculate the zone.
Up next: fast arrangement simplification.

Tuesday, May 24, 2011

I Hate C++ Part 857: But I Already Had Coffee!

Sigh...
(ioMesh.is_edge(pts[pts.size()-2],pts[pts.size()-1]),h,vnum)
Clearly it's time to switch to espresso.

Thursday, May 06, 2010

Importing Faces Into CGAL Arrangements

This problems occurs repeatedly in the X-Plane scenery tools code: we need to import a series of polygons into a single arrangement_2 structure, and we want to tag the faces contained by these polygons as they appear in the arrangement. This isn't entirely trivial for two reasons:
  • The polygons may collide with each other and thus there may be a 1:many relationship between the original data and the final map.
  • The polygons may be self-intersected or in other ways "hosed", so we need a particular strategy for handling this situation.
CGAL provides a number of built-in tools to deal with these situations. Here are 3 basic and useful building blocks:
  1. If you are using the general polygon set code that is built on top of arrangements, you can simply perform unions and intersections of a large number of faces. For merging a large number of areas, this code is faster than anything else you might code, because it can do an N-way divide and conquer, where N is larger than 2.
  2. For custom merging and handling of multiple polygons, you can use the overlay free function, which lets you specify how the combinations of each set of face from two maps are handled.
  3. You can simply insert a set of curves into an empty arrangement and they will be "swept" together. This is a useful way to turn a messy polygon into something useful - it finds intersections, builds topology, runs quickly, and handles input no matter how degenerate.
For example, if the goal is to have any area contained by any piece of polygon as "inside" you can simply insert all polygon sides into an empty arrangement and then tag every bounded face.

Finding Polygon Internal Areas

When building arrangements out of polygons of dubious origin (or simply building an arrangement out of a large number of unrelated polygons) I use a bulk insert to "sweep" the curves into the arrangement. How do I then find the faces? Here are three techniques:
  1. The contained area of a polygon can be found by simply checking whether the face is bounded or not. This is not useful though when importing multiple polygons at the same time. (When I need this technique, each polygon is individually imported into its own arrangement, then all arrangements are merged later, typically with general-polygon-set code.)

  2. We can implement a "toggle" policy (e.g. each line toggles interior vs. exterior) by doing a search from the outside to the inside of the arrangement, toggling whether we are "inside" or "outside" each time we cross a halfedge. The halfedges can retain curve-based properties; typically I use a consolidated data curve so that halfedges retain every property attached to them.

    One danger: an antenna will produce incorrect results in this technique because it won't toggle the data property twice. This can be hard to work around because data from the insert is maintained per edge, not half-edge

    Unfortunately, topology of the final arrangement doesn't help us resolve this either. Imagine an antenna (in the original polygon that crashes into another polygon, thus becoming part of a real partition. Technically the original face is not split by the antenna, but the faces in the final arrangement are, so saying face()==twin()->face() doesn't tell us we have an antenna in the original.

    Two ways to work around this: don't insert antennas, or don't tag known antennas with any data. Both cases require knowing that we have an antenna ahead of time.

  3. Sometimes we want to use a "winding rule" - that is, contain areas inside closed left turning contours. This is, for example, useful when calculating offset buffers and minkowski sums; the artifacts from strange shapes being offset too much turn out not to be left turning contours and get thrown out.

    To find the winding rule areas, we have to look at the direction of the curves inserted.

    The way my code does this is to look at the direction of the underlying curve vs. the direction of the half-edge and then mark the half-edge as being on the "inside" or "outside" of the winding with a dat a field on the half-edge itself. This is reconstructed after bulk insert, and then we can traverse the whole arrangement, counting windings.

    The limitation here is similar to above: if we have an antenna, the underlying curve can have only one direction, not two, and one half-edge will be incorrectly tagged. Fortunately antennas are not typically necessary to produce offset buffers.

It should be noted that if you insert curves incrementally (insert one curve into the arrangement) an observer of the arrangement returns all generated and overlapped half-edges, which gives you the contained bounds of the contour inserted. I use this technique when inserting a low-side-count face into a very complex arrangement, to avoid re-sweeping a huge amount of data. Overlays and bulk inserts do not produce "per curve" announcements via the observer mechanism.

Friday, April 23, 2010

CGAL: It's All About the Mantissa

In a past post I described CGAL as having no rounding errors. It does this by using number types of variable size (using dynamically allocated memory per number!) so that it never runs out of digits. (It also maintains the numerator and denominator of fractions separately to avoid problems with repeating decimals.)

The advantage of this is that geometric algorithms that rely on precise calculations never go haywire due to rounding errors. For example, when using fixed-precision math (e.g. IEEE floats) the intersection of two near-parallel lines will be calculated inaccurately - sometimes with the intersection showing up miles from the original lines. CGAL always has more precision, so it avoids this problem.

But there is one down-side: when you perform a series of intersections, the result is exact numbers whose mantissas (the number of actual digits) have grown very long. And CGAL won't blink about making them even longer as you do more calculations.

Instead CGAL will become insanely slow.

I hit this case the other day. The first piece of processing I do is to combine a whole pile of vector data from OSM into one integrated map. While OSM is not particularly high precision (from a bits standpoint) the resulting intersecting points are calculated "perfectly", sometimes with very large mantissas.

I then wrote a piece of code to take a city block from that OSM map and perform some calculations to find the sidewalk calculation. The problem: the four corners of the city block were already very long numbers since they were the result of a CGAL calculation. Thus a long calculation on a long calculation becomes very slow.

The original algorithm took about 36 minutes for a fully optimized build to find all sidewalks in San Diego. That is way too slow, and unusable for our project.

I the put a rounding stage in: fore each corner of the block, I would convert it to a regular 64-bit IEEE float and then back to CGAL, throwing out any "extra" precision that CGAL was saving. Note that the 64-bit float already gives me better than 1 millimeter precision, which is more than overkill for a road. The algorithm run on the "simplified" data ran in 67 seconds.

Now there is one danger: if, due to mismatched road locations in OSM or conflicting edits, some of the "blocks" were really tiny (less than 1 mm) CGAL would have correctly built that block using infinite precision, and my "rounding" would have incorrectly reshaped those blocks, perhaps turning them inside out or in some other way damaging them.

So a necessary step to productizing this 'resolution reduction' is to do a sanity check on each resulting block. Fortunately most of the time if the block contains too-small-to-use data, we don't need the data in the first place.

Thursday, December 10, 2009

Best Linker Error. Ever.

I did not induce this on purpose, it happened during normal development:
ld: bl out of range (67108768 max is +/-16M) from CGAL::Multiset > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >*, CGAL::Compare_events > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arr_construction_event > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, std::allocator >::_destroy(CGAL::Multiset > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >*, CGAL::Compare_events > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arr_construction_event > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, std::allocator >::Node*)at 0x002AC4E0 in __text of /Volumes/RAID/code/XPTools_refactoring/build/SceneryTools.build/Release/MeshTool.build/Objects-normal/ppc/MeshTool_Create.o to CGAL::Multiset > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >*, CGAL::Compare_events > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arr_construction_event > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, std::allocator >::_destroy(CGAL::Multiset > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >*, CGAL::Compare_events > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arr_construction_event > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, std::allocator >::Node*)at 0x002AC4E0 in __text of /Volumes/RAID/code/XPTools_refactoring/build/SceneryTools.build/Release/MeshTool.build/Objects-normal/ppc/MeshTool_Create.o in CGAL::Multiset > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >*, CGAL::Compare_events > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arr_construction_event > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, std::allocator >::_destroy(CGAL::Multiset > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >*, CGAL::Compare_events > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arr_construction_event > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > >, CGAL::Arr_construction_subcurve > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, CGAL::Arrangement_on_surface_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, CGAL::Arr_bounded_planar_topology_traits_2 > > >, std::vector > > > >, std::allocator > > > > > >, CGAL::Arr_consolidated_curve_data_traits_2 > > > >, int> >, Dcel> > > >, std::allocator >::Node*)from /Volumes/RAID/code/XPTools_refactoring/build/SceneryTools.build/Release/MeshTool.build/Objects-normal/ppc/MeshTool_Create.o
Are we having fun yet?