diff --git a/cmake/filelistCore.cmake b/cmake/filelistCore.cmake index d2a3227c21e..c2398990585 100644 --- a/cmake/filelistCore.cmake +++ b/cmake/filelistCore.cmake @@ -23,6 +23,7 @@ set( core_sources Geometry/Adjacency.cpp Geometry/Area.cpp Geometry/CatmullClarkSubdivider.cpp + Geometry/deprecated/TopologicalMesh.cpp Geometry/HeatDiffusion.cpp Geometry/Laplacian.cpp Geometry/LoopSubdivider.cpp @@ -91,6 +92,7 @@ set( core_headers Geometry/Area.hpp Geometry/CatmullClarkSubdivider.hpp Geometry/Curve2D.hpp + Geometry/deprecated/TopologicalMesh.hpp Geometry/DistanceQueries.hpp Geometry/Frustum.hpp Geometry/HeatDiffusion.hpp @@ -154,6 +156,7 @@ set( core_inlines Containers/Grid.inl Containers/Tex.inl Geometry/Curve2D.inl + Geometry/deprecated/TopologicalMesh.inl Geometry/DistanceQueries.inl Geometry/MeshPrimitives.inl Geometry/PolyLine.inl diff --git a/src/Core/Geometry/CatmullClarkSubdivider.cpp b/src/Core/Geometry/CatmullClarkSubdivider.cpp index 9b04d269e8e..7d3d5e393ec 100644 --- a/src/Core/Geometry/CatmullClarkSubdivider.cpp +++ b/src/Core/Geometry/CatmullClarkSubdivider.cpp @@ -9,8 +9,7 @@ bool CatmullClarkSubdivider::prepare( TopologicalMesh& mesh ) { mesh.add_property( m_epH ); mesh.add_property( m_fpH ); mesh.add_property( m_creaseWeights ); - mesh.createAllPropsOnFaces( - m_normalPropF, m_floatPropsF, m_vec2PropsF, m_vec3PropsF, m_vec4PropsF ); + ///\todo mesh.createAllPropsOnFaces( m_normalPropF ); mesh.add_property( m_hV ); for ( uint i = 0; i < mesh.n_halfedges(); ++i ) { @@ -30,7 +29,7 @@ bool CatmullClarkSubdivider::cleanup( TopologicalMesh& mesh ) { mesh.remove_property( m_epH ); mesh.remove_property( m_fpH ); mesh.remove_property( m_creaseWeights ); - mesh.clearAllProps( m_normalPropF, m_floatPropsF, m_vec2PropsF, m_vec3PropsF, m_vec4PropsF ); + ///\todo mesh.clearAllProps( m_normalPropF ); mesh.remove_property( m_hV ); return true; } @@ -78,8 +77,7 @@ bool CatmullClarkSubdivider::subdivide( TopologicalMesh& mesh, #pragma omp critical { m_newFaceVertexOps[iter].push_back( V_OPS( vh, ops ) ); } // deal with properties - mesh.interpolateAllPropsOnFaces( - fh, m_normalPropF, m_floatPropsF, m_vec2PropsF, m_vec3PropsF, m_vec4PropsF ); + ///\todo mesh.interpolateAllPropsOnFaces( fh, m_normalPropF ); } // Compute position for new (edge-) vertices and store them in the edge property @@ -166,8 +164,8 @@ bool CatmullClarkSubdivider::subdivide( TopologicalMesh& mesh, mesh.set_next_halfedge_handle( heh6, heh2 ); // deal with properties - mesh.copyAllProps( heh3, heh5 ); - mesh.copyAllProps( heh1, heh6 ); + ///\todo mesh.copyAllProps( heh3, heh5 ); + ///\todo mesh.copyAllProps( heh1, heh6 ); m_triangulationPropOps.push_back( {heh5, {{1, heh3}}} ); m_triangulationPropOps.push_back( {heh6, {{1, heh1}}} ); } @@ -200,14 +198,13 @@ void CatmullClarkSubdivider::split_face( TopologicalMesh& mesh, mesh.set_face_handle( hold, fh ); // deal with properties for vh - mesh.copyAllPropsFromFace( - fh, hold, m_normalPropF, m_floatPropsF, m_vec2PropsF, m_vec3PropsF, m_vec4PropsF ); + ///\todo mesh.copyAllPropsFromFace( fh, hold, m_normalPropF ); // go around new vertex to build topology hold = mesh.opposite_halfedge_handle( hold ); // deal with properties for hold - mesh.copyAllProps( hend, hold ); + ///\todo mesh.copyAllProps( hend, hold ); m_newFacePropOps[iter].push_back( {hold, {{1, hend}}} ); const Scalar inv_val = Scalar( 1 ) / valence; @@ -231,8 +228,7 @@ void CatmullClarkSubdivider::split_face( TopologicalMesh& mesh, mesh.set_next_halfedge_handle( hold, hh ); // deal with properties for hnew - mesh.copyAllPropsFromFace( - fh, hnew, m_normalPropF, m_floatPropsF, m_vec2PropsF, m_vec3PropsF, m_vec4PropsF ); + ///\todo mesh.copyAllPropsFromFace( fh, hnew, m_normalPropF ); // prepare for next face hh = mesh.next_halfedge_handle( hnext ); @@ -240,7 +236,7 @@ void CatmullClarkSubdivider::split_face( TopologicalMesh& mesh, mesh.set_next_halfedge_handle( hnext, hnew ); // this has to be done after hh ! // deal with properties for hold - mesh.copyAllProps( hnext, hold ); + ///\todo mesh.copyAllProps( hnext, hold ); m_newFacePropOps[iter].push_back( {hold, {{1, hnext}}} ); p_ops[i] = {inv_val, hh}; } @@ -310,7 +306,7 @@ void CatmullClarkSubdivider::split_edge( TopologicalMesh& mesh, mesh.set_halfedge_handle( mesh.face_handle( opp_new_heh ), opp_new_heh ); // deal with custom properties - mesh.interpolateAllProps( t_heh, opp_heh, opp_new_heh, 0.5 ); + ///\todo mesh.interpolateAllProps( t_heh, opp_heh, opp_new_heh, 0.5 ); m_newEdgePropOps[iter].push_back( {opp_new_heh, {{0.5, t_heh}, {0.5, opp_heh}}} ); } @@ -320,10 +316,10 @@ void CatmullClarkSubdivider::split_edge( TopologicalMesh& mesh, mesh.set_halfedge_handle( mesh.face_handle( heh ), heh ); // deal with custom properties - mesh.copyAllProps( heh, new_heh ); + ///\todo mesh.copyAllProps( heh, new_heh ); m_newEdgePropOps[iter].push_back( {new_heh, {{1, heh}}} ); HeHandle heh_prev = mesh.prev_halfedge_handle( heh ); - mesh.interpolateAllProps( heh_prev, new_heh, heh, 0.5 ); + ///\todo mesh.interpolateAllProps( heh_prev, new_heh, heh, 0.5 ); m_newEdgePropOps[iter].push_back( {heh, {{0.5, heh_prev}, {0.5, new_heh}}} ); } diff --git a/src/Core/Geometry/LoopSubdivider.cpp b/src/Core/Geometry/LoopSubdivider.cpp index 7076a28f00f..a752154fb3b 100644 --- a/src/Core/Geometry/LoopSubdivider.cpp +++ b/src/Core/Geometry/LoopSubdivider.cpp @@ -183,8 +183,8 @@ void LoopSubdivider::corner_cutting( TopologicalMesh& mesh, mesh.set_halfedge_handle( fh_new, heh1 ); // deal with custom properties - mesh.copyAllProps( heh1, heh4 ); - mesh.copyAllProps( heh5, heh3 ); + ///\todo mesh.copyAllProps( heh1, heh4 ); + ///\todo mesh.copyAllProps( heh5, heh3 ); m_newFacePropOps[iter].push_back( {heh4, {{1, heh1}}} ); m_newFacePropOps[iter].push_back( {heh3, {{1, heh5}}} ); } @@ -237,7 +237,9 @@ void LoopSubdivider::split_edge( TopologicalMesh& mesh, mesh.set_halfedge_handle( mesh.face_handle( opp_new_heh ), opp_new_heh ); // deal with custom properties - mesh.interpolateAllProps( t_heh, opp_heh, opp_new_heh, 0.5 ); + + /// \todo + // mesh.interpolateAllProps( t_heh, opp_heh, opp_new_heh, 0.5 ); m_newEdgePropOps[iter].push_back( {opp_new_heh, {{0.5, t_heh}, {0.5, opp_heh}}} ); } @@ -247,10 +249,10 @@ void LoopSubdivider::split_edge( TopologicalMesh& mesh, mesh.set_halfedge_handle( mesh.face_handle( heh ), heh ); // deal with custom properties - mesh.copyAllProps( heh, new_heh ); + /// \todo mesh.copyAllProps( heh, new_heh ); m_newEdgePropOps[iter].push_back( {new_heh, {{1, heh}}} ); HeHandle heh_prev = mesh.prev_halfedge_handle( heh ); - mesh.interpolateAllProps( heh_prev, new_heh, heh, 0.5 ); + /// \todo mesh.interpolateAllProps( heh_prev, new_heh, heh, 0.5 ); m_newEdgePropOps[iter].push_back( {heh, {{0.5, heh_prev}, {0.5, new_heh}}} ); } diff --git a/src/Core/Geometry/TopologicalMesh.cpp b/src/Core/Geometry/TopologicalMesh.cpp index 10c3231541f..60fd5c1ac90 100644 --- a/src/Core/Geometry/TopologicalMesh.cpp +++ b/src/Core/Geometry/TopologicalMesh.cpp @@ -5,7 +5,6 @@ #include -#include #include #include @@ -77,7 +76,20 @@ bool TopologicalMesh::checkIntegrity() const { for ( auto he_itr {halfedges_begin()}; he_itr != halfedges_end(); ++he_itr ) { auto widx = property( m_wedgeIndexPph, *he_itr ); - if ( widx.isValid() ) + + if ( status( *he_itr ).deleted() ) { continue; } + + if ( is_boundary( *he_itr ) != widx.isInvalid() ) + { + LOG( logWARNING ) << "topological mesh wedge inconsistency, boundary he (" + << ( is_boundary( *he_itr ) ? "true," : "false," ) << he_itr->idx() + << ") != invalid Wedge (" << ( widx.isInvalid() ? "true," : "false," ) + << widx << ") ref " + << ( widx.isValid() ? m_wedges.getWedge( widx ).getRefCount() : 0 ); + ret = false; + } + + if ( widx.isValid() ) // i.e. non boudnary { count[widx]++; @@ -85,10 +97,11 @@ bool TopologicalMesh::checkIntegrity() const { { LOG( logWARNING ) << "topological mesh wedge inconsistency, wedge and to position " "differ for widx " - << widx << ", have " + << widx << ", have (" << m_wedges.getWedgeData( widx ).m_position.transpose() - << "instead of " - << point( to_vertex_handle( *he_itr ) ).transpose(); + << ") instead of (" + << point( to_vertex_handle( *he_itr ) ).transpose() << ")"; + ret = false; } } } @@ -97,16 +110,74 @@ bool TopologicalMesh::checkIntegrity() const { { if ( m_wedges.getWedge( WedgeIndex {widx} ).getRefCount() != count[widx] ) { - LOG( logWARNING ) << "topological mesh wedge count inconsistency, have " << count[widx] - << " instead of " + LOG( logWARNING ) << "topological mesh wedge count inconsistency, topo count [ " + << count[widx] << " ] != wedge count [ " << m_wedges.getWedge( WedgeIndex {widx} ).getRefCount() - << " for wedge id " << widx; + << " ] for id " << widx; ret = false; } } return ret; } +void TopologicalMesh::triangulate() { + + auto fix = [this]( HalfedgeHandle next_he, const std::vector& old_heh ) { + // tagged if already fixed + auto to_vh = to_vertex_handle( next_he ); + // find ref in old_he to copy wedge idx + + auto ref = std::find_if( + old_heh.begin(), old_heh.end(), [this, to_vh]( const HalfedgeHandle& he ) { + return to_vertex_handle( he ) == to_vh; + } ); + if ( ref != old_heh.end() ) + { + property( m_wedgeIndexPph, next_he ) = + m_wedges.newReference( property( m_wedgeIndexPph, *ref ) ); + } + else + { LOG( logERROR ) << "triangulate::fix reference halfedge not found"; } + status( next_he ).set_tagged( true ); + }; + + FaceIter f_it( faces_begin() ), f_end( faces_end() ); + for ( ; f_it != f_end; ++f_it ) + { + // save original halfedge of the face + std::vector old_heh; + ConstFaceHalfedgeIter fh_itr = cfh_iter( *f_it ); + for ( ; fh_itr.is_valid(); ++fh_itr ) + { + old_heh.push_back( *fh_itr ); + } + auto size = old_heh.size(); + // if ( size <= 3 ) continue; + + // base openmesh triangulate + base::triangulate( *f_it ); + + // fix newly created he + for ( size_t i = 0; i < size; ++i ) + { + auto next_he = next_halfedge_handle( old_heh[i] ); + // if next_he is not the same as next in old_heh, then it's a new one. + // fix tag halfedge so that it is not fixed two times (in case opposite halfedge is also + // parsed in this loop. + if ( !status( next_he ).tagged() && next_he != old_heh[( i + 1 ) % size] ) + { + fix( next_he, old_heh ); + fix( opposite_halfedge_handle( next_he ), old_heh ); + } + } + } + // untag everything + for ( auto& he : halfedges() ) + { + status( he ).set_tagged( false ); + } +} + void printWedgesInfo( const Ra::Core::Geometry::TopologicalMesh& topo ) { using namespace Ra::Core; @@ -128,217 +199,150 @@ void printWedgesInfo( const Ra::Core::Geometry::TopologicalMesh& topo ) { } } -template -void addAttribPairToCore( TriangleMesh& triMesh, - const TopologicalMesh* topoMesh, - OpenMesh::HPropHandleT oh, - std::vector

& vprop ) { - AttribHandle h = triMesh.addAttrib( topoMesh->property( oh ).name() ); - vprop.push_back( std::make_pair( h, oh ) ); +TopologicalMesh::TopologicalMesh() { + add_property( m_inputTriangleMeshIndexPph ); + add_property( m_wedgeIndexPph ); } template -using HandleAndValueVector = std::vector, T>, - Eigen::aligned_allocator, T>>>; - -template -void copyAttribToCoreVertex( HandleAndValueVector& data, - const TopologicalMesh* topoMesh, - const std::vector

& vprop, - TopologicalMesh::HalfedgeHandle heh ) { - for ( auto pp : vprop ) +void copyWedgeDataToAttribContainer( AlignedStdVector::Container>& c, + const VectorArray& wd ) { + for ( size_t i = 0; i < wd.size(); ++i ) { - data.push_back( std::make_pair( pp.first, topoMesh->property( pp.second, heh ) ) ); + c[i].push_back( wd[i] ); } } -template -void copyAttribToCore( TriangleMesh& triMesh, const HandleAndValueVector& data ) { - - for ( auto pp : data ) +template +void moveContainerToMesh( IndexedGeometry& out, + const std::vector& names, + AlignedStdVector::Container>& wedgeAttribData ) { + for ( size_t i = 0; i < wedgeAttribData.size(); ++i ) { - auto& attr = triMesh.getAttrib( pp.first ); - auto& attrData = attr.getDataWithLock(); - attrData.push_back( pp.second ); - attr.unlock(); + auto attrHandle = out.template addAttrib( names[i] ); + out.getAttrib( attrHandle ).setData( std::move( wedgeAttribData[i] ) ); } } -//! [Default command implementation] -struct DefaultNonManifoldFaceCommand { - /// \brief details string is printed along with the message - DefaultNonManifoldFaceCommand( std::string details = {} ) : m_details {details} {} - /// \brief Initalize with input Ra::Core::Geometry::TriangleMesh - inline void initialize( const TriangleMesh& /*triMesh*/ ) {} - /// \brief Process non-manifold face - inline void process( const std::vector& /*face_vhandles*/ ) { - LOG( logWARNING ) << "Invalid face handle returned : face not added " + m_details; - /// TODO memorize invalid faces for post processing ... - /// see - /// https://www.graphics.rwth-aachen.de/media/openflipper_static/Daily-Builds/Doc/Free/Developer/OBJImporter_8cc_source.html - /// for an exemple of loading - } - /// \brief If needed, apply post-processing on the Ra::Core::Geometry::TopologicalMesh - inline void postProcess( TopologicalMesh& /*tm*/ ) {} - //! [Default command implementation] - private: - std::string m_details; -}; - -void TopologicalMesh::initWithWedge( const TriangleMesh& triMesh ) { - initWithWedge( triMesh, DefaultNonManifoldFaceCommand( "[initWithWedges]" ) ); -} - -TopologicalMesh::TopologicalMesh( const TriangleMesh& triMesh ) : - TopologicalMesh( triMesh, DefaultNonManifoldFaceCommand( "[default ctor (props)]" ) ) {} - -TopologicalMesh::TopologicalMesh() { - add_property( m_inputTriangleMeshIndexPph ); - add_property( m_wedgeIndexPph ); -} - TriangleMesh TopologicalMesh::toTriangleMesh() { - struct VertexDataInternal { - Vector3 _vertex; - Vector3 _normal; - - HandleAndValueVector _float; - HandleAndValueVector _vec2; - HandleAndValueVector _vec3; - HandleAndValueVector _vec4; - - EIGEN_MAKE_ALIGNED_OPERATOR_NEW - - bool operator==( const VertexDataInternal& lhs ) const { - return _vertex == lhs._vertex && _normal == lhs._normal && _float == lhs._float && - _vec2 == lhs._vec2 && _vec3 == lhs._vec3 && _vec4 == lhs._vec4; - } - }; - - struct hash_vec { - size_t operator()( const VertexDataInternal& lvalue ) const { - size_t hx = std::hash()( lvalue._vertex[0] ); - size_t hy = std::hash()( lvalue._vertex[1] ); - size_t hz = std::hash()( lvalue._vertex[2] ); - return ( hx ^ ( hy << 1 ) ) ^ hz; - } - }; + // first cleanup deleted element + garbage_collection(); TriangleMesh out; - - using VertexMap = std::unordered_map; - - VertexMap vertexHandles; - - if ( !get_property_handle( m_outputTriangleMeshIndexPph, "OutputTriangleMeshIndices" ) ) - { add_property( m_outputTriangleMeshIndexPph, "OutputTriangleMeshIndices" ); } - std::vector> vprop_float; - std::vector> vprop_vec2; - std::vector> vprop_vec3; - std::vector> vprop_vec4; - - // loop over all attribs and build correspondance pair - vprop_float.reserve( m_floatPph.size() ); - for ( auto oh : m_floatPph ) - addAttribPairToCore( out, this, oh, vprop_float ); - vprop_vec2.reserve( m_vec2Pph.size() ); - for ( auto oh : m_vec2Pph ) - addAttribPairToCore( out, this, oh, vprop_vec2 ); - vprop_vec3.reserve( m_vec3Pph.size() ); - for ( auto oh : m_vec3Pph ) - addAttribPairToCore( out, this, oh, vprop_vec3 ); - vprop_vec4.reserve( m_vec4Pph.size() ); - for ( auto oh : m_vec4Pph ) - addAttribPairToCore( out, this, oh, vprop_vec4 ); - - // iterator over all faces - unsigned int vertexIndex = 0; - - // out will have at least n_vertices vertices and normals. - TriangleMesh::PointAttribHandle::Container vertices; - TriangleMesh::NormalAttribHandle::Container normals; TriangleMesh::IndexContainerType indices; - vertices.reserve( n_vertices() ); - normals.reserve( n_vertices() ); - indices.reserve( n_faces() ); + /// add attribs to out + TriangleMesh::PointAttribHandle::Container wedgePosition; + AlignedStdVector::Container> wedgeFloatAttribData( + m_wedges.m_floatAttribNames.size() ); + AlignedStdVector::Container> wedgeVector2AttribData( + m_wedges.m_vector2AttribNames.size() ); + AlignedStdVector::Container> wedgeVector3AttribData( + m_wedges.m_vector3AttribNames.size() ); + AlignedStdVector::Container> wedgeVector4AttribData( + m_wedges.m_vector4AttribNames.size() ); + + /// Wedges are output vertices ! + for ( WedgeIndex widx {0}; widx < WedgeIndex {m_wedges.size()}; ++widx ) + { + const auto& wd = m_wedges.getWedgeData( widx ); + wedgePosition.push_back( wd.m_position ); + copyWedgeDataToAttribContainer( wedgeFloatAttribData, wd.m_floatAttrib ); + copyWedgeDataToAttribContainer( wedgeVector2AttribData, wd.m_vector2Attrib ); + copyWedgeDataToAttribContainer( wedgeVector3AttribData, wd.m_vector3Attrib ); + copyWedgeDataToAttribContainer( wedgeVector4AttribData, wd.m_vector4Attrib ); + } + + out.setVertices( std::move( wedgePosition ) ); + moveContainerToMesh( out, m_wedges.m_floatAttribNames, wedgeFloatAttribData ); + moveContainerToMesh( out, m_wedges.m_vector2AttribNames, wedgeVector2AttribData ); + moveContainerToMesh( out, m_wedges.m_vector3AttribNames, wedgeVector3AttribData ); + moveContainerToMesh( out, m_wedges.m_vector4AttribNames, wedgeVector4AttribData ); for ( TopologicalMesh::FaceIter f_it = faces_sbegin(); f_it != faces_end(); ++f_it ) { int tindices[3]; int i = 0; - // iterator over vertex (through halfedge to get access to halfedge normals) for ( TopologicalMesh::ConstFaceHalfedgeIter fh_it = cfh_iter( *f_it ); fh_it.is_valid(); ++fh_it ) { - VertexDataInternal v; CORE_ASSERT( i < 3, "Non-triangular face found." ); - v._vertex = point( to_vertex_handle( *fh_it ) ); - - if ( has_halfedge_normals() ) - { v._normal = normal( to_vertex_handle( *fh_it ), *f_it ); } - copyAttribToCoreVertex( v._float, this, vprop_float, *fh_it ); - copyAttribToCoreVertex( v._vec2, this, vprop_vec2, *fh_it ); - copyAttribToCoreVertex( v._vec3, this, vprop_vec3, *fh_it ); - copyAttribToCoreVertex( v._vec4, this, vprop_vec4, *fh_it ); - - int vi; - VertexMap::iterator vtr = vertexHandles.find( v ); - if ( vtr == vertexHandles.end() ) - { - vi = int( vertexIndex++ ); - vertexHandles.insert( vtr, VertexMap::value_type( v, vi ) ); - vertices.push_back( v._vertex ); - if ( has_halfedge_normals() ) { normals.push_back( v._normal ); } - copyAttribToCore( out, v._float ); - copyAttribToCore( out, v._vec2 ); - copyAttribToCore( out, v._vec3 ); - copyAttribToCore( out, v._vec4 ); - } - else - { vi = vtr->second; } - tindices[i] = vi; - property( m_outputTriangleMeshIndexPph, *fh_it ) = vi; + tindices[i] = property( m_wedgeIndexPph, *fh_it ); i++; } indices.emplace_back( tindices[0], tindices[1], tindices[2] ); } - out.setVertices( std::move( vertices ) ); - if ( has_halfedge_normals() ) { out.setNormals( std::move( normals ) ); } + out.setIndices( std::move( indices ) ); - CORE_ASSERT( vertexIndex == vertices.size(), - "Inconsistent number of faces in generated TriangleMesh." ); return out; } -template -void copyWedgeDataToAttribContainer( AlignedStdVector::Container>& c, - const VectorArray& wd ) { - for ( size_t i = 0; i < wd.size(); ++i ) +LineMesh TopologicalMesh::toLineMesh() { + // first cleanup deleted element + garbage_collection(); + + LineMesh out; + LineMesh::IndexContainerType indices; + + TriangleMesh::PointAttribHandle::Container wedgePosition; + AlignedStdVector::Container> wedgeFloatAttribData( + m_wedges.m_floatAttribNames.size() ); + AlignedStdVector::Container> wedgeVector2AttribData( + m_wedges.m_vector2AttribNames.size() ); + AlignedStdVector::Container> wedgeVector3AttribData( + m_wedges.m_vector3AttribNames.size() ); + AlignedStdVector::Container> wedgeVector4AttribData( + m_wedges.m_vector4AttribNames.size() ); + + /// Wedges are output vertices ! + for ( WedgeIndex widx {0}; widx < WedgeIndex( m_wedges.size() ); ++widx ) { - c[i].push_back( wd[i] ); + const auto& wd = m_wedges.getWedgeData( widx ); + wedgePosition.push_back( wd.m_position ); + copyWedgeDataToAttribContainer( wedgeFloatAttribData, wd.m_floatAttrib ); + copyWedgeDataToAttribContainer( wedgeVector2AttribData, wd.m_vector2Attrib ); + copyWedgeDataToAttribContainer( wedgeVector3AttribData, wd.m_vector3Attrib ); + copyWedgeDataToAttribContainer( wedgeVector4AttribData, wd.m_vector4Attrib ); } -} -template -void moveContainerToMesh( TriangleMesh& out, - const std::vector& names, - AlignedStdVector::Container>& wedgeAttribData ) { - for ( size_t i = 0; i < wedgeAttribData.size(); ++i ) + out.setVertices( std::move( wedgePosition ) ); + moveContainerToMesh( out, m_wedges.m_floatAttribNames, wedgeFloatAttribData ); + moveContainerToMesh( out, m_wedges.m_vector2AttribNames, wedgeVector2AttribData ); + moveContainerToMesh( out, m_wedges.m_vector3AttribNames, wedgeVector3AttribData ); + moveContainerToMesh( out, m_wedges.m_vector4AttribNames, wedgeVector4AttribData ); + + for ( TopologicalMesh::EdgeIter e_it = edges_sbegin(); e_it != edges_end(); ++e_it ) { - auto attrHandle = out.addAttrib( names[i] ); - out.getAttrib( attrHandle ).setData( std::move( wedgeAttribData[i] ) ); + int tindices[2]; + + // take care of boundaries + auto he0 = halfedge_handle( *e_it, 0 ); + if ( OpenMesh::ArrayKernel::is_boundary( he0 ) ) + { he0 = prev_halfedge_handle( opposite_halfedge_handle( he0 ) ); } + if ( OpenMesh::ArrayKernel::is_boundary( he0 ) ) continue; + auto he1 = halfedge_handle( *e_it, 1 ); + if ( OpenMesh::ArrayKernel::is_boundary( he1 ) ) + { he1 = prev_halfedge_handle( opposite_halfedge_handle( he1 ) ); } + if ( OpenMesh::ArrayKernel::is_boundary( he1 ) ) continue; + + tindices[0] = property( m_wedgeIndexPph, he0 ); + tindices[1] = property( m_wedgeIndexPph, he1 ); + + indices.emplace_back( tindices[0], tindices[1] ); } -} -TriangleMesh TopologicalMesh::toTriangleMeshFromWedges() { + out.setIndices( std::move( indices ) ); + + return out; +} +PolyMesh TopologicalMesh::toPolyMesh() { // first cleanup deleted element garbage_collection(); - TriangleMesh out; - TriangleMesh::IndexContainerType indices; + PolyMesh out; + PolyMesh::IndexContainerType indices; /// add attribs to out std::vector> wedgeFloatAttribHandles; @@ -357,7 +361,7 @@ TriangleMesh TopologicalMesh::toTriangleMeshFromWedges() { m_wedges.m_vector4AttribNames.size() ); /// Wedges are output vertices ! - for ( WedgeIndex widx {0}; widx < WedgeIndex {m_wedges.size()}; ++widx ) + for ( WedgeIndex widx {0}; widx < WedgeIndex( m_wedges.size() ); ++widx ) { const auto& wd = m_wedges.getWedgeData( widx ); wedgePosition.push_back( wd.m_position ); @@ -375,17 +379,17 @@ TriangleMesh TopologicalMesh::toTriangleMeshFromWedges() { for ( TopologicalMesh::FaceIter f_it = faces_sbegin(); f_it != faces_end(); ++f_it ) { - int tindices[3]; int i = 0; - + PolyMesh::IndexType faceIndices( valence( *f_it ) ); + // iterator over vertex (through halfedge to get access to halfedge normals) for ( TopologicalMesh::ConstFaceHalfedgeIter fh_it = cfh_iter( *f_it ); fh_it.is_valid(); ++fh_it ) { - CORE_ASSERT( i < 3, "Non-triangular face found." ); - tindices[i] = property( m_wedgeIndexPph, *fh_it ); + faceIndices( i ) = property( m_wedgeIndexPph, *fh_it ); i++; } - indices.emplace_back( tindices[0], tindices[1], tindices[2] ); + // LOG( logDEBUG ) << "add polymesh face " << faceIndices.transpose(); + indices.push_back( faceIndices ); } out.setIndices( std::move( indices ) ); @@ -393,147 +397,98 @@ TriangleMesh TopologicalMesh::toTriangleMeshFromWedges() { return out; } -void TopologicalMesh::updateTriangleMesh( Ra::Core::Geometry::TriangleMesh& /*mesh*/ ) { - CORE_ASSERT( false, "not implemented yet" ); - ///\todo ;) +void TopologicalMesh::updateTriangleMesh( Ra::Core::Geometry::TriangleMesh& out ) { + TriangleMesh::PointAttribHandle::Container wedgePosition; + AlignedStdVector::Container> wedgeFloatAttribData( + m_wedges.m_floatAttribNames.size() ); + AlignedStdVector::Container> wedgeVector2AttribData( + m_wedges.m_vector2AttribNames.size() ); + AlignedStdVector::Container> wedgeVector3AttribData( + m_wedges.m_vector3AttribNames.size() ); + AlignedStdVector::Container> wedgeVector4AttribData( + m_wedges.m_vector4AttribNames.size() ); + + /// Wedges are output vertices ! + for ( WedgeIndex widx {0}; widx < WedgeIndex( m_wedges.size() ); ++widx ) + { + const auto& wd = m_wedges.getWedgeData( widx ); + wedgePosition.push_back( wd.m_position ); + copyWedgeDataToAttribContainer( wedgeFloatAttribData, wd.m_floatAttrib ); + copyWedgeDataToAttribContainer( wedgeVector2AttribData, wd.m_vector2Attrib ); + copyWedgeDataToAttribContainer( wedgeVector3AttribData, wd.m_vector3Attrib ); + copyWedgeDataToAttribContainer( wedgeVector4AttribData, wd.m_vector4Attrib ); + } + + out.setVertices( std::move( wedgePosition ) ); + moveContainerToMesh( out, m_wedges.m_floatAttribNames, wedgeFloatAttribData ); + moveContainerToMesh( out, m_wedges.m_vector2AttribNames, wedgeVector2AttribData ); + moveContainerToMesh( out, m_wedges.m_vector3AttribNames, wedgeVector3AttribData ); + moveContainerToMesh( out, m_wedges.m_vector4AttribNames, wedgeVector4AttribData ); } -bool TopologicalMesh::splitEdge( TopologicalMesh::EdgeHandle eh, Scalar f ) { - // Global schema of operation - /* - TRIANGLES ONLY - before after - A A - / F0 \ / F2 | F0 \ - / \ / | \ - /h1 h0\ /h1 e2|e0 h0\ - / he0 \ / he2 | he0 \ - V1 -------- V0 V1 ------ V ------ V0 - \ he1 / \ he3 | he1 / - \o1 o0/ \o1 e3|e1 o0/ - \ / \ | / - \ F1 / \ F3 | F1 / - B B +void TopologicalMesh::updateTriangleMeshNormals( + AttribArrayGeometry::NormalAttribHandle::Container& normals ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } - */ + for ( unsigned int widx = 0; widx < m_wedges.size(); ++widx ) + { + normals[widx] = m_wedges.getWedgeData( widx, m_normalsIndex ); + } +} - // incorrect factor - if ( f < 0 || f > 1 ) { return false; } +void TopologicalMesh::updateTriangleMeshNormals( Ra::Core::Geometry::TriangleMesh& out ) { + auto& normals = out.normalsWithLock(); + updateTriangleMeshNormals( normals ); + out.normalsUnlock(); +} - // get existing topology data - HalfedgeHandle he0 = halfedge_handle( eh, 0 ); - HalfedgeHandle he1 = halfedge_handle( eh, 1 ); - VertexHandle v0 = to_vertex_handle( he0 ); - VertexHandle v1 = to_vertex_handle( he1 ); - FaceHandle F0 = face_handle( he0 ); - FaceHandle F1 = face_handle( he1 ); - - // not triangles or holes - if ( ( !is_boundary( he0 ) && valence( F0 ) != 3 ) || - ( !is_boundary( he1 ) && valence( F1 ) != 3 ) ) - { return false; } - - // add the new vertex - const Point p = Point( f * point( v0 ) + ( Scalar( 1. ) - f ) * point( v1 ) ); - VertexHandle v = add_vertex( p ); - - // create the new faces and reconnect the topology - HalfedgeHandle he3 = new_edge( v, v1 ); - HalfedgeHandle he2 = opposite_halfedge_handle( he3 ); - set_halfedge_handle( v, he0 ); - set_vertex_handle( he1, v ); - - // does F0 exist - if ( !is_boundary( he0 ) ) - { - HalfedgeHandle h0 = next_halfedge_handle( he0 ); - HalfedgeHandle h1 = next_halfedge_handle( h0 ); - // create new edge - VertexHandle A = to_vertex_handle( h0 ); - HalfedgeHandle e2 = new_edge( v, A ); - HalfedgeHandle e0 = opposite_halfedge_handle( e2 ); - // split F0 - FaceHandle F2 = new_face(); - set_halfedge_handle( F0, he0 ); - set_halfedge_handle( F2, h1 ); - // update F0 - set_face_handle( h0, F0 ); - set_face_handle( e0, F0 ); - set_face_handle( he0, F0 ); - set_next_halfedge_handle( he0, h0 ); - set_next_halfedge_handle( h0, e0 ); - set_next_halfedge_handle( e0, he0 ); - // update F2 - set_face_handle( h1, F2 ); - set_face_handle( he2, F2 ); - set_face_handle( e2, F2 ); - set_next_halfedge_handle( e2, h1 ); - set_next_halfedge_handle( h1, he2 ); - set_next_halfedge_handle( he2, e2 ); - // deal with custom properties - // interpolate at he2 - interpolateAllProps( h1, he0, he2, 0.5 ); - // copy at e0, and e2 - copyAllProps( he2, e0 ); +void TopologicalMesh::update( const Ra::Core::Geometry::TriangleMesh& triMesh ) { + for ( size_t i = 0; i < triMesh.vertices().size(); ++i ) + { + WedgeData wd; + wd.m_position = triMesh.vertices()[i]; + copyMeshToWedgeData( triMesh, + i, + m_wedges.m_wedgeFloatAttribHandles, + m_wedges.m_wedgeVector2AttribHandles, + m_wedges.m_wedgeVector3AttribHandles, + m_wedges.m_wedgeVector4AttribHandles, + &wd ); + m_wedges.setWedgeData( i, wd ); } - else + // update positions + for ( auto itr = halfedges_begin(), stop = halfedges_end(); itr != stop; ++itr ) { - HalfedgeHandle h1 = prev_halfedge_handle( he0 ); - set_next_halfedge_handle( h1, he2 ); - set_next_halfedge_handle( he2, he0 ); - // next halfedge handle of he0 already is h0 - // halfedge handle of V already is he0 - } - - // does F1 exist - if ( !is_boundary( he1 ) ) - { - HalfedgeHandle o1 = next_halfedge_handle( he1 ); - HalfedgeHandle o0 = next_halfedge_handle( o1 ); - // create new edge - VertexHandle B = to_vertex_handle( o1 ); - HalfedgeHandle e1 = new_edge( v, B ); - HalfedgeHandle e3 = opposite_halfedge_handle( e1 ); - // split F1 - FaceHandle F3 = new_face(); - set_halfedge_handle( F3, o1 ); - set_halfedge_handle( F1, he1 ); - // update F1 - set_face_handle( o1, F3 ); - set_face_handle( e3, F3 ); - set_face_handle( he3, F3 ); - set_next_halfedge_handle( he3, o1 ); - set_next_halfedge_handle( o1, e3 ); - set_next_halfedge_handle( e3, he3 ); - // update F3 - set_face_handle( o0, F1 ); - set_face_handle( he1, F1 ); - set_face_handle( e1, F1 ); - set_next_halfedge_handle( he1, e1 ); - set_next_halfedge_handle( e1, o0 ); - set_next_halfedge_handle( o0, he1 ); - // deal with custom properties - // first copy at he3 - copyAllProps( he1, he3 ); - // interpolate at he1 - interpolateAllProps( o0, he3, he1, 0.5 ); - // copy at e1, and e3 - copyAllProps( he1, e3 ); - copyAllProps( o1, e1 ); + point( to_vertex_handle( *itr ) ) = + m_wedges.getWedgeData( getWedgeIndex( *itr ) ).m_position; } - else +} + +void TopologicalMesh::updatePositions( const Ra::Core::Geometry::TriangleMesh& triMesh ) { + updatePositions( triMesh.vertices() ); +} + +void TopologicalMesh::updatePositions( + const AttribArrayGeometry::PointAttribHandle::Container& vertices ) { + + for ( size_t i = 0; i < vertices.size(); ++i ) { - HalfedgeHandle o1 = next_halfedge_handle( he1 ); - // next halfedge handle of o0 already is he1 - set_next_halfedge_handle( he1, he3 ); - set_next_halfedge_handle( he3, o1 ); - // halfedge handle of V already is he0 + m_wedges.m_data[i].getWedgeData().m_position = vertices[i]; + point( m_wedges.m_data[i].getWedgeData().m_vertexHandle ) = vertices[i]; } +} - // ensure consistency at v1 - if ( halfedge_handle( v1 ) == he0 ) { set_halfedge_handle( v1, he2 ); } +void TopologicalMesh::updateNormals( const Ra::Core::Geometry::TriangleMesh& triMesh ) { + auto& normals = triMesh.normals(); - return true; + for ( size_t i = 0; i < triMesh.vertices().size(); ++i ) + { + m_wedges.setWedgeAttrib( i, m_normalsIndex, normals[i] ); + } } template @@ -687,7 +642,7 @@ void TopologicalMesh::split_copy( EdgeHandle _eh, VertexHandle _vh ) { } } -bool TopologicalMesh::splitEdgeWedge( TopologicalMesh::EdgeHandle eh, Scalar f ) { +bool TopologicalMesh::splitEdge( TopologicalMesh::EdgeHandle eh, Scalar f ) { // Global schema of operation /* TRIANGLES ONLY @@ -825,70 +780,186 @@ bool TopologicalMesh::splitEdgeWedge( TopologicalMesh::EdgeHandle eh, Scalar f ) return true; } -void TopologicalMesh::collapseWedge( TopologicalMesh::HalfedgeHandle heh ) { - HalfedgeHandle h = heh; +//----------------------------------------------------------------------------- +void TopologicalMesh::collapse( HalfedgeHandle _hh, bool keepFrom ) { + HalfedgeHandle h0 = _hh; + HalfedgeHandle h1 = next_halfedge_handle( h0 ); + HalfedgeHandle o0 = opposite_halfedge_handle( h0 ); + HalfedgeHandle o1 = next_halfedge_handle( o0 ); + + // remove edge + collapse_edge( h0, keepFrom ); + + // remove loops + if ( next_halfedge_handle( next_halfedge_handle( h1 ) ) == h1 ) + collapse_loop( next_halfedge_handle( h1 ) ); + if ( next_halfedge_handle( next_halfedge_handle( o1 ) ) == o1 ) collapse_loop( o1 ); +} + +//----------------------------------------------------------------------------- +void TopologicalMesh::collapse_edge( HalfedgeHandle _hh, bool keepFrom ) { + HalfedgeHandle h = _hh; HalfedgeHandle hn = next_halfedge_handle( h ); HalfedgeHandle hp = prev_halfedge_handle( h ); - HalfedgeHandle o = opposite_halfedge_handle( h ); - HalfedgeHandle on = next_halfedge_handle( o ); - HalfedgeHandle op = prev_halfedge_handle( o ); + HalfedgeHandle o = opposite_halfedge_handle( h ); + HalfedgeHandle on = next_halfedge_handle( o ); + HalfedgeHandle ono = opposite_halfedge_handle( on ); + HalfedgeHandle op = prev_halfedge_handle( o ); - // FaceHandle fh = face_handle( h ); - // FaceHandle fo = face_handle( o ); + FaceHandle fh = face_handle( h ); + FaceHandle fo = face_handle( o ); VertexHandle vh = to_vertex_handle( h ); VertexHandle vo = to_vertex_handle( o ); - auto position = m_wedges.getWedgeData( property( m_wedgeIndexPph, heh ) ).m_position; - auto widx = property( m_wedgeIndexPph, heh ); - - CORE_ASSERT( widx.isValid(), "try to collapse onto an invalid wedge" ); - CORE_ASSERT( !isFeatureVertex( vo ), "try to collapse a feature vertex" ); - - for ( VertexIHalfedgeIter vih_it( vih_iter( vo ) ); vih_it.is_valid(); ++vih_it ) + auto widx = getWedgeIndex( h ); + if ( widx.isInvalid() ) // i.e. h is boundary + widx = getWedgeIndex( op ); + auto otherWidx = getWedgeIndex( op ); + if ( otherWidx.isInvalid() ) // i.e. h is boundary + otherWidx = getWedgeIndex( h ); + + // halfedge -> vertex + + // manual iter for from fixup + auto currentWidx = widx; + auto ringWidx = WedgeIndex {}; + int phase = 0; + HalfedgeHandle start = prev_halfedge_handle( opposite_halfedge_handle( hp ) ); + HalfedgeHandle vih = start; + do { - // delete and set to new widx - m_wedges.del( property( m_wedgeIndexPph, *vih_it ) ); - property( m_wedgeIndexPph, *vih_it ) = m_wedges.newReference( widx ); + set_vertex_handle( vih, vh ); + if ( !is_boundary( vih ) ) + { + if ( !keepFrom ) + { + if ( phase == 0 ) + { + CORE_ASSERT( ringWidx.isInvalid(), "" ); + phase = 1; + ringWidx = getWedgeIndex( vih ); + } + if ( phase == 1 && ringWidx != getWedgeIndex( vih ) ) + { + CORE_ASSERT( ringWidx.isValid(), "" ); + CORE_ASSERT( getWedgeIndex( vih ).isValid(), "" ); + phase = 2; + currentWidx = otherWidx; + } + replaceWedgeIndex( vih, currentWidx ); + } + else + { m_wedges.setWedgePosition( getWedgeIndex( vih ), point( vh ) ); } + } + vih = prev_halfedge_handle( opposite_halfedge_handle( vih ) ); + } while ( vih != start ); + // Reference version from openmesh + // for ( VertexIHalfedgeIter vih_it( vih_iter( vo ) ); vih_it.is_valid(); ++vih_it ) + // { + // set_vertex_handle( *vih_it, vh ); + // if ( !is_boundary( *vih_it ) ) + // { + // if ( !keepFrom ) { replaceWedgeIndex( *vih_it, widx ); } + // else + // { m_wedges.setWedgePosition( getWedgeIndex( *vih_it ), point( vh ) ); } + // } + // } + + // halfedge -> halfedge + set_next_halfedge_handle( hp, hn ); + if ( !is_boundary( hp ) ) + if ( !keepFrom ) replaceWedgeIndex( hp, widx ); + + set_next_halfedge_handle( op, on ); + + if ( keepFrom ) + { + if ( !is_boundary( op ) ) replaceWedgeIndex( op, getWedgeIndex( ono ) ); } - // but remove one ref for the deleted opposite he - m_wedges.del( property( m_wedgeIndexPph, o ) ); + // face -> halfedge + if ( fh.is_valid() ) set_halfedge_handle( fh, hn ); + if ( fo.is_valid() ) set_halfedge_handle( fo, on ); + + // vertex -> halfedge + if ( halfedge_handle( vh ) == o ) set_halfedge_handle( vh, hn ); + adjust_outgoing_halfedge( vh ); + set_isolated( vo ); + + // delete stuff + status( edge_handle( h ) ).set_deleted( true ); + + status( vo ).set_deleted( true ); + + m_wedges.del( getWedgeIndex( h ) ); + m_wedges.del( getWedgeIndex( o ) ); - // and delete wedge of the remove he - // first if h is not boundary, copy the wedgeIndex of hn to hp to it - if ( !is_boundary( h ) ) + if ( has_halfedge_status() ) { - property( m_wedgeIndexPph, hp ) = - m_wedges.newReference( property( m_wedgeIndexPph, opposite_halfedge_handle( hn ) ) ); + status( h ).set_deleted( true ); + status( o ).set_deleted( true ); } - m_wedges.del( property( m_wedgeIndexPph, hn ) ); - m_wedges.del( property( m_wedgeIndexPph, opposite_halfedge_handle( hn ) ) ); +} + +//----------------------------------------------------------------------------- +void TopologicalMesh::collapse_loop( HalfedgeHandle _hh ) { + HalfedgeHandle h0 = _hh; + HalfedgeHandle h1 = next_halfedge_handle( h0 ); + + HalfedgeHandle o0 = opposite_halfedge_handle( h0 ); + HalfedgeHandle o1 = opposite_halfedge_handle( h1 ); + + VertexHandle v0 = to_vertex_handle( h0 ); + VertexHandle v1 = to_vertex_handle( h1 ); + + FaceHandle fh = face_handle( h0 ); + FaceHandle fo = face_handle( o0 ); + + // is it a loop ? + assert( ( next_halfedge_handle( h1 ) == h0 ) && ( h1 != o0 ) ); + + // halfedge -> halfedge + set_next_halfedge_handle( h1, next_halfedge_handle( o0 ) ); + replaceWedgeIndex( h1, getWedgeIndex( o0 ) ); + set_next_halfedge_handle( prev_halfedge_handle( o0 ), h1 ); - if ( !is_boundary( o ) ) + // halfedge -> face + set_face_handle( h1, fo ); + + // vertex -> halfedge + set_halfedge_handle( v0, h1 ); + adjust_outgoing_halfedge( v0 ); + set_halfedge_handle( v1, o1 ); + adjust_outgoing_halfedge( v1 ); + + // face -> halfedge + if ( fo.is_valid() && halfedge_handle( fo ) == o0 ) { set_halfedge_handle( fo, h1 ); } + + // delete stuff + if ( fh.is_valid() ) { - property( m_wedgeIndexPph, on ) = - m_wedges.newReference( property( m_wedgeIndexPph, opposite_halfedge_handle( op ) ) ); + set_halfedge_handle( fh, InvalidHalfedgeHandle ); + status( fh ).set_deleted( true ); } - m_wedges.del( property( m_wedgeIndexPph, op ) ); - m_wedges.del( property( m_wedgeIndexPph, opposite_halfedge_handle( op ) ) ); + status( edge_handle( h0 ) ).set_deleted( true ); - base::collapse( h ); + m_wedges.del( getWedgeIndex( h0 ) ); + m_wedges.del( getWedgeIndex( o0 ) ); - for ( VertexIHalfedgeIter vih_it( vih_iter( vh ) ); vih_it.is_valid(); ++vih_it ) + if ( has_halfedge_status() ) { - // delete and set to new widx - m_wedges.setWedgePosition( property( m_wedgeIndexPph, *vih_it ), position ); + status( h0 ).set_deleted( true ); + status( o0 ).set_deleted( true ); } } +void TopologicalMesh::collapseWedge( TopologicalMesh::HalfedgeHandle heh, bool keepFromWedges ) { + collapse( heh, keepFromWedges ); +} + void TopologicalMesh::garbage_collection() { - for ( HalfedgeIter he_it = halfedges_begin(); he_it != halfedges_end(); ++he_it ) - { - // already done in collapseWedge - // if ( status( *he_it ).deleted() ) { m_wedges.del(property( - // m_wedgeIndexPph, *he_it )); } - } + // Wedge Ref count is already up to date, do not del again ! auto offset = m_wedges.computeCleanupOffset(); for ( HalfedgeIter he_it = halfedges_begin(); he_it != halfedges_end(); ++he_it ) @@ -932,42 +1003,6 @@ void TopologicalMesh::delete_face( FaceHandle _fh, bool _delete_isolated_vertice base::delete_face( _fh, _delete_isolated_vertices ); } -void TopologicalMesh::InitWedgeProps::operator()( AttribBase* attr ) const { - if ( attr->getSize() != m_triMesh.vertices().size() ) - { LOG( logWARNING ) << "[TopologicalMesh] Skip badly sized attribute " << attr->getName(); } - else if ( attr->getName() != std::string( "in_position" ) ) - { - if ( attr->isFloat() ) - { - m_topo->m_wedges.m_wedgeFloatAttribHandles.push_back( - m_triMesh.getAttribHandle( attr->getName() ) ); - m_topo->m_wedges.addProp( attr->getName() ); - } - else if ( attr->isVector2() ) - { - m_topo->m_wedges.m_wedgeVector2AttribHandles.push_back( - m_triMesh.getAttribHandle( attr->getName() ) ); - m_topo->m_wedges.addProp( attr->getName() ); - } - else if ( attr->isVector3() ) - { - m_topo->m_wedges.m_wedgeVector3AttribHandles.push_back( - m_triMesh.getAttribHandle( attr->getName() ) ); - m_topo->m_wedges.addProp( attr->getName() ); - } - else if ( attr->isVector4() ) - { - m_topo->m_wedges.m_wedgeVector4AttribHandles.push_back( - m_triMesh.getAttribHandle( attr->getName() ) ); - m_topo->m_wedges.addProp( attr->getName() ); - } - else - LOG( logWARNING ) - << "Warning, mesh attribute " << attr->getName() - << " type is not supported (only float, vec2, vec3 nor vec4 are supported)"; - } -} - /////////////// WEDGES RELATED STUFF ///////////////// TopologicalMesh::WedgeIndex diff --git a/src/Core/Geometry/TopologicalMesh.hpp b/src/Core/Geometry/TopologicalMesh.hpp index 92ead49a09c..e6b3640ea24 100644 --- a/src/Core/Geometry/TopologicalMesh.hpp +++ b/src/Core/Geometry/TopologicalMesh.hpp @@ -54,14 +54,28 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT; using base::PolyMesh_ArrayKernelT; - using Index = Ra::Core::Utils::Index; using Vector3 = Ra::Core::Vector3; + using Index = Ra::Core::Utils::Index; class Wedge; + class WedgeCollection; public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW - using WedgeIndex = Index; + class WedgeData; + using WedgeIndex = Ra::Core::Utils::Index; + + /** + * Construct an empty topological mesh, only initialize mandatory properties. + */ + explicit TopologicalMesh(); + + /** + * \brief Convenience constructor + * \see TopologicalMesh( const Ra::Core::Geometry::TriangleMesh&, NonManifoldFaceCommand) + */ + template + explicit TopologicalMesh( const Ra::Core::Geometry::IndexedGeometry& mesh ); /** * Construct a topological mesh from a triangle mesh. @@ -75,25 +89,16 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT - explicit TopologicalMesh( const Ra::Core::Geometry::TriangleMesh& triMesh, + template + explicit TopologicalMesh( const Ra::Core::Geometry::IndexedGeometry& mesh, NonManifoldFaceCommand command ); - /** - * \brief Convenience constructor - * \see TopologicalMesh( const Ra::Core::Geometry::TriangleMesh&, NonManifoldFaceCommand) - */ - explicit TopologicalMesh( const Ra::Core::Geometry::TriangleMesh& triMesh ); - void initWithWedge( const Ra::Core::Geometry::TriangleMesh& triMesh ); - template - void initWithWedge( const Ra::Core::Geometry::TriangleMesh& triMesh, + template + void initWithWedge( const Ra::Core::Geometry::IndexedGeometry& mesh ); + template + void initWithWedge( const Ra::Core::Geometry::IndexedGeometry& mesh, NonManifoldFaceCommand command ); - /** - * Construct an empty topological mesh, only init mandatory properties. - */ - explicit TopologicalMesh(); - /** * Return a triangleMesh from the topological mesh. * \note This is a costly operation. @@ -107,15 +112,22 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT& getOutputTriangleMeshIndexPropHandle() const; - /** - * \name Const access to handles of the HalfEdge properties coming from - * the TriangleMesh attributes. - */ - ///@{ - [[deprecated]] inline const std::vector>& - getFloatPropsHandles() const; - [[deprecated]] inline const std::vector>& - getVector2PropsHandles() const; - [[deprecated]] inline const std::vector>& - getVector3PropsHandles() const; - [[deprecated]] inline const std::vector>& - getVector4PropsHandles() const; - ///@} - /** * \name Dealing with normals * Utils to deal with normals when modifying the mesh topology. */ ///@{ - /** - * Create a new property for normals on faces of \a mesh. - * \note This new property will have to be propagated onto the newly created - * halfedges with copyNormal(). - */ - inline void createNormalPropOnFaces( OpenMesh::FPropHandleT& fProp ); - - /** - * Remove face property \a prop from \a mesh. - * \note Invalidates the property handle. - */ - inline void clearProp( OpenMesh::FPropHandleT& fProp ); - - /** - * Copy the normal property from \a input_heh to \a copy_heh. - */ - inline void copyNormal( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ); - - /** Copy the face normal property \a fProp from \a fh to \a heh. - * \note \a fProp must have been previously created through createNormalPropOnFaces(). - */ - inline void - copyNormalFromFace( FaceHandle fh, HalfedgeHandle heh, OpenMesh::FPropHandleT fProp ); - - /** - * Interpolate normal property on edge center (after edge split). - */ - inline void - interpolateNormal( HalfedgeHandle in_a, HalfedgeHandle in_b, HalfedgeHandle out, Scalar f ); - - /** Interpolate normal property on face center. - * \note \a fProp must have been previously created through createNormalPropOnFaces(). - */ - inline void interpolateNormalOnFaces( FaceHandle fh, OpenMesh::FPropHandleT fProp ); + void updateWedgeNormals(); ///@} - - /** - * \name Dealing with custom properties - * Utils to deal with custom properties of any type when modifying the mesh topology. - */ - ///@{ - - /** - * Create a new property for each \a input properties of \a mesh on faces. - * \note This new property will have to be propagated onto the newly created - * halfedges with copyProps(). - */ - template - void createPropsOnFaces( const std::vector>& input, - std::vector>& output ); - - /** - * Remove \a props from \a mesh. - * \note Clears \a props. - */ - template - void clearProps( std::vector>& props ); - - /** - * Copy \a props properties from \a input_heh to \a copy_heh. - */ - template - void copyProps( HalfedgeHandle input_heh, - HalfedgeHandle copy_heh, - const std::vector>& props ); - - /** - * Copy face properties \a props from \a fh to \a heh. - * \note \a fProps must have been previously created through createPropsOnFaces(). - */ - template - void copyPropsFromFace( FaceHandle fh, - HalfedgeHandle heh, - const std::vector>& fProps, - const std::vector>& hProps ); - - /** - * Interpolate \a props on edge center (after edge split). - */ - template - void interpolateProps( HalfedgeHandle in_a, - HalfedgeHandle in_b, - HalfedgeHandle out, - Scalar f, - const std::vector>& props ); - - /** - * Interpolate \a hprops on face center. - * \note \a fProps must have been previously created through createPropsOnFaces(). - */ - template - void interpolatePropsOnFaces( FaceHandle fh, - const std::vector>& hProps, - const std::vector>& fProps ); - ///@} - /** - * \name Deal with all attributes* Utils to deal with the normal and - custom properties when modifying the mesh topology.*/ - - ///@{ - - /** - * Create a new property for each property of \a mesh on faces. - * Outputs the new face properties handles in the corresponding output parameters. - * \note These new properties will have to be propagated onto the newly created - * halfedges with copyAllProps(). - */ - inline void createAllPropsOnFaces( OpenMesh::FPropHandleT& normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ); - - /** - * Remove all the given properties from \a mesh. - * \note Invalidates \a normalProp and clears the given property containers. - */ - inline void clearAllProps( OpenMesh::FPropHandleT& normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ); - - /** - * Copy all properties from \a input_heh to \a copy_heh. - */ - inline void copyAllProps( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ); - - /** - * Copy all given face properties from \a fh to \a heh. - * \note Each property must have been previously created either all at once - * through createAllPropsOnFaces(), or individually through - * createNormalPropOnFaces() and createPropsOnFaces(). - */ - inline void copyAllPropsFromFace( FaceHandle fh, - HalfedgeHandle heh, - OpenMesh::FPropHandleT normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ); - - /** - * Interpolate all properties on edge center (after edge split). - */ - inline void - interpolateAllProps( HalfedgeHandle in_a, HalfedgeHandle in_b, HalfedgeHandle out, Scalar f ); - - /** - * Interpolate \a hprops on face center. - * \note Each property must have been previously created either all at once - * through createAllPropsOnFaces(), or individually through - * createNormalPropOnFaces() and createPropsOnFaces(). - */ - inline void - interpolateAllPropsOnFaces( FaceHandle fh, - OpenMesh::FPropHandleT normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ); - ///@} - - /** - * Inner class WedgeData represents the actual data per wedge, including position. - * - * At any time m_position as to be equal to the wedge's vertex point. - * All wedges have the same set of attributes. - * Access and management is delegated to TopologicalMesh and WedgeCollection - */ - class WedgeData - { - public: - EIGEN_MAKE_ALIGNED_OPERATOR_NEW - - // Index m_inputTriangleMeshIndex; - // Index m_outputTriangleMeshIndex; - Vector3 m_position {}; - VectorArray m_floatAttrib; - VectorArray m_vector2Attrib; - VectorArray m_vector3Attrib; - VectorArray m_vector4Attrib; - - template - inline VectorArray& getAttribArray(); - - explicit WedgeData() = default; - inline bool operator==( const WedgeData& lhs ) const; - inline bool operator!=( const WedgeData& lhs ) const; - inline bool operator<( const WedgeData& lhs ) const; - friend Wedge; - - private: - // return 1 : equals, 2: strict less, 3: strict greater - template - static int compareVector( const T& a, const T& b ); - }; - /** * \name Topological operations */ @@ -400,7 +188,6 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT + [[deprecated( "use getWedgeAttrib() instead." )]] const T& + getWedgeData( const WedgeIndex& idx, const std::string& name ) const; + template + const T& getWedgeAttrib( const WedgeIndex& idx, const std::string& name ) const; /** * Return the wedge refcount, for debug purpose. @@ -449,7 +243,19 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT - inline bool setWedgeData( const WedgeIndex& idx, const std::string& name, const T& value ); + [[deprecated( "use setWedgeAttrib() instead." )]] inline bool + setWedgeData( const WedgeIndex& idx, const std::string& name, const T& value ); + + template + inline bool setWedgeAttrib( const WedgeIndex& idx, const std::string& name, const T& value ); + + /** return a WedgeData with all attrib initialized to default values */ + inline WedgeData newWedgeData() const { return m_wedges.newWedgeData(); } + /** return a WedgeData with position (and vertex handle) initialized from the value of he's to + * vertex. */ + inline WedgeData newWedgeData( HalfedgeHandle he ) const { + return m_wedges.newWedgeData( to_vertex_handle( he ), point( to_vertex_handle( he ) ) ); + } /** * Replace the wedge data associated with an halfedge. @@ -458,6 +264,11 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT + inline int addWedgeAttrib( const std::string& name, T value = {} ) { + return m_wedges.addAttrib( name, value ); + } + /** * Replace the wedge index associated with an halfedge. * The old wedge is "deleted". The new wedge reference count is incremented. @@ -478,6 +289,10 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT& getVec4AttribNames() const; inline const std::vector& getVec3AttribNames() const; @@ -506,22 +321,51 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT + inline VectorArray& getAttribArray(); + template + inline const VectorArray& getAttribArray() const; + friend Wedge; + + // Index m_inputTriangleMeshIndex; + // Index m_outputTriangleMeshIndex; + VertexHandle m_vertexHandle; + Vector3 m_position {}; + VectorArray m_floatAttrib; + VectorArray m_vector2Attrib; + VectorArray m_vector3Attrib; + VectorArray m_vector4Attrib; private: - TopologicalMesh* m_topo; - const TriangleMesh& m_triMesh; + // return 1 : equals, 2: strict less, 3: strict greater + template + static int compareVector( const T& a, const T& b ); }; - class WedgeCollection; - // + private: + // base on openmesh version + void collapse_edge( HalfedgeHandle, bool ); + void collapse_loop( HalfedgeHandle ); + /** * This private class manage wedge data and refcount, to maintain deleted status * @@ -529,12 +373,6 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT - inline const std::vector& getNameArray() const; - - template - inline std::vector& getNameArray(); - /** * Add wd to the wedge collection, and return the index. * If a wedge with same data is already present, it's index is returned, @@ -589,54 +428,88 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT + inline const T& getWedgeData( const WedgeIndex& idx, const std::string& name ) const; + template + inline T& getWedgeData( const WedgeIndex& idx, int attribIndex ); + template + inline const T& getWedgeAttrib( const WedgeIndex& idx, const std::string& name ) const; + template + inline T& getWedgeAttrib( const WedgeIndex& idx, int attribIndex ); + + unsigned int getWedgeRefCount( const WedgeIndex& idx ) const; + /// \see TopologicalMesh::setWedgeData inline void setWedgeData( const WedgeIndex& idx, const WedgeData& wd ); - /// \see TopologicalMesh::setWedgeData + /// change WedgeData member name to value. + /// wd is moidified accordingly. + /// \return false if name is not of type T + /// \retrun true on sucess template - inline bool setWedgeData( const WedgeIndex& idx, const std::string& name, const T& value ); + inline bool + setWedgeAttrib( TopologicalMesh::WedgeData& wd, const std::string& name, const T& value ); + template + inline bool + setWedgeAttrib( const WedgeIndex& idx, const std::string& name, const T& value ); + template + inline void setWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + const int& attribIndex, + const T& value ); + + template + inline int getWedgeAttribIndex( const std::string& name ); + inline bool setWedgePosition( const WedgeIndex& idx, const Vector3& value ); + /// management + + template + inline const std::vector& getNameArray() const; + // name is supposed to be unique within all attribs // not checks are performed + // return the index of the the newly added attrib. + template + int addAttribName( const std::string& name ); + + // add attrib to all wedges with default value value template - void addProp( const std::string& name ); + int addAttrib( const std::string& name, const T& value = {} ); /// return the offset ot apply to each wedgeindex so that /// after garbageCollection all indices are valid and coherent. std::vector computeCleanupOffset() const; - - /// remove unreferenced wedge, halfedges need to be reindexed. - inline void garbageCollection(); - /// \todo removeDuplicateWedge /// merge wedges with same data /// return old->new index correspondance to update wedgeIndexPph /// inline void removeDuplicateWedge + inline size_t size() const { return m_data.size(); } + /// remove unreferenced wedge, halfedges need to be reindexed. + inline void garbageCollection(); + + inline void clean(); + + // return a new wedgeData with uninit values. + inline WedgeData newWedgeData() const; + inline WedgeData newWedgeData( TopologicalMesh::VertexHandle vh, + TopologicalMesh::Point p ) const; + + ///\ todo private: /// attrib names associated to vertex/wedges, getted from CoreMesh, if any, std::vector m_floatAttribNames; std::vector m_vector2AttribNames; @@ -644,24 +517,59 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT m_vector4AttribNames; /// attrib handle from the CoreMesh given at construction, if any. + /// used by TopologicalMesh::update() std::vector> m_wedgeFloatAttribHandles; std::vector> m_wedgeVector2AttribHandles; std::vector> m_wedgeVector3AttribHandles; std::vector> m_wedgeVector4AttribHandles; - // private: + template + inline std::vector& getNameArray(); AlignedStdVector m_data; }; - WedgeData interpolateWedgeAttributes( const WedgeData&, const WedgeData&, Scalar alpha ); + // internal function to build Core Mesh attribs correspondance to wedge attribs. + template + class InitWedgeAttribs + { + public: + InitWedgeAttribs( TopologicalMesh* topo, + const Ra::Core::Geometry::IndexedGeometry& triMesh ) : + m_topo( topo ), m_triMesh( triMesh ) {} + void operator()( AttribBase* attr ) const; + + private: + TopologicalMesh* m_topo; + const Ra::Core::Geometry::IndexedGeometry& m_triMesh; + }; + //! [Default command implementation] template - inline void copyAttribToWedgeData( const TriangleMesh& mesh, + struct DefaultNonManifoldFaceCommand { + /// \brief details string is printed along with the message + DefaultNonManifoldFaceCommand( std::string details = {} ) : m_details {details} {} + /// \brief Initalize with input Ra::Core::Geometry::TriangleMesh + inline void initialize( const Ra::Core::Geometry::IndexedGeometry& ) {} + /// \brief Process non-manifold face + inline void process( const std::vector& /*face_vhandles*/ ) { + LOG( logWARNING ) << "Invalid face handle returned : face not added " + m_details; + } + /// \brief If needed, apply post-processing on the Ra::Core::Geometry::TopologicalMesh + inline void postProcess( TopologicalMesh& ) {} + //! [Default command implementation] + private: + std::string m_details; + }; + + WedgeData interpolateWedgeAttributes( const WedgeData&, const WedgeData&, Scalar alpha ); + + template + inline void copyAttribToWedgeData( const Ra::Core::Geometry::IndexedGeometry& mesh, unsigned int vindex, const std::vector>& attrHandleVec, VectorArray* to ); - - inline void copyMeshToWedgeData( const TriangleMesh& mesh, + template + inline void copyMeshToWedgeData( const Ra::Core::Geometry::IndexedGeometry& mesh, unsigned int vindex, const std::vector>& wprop_float, const std::vector>& wprop_vec2, @@ -674,21 +582,6 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT, T>, Eigen::aligned_allocator, T>>>; - template - using PropPair = std::pair, OpenMesh::HPropHandleT>; - - template - [[deprecated]] inline void copyAttribToTopo( const TriangleMesh& triMesh, - const std::vector>& vprop, - TopologicalMesh::HalfedgeHandle heh, - unsigned int vindex ); - - template - [[deprecated]] inline void addAttribPairToTopo( const TriangleMesh& triMesh, - AttribManager::pointer_type attr, - std::vector>& vprop, - std::vector>& pph ); - void split_copy( EdgeHandle _eh, VertexHandle _vh ); void split( EdgeHandle _eh, VertexHandle _vh ); @@ -698,10 +591,10 @@ class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT m_inputTriangleMeshIndexPph; OpenMesh::HPropHandleT m_outputTriangleMeshIndexPph; - [[deprecated]] std::vector> m_floatPph; - [[deprecated]] std::vector> m_vec2Pph; - [[deprecated]] std::vector> m_vec3Pph; - [[deprecated]] std::vector> m_vec4Pph; + + int m_normalsIndex {-1}; + // vertex handle idx -> face handle idx -> wedge idx with the same normal + std::vector>> m_vertexFaceWedgesWithSameNormals; friend class TMOperations; }; diff --git a/src/Core/Geometry/TopologicalMesh.inl b/src/Core/Geometry/TopologicalMesh.inl index d6cbe5d19bf..39ad33a2fa3 100644 --- a/src/Core/Geometry/TopologicalMesh.inl +++ b/src/Core/Geometry/TopologicalMesh.inl @@ -9,239 +9,464 @@ namespace Ra { namespace Core { namespace Geometry { -template -inline TopologicalMesh::TopologicalMesh( const TriangleMesh& triMesh, - NonManifoldFaceCommand command ) : - TopologicalMesh() { +//////////////////////////////////////////////////////////////////////////////// +/////////////////// WedgeData ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// - LOG( logINFO ) << "TopologicalMesh: load triMesh with " << triMesh.getIndices().size() - << " faces and " << triMesh.vertices().size() << " vertices."; +inline bool TopologicalMesh::WedgeData::operator==( const TopologicalMesh::WedgeData& lhs ) const { + return + // do not have this yet, not sure we need to test them + // m_inputTriangleMeshIndex == lhs.m_inputTriangleMeshIndex && + // m_outputTriangleMeshIndex == lhs.m_outputTriangleMeshIndex && + m_position == lhs.m_position && m_floatAttrib == lhs.m_floatAttrib && + m_vector2Attrib == lhs.m_vector2Attrib && m_vector3Attrib == lhs.m_vector3Attrib && + m_vector4Attrib == lhs.m_vector4Attrib; +} - struct hash_vec { - size_t operator()( const Vector3& lvalue ) const { - size_t hx = std::hash()( lvalue[0] ); - size_t hy = std::hash()( lvalue[1] ); - size_t hz = std::hash()( lvalue[2] ); - return ( hx ^ ( hy << 1 ) ) ^ hz; - } - }; - // use a hashmap for fast search of existing vertex position - using VertexMap = std::unordered_map; - VertexMap vertexHandles; +bool TopologicalMesh::WedgeData::operator!=( const TopologicalMesh::WedgeData& lhs ) const { + return !( *this == lhs ); +} - std::vector> vprop_float; - std::vector, OpenMesh::HPropHandleT>> vprop_vec2; - std::vector, OpenMesh::HPropHandleT>> vprop_vec3; - std::vector, OpenMesh::HPropHandleT>> vprop_vec4; +inline bool TopologicalMesh::WedgeData::operator<( const TopologicalMesh::WedgeData& lhs ) const { - // loop over all attribs and build correspondance pair - triMesh.vertexAttribs().for_each_attrib( - [&triMesh, this, &vprop_float, &vprop_vec2, &vprop_vec3, &vprop_vec4]( const auto& attr ) { - // skip builtin attribs - if ( attr->getName() != std::string( "in_position" ) && - attr->getName() != std::string( "in_normal" ) ) - { - if ( attr->isFloat() ) - addAttribPairToTopo( triMesh, attr, vprop_float, m_floatPph ); - else if ( attr->isVector2() ) - addAttribPairToTopo( triMesh, attr, vprop_vec2, m_vec2Pph ); - else if ( attr->isVector3() ) - addAttribPairToTopo( triMesh, attr, vprop_vec3, m_vec3Pph ); - else if ( attr->isVector4() ) - addAttribPairToTopo( triMesh, attr, vprop_vec4, m_vec4Pph ); - else - LOG( logWARNING ) - << "Warning, mesh attribute " << attr->getName() - << " type is not supported (only float, vec2, vec3 nor vec4 are supported)"; - } - } ); + CORE_ASSERT( ( m_floatAttrib.size() == lhs.m_floatAttrib.size() ) && + ( m_vector2Attrib.size() == lhs.m_vector2Attrib.size() ) && + ( m_vector3Attrib.size() == lhs.m_vector3Attrib.size() ) && + ( m_vector4Attrib.size() == lhs.m_vector4Attrib.size() ), + "Could only compare wedge with same number of attributes" ); - // loop over all attribs and build correspondance pair - triMesh.vertexAttribs().for_each_attrib( InitWedgeProps {this, triMesh} ); + { + int comp = compareVector( m_position, lhs.m_position ); + if ( comp == 2 ) return true; + if ( comp == 3 ) return false; + } + for ( size_t i = 0; i < m_floatAttrib.size(); i++ ) + { + if ( m_floatAttrib[i] < lhs.m_floatAttrib[i] ) + return true; + else if ( m_floatAttrib[i] > lhs.m_floatAttrib[i] ) + return false; + } - size_t num_triangles = triMesh.getIndices().size(); + for ( size_t i = 0; i < m_vector2Attrib.size(); i++ ) + { + int comp = compareVector( m_vector2Attrib[i], lhs.m_vector2Attrib[i] ); + if ( comp == 2 ) return true; + if ( comp == 3 ) return false; + } + for ( size_t i = 0; i < m_vector3Attrib.size(); i++ ) + { + int comp = compareVector( m_vector3Attrib[i], lhs.m_vector3Attrib[i] ); + if ( comp == 2 ) return true; + if ( comp == 3 ) return false; + } + for ( size_t i = 0; i < m_vector4Attrib.size(); i++ ) + { + int comp = compareVector( m_vector4Attrib[i], lhs.m_vector4Attrib[i] ); + if ( comp == 2 ) return true; + if ( comp == 3 ) return false; + } + return false; +} - command.initialize( triMesh ); +#define GET_ATTRIB_ARRAY_HELPER( TYPE, NAME ) \ + template <> \ + inline VectorArray& TopologicalMesh::WedgeData::getAttribArray() { \ + return m_##NAME##Attrib; \ + } \ + template <> \ + inline const VectorArray& TopologicalMesh::WedgeData::getAttribArray() const { \ + return m_##NAME##Attrib; \ + } - const bool hasNormals = !triMesh.normals().empty(); - if ( !hasNormals ) +GET_ATTRIB_ARRAY_HELPER( float, float ) +GET_ATTRIB_ARRAY_HELPER( Vector2, vector2 ) +GET_ATTRIB_ARRAY_HELPER( Vector3, vector3 ) +GET_ATTRIB_ARRAY_HELPER( Vector4, vector4 ) +#undef GET_ATTRIB_ARRAY_HELPER + +template +inline VectorArray& TopologicalMesh::WedgeData::getAttribArray() { + static_assert( sizeof( T ) == -1, "this type is not supported" ); +} + +// return 1 : equals, 2: strict less, 3: strict greater +template +int TopologicalMesh::WedgeData::compareVector( const T& a, const T& b ) { + for ( int i = 0; i < T::RowsAtCompileTime; i++ ) { - release_face_normals(); - release_vertex_normals(); - release_halfedge_normals(); + if ( a[i] < b[i] ) return 2; + if ( a[i] > b[i] ) return 3; } - for ( unsigned int i = 0; i < num_triangles; i++ ) - { - std::vector face_vhandles( 3 ); - std::vector face_normals( 3 ); - std::vector face_vertexIndex( 3 ); - std::vector face_wedges( 3 ); - const auto& triangle = triMesh.getIndices()[i]; - for ( size_t j = 0; j < 3; ++j ) - { - unsigned int inMeshVertexIndex = triangle[j]; - const Vector3& p = triMesh.vertices()[inMeshVertexIndex]; + // (a == b) + return 1; +} - typename VertexMap::iterator vtr = vertexHandles.find( p ); +//////////////////////////////////////////////////////////////////////////////// +/////////////////// Wedge ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// - TopologicalMesh::VertexHandle vh; - if ( vtr == vertexHandles.end() ) - { - vh = add_vertex( p ); - vertexHandles.insert( vtr, typename VertexMap::value_type( p, vh ) ); - } - else - { vh = vtr->second; } +// all in class for the moment - face_vhandles[j] = vh; - face_vertexIndex[j] = inMeshVertexIndex; - if ( hasNormals ) face_normals[j] = triMesh.normals()[inMeshVertexIndex]; +//////////////////////////////////////////////////////////////////////////////// +/////////////////// WedgeCollection ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// - WedgeData wd; - wd.m_position = p; +inline void TopologicalMesh::WedgeCollection::del( const TopologicalMesh::WedgeIndex& idx ) { + if ( idx.isValid() ) m_data[idx].decrementRefCount(); +} - copyMeshToWedgeData( triMesh, - inMeshVertexIndex, - m_wedges.m_wedgeFloatAttribHandles, - m_wedges.m_wedgeVector2AttribHandles, - m_wedges.m_wedgeVector3AttribHandles, - m_wedges.m_wedgeVector4AttribHandles, - &wd ); +inline TopologicalMesh::WedgeIndex +TopologicalMesh::WedgeCollection::newReference( const TopologicalMesh::WedgeIndex& idx ) { + if ( idx.isValid() ) m_data[idx].incrementRefCount(); + return idx; +} - face_wedges[j] = m_wedges.add( wd ); - } +inline const TopologicalMesh::Wedge& +TopologicalMesh::WedgeCollection::getWedge( const TopologicalMesh::WedgeIndex& idx ) const { + return m_data[idx]; +} - // take care of degen, see below in method initWithWedge for comments - // copied from initFromWedges EXECPT THE TWO LINES WITH COMMENT - // x-----------------------------------------------------------------------------------x - { - auto begin = face_vhandles.begin(); - if ( face_vhandles.size() > 2 ) - { - auto end = face_vhandles.end() - 1; - auto wedgeEnd = face_wedges.end() - 1; - auto normalEnd = face_normals.end() - 1; +inline const TopologicalMesh::WedgeData& +TopologicalMesh::WedgeCollection::getWedgeData( const WedgeIndex& idx ) const { + CORE_ASSERT( idx.isValid() && !m_data[idx].isDeleted(), + "access to invalid or deleted wedge is prohibited" ); - while ( begin != end && *begin == *end ) - { - end--; - // WARNING explicit del wedge (not needed in initWithWedges) - m_wedges.del( *wedgeEnd ); - wedgeEnd--; - normalEnd--; - } - face_vhandles.erase( end + 1, face_vhandles.end() ); - face_wedges.erase( wedgeEnd + 1, face_wedges.end() ); - face_normals.erase( normalEnd + 1, face_normals.end() ); - } - } + return m_data[idx].getWedgeData(); +} - { - auto first = face_vhandles.begin(); - auto wedgeFirst = face_wedges.begin(); - auto normalFirst = face_normals.begin(); - auto last = face_vhandles.end(); +template +inline const T& +TopologicalMesh::WedgeCollection::getWedgeData( const TopologicalMesh::WedgeIndex& idx, + const std::string& name ) const { + return getWedgeAttrib( idx, name ); +} - if ( first != last ) - { - auto result = first; - auto wedgeResult = wedgeFirst; - auto normalResult = normalFirst; - while ( ++first != last ) - { - if ( !( *result == *first ) ) - { - ++result; - ++wedgeResult; - ++normalResult; - if ( result != first ) - { - *result = std::move( *first ); - // WARNING explicit del wedge (not needed in initWithWedges) - m_wedges.del( *wedgeResult ); - *wedgeResult = std::move( *wedgeFirst ); - *normalResult = std::move( *normalFirst ); - } - } - } - face_vhandles.erase( result + 1, face_vhandles.end() ); - face_wedges.erase( wedgeResult + 1, face_wedges.end() ); - face_normals.erase( normalResult + 1, face_normals.end() ); - } +template +inline const T& +TopologicalMesh::WedgeCollection::getWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + const std::string& name ) const { + if ( idx.isValid() ) + { + auto nameArray = getNameArray(); + auto itr = std::find( nameArray.begin(), nameArray.end(), name ); + if ( itr != nameArray.end() ) + { + auto attrIndex = std::distance( nameArray.begin(), itr ); + return m_data[idx].getWedgeData().getAttribArray()[attrIndex]; } - TopologicalMesh::FaceHandle fh; - // skip 2 vertex face - if ( face_vhandles.size() > 2 ) fh = add_face( face_vhandles ); - // x-----------------------------------------------------------------------------------x + else + { + LOG( logERROR ) << "Warning, set wedge: no wedge attrib named " << name << " of type " + << typeid( T ).name(); + } + } + static T dummy; + return dummy; +} - if ( fh.is_valid() ) +template +inline T& TopologicalMesh::WedgeCollection::getWedgeData( const TopologicalMesh::WedgeIndex& idx, + int attribIndex ) { + return getWedgeAttrib( idx, attribIndex ); +} + +template +inline T& TopologicalMesh::WedgeCollection::getWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + int attribIndex ) { + return m_data[idx].getWedgeData().getAttribArray()[attribIndex]; +} + +inline unsigned int +TopologicalMesh::WedgeCollection::getWedgeRefCount( const WedgeIndex& idx ) const { + CORE_ASSERT( idx.isValid(), "access to invalid or deleted wedge is prohibited" ); + return m_data[idx].getRefCount(); +} + +inline void TopologicalMesh::WedgeCollection::setWedgeData( const TopologicalMesh::WedgeIndex& idx, + const TopologicalMesh::WedgeData& wd ) { + if ( !( wd.m_floatAttrib.size() == m_floatAttribNames.size() && + wd.m_vector2Attrib.size() == m_vector2AttribNames.size() && + wd.m_vector3Attrib.size() == m_vector3AttribNames.size() && + wd.m_vector4Attrib.size() == m_vector4AttribNames.size() ) ) + { + LOG( logWARNING ) << "Warning, topological mesh set wedge: number of attribs inconsistency"; + } + if ( idx.isValid() ) m_data[idx].setWedgeData( wd ); +} + +template +inline bool TopologicalMesh::WedgeCollection::setWedgeAttrib( TopologicalMesh::WedgeData& wd, + const std::string& name, + const T& value ) { + auto nameArray = getNameArray(); + auto itr = std::find( nameArray.begin(), nameArray.end(), name ); + if ( itr != nameArray.end() ) + { + auto attrIndex = std::distance( nameArray.begin(), itr ); + wd.getAttribArray()[attrIndex] = value; + return true; + } + else + { + LOG( logERROR ) << "Warning, set wedge: no wedge attrib named " << name << " of type " + << typeid( T ).name(); + } + return false; +} + +template +inline bool +TopologicalMesh::WedgeCollection::setWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + const std::string& name, + const T& value ) { + if ( idx.isValid() ) + { + auto nameArray = getNameArray(); + auto itr = std::find( nameArray.begin(), nameArray.end(), name ); + if ( itr != nameArray.end() ) { - for ( size_t vindex = 0; vindex < face_vhandles.size(); vindex++ ) - { - TopologicalMesh::HalfedgeHandle heh = halfedge_handle( face_vhandles[vindex], fh ); - if ( hasNormals ) set_normal( heh, face_normals[vindex] ); - property( m_inputTriangleMeshIndexPph, heh ) = face_vertexIndex[vindex]; - copyAttribToTopo( triMesh, vprop_float, heh, face_vertexIndex[vindex] ); - copyAttribToTopo( triMesh, vprop_vec2, heh, face_vertexIndex[vindex] ); - copyAttribToTopo( triMesh, vprop_vec3, heh, face_vertexIndex[vindex] ); - copyAttribToTopo( triMesh, vprop_vec4, heh, face_vertexIndex[vindex] ); - property( m_wedgeIndexPph, heh ) = face_wedges[vindex]; - } + auto attrIndex = std::distance( nameArray.begin(), itr ); + m_data[idx].getWedgeData().getAttribArray()[attrIndex] = value; + return true; } else { - for ( auto wedgeIndex : face_wedges ) - m_wedges.del( wedgeIndex ); - command.process( face_vhandles ); + LOG( logERROR ) << "Warning, set wedge: no wedge attrib named " << name << " of type " + << typeid( T ).name(); } - face_vhandles.clear(); - face_normals.clear(); - face_vertexIndex.clear(); } - command.postProcess( *this ); + return false; +} - // grabage collect since some wedge might already be deleted - garbage_collection(); +template +inline void +TopologicalMesh::WedgeCollection::setWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + const int& attrIndex, + const T& value ) { + m_data[idx].getWedgeData().getAttribArray()[attrIndex] = value; } -template -void TopologicalMesh::initWithWedge( const TriangleMesh& triMesh, NonManifoldFaceCommand command ) { +template +inline int TopologicalMesh::WedgeCollection::getWedgeAttribIndex( const std::string& name ) { + auto nameArray = getNameArray(); + auto itr = std::find( nameArray.begin(), nameArray.end(), name ); + if ( itr != nameArray.end() ) { return std::distance( nameArray.begin(), itr ); } + return 0; +} - LOG( logINFO ) << "TopologicalMesh: load triMesh with " << triMesh.getIndices().size() - << " faces and " << triMesh.vertices().size() << " vertices."; +inline bool +TopologicalMesh::WedgeCollection::setWedgePosition( const TopologicalMesh::WedgeIndex& idx, + const Vector3& value ) { + if ( idx.isValid() ) + { + m_data[idx].getWedgeData().m_position = value; + return true; + } + return false; +} - ///\todo use a kdtree - struct hash_vec { - size_t operator()( const Vector3& lvalue ) const { - size_t hx = std::hash()( lvalue[0] ); - size_t hy = std::hash()( lvalue[1] ); - size_t hz = std::hash()( lvalue[2] ); - return ( hx ^ ( hy << 1 ) ) ^ hz; +#define GET_NAME_ARRAY_HELPER( TYPE, NAME ) \ + template <> \ + inline const std::vector& TopologicalMesh::WedgeCollection::getNameArray() \ + const { \ + return m_##NAME##AttribNames; \ + } \ + template <> \ + inline std::vector& TopologicalMesh::WedgeCollection::getNameArray() { \ + return m_##NAME##AttribNames; \ + } + +GET_NAME_ARRAY_HELPER( float, float ) +GET_NAME_ARRAY_HELPER( Vector2, vector2 ) +GET_NAME_ARRAY_HELPER( Vector3, vector3 ) +GET_NAME_ARRAY_HELPER( Vector4, vector4 ) + +#undef GET_NAME_ARRAY_HELPER +// These template functions are defined above for supported types. +// For unsupported types they simply generate a compile error. +template +inline const std::vector& TopologicalMesh::WedgeCollection::getNameArray() const { + + LOG( logWARNING ) << "Warning, mesh attribute " << typeid( T ).name() + << " is not supported (only float, vec2, vec3 nor vec4 are supported)"; + static_assert( sizeof( T ) == -1, "this type is not supported" ); + return m_floatAttribNames; +} + +template +inline std::vector& TopologicalMesh::WedgeCollection::getNameArray() { + + LOG( logWARNING ) << "Warning, mesh attribute " << typeid( T ).name() + << " is not supported (only float, vec2, vec3 nor vec4 are supported)"; + static_assert( sizeof( T ) == -1, "this type is not supported" ); + return m_floatAttribNames; +} +template +int TopologicalMesh::WedgeCollection::addAttribName( const std::string& name ) { + if ( name != std::string( "in_position" ) ) { getNameArray().push_back( name ); } + return getNameArray().size() - 1; +} + +template +int TopologicalMesh::WedgeCollection::addAttrib( const std::string& name, const T& value ) { + + auto index = addAttribName( name ); + for ( auto& w : m_data ) + { + CORE_ASSERT( index = w.getWedgeData().getAttribArray().size(), + "inconsistent wedge attrib" ); + w.getWedgeData().getAttribArray().push_back( value ); + } + return index; +} + +inline void TopologicalMesh::WedgeCollection::garbageCollection() { + m_data.erase( std::remove_if( m_data.begin(), + m_data.end(), + []( const Wedge& w ) { return w.isDeleted(); } ), + m_data.end() ); +} + +inline void TopologicalMesh::WedgeCollection::clean() { + m_data.clear(); + m_floatAttribNames.clear(); + m_vector2AttribNames.clear(); + m_vector3AttribNames.clear(); + m_vector4AttribNames.clear(); + m_wedgeFloatAttribHandles.clear(); + m_wedgeVector2AttribHandles.clear(); + m_wedgeVector3AttribHandles.clear(); + m_wedgeVector4AttribHandles.clear(); +} + +template +void init( VectorArray& vec, const std::vector names ) { + for ( size_t i = 0; i < names.size(); ++i ) + { + vec.emplace_back(); + } +} +// return a new wedgeData with uninit values. +inline TopologicalMesh::WedgeData TopologicalMesh::WedgeCollection::newWedgeData() const { + WedgeData ret; + init( ret.getAttribArray(), m_floatAttribNames ); + init( ret.getAttribArray(), m_vector2AttribNames ); + init( ret.getAttribArray(), m_vector3AttribNames ); + init( ret.getAttribArray(), m_vector4AttribNames ); + return ret; +} + +inline TopologicalMesh::WedgeData +TopologicalMesh::WedgeCollection::newWedgeData( TopologicalMesh::VertexHandle vh, + TopologicalMesh::Point p ) const { + WedgeData ret = newWedgeData(); + ret.m_vertexHandle = vh; + ret.m_position = p; + return ret; +} + +//////////////////////////////////////////////////////////////////////////////// +/////////////////// InitWedgeProps ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +template +void TopologicalMesh::InitWedgeAttribs::operator()( AttribBase* attr ) const { + if ( attr->getSize() != m_triMesh.vertices().size() ) + { LOG( logWARNING ) << "[TopologicalMesh] Skip badly sized attribute " << attr->getName(); } + else if ( attr->getName() != std::string( "in_position" ) ) + { + if ( attr->isFloat() ) + { + m_topo->m_wedges.m_wedgeFloatAttribHandles.push_back( + m_triMesh.template getAttribHandle( attr->getName() ) ); + m_topo->m_wedges.addAttribName( attr->getName() ); + } + else if ( attr->isVector2() ) + { + m_topo->m_wedges.m_wedgeVector2AttribHandles.push_back( + m_triMesh.template getAttribHandle( attr->getName() ) ); + m_topo->m_wedges.addAttribName( attr->getName() ); + } + else if ( attr->isVector3() ) + { + m_topo->m_wedges.m_wedgeVector3AttribHandles.push_back( + m_triMesh.template getAttribHandle( attr->getName() ) ); + m_topo->m_wedges.addAttribName( attr->getName() ); + } + else if ( attr->isVector4() ) + { + m_topo->m_wedges.m_wedgeVector4AttribHandles.push_back( + m_triMesh.template getAttribHandle( attr->getName() ) ); + m_topo->m_wedges.addAttribName( attr->getName() ); } - }; + else + LOG( logWARNING ) + << "Warning, mesh attribute " << attr->getName() + << " type is not supported (only float, vec2, vec3 nor vec4 are supported)"; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/////////////////// TopologicalMesh ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +struct hash_vec { + std::size_t operator()( const Vector3& lvalue ) const { + size_t hx = std::hash()( lvalue[0] ); + size_t hy = std::hash()( lvalue[1] ); + size_t hz = std::hash()( lvalue[2] ); + return ( hx ^ ( hy << 1 ) ) ^ hz; + } +}; + +template +TopologicalMesh::TopologicalMesh( const Ra::Core::Geometry::IndexedGeometry& mesh ) : + TopologicalMesh( mesh, DefaultNonManifoldFaceCommand( "[default ctor (props)]" ) ) {} + +template +TopologicalMesh::TopologicalMesh( const IndexedGeometry& mesh, + NonManifoldFaceCommand command ) : + TopologicalMesh() { + initWithWedge( mesh, command ); +} + +template +void TopologicalMesh::initWithWedge( const IndexedGeometry& mesh ) { + initWithWedge( mesh, DefaultNonManifoldFaceCommand( "[initWithWedges]" ) ); +} + +template +void TopologicalMesh::initWithWedge( const IndexedGeometry& mesh, + NonManifoldFaceCommand command ) { + + clean(); + + LOG( logINFO ) << "TopologicalMesh: load mesh with " << mesh.getIndices().size() + << " faces and " << mesh.vertices().size() << " vertices."; // use a hashmap for fast search of existing vertex position using VertexMap = std::unordered_map; VertexMap vertexHandles; // loop over all attribs and build correspondance pair - triMesh.vertexAttribs().for_each_attrib( InitWedgeProps {this, triMesh} ); + mesh.vertexAttribs().for_each_attrib( InitWedgeAttribs {this, mesh} ); - size_t num_triangles = triMesh.getIndices().size(); + size_t num_triangles = mesh.getIndices().size(); - for ( size_t i = 0; i < triMesh.vertices().size(); ++i ) + for ( size_t i = 0; i < mesh.vertices().size(); ++i ) { // create an empty wedge, with 0 ref Wedge w; WedgeData wd; - wd.m_position = triMesh.vertices()[i]; - copyMeshToWedgeData( triMesh, - i, - m_wedges.m_wedgeFloatAttribHandles, - m_wedges.m_wedgeVector2AttribHandles, - m_wedges.m_wedgeVector3AttribHandles, - m_wedges.m_wedgeVector4AttribHandles, - &wd ); + wd.m_position = mesh.vertices()[i]; + copyMeshToWedgeData( mesh, + i, + m_wedges.m_wedgeFloatAttribHandles, + m_wedges.m_wedgeVector2AttribHandles, + m_wedges.m_wedgeVector3AttribHandles, + m_wedges.m_wedgeVector4AttribHandles, + &wd ); // here ref is not incremented w.setWedgeData( std::move( wd ) ); // the newly added wedge is not referenced yet, will be done with `newReference` when @@ -251,7 +476,7 @@ void TopologicalMesh::initWithWedge( const TriangleMesh& triMesh, NonManifoldFac LOG( logINFO ) << "TopologicalMesh: have " << m_wedges.size() << " wedges "; - const bool hasNormals = !triMesh.normals().empty(); + const bool hasNormals = !mesh.normals().empty(); if ( !hasNormals ) { release_face_normals(); @@ -259,18 +484,19 @@ void TopologicalMesh::initWithWedge( const TriangleMesh& triMesh, NonManifoldFac release_halfedge_normals(); } - command.initialize( triMesh ); + command.initialize( mesh ); for ( unsigned int i = 0; i < num_triangles; i++ ) { - std::vector face_vhandles( 3 ); - std::vector face_normals( 3 ); - std::vector face_wedges( 3 ); - const auto& triangle = triMesh.getIndices()[i]; + const auto& face = mesh.getIndices()[i]; + const size_t num_vert = face.size(); + std::vector face_vhandles( num_vert ); + std::vector face_normals( num_vert ); + std::vector face_wedges( num_vert ); - for ( size_t j = 0; j < 3; ++j ) + for ( size_t j = 0; j < num_vert; ++j ) { - unsigned int inMeshVertexIndex = triangle[j]; - const Vector3& p = triMesh.vertices()[inMeshVertexIndex]; + unsigned int inMeshVertexIndex = face[j]; + const Vector3& p = mesh.vertices()[inMeshVertexIndex]; typename VertexMap::iterator vtr = vertexHandles.find( p ); TopologicalMesh::VertexHandle vh; @@ -283,8 +509,9 @@ void TopologicalMesh::initWithWedge( const TriangleMesh& triMesh, NonManifoldFac { vh = vtr->second; } face_vhandles[j] = vh; - if ( hasNormals ) face_normals[j] = triMesh.normals()[inMeshVertexIndex]; + if ( hasNormals ) face_normals[j] = mesh.normals()[inMeshVertexIndex]; face_wedges[j] = WedgeIndex {inMeshVertexIndex}; + m_wedges.m_data[inMeshVertexIndex].getWedgeData().m_vertexHandle = vh; } // remove consecutive equal vertex @@ -372,43 +599,60 @@ void TopologicalMesh::initWithWedge( const TriangleMesh& triMesh, NonManifoldFac face_normals.clear(); } command.postProcess( *this ); + if ( hasNormals ) + { + m_normalsIndex = m_wedges.getWedgeAttribIndex( "in_normal" ); + m_vertexFaceWedgesWithSameNormals.clear(); + m_vertexFaceWedgesWithSameNormals.resize( n_vertices() ); + + for ( auto itr = vertices_begin(), stop = vertices_end(); itr != stop; ++itr ) + { + std::unordered_map, std::set>, + hash_vec> + normalSharedByWedges; + + auto vh = *itr; + + for ( ConstVertexIHalfedgeIter vh_it = cvih_iter( vh ); vh_it.is_valid(); ++vh_it ) + { + const auto& widx = property( m_wedgeIndexPph, *vh_it ); + if ( widx.isValid() && !m_wedges.getWedge( widx ).isDeleted() ) + { + auto oldNormal = m_wedges.getWedgeData( widx, m_normalsIndex ); + normalSharedByWedges[oldNormal].first.insert( face_handle( *vh_it ) ); + normalSharedByWedges[oldNormal].second.insert( widx ); + } + } + + for ( const auto& pair : normalSharedByWedges ) + { + for ( const auto& fh : pair.second.first ) + { + auto& v = m_vertexFaceWedgesWithSameNormals[vh.idx()][fh.idx()]; + v.insert( v.end(), pair.second.second.begin(), pair.second.second.end() ); + } + } + } + } LOG( logINFO ) << "TopologicalMesh: load end with " << m_wedges.size() << " wedges "; } -template -void TopologicalMesh::copyAttribToWedgeData( const TriangleMesh& mesh, +template +void TopologicalMesh::copyAttribToWedgeData( const IndexedGeometry& mesh, unsigned int vindex, const std::vector>& attrHandleVec, VectorArray* to ) { for ( auto handle : attrHandleVec ) { - auto& attr = mesh.getAttrib( handle ); + auto& attr = mesh.template getAttrib( handle ); to->push_back( attr.data()[vindex] ); } } template -void TopologicalMesh::addAttribPairToTopo( const TriangleMesh& triMesh, - AttribManager::pointer_type attr, - std::vector>& vprop, - std::vector>& pph ) { - AttribHandle h = triMesh.getAttribHandle( attr->getName() ); - if ( attr->getSize() == triMesh.vertices().size() ) - { - OpenMesh::HPropHandleT oh; - this->add_property( oh, attr->getName() ); - vprop.push_back( std::make_pair( h, oh ) ); - pph.push_back( oh ); - } - else - { - LOG( logWARNING ) << "[TopologicalMesh] Skip badly sized attribute " << attr->getName() - << "."; - } -} - -void TopologicalMesh::copyMeshToWedgeData( const TriangleMesh& mesh, +void TopologicalMesh::copyMeshToWedgeData( const IndexedGeometry& mesh, unsigned int vindex, const std::vector>& wprop_float, const std::vector>& wprop_vec2, @@ -422,40 +666,7 @@ void TopologicalMesh::copyMeshToWedgeData( const TriangleMesh& mesh, copyAttribToWedgeData( mesh, vindex, wprop_vec4, &wd->m_vector4Attrib ); } -template -void TopologicalMesh::copyAttribToTopo( const TriangleMesh& triMesh, - const std::vector>& vprop, - TopologicalMesh::HalfedgeHandle heh, - unsigned int vindex ) { - for ( auto pp : vprop ) - { - this->property( pp.second, heh ) = triMesh.getAttrib( pp.first ).data()[vindex]; - } -} - -inline const TopologicalMesh::Normal& TopologicalMesh::normal( VertexHandle vh, - FaceHandle fh ) const { - // find halfedge that point to vh and member of fh - if ( !has_halfedge_normals() ) - { - LOG( logERROR ) << "TopologicalMesh has no normals, return dummy ref to (0,0,0)"; - static TopologicalMesh::Normal dummy {0_ra, 0_ra, 0_ra}; - return dummy; - } - return normal( halfedge_handle( vh, fh ) ); -} - -inline void TopologicalMesh::set_normal( VertexHandle vh, FaceHandle fh, const Normal& n ) { - if ( !has_halfedge_normals() ) - { - LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; - return; - } - - set_normal( halfedge_handle( vh, fh ), n ); -} - -inline void TopologicalMesh::propagate_normal_to_halfedges( VertexHandle vh ) { +inline void TopologicalMesh::propagate_normal_to_wedges( VertexHandle vh ) { if ( !has_halfedge_normals() ) { LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; @@ -463,7 +674,11 @@ inline void TopologicalMesh::propagate_normal_to_halfedges( VertexHandle vh ) { } for ( VertexIHalfedgeIter vih_it = vih_iter( vh ); vih_it.is_valid(); ++vih_it ) { - set_normal( *vih_it, normal( vh ) ); + auto wd = getWedgeData( property( getWedgeIndexPph(), *vih_it ) ); + + m_wedges.setWedgeAttrib( wd, "in_normal", normal( vh ) ); + + replaceWedge( *vih_it, wd ); } } @@ -478,271 +693,59 @@ inline TopologicalMesh::HalfedgeHandle TopologicalMesh::halfedge_handle( VertexH } inline const OpenMesh::HPropHandleT& -TopologicalMesh::getInputTriangleMeshIndexPropHandle() const { - return m_inputTriangleMeshIndexPph; -} - -inline const OpenMesh::HPropHandleT& -TopologicalMesh::getOutputTriangleMeshIndexPropHandle() const { - return m_outputTriangleMeshIndexPph; -} - -inline const std::vector>& -TopologicalMesh::getFloatPropsHandles() const { - return m_floatPph; -} - -inline const std::vector>& -TopologicalMesh::getVector2PropsHandles() const { - return m_vec2Pph; -} - -inline const std::vector>& -TopologicalMesh::getVector3PropsHandles() const { - return m_vec3Pph; -} - -inline const std::vector>& -TopologicalMesh::getVector4PropsHandles() const { - return m_vec4Pph; -} - -inline void TopologicalMesh::createNormalPropOnFaces( OpenMesh::FPropHandleT& fProp ) { - if ( !has_halfedge_normals() ) - { - LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; - return; - } - auto nph = halfedge_normals_pph(); - add_property( fProp, property( nph ).name() + "_subdiv_copy_F" ); -} - -inline void TopologicalMesh::clearProp( OpenMesh::FPropHandleT& fProp ) { - remove_property( fProp ); -} - -inline void TopologicalMesh::copyNormal( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ) { - if ( !has_halfedge_normals() ) - { - LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; - return; - } - auto nph = halfedge_normals_pph(); - property( nph, copy_heh ) = property( nph, input_heh ); -} - -inline void TopologicalMesh::copyNormalFromFace( FaceHandle fh, - HalfedgeHandle heh, - OpenMesh::FPropHandleT fProp ) { - if ( !has_halfedge_normals() ) - { - LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; - return; - } - auto nph = halfedge_normals_pph(); - property( nph, heh ) = property( fProp, fh ); -} - -inline void TopologicalMesh::interpolateNormal( HalfedgeHandle in_a, - HalfedgeHandle in_b, - HalfedgeHandle out, - Scalar f ) { - auto nph = halfedge_normals_pph(); - property( nph, out ) = - ( ( 1 - f ) * property( nph, in_a ) + f * property( nph, in_b ) ).normalized(); -} - -inline void TopologicalMesh::interpolateNormalOnFaces( FaceHandle fh, - OpenMesh::FPropHandleT fProp ) { - if ( !has_halfedge_normals() ) - { - LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; - return; - } - auto nph = halfedge_normals_pph(); - - // init sum to first - auto heh = halfedge_handle( fh ); - property( fProp, fh ) = property( nph, heh ); - heh = next_halfedge_handle( heh ); - - // sum others - for ( size_t i = 1; i < valence( fh ); ++i ) - { - property( fProp, fh ) += property( nph, heh ); - heh = next_halfedge_handle( heh ); - } - - // normalize - property( fProp, fh ) = property( fProp, fh ).normalized(); -} - -template -void TopologicalMesh::createPropsOnFaces( const std::vector>& input, - std::vector>& output ) { - output.reserve( input.size() ); - for ( const auto& oh : input ) - { - OpenMesh::FPropHandleT oh_; - add_property( oh_, property( oh ).name() + "_subdiv_copy_F" ); - output.push_back( oh_ ); - } -} - -template -void TopologicalMesh::clearProps( std::vector>& props ) { - for ( auto& oh : props ) - { - remove_property( oh ); - } - props.clear(); +TopologicalMesh::getInputTriangleMeshIndexPropHandle() const { + return m_inputTriangleMeshIndexPph; } -template -void TopologicalMesh::copyProps( HalfedgeHandle input_heh, - HalfedgeHandle copy_heh, - const std::vector>& props ) { - for ( const auto& oh : props ) - { - property( oh, copy_heh ) = property( oh, input_heh ); - } +inline const OpenMesh::HPropHandleT& +TopologicalMesh::getOutputTriangleMeshIndexPropHandle() const { + return m_outputTriangleMeshIndexPph; } -template -void TopologicalMesh::copyPropsFromFace( FaceHandle fh, - HalfedgeHandle heh, - const std::vector>& fProps, - const std::vector>& hProps ) { - for ( uint i = 0; i < fProps.size(); ++i ) +inline void TopologicalMesh::updateWedgeNormals() { + if ( !has_halfedge_normals() ) { - auto hp = hProps[i]; - auto fp = fProps[i]; - property( hp, heh ) = property( fp, fh ); + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; } -} - -template -void TopologicalMesh::interpolateProps( HalfedgeHandle in_a, - HalfedgeHandle in_b, - HalfedgeHandle out, - Scalar f, - const std::vector>& props ) { - // interpolate properties - for ( const auto& oh : props ) + // update_face_normals(); + FaceIter f_it( faces_sbegin() ), f_end( faces_end() ); + for ( ; f_it != f_end; ++f_it ) { - property( oh, out ) = ( 1 - f ) * property( oh, in_a ) + f * property( oh, in_b ); + auto fv_it = this->cfv_iter( *f_it ); + const auto& p0 = point( *fv_it ); + ++fv_it; + const auto& p1 = point( *fv_it ); + ++fv_it; + const auto& p2 = point( *fv_it ); + ++fv_it; + const Normal n = Ra::Core::Geometry::triangleNormal( p0, p1, p2 ); + set_normal( *f_it, n ); } -} -template -void TopologicalMesh::interpolatePropsOnFaces( - FaceHandle fh, - const std::vector>& hProps, - const std::vector>& fProps ) { - auto heh = halfedge_handle( fh ); - const size_t v = valence( fh ); - - // init sum to first - for ( size_t j = 0; j < fProps.size(); ++j ) + for ( auto& w : m_wedges.m_data ) { - auto hp = hProps[j]; - auto fp = fProps[j]; - property( fp, fh ) = property( hp, heh ); + w.getWedgeData().m_vector3Attrib[m_normalsIndex] = Normal {0_ra, 0_ra, 0_ra}; } - heh = next_halfedge_handle( heh ); - // sum others - for ( size_t i = 1; i < v; ++i ) + + for ( auto v_itr = vertices_begin(), stop = vertices_end(); v_itr != stop; ++v_itr ) { - for ( size_t j = 0; j < fProps.size(); ++j ) + for ( ConstVertexFaceIter f_itr = cvf_iter( *v_itr ); f_itr.is_valid(); ++f_itr ) { - auto hp = hProps[j]; - auto fp = fProps[j]; - property( fp, fh ) += property( hp, heh ); + for ( const auto& widx : m_vertexFaceWedgesWithSameNormals[v_itr->idx()][f_itr->idx()] ) + { + m_wedges.m_data[widx].getWedgeData().m_vector3Attrib[m_normalsIndex] += + normal( *f_itr ); + } } - heh = next_halfedge_handle( heh ); } - // normalize - for ( size_t j = 0; j < fProps.size(); ++j ) + + for ( auto& w : m_wedges.m_data ) { - auto fp = fProps[j]; - property( fp, fh ) = property( fp, fh ) / v; + w.getWedgeData().m_vector3Attrib[m_normalsIndex].normalize(); } } -inline void -TopologicalMesh::createAllPropsOnFaces( OpenMesh::FPropHandleT& normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ) { - createNormalPropOnFaces( normalProp ); - createPropsOnFaces( getFloatPropsHandles(), floatProps ); - createPropsOnFaces( getVector2PropsHandles(), vec2Props ); - createPropsOnFaces( getVector3PropsHandles(), vec3Props ); - createPropsOnFaces( getVector4PropsHandles(), vec4Props ); -} - -inline void -TopologicalMesh::clearAllProps( OpenMesh::FPropHandleT& normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ) { - clearProp( normalProp ); - clearProps( floatProps ); - clearProps( vec2Props ); - clearProps( vec3Props ); - clearProps( vec4Props ); -} - -inline void TopologicalMesh::copyAllProps( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ) { - copyNormal( input_heh, copy_heh ); - copyProps( input_heh, copy_heh, getFloatPropsHandles() ); - copyProps( input_heh, copy_heh, getVector2PropsHandles() ); - copyProps( input_heh, copy_heh, getVector3PropsHandles() ); - copyProps( input_heh, copy_heh, getVector4PropsHandles() ); -} - -inline void -TopologicalMesh::copyAllPropsFromFace( FaceHandle fh, - HalfedgeHandle heh, - OpenMesh::FPropHandleT normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ) { - copyNormalFromFace( fh, heh, normalProp ); - copyPropsFromFace( fh, heh, floatProps, getFloatPropsHandles() ); - copyPropsFromFace( fh, heh, vec2Props, getVector2PropsHandles() ); - copyPropsFromFace( fh, heh, vec3Props, getVector3PropsHandles() ); - copyPropsFromFace( fh, heh, vec4Props, getVector4PropsHandles() ); -} - -inline void TopologicalMesh::interpolateAllProps( HalfedgeHandle in_a, - HalfedgeHandle in_b, - HalfedgeHandle out, - Scalar f ) { - interpolateNormal( in_a, in_b, out, f ); - interpolateProps( in_a, in_b, out, f, getFloatPropsHandles() ); - interpolateProps( in_a, in_b, out, f, getVector2PropsHandles() ); - interpolateProps( in_a, in_b, out, f, getVector3PropsHandles() ); - interpolateProps( in_a, in_b, out, f, getVector4PropsHandles() ); -} - -inline void TopologicalMesh::interpolateAllPropsOnFaces( - FaceHandle fh, - OpenMesh::FPropHandleT normalProp, - std::vector>& floatProps, - std::vector>& vec2Props, - std::vector>& vec3Props, - std::vector>& vec4Props ) { - interpolateNormalOnFaces( fh, normalProp ); - interpolatePropsOnFaces( fh, getFloatPropsHandles(), floatProps ); - interpolatePropsOnFaces( fh, getVector2PropsHandles(), vec2Props ); - interpolatePropsOnFaces( fh, getVector3PropsHandles(), vec3Props ); - interpolatePropsOnFaces( fh, getVector4PropsHandles(), vec4Props ); -} - inline std::set TopologicalMesh::getVertexWedges( OpenMesh::VertexHandle vh ) const { std::set ret; @@ -760,25 +763,44 @@ TopologicalMesh::getWedgeIndex( OpenMesh::HalfedgeHandle heh ) const { return property( getWedgeIndexPph(), heh ); } -inline const TopologicalMesh::WedgeData& -TopologicalMesh::getWedgeData( const WedgeIndex& idx ) const { - return m_wedges.getWedgeData( idx ); -} - inline unsigned int TopologicalMesh::getWedgeRefCount( const WedgeIndex& idx ) const { return m_wedges.getWedgeRefCount( idx ); } +template +inline bool TopologicalMesh::setWedgeData( const TopologicalMesh::WedgeIndex& idx, + const std::string& name, + const T& value ) { + return setWedgeAttrib( idx, name, value ); +} + +template +inline bool TopologicalMesh::setWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + const std::string& name, + const T& value ) { + return m_wedges.setWedgeAttrib( idx, name, value ); +} + inline void TopologicalMesh::setWedgeData( TopologicalMesh::WedgeIndex widx, const TopologicalMesh::WedgeData& wedge ) { m_wedges.setWedgeData( widx, wedge ); } +inline const TopologicalMesh::WedgeData& +TopologicalMesh::getWedgeData( const WedgeIndex& idx ) const { + return m_wedges.getWedgeData( idx ); +} + template -inline bool TopologicalMesh::setWedgeData( const TopologicalMesh::WedgeIndex& idx, - const std::string& name, - const T& value ) { - return m_wedges.setWedgeData( idx, name, value ); +inline const T& TopologicalMesh::getWedgeData( const TopologicalMesh::WedgeIndex& idx, + const std::string& name ) const { + return getWedgeAttrib( idx, name ); +} + +template +inline const T& TopologicalMesh::getWedgeAttrib( const TopologicalMesh::WedgeIndex& idx, + const std::string& name ) const { + return m_wedges.getWedgeData( idx, name ); } inline void TopologicalMesh::replaceWedge( OpenMesh::HalfedgeHandle he, const WedgeData& wd ) { @@ -804,7 +826,8 @@ inline void TopologicalMesh::mergeEqualWedges( OpenMesh::VertexHandle vh ) { for ( auto itr = vih_iter( vh ); itr.is_valid(); ++itr ) { // replace will search if wedge already present and use it, so merge occurs. - replaceWedge( *itr, getWedgeData( property( getWedgeIndexPph(), *itr ) ) ); + if ( !is_boundary( *itr ) ) + replaceWedge( *itr, getWedgeData( property( getWedgeIndexPph(), *itr ) ) ); } } @@ -843,210 +866,6 @@ TopologicalMesh::getWedgeIndexPph() const { return m_wedgeIndexPph; } -//////////////////////////////////////////////////////////////////////////////// -/////////////////// WEDGES RELATED STUFF ////////////////////////////// -/////////////////// WedgeCollection ////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -inline void TopologicalMesh::WedgeCollection::del( const TopologicalMesh::WedgeIndex& idx ) { - if ( idx.isValid() ) m_data[idx].decrementRefCount(); -} - -inline TopologicalMesh::WedgeIndex -TopologicalMesh::WedgeCollection::newReference( const TopologicalMesh::WedgeIndex& idx ) { - if ( idx.isValid() ) m_data[idx].incrementRefCount(); - return idx; -} - -inline const TopologicalMesh::Wedge& -TopologicalMesh::WedgeCollection::getWedge( const TopologicalMesh::WedgeIndex& idx ) const { - return m_data[idx]; -} - -inline void TopologicalMesh::WedgeCollection::setWedgeData( const TopologicalMesh::WedgeIndex& idx, - const TopologicalMesh::WedgeData& wd ) { - if ( !( wd.m_floatAttrib.size() == m_floatAttribNames.size() && - wd.m_vector2Attrib.size() == m_vector2AttribNames.size() && - wd.m_vector3Attrib.size() == m_vector3AttribNames.size() && - wd.m_vector4Attrib.size() == m_vector4AttribNames.size() ) ) - { - LOG( logWARNING ) << "Warning, topological mesh set wedge: number of attribs inconsistency"; - } - if ( idx.isValid() ) m_data[idx].setWedgeData( wd ); -} - -#define GET_NAME_ARRAY_HELPER( TYPE, NAME ) \ - template <> \ - inline const std::vector& TopologicalMesh::WedgeCollection::getNameArray() \ - const { \ - return m_##NAME##AttribNames; \ - } \ - template <> \ - inline std::vector& TopologicalMesh::WedgeCollection::getNameArray() { \ - return m_##NAME##AttribNames; \ - } - -GET_NAME_ARRAY_HELPER( float, float ) -GET_NAME_ARRAY_HELPER( Vector2, vector2 ) -GET_NAME_ARRAY_HELPER( Vector3, vector3 ) -GET_NAME_ARRAY_HELPER( Vector4, vector4 ) - -#undef GET_NAME_ARRAY_HELPER -// These template functions are defined above for supported types. -// For unsupported types they simply generate a compile error. -template -inline const std::vector& TopologicalMesh::WedgeCollection::getNameArray() const { - - LOG( logWARNING ) << "Warning, mesh attribute " << typeid( T ).name() - << " is not supported (only float, vec2, vec3 nor vec4 are supported)"; - static_assert( sizeof( T ) == -1, "this type is not supported" ); - return m_floatAttribNames; -} - -template -inline std::vector& TopologicalMesh::WedgeCollection::getNameArray() { - - LOG( logWARNING ) << "Warning, mesh attribute " << typeid( T ).name() - << " is not supported (only float, vec2, vec3 nor vec4 are supported)"; - static_assert( sizeof( T ) == -1, "this type is not supported" ); - return m_floatAttribNames; -} - -template -inline bool TopologicalMesh::WedgeCollection::setWedgeData( const TopologicalMesh::WedgeIndex& idx, - const std::string& name, - const T& value ) { - if ( idx.isValid() ) - { - auto nameArray = getNameArray(); - auto itr = std::find( nameArray.begin(), nameArray.end(), name ); - if ( itr != nameArray.end() ) - { - auto attrIndex = std::distance( nameArray.begin(), itr ); - m_data[idx].getWedgeData().getAttribArray()[attrIndex] = value; - return true; - } - else - { - LOG( logERROR ) << "Warning, set wedge: no wedge attrib named " << name << " of type " - << typeid( T ).name(); - } - } - return false; -} - -inline bool -TopologicalMesh::WedgeCollection::setWedgePosition( const TopologicalMesh::WedgeIndex& idx, - const Vector3& value ) { - if ( idx.isValid() ) - { - m_data[idx].getWedgeData().m_position = value; - return true; - } - return false; -} - -template -void TopologicalMesh::WedgeCollection::addProp( const std::string& name ) { - if ( name != std::string( "in_position" ) ) { getNameArray().push_back( name ); } -} - -inline void TopologicalMesh::WedgeCollection::garbageCollection() { - m_data.erase( std::remove_if( m_data.begin(), - m_data.end(), - []( const Wedge& w ) { return w.isDeleted(); } ), - m_data.end() ); -} - -//////////////////////////////////////////////////////////////////////////////// -/////////////////// WedgeData ////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -// return 1 : equals, 2: strict less, 3: strict greater -template -int TopologicalMesh::WedgeData::compareVector( const T& a, const T& b ) { - for ( int i = 0; i < T::RowsAtCompileTime; i++ ) - { - if ( a[i] < b[i] ) return 2; - if ( a[i] > b[i] ) return 3; - } - // (a == b) - return 1; -} - -inline bool TopologicalMesh::WedgeData::operator==( const TopologicalMesh::WedgeData& lhs ) const { - return - // do not have this yet, not sure we need to test them - // m_inputTriangleMeshIndex == lhs.m_inputTriangleMeshIndex && - // m_outputTriangleMeshIndex == lhs.m_outputTriangleMeshIndex && - m_position == lhs.m_position && m_floatAttrib == lhs.m_floatAttrib && - m_vector2Attrib == lhs.m_vector2Attrib && m_vector3Attrib == lhs.m_vector3Attrib && - m_vector4Attrib == lhs.m_vector4Attrib; -} - -inline bool TopologicalMesh::WedgeData::operator<( const TopologicalMesh::WedgeData& lhs ) const { - - CORE_ASSERT( ( m_floatAttrib.size() == lhs.m_floatAttrib.size() ) && - ( m_vector2Attrib.size() == lhs.m_vector2Attrib.size() ) && - ( m_vector3Attrib.size() == lhs.m_vector3Attrib.size() ) && - ( m_vector4Attrib.size() == lhs.m_vector4Attrib.size() ), - "Could only compare wedge with same number of attributes" ); - - { - int comp = compareVector( m_position, lhs.m_position ); - if ( comp == 2 ) return true; - if ( comp == 3 ) return false; - } - for ( size_t i = 0; i < m_floatAttrib.size(); i++ ) - { - if ( m_floatAttrib[i] < lhs.m_floatAttrib[i] ) - return true; - else if ( m_floatAttrib[i] > lhs.m_floatAttrib[i] ) - return false; - } - - for ( size_t i = 0; i < m_vector2Attrib.size(); i++ ) - { - int comp = compareVector( m_vector2Attrib[i], lhs.m_vector2Attrib[i] ); - if ( comp == 2 ) return true; - if ( comp == 3 ) return false; - } - for ( size_t i = 0; i < m_vector3Attrib.size(); i++ ) - { - int comp = compareVector( m_vector3Attrib[i], lhs.m_vector3Attrib[i] ); - if ( comp == 2 ) return true; - if ( comp == 3 ) return false; - } - for ( size_t i = 0; i < m_vector4Attrib.size(); i++ ) - { - int comp = compareVector( m_vector4Attrib[i], lhs.m_vector4Attrib[i] ); - if ( comp == 2 ) return true; - if ( comp == 3 ) return false; - } - return false; -} - -bool TopologicalMesh::WedgeData::operator!=( const TopologicalMesh::WedgeData& lhs ) const { - return !( *this == lhs ); -} - -#define GET_ATTRIB_ARRAY_HELPER( TYPE, NAME ) \ - template <> \ - inline VectorArray& TopologicalMesh::WedgeData::getAttribArray() { \ - return m_##NAME##Attrib; \ - } - -GET_ATTRIB_ARRAY_HELPER( float, float ) -GET_ATTRIB_ARRAY_HELPER( Vector2, vector2 ) -GET_ATTRIB_ARRAY_HELPER( Vector3, vector3 ) -GET_ATTRIB_ARRAY_HELPER( Vector4, vector4 ) -#undef GET_ATTRIB_ARRAY_HELPER - -template -inline VectorArray& TopologicalMesh::WedgeData::getAttribArray() { - static_assert( sizeof( T ) == -1, "this type is not supported" ); -} - } // namespace Geometry } // namespace Core } // namespace Ra diff --git a/src/Core/Geometry/deprecated/TopologicalMesh.cpp b/src/Core/Geometry/deprecated/TopologicalMesh.cpp new file mode 100644 index 00000000000..cd265796a54 --- /dev/null +++ b/src/Core/Geometry/deprecated/TopologicalMesh.cpp @@ -0,0 +1,470 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +namespace Ra { +namespace Core { +namespace Geometry { +namespace deprecated { + +using namespace Utils; // log, AttribXXX + +///////////////// HELPERS /////////////// + +template +void addAttribPairToCore( TriangleMesh& triMesh, + const TopologicalMesh* topoMesh, + OpenMesh::HPropHandleT oh, + std::vector

& vprop ) { + AttribHandle h = triMesh.addAttrib( topoMesh->property( oh ).name() ); + vprop.push_back( std::make_pair( h, oh ) ); +} + +template +using HandleAndValueVector = std::vector, T>, + Eigen::aligned_allocator, T>>>; + +template +void copyAttribToCoreVertex( HandleAndValueVector& data, + const TopologicalMesh* topoMesh, + const std::vector

& vprop, + TopologicalMesh::HalfedgeHandle heh ) { + for ( auto pp : vprop ) + { + data.push_back( std::make_pair( pp.first, topoMesh->property( pp.second, heh ) ) ); + } +} + +template +void copyAttribToCore( TriangleMesh& triMesh, const HandleAndValueVector& data ) { + + for ( auto pp : data ) + { + auto& attr = triMesh.getAttrib( pp.first ); + auto& attrData = attr.getDataWithLock(); + attrData.push_back( pp.second ); + attr.unlock(); + } +} + +//! [Default command implementation] +struct DefaultNonManifoldFaceCommand { + /// \brief details string is printed along with the message + DefaultNonManifoldFaceCommand( std::string details = {} ) : m_details {details} {} + /// \brief Initalize with input Ra::Core::Geometry::TriangleMesh + inline void initialize( const TriangleMesh& /*triMesh*/ ) {} + /// \brief Process non-manifold face + inline void process( const std::vector& /*face_vhandles*/ ) { + LOG( logWARNING ) << "Invalid face handle returned : face not added " + m_details; + /// TODO memorize invalid faces for post processing ... + /// see + /// https://www.graphics.rwth-aachen.de/media/openflipper_static/Daily-Builds/Doc/Free/Developer/OBJImporter_8cc_source.html + /// for an exemple of loading + } + /// \brief If needed, apply post-processing on the TopologicalMesh + inline void postProcess( TopologicalMesh& /*tm*/ ) {} + //! [Default command implementation] + private: + std::string m_details; +}; + +TopologicalMesh::TopologicalMesh( const TriangleMesh& triMesh ) : + TopologicalMesh( triMesh, DefaultNonManifoldFaceCommand( "[default ctor (props)]" ) ) {} + +TopologicalMesh::TopologicalMesh() { + add_property( m_inputTriangleMeshIndexPph ); +} + +TriangleMesh TopologicalMesh::toTriangleMesh() { + struct VertexDataInternal { + Vector3 _vertex; + Vector3 _normal; + + HandleAndValueVector _float; + HandleAndValueVector _vec2; + HandleAndValueVector _vec3; + HandleAndValueVector _vec4; + + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + + bool operator==( const VertexDataInternal& lhs ) const { + return _vertex == lhs._vertex && _normal == lhs._normal && _float == lhs._float && + _vec2 == lhs._vec2 && _vec3 == lhs._vec3 && _vec4 == lhs._vec4; + } + }; + + struct hash_vec { + size_t operator()( const VertexDataInternal& lvalue ) const { + size_t hx = std::hash()( lvalue._vertex[0] ); + size_t hy = std::hash()( lvalue._vertex[1] ); + size_t hz = std::hash()( lvalue._vertex[2] ); + return ( hx ^ ( hy << 1 ) ) ^ hz; + } + }; + + TriangleMesh out; + + using VertexMap = std::unordered_map; + + VertexMap vertexHandles; + + if ( !get_property_handle( m_outputTriangleMeshIndexPph, "OutputTriangleMeshIndices" ) ) + { add_property( m_outputTriangleMeshIndexPph, "OutputTriangleMeshIndices" ); } + std::vector> vprop_float; + std::vector> vprop_vec2; + std::vector> vprop_vec3; + std::vector> vprop_vec4; + + // loop over all attribs and build correspondance pair + vprop_float.reserve( m_floatPph.size() ); + for ( auto oh : m_floatPph ) + addAttribPairToCore( out, this, oh, vprop_float ); + vprop_vec2.reserve( m_vec2Pph.size() ); + for ( auto oh : m_vec2Pph ) + addAttribPairToCore( out, this, oh, vprop_vec2 ); + vprop_vec3.reserve( m_vec3Pph.size() ); + for ( auto oh : m_vec3Pph ) + addAttribPairToCore( out, this, oh, vprop_vec3 ); + vprop_vec4.reserve( m_vec4Pph.size() ); + for ( auto oh : m_vec4Pph ) + addAttribPairToCore( out, this, oh, vprop_vec4 ); + + // iterator over all faces + unsigned int vertexIndex = 0; + + // out will have at least n_vertices vertices and normals. + TriangleMesh::PointAttribHandle::Container vertices; + TriangleMesh::NormalAttribHandle::Container normals; + TriangleMesh::IndexContainerType indices; + + vertices.reserve( n_vertices() ); + normals.reserve( n_vertices() ); + indices.reserve( n_faces() ); + + for ( TopologicalMesh::FaceIter f_it = faces_sbegin(); f_it != faces_end(); ++f_it ) + { + int tindices[3]; + int i = 0; + + // iterator over vertex (through halfedge to get access to halfedge normals) + for ( TopologicalMesh::ConstFaceHalfedgeIter fh_it = cfh_iter( *f_it ); fh_it.is_valid(); + ++fh_it ) + { + VertexDataInternal v; + CORE_ASSERT( i < 3, "Non-triangular face found." ); + v._vertex = point( to_vertex_handle( *fh_it ) ); + + if ( has_halfedge_normals() ) + { v._normal = normal( to_vertex_handle( *fh_it ), *f_it ); } + copyAttribToCoreVertex( v._float, this, vprop_float, *fh_it ); + copyAttribToCoreVertex( v._vec2, this, vprop_vec2, *fh_it ); + copyAttribToCoreVertex( v._vec3, this, vprop_vec3, *fh_it ); + copyAttribToCoreVertex( v._vec4, this, vprop_vec4, *fh_it ); + + int vi; + VertexMap::iterator vtr = vertexHandles.find( v ); + if ( vtr == vertexHandles.end() ) + { + vi = int( vertexIndex++ ); + vertexHandles.insert( vtr, VertexMap::value_type( v, vi ) ); + vertices.push_back( v._vertex ); + if ( has_halfedge_normals() ) { normals.push_back( v._normal ); } + copyAttribToCore( out, v._float ); + copyAttribToCore( out, v._vec2 ); + copyAttribToCore( out, v._vec3 ); + copyAttribToCore( out, v._vec4 ); + } + else + { vi = vtr->second; } + tindices[i] = vi; + property( m_outputTriangleMeshIndexPph, *fh_it ) = vi; + i++; + } + indices.emplace_back( tindices[0], tindices[1], tindices[2] ); + } + out.setVertices( std::move( vertices ) ); + if ( has_halfedge_normals() ) { out.setNormals( std::move( normals ) ); } + out.setIndices( std::move( indices ) ); + CORE_ASSERT( vertexIndex == vertices.size(), + "Inconsistent number of faces in generated TriangleMesh." ); + + return out; +} + +bool TopologicalMesh::splitEdge( TopologicalMesh::EdgeHandle eh, Scalar f ) { + // Global schema of operation + /* + TRIANGLES ONLY + before after + A A + / F0 \ / F2 | F0 \ + / \ / | \ + /h1 h0\ /h1 e2|e0 h0\ + / he0 \ / he2 | he0 \ + V1 -------- V0 V1 ------ V ------ V0 + \ he1 / \ he3 | he1 / + \o1 o0/ \o1 e3|e1 o0/ + \ / \ | / + \ F1 / \ F3 | F1 / + B B + + */ + + // incorrect factor + if ( f < 0 || f > 1 ) { return false; } + + // get existing topology data + HalfedgeHandle he0 = halfedge_handle( eh, 0 ); + HalfedgeHandle he1 = halfedge_handle( eh, 1 ); + VertexHandle v0 = to_vertex_handle( he0 ); + VertexHandle v1 = to_vertex_handle( he1 ); + FaceHandle F0 = face_handle( he0 ); + FaceHandle F1 = face_handle( he1 ); + + // not triangles or holes + if ( ( !is_boundary( he0 ) && valence( F0 ) != 3 ) || + ( !is_boundary( he1 ) && valence( F1 ) != 3 ) ) + { return false; } + + // add the new vertex + const Point p = Point( f * point( v0 ) + ( Scalar( 1. ) - f ) * point( v1 ) ); + VertexHandle v = add_vertex( p ); + + // create the new faces and reconnect the topology + HalfedgeHandle he3 = new_edge( v, v1 ); + HalfedgeHandle he2 = opposite_halfedge_handle( he3 ); + set_halfedge_handle( v, he0 ); + set_vertex_handle( he1, v ); + + // does F0 exist + if ( !is_boundary( he0 ) ) + { + HalfedgeHandle h0 = next_halfedge_handle( he0 ); + HalfedgeHandle h1 = next_halfedge_handle( h0 ); + // create new edge + VertexHandle A = to_vertex_handle( h0 ); + HalfedgeHandle e2 = new_edge( v, A ); + HalfedgeHandle e0 = opposite_halfedge_handle( e2 ); + // split F0 + FaceHandle F2 = new_face(); + set_halfedge_handle( F0, he0 ); + set_halfedge_handle( F2, h1 ); + // update F0 + set_face_handle( h0, F0 ); + set_face_handle( e0, F0 ); + set_face_handle( he0, F0 ); + set_next_halfedge_handle( he0, h0 ); + set_next_halfedge_handle( h0, e0 ); + set_next_halfedge_handle( e0, he0 ); + // update F2 + set_face_handle( h1, F2 ); + set_face_handle( he2, F2 ); + set_face_handle( e2, F2 ); + set_next_halfedge_handle( e2, h1 ); + set_next_halfedge_handle( h1, he2 ); + set_next_halfedge_handle( he2, e2 ); + // deal with custom properties + // interpolate at he2 + interpolateAllProps( h1, he0, he2, 0.5 ); + // copy at e0, and e2 + copyAllProps( he2, e0 ); + } + else + { + HalfedgeHandle h1 = prev_halfedge_handle( he0 ); + set_next_halfedge_handle( h1, he2 ); + set_next_halfedge_handle( he2, he0 ); + // next halfedge handle of he0 already is h0 + // halfedge handle of V already is he0 + } + + // does F1 exist + if ( !is_boundary( he1 ) ) + { + HalfedgeHandle o1 = next_halfedge_handle( he1 ); + HalfedgeHandle o0 = next_halfedge_handle( o1 ); + // create new edge + VertexHandle B = to_vertex_handle( o1 ); + HalfedgeHandle e1 = new_edge( v, B ); + HalfedgeHandle e3 = opposite_halfedge_handle( e1 ); + // split F1 + FaceHandle F3 = new_face(); + set_halfedge_handle( F3, o1 ); + set_halfedge_handle( F1, he1 ); + // update F1 + set_face_handle( o1, F3 ); + set_face_handle( e3, F3 ); + set_face_handle( he3, F3 ); + set_next_halfedge_handle( he3, o1 ); + set_next_halfedge_handle( o1, e3 ); + set_next_halfedge_handle( e3, he3 ); + // update F3 + set_face_handle( o0, F1 ); + set_face_handle( he1, F1 ); + set_face_handle( e1, F1 ); + set_next_halfedge_handle( he1, e1 ); + set_next_halfedge_handle( e1, o0 ); + set_next_halfedge_handle( o0, he1 ); + // deal with custom properties + // first copy at he3 + copyAllProps( he1, he3 ); + // interpolate at he1 + interpolateAllProps( o0, he3, he1, 0.5 ); + // copy at e1, and e3 + copyAllProps( he1, e3 ); + copyAllProps( o1, e1 ); + } + else + { + HalfedgeHandle o1 = next_halfedge_handle( he1 ); + // next halfedge handle of o0 already is he1 + set_next_halfedge_handle( he1, he3 ); + set_next_halfedge_handle( he3, o1 ); + // halfedge handle of V already is he0 + } + + // ensure consistency at v1 + if ( halfedge_handle( v1 ) == he0 ) { set_halfedge_handle( v1, he2 ); } + + return true; +} + +//----------------------------------------------------------------------------- +// from /OpenMesh/Core/Mesh/TriConnectivity.cc +void TopologicalMesh::split( EdgeHandle _eh, VertexHandle _vh ) { + HalfedgeHandle h0 = halfedge_handle( _eh, 0 ); + HalfedgeHandle o0 = halfedge_handle( _eh, 1 ); + + VertexHandle v2 = to_vertex_handle( o0 ); + + HalfedgeHandle e1 = new_edge( _vh, v2 ); + HalfedgeHandle t1 = opposite_halfedge_handle( e1 ); + + FaceHandle f0 = face_handle( h0 ); + FaceHandle f3 = face_handle( o0 ); + + set_halfedge_handle( _vh, h0 ); + set_vertex_handle( o0, _vh ); + + if ( !is_boundary( h0 ) ) + { + HalfedgeHandle h1 = next_halfedge_handle( h0 ); + HalfedgeHandle h2 = next_halfedge_handle( h1 ); + + VertexHandle v1 = to_vertex_handle( h1 ); + + HalfedgeHandle e0 = new_edge( _vh, v1 ); + HalfedgeHandle t0 = opposite_halfedge_handle( e0 ); + + FaceHandle f1 = new_face(); + set_halfedge_handle( f0, h0 ); + set_halfedge_handle( f1, h2 ); + + set_face_handle( h1, f0 ); + set_face_handle( t0, f0 ); + set_face_handle( h0, f0 ); + + set_face_handle( h2, f1 ); + set_face_handle( t1, f1 ); + set_face_handle( e0, f1 ); + + set_next_halfedge_handle( h0, h1 ); + set_next_halfedge_handle( h1, t0 ); + set_next_halfedge_handle( t0, h0 ); + + set_next_halfedge_handle( e0, h2 ); + set_next_halfedge_handle( h2, t1 ); + set_next_halfedge_handle( t1, e0 ); + } + else + { + set_next_halfedge_handle( prev_halfedge_handle( h0 ), t1 ); + set_next_halfedge_handle( t1, h0 ); + // halfedge handle of _vh already is h0 + } + + if ( !is_boundary( o0 ) ) + { + HalfedgeHandle o1 = next_halfedge_handle( o0 ); + HalfedgeHandle o2 = next_halfedge_handle( o1 ); + + VertexHandle v3 = to_vertex_handle( o1 ); + + HalfedgeHandle e2 = new_edge( _vh, v3 ); + HalfedgeHandle t2 = opposite_halfedge_handle( e2 ); + + FaceHandle f2 = new_face(); + set_halfedge_handle( f2, o1 ); + set_halfedge_handle( f3, o0 ); + + set_face_handle( o1, f2 ); + set_face_handle( t2, f2 ); + set_face_handle( e1, f2 ); + + set_face_handle( o2, f3 ); + set_face_handle( o0, f3 ); + set_face_handle( e2, f3 ); + + set_next_halfedge_handle( e1, o1 ); + set_next_halfedge_handle( o1, t2 ); + set_next_halfedge_handle( t2, e1 ); + + set_next_halfedge_handle( o0, e2 ); + set_next_halfedge_handle( e2, o2 ); + set_next_halfedge_handle( o2, o0 ); + } + else + { + set_next_halfedge_handle( e1, next_halfedge_handle( o0 ) ); + set_next_halfedge_handle( o0, e1 ); + set_halfedge_handle( _vh, e1 ); + } + + if ( halfedge_handle( v2 ) == h0 ) set_halfedge_handle( v2, t1 ); +} + +//----------------------------------------------------------------------------- + +void TopologicalMesh::split_copy( EdgeHandle _eh, VertexHandle _vh ) { + const VertexHandle v0 = to_vertex_handle( halfedge_handle( _eh, 0 ) ); + const VertexHandle v1 = to_vertex_handle( halfedge_handle( _eh, 1 ) ); + + const int nf = n_faces(); + + // Split the halfedge ( handle will be preserved) + split( _eh, _vh ); + + // Copy the properties of the original edge to all neighbor edges that + // have been created + for ( VEIter ve_it = ve_iter( _vh ); ve_it.is_valid(); ++ve_it ) + copy_all_properties( _eh, *ve_it, true ); + + for ( auto vh : {v0, v1} ) + { + // get the halfedge pointing from new vertex to old vertex + const HalfedgeHandle h = find_halfedge( _vh, vh ); + // for boundaries there are no faces whose properties need to be copied + if ( !is_boundary( h ) ) + { + FaceHandle fh0 = face_handle( h ); + FaceHandle fh1 = face_handle( opposite_halfedge_handle( prev_halfedge_handle( h ) ) ); + // is fh0 the new face? + if ( fh0.idx() >= nf ) std::swap( fh0, fh1 ); + + // copy properties from old face to new face + copy_all_properties( fh0, fh1, true ); + } + } +} + +} // namespace deprecated +} // namespace Geometry +} // namespace Core +} // namespace Ra diff --git a/src/Core/Geometry/deprecated/TopologicalMesh.hpp b/src/Core/Geometry/deprecated/TopologicalMesh.hpp new file mode 100644 index 00000000000..fe538b798b8 --- /dev/null +++ b/src/Core/Geometry/deprecated/TopologicalMesh.hpp @@ -0,0 +1,396 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace Ra { +namespace Core { +namespace Geometry { +namespace deprecated { +using namespace Utils; // log, AttribXXX + +/** + * Define the Traits to be used by OpenMesh for TopologicalMesh. + */ +struct TopologicalMeshTraits : OpenMesh::DefaultTraits { + using Point = Ra::Core::Vector3; + using Normal = Ra::Core::Vector3; + + VertexAttributes( OpenMesh::Attributes::Status | OpenMesh::Attributes::Normal ); + FaceAttributes( OpenMesh::Attributes::Status | OpenMesh::Attributes::Normal ); + EdgeAttributes( OpenMesh::Attributes::Status ); + // Add OpenMesh::Attributes::PrevHalfedge for efficiency ? + HalfedgeAttributes( OpenMesh::Attributes::Status | OpenMesh::Attributes::Normal ); +}; + +/** + * This class represents a mesh with topological information on the + * vertex graph, using a half-edge representation. + * + * This integration is inspired by: + * https://gist.github.com/Unril/03fa353d0461ed6bd41d + * + * \todo rename methods to respect Radium guideline (get/set/is, camelCase) + * \todo private inheritance from OpenMesh, and import relevant methods. + */ +class RA_CORE_API TopologicalMesh : public OpenMesh::PolyMesh_ArrayKernelT +{ + private: + using base = OpenMesh::PolyMesh_ArrayKernelT; + using base::PolyMesh_ArrayKernelT; + using Index = Ra::Core::Utils::Index; + using Vector3 = Ra::Core::Vector3; + + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + + /** + * Construct a topological mesh from a triangle mesh. + * This operation merges vertex with same position, but keeps vertex + * attributes on halfedges, so that TriangleMesh vertices with the same 3D + * position are represented only once in the topological mesh. + * \note This is a costly operation. + * + * \tparam NonManifoldFaceCommand Command executed when non-manifold faces are + * found. API and default implementation: + * \snippet Core/Geometry/TopologicalMesh.cpp Default command implementation + * + */ + template + explicit TopologicalMesh( const Ra::Core::Geometry::TriangleMesh& triMesh, + NonManifoldFaceCommand command ); + + /** + * \brief Convenience constructor + * \see TopologicalMesh( const Ra::Core::Geometry::TriangleMesh&, NonManifoldFaceCommand) + */ + explicit TopologicalMesh( const Ra::Core::Geometry::TriangleMesh& triMesh ); + + /** + * Construct an empty topological mesh, only init mandatory properties. + */ + explicit TopologicalMesh(); + + /** + * Return a triangleMesh from the topological mesh. + * \note This is a costly operation. + */ + TriangleMesh toTriangleMesh(); + + /** + * Update triangle mesh data, assuming the mesh and this topo mesh has the + * same topology. + * \warning note implemented yet. + */ + void updateTriangleMesh( Ra::Core::Geometry::TriangleMesh& mesh ); + + // import other version of halfedge_handle method + using base::halfedge_handle; + + /** + * Return the half-edge associated with a given vertex and face. + * \note Asserts if vh is not a member of fh. + */ + inline HalfedgeHandle halfedge_handle( VertexHandle vh, FaceHandle fh ) const; + + /** + * Get normal of the vertex vh, when member of fh. + * \note Asserts if vh is not a member of fh. + */ + [[deprecated]] inline const Normal& normal( VertexHandle vh, FaceHandle fh ) const; + + /** + * Set normal of the vertex vh, when member of fh. + * \note Asserts if vh is not a member of fh. + */ + [[deprecated]] void set_normal( VertexHandle vh, FaceHandle fh, const Normal& n ); + + /// Import Base definition of normal and set normal. + ///@{ + using base::normal; + using base::set_normal; + ///@} + + /** + * Set the normal n to all the halfedges that points to vh (i.e. incomming + * halfedges) . + * If you work with vertex normals, please call this function on all vertex + * handles before convertion with toTriangleMesh. + */ + [[deprecated]] void propagate_normal_to_halfedges( VertexHandle vh ); + + /** + * Return a handle to the halfedge property storing vertices indices within + * the TriangleMesh *this has been built on. + */ + inline const OpenMesh::HPropHandleT& getInputTriangleMeshIndexPropHandle() const; + + /** + * Return a handle to the halfedge property storing vertices indices within + * the TriangleMesh returned by toTriangleMesh(). + * \note This property is valid only after toTriangleMesh() has been called. + */ + inline const OpenMesh::HPropHandleT& getOutputTriangleMeshIndexPropHandle() const; + + /** + * \name Const access to handles of the HalfEdge properties coming from + * the TriangleMesh attributes. + */ + ///@{ + [[deprecated]] inline const std::vector>& + getFloatPropsHandles() const; + [[deprecated]] inline const std::vector>& + getVector2PropsHandles() const; + [[deprecated]] inline const std::vector>& + getVector3PropsHandles() const; + [[deprecated]] inline const std::vector>& + getVector4PropsHandles() const; + ///@} + + /** + * \name Dealing with normals + * Utils to deal with normals when modifying the mesh topology. + */ + ///@{ + + /** + * Create a new property for normals on faces of \a mesh. + * \note This new property will have to be propagated onto the newly created + * halfedges with copyNormal(). + */ + inline void createNormalPropOnFaces( OpenMesh::FPropHandleT& fProp ); + + /** + * Remove face property \a prop from \a mesh. + * \note Invalidates the property handle. + */ + inline void clearProp( OpenMesh::FPropHandleT& fProp ); + + /** + * Copy the normal property from \a input_heh to \a copy_heh. + */ + inline void copyNormal( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ); + + /** Copy the face normal property \a fProp from \a fh to \a heh. + * \note \a fProp must have been previously created through createNormalPropOnFaces(). + */ + inline void + copyNormalFromFace( FaceHandle fh, HalfedgeHandle heh, OpenMesh::FPropHandleT fProp ); + + /** + * Interpolate normal property on edge center (after edge split). + */ + inline void + interpolateNormal( HalfedgeHandle in_a, HalfedgeHandle in_b, HalfedgeHandle out, Scalar f ); + + /** Interpolate normal property on face center. + * \note \a fProp must have been previously created through createNormalPropOnFaces(). + */ + inline void interpolateNormalOnFaces( FaceHandle fh, OpenMesh::FPropHandleT fProp ); + ///@} + + /** + * \name Dealing with custom properties + * Utils to deal with custom properties of any type when modifying the mesh topology. + */ + ///@{ + + /** + * Create a new property for each \a input properties of \a mesh on faces. + * \note This new property will have to be propagated onto the newly created + * halfedges with copyProps(). + */ + template + void createPropsOnFaces( const std::vector>& input, + std::vector>& output ); + + /** + * Remove \a props from \a mesh. + * \note Clears \a props. + */ + template + void clearProps( std::vector>& props ); + + /** + * Copy \a props properties from \a input_heh to \a copy_heh. + */ + template + void copyProps( HalfedgeHandle input_heh, + HalfedgeHandle copy_heh, + const std::vector>& props ); + + /** + * Copy face properties \a props from \a fh to \a heh. + * \note \a fProps must have been previously created through createPropsOnFaces(). + */ + template + void copyPropsFromFace( FaceHandle fh, + HalfedgeHandle heh, + const std::vector>& fProps, + const std::vector>& hProps ); + + /** + * Interpolate \a props on edge center (after edge split). + */ + template + void interpolateProps( HalfedgeHandle in_a, + HalfedgeHandle in_b, + HalfedgeHandle out, + Scalar f, + const std::vector>& props ); + + /** + * Interpolate \a hprops on face center. + * \note \a fProps must have been previously created through createPropsOnFaces(). + */ + template + void interpolatePropsOnFaces( FaceHandle fh, + const std::vector>& hProps, + const std::vector>& fProps ); + ///@} + /** + * \name Deal with all attributes* Utils to deal with the normal and + custom properties when modifying the mesh topology.*/ + + ///@{ + + /** + * Create a new property for each property of \a mesh on faces. + * Outputs the new face properties handles in the corresponding output parameters. + * \note These new properties will have to be propagated onto the newly created + * halfedges with copyAllProps(). + */ + inline void createAllPropsOnFaces( OpenMesh::FPropHandleT& normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ); + + /** + * Remove all the given properties from \a mesh. + * \note Invalidates \a normalProp and clears the given property containers. + */ + inline void clearAllProps( OpenMesh::FPropHandleT& normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ); + + /** + * Copy all properties from \a input_heh to \a copy_heh. + */ + inline void copyAllProps( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ); + + /** + * Copy all given face properties from \a fh to \a heh. + * \note Each property must have been previously created either all at once + * through createAllPropsOnFaces(), or individually through + * createNormalPropOnFaces() and createPropsOnFaces(). + */ + inline void copyAllPropsFromFace( FaceHandle fh, + HalfedgeHandle heh, + OpenMesh::FPropHandleT normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ); + + /** + * Interpolate all properties on edge center (after edge split). + */ + inline void + interpolateAllProps( HalfedgeHandle in_a, HalfedgeHandle in_b, HalfedgeHandle out, Scalar f ); + + /** + * Interpolate \a hprops on face center. + * \note Each property must have been previously created either all at once + * through createAllPropsOnFaces(), or individually through + * createNormalPropOnFaces() and createPropsOnFaces(). + */ + inline void + interpolateAllPropsOnFaces( FaceHandle fh, + OpenMesh::FPropHandleT normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ); + ///@} + + /** + * \name Topological operations + */ + ///@{ + /** + * Apply a 2-4 edge split. + * \param eh The handle to the edge to split. + * \param f The interpolation factor to place the new point on the edge. + * Must be in [0,1]. + * \return True if the edge has been split, false otherwise. + * \note Only applies on edges between 2 triangles, and if \a f is in [0,1]. + * \note Mesh attributes are linearly interpolated on the newly created + * halfedge. + * \note f=0 correspond to halfedge_handle( eh, 0 ) (i.e. first vertex of + * the edge) + */ + bool splitEdge( TopologicalMesh::EdgeHandle eh, Scalar f ); + bool splitEdgeWedge( TopologicalMesh::EdgeHandle eh, Scalar f ); + + ///@} + + private: + template + using HandleAndValueVector = + std::vector, T>, + Eigen::aligned_allocator, T>>>; + + template + using PropPair = std::pair, OpenMesh::HPropHandleT>; + + template + inline void copyAttribToTopo( const TriangleMesh& triMesh, + const std::vector>& vprop, + TopologicalMesh::HalfedgeHandle heh, + unsigned int vindex ); + + template + inline void addAttribPairToTopo( const TriangleMesh& triMesh, + AttribManager::pointer_type attr, + std::vector>& vprop, + std::vector>& pph ); + + void split_copy( EdgeHandle _eh, VertexHandle _vh ); + void split( EdgeHandle _eh, VertexHandle _vh ); + + ///\todo to be deleted/updated + OpenMesh::HPropHandleT m_inputTriangleMeshIndexPph; + OpenMesh::HPropHandleT m_outputTriangleMeshIndexPph; + [[deprecated]] std::vector> m_floatPph; + [[deprecated]] std::vector> m_vec2Pph; + [[deprecated]] std::vector> m_vec3Pph; + [[deprecated]] std::vector> m_vec4Pph; + + friend class TMOperations; +}; + +} // namespace deprecated +} // namespace Geometry +} // namespace Core +} // namespace Ra +#include diff --git a/src/Core/Geometry/deprecated/TopologicalMesh.inl b/src/Core/Geometry/deprecated/TopologicalMesh.inl new file mode 100644 index 00000000000..ae0edd8dd4a --- /dev/null +++ b/src/Core/Geometry/deprecated/TopologicalMesh.inl @@ -0,0 +1,472 @@ +#pragma once + +#include "TopologicalMesh.hpp" + +#include +#include + +namespace Ra { +namespace Core { +namespace Geometry { +namespace deprecated { +template +inline TopologicalMesh::TopologicalMesh( const TriangleMesh& triMesh, + NonManifoldFaceCommand command ) : + TopologicalMesh() { + + LOG( logINFO ) << "TopologicalMesh: load triMesh with " << triMesh.getIndices().size() + << " faces and " << triMesh.vertices().size() << " vertices."; + + struct hash_vec { + size_t operator()( const Vector3& lvalue ) const { + size_t hx = std::hash()( lvalue[0] ); + size_t hy = std::hash()( lvalue[1] ); + size_t hz = std::hash()( lvalue[2] ); + return ( hx ^ ( hy << 1 ) ) ^ hz; + } + }; + // use a hashmap for fast search of existing vertex position + using VertexMap = std::unordered_map; + VertexMap vertexHandles; + + std::vector> vprop_float; + std::vector, OpenMesh::HPropHandleT>> vprop_vec2; + std::vector, OpenMesh::HPropHandleT>> vprop_vec3; + std::vector, OpenMesh::HPropHandleT>> vprop_vec4; + + // loop over all attribs and build correspondance pair + triMesh.vertexAttribs().for_each_attrib( + [&triMesh, this, &vprop_float, &vprop_vec2, &vprop_vec3, &vprop_vec4]( const auto& attr ) { + // skip builtin attribs + if ( attr->getName() != std::string( "in_position" ) && + attr->getName() != std::string( "in_normal" ) ) + { + if ( attr->isFloat() ) + addAttribPairToTopo( triMesh, attr, vprop_float, m_floatPph ); + else if ( attr->isVector2() ) + addAttribPairToTopo( triMesh, attr, vprop_vec2, m_vec2Pph ); + else if ( attr->isVector3() ) + addAttribPairToTopo( triMesh, attr, vprop_vec3, m_vec3Pph ); + else if ( attr->isVector4() ) + addAttribPairToTopo( triMesh, attr, vprop_vec4, m_vec4Pph ); + else + LOG( logWARNING ) + << "Warning, mesh attribute " << attr->getName() + << " type is not supported (only float, vec2, vec3 nor vec4 are supported)"; + } + } ); + + size_t num_triangles = triMesh.getIndices().size(); + + command.initialize( triMesh ); + + const bool hasNormals = !triMesh.normals().empty(); + if ( !hasNormals ) + { + release_face_normals(); + release_vertex_normals(); + release_halfedge_normals(); + } + for ( unsigned int i = 0; i < num_triangles; i++ ) + { + std::vector face_vhandles( 3 ); + std::vector face_normals( 3 ); + std::vector face_vertexIndex( 3 ); + const auto& triangle = triMesh.getIndices()[i]; + for ( size_t j = 0; j < 3; ++j ) + { + unsigned int inMeshVertexIndex = triangle[j]; + const Vector3& p = triMesh.vertices()[inMeshVertexIndex]; + + typename VertexMap::iterator vtr = vertexHandles.find( p ); + + TopologicalMesh::VertexHandle vh; + if ( vtr == vertexHandles.end() ) + { + vh = add_vertex( p ); + vertexHandles.insert( vtr, typename VertexMap::value_type( p, vh ) ); + } + else + { vh = vtr->second; } + + face_vhandles[j] = vh; + face_vertexIndex[j] = inMeshVertexIndex; + if ( hasNormals ) face_normals[j] = triMesh.normals()[inMeshVertexIndex]; + } + + TopologicalMesh::FaceHandle fh; + + // skip 2 vertex face + if ( face_vhandles.size() > 2 ) fh = add_face( face_vhandles ); + // x-----------------------------------------------------------------------------------x + + if ( fh.is_valid() ) + { + for ( size_t vindex = 0; vindex < face_vhandles.size(); vindex++ ) + { + TopologicalMesh::HalfedgeHandle heh = halfedge_handle( face_vhandles[vindex], fh ); + if ( hasNormals ) set_normal( heh, face_normals[vindex] ); + property( m_inputTriangleMeshIndexPph, heh ) = face_vertexIndex[vindex]; + copyAttribToTopo( triMesh, vprop_float, heh, face_vertexIndex[vindex] ); + copyAttribToTopo( triMesh, vprop_vec2, heh, face_vertexIndex[vindex] ); + copyAttribToTopo( triMesh, vprop_vec3, heh, face_vertexIndex[vindex] ); + copyAttribToTopo( triMesh, vprop_vec4, heh, face_vertexIndex[vindex] ); + } + } + else + { command.process( face_vhandles ); } + face_vhandles.clear(); + face_normals.clear(); + face_vertexIndex.clear(); + } + command.postProcess( *this ); + + // grabage collect since some wedge might already be deleted + garbage_collection(); +} + +template +void TopologicalMesh::addAttribPairToTopo( const TriangleMesh& triMesh, + AttribManager::pointer_type attr, + std::vector>& vprop, + std::vector>& pph ) { + AttribHandle h = triMesh.getAttribHandle( attr->getName() ); + if ( attr->getSize() == triMesh.vertices().size() ) + { + OpenMesh::HPropHandleT oh; + this->add_property( oh, attr->getName() ); + vprop.push_back( std::make_pair( h, oh ) ); + pph.push_back( oh ); + } + else + { + LOG( logWARNING ) << "[TopologicalMesh] Skip badly sized attribute " << attr->getName() + << "."; + } +} + +template +void TopologicalMesh::copyAttribToTopo( const TriangleMesh& triMesh, + const std::vector>& vprop, + TopologicalMesh::HalfedgeHandle heh, + unsigned int vindex ) { + for ( auto pp : vprop ) + { + this->property( pp.second, heh ) = triMesh.getAttrib( pp.first ).data()[vindex]; + } +} + +inline const TopologicalMesh::Normal& TopologicalMesh::normal( VertexHandle vh, + FaceHandle fh ) const { + // find halfedge that point to vh and member of fh + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, return dummy ref to (0,0,0)"; + static TopologicalMesh::Normal dummy {0_ra, 0_ra, 0_ra}; + return dummy; + } + return normal( halfedge_handle( vh, fh ) ); +} + +inline void TopologicalMesh::set_normal( VertexHandle vh, FaceHandle fh, const Normal& n ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } + + set_normal( halfedge_handle( vh, fh ), n ); +} + +inline void TopologicalMesh::propagate_normal_to_halfedges( VertexHandle vh ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } + for ( VertexIHalfedgeIter vih_it = vih_iter( vh ); vih_it.is_valid(); ++vih_it ) + { + set_normal( *vih_it, normal( vh ) ); + } +} + +inline TopologicalMesh::HalfedgeHandle TopologicalMesh::halfedge_handle( VertexHandle vh, + FaceHandle fh ) const { + for ( ConstVertexIHalfedgeIter vih_it = cvih_iter( vh ); vih_it.is_valid(); ++vih_it ) + { + if ( face_handle( *vih_it ) == fh ) { return *vih_it; } + } + CORE_ASSERT( false, "vh is not a vertex of face fh" ); + return HalfedgeHandle(); +} + +inline const OpenMesh::HPropHandleT& +TopologicalMesh::getInputTriangleMeshIndexPropHandle() const { + return m_inputTriangleMeshIndexPph; +} + +inline const OpenMesh::HPropHandleT& +TopologicalMesh::getOutputTriangleMeshIndexPropHandle() const { + return m_outputTriangleMeshIndexPph; +} + +inline const std::vector>& +TopologicalMesh::getFloatPropsHandles() const { + return m_floatPph; +} + +inline const std::vector>& +TopologicalMesh::getVector2PropsHandles() const { + return m_vec2Pph; +} + +inline const std::vector>& +TopologicalMesh::getVector3PropsHandles() const { + return m_vec3Pph; +} + +inline const std::vector>& +TopologicalMesh::getVector4PropsHandles() const { + return m_vec4Pph; +} + +inline void TopologicalMesh::createNormalPropOnFaces( OpenMesh::FPropHandleT& fProp ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } + auto nph = halfedge_normals_pph(); + add_property( fProp, property( nph ).name() + "_subdiv_copy_F" ); +} + +inline void TopologicalMesh::clearProp( OpenMesh::FPropHandleT& fProp ) { + remove_property( fProp ); +} + +inline void TopologicalMesh::copyNormal( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } + auto nph = halfedge_normals_pph(); + property( nph, copy_heh ) = property( nph, input_heh ); +} + +inline void TopologicalMesh::copyNormalFromFace( FaceHandle fh, + HalfedgeHandle heh, + OpenMesh::FPropHandleT fProp ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } + auto nph = halfedge_normals_pph(); + property( nph, heh ) = property( fProp, fh ); +} + +inline void TopologicalMesh::interpolateNormal( HalfedgeHandle in_a, + HalfedgeHandle in_b, + HalfedgeHandle out, + Scalar f ) { + auto nph = halfedge_normals_pph(); + property( nph, out ) = + ( ( 1 - f ) * property( nph, in_a ) + f * property( nph, in_b ) ).normalized(); +} + +inline void TopologicalMesh::interpolateNormalOnFaces( FaceHandle fh, + OpenMesh::FPropHandleT fProp ) { + if ( !has_halfedge_normals() ) + { + LOG( logERROR ) << "TopologicalMesh has no normals, nothing set"; + return; + } + auto nph = halfedge_normals_pph(); + + // init sum to first + auto heh = halfedge_handle( fh ); + property( fProp, fh ) = property( nph, heh ); + heh = next_halfedge_handle( heh ); + + // sum others + for ( size_t i = 1; i < valence( fh ); ++i ) + { + property( fProp, fh ) += property( nph, heh ); + heh = next_halfedge_handle( heh ); + } + + // normalize + property( fProp, fh ) = property( fProp, fh ).normalized(); +} + +template +void TopologicalMesh::createPropsOnFaces( const std::vector>& input, + std::vector>& output ) { + output.reserve( input.size() ); + for ( const auto& oh : input ) + { + OpenMesh::FPropHandleT oh_; + add_property( oh_, property( oh ).name() + "_subdiv_copy_F" ); + output.push_back( oh_ ); + } +} + +template +void TopologicalMesh::clearProps( std::vector>& props ) { + for ( auto& oh : props ) + { + remove_property( oh ); + } + props.clear(); +} + +template +void TopologicalMesh::copyProps( HalfedgeHandle input_heh, + HalfedgeHandle copy_heh, + const std::vector>& props ) { + for ( const auto& oh : props ) + { + property( oh, copy_heh ) = property( oh, input_heh ); + } +} + +template +void TopologicalMesh::copyPropsFromFace( FaceHandle fh, + HalfedgeHandle heh, + const std::vector>& fProps, + const std::vector>& hProps ) { + for ( uint i = 0; i < fProps.size(); ++i ) + { + auto hp = hProps[i]; + auto fp = fProps[i]; + property( hp, heh ) = property( fp, fh ); + } +} + +template +void TopologicalMesh::interpolateProps( HalfedgeHandle in_a, + HalfedgeHandle in_b, + HalfedgeHandle out, + Scalar f, + const std::vector>& props ) { + // interpolate properties + for ( const auto& oh : props ) + { + property( oh, out ) = ( 1 - f ) * property( oh, in_a ) + f * property( oh, in_b ); + } +} + +template +void TopologicalMesh::interpolatePropsOnFaces( + FaceHandle fh, + const std::vector>& hProps, + const std::vector>& fProps ) { + auto heh = halfedge_handle( fh ); + const size_t v = valence( fh ); + + // init sum to first + for ( size_t j = 0; j < fProps.size(); ++j ) + { + auto hp = hProps[j]; + auto fp = fProps[j]; + property( fp, fh ) = property( hp, heh ); + } + heh = next_halfedge_handle( heh ); + // sum others + for ( size_t i = 1; i < v; ++i ) + { + for ( size_t j = 0; j < fProps.size(); ++j ) + { + auto hp = hProps[j]; + auto fp = fProps[j]; + property( fp, fh ) += property( hp, heh ); + } + heh = next_halfedge_handle( heh ); + } + // normalize + for ( size_t j = 0; j < fProps.size(); ++j ) + { + auto fp = fProps[j]; + property( fp, fh ) = property( fp, fh ) / v; + } +} + +inline void +TopologicalMesh::createAllPropsOnFaces( OpenMesh::FPropHandleT& normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ) { + createNormalPropOnFaces( normalProp ); + createPropsOnFaces( getFloatPropsHandles(), floatProps ); + createPropsOnFaces( getVector2PropsHandles(), vec2Props ); + createPropsOnFaces( getVector3PropsHandles(), vec3Props ); + createPropsOnFaces( getVector4PropsHandles(), vec4Props ); +} + +inline void +TopologicalMesh::clearAllProps( OpenMesh::FPropHandleT& normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ) { + clearProp( normalProp ); + clearProps( floatProps ); + clearProps( vec2Props ); + clearProps( vec3Props ); + clearProps( vec4Props ); +} + +inline void TopologicalMesh::copyAllProps( HalfedgeHandle input_heh, HalfedgeHandle copy_heh ) { + copyNormal( input_heh, copy_heh ); + copyProps( input_heh, copy_heh, getFloatPropsHandles() ); + copyProps( input_heh, copy_heh, getVector2PropsHandles() ); + copyProps( input_heh, copy_heh, getVector3PropsHandles() ); + copyProps( input_heh, copy_heh, getVector4PropsHandles() ); +} + +inline void +TopologicalMesh::copyAllPropsFromFace( FaceHandle fh, + HalfedgeHandle heh, + OpenMesh::FPropHandleT normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ) { + copyNormalFromFace( fh, heh, normalProp ); + copyPropsFromFace( fh, heh, floatProps, getFloatPropsHandles() ); + copyPropsFromFace( fh, heh, vec2Props, getVector2PropsHandles() ); + copyPropsFromFace( fh, heh, vec3Props, getVector3PropsHandles() ); + copyPropsFromFace( fh, heh, vec4Props, getVector4PropsHandles() ); +} + +inline void TopologicalMesh::interpolateAllProps( HalfedgeHandle in_a, + HalfedgeHandle in_b, + HalfedgeHandle out, + Scalar f ) { + interpolateNormal( in_a, in_b, out, f ); + interpolateProps( in_a, in_b, out, f, getFloatPropsHandles() ); + interpolateProps( in_a, in_b, out, f, getVector2PropsHandles() ); + interpolateProps( in_a, in_b, out, f, getVector3PropsHandles() ); + interpolateProps( in_a, in_b, out, f, getVector4PropsHandles() ); +} + +inline void TopologicalMesh::interpolateAllPropsOnFaces( + FaceHandle fh, + OpenMesh::FPropHandleT normalProp, + std::vector>& floatProps, + std::vector>& vec2Props, + std::vector>& vec3Props, + std::vector>& vec4Props ) { + interpolateNormalOnFaces( fh, normalProp ); + interpolatePropsOnFaces( fh, getFloatPropsHandles(), floatProps ); + interpolatePropsOnFaces( fh, getVector2PropsHandles(), vec2Props ); + interpolatePropsOnFaces( fh, getVector3PropsHandles(), vec3Props ); + interpolatePropsOnFaces( fh, getVector4PropsHandles(), vec4Props ); +} + +} // namespace deprecated +} // namespace Geometry +} // namespace Core +} // namespace Ra diff --git a/tests/ExampleApps/DrawPrimitivesApp/minimalradium.cpp b/tests/ExampleApps/DrawPrimitivesApp/minimalradium.cpp index da964733931..f06d36e4128 100644 --- a/tests/ExampleApps/DrawPrimitivesApp/minimalradium.cpp +++ b/tests/ExampleApps/DrawPrimitivesApp/minimalradium.cpp @@ -40,6 +40,7 @@ const bool ENABLE_DISKS = true; const bool ENABLE_NORMALS = true; const bool ENABLE_POLYS = true; const bool ENABLE_LOGO = true; +const bool ENABLE_COLLAPSE = true; using namespace Ra; using namespace Ra::Core; @@ -645,6 +646,25 @@ void MinimalComponent::initialize() { Eigen::UniformScaling( 0.06_ra )} ); addRenderObject( renderObject1 ); + + Ra::Core::Geometry::TopologicalMesh topo {poly1->getCoreGeometry()}; + topo.triangulate(); + topo.checkIntegrity(); + auto triangulated = topo.toTriangleMesh(); + std::shared_ptr poly2( new Mesh( "Poly", std::move( triangulated ) ) ); + poly2->getCoreGeometry().addAttrib( + "in_color", + Vector4Array {poly2->getNumVertices(), + colorBoost * Utils::Color {0_ra, 0.6_ra, 0.1_ra}} ); + + auto renderObject2 = RenderObject::createRenderObject( + "triangulated", this, RenderObjectType::Geometry, poly2, {} ); + renderObject2->setMaterial( blinnPhongMaterial ); + renderObject2->setLocalTransform( + Transform {Translation( Vector3( cellCorner ) + toCellCenter ) * + Eigen::UniformScaling( 0.03_ra )} ); + + addRenderObject( renderObject2 ); } if ( ENABLE_LOGO ) @@ -709,6 +729,234 @@ void MinimalComponent::initialize() { } } } + + if ( ENABLE_COLLAPSE ) + { + updateCellCorner( cellCorner, cellSize, nCellX, nCellY ); + updateCellCorner( cellCorner, cellSize, nCellX, nCellY ); + + using namespace Ra::Core; + using namespace Ra::Core::Utils; + using namespace Ra::Core::Geometry; + auto findHalfedge = []( TopologicalMesh& topo, + const Vector3& from, + const Vector3& to ) -> optional { + bool found; + TopologicalMesh::HalfedgeHandle he; + for ( auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); + ++he_iter ) + { + + if ( topo.point( topo.to_vertex_handle( he_iter ) ) == to && + topo.point( topo.from_vertex_handle( he_iter ) ) == from ) + { + found = true; + he = *he_iter; + } + } + if ( found ) return he; + return {}; + }; + + auto addMesh = [this, colorBoost, plainMaterial]( Vector3 pos, TopologicalMesh topo1 ) { + topo1.checkIntegrity(); + auto mesh1 = topo1.toTriangleMesh(); + std::shared_ptr poly( new Mesh( "TEST", std::move( mesh1 ) ) ); + + auto renderObject2 = RenderObject::createRenderObject( + "TEST", this, RenderObjectType::Geometry, poly, {} ); + renderObject2->setMaterial( plainMaterial ); + renderObject2->setLocalTransform( Transform { + Translation( Vector3( pos ) ) * Eigen::UniformScaling( 0.003_ra )} ); + + addRenderObject( renderObject2 ); + }; + + Vector3Array points { + {00._ra, 00._ra, 00._ra}, + {10._ra, 00._ra, 00._ra}, + {05._ra, 05._ra, 00._ra}, + {05._ra, 10._ra, 00._ra}, + {15._ra, 05._ra, 00._ra}, + {10._ra, 08._ra, 00._ra}, + {10._ra, 12._ra, 00._ra}, + {15._ra, 10._ra, 00._ra}, + }; + Vector3Array points2 = {points[0], points[0], points[1], points[1], points[1], points[2], + points[2], points[2], points[2], points[3], points[3], points[3], + points[4], points[4], points[5], points[5], points[5], points[5], + points[5], points[5], points[6], points[6], points[7], points[7]}; + + Vector4Array colors = { + {0_ra, 0_ra, 0_ra, 1_ra}, {1_ra, 1_ra, 1_ra, 1_ra}, {2_ra, 2_ra, 2_ra, 1_ra}, + {3_ra, 3_ra, 3_ra, 1_ra}, {4_ra, 4_ra, 4_ra, 1_ra}, {5_ra, 5_ra, 5_ra, 1_ra}, + {6_ra, 6_ra, 6_ra, 1_ra}, {7_ra, 7_ra, 7_ra, 1_ra}, {8_ra, 8_ra, 8_ra, 1_ra}, + {9_ra, 9_ra, 9_ra, 1_ra}, {10_ra, 10_ra, 10_ra, 1_ra}, {11_ra, 11_ra, 11_ra, 1_ra}, + {12_ra, 12_ra, 12_ra, 1_ra}, {13_ra, 13_ra, 13_ra, 1_ra}, {14_ra, 14_ra, 14_ra, 1_ra}, + {15_ra, 15_ra, 15_ra, 1_ra}, {16_ra, 16_ra, 16_ra, 1_ra}, {17_ra, 17_ra, 17_ra, 1_ra}, + {18_ra, 18_ra, 18_ra, 1_ra}, {19_ra, 19_ra, 19_ra, 1_ra}, {20_ra, 20_ra, 20_ra, 1_ra}, + {21_ra, 21_ra, 21_ra, 1_ra}, {22_ra, 22_ra, 22_ra, 1_ra}, {23_ra, 23_ra, 23_ra, 1_ra}, + }; + + for ( auto& c : colors ) + { + c = colorBoost * Vector4 {dis01( gen ), dis01( gen ), dis01( gen ), 1_ra}; + } + + Vector3uArray indices1 { + {0, 2, 1}, {0, 3, 2}, {1, 2, 5}, {2, 3, 5}, {1, 5, 4}, {3, 6, 5}, {5, 6, 7}, {4, 5, 7}}; + Vector3uArray indices3 = {{0, 2, 1}, {1, 2, 5}, {1, 5, 4}, {3, 6, 5}, {5, 6, 7}, {4, 5, 7}}; + + Vector3uArray indices4 = { + {0, 2, 5}, {3, 14, 6}, {4, 12, 15}, {11, 18, 20}, {17, 22, 21}, {16, 13, 23}}; + + Vector3uArray indices2 {{0, 5, 2}, + {1, 9, 8}, + {3, 6, 14}, + {7, 10, 19}, + {4, 15, 12}, + {11, 20, 18}, + {17, 21, 22}, + {16, 23, 13}}; + Vector4Array colors2 {24, Color::White()}; + for ( const auto& face : indices2 ) + { + colors2[face[0]] = colors[face[0]]; + colors2[face[1]] = colors[face[0]]; + colors2[face[2]] = colors[face[0]]; + } + + Vector4Array colors3 {24, Color::White()}; + std::vector topFaceIndices {1, 3, 5, 6}; + std::vector bottomFaceIndices {0, 2, 4, 7}; + + for ( const auto& faceIndex : topFaceIndices ) + { + colors3[indices2[faceIndex][0]] = colors[0]; + colors3[indices2[faceIndex][1]] = colors[0]; + colors3[indices2[faceIndex][2]] = colors[0]; + } + for ( const auto& faceIndex : bottomFaceIndices ) + { + colors3[indices2[faceIndex][0]] = colors[1]; + colors3[indices2[faceIndex][1]] = colors[1]; + colors3[indices2[faceIndex][2]] = colors[1]; + } + Vector4Array colors4 {24, Color::White()}; + + std::vector> splitContinuousWedges {// 0 + {0}, + {1}, + // 1 + {2, 3, 4}, + // 2 + {8, 7}, + {5, 6}, + // 3 + {9, 10, 11}, + // 4 + {12, 13}, + // 5 + {14, 15, 16}, + {17, 18, 19}, + // 6 + {20, 21}, + // 7 + {22, 23}}; + + for ( size_t i = 0; i < splitContinuousWedges.size(); ++i ) + { + for ( const auto& widx : splitContinuousWedges[i] ) + { + colors4[widx] = colors[i]; + } + } + + auto addMergeScene = + [findHalfedge, addMesh, &cellCorner, toCellCenter, cellSize, nCellX, nCellY]( + Vector3 pos, + const Vector3Array& points, + const Vector4Array& colors, + const Vector3uArray& indices1, + Vector3 from, + Vector3 to ) { + TriangleMesh mesh1; + TopologicalMesh topo1; + optional optHe; + Vector3 up {0_ra, .05_ra, 0_ra}; + + mesh1.setVertices( points ); + mesh1.addAttrib( Mesh::getAttribName( Mesh::VERTEX_COLOR ), + Vector4Array {colors.begin(), colors.begin() + points.size()} ); + mesh1.setIndices( indices1 ); + topo1 = TopologicalMesh {mesh1}; + topo1.mergeEqualWedges(); + topo1.garbage_collection(); + topo1.checkIntegrity(); + optHe = findHalfedge( topo1, from, to ); + + addMesh( pos, topo1 ); + + pos += up; + topo1.collapse( *optHe ); + addMesh( pos, topo1 ); + + topo1 = TopologicalMesh {mesh1}; + optHe = findHalfedge( topo1, from, to ); + pos += up; + topo1.collapse( *optHe, true ); + addMesh( pos, topo1 ); + + std::swap( from, to ); + topo1 = TopologicalMesh {mesh1}; + topo1.mergeEqualWedges(); + + optHe = findHalfedge( topo1, from, to ); + + pos += up; + topo1.collapse( *optHe ); + addMesh( pos, topo1 ); + + topo1 = TopologicalMesh {mesh1}; + optHe = findHalfedge( topo1, from, to ); + pos += up; + topo1.collapse( *optHe, true ); + addMesh( pos, topo1 ); + }; + Vector3 dx = Vector3( cellSize / 8_ra, 0_ra, 0_ra ); + Vector3 pos = cellCorner; + pos[2] += toCellCenter[2]; + // With "continuous" wedges. + addMergeScene( pos, points, colors, indices1, points[5], points[2] ); + + // with "top/bottom" wedges + addMergeScene( pos, points2, colors3, indices2, points[5], points[2] ); + pos += dx; + + // with continuous"top/bottom" wedges + addMergeScene( pos, points2, colors4, indices2, points[5], points[2] ); + pos += dx; + + // with "flat face" wedges + addMergeScene( pos, points2, colors2, indices2, points[5], points[2] ); + pos += dx; + + // boundary + // With "continuous" wedges. + addMergeScene( pos, points, colors, indices3, points[5], points[2] ); + pos += dx; + + // with "top/bottom" wedges + addMergeScene( pos, points2, colors3, indices4, points[5], points[2] ); + pos += dx; + + // with continuous"top/bottom" wedges + addMergeScene( pos, points2, colors4, indices4, points[5], points[2] ); + pos += dx; + + // with "flat face" wedges + addMergeScene( pos, points2, colors2, indices4, points[5], points[2] ); + } } /// This system will be added to the engine. Every frame it will diff --git a/tests/unittest/Core/topomesh.cpp b/tests/unittest/Core/topomesh.cpp index de7522229a3..cee787f558e 100644 --- a/tests/unittest/Core/topomesh.cpp +++ b/tests/unittest/Core/topomesh.cpp @@ -110,19 +110,21 @@ class WedgeDataAndIdx bool operator!=( const WedgeDataAndIdx& lhs ) const { return !( *this == lhs ); } }; -#define COPY_TO_WEDGES_VECTOR_HELPER( UPTYPE, REALTYPE ) \ - if ( attr->is##UPTYPE() ) \ - { \ - auto data = \ - meshOne.getAttrib( meshOne.getAttribHandle( attr->getName() ) ).data(); \ - for ( size_t i = 0; i < size; ++i ) \ - { \ - wedgesMeshOne[i].m_data.getAttribArray().push_back( data[i] ); \ - } \ +#define COPY_TO_WEDGES_VECTOR_HELPER( UPTYPE, REALTYPE ) \ + if ( attr->is##UPTYPE() ) \ + { \ + auto data = \ + meshOne.getAttrib( meshOne.template getAttribHandle( attr->getName() ) ) \ + .data(); \ + for ( size_t i = 0; i < size; ++i ) \ + { \ + wedgesMeshOne[i].m_data.getAttribArray().push_back( data[i] ); \ + } \ } +template void copyToWedgesVector( size_t size, - const TriangleMesh& meshOne, + const IndexedGeometry& meshOne, AlignedStdVector& wedgesMeshOne, AttribBase* attr ) { @@ -140,6 +142,7 @@ void copyToWedgesVector( size_t size, wedgesMeshOne[i].m_data.m_position = data[i]; } } + COPY_TO_WEDGES_VECTOR_HELPER( Float, float ); COPY_TO_WEDGES_VECTOR_HELPER( Vector2, Vector2 ); COPY_TO_WEDGES_VECTOR_HELPER( Vector3, Vector3 ); @@ -148,13 +151,18 @@ void copyToWedgesVector( size_t size, } #undef COPY_TO_WEDGES_VECTOR_HELPER -bool isSameMeshWedge( const Ra::Core::Geometry::TriangleMesh& meshOne, - const Ra::Core::Geometry::TriangleMesh& meshTwo ) { +template +bool isSameMeshWedge( const Ra::Core::Geometry::IndexedGeometry& meshOne, + const Ra::Core::Geometry::IndexedGeometry& meshTwo ) { using namespace Ra::Core; using namespace Ra::Core::Geometry; // Check length + // LOG( logDEBUG ) << meshOne.vertices().size() << " / " << meshTwo.vertices().size(); + // LOG( logDEBUG ) << meshOne.normals().size() << " / " << meshTwo.normals().size(); + // LOG( logDEBUG ) << meshOne.getIndices().size() << " / " << meshTwo.getIndices().size(); + if ( meshOne.vertices().size() != meshTwo.vertices().size() ) return false; if ( meshOne.normals().size() != meshTwo.normals().size() ) return false; if ( meshOne.getIndices().size() != meshTwo.getIndices().size() ) return false; @@ -171,22 +179,28 @@ bool isSameMeshWedge( const Ra::Core::Geometry::TriangleMesh& meshOne, wedgesMeshTwo.push_back( wd ); } using namespace std::placeholders; - auto f1 = - std::bind( copyToWedgesVector, size, std::cref( meshOne ), std::ref( wedgesMeshOne ), _1 ); + auto f1 = std::bind( + copyToWedgesVector, size, std::cref( meshOne ), std::ref( wedgesMeshOne ), _1 ); meshOne.vertexAttribs().for_each_attrib( f1 ); - auto f2 = - std::bind( copyToWedgesVector, size, std::cref( meshTwo ), std::ref( wedgesMeshTwo ), _1 ); + auto f2 = std::bind( + copyToWedgesVector, size, std::cref( meshTwo ), std::ref( wedgesMeshTwo ), _1 ); meshTwo.vertexAttribs().for_each_attrib( f2 ); std::sort( wedgesMeshOne.begin(), wedgesMeshOne.end() ); std::sort( wedgesMeshTwo.begin(), wedgesMeshTwo.end() ); - if ( wedgesMeshOne != wedgesMeshTwo ) return false; + if ( wedgesMeshOne != wedgesMeshTwo ) + { + // LOG( logDEBUG ) << "not same wedges"; + return false; + } std::vector newMeshOneIdx( wedgesMeshOne.size() ); std::vector newMeshTwoIdx( wedgesMeshOne.size() ); - size_t curIdx = 0; + + size_t curIdx = 0; + newMeshOneIdx[wedgesMeshOne[0].m_idx] = 0; newMeshTwoIdx[wedgesMeshTwo[0].m_idx] = 0; @@ -194,31 +208,50 @@ bool isSameMeshWedge( const Ra::Core::Geometry::TriangleMesh& meshOne, { if ( wedgesMeshOne[i] != wedgesMeshOne[i - 1] ) ++curIdx; newMeshOneIdx[wedgesMeshOne[i].m_idx] = curIdx; + // std::cout << wedgesMeshOne[i].m_idx << " : " << curIdx << "\n"; } - + // std::cout << "***\n"; curIdx = 0; for ( size_t i = 1; i < wedgesMeshTwo.size(); ++i ) { if ( wedgesMeshTwo[i] != wedgesMeshTwo[i - 1] ) ++curIdx; newMeshTwoIdx[wedgesMeshTwo[i].m_idx] = curIdx; + // std::cout << wedgesMeshTwo[i].m_idx << " : " << curIdx << "\n"; } - auto indices1 = meshOne.getIndices(); - auto indices2 = meshTwo.getIndices(); + typename Ra::Core::Geometry::IndexedGeometry::IndexContainerType indices1 = + meshOne.getIndices(); + typename Ra::Core::Geometry::IndexedGeometry::IndexContainerType indices2 = + meshTwo.getIndices(); - for ( auto& triangle : indices1 ) + for ( auto& face : indices1 ) { - triangle[0] = newMeshOneIdx[triangle[0]]; - triangle[1] = newMeshOneIdx[triangle[1]]; - triangle[2] = newMeshOneIdx[triangle[2]]; + // std::cout << "face "; + for ( int i = 0; i < face.size(); ++i ) + { + face( i ) = newMeshOneIdx[face( i )]; + // std::cout << face( i ) << " "; + } + // std::cout << "\n"; + } + // std::cout << "***\n"; + for ( auto& face : indices2 ) + { + + // std::cout << "face "; + for ( int i = 0; i < face.size(); ++i ) + { + face( i ) = newMeshTwoIdx[face( i )]; + // std::cout << face( i ) << " "; + } + // std::cout << "\n"; } - for ( auto& triangle : indices2 ) + if ( indices1 != indices2 ) { - triangle[0] = newMeshTwoIdx[triangle[0]]; - triangle[1] = newMeshTwoIdx[triangle[1]]; - triangle[2] = newMeshTwoIdx[triangle[2]]; + // LOG( logDEBUG ) << "not same indices"; + return false; } - return indices1 == indices2; + return true; } TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMesh]" ) { @@ -227,21 +260,10 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe using Ra::Core::Geometry::TriangleMesh; auto testConverter = []( const TriangleMesh& mesh ) { - auto topologicalMesh = TopologicalMesh( mesh ); - auto topologicalMeshWedge = TopologicalMesh {}; - topologicalMeshWedge.initWithWedge( mesh ); - auto newMeshWedge = topologicalMesh.toTriangleMeshFromWedges(); - auto newMesh = topologicalMesh.toTriangleMesh(); - auto newMeshWedgeWedge = topologicalMeshWedge.toTriangleMeshFromWedges(); - auto newMeshWedgeWithout = topologicalMeshWedge.toTriangleMesh(); + auto topologicalMesh = TopologicalMesh( mesh ); + auto newMesh = topologicalMesh.toTriangleMesh(); REQUIRE( isSameMesh( mesh, newMesh ) ); - REQUIRE( isSameMesh( mesh, newMeshWedge ) ); - REQUIRE( isSameMesh( mesh, newMeshWedgeWedge ) ); - REQUIRE( isSameMesh( mesh, newMeshWedgeWithout ) ); - REQUIRE( isSameMeshWedge( mesh, newMeshWedge ) ); - REQUIRE( isSameMeshWedge( mesh, newMeshWedgeWedge ) ); REQUIRE( topologicalMesh.checkIntegrity() ); - REQUIRE( topologicalMeshWedge.checkIntegrity() ); }; SECTION( "Closed mesh" ) { @@ -301,53 +323,22 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe attrib4.unlock(); attrib5.unlock(); - auto topologicalMesh = TopologicalMesh( mesh ); - auto topologicalMeshWedge = TopologicalMesh {}; - topologicalMeshWedge.initWithWedge( mesh ); - auto newMesh = topologicalMesh.toTriangleMesh(); - auto newMeshWedge = topologicalMesh.toTriangleMeshFromWedges(); - auto newMeshWedgeWedge = topologicalMeshWedge.toTriangleMeshFromWedges(); - auto newMeshWedgeWithout = topologicalMeshWedge.toTriangleMesh(); + auto topologicalMesh = TopologicalMesh( mesh ); + auto newMesh = topologicalMesh.toTriangleMesh(); REQUIRE( isSameMesh( mesh, newMesh ) ); - REQUIRE( isSameMesh( mesh, newMeshWedge ) ); - REQUIRE( isSameMesh( mesh, newMeshWedgeWedge ) ); - REQUIRE( isSameMesh( mesh, newMeshWedgeWithout ) ); - REQUIRE( isSameMeshWedge( mesh, newMeshWedge ) ); - REQUIRE( isSameMeshWedge( mesh, newMeshWedgeWedge ) ); REQUIRE( topologicalMesh.checkIntegrity() ); - REQUIRE( topologicalMeshWedge.checkIntegrity() ); // oversize attrib not suported REQUIRE( !newMesh.hasAttrib( "vector5_attrib" ) ); - REQUIRE( !newMeshWedge.hasAttrib( "vector5_attrib" ) ); - REQUIRE( !newMeshWedgeWedge.hasAttrib( "vector5_attrib" ) ); - REQUIRE( !newMeshWedgeWithout.hasAttrib( "vector5_attrib" ) ); REQUIRE( newMesh.hasAttrib( "vector2_attrib" ) ); - REQUIRE( newMeshWedge.hasAttrib( "vector2_attrib" ) ); - REQUIRE( newMeshWedgeWedge.hasAttrib( "vector2_attrib" ) ); REQUIRE( newMesh.hasAttrib( "vector4_attrib" ) ); - REQUIRE( newMeshWedge.hasAttrib( "vector4_attrib" ) ); - REQUIRE( newMeshWedgeWedge.hasAttrib( "vector4_attrib" ) ); - - // When init with wedge, and converted from propos (without wedges) wedge, attribs are lost. - REQUIRE( !newMeshWedgeWithout.hasAttrib( "vector2_attrib" ) ); - REQUIRE( !newMeshWedgeWithout.hasAttrib( "vector4_attrib" ) ); // empty attrib not converted REQUIRE( !newMesh.hasAttrib( "evector2_attrib" ) ); - REQUIRE( !newMeshWedge.hasAttrib( "evector2_attrib" ) ); - REQUIRE( !newMeshWedgeWedge.hasAttrib( "veector2_attrib" ) ); - REQUIRE( !newMeshWedgeWithout.hasAttrib( "veector2_attrib" ) ); REQUIRE( !newMesh.hasAttrib( "evector4_attrib" ) ); - REQUIRE( !newMeshWedge.hasAttrib( "evector4_attrib" ) ); - REQUIRE( !newMeshWedgeWedge.hasAttrib( "veector4_attrib" ) ); - REQUIRE( !newMeshWedgeWithout.hasAttrib( "veector4_attrib" ) ); REQUIRE( !newMesh.hasAttrib( "evector5_attrib" ) ); - REQUIRE( !newMeshWedge.hasAttrib( "evector5_attrib" ) ); - REQUIRE( !newMeshWedgeWedge.hasAttrib( "evector5_attrib" ) ); - REQUIRE( !newMeshWedgeWithout.hasAttrib( "evector5_attrib" ) ); } SECTION( "Edit topo mesh" ) { @@ -355,32 +346,23 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe auto topologicalMesh = TopologicalMesh( mesh ); auto newMesh = topologicalMesh.toTriangleMesh(); - auto newMesh2 = topologicalMesh.toTriangleMeshFromWedges(); topologicalMesh.setWedgeData( TopologicalMesh::WedgeIndex {0}, "in_normal", Vector3( 0, 0, 0 ) ); - auto newMesh3 = topologicalMesh.toTriangleMeshFromWedges(); + auto newMeshModified = topologicalMesh.toTriangleMesh(); REQUIRE( isSameMesh( mesh, newMesh ) ); - REQUIRE( isSameMesh( mesh, newMesh2 ) ); - REQUIRE( isSameMeshWedge( mesh, newMesh2 ) ); - REQUIRE( !isSameMeshWedge( mesh, newMesh3 ) ); + REQUIRE( isSameMeshWedge( mesh, newMesh ) ); + REQUIRE( !isSameMeshWedge( mesh, newMeshModified ) ); REQUIRE( topologicalMesh.checkIntegrity() ); } SECTION( "Test skip empty attributes" ) { auto mesh = Ra::Core::Geometry::makeCylinder( Vector3( 0, 0, 0 ), Vector3( 0, 0, 1 ), 1 ); mesh.addAttrib( "empty" ); - auto topologicalMesh = TopologicalMesh( mesh ); - auto topologicalMeshWedge = TopologicalMesh {}; - topologicalMeshWedge.initWithWedge( mesh ); - auto newMesh = topologicalMesh.toTriangleMesh(); - auto newMeshWedge = topologicalMesh.toTriangleMeshFromWedges(); - auto newMeshWedgeWedge = topologicalMeshWedge.toTriangleMeshFromWedges(); + auto topologicalMesh = TopologicalMesh( mesh ); + auto newMesh = topologicalMesh.toTriangleMesh(); REQUIRE( !newMesh.hasAttrib( "empty" ) ); - REQUIRE( !newMeshWedge.hasAttrib( "empty" ) ); - REQUIRE( !newMeshWedgeWedge.hasAttrib( "empty" ) ); REQUIRE( topologicalMesh.checkIntegrity() ); - REQUIRE( topologicalMeshWedge.checkIntegrity() ); } SECTION( "Test normals" ) { @@ -399,7 +381,7 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe v_it != topologicalMesh.vertices_end(); ++v_it ) { - topologicalMesh.propagate_normal_to_halfedges( *v_it ); + topologicalMesh.propagate_normal_to_wedges( *v_it ); } auto newMesh = topologicalMesh.toTriangleMesh(); @@ -428,14 +410,8 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe mesh.setVertices( std::move( vertices ) ); mesh.setIndices( std::move( indices ) ); TopologicalMesh topo1 {mesh}; - TopologicalMesh topo2; - topo2.initWithWedge( mesh ); - REQUIRE( topo1.checkIntegrity() ); - REQUIRE( topo2.checkIntegrity() ); - TriangleMesh mesh1 = topo1.toTriangleMesh(); - TriangleMesh mesh2 = topo2.toTriangleMeshFromWedges(); // there is no normals at all. REQUIRE( !topo1.has_halfedge_normals() ); @@ -443,12 +419,7 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe for ( auto vitr = topo1.vertices_begin(), vend = topo1.vertices_end(); vitr != vend; ++vitr ) { - for ( auto fitr = topo1.vf_iter( *vitr ); fitr.is_valid(); ++fitr ) - { - auto n = topo1.normal( *vitr, *fitr ); - REQUIRE( Math::areApproxEqual( n.squaredNorm(), 0_ra ) ); - } - topo1.propagate_normal_to_halfedges( *vitr ); + topo1.propagate_normal_to_wedges( *vitr ); REQUIRE( !topo1.has_halfedge_normals() ); REQUIRE( !topo1.has_face_normals() ); } @@ -456,26 +427,11 @@ TEST_CASE( "Core/Geometry/TopologicalMesh", "[Core][Core/Geometry][TopologicalMe // nor on faces nor if we try to create them REQUIRE( !topo1.has_face_normals() ); OpenMesh::FPropHandleT fProp; - topo1.createNormalPropOnFaces( fProp ); - auto vh = *topo1.vertices_begin(); - auto he1 = topo1.halfedge_handle( vh ); - auto he2 = topo1.next_halfedge_handle( he1 ); - auto fh = topo1.face_handle( he1 ); - REQUIRE( !fProp.is_valid() ); - REQUIRE( !topo1.has_face_normals() ); - // even if we try to copy them, but no access error - topo1.copyNormal( he1, he2 ); - topo1.copyNormalFromFace( fh, he1, fProp ); - topo1.interpolateNormalOnFaces( fh, fProp ); - REQUIRE( !topo1.has_halfedge_normals() ); REQUIRE( mesh.vertexAttribs().hasSameAttribs( mesh1.vertexAttribs() ) ); - REQUIRE( mesh.vertexAttribs().hasSameAttribs( mesh2.vertexAttribs() ) ); REQUIRE( isSameMesh( mesh, mesh1 ) ); - REQUIRE( isSameMesh( mesh, mesh2 ) ); REQUIRE( mesh1.normals().size() == 0 ); - REQUIRE( mesh2.normals().size() == 0 ); } } @@ -490,7 +446,7 @@ void test_split( TopologicalMesh& topo, TopologicalMesh::EdgeHandle eh, float f float f0 = topo.getWedgeData( *( topo.getVertexWedges( v0 ) ).begin() ).m_floatAttrib[0]; auto p1 = topo.point( v1 ); float f1 = topo.getWedgeData( *( topo.getVertexWedges( v1 ) ).begin() ).m_floatAttrib[0]; - topo.splitEdgeWedge( eh, f ); + topo.splitEdge( eh, f ); // check validity REQUIRE( topo.is_valid_handle( he0 ) ); @@ -518,6 +474,66 @@ void test_split( TopologicalMesh& topo, TopologicalMesh::EdgeHandle eh, float f REQUIRE( Math::areApproxEqual( ( psplit - wd.m_position ).squaredNorm(), 0.f ) ); } +void test_poly() { + Ra::Core::Geometry::PolyMesh polyMesh; + polyMesh.setVertices( { + // quad + {-1.1_ra, -0_ra, 0_ra}, + {1.1_ra, -0_ra, 0_ra}, + {1_ra, 1_ra, 0_ra}, + {-1_ra, 1_ra, 0_ra}, + // hepta + {2_ra, 2_ra, 0_ra}, + {2_ra, 3_ra, 0_ra}, + {0_ra, 4_ra, 0_ra}, + {-2_ra, 3_ra, 0_ra}, + {-2_ra, 2_ra, 0_ra}, + // degen + {-1.1_ra, -2_ra, 0_ra}, + {-0.5_ra, -2_ra, 0_ra}, + {-0.3_ra, -2_ra, 0_ra}, + {0.0_ra, -2_ra, 0_ra}, + {0.001_ra, -2_ra, 0_ra}, + {0.3_ra, -2_ra, 0_ra}, + {0.5_ra, -2_ra, 0_ra}, + {1.1_ra, -2_ra, 0_ra}, + // degen2 + {-1_ra, -3_ra, 0_ra}, + {1_ra, -3_ra, 0_ra}, + + } ); + + Vector3Array normals; + normals.resize( polyMesh.vertices().size() ); + std::transform( + polyMesh.vertices().cbegin(), + polyMesh.vertices().cend(), + normals.begin(), + []( const Vector3& v ) { return ( v + Vector3( 0_ra, 0_ra, 1_ra ) ).normalized(); } ); + polyMesh.setNormals( normals ); + + auto quad = VectorNui( 4 ); + quad << 0, 1, 2, 3; + auto hepta = VectorNui( 7 ); + hepta << 3, 2, 4, 5, 6, 7, 8; + auto degen = VectorNui( 10 ); + degen << 1, 0, 9, 10, 11, 12, 13, 14, 15, 16; + auto degen2 = VectorNui( 10 ); + degen2 << 14, 13, 12, 11, 10, 9, 17, 18, 16, 15; + polyMesh.setIndices( {quad, hepta, degen, degen2} ); + + TopologicalMesh topologicalMesh; + topologicalMesh.initWithWedge( polyMesh ); + auto newMesh = topologicalMesh.toPolyMesh(); + REQUIRE( isSameMeshWedge( newMesh, polyMesh ) ); +} + +TEST_CASE( "Core/Geometry/TopologicalMesh/PolyMesh", + "[Core][Core/Geometry][TopologicalMesh][PolyMesh]" ) { + + test_poly(); +} + /// \todo TEST_CASE( "Core/Geometry/TopologicalMesh/Subdivider", /// "[Core][Core/Geometry][TopologicalMesh]" ) { // using Catmull = @@ -569,15 +585,12 @@ TEST_CASE( "Core/Geometry/TopologicalMesh/Manifold", "[Core][Core/Geometry][Topo SECTION( "Non manifold faces" ) { struct MyNonManifoldCommand { inline MyNonManifoldCommand( int target ) : targetNonManifoldFaces( target ) {} - inline void initialize( const TriangleMesh& /*triMesh*/ ) {} - inline void - process( const std::vector& /*face_vhandles*/ ) { + inline void initialize( const IndexedGeometry& ) {} + inline void process( const std::vector& ) { LOG( logINFO ) << "Non Manifold face found"; nonManifoldFaces++; } - inline void postProcess( TopologicalMesh& /*tm*/ ) { - // Todo : For each non manifold face, remove the vertices that are not part of a - // face of the topomesh For the test, this will reduce the mesh_2 to mesh1 + inline void postProcess( TopologicalMesh& ) { REQUIRE( nonManifoldFaces == targetNonManifoldFaces ); LOG( logINFO ) << "Process non-manifold faces"; } @@ -610,25 +623,12 @@ TEST_CASE( "Core/Geometry/TopologicalMesh/Manifold", "[Core][Core/Geometry][Topo MyNonManifoldCommand command ) { // test with functor TopologicalMesh topoWithCommand {candidateMesh, command}; - TopologicalMesh topoWedgeWithCommand; - topoWedgeWithCommand.initWithWedge( candidateMesh, command ); - auto convertedMeshWithCommand = topoWithCommand.toTriangleMesh(); - auto convertedMeshWithCommandFromWedge = topoWithCommand.toTriangleMeshFromWedges(); - auto convertedMeshWedgeWithCommand = topoWedgeWithCommand.toTriangleMeshFromWedges(); + auto convertedMeshWithCommand = topoWithCommand.toTriangleMesh(); REQUIRE( isSameMesh( referenceMesh, convertedMeshWithCommand ) ); - REQUIRE( isSameMesh( referenceMesh, convertedMeshWedgeWithCommand ) ); - REQUIRE( isSameMesh( referenceMesh, convertedMeshWithCommandFromWedge ) ); // test without functor TopologicalMesh topoWithoutCommand {candidateMesh}; - TopologicalMesh topoWedgeWithoutCommand {}; - topoWedgeWithoutCommand.initWithWedge( candidateMesh ); auto convertedMeshWithoutCommand = topoWithoutCommand.toTriangleMesh(); - auto convertedMeshWithoutCommandFromWedge = - topoWithoutCommand.toTriangleMeshFromWedges(); - auto convertedMeshWedgeWithoutCommand = topoWedgeWithoutCommand.toTriangleMesh(); REQUIRE( isSameMesh( referenceMesh, convertedMeshWithoutCommand ) ); - REQUIRE( isSameMesh( referenceMesh, convertedMeshWedgeWithoutCommand ) ); - REQUIRE( isSameMesh( referenceMesh, convertedMeshWithoutCommandFromWedge ) ); return convertedMeshWithoutCommand; }; @@ -727,17 +727,12 @@ TEST_CASE( "Core/Geometry/TopologicalMesh/Manifold", "[Core][Core/Geometry][Topo inline MyNonManifoldCommand( std::vector>& faulty ) : m_faulty( faulty ) {} - inline void initialize( const TriangleMesh& /*triMesh*/ ) {} + inline void initialize( const IndexedGeometry& ) {} inline void process( const std::vector& face_vhandles ) { - LOG( logINFO ) << "Non Manifold face found"; m_faulty.push_back( face_vhandles ); nonManifoldFaces++; } - inline void postProcess( TopologicalMesh& /*tm*/ ) { - // Todo : For each non manifold face, remove the vertices that are not part of a - // face of the topomesh For the test, this will reduce the mesh_2 to mesh1 - LOG( logINFO ) << "Process non-manifold faces"; - } + inline void postProcess( TopologicalMesh& ) {} std::vector>& m_faulty; int nonManifoldFaces {0}; }; @@ -789,46 +784,39 @@ TEST_CASE( "Core/Geometry/TopologicalMesh/Manifold", "[Core][Core/Geometry][Topo TEST_CASE( "Core/Geometry/TopologicalMesh/Initialization", "[Core][Core/Geometry][TopologicalMesh]" ) { - Ra::Core::Geometry::TopologicalMesh topologicalMesh; - Ra::Core::Geometry::TopologicalMesh::VertexHandle vhandle[3]; - Ra::Core::Geometry::TopologicalMesh::FaceHandle fhandle; - - vhandle[0] = - topologicalMesh.add_vertex( Ra::Core::Geometry::TopologicalMesh::Point( 1, -1, -1 ) ); - vhandle[1] = - topologicalMesh.add_vertex( Ra::Core::Geometry::TopologicalMesh::Point( 1, -1, 1 ) ); - vhandle[2] = - topologicalMesh.add_vertex( Ra::Core::Geometry::TopologicalMesh::Point( -1, -1, 1 ) ); - - std::vector face_vhandles; + TopologicalMesh topo; + TopologicalMesh::VertexHandle vhandle[3]; + TopologicalMesh::FaceHandle fhandle; + + vhandle[0] = topo.add_vertex( TopologicalMesh::Point( 1, -1, -1 ) ); + vhandle[1] = topo.add_vertex( TopologicalMesh::Point( 1, -1, 1 ) ); + vhandle[2] = topo.add_vertex( TopologicalMesh::Point( -1, -1, 1 ) ); + + std::vector face_vhandles; face_vhandles.push_back( vhandle[0] ); face_vhandles.push_back( vhandle[1] ); face_vhandles.push_back( vhandle[2] ); - fhandle = topologicalMesh.add_face( face_vhandles ); + fhandle = topo.add_face( face_vhandles ); // newly created face have invalid wedges on halfedges - auto heh = topologicalMesh.halfedge_handle( fhandle ); - REQUIRE( topologicalMesh.property( topologicalMesh.getWedgeIndexPph(), heh ).isInvalid() ); - heh = topologicalMesh.next_halfedge_handle( heh ); - REQUIRE( topologicalMesh.property( topologicalMesh.getWedgeIndexPph(), heh ).isInvalid() ); - heh = topologicalMesh.next_halfedge_handle( heh ); - REQUIRE( topologicalMesh.property( topologicalMesh.getWedgeIndexPph(), heh ).isInvalid() ); - - std::cout << "faces: " << topologicalMesh.n_faces() << std::endl; - REQUIRE( topologicalMesh.n_faces() == 1 ); - - topologicalMesh.request_face_status(); - topologicalMesh.delete_face( fhandle, false ); - topologicalMesh.garbage_collection(); - std::cout << "faces: " << topologicalMesh.n_faces() << std::endl; - REQUIRE( topologicalMesh.n_faces() == 0 ); + auto heh = topo.halfedge_handle( fhandle ); + REQUIRE( topo.property( topo.getWedgeIndexPph(), heh ).isInvalid() ); + heh = topo.next_halfedge_handle( heh ); + REQUIRE( topo.property( topo.getWedgeIndexPph(), heh ).isInvalid() ); + heh = topo.next_halfedge_handle( heh ); + REQUIRE( topo.property( topo.getWedgeIndexPph(), heh ).isInvalid() ); + REQUIRE( topo.n_faces() == 1 ); + + topo.request_face_status(); + topo.delete_face( fhandle, false ); + topo.garbage_collection(); + REQUIRE( topo.n_faces() == 0 ); } TEST_CASE( "Core/Geometry/TopologicalMesh/MergeWedges", "[Core][Core/Geometry][TopologicalMesh]" ) { auto mesh = Ra::Core::Geometry::makeSharpBox(); - auto topo = TopologicalMesh {}; - topo.initWithWedge( mesh ); + auto topo = TopologicalMesh {mesh}; std::set wedgesIndices; for ( auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) @@ -866,3 +854,324 @@ TEST_CASE( "Core/Geometry/TopologicalMesh/MergeWedges", "[Core][Core/Geometry][T REQUIRE( wedgesIndices.size() == 8 ); REQUIRE( topo.checkIntegrity() ); } + +template +void testAttrib( const IndexedGeometry& mesh, const std::string& name, float value ) { + + auto attribHandle = mesh.template getAttribHandle( name ); + REQUIRE( attribHandle.idx().isValid() ); + auto& attrib = mesh.getAttrib( attribHandle ); + for ( const auto& v : attrib.data() ) + { + REQUIRE( v == value ); + } +} + +TEST_CASE( "Core/Geometry/TopologicalMesh/Triangulate", "[Core][Core/Geometry][TopologicalMesh]" ) { + TopologicalMesh topo {}; + TopologicalMesh::VertexHandle vhandle[4]; + TopologicalMesh::FaceHandle fhandle; + + vhandle[0] = topo.add_vertex( TopologicalMesh::Point( -1, -1, 1 ) ); + vhandle[1] = topo.add_vertex( TopologicalMesh::Point( 1, -1, 1 ) ); + vhandle[2] = topo.add_vertex( TopologicalMesh::Point( 1, 1, 1 ) ); + vhandle[3] = topo.add_vertex( TopologicalMesh::Point( -1, 1, 1 ) ); + + std::vector face_vhandles; + face_vhandles.push_back( vhandle[0] ); + face_vhandles.push_back( vhandle[1] ); + face_vhandles.push_back( vhandle[2] ); + face_vhandles.push_back( vhandle[3] ); + fhandle = topo.add_face( face_vhandles ); + + REQUIRE( topo.n_faces() == 1 ); + + auto index1 = topo.addWedgeAttrib( "test1", 1.f ); + REQUIRE( index1 == 0 ); + REQUIRE( topo.getFloatAttribNames().size() == 1 ); + REQUIRE( topo.getFloatAttribNames()[0] == "test1" ); + + for ( const auto& he : topo.halfedges() ) + { + if ( topo.is_boundary( he ) ) continue; + + auto wd = topo.newWedgeData(); + + // need to set position and vertex handle for new wedges + wd.m_vertexHandle = topo.to_vertex_handle( he ); + wd.m_position = topo.point( wd.m_vertexHandle ); + + REQUIRE( wd.m_floatAttrib.size() == 1 ); + wd.m_floatAttrib[index1] = 2.f; + topo.replaceWedge( he, wd ); + } + REQUIRE( topo.checkIntegrity() ); + + auto index2 = topo.addWedgeAttrib( "test2", 2.f ); + REQUIRE( index2 == 1 ); + for ( const auto& he : topo.halfedges() ) + { + if ( topo.is_boundary( he ) ) continue; + // our we require the wedge to be already set for this he + auto wd = topo.newWedgeData( he ); + + REQUIRE( wd.m_vertexHandle == topo.to_vertex_handle( he ) ); + REQUIRE( wd.m_position == topo.point( wd.m_vertexHandle ) ); + + REQUIRE( wd.m_floatAttrib.size() == 2 ); + wd.m_floatAttrib[index2] = 3.f; + topo.replaceWedge( he, wd ); + } + + auto index3 = topo.addWedgeAttrib( "test3", 3.f ); + REQUIRE( index3 == 2 ); + for ( const auto& he : topo.halfedges() ) + { + if ( topo.is_boundary( he ) ) continue; + + auto wedgeIndex = topo.getWedgeIndex( he ); + REQUIRE( topo.getWedgeRefCount( wedgeIndex ) == 1 ); + auto wedgeData = topo.getWedgeData( wedgeIndex ); + + REQUIRE( wedgeData.m_floatAttrib[index1] == 0.f ); + REQUIRE( wedgeData.m_floatAttrib[index2] == 3.f ); + REQUIRE( wedgeData.m_floatAttrib[index3] == 3.f ); + } + + auto poly = topo.toPolyMesh(); + REQUIRE( poly.vertices().size() == 4 ); + REQUIRE( poly.getIndices().size() == 1 ); + REQUIRE( poly.getIndices()[0].size() == 4 ); + testAttrib( poly, "test1", 0.f ); + testAttrib( poly, "test2", 3.f ); + testAttrib( poly, "test3", 3.f ); + + topo.triangulate(); + topo.checkIntegrity(); + auto tri = topo.toTriangleMesh(); + REQUIRE( tri.vertices().size() == 4 ); + REQUIRE( tri.getIndices().size() == 2 ); + REQUIRE( tri.getIndices()[0].size() == 3 ); + REQUIRE( tri.getIndices()[1].size() == 3 ); + testAttrib( tri, "test1", 0.f ); + testAttrib( tri, "test2", 3.f ); + testAttrib( tri, "test3", 3.f ); +} + +optional +findHalfedge( TopologicalMesh& topo, const Vector3& from, const Vector3& to ) { + bool found; + TopologicalMesh::HalfedgeHandle he; + for ( auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); ++he_iter ) + { + + if ( topo.point( topo.to_vertex_handle( he_iter ) ) == from && + topo.point( topo.from_vertex_handle( he_iter ) ) == to ) + { + found = true; + he = *he_iter; + } + } + if ( found ) return he; + return {}; +} + +TEST_CASE( "Core/TopologicalMesh/CollapseWedge" ) { + using namespace Ra::Core; + using namespace Ra::Core::Utils; + using namespace Ra::Core::Geometry; + auto findHalfedge = []( TopologicalMesh& topo, + const Vector3& from, + const Vector3& to ) -> optional { + bool found; + TopologicalMesh::HalfedgeHandle he; + for ( auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); ++he_iter ) + { + + if ( topo.point( topo.to_vertex_handle( he_iter ) ) == to && + topo.point( topo.from_vertex_handle( he_iter ) ) == from ) + { + found = true; + he = *he_iter; + } + } + if ( found ) return he; + return {}; + }; + + Vector3Array points1 { + {00._ra, 00._ra, 00._ra}, + {10._ra, 00._ra, 00._ra}, + {05._ra, 05._ra, 00._ra}, + {05._ra, 10._ra, 00._ra}, + {15._ra, 05._ra, 00._ra}, + {10._ra, 08._ra, 00._ra}, + {10._ra, 12._ra, 00._ra}, + {15._ra, 10._ra, 00._ra}, + }; + Vector3Array points2 = {points1[0], points1[0], points1[1], points1[1], points1[1], points1[2], + points1[2], points1[2], points1[2], points1[3], points1[3], points1[3], + points1[4], points1[4], points1[5], points1[5], points1[5], points1[5], + points1[5], points1[5], points1[6], points1[6], points1[7], points1[7]}; + + Vector4Array colors1 = { + {0_ra, 0_ra, 0_ra, 1_ra}, {1_ra, 1_ra, 1_ra, 1_ra}, {2_ra, 2_ra, 2_ra, 1_ra}, + {3_ra, 3_ra, 3_ra, 1_ra}, {4_ra, 4_ra, 4_ra, 1_ra}, {5_ra, 5_ra, 5_ra, 1_ra}, + {6_ra, 6_ra, 6_ra, 1_ra}, {7_ra, 7_ra, 7_ra, 1_ra}, {8_ra, 8_ra, 8_ra, 1_ra}, + {9_ra, 9_ra, 9_ra, 1_ra}, {10_ra, 10_ra, 10_ra, 1_ra}, {11_ra, 11_ra, 11_ra, 1_ra}, + {12_ra, 12_ra, 12_ra, 1_ra}, {13_ra, 13_ra, 13_ra, 1_ra}, {14_ra, 14_ra, 14_ra, 1_ra}, + {15_ra, 15_ra, 15_ra, 1_ra}, {16_ra, 16_ra, 16_ra, 1_ra}, {17_ra, 17_ra, 17_ra, 1_ra}, + {18_ra, 18_ra, 18_ra, 1_ra}, {19_ra, 19_ra, 19_ra, 1_ra}, {20_ra, 20_ra, 20_ra, 1_ra}, + {21_ra, 21_ra, 21_ra, 1_ra}, {22_ra, 22_ra, 22_ra, 1_ra}, {23_ra, 23_ra, 23_ra, 1_ra}, + }; + + Vector3uArray indices1 { + {0, 2, 1}, {0, 3, 2}, {1, 2, 5}, {2, 3, 5}, {1, 5, 4}, {3, 6, 5}, {5, 6, 7}, {4, 5, 7}}; + + Vector3uArray indices3 = {{0, 2, 1}, {1, 2, 5}, {1, 5, 4}, {3, 6, 5}, {5, 6, 7}, {4, 5, 7}}; + + Vector3uArray indices4 = { + {0, 2, 5}, {3, 14, 6}, {4, 12, 15}, {11, 18, 20}, {17, 22, 21}, {16, 13, 23}}; + + Vector3uArray indices2 {{0, 5, 2}, + {1, 9, 8}, + {3, 6, 14}, + {7, 10, 19}, + {4, 15, 12}, + {11, 20, 18}, + {17, 21, 22}, + {16, 23, 13}}; + Vector4Array colors2 {24, Color::White()}; + for ( const auto& face : indices2 ) + { + colors2[face[0]] = colors1[face[0]]; + colors2[face[1]] = colors1[face[0]]; + colors2[face[2]] = colors1[face[0]]; + } + + Vector4Array colors3 {24, Color::White()}; + std::vector topFaceIndices {1, 3, 5, 6}; + std::vector bottomFaceIndices {0, 2, 4, 7}; + + for ( const auto& faceIndex : topFaceIndices ) + { + colors3[indices2[faceIndex][0]] = colors1[0]; + colors3[indices2[faceIndex][1]] = colors1[0]; + colors3[indices2[faceIndex][2]] = colors1[0]; + } + for ( const auto& faceIndex : bottomFaceIndices ) + { + colors3[indices2[faceIndex][0]] = colors1[1]; + colors3[indices2[faceIndex][1]] = colors1[1]; + colors3[indices2[faceIndex][2]] = colors1[1]; + } + + Vector4Array colors4 {24, Color::White()}; + + std::vector> splitContinuousWedges {// 0 + {0}, + {1}, + // 1 + {2, 3, 4}, + // 2 + {8, 7}, + {5, 6}, + // 3 + {9, 10, 11}, + // 4 + {12, 13}, + // 5 + {14, 15, 16}, + {17, 18, 19}, + // 6 + {20, 21}, + // 7 + {22, 23}}; + + for ( size_t i = 0; i < splitContinuousWedges.size(); ++i ) + { + for ( const auto& widx : splitContinuousWedges[i] ) + { + colors4[widx] = colors1[i]; + } + } + + ///\todo add more checks on resulting topology and wedges after collapse + auto addMergeScene = [findHalfedge]( const Vector3Array& points, + const Vector4Array& colors, + const Vector3uArray& indices, + Vector3 from, + Vector3 to ) { + TriangleMesh mesh1; + TopologicalMesh topo1; + optional optHe; + + mesh1.setVertices( points ); + mesh1.addAttrib( "color", Vector4Array {colors.begin(), colors.begin() + points.size()} ); + mesh1.setIndices( indices ); + + topo1 = TopologicalMesh {mesh1}; + topo1.mergeEqualWedges(); + topo1.garbage_collection(); + + topo1.checkIntegrity(); + optHe = findHalfedge( topo1, from, to ); + REQUIRE( optHe ); + + topo1.collapse( *optHe ); + REQUIRE( topo1.checkIntegrity() ); + + topo1 = TopologicalMesh {mesh1}; + optHe = findHalfedge( topo1, from, to ); + REQUIRE( optHe ); + + topo1.collapse( *optHe, true ); + REQUIRE( topo1.checkIntegrity() ); + + std::swap( from, to ); + + topo1 = TopologicalMesh {mesh1}; + topo1.mergeEqualWedges(); + topo1.garbage_collection(); + optHe = findHalfedge( topo1, from, to ); + REQUIRE( optHe ); + + topo1.collapse( *optHe ); + REQUIRE( topo1.checkIntegrity() ); + + topo1 = TopologicalMesh {mesh1}; + optHe = findHalfedge( topo1, from, to ); + REQUIRE( optHe ); + + topo1.collapse( *optHe, true ); + REQUIRE( topo1.checkIntegrity() ); + }; + + SECTION( "With continuous wedges." ) { + addMergeScene( points1, colors1, indices1, points1[5], points1[2] ); + } + + SECTION( "with top/bottom wedges" ) { + addMergeScene( points2, colors3, indices2, points1[5], points1[2] ); + } + + SECTION( "with continuous top/bottom wedges" ) { + addMergeScene( points2, colors4, indices2, points1[5], points1[2] ); + } + SECTION( "with flat face wedges" ) { + addMergeScene( points2, colors2, indices2, points1[5], points1[2] ); + } + + SECTION( "boundary With continuous wedges." ) { + addMergeScene( points1, colors1, indices3, points1[5], points1[2] ); + } + SECTION( "boundary with top/bottom wedges" ) { + addMergeScene( points2, colors3, indices4, points1[5], points1[2] ); + } + SECTION( "boundary with continuous top/bottom wedges" ) { + addMergeScene( points2, colors4, indices4, points1[5], points1[2] ); + } + SECTION( "boundary with flat face wedges" ) { + addMergeScene( points2, colors2, indices4, points1[5], points1[2] ); + } +}