// Copyright (c) 1997-2002 Max-Planck-Institute Saarbruecken (Germany). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // // $URL: https://github.com/CGAL/cgal/blob/v5.1/Nef_S2/include/CGAL/Nef_S2/SM_checker.h $ // $Id: SM_checker.h 0779373 2020-03-26T13:31:46+01:00 Sébastien Loriot // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // // Author(s) : Michael Seel #ifndef CGAL_SM_CHECKER_H #define CGAL_SM_CHECKER_H #include #include #include #define CGAL_USING(t) typedef typename Base::t t namespace CGAL { /*{\Moptions outfile=SM_checker.man }*/ /*{\Manpage {SM_checker}{PMCDEC,GEOM}{Plane map checking}{}}*/ /*{\Mdefinition An instance |\Mvar| of the data type |\Mname| is a decorator to check the structure of a plane map. It is generic with respect to two template concepts. |PMCDEC| has to be a decorator model of our |SM_const_decorator| concept. |GEOM| has to be a model of our geometry kernel concept.}*/ /*{\Mgeneralization SM_const_decorator}*/ template class SM_checker : public PMCDEC { typedef PMCDEC Base; const GEOM& K; public: /*{\Mtypes 3}*/ typedef PMCDEC SM_const_decorator; /*{\Mtypemember equals |PMCDEC|. Add link to PMCDEC concept.}*/ typedef typename PMCDEC::Plane_map Plane_map; /*{\Mtypemember equals |PMCDEC::Plane_map|, the underlying plane map type.}*/ typedef GEOM Geometry; /*{\Mtypemember equals |GEOM|. Add link to GEOM concept.\\ \precond |Geometry::Point_2| equals |Plane_map::Point|. }*/ typedef typename GEOM::Point_2 Point; typedef typename GEOM::Direction_2 Direction; CGAL_USING(Vertex_const_handle); CGAL_USING(SEdge_handle); CGAL_USING(Vertex_const_iterator); CGAL_USING(Halfedge_const_iterator); CGAL_USING(Halfedge_around_vertex_const_circulator); CGAL_USING(Halfedge_around_face_const_circulator); /*{\Mtext Iterators, handles, and circulators are inherited from |SM_const_decorator|.}*/ /*{\Mcreation 3}*/ SM_checker(Plane_map& P, const Geometry& k = Geometry()) : Base(P), K(k) {} /*{\Mcreate constructs a plane map checker working on |P| with geometric predicates used from |k|.}*/ SM_checker(const Base& D, const Geometry& k = Geometry()) : Base(D), K(k) {} /*{\Moperations 2 }*/ Direction direction(Halfedge_const_handle e) const { return K.construct_direction( point(source(e)),point(target(e))); } bool is_forward(Halfedge_const_handle e) const { return K.compare_xy(point(source(e)),point(target(e)))<0; } void check_order_preserving_embedding(Vertex_const_handle v) const; /*{\Mop checks if the embedding of the targets of the edges in the adjacency list |A(v)| is counter-clockwise order-preserving with respect to the order of the edges in |A(v)|.}*/ void check_order_preserving_embedding() const; /*{\Mop checks if the embedding of all vertices of |P| is counter-clockwise order-preserving with respect to the adjacency list ordering of all vertices.}*/ void check_forward_prefix_condition(Vertex_const_handle v) const; /*{\Mop checks the forward prefix property of the adjacency list of |v|.}*/ Halfedge_const_iterator check_boundary_is_clockwise_weakly_polygon() const; /*{\Mop checks if the outer face cycle of |P| is a clockwise weakly polygon and returns a halfedge on the boundary. \precond |P| is a connected graph. }*/ void check_is_triangulation() const; /*{\Mop checks if |P| is a triangulation.}*/ }; // SM_checker template void SM_checker:: check_order_preserving_embedding(Vertex_const_handle v) const { std::ostrstream error_status; CGAL::set_pretty_mode ( error_status ); Halfedge_const_handle ef = first_out_edge(v) ,e=ef,en,enn; error_status << "check_order_preserving_embedding\n"; error_status << "vertex " << PV(v) << endl; if ( e != Halfedge_const_handle() ) { while ( true ) { en = cyclic_adj_succ(e); enn = cyclic_adj_succ(en); if (en == ef) break; error_status << " -> " << point(target(e)); error_status << " " << point(target(en)) << " "; error_status << " " << point(target(enn)) << endl; if ( !K.strictly_ordered_ccw(direction(e),direction(en), direction(enn)) || !K.strictly_ordered_ccw(direction(e),direction(en), direction(ef)) ) { error_status << "ccw order violate!" << endl << '\0'; CGAL_error_msg(error_status.str()); } e = en; } } error_status.freeze(0); } template void SM_checker:: check_forward_prefix_condition(Vertex_const_handle v) const { Halfedge_const_handle ef = first_out_edge(v); if ( ef == Halfedge_const_handle() ) return; Halfedge_const_handle el = cyclic_adj_pred(ef); bool is_left_turn = K.left_turn(point(v), point(target(ef)), point(target(el))); bool el_forward = is_forward(el); bool ef_forward = is_forward(ef); CGAL_assertion_msg( (ef == el || ef_forward && !el_forward || ef_forward && el_forward && is_left_turn || !ef_forward && !el_forward && is_left_turn) , "check_forward_prefix_condition: first backward, last forward\n"); } template void SM_checker:: check_order_preserving_embedding() const { Vertex_const_iterator vit; for (vit = vertices_begin(); vit != vertices_end(); ++vit) { check_order_preserving_embedding(vit); check_forward_prefix_condition(vit); } } template typename SM_checker::Halfedge_const_iterator SM_checker:: check_boundary_is_clockwise_weakly_polygon() const { Vertex_const_iterator vit, v_min; for (vit = v_min = vertices_begin() ; vit != vertices_end(); ++vit) if ( K.compare_xy(point(vit), point(v_min))<0 ) v_min = vit; CGAL_assertion_msg(!is_isolated(v_min),"Minimal vertex not connected."); Sphere_point p_min = point(v_min); // determine boundary edge incident to v_min: Halfedge_const_handle e_boundary_at_v_min = first_out_edge(v_min); // all out edges are forward oriented due to minimality Halfedge_around_vertex_const_circulator hvit(e_boundary_at_v_min), hend(hvit); do { --hvit; Sphere_point p1 = point(target(e_boundary_at_v_min)); Sphere_point p2 = point(target(hvit)); if ( K.orientation(p_min,p1,p2) > 0 ) { // left_turn e_boundary_at_v_min = hvit; break; } } while (hvit != hend); // now e_boundary_at_v_min is highest starting edge in bundle!! int winding_around_globally=0; Halfedge_around_face_const_circulator hfit(e_boundary_at_v_min),hstart(hfit); Halfedge_const_handle e_prev = next(e_boundary_at_v_min); /* we run counterclockwise around the outer face cycle and allow only position where the direction vector of an edge gets smaller again */ Direction d_prev = direction(e_prev); CGAL_For_all_backwards(hstart,hfit) { Direction d_curr = direction(hfit); if ( d_curr < d_prev ) ++winding_around_globally; d_prev = d_curr; } CGAL_assertion(winding_around_globally == 1); return e_boundary_at_v_min; } template void SM_checker:: check_is_triangulation() const { Halfedge_const_iterator eb; check_integrity_and_topological_planarity(false); CGAL_assertion(number_of_connected_components() == 1); check_order_preserving_embedding(); eb = check_boundary_is_clockwise_weakly_polygon(); CGAL::Hash_map< Halfedge_const_iterator, bool> on_boundary(false); Halfedge_around_face_const_circulator hit(eb), hend(hit); std::ostrstream error_status; CGAL::set_pretty_mode ( error_status ); error_status << "check_is_triangulation\n"; error_status << "on boundary:\n"; CGAL_For_all(hit,hend) { error_status << " " << PE(hit) << endl; on_boundary[hit]=true; } Halfedge_const_iterator eit; for( eit = halfedges_begin(); eit != halfedges_end(); ++eit) { if (on_boundary[eit]) continue; hit = hend = eit; int edges_in_face_cycle=0; CGAL_For_all(hit,hend) { error_status << PE(hit); ++edges_in_face_cycle; } CGAL_assertion_msg(edges_in_face_cycle==3,error_status.str()); } error_status.freeze(0); } /*\subsection{Plane map input and output} The input and output is mainly triggered by an IO Decorator which has the control over the IO format and does some basic parsing when reading input.*/ } //namespace CGAL #undef CGAL_USING #endif // CGAL_SM_CHECKER_H