diff --git a/src/OpenMesh/Core/Mesh/PolyConnectivity.hh b/src/OpenMesh/Core/Mesh/PolyConnectivity.hh index bdce69b5..d8adcd15 100644 --- a/src/OpenMesh/Core/Mesh/PolyConnectivity.hh +++ b/src/OpenMesh/Core/Mesh/PolyConnectivity.hh @@ -45,6 +45,7 @@ #define OPENMESH_POLYCONNECTIVITY_HH #include +#include namespace OpenMesh { @@ -64,6 +65,42 @@ namespace Iterators class GenericCirculatorT; } +template +class EntityRange; + +template< + typename CONTAINER_T, + typename ITER_T, + ITER_T (CONTAINER_T::*begin_fn)() const, + ITER_T (CONTAINER_T::*end_fn)() const> +struct RangeTraitT +{ + using CONTAINER_TYPE = CONTAINER_T; + using ITER_TYPE = ITER_T; + static ITER_TYPE begin(const CONTAINER_TYPE& _container) { return (_container.*begin_fn)(); } + static ITER_TYPE end(const CONTAINER_TYPE& _container) { return (_container.*end_fn)(); } +}; + + +template< + typename CONTAINER_T, + typename ITER_T, + typename CENTER_ENTITY_T, + typename TO_ENTITY_T, + ITER_T (CONTAINER_T::*begin_fn)(CENTER_ENTITY_T) const, + ITER_T (CONTAINER_T::*end_fn)(CENTER_ENTITY_T) const> +struct CirculatorRangeTraitT +{ + using CONTAINER_TYPE = CONTAINER_T; + using ITER_TYPE = ITER_T; + using CENTER_ENTITY_TYPE = CENTER_ENTITY_T; + using TO_ENTITYE_TYPE = TO_ENTITY_T; + static ITER_TYPE begin(const CONTAINER_TYPE& _container, CENTER_ENTITY_TYPE _ce) { return (_container.*begin_fn)(_ce); } + static ITER_TYPE end(const CONTAINER_TYPE& _container, CENTER_ENTITY_TYPE _ce) { return (_container.*end_fn)(_ce); } +}; + + + /** \brief Connectivity Class for polygonal meshes */ class OPENMESHDLLEXPORT PolyConnectivity : public ArrayKernel @@ -970,122 +1007,104 @@ public: /** @name Range based iterators and circulators */ //@{ - /// Generic class for vertex/halfedge/edge/face ranges. - template< - typename CONTAINER_TYPE, - typename ITER_TYPE, - ITER_TYPE (CONTAINER_TYPE::*begin_fn)() const, - ITER_TYPE (CONTAINER_TYPE::*end_fn)() const> - class EntityRange { - public: - typedef ITER_TYPE iterator; - typedef ITER_TYPE const_iterator; - - explicit EntityRange(CONTAINER_TYPE &container) : container_(container) {} - ITER_TYPE begin() const { return (container_.*begin_fn)(); } - ITER_TYPE end() const { return (container_.*end_fn)(); } - - private: - CONTAINER_TYPE &container_; - }; - typedef EntityRange< + typedef EntityRange ConstVertexRange; - typedef EntityRange< + &PolyConnectivity::vertices_end>> ConstVertexRange; + typedef EntityRange ConstVertexRangeSkipping; - typedef EntityRange< + &PolyConnectivity::vertices_end>> ConstVertexRangeSkipping; + typedef EntityRange ConstHalfedgeRange; - typedef EntityRange< + &PolyConnectivity::halfedges_end>> ConstHalfedgeRange; + typedef EntityRange ConstHalfedgeRangeSkipping; - typedef EntityRange< + &PolyConnectivity::halfedges_end>> ConstHalfedgeRangeSkipping; + typedef EntityRange ConstEdgeRange; - typedef EntityRange< + &PolyConnectivity::edges_end>> ConstEdgeRange; + typedef EntityRange ConstEdgeRangeSkipping; - typedef EntityRange< + &PolyConnectivity::edges_end>> ConstEdgeRangeSkipping; + typedef EntityRange ConstFaceRange; - typedef EntityRange< + &PolyConnectivity::faces_end>> ConstFaceRange; + typedef EntityRange ConstFaceRangeSkipping; + &PolyConnectivity::faces_end>> ConstFaceRangeSkipping; + /** * @return The vertices as a range object suitable * for C++11 range based for loops. Will skip deleted vertices. */ - ConstVertexRangeSkipping vertices() const { return ConstVertexRangeSkipping(*this); } + ConstVertexRangeSkipping vertices() const; /** * @return The vertices as a range object suitable * for C++11 range based for loops. Will include deleted vertices. */ - ConstVertexRange all_vertices() const { return ConstVertexRange(*this); } + ConstVertexRange all_vertices() const; /** * @return The halfedges as a range object suitable * for C++11 range based for loops. Will skip deleted halfedges. */ - ConstHalfedgeRangeSkipping halfedges() const { return ConstHalfedgeRangeSkipping(*this); } + ConstHalfedgeRangeSkipping halfedges() const; /** * @return The halfedges as a range object suitable * for C++11 range based for loops. Will include deleted halfedges. */ - ConstHalfedgeRange all_halfedges() const { return ConstHalfedgeRange(*this); } + ConstHalfedgeRange all_halfedges() const; /** * @return The edges as a range object suitable * for C++11 range based for loops. Will skip deleted edges. */ - ConstEdgeRangeSkipping edges() const { return ConstEdgeRangeSkipping(*this); } + ConstEdgeRangeSkipping edges() const; /** * @return The edges as a range object suitable * for C++11 range based for loops. Will include deleted edges. */ - ConstEdgeRange all_edges() const { return ConstEdgeRange(*this); } + ConstEdgeRange all_edges() const; /** * @return The faces as a range object suitable * for C++11 range based for loops. Will skip deleted faces. */ - ConstFaceRangeSkipping faces() const { return ConstFaceRangeSkipping(*this); } + ConstFaceRangeSkipping faces() const; /** * @return The faces as a range object suitable * for C++11 range based for loops. Will include deleted faces. */ - ConstFaceRange all_faces() const { return ConstFaceRange(*this); } + ConstFaceRange all_faces() const; + /// Generic class for iterator ranges. - template< - typename CONTAINER_TYPE, - typename ITER_TYPE, - typename CENTER_ENTITY_TYPE, - ITER_TYPE (CONTAINER_TYPE::*begin_fn)(CENTER_ENTITY_TYPE) const, - ITER_TYPE (CONTAINER_TYPE::*end_fn)(CENTER_ENTITY_TYPE) const> - class CirculatorRange { + template + class CirculatorRange : public SmartRangeT, typename CirculatorRangeTraitT::TO_ENTITYE_TYPE>{ public: + typedef typename CirculatorRangeTraitT::ITER_TYPE ITER_TYPE; + typedef typename CirculatorRangeTraitT::CENTER_ENTITY_TYPE CENTER_ENTITY_TYPE; + typedef typename CirculatorRangeTraitT::CONTAINER_TYPE CONTAINER_TYPE; typedef ITER_TYPE iterator; typedef ITER_TYPE const_iterator; @@ -1093,139 +1112,132 @@ public: const CONTAINER_TYPE &container, CENTER_ENTITY_TYPE center) : container_(container), center_(center) {} - ITER_TYPE begin() const { return (container_.*begin_fn)(center_); } - ITER_TYPE end() const { return (container_.*end_fn)(center_); } + ITER_TYPE begin() const { return CirculatorRangeTraitT::begin(container_, center_); } + ITER_TYPE end() const { return CirculatorRangeTraitT::end(container_, center_); } private: const CONTAINER_TYPE &container_; CENTER_ENTITY_TYPE center_; }; - typedef CirculatorRange< + + typedef CirculatorRange ConstVertexVertexRange; - typedef CirculatorRange< + &PolyConnectivity::cvv_cwend>> ConstVertexVertexRange; + typedef CirculatorRange ConstVertexIHalfedgeRange; - typedef CirculatorRange< + &PolyConnectivity::cvih_end>> ConstVertexIHalfedgeRange; + typedef CirculatorRange ConstVertexOHalfedgeRange; - typedef CirculatorRange< + &PolyConnectivity::cvoh_end>> ConstVertexOHalfedgeRange; + typedef CirculatorRange ConstVertexEdgeRange; - typedef CirculatorRange< + &PolyConnectivity::cve_end>> ConstVertexEdgeRange; + typedef CirculatorRange ConstVertexFaceRange; - typedef CirculatorRange< + &PolyConnectivity::cvf_end>> ConstVertexFaceRange; + typedef CirculatorRange ConstFaceVertexRange; - typedef CirculatorRange< + &PolyConnectivity::cfv_end>> ConstFaceVertexRange; + typedef CirculatorRange ConstFaceHalfedgeRange; - typedef CirculatorRange< + &PolyConnectivity::cfh_end>> ConstFaceHalfedgeRange; + typedef CirculatorRange ConstFaceEdgeRange; - typedef CirculatorRange< + &PolyConnectivity::cfe_end>> ConstFaceEdgeRange; + typedef CirculatorRange ConstFaceFaceRange; + &PolyConnectivity::cff_end>> ConstFaceFaceRange; /** * @return The vertices adjacent to the specified vertex * as a range object suitable for C++11 range based for loops. */ - ConstVertexVertexRange vv_range(VertexHandle _vh) const { - return ConstVertexVertexRange(*this, _vh); - } + ConstVertexVertexRange vv_range(VertexHandle _vh) const; /** * @return The incoming halfedges incident to the specified vertex * as a range object suitable for C++11 range based for loops. */ - ConstVertexIHalfedgeRange vih_range(VertexHandle _vh) const { - return ConstVertexIHalfedgeRange(*this, _vh); - } + ConstVertexIHalfedgeRange vih_range(VertexHandle _vh) const; /** * @return The outgoing halfedges incident to the specified vertex * as a range object suitable for C++11 range based for loops. */ - ConstVertexOHalfedgeRange voh_range(VertexHandle _vh) const { - return ConstVertexOHalfedgeRange(*this, _vh); - } + ConstVertexOHalfedgeRange voh_range(VertexHandle _vh) const; /** * @return The edges incident to the specified vertex * as a range object suitable for C++11 range based for loops. */ - ConstVertexEdgeRange ve_range(VertexHandle _vh) const { - return ConstVertexEdgeRange(*this, _vh); - } + ConstVertexEdgeRange ve_range(VertexHandle _vh) const ; /** * @return The faces incident to the specified vertex * as a range object suitable for C++11 range based for loops. */ - ConstVertexFaceRange vf_range(VertexHandle _vh) const { - return ConstVertexFaceRange(*this, _vh); - } + ConstVertexFaceRange vf_range(VertexHandle _vh) const; /** * @return The vertices incident to the specified face * as a range object suitable for C++11 range based for loops. */ - ConstFaceVertexRange fv_range(FaceHandle _fh) const { - return ConstFaceVertexRange(*this, _fh); - } + ConstFaceVertexRange fv_range(FaceHandle _fh) const; /** * @return The halfedges incident to the specified face * as a range object suitable for C++11 range based for loops. */ - ConstFaceHalfedgeRange fh_range(FaceHandle _fh) const { - return ConstFaceHalfedgeRange(*this, _fh); - } + ConstFaceHalfedgeRange fh_range(FaceHandle _fh) const; /** * @return The edges incident to the specified face * as a range object suitable for C++11 range based for loops. */ - ConstFaceEdgeRange fe_range(FaceHandle _fh) const { - return ConstFaceEdgeRange(*this, _fh); - } + ConstFaceEdgeRange fe_range(FaceHandle _fh) const; /** * @return The faces adjacent to the specified face * as a range object suitable for C++11 range based for loops. */ - ConstFaceFaceRange ff_range(FaceHandle _fh) const { - return ConstFaceFaceRange(*this, _fh); - } + ConstFaceFaceRange ff_range(FaceHandle _fh) const; //@} @@ -1487,11 +1499,74 @@ private: // Working storage for add_face() }//namespace OpenMesh + #include #include namespace OpenMesh { +/// Generic class for vertex/halfedge/edge/face ranges. +template +class EntityRange : public SmartRangeT, typename RangeTraitT::ITER_TYPE::value_handle> { + public: + typedef typename RangeTraitT::ITER_TYPE iterator; + typedef typename RangeTraitT::ITER_TYPE const_iterator; + + explicit EntityRange(typename RangeTraitT::CONTAINER_TYPE &container) : container_(container) {} + typename RangeTraitT::ITER_TYPE begin() const { return RangeTraitT::begin(container_); } + typename RangeTraitT::ITER_TYPE end() const { return RangeTraitT::end(container_); } + + private: + typename RangeTraitT::CONTAINER_TYPE &container_; +}; + + +inline PolyConnectivity::ConstVertexRangeSkipping PolyConnectivity::vertices() const { return ConstVertexRangeSkipping(*this); } +inline PolyConnectivity::ConstVertexRange PolyConnectivity::all_vertices() const { return ConstVertexRange(*this); } +inline PolyConnectivity::ConstHalfedgeRangeSkipping PolyConnectivity::halfedges() const { return ConstHalfedgeRangeSkipping(*this); } +inline PolyConnectivity::ConstHalfedgeRange PolyConnectivity::all_halfedges() const { return ConstHalfedgeRange(*this); } +inline PolyConnectivity::ConstEdgeRangeSkipping PolyConnectivity::edges() const { return ConstEdgeRangeSkipping(*this); } +inline PolyConnectivity::ConstEdgeRange PolyConnectivity::all_edges() const { return ConstEdgeRange(*this); } +inline PolyConnectivity::ConstFaceRangeSkipping PolyConnectivity::faces() const { return ConstFaceRangeSkipping(*this); } +inline PolyConnectivity::ConstFaceRange PolyConnectivity::all_faces() const { return ConstFaceRange(*this); } + +inline PolyConnectivity::ConstVertexVertexRange PolyConnectivity::vv_range(VertexHandle _vh) const { + return ConstVertexVertexRange(*this, _vh); +} + +inline PolyConnectivity::ConstVertexIHalfedgeRange PolyConnectivity::vih_range(VertexHandle _vh) const { + return ConstVertexIHalfedgeRange(*this, _vh); +} + +inline PolyConnectivity::ConstVertexOHalfedgeRange PolyConnectivity::voh_range(VertexHandle _vh) const { + return ConstVertexOHalfedgeRange(*this, _vh); +} + +inline PolyConnectivity::ConstVertexEdgeRange PolyConnectivity::ve_range(VertexHandle _vh) const { + return ConstVertexEdgeRange(*this, _vh); +} + +inline PolyConnectivity::ConstVertexFaceRange PolyConnectivity::vf_range(VertexHandle _vh) const { + return ConstVertexFaceRange(*this, _vh); +} + +inline PolyConnectivity::ConstFaceVertexRange PolyConnectivity::fv_range(FaceHandle _fh) const { + return ConstFaceVertexRange(*this, _fh); +} + +inline PolyConnectivity::ConstFaceHalfedgeRange PolyConnectivity::fh_range(FaceHandle _fh) const { + return ConstFaceHalfedgeRange(*this, _fh); +} + +inline PolyConnectivity::ConstFaceEdgeRange PolyConnectivity::fe_range(FaceHandle _fh) const { + return ConstFaceEdgeRange(*this, _fh); +} + +inline PolyConnectivity::ConstFaceFaceRange PolyConnectivity::ff_range(FaceHandle _fh) const { + return ConstFaceFaceRange(*this, _fh); +} + + inline PolyConnectivity::VertexIter PolyConnectivity::vertices_begin() { return VertexIter(*this, VertexHandle(0)); } diff --git a/src/OpenMesh/Core/Mesh/SmartRange.hh b/src/OpenMesh/Core/Mesh/SmartRange.hh new file mode 100644 index 00000000..fdb2b15b --- /dev/null +++ b/src/OpenMesh/Core/Mesh/SmartRange.hh @@ -0,0 +1,84 @@ +/* ========================================================================= * + * * + * OpenMesh * + * Copyright (c) 2001-2019, RWTH-Aachen University * + * Department of Computer Graphics and Multimedia * + * All rights reserved. * + * www.openmesh.org * + * * + *---------------------------------------------------------------------------* + * This file is part of OpenMesh. * + *---------------------------------------------------------------------------* + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following conditions * + * are met: * + * * + * 1. Redistributions of source code must retain the above copyright notice, * + * this list of conditions and the following disclaimer. * + * * + * 2. Redistributions in binary form must reproduce the above copyright * + * notice, this list of conditions and the following disclaimer in the * + * documentation and/or other materials provided with the distribution. * + * * + * 3. Neither the name of the copyright holder nor the names of its * + * contributors may be used to endorse or promote products derived from * + * this software without specific prior written permission. * + * * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * + * * + * ========================================================================= */ + + +#pragma once + +#include + +//== NAMESPACES =============================================================== + +namespace OpenMesh { + +//== FORWARD DECLARATION ====================================================== + +//== CLASS DEFINITION ========================================================= + +/// Base class for all smart range types +template +struct SmartRangeT +{ + // TODO: Someone with better c++ knowledge may improve the code below. + + template + auto sum(Functor f) -> decltype (f(std::declval())+f(std::declval())) + { + auto range = static_cast(this); + auto begin = range->begin(); + auto end = range->end(); + assert(begin != end); + decltype (f(*begin) + f(*begin)) sum = f(*begin); + auto it = begin; + ++it; + for (; it != end; ++it) + sum += f(*it); + return sum; + } + +}; + + + +//============================================================================= +} // namespace OpenMesh +//============================================================================= + +//============================================================================= diff --git a/src/Unittests/unittests_smart_ranges.cc b/src/Unittests/unittests_smart_ranges.cc new file mode 100644 index 00000000..e817bc07 --- /dev/null +++ b/src/Unittests/unittests_smart_ranges.cc @@ -0,0 +1,196 @@ +#include +#include + +#include + +#include +#include + +namespace { + +class OpenMeshSmartRanges : public OpenMeshBase { + +protected: + + // This function is called before each test is run + virtual void SetUp() { + + mesh_.clear(); + + // Add some vertices + Mesh::VertexHandle vhandle[8]; + vhandle[0] = mesh_.add_vertex(Mesh::Point(-1, -1, 1)); + vhandle[1] = mesh_.add_vertex(Mesh::Point( 1, -1, 1)); + vhandle[2] = mesh_.add_vertex(Mesh::Point( 1, 1, 1)); + vhandle[3] = mesh_.add_vertex(Mesh::Point(-1, 1, 1)); + vhandle[4] = mesh_.add_vertex(Mesh::Point(-1, -1, -1)); + vhandle[5] = mesh_.add_vertex(Mesh::Point( 1, -1, -1)); + vhandle[6] = mesh_.add_vertex(Mesh::Point( 1, 1, -1)); + vhandle[7] = mesh_.add_vertex(Mesh::Point(-1, 1, -1)); + + // Add six faces to form a cube + std::vector face_vhandles; + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[0]); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[3]); + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[3]); + mesh_.add_face(face_vhandles); + + //======================= + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[7]); + face_vhandles.push_back(vhandle[6]); + face_vhandles.push_back(vhandle[5]); + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[7]); + face_vhandles.push_back(vhandle[5]); + face_vhandles.push_back(vhandle[4]); + mesh_.add_face(face_vhandles); + + //======================= + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[0]); + face_vhandles.push_back(vhandle[4]); + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[4]); + face_vhandles.push_back(vhandle[5]); + mesh_.add_face(face_vhandles); + + //======================= + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[5]); + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[5]); + face_vhandles.push_back(vhandle[6]); + mesh_.add_face(face_vhandles); + + + //======================= + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[3]); + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[6]); + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[3]); + face_vhandles.push_back(vhandle[6]); + face_vhandles.push_back(vhandle[7]); + mesh_.add_face(face_vhandles); + + //======================= + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[0]); + face_vhandles.push_back(vhandle[3]); + face_vhandles.push_back(vhandle[7]); + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + face_vhandles.push_back(vhandle[0]); + face_vhandles.push_back(vhandle[7]); + face_vhandles.push_back(vhandle[4]); + mesh_.add_face(face_vhandles); + + + // Test setup: + // + // + // 3 ======== 2 + // / /| + // / / | z + // 0 ======== 1 | | + // | | | | y + // | 7 | 6 | / + // | | / | / + // | |/ |/ + // 4 ======== 5 -------> x + // + + // Check setup + EXPECT_EQ(18u, mesh_.n_edges() ) << "Wrong number of Edges"; + EXPECT_EQ(36u, mesh_.n_halfedges() ) << "Wrong number of HalfEdges"; + EXPECT_EQ(8u, mesh_.n_vertices() ) << "Wrong number of vertices"; + EXPECT_EQ(12u, mesh_.n_faces() ) << "Wrong number of faces"; + } + + // This function is called after all tests are through + virtual void TearDown() { + + // Do some final stuff with the member data here... + + mesh_.clear(); + } + + // Member already defined in OpenMeshBase + //Mesh mesh_; +}; + +/* + * ==================================================================== + * Define tests below + * ==================================================================== + */ + + +template +struct F +{ + int operator()(HandleT ) { return 1; } +}; + +/* Test if smart ranges work + */ +TEST_F(OpenMeshSmartRanges, Sum) +{ + auto one = [](OpenMesh::VertexHandle ) { return 1; }; + EXPECT_EQ(mesh_.vertices().sum(one), mesh_.n_vertices()); + EXPECT_EQ(mesh_.vertices().sum(F()), mesh_.n_vertices()); + EXPECT_EQ(mesh_.halfedges().sum(F()), mesh_.n_halfedges()); + EXPECT_EQ(mesh_.edges().sum(F()), mesh_.n_edges()); + EXPECT_EQ(mesh_.faces().sum(F()), mesh_.n_faces()); + + for (auto vh : mesh_.vertices()) + EXPECT_EQ(vh.vertices().sum(F()), mesh_.valence(vh)); + for (auto vh : mesh_.vertices()) + EXPECT_EQ(vh.faces().sum(F()), mesh_.valence(vh)); + for (auto vh : mesh_.vertices()) + EXPECT_EQ(vh.outgoing_halfedges().sum(F()), mesh_.valence(vh)); + for (auto vh : mesh_.vertices()) + EXPECT_EQ(vh.incoming_halfedges().sum(F()), mesh_.valence(vh)); + + for (auto fh : mesh_.faces()) + EXPECT_EQ(fh.vertices().sum(F()), mesh_.valence(fh)); + for (auto fh : mesh_.faces()) + EXPECT_EQ(fh.halfedges().sum(F()), mesh_.valence(fh)); + for (auto fh : mesh_.faces()) + EXPECT_EQ(fh.edges().sum(F()), mesh_.valence(fh)); + for (auto fh : mesh_.faces()) + EXPECT_EQ(fh.faces().sum(F()), 3); +} + + + +}