// Copyright (c) 2019 CNRS and LIRIS' Establishments (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org); you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 3 of the License, // or (at your option) any later version. // // Licensees holding a valid commercial license may use this file in // accordance with the commercial license agreement provided with the software. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. // // $URL$ // $Id$ // SPDX-License-Identifier: LGPL-3.0+ // // Author(s) : Guillaume Damiand // #ifndef CGAL_HOMOTOPY_TESTER_H #define CGAL_HOMOTOPY_TESTER_H 1 // Should be defined before to include Path_on_surface_with_rle.h // If nothing is defined, use V1 // #define CGAL_PWRLE_TURN_V1 // Compute turns by turning (method of CMap) // #define CGAL_PWRLE_TURN_V2 // Compute turns by using an id of darts, given by an hash-table (built and given by Homotopy_tester) #define CGAL_PWRLE_TURN_V3 // Compute turns by using an id of darts, associated in Info of Darts (build by Homotopy_tester) #include #include #include #include #include #include #include #include #include #include #include namespace CGAL { struct CMap_for_homotopy_tester_items { template struct Dart_wrapper { #ifdef CGAL_PWRLE_TURN_V3 typedef std::size_t Dart_info; #endif // CGAL_PWRLE_TURN_V3 typedef CGAL::cpp11::tuple<> Attributes; }; }; typedef CGAL::Combinatorial_map<2, CMap_for_homotopy_tester_items> CMap_for_homotopy_tester; template class Homotopy_tester { public: typedef typename CMap_for_homotopy_tester::Dart_handle Dart_handle; typedef typename CMap_for_homotopy_tester::Dart_const_handle Dart_const_handle; typedef CGAL::Union_find UFTree; typedef typename UFTree::handle UFTree_handle; typedef typename Get_map::type Map; typedef CGAL::Union_find UFTree2; typedef typename UFTree2::handle UFTree_handle2; // Associate each dart of the original map, not removed, a pair of darts in the // reduced map. typedef boost::unordered_map > TPaths; #ifdef CGAL_PWRLE_TURN_V2 typedef boost::unordered_map TDartIds; #endif //CGAL_PWRLE_TURN_V2 Homotopy_tester(const Mesh& amap, bool display_time=false) : m_original_map(amap) { if (!m_map.is_without_boundary(1)) { std::cerr<<"ERROR: the given amap has 1-boundaries; " <<"such a surface is not possible to process here." < origin_to_copy; // The mapping between darts of the copy into darts of the original map. boost::unordered_map copy_to_origin; // We copy the original map, while keeping mappings between darts. // m_map.copy(m_original_map, &origin_to_copy, ©_to_origin); if (display_time) { t2.stop(); std::cout<<"[TIME] Copy map: "<(dh1); std::size_t id=0; for(; dh1!=NULL; dh1=(dh1==dh2?NULL:dh2)) // We have two vertices to process { Dart_handle cur_dh=dh1; do { #ifdef CGAL_PWRLE_TURN_V2 m_dart_ids[cur_dh]=id++; #else // CGAL_PWRLE_TURN_V2 // Here we use CGAL_PWRLE_TURN_V3 m_map.info(cur_dh)=id++; #endif // CGAL_PWRLE_TURN_V2 cur_dh=m_map.template beta<2, 1>(cur_dh); } while(cur_dh!=dh1); } if (display_time) { t2.stop(); std::cout<<"[TIME] Label darts: "<(dh2))<(dh2); } while(dh2!=it); } } m_map.free_mark(marktemp); m_map.display_darts(std::cout); #endif if (display_time) { t.stop(); std::cout<<"[TIME] Total time for computation of reduced map: " <& p, bool display_time=false) const { if (p.is_empty()) { return true; } if (!p.is_closed()) { std::cerr<<"Error: is_contractible requires a closed path."< pt=transform_original_path_into_quad_surface_for_torus(p); int a, b; count_edges_of_path_on_torus(pt, a, b); res=(a==0 && b==0); } else { Path_on_surface_with_rle pt=transform_original_path_into_quad_surface_with_rle(p); pt.canonize(); res=pt.is_empty(); } if (display_time) { t.stop(); std::cout<<"[TIME] is_contractible: "<& p1, const Path_on_surface& p2, bool display_time=false) const { if (p1.is_empty() && p2.is_empty()) { return true; } if ((!p1.is_empty() && !p1.is_closed()) || (!p2.is_empty() && !p2.is_closed())) { std::cerr<<"Error: are_freely_homotopic requires two closed paths." < pt1=transform_original_path_into_quad_surface_for_torus(p1); Path_on_surface pt2=transform_original_path_into_quad_surface_for_torus(p2); int a1, a2, b1, b2; count_edges_of_path_on_torus(pt1, a1, b1); count_edges_of_path_on_torus(pt2, a2, b2); res=(a1==a2 && b1==b2); } else { Path_on_surface_with_rle pt1=transform_original_path_into_quad_surface_with_rle(p1); Path_on_surface_with_rle pt2=transform_original_path_into_quad_surface_with_rle(p2); pt1.canonize(); pt2.canonize(); res=(pt1==pt2); // Do here to be counted in the computation time #ifdef CGAL_TRACE_PATH_TESTS std::cout<<"Length of reduced paths: "<& p1, const Path_on_surface& p2, bool display_time=false) const { if (p1.is_empty() && p2.is_empty()) { return true; } if (p1.is_empty() || p2.is_empty()) { return false; } if (!m_original_map.template belong_to_same_cell<0>(p1.front(), p2.front()) || !m_original_map.template belong_to_same_cell<0>(m_original_map.other_extremity(p1.back()), m_original_map.other_extremity(p2.back()))) { std::cerr<<"Error: are_base_point_homotopic requires two paths that" <<" share the same vertices as extremities."< path=p1; Path_on_surface path2=p2; path2.reverse(); path+=path2; bool res=is_contractible(path); if (display_time) { t.stop(); std::cout<<"[TIME] are_base_point_homotopic: "<& path, int& a, int& b) const { CGAL_assertion(m_map.number_of_darts()==4); Dart_const_handle dha=m_map.darts().begin(); Dart_const_handle dhb=m_map.template beta<1>(dha); a=0; b=0; for (std::size_t i=0; i(dha)) { --a; } else if (path[i]==dhb) { ++b; } else if (path[i]==m_map.template beta<2>(dhb)) { --b; } } } Path_on_surface transform_original_path_into_quad_surface_for_torus(const Path_on_surface& path) const { CGAL_assertion(m_map.number_of_darts()==4); Path_on_surface res(m_map); if (path.is_empty()) return res; Dart_const_handle cur; for (std::size_t i=0; i(cur); } } } res.update_is_closed(); CGAL_assertion(res.is_empty() || res.is_closed()); CGAL_assertion(res.is_valid()); return res; } Path_on_surface_with_rle transform_original_path_into_quad_surface_with_rle (const Path_on_surface& path) const { Path_on_surface_with_rle res(m_map #ifdef CGAL_PWRLE_TURN_V2 , m_dart_ids #endif //CGAL_PWRLE_TURN_V2 ); if (path.is_empty()) return res; for (std::size_t i=0; i& vertices) { uftrees.clear(); vertices.clear(); typename Map::size_type treated=m_original_map.get_new_mark(); for (typename Map::Dart_range::const_iterator it=m_original_map.darts().begin(), itend=m_original_map.darts().end(); it!=itend; ++it) { if (!m_original_map.is_marked(it, treated)) { UFTree_handle2 newuf=uftrees.make_set(it); for (typename Map:: template Dart_of_cell_range<0>::const_iterator itv=m_original_map.template darts_of_cell<0>(it).begin(), itvend=m_original_map.template darts_of_cell<0>(it).end(); itv!=itvend; ++itv) { vertices[itv]=newuf; m_original_map.mark(itv, treated); } } } m_original_map.free_mark(treated); } void initialize_faces(UFTree& uftrees, boost::unordered_map& faces) { uftrees.clear(); faces.clear(); typename CMap_for_homotopy_tester::size_type treated=m_map.get_new_mark(); for (typename CMap_for_homotopy_tester::Dart_range::iterator it=m_map.darts().begin(), itend=m_map.darts().end(); it!=itend; ++it) { if (!m_map.is_marked(it, treated)) { UFTree_handle newuf=uftrees.make_set(it); Dart_handle cur=it; do { faces[cur]=newuf; m_map.mark(cur, treated); cur=m_map.template beta<1>(cur); } while (cur!=it); } } m_map.free_mark(treated); } UFTree_handle get_uftree(const UFTree& uftrees, const boost::unordered_map& mapdhtouf, Dart_const_handle dh) const { CGAL_assertion(dh!=NULL); CGAL_assertion(mapdhtouf.find(dh)!=mapdhtouf.end()); return uftrees.find(mapdhtouf.find(dh)->second); } UFTree_handle2 get_uftree2(const UFTree2& uftrees, const boost::unordered_map& mapdhtouf, typename Map::Dart_const_handle dh) const { // CGAL_assertion(dh!=NULL); CGAL_assertion(mapdhtouf.find(dh)!=mapdhtouf.end()); return uftrees.find(mapdhtouf.find(dh)->second); } /// Mark the edge containing adart in the original map. void mark_edge(const Map& amap, typename Map::Dart_const_handle adart, std::size_t amark) { amap.mark(amap.template beta<2>(adart), amark); amap.mark(adart, amark); } /// Erase the edge given by adart (which belongs to the map m_map) from the /// associative array copy_to_origin, and erase the corresponding edge /// (which belongs to the map m_original_map) from the array origin_to_copy void erase_edge_from_associative_arrays (Dart_handle adart, boost::unordered_map& origin_to_copy, boost::unordered_map& copy_to_origin) { origin_to_copy.erase(m_original_map.template beta<2> (copy_to_origin[adart])); origin_to_copy.erase(copy_to_origin[adart]); copy_to_origin.erase(m_map.template beta<2>(adart)); copy_to_origin.erase(adart); } /// Step 1) Transform m_map into an equivalent surface having only one /// vertex. All edges contracted during this step belong to the spanning /// tree T, and thus corresponding edges in m_original_map are marked. void surface_simplification_in_one_vertex (boost::unordered_map& origin_to_copy, boost::unordered_map& copy_to_origin) { UFTree2 uftrees; // uftree of vertices; one tree for each vertex, // contains one dart of the vertex boost::unordered_map vertices; initialize_vertices(uftrees, vertices); /* m_map.set_automatic_attributes_management(false); for (typename CMap_for_homotopy_tester::Dart_range::iterator it=m_map.darts().begin(), itend=m_map.darts().end(); it!=itend; ++it) { if (m_map.is_dart_used(it) && get_uftree(uftrees, vertices, it)!= get_uftree(uftrees, vertices, m_map.template beta<2>(it))) { mark_edge(m_original_map, copy_to_origin[it], m_mark_T); erase_edge_from_associative_arrays(it, origin_to_copy, copy_to_origin); uftrees.unify_sets(get_uftree(uftrees, vertices, it), get_uftree(uftrees, vertices, m_map.template beta<2>(it))); //m_map.template contract_cell<1>(it); Dart_handle d1=it, d2=m_map.template beta<2>(it); m_map.template link_beta<1>(m_map.template beta<0>(d1), m_map.template beta<1>(d1)); m_map.template link_beta<1>(m_map.template beta<0>(d2), m_map.template beta<1>(d2)); m_map.erase_dart(d1); m_map.erase_dart(d2); } } m_map.set_automatic_attributes_management(true); */ /* New version that does not need to first copy the map before to simplify it */ Dart_handle d1, d2; for (typename Map::Dart_range::const_iterator it=m_original_map.darts().begin(), itend=m_original_map.darts().end(); it!=itend; ++it) { if (typename Map::Dart_const_handle(it)(it)) { if (get_uftree2(uftrees, vertices, it)!= get_uftree2(uftrees, vertices, m_original_map.template beta<2>(it))) { m_original_map.mark(m_original_map.template beta<2>(it), m_mark_T); m_original_map.mark(it, m_mark_T); uftrees.unify_sets(get_uftree2(uftrees, vertices, it), get_uftree2(uftrees, vertices, m_original_map.template beta<2>(it))); } else { d1=m_map.create_dart(); d2=m_map.create_dart(); m_map.template basic_link_beta_for_involution<2>(d1, d2); origin_to_copy[it]=d1; origin_to_copy[m_original_map.template beta<2>(it)]=d2; copy_to_origin[d1]=it; copy_to_origin[d2]=m_original_map.template beta<2>(it); } } } /// Now we only need to do the basic_link_beta_1 typename Map::Dart_const_handle dd1; for (typename Map::Dart_range::const_iterator it=m_original_map.darts().begin(), itend=m_original_map.darts().end(); it!=itend; ++it) { if (!m_original_map.is_marked(it, m_mark_T)) { dd1=m_original_map.template beta<1>(it); while(m_original_map.is_marked(dd1, m_mark_T)) { dd1=m_original_map.template beta<1>(dd1); } m_map.basic_link_beta_1(origin_to_copy[it], origin_to_copy[dd1]); } } } /// Step 2) Compute, for each edge of m_original_map not in the spanning /// tree T, the pair of darts of the edge in m_copy. This pair of edges /// will be updated later (in surface_simplification_in_one_face() and in /// surface_quadrangulate() ) void compute_length_two_paths (const boost::unordered_map& origin_to_copy) { paths.clear(); for (typename Map::Dart_range::const_iterator it=m_original_map.darts().begin(), itend=m_original_map.darts().end(); it!=itend; ++it) { if (!m_original_map.is_marked(it, m_mark_T)) { CGAL_assertion(!m_original_map.template is_free<2>(it)); if (typename Map::Dart_const_handle(it)(it)) { paths[it]=std::make_pair (origin_to_copy.at(it), m_map.template beta<2>(origin_to_copy.at(it))); CGAL_assertion(paths[it].first!=paths[it].second); CGAL_assertion(paths[it].first==m_map.template beta<2>(paths[it].second)); } } } #ifdef CGAL_TRACE_CMAP_TOOLS std::cout<<"Number of darts in paths: "<& origin_to_copy, boost::unordered_map& copy_to_origin) { UFTree uftrees; // uftree of faces; one tree for each face, // contains one dart of the face boost::unordered_map faces; initialize_faces(uftrees, faces); m_map.set_automatic_attributes_management(false); typename Map::size_type toremove=m_map.get_new_mark(); Dart_handle currentdart=NULL, oppositedart=NULL; for (typename CMap_for_homotopy_tester::Dart_range::iterator it=m_map.darts().begin(), itend=m_map.darts().end(); it!=itend; ++it) { currentdart=it; CGAL_assertion(!m_map.template is_free<2>(currentdart)); // TODO later, support opened surfaces oppositedart=m_map.template beta<2>(currentdart); if (currentdart(currentdart)!=oppositedart); CGAL_assertion(m_map.template beta<1>(currentdart)!=oppositedart); uftrees.unify_sets(get_uftree(uftrees, faces, currentdart), get_uftree(uftrees, faces, oppositedart)); m_map.mark(currentdart, toremove); m_map.mark(oppositedart, toremove); mark_edge(m_original_map, copy_to_origin[currentdart], m_mark_L); } } } /* m_map.display_characteristics(std::cout) << ", valid=" << m_map.is_valid() << std::endl; m_map.display_darts(std::cout)<(it); } } } m_map.set_automatic_attributes_management(true); m_map.free_mark(toremove); } /// Step 4) quadrangulate the surface. void surface_quadrangulate() { // Here the map has only one face and one vertex. typename Map::size_type oldedges=m_map.get_new_mark(); m_map.negate_mark(oldedges); // now all edges are marked // 1) We insert a vertex in the face. // New edges created by the operation are not marked. m_map.insert_cell_0_in_cell_2(m_map.darts().begin()); // m_map.display_darts(std::cout); // 2) We update the pair of darts // std::cout<<"************************************************"<& p=itp->second; //std::cout<<"Pair: "<(p.first); p.second=m_map.template beta<0>(p.second); //std::cout<<" -> "<(it); } } m_map.free_mark(oldedges); } /// Update all length two paths, before edge removal. Edges that will be /// removed are marked with toremove mark. void update_length_two_paths_before_edge_removals (typename Map::size_type toremove, const boost::unordered_map& copy_to_origin) { // std::cout<<"************************************************"<(it)) { // Surviving dart => belongs to the border of the face std::pair& p=paths[it]; Dart_handle initdart=m_map.darts().iterator_to (const_cast (*(p.first))); Dart_handle initdart2=m_map.template beta<2>(initdart); CGAL_assertion(initdart2==p.second); CGAL_assertion(!m_map.is_marked(initdart, toremove)); CGAL_assertion(!m_map.is_marked(initdart2, toremove)); // 1) We update the dart associated with p.second p.second=m_map.template beta<1>(initdart); while (m_map.is_marked(p.second, toremove)) { p.second=m_map.template beta<2, 1>(p.second); } // 2) We do the same loop, linking all the inner darts with p.second initdart=m_map.template beta<1>(initdart); while (m_map.is_marked(initdart, toremove)) { CGAL_assertion(copy_to_origin.count(initdart)==1); typename Map::Dart_const_handle d1=copy_to_origin.find(initdart)->second; typename Map::Dart_const_handle d2=m_original_map.template beta<2>(d1); if (d1(initdart); } // 3) We do the same loop but starting from initdart2 initdart2=m_map.template beta<1>(initdart2); Dart_handle enddart2=initdart2; while (m_map.is_marked(enddart2, toremove)) { enddart2=m_map.template beta<2, 1>(enddart2); } while (m_map.is_marked(initdart2, toremove)) { CGAL_assertion(copy_to_origin.count(initdart2)==1); typename Map::Dart_const_handle d1=copy_to_origin.find(initdart2)->second; typename Map::Dart_const_handle d2=m_original_map.template beta<2>(d1); if (d1(initdart2); } } } } /// @return true iff the edge containing adart is associated with a path. /// (used for debug purpose because we are suppose to be able to /// test this by using directly the mark m_mark_T). bool is_edge_has_path(typename Map::Dart_const_handle adart) const { typename Map::Dart_const_handle opposite=m_original_map.template beta<2>(adart); if (adart& get_pair_of_darts (typename Map::Dart_const_handle adart) { CGAL_assertion(!m_original_map.is_marked(adart, m_mark_T)); CGAL_assertion(is_edge_has_path(adart)); typename Map::Dart_const_handle opposite=m_original_map.template beta<2>(adart); if (adartsecond; } return paths.find(opposite)->second; } Dart_const_handle get_first_dart_of_the_path (typename Map::Dart_const_handle adart, bool withopposite=true) const { CGAL_assertion(!m_original_map.is_marked(adart, m_mark_T)); CGAL_assertion(is_edge_has_path(adart)); typename Map::Dart_const_handle opposite=m_original_map.template beta<2>(adart); if (adart& p=paths.find(adart)->second; return p.first; } const std::pair& p=paths.find(opposite)->second; return (withopposite?m_map.template beta<2>(p.second):p.second); } Dart_const_handle get_second_dart_of_the_path (typename Map::Dart_const_handle adart, bool withopposite=true) const { CGAL_assertion(!m_original_map.is_marked(adart, m_mark_T)); CGAL_assertion(is_edge_has_path(adart)); typename Map::Dart_const_handle opposite=m_original_map.template beta<2>(adart); if (adart& p=paths.find(adart)->second; return p.second; } const std::pair& p=paths.find(opposite)->second; return (withopposite?m_map.template beta<2>(p.first):p.first); } /// Test if paths are valid, i.e.: /// 1) all the darts of m_original_map that do not belong to T are /// associated with a pair of darts; /// 2) all the darts of the paths belong to m_map; /// 3) the origin of the second dart of the pair is the extremity of the /// first dart. /// 4) all the darts of m_map are not free (both for beta 1 and 2) /// 5) The two darts in a pair are different bool are_paths_valid() const { if (paths.empty()) { return true; } bool res=true; for (auto it=m_original_map.darts().begin(), itend=m_original_map.darts().end(); it!=itend; ++it) { if (!m_original_map.is_marked(it, m_mark_T)) { if (!is_edge_has_path(it)) { std::cout<<"ERROR: an edge that does not belong to the spanning " <<"tree T has no associated path."<second.first)) { std::cout<<"ERROR: first dart in paths does not exist anymore in m_map." <second.first)) { std::cout<<"ERROR: first dart in paths does not belong to m_map." <second.second)) { std::cout<<"ERROR: second dart in paths does not exist anymore in m_map." <second.second)) { std::cout<<"ERROR: second dart in paths does not belong to m_map." <second.first==it->second.second) { std::cout<<"ERROR: two darts in the same pair are equal." <(m_map, dd1, d2)) { std::cout<<"ERROR: the two darts in a path are not consecutive." <::storage_type m_original_map; // The original map CMap_for_homotopy_tester m_map; /// the transformed map TPaths paths; /// Pair of edges associated with each edge of m_original_map /// (except the edges that belong to the spanning tree T). std::size_t m_mark_T; /// mark each edge of m_original_map that belong to the spanning tree T std::size_t m_mark_L; /// mark each edge of m_original_map that belong to the dual spanning tree L #ifdef CGAL_PWRLE_TURN_V2 TDartIds m_dart_ids; /// Ids of each dart of the transformed map, between 0 and n-1 (n being the number of darts) /// so that darts between 0...(n/2)-1 belong to the same vertex and /// d1=beta<1, 2>(d0), d2=beta<1, 2>(d1)... /// The same for darts between n/2...n-1 for the second vertex /// Thanks to these ids, we can compute in constant time the positive and /// negative turns between two consecutive darts #endif // CGAL_PWRLE_TURN_V2 }; } // namespace CGAL #endif // CGAL_HOMOTOPY_TESTER_H // // EOF //