Programming Triangulations: Triangulation Template Library TTL INF-MAT5370, Øyvind Hjelle Goal: Implementation of a collection of generic algorithms for triangulation that are independent and clearly separated from the underlying data structure. Using G-maps: dart algebra and -iterators (involutions) to model the triangulation at an abstract level. Implementation (in C ) by generic programming in the spirit of STL (The Standard Template Library) 1 The half-edge data structure revisited Node 1 (next edge in triangle) 1 (twin edge) 1 HalfEdge 1 Triangle N Triangulation Classes: Node, HalfEdge, Triangle, Triangulation class Node: geometry x, y, z . class HalfEdge: pointer to source node, “next edge in face” and “twin edge”. class Triangle: pointer to one of its half-edges (the triangle’s “leading edge”) class Triangulation: std::list Triangle Typical member functions: HalfEdge::getNextEdgeInFace ” ” HalfEdge::getTwinEdge ” ” 0 2 1 0 d d 2 !" # $ " # $ % " # !" # $ " # & ' ( #" # !" # )(( " # & ' * +% !" # $ # !" # $ # $ # * - ( ( % )(( " # ( )(( #" # ! $ " # & ' !" # $ " # !" # $ # +% " # " # & ' #" # #" # # % % " # " # & ' # # " # " # & ' # , ( ( #" # #" # 3 + # . + !" # $/ #" # # 0 !" # $ % " # !" # $ . . +% " !" # + # #+ $/ $/ # # !" # ! . . $/ $/ ! # Note: This design is just for convenience in the presentation and not necessarily the actual design. Memory-handling (garbage collection) is also necessary. 4 Generic Design of TTL and the Adaptation Layer Application Algorithms on triangulations Fixed Application Data Structure (e.g. half-edge data structure) Static design Application Triangulation Template Library (TTL) Generic algorithms based on G-maps, and simple geometric and topological operations. Topological elements: Darts D = { d } Iterators α i : D → D, i = 0,1,2. namespaces ttl and ttl_util Adaptation layer Interface to application data structure. • Dart class with α-iterators • Traits class with geometric and topological operations, and type definitions. Application Data Structure Generic design of TTL 5 Adaptation layer Implements a set of requirements specified by function templates in TTL used by the application: Dart class (or struct) with -iterators Traits class (or struct) with basic geometric predicates, and geometric modifiers. namespace ttl and ttl_util: #include ttl.h ttl::locateTriangle( parameters ) or using namespace ttl; locateTriangle( parameters ) 6 TTL is totally independent of the underlying data structure in contrast to the a static non-generic design with algorithms working directly on a specific data structure; see figure. 7 Topological Queries and the Dart class Definition: Topological queries consists of functions that act on the topology but do not change the topology (or the geometry) of triangulations. Implemented generically using darts and -iterators in G-maps. Example: Let d v i , e j , t k . Is e j a boundary edge? bool isBoundaryEdge d : if d d return TRUE 2 // Fixed point! else return FALSE 8 The behaviour of isBoundaryEdge d is not affected by how the incoming dart is implemented Use a C function template ! namespace ttl { template class DartType bool isBoundaryEdge(const DartType& d) { DartType dart_iter d; if (dart_iter.alpha2() d) return true; else return false; } }; Requires a Dart class: class Dart { Dart (const Dart& d); // Copy constructor operator (const Dart& d) {...}; Dart& alpha2() {...} // 2 d ... }; 9 0 !" # $ # 11 2 0 0 ! # 0 # 4 0 11 # 3 ! 4 $ 5 0 5 6 0 5 7 4 $ 5 ! # # 8/# " # & ' # # 8/# " # & ' 8/# " # & ' 4 $ 5 0 5 9 : ! 11 25 11 % ! # # 5 5 # 8/# +% " # # 8/# +% 4 &! 5 5 11 25 : ! " # $ 5 Dart class for half-edge data structure. 10 Other topological queries in TTL: bool isBoundaryTriangle(const DartType& d) bool isBoundaryNode(const DartType& d); int getDegreeOfNode(const DartType& d); void getAdjacentTriangles(const DartType& d, DartType& dt1, DartType& dt2, DartType& dt3); Note: Only DartType in interface! 11 ... ... d start d3 d2 d 2 = α1 α 0 ( d start ) d 3 = α1 α 2 α1 α 2 ( d 2 ) Iteration at the boundary of a triangulation. void getBoundary(const DartType& d, list DartType & boundary); Alternative: Implement a boundary iterator (a “circulator”) and return this type from getBoundary !!! Exercise! 12 “Circulators”, example with Orbit2 Circular sequences (contrary to STL). template class DartType class Orbit2_Iterator { DartType d_; public: Orbit2_Iterator(const DartType& d); ... DartType& operator*() {return d_;} Orbit2& operator () {...; return *this;} Orbit2& operator- -() {...; return *this;} }; Exercise: Implement class Orbit0 and class Orbit1 How should fixed point be handled, ( 2 d d ?) 13 Geometric Queries and the Traits Class v i, (a) (b) d ′ = α1 α 0 (d ) H (d ) v i, ej ej tk tk d d vi H (d ) vi H (d ' ) (b): Intersection of half-planes PROBLEM. Given a point p d v i , e j , t k . Is p R 2 and a ccw dart tk ? Solution: La H d be the half-plane “left of” d containing v i and the node of 0 d ; Figure (a). p tk p Hd H 1 0 d H 0 1 d 14 The query p H d can be implemented by evaluating the sign of a determinant. Let v i x1, y1 , vi x2, y2 , p x 3 , y 3 and x1 y1 1 |D| x2 y2 1 x1 x3 y2 y3 x2 x3 y1 x3 y3 1 p Hd |D| 0 Numerical calculations on the application side, in the adaptation layer, in a traits class! class MyTraits { bool inLeftHalfPlane(...) ... }; 15 Definition. A traits class is a C class that encapsulates a set of types and functions necessary for template classes and template functions to manipulate objects of types for which they are instantiated. Thus, our traits class is an ordinary C class where the application programmer implements functions required by function templates in TTL for interfacing the actual data structure. implementation of p C t k , with d vi, ej, tk : namespace ttl { template class TraitsType, class PointType, class DartType bool inTriangle(const PointType& p, DartType& d) { for (int i 0; i 3; i // d is CCW ){ if (!TraitsType::inLeftHalfPlane(p, d)) return false; d.alpha0().alpha1(); } return true; } }; 16 struct MyTraits { // (or a class) static bool inLeftHalfPlane(const MyPoint& p, const MyDart& d) {...} ... }; MyPoint p; MyDart d; bool in_triangle ttl::inTriangle MyTraits (p,d); ... “Standard functions” like inLeftHalfPlane are also in: namespace ttl_util { bool inLeftHalfPlaneFast(const MyPoint& p, const MyDart& d) {...} double scalarProduct(const DartType& d1, ...); double crossProduct(const DartType& d1, ...); ... }; 17 Point location in a triangulation PROBLEM. Given a point p and a triangle t start in P. t ; or decide if Find the triangle t in P such that p p is outside . Fast algorithms for Problem important in, incremental Delaunay triangulation algorithms, when evaluating f P x, y . Solution: Given d Assume 2 db v i , e i , t start oriented ccw in t start . P has convex boundary: d b and p H db p is outside P. 18 Algorithm p d Algorithm: Dart locateTriangle (Point p, Dart d, bool found) 1. d start : d 2. if p H d 3. d : 1 0 d // next edge ccw. in t 4. if d d start 5. FOUND : true, RETURN // inside triangle 6. else // try to move to the adjacent triangle 7. if 2 d d // check if on boundary 8. FOUND : false, RETURN // outside 9. d start : 0 2 d i 10. d : 1 2 d // next edge ccw. in adj. t 11. GOTO Step 2 19 namespace ttl { template class TraitsType, class PointType, class DartType bool locateFace(PointType& point, DartType& dart_iter) { DartType dart_start dart_iter; DartType dart_prev; for (;;) { // ”endless loop” if (TraitsType::inLeftHalfPlane(dart_iter, point)) { dart_iter.alpha0().alpha1(); if (dart_iter dart_start) return true; // left to all edges in face } else { // try to move to the adjacent triangle dart_prev dart_iter; dart_iter.alpha2(); if (dart_iter dart_prev) return false; // iteration to outside boundary dart_start dart_iter; dart_start.alpha0(); dart_iter.alpha1(); // avoid twice on same edge and ccw in next } } // end for loop } }; 20 Geometric and Topological Modifiers Assumpton: Geometric embedding information only. 3D position of nodes So far TTL has no modifiers that change positions of nodes. Topological modifiers; three kinds: 1. Add nodes, edges and triangles. 2. Remove nodes, edges and triangles. 3. Modifiers that preserve |V|, |E| and |T| edge-swapping, e.g. inserting constraints. (Recall also: all possible triangulations of a set of points can be reached by a sequence of edge-swaps) 21 Swapping and sewing: d 6′ d4 d 3′ v4 v2 d6 d3 d 4′ ei d5 v1 d1 d2 ei′ d 2′ d1′ v3 d 5′ Sewing-operations when Definition: Two darts d i and d j are said to be k-sewed, or in a G-map if k d i d j (and k d j d i ). k -sewed, Swapping e to e involves six sewings: sew1 d i , d i , i 1, 6. Sewings are not used in TTL as atomic operations (like -iterators), thus: 22 Topological modifiers in TTL that need swapping require a function swapEdge(DartType& d) in the traits class of the adaptation layer that swaps an edge associated with the given dart: Algorithm: MyTraits::swapEdge(DartType& d) // Triangle “left of” d d2 2 d1 , d5 1 d2 , d1 d6 0 d2 , d3 1 d6 . 0 d5 , d2 1 d1 , // Triangle right of d d5 1 d1 , d4 d4 1 d6 1 d4 , d6 0 d3 , 0 d5 , d3 // Make the sewings for (i 1, , 6) sew1(d i , d i 23 Other topological modifiers in ttl: namespace ttl { insertNode(DartType& d, NodeType& n) // Requires: // MyTraits::splitTriangle(DartType& d, NodeType& n); ttl::removeNode(DartType& d) // Requires: // MyTraits::reverse_splitTriangle(DartType& d, NodeType& n); }; 24 Generic Delaunay Triangulation Incremental Delaunay triangulation: (a) (b) × × × × × × × × × × × × × × × × (a): × × 2 (b): × node1 × × × × × × × × × × × × × 3 Triangulation::initTwoEnclosingTriangles( for (i 1, ...) ttl::insertNode(dart, node i ) 25 Step 1 Locate triangle t in N that contains p. Step 2 Split t into three triangles and obtain N 1 . Step 3 Swapping procedure to obtain Delaunay triangulation (a) N 1. (b) d1,1 d1,2 d1 d3,2 d3,1 p d3 d0 d2 d2,2 d2,1 After Step 2 Algorithm: ttl::insertNode(Point p, Dart d 1. 2. 3. 4. 5. 6. 7. 8. dt ttl::locateTriangle(p, d, found) d0 app::splitTriangle(d t , p) // Traits d1 2 1 0 1 2 1 d0 d2 2 1 0 1 d0 d3 2 1 2 0 d0 ttl::recSwapDelaunay(d 1 ) ttl::recSwapDelaunay(d 2 ) ttl::recSwapDelaunay(d 3 ) 26 The swapping procedure with dart algebra: Algorithm: recSwapDelaunay(Dart d i ) 1. if (ttl::circumcircleTest(d i ) TRUE) 2. RETURN // Do not swap 3. d i,1 2 1 di 4. d i,2 2 0 1 0 di 5. app::swapEdge(d i ) // In Traits 6. ttl::recSwapDelaunay(d i,1 ) // recursion 7. ttl::recSwapDelaunay(d i,2 ) // recursion (a) (b) d1,1 d1,1 d1,2 d1,2 d1 d3,2 d3,1 d3,2 p d0 d3 (c) d2 d2,1 d0 d2,2 (d) d1,1 d3,2 p d3 d3 d3,1 d2,2 d3,2 p d0 d2 d2,1 d1,1 p d0 d3,1 d3,1 d2,2 d2 d2,1 d2,2 (e) d2 d2,1 d1,1 p d0 d3,1 d2,2 d2 d2,1 27 (a) (b) d1,1 d1,1 d1,2 d1,2 d1 d3,2 d3,1 d3,2 p d0 d3 d3,1 d2,2 (c) d2 d0 d2,2 (d) d2 d2,1 d1,1 d3,2 p d3 d3 d2,1 d1,1 d3,2 p d0 p d0 d3,1 d3,1 d2,2 d2 d2,1 d2,2 (e) d2 d2,1 d1,1 p d0 d3,1 d2,2 d2 d2,1 Algorithm: recSwapDelaunay(Dart d i ) Recall Theorem: On termination, all edges are locally optimal and N 1 is a Delaunay. 28 The circumcircle test d3 β d d4 ei d2 α d1 Circumcircle test Swap iff sin cos Interpret the darts d i , i space. 0, 1, 2, 3, 4 as unit vectors in 3D sin d1 d2 e3 sin d3 d4 e3 cos where e 3 cos sin cos 0, 0, 1 . d1 d2 d3 d4, 29 Assume: app::crossProduct(d i , d j ) app::scalarProduct(d i , d j ) di dj e3 d1 d2 Algorithm: bool ttl::circumcircleTest(Dart d) 1. d 1 1 0 1 2 d 2. d 2 0 1 2 d 3. d 3 0 1 d 4. d 4 1 0 1 d 5. sin app::crossProduct(d 1 , d 2 ) 6. sin app::crossProduct(d 3 , d 4 ) 7. cos app::scalarProduct(d 1 , d 2 ) 8. cos app::scalarProduct(d 3 , d 4 ) 9. if sin cos cos sin 0 10. return FALSE // swap the edge 11. else 12. return TRUE Notes on TTL: Step 9 is replaced with more robust tests Cycling and infinite loops are handled Default implementations: ttl_util::crossProduct and ttl_util::scalarProduct 30 (a) (b) × × × × × × × × × × × × × × × × (c) (d) (b): P B Removing artificial boundary: namespace ttl { removeBoundaryNode(DartType& d){...} // Requires: app::removeBoundaryTriangle(d) }; 31 Some final remarks: Sort point set P lexicographically (cf. divide-and-conquer algorithm). Use std::sort in STL and provide function object (a.k.a. functor) “compare”. Rule on semantics: darts outside a quadrilateral are not changed when swapping the diagonal. Cf. darts d 1,1 and d 1,2 in recSwapDelaunay. ttl::insertConstraint (no additional requirements) Clean interfaces Compact algorithms Easy to read and maintain Probably no significant loss of efficiency (more efficient than class inheritance and dynamic binding) -iterators as C inline functions (in class Dart) 32