diff --git a/src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp b/src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp index ca957e18..75d7d4f5 100644 --- a/src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp +++ b/src/geode/inspector/inspection/criterion/adjacency/brep_meshes_adjacency.cpp @@ -23,6 +23,9 @@ #include +#include +#include + #include #include @@ -30,9 +33,10 @@ #include #include +#include -#include #include +#include #include #include @@ -84,61 +88,135 @@ namespace geode InspectionIssuesMap< PolyhedronFacet >& components_wrong_adjacencies ) const { + if( model().nb_active_blocks() == 0 ) + { + return; + } + const auto surface_polygons = surfaces_polygons_unique_vertices(); + std::vector< async::task< + std::pair< uuid, InspectionIssues< PolyhedronFacet > > > > + tasks; for( const auto& block : model().active_blocks() ) { - const geode::SolidMeshAdjacency3D inspector{ block.mesh() }; - auto wrong_adjacencies = - inspector.polyhedron_facets_with_wrong_adjacency(); - wrong_adjacencies.set_description( absl::StrCat( "Block ", - block.name().value_or( block.id().string() ), " (", - block.id().string(), - ") polyhedron facets adjacencies issues" ) ); - const auto& mesh = block.mesh(); - for( const auto polyhedron_id : Range{ mesh.nb_polyhedra() } ) - { - for( const auto facet_id : - LRange{ mesh.nb_polyhedron_facets( polyhedron_id ) } ) + tasks.emplace_back( async::spawn( [this, &block, + &surface_polygons] { + return std::pair{ block.id(), + block_wrong_adjacencies( block, surface_polygons ) }; + } ) ); + } + for( auto& task : async::when_all( tasks ).get() ) + { + auto [block_id, issues] = task.get(); + components_wrong_adjacencies.add_issues_to_map( + block_id, std::move( issues ) ); + } + } + + private: + using SortedUniqueVertices = PolygonVertices; + + std::vector< index_t > mesh_unique_vertices( + const ComponentID& component_id, index_t nb_vertices ) const + { + std::vector< index_t > unique_vertices( nb_vertices ); + for( const auto v : Indices{ unique_vertices } ) + { + unique_vertices[v] = + model().unique_vertex( { component_id, v } ); + } + return unique_vertices; + } + + // Sorted unique vertices of every surface polygon + absl::flat_hash_set< SortedUniqueVertices > + surfaces_polygons_unique_vertices() const + { + std::vector< async::task< std::vector< SortedUniqueVertices > > > + tasks; + for( const auto& surface : model().surfaces() ) + { + tasks.emplace_back( async::spawn( [this, &surface] { + const auto& mesh = surface.mesh(); + const auto unique_vertices = mesh_unique_vertices( + surface.component_id(), mesh.nb_vertices() ); + std::vector< SortedUniqueVertices > surface_polygons; + surface_polygons.reserve( mesh.nb_polygons() ); + for( const auto polygon_id : Range{ mesh.nb_polygons() } ) { - const PolyhedronFacet polyhedron_facet{ polyhedron_id, - facet_id }; - if( !mesh.is_polyhedron_facet_on_border( - polyhedron_facet ) ) + auto vertices = mesh.polygon_vertices( polygon_id ); + for( auto& vertex : vertices ) { - continue; + vertex = unique_vertices[vertex]; } - const auto is_on_surface = - polyhedron_facet_is_on_a_surface( - block, polyhedron_facet ); - if( is_on_surface.has_value() - && !is_on_surface.value() ) + if( absl::c_contains( vertices, NO_ID ) ) { - wrong_adjacencies.add_issue( polyhedron_facet, - absl::StrCat( "facet ", facet_id, - " of polyhedron ", polyhedron_id, - " has no adjacencies but is not part of a " - "model Surface." ) ); + continue; } + absl::c_sort( vertices ); + surface_polygons.emplace_back( std::move( vertices ) ); } + return surface_polygons; + } ) ); + } + absl::flat_hash_set< SortedUniqueVertices > polygons; + for( auto& task : async::when_all( tasks ).get() ) + { + for( auto& polygon : task.get() ) + { + polygons.emplace( std::move( polygon ) ); } - components_wrong_adjacencies.add_issues_to_map( - block.id(), std::move( wrong_adjacencies ) ); } + return polygons; } - private: - absl::optional< bool > polyhedron_facet_is_on_a_surface( + InspectionIssues< PolyhedronFacet > block_wrong_adjacencies( const Block3D& block, - const PolyhedronFacet& polyhedron_facet ) const + const absl::flat_hash_set< SortedUniqueVertices >& + surface_polygons ) const { - const auto facet_unique_vertices = - polygon_unique_vertices( model(), block, polyhedron_facet ); - if( absl::c_contains( facet_unique_vertices, geode::NO_ID ) ) + const auto& mesh = block.mesh(); + const geode::SolidMeshAdjacency3D inspector{ mesh }; + auto wrong_adjacencies = + inspector.polyhedron_facets_with_wrong_adjacency(); + wrong_adjacencies.set_description( absl::StrCat( "Block ", + block.name().value_or( block.id().string() ), " (", + block.id().string(), + ") polyhedron facets adjacencies issues" ) ); + const auto unique_vertices = mesh_unique_vertices( + block.component_id(), mesh.nb_vertices() ); + for( const auto polyhedron_id : Range{ mesh.nb_polyhedra() } ) { - return std::nullopt; + for( const auto facet_id : + LRange{ mesh.nb_polyhedron_facets( polyhedron_id ) } ) + { + const PolyhedronFacet polyhedron_facet{ polyhedron_id, + facet_id }; + if( !mesh.is_polyhedron_facet_on_border( + polyhedron_facet ) ) + { + continue; + } + SortedUniqueVertices facet_unique_vertices; + for( const auto vertex : + mesh.polyhedron_facet_vertices( polyhedron_facet ) ) + { + facet_unique_vertices.push_back( + unique_vertices[vertex] ); + } + absl::c_sort( facet_unique_vertices ); + // Facets with unlinked vertices cannot be on a Surface + if( absl::c_contains( facet_unique_vertices, NO_ID ) + || !surface_polygons.contains( facet_unique_vertices ) ) + { + wrong_adjacencies.add_issue( polyhedron_facet, + absl::StrCat( "facet ", facet_id, " of polyhedron ", + polyhedron_id, + " has no adjacencies but is not part of a " + "model Surface." ) ); + } + } } - return !detail::surface_component_mesh_polygons( - model(), facet_unique_vertices ) - .empty(); + return wrong_adjacencies; } }; diff --git a/src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp b/src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp index 5762d376..3b0b43f3 100644 --- a/src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp +++ b/src/geode/inspector/inspection/criterion/adjacency/solid_adjacency.cpp @@ -23,6 +23,10 @@ #include +#include + +#include + #include #include @@ -65,46 +69,83 @@ namespace geode mesh_.name().value_or( mesh_.id().string() ), " polyhedron facets adjacencies issues" ) }; + const auto wrong_facets = facets_with_wrong_adjacency(); for( const auto polyhedron_id : Range{ mesh_.nb_polyhedra() } ) { - for( const auto facet_id : - LRange{ mesh_.nb_polyhedron_facets( polyhedron_id ) } ) + for( const auto facet_id : wrong_facets[polyhedron_id] ) { - const PolyhedronFacet polyhedron_facet{ polyhedron_id, - facet_id }; - if( !mesh_.is_polyhedron_facet_on_border( polyhedron_facet ) - && !mesh_polyhedron_facet_has_right_adjacency( - polyhedron_facet ) ) - { - wrong_adjacency_facets.add_issue( polyhedron_facet, - absl::StrCat( "facet ", facet_id, " of polyhedron ", - polyhedron_id, " has wrong adjacencies" ) ); - } + wrong_adjacency_facets.add_issue( + PolyhedronFacet{ polyhedron_id, facet_id }, + absl::StrCat( "facet ", facet_id, " of polyhedron ", + polyhedron_id, " has wrong adjacencies" ) ); } } return wrong_adjacency_facets; } private: + // Facets with wrong adjacency of each polyhedron + std::vector< absl::InlinedVector< local_index_t, 1 > > + facets_with_wrong_adjacency() const + { + std::vector< absl::InlinedVector< local_index_t, 1 > > wrong_facets( + mesh_.nb_polyhedra() ); + async::parallel_for( + async::irange( index_t{ 0 }, mesh_.nb_polyhedra() ), + [this, &wrong_facets]( index_t polyhedron_id ) { + for( const auto facet_id : + LRange{ mesh_.nb_polyhedron_facets( polyhedron_id ) } ) + { + const PolyhedronFacet polyhedron_facet{ polyhedron_id, + facet_id }; + if( !mesh_.is_polyhedron_facet_on_border( + polyhedron_facet ) + && !mesh_polyhedron_facet_has_right_adjacency( + polyhedron_facet ) ) + { + wrong_facets[polyhedron_id].push_back( facet_id ); + } + } + } ); + return wrong_facets; + } + bool mesh_polyhedron_facet_has_right_adjacency( const PolyhedronFacet& polyhedron_facet ) const { const auto adjacent_polyhedron = mesh_.polyhedron_adjacent( polyhedron_facet ); const auto polyhedron_adj_id = adjacent_polyhedron.value(); - const auto facet_vertices = - detail::OrientedVertexCycle< PolyhedronFacetVertices >( - mesh_.polyhedron_facet_vertices( polyhedron_facet ) ); + if( polyhedron_adj_id >= mesh_.nb_polyhedra() ) + { + return false; + } + const auto nb_facet_vertices = + mesh_.nb_polyhedron_facet_vertices( polyhedron_facet ); + std::optional< + detail::OrientedVertexCycle< PolyhedronFacetVertices > > + facet_vertices; for( const auto facet_id : LRange{ mesh_.nb_polyhedron_facets( polyhedron_adj_id ) } ) { const PolyhedronFacet adj_facet{ polyhedron_adj_id, facet_id }; - const auto adj_facet_vertices = - detail::OrientedVertexCycle< PolyhedronFacetVertices >( - mesh_.polyhedron_facet_vertices( adj_facet ) ); - if( facet_vertices.is_opposite( adj_facet_vertices ) - && mesh_.polyhedron_adjacent( adj_facet ) - == polyhedron_facet.polyhedron_id ) + // Cheap checks before comparing the vertex cycles + if( mesh_.polyhedron_adjacent( adj_facet ) + != polyhedron_facet.polyhedron_id + || mesh_.nb_polyhedron_facet_vertices( adj_facet ) + != nb_facet_vertices ) + { + continue; + } + if( !facet_vertices ) + { + facet_vertices.emplace( + mesh_.polyhedron_facet_vertices( polyhedron_facet ) ); + } + const detail::OrientedVertexCycle< PolyhedronFacetVertices > + adj_facet_vertices{ mesh_.polyhedron_facet_vertices( + adj_facet ) }; + if( facet_vertices->is_opposite( adj_facet_vertices ) ) { return true; } diff --git a/src/geode/inspector/inspection/criterion/adjacency/surface_adjacency.cpp b/src/geode/inspector/inspection/criterion/adjacency/surface_adjacency.cpp index 2be4f71f..ad0c8458 100644 --- a/src/geode/inspector/inspection/criterion/adjacency/surface_adjacency.cpp +++ b/src/geode/inspector/inspection/criterion/adjacency/surface_adjacency.cpp @@ -87,6 +87,10 @@ namespace geode const auto adjacent_polygon = mesh_.polygon_adjacent( polygon_edge ); const auto polygon_adj_id = adjacent_polygon.value(); + if( polygon_adj_id >= mesh_.nb_polygons() ) + { + return false; + } const auto v0 = mesh_.polygon_edge_vertex( polygon_edge, 0 ); const auto v1 = mesh_.polygon_edge_vertex( polygon_edge, 1 ); for( const auto edge_id : diff --git a/src/geode/inspector/inspection/criterion/internal/component_meshes_adjacency.cpp b/src/geode/inspector/inspection/criterion/internal/component_meshes_adjacency.cpp index a16f6342..f1062763 100644 --- a/src/geode/inspector/inspection/criterion/internal/component_meshes_adjacency.cpp +++ b/src/geode/inspector/inspection/criterion/internal/component_meshes_adjacency.cpp @@ -23,13 +23,16 @@ #include +#include + #include #include +#include #include -#include +#include #include #include #include @@ -38,28 +41,92 @@ namespace { + // True if both unique vertices are linked to the vertices of an edge of a + // same Line template < typename Model > - bool polygon_edge_is_on_a_line( const Model& model, - const geode::Surface< Model::dim >& surface, - const geode::PolygonEdge& polygon_edge ) + bool unique_vertices_are_on_a_line_edge( const Model& model, + geode::index_t unique_vertex0, + geode::index_t unique_vertex1 ) { - const auto edge_unique_vertices = - geode::edge_unique_vertices( model, surface, polygon_edge ); - geode::OpenGeodeInspectorInspectionException::check_exception( - edge_unique_vertices[0] != geode::NO_ID - && edge_unique_vertices[1] != geode::NO_ID, - surface.mesh().edge_barycenter( polygon_edge ), - geode::OpenGeodeException::TYPE::data, - "[ComponentMeshesAdjacency] Missing unique_vertices" ); - geode::OpenGeodeInspectorInspectionException::check_exception( - edge_unique_vertices[0] < model.nb_unique_vertices() - && edge_unique_vertices[1] < model.nb_unique_vertices(), - surface.mesh().edge_barycenter( polygon_edge ), - geode::OpenGeodeException::TYPE::data, - "[ComponentMeshesAdjacency] Wrong unique_vertices" ); - return !geode::detail::line_component_mesh_edges( - model, edge_unique_vertices ) - .empty(); + if( unique_vertex0 == unique_vertex1 ) + { + return false; + } + const auto& cmvs1 = model.component_mesh_vertices( unique_vertex1 ); + for( const auto& cmv0 : + model.component_mesh_vertices( unique_vertex0 ) ) + { + if( cmv0.component_id.type + != geode::Line< Model::dim >::component_type_static() ) + { + continue; + } + for( const auto& cmv1 : cmvs1 ) + { + if( cmv1.component_id.id != cmv0.component_id.id ) + { + continue; + } + const auto& mesh = model.line( cmv0.component_id.id ).mesh(); + for( const auto& edge_vertex : + mesh.edges_around_vertex( cmv0.vertex ) ) + { + if( mesh.edge_vertex( edge_vertex.opposite() ) + == cmv1.vertex ) + { + return true; + } + } + } + } + return false; + } + + template < typename Model > + geode::InspectionIssues< geode::PolygonEdge > surface_wrong_adjacencies( + const Model& model, const geode::Surface< Model::dim >& surface ) + { + const auto& mesh = surface.mesh(); + const geode::SurfaceMeshAdjacency< Model::dim > inspector{ mesh }; + auto issues = inspector.polygon_edges_with_wrong_adjacency(); + issues.set_description( absl::StrCat( "Surface ", + surface.name().value_or( surface.id().string() ), " (", + surface.id().string(), ") polygon edges adjacency issues" ) ); + std::vector< geode::index_t > unique_vertices( mesh.nb_vertices() ); + for( const auto v : geode::Indices{ unique_vertices } ) + { + unique_vertices[v] = + model.unique_vertex( { surface.component_id(), v } ); + } + for( const auto polygon_id : geode::Range{ mesh.nb_polygons() } ) + { + for( const auto edge_id : + geode::LRange{ mesh.nb_polygon_edges( polygon_id ) } ) + { + const geode::PolygonEdge polygon_edge{ polygon_id, edge_id }; + if( !mesh.is_edge_on_border( polygon_edge ) ) + { + continue; + } + const auto edge_vertices = + mesh.polygon_edge_vertices( polygon_edge ); + const auto unique_vertex0 = unique_vertices[edge_vertices[0]]; + const auto unique_vertex1 = unique_vertices[edge_vertices[1]]; + // Vertices without (valid) unique vertex cannot be on a Line + if( unique_vertex0 >= model.nb_unique_vertices() + || unique_vertex1 >= model.nb_unique_vertices() + || !unique_vertices_are_on_a_line_edge( + model, unique_vertex0, unique_vertex1 ) ) + { + issues.add_issue( polygon_edge, + absl::StrCat( "edge ", edge_id, " of polygon ", + polygon_id, + " has no adjacencies but is not part of a model " + "Line." ) ); + } + } + } + return issues; } } // namespace @@ -80,49 +147,21 @@ namespace geode InspectionIssuesMap< PolygonEdge >& components_wrong_adjacencies ) const { + std::vector< async::task< + std::pair< uuid, InspectionIssues< PolygonEdge > > > > + tasks; for( const auto& surface : model_.active_surfaces() ) { - const SurfaceMeshAdjacency< Model::dim > inspector{ - surface.mesh() - }; - auto issues = inspector.polygon_edges_with_wrong_adjacency(); - issues.set_description( absl::StrCat( "Surface ", - surface.name().value_or( surface.id().string() ), " (", - surface.id().string(), - ") polygon edges adjacency issues" ) ); - const auto& mesh = surface.mesh(); - for( const auto polygon_id : Range{ mesh.nb_polygons() } ) - { - for( const auto edge_id : - LRange{ mesh.nb_polygon_edges( polygon_id ) } ) - { - const PolygonEdge polygon_edge{ polygon_id, edge_id }; - try - { - if( mesh.is_edge_on_border( polygon_edge ) - && !polygon_edge_is_on_a_line( - model_, surface, polygon_edge ) ) - { - issues.add_issue( polygon_edge, - absl::StrCat( "edge ", edge_id, - " of polygon ", polygon_id, - " has no adjacencies but is not part " - "of a model Line." ) ); - } - } - catch( const OpenGeodeException& e ) - { - Logger::warning( e.what() ); - issues.add_issue( polygon_edge, - absl::StrCat( "edge ", edge_id, " of polygon ", - polygon_id, - " has no adjacencies but is not part of a " - "model Line." ) ); - } - } - } + tasks.emplace_back( async::spawn( [this, &surface] { + return std::pair{ surface.id(), + surface_wrong_adjacencies( model_, surface ) }; + } ) ); + } + for( auto& task : async::when_all( tasks ).get() ) + { + auto [surface_id, issues] = task.get(); components_wrong_adjacencies.add_issues_to_map( - surface.id(), std::move( issues ) ); + surface_id, std::move( issues ) ); } } diff --git a/src/geode/inspector/inspection/criterion/manifold/brep_meshes_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/brep_meshes_manifold.cpp index 8f35aa8e..7eb98e51 100644 --- a/src/geode/inspector/inspection/criterion/manifold/brep_meshes_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/brep_meshes_manifold.cpp @@ -23,6 +23,12 @@ #include +#include +#include +#include + +#include + #include #include #include @@ -132,7 +138,7 @@ namespace geode block.name().value_or( block.id().string() ), " (", block.id().string(), ") non manifold edges" ) ); components_non_manifold_edges.add_issues_to_map( - block.id(), non_manifold_edges ); + block.id(), std::move( non_manifold_edges ) ); } } @@ -148,7 +154,7 @@ namespace geode block.name().value_or( block.id().string() ), " (", block.id().string(), ") non manifold facets" ) ); components_non_manifold_facets.add_issues_to_map( - block.id(), non_manifold_facets ); + block.id(), std::move( non_manifold_facets ) ); } } @@ -156,14 +162,26 @@ namespace geode InspectionIssues< BRepNonManifoldEdge >& issues ) const { using Edge = detail::VertexCycle< std::array< index_t, 2 > >; - absl::linked_hash_map< Edge, std::vector< uuid > > edges; + struct EdgeSurfaces + { + index_t first_occurrence; + absl::InlinedVector< uuid, 2 > surfaces; + }; + absl::flat_hash_map< Edge, EdgeSurfaces > edges_map; + index_t occurrence{ 0 }; for( const auto& surface : model().active_surfaces() ) { const auto& mesh = surface.mesh(); + std::vector< index_t > unique_vertices( mesh.nb_vertices() ); + for( const auto v : Indices{ unique_vertices } ) + { + unique_vertices[v] = + model().unique_vertex( { surface.component_id(), v } ); + } for( const auto polygon_id : Range{ mesh.nb_polygons() } ) { - const auto vertices = mesh.polygon_vertices( polygon_id ); - for( const auto edge_id : LIndices{ vertices } ) + for( const auto edge_id : + LRange{ mesh.nb_polygon_edges( polygon_id ) } ) { const auto adj = mesh.polygon_adjacent( { polygon_id, edge_id } ); @@ -171,34 +189,43 @@ namespace geode { continue; } - const auto vertex0 = model().unique_vertex( - { surface.component_id(), vertices[edge_id] } ); - const auto vertex1 = - model().unique_vertex( { surface.component_id(), - vertices[edge_id == vertices.size() - 1 - ? 0 - : edge_id + 1] } ); - const auto info = edges.try_emplace( - Edge{ std::array{ vertex0, vertex1 } }, - std::vector{ surface.id() } ); - if( !info.second ) + const auto edge_vertices = mesh.polygon_edge_vertices( + { polygon_id, edge_id } ); + const auto vertex0 = unique_vertices[edge_vertices[0]]; + const auto vertex1 = unique_vertices[edge_vertices[1]]; + // Unlinked vertices are reported by the topology + // inspection, they cannot be compared here + if( vertex0 == NO_ID || vertex1 == NO_ID ) { - info.first->second.push_back( surface.id() ); + continue; } + auto [it, inserted] = edges_map.try_emplace( + Edge{ std::array{ vertex0, vertex1 } }, + EdgeSurfaces{ occurrence, {} } ); + it->second.surfaces.push_back( surface.id() ); + occurrence++; } } } - for( auto& edge : edges ) + std::vector< std::pair< Edge, EdgeSurfaces > > edges; + for( auto& edge : edges_map ) { - if( edge.second.size() <= 1 ) + if( edge.second.surfaces.size() > 1 ) { - continue; + edges.emplace_back( edge.first, std::move( edge.second ) ); } + } + absl::c_sort( edges, []( const auto& lhs, const auto& rhs ) { + return lhs.second.first_occurrence + < rhs.second.first_occurrence; + } ); + for( auto& edge : edges ) + { std::string message = absl::StrCat( "Model edge between unique vertices ", edge.first.vertices()[0], " and ", edge.first.vertices()[1], " is not manifold: it is on several Surfaces: " ); - for( const auto surface_uuid : edge.second ) + for( const auto& surface_uuid : edge.second.surfaces ) { absl::StrAppend( &message, model() @@ -208,8 +235,9 @@ namespace geode " (", surface_uuid.string(), "), " ); } absl::StrAppend( &message, "but not on their border" ); - issues.add_issue( - BRepNonManifoldEdge{ edge.first.vertices(), edge.second }, + issues.add_issue( BRepNonManifoldEdge{ edge.first.vertices(), + { edge.second.surfaces.begin(), + edge.second.surfaces.end() } }, message ); } for( const auto& line : model().active_lines() ) @@ -279,11 +307,12 @@ namespace geode [[nodiscard]] bool several_cmvs_on_one_vertex( absl::Span< const index_t > unique_vertices ) const { - std::vector< uuid > surfaces; for( const auto unique_vertex : unique_vertices ) { - if( model().component_mesh_vertices( unique_vertex ).size() - > 1 ) + // Unlinked vertices are reported by the topology inspection + if( unique_vertex == NO_ID + || model().component_mesh_vertices( unique_vertex ).size() + > 1 ) { return true; } @@ -304,14 +333,27 @@ namespace geode BRepComponentMeshesManifold::inspect_brep_manifold() const { BRepMeshesManifoldInspectionResult result; - impl_->add_component_meshes_non_manifold_vertices( - result.meshes_non_manifold_vertices ); - impl_->add_component_meshes_non_manifold_edges( - result.meshes_non_manifold_edges ); - impl_->add_component_meshes_non_manifold_facets( - result.meshes_non_manifold_facets ); - impl_->add_model_non_manifold_edges( result.brep_non_manifold_edges ); - impl_->add_model_non_manifold_facets( result.brep_non_manifold_facets ); + async::parallel_invoke( + [this, &result] { + impl_->add_component_meshes_non_manifold_vertices( + result.meshes_non_manifold_vertices ); + }, + [this, &result] { + impl_->add_component_meshes_non_manifold_edges( + result.meshes_non_manifold_edges ); + }, + [this, &result] { + impl_->add_component_meshes_non_manifold_facets( + result.meshes_non_manifold_facets ); + }, + [this, &result] { + impl_->add_model_non_manifold_edges( + result.brep_non_manifold_edges ); + }, + [this, &result] { + impl_->add_model_non_manifold_facets( + result.brep_non_manifold_facets ); + } ); return result; } } // namespace geode diff --git a/src/geode/inspector/inspection/criterion/manifold/section_meshes_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/section_meshes_manifold.cpp index b50bf980..a0d225df 100644 --- a/src/geode/inspector/inspection/criterion/manifold/section_meshes_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/section_meshes_manifold.cpp @@ -23,6 +23,8 @@ #include +#include + #include #include @@ -83,10 +85,15 @@ namespace geode SectionComponentMeshesManifold::inspect_section_manifold() const { SectionMeshesManifoldInspectionResult result; - impl_->add_surfaces_meshes_non_manifold_vertices( - result.meshes_non_manifold_vertices ); - impl_->add_surfaces_meshes_non_manifold_edges( - result.meshes_non_manifold_edges ); + async::parallel_invoke( + [this, &result] { + impl_->add_surfaces_meshes_non_manifold_vertices( + result.meshes_non_manifold_vertices ); + }, + [this, &result] { + impl_->add_surfaces_meshes_non_manifold_edges( + result.meshes_non_manifold_edges ); + } ); return result; } } // namespace geode diff --git a/src/geode/inspector/inspection/criterion/manifold/solid_edge_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/solid_edge_manifold.cpp index 23f1296d..4b8d3705 100644 --- a/src/geode/inspector/inspection/criterion/manifold/solid_edge_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/solid_edge_manifold.cpp @@ -23,8 +23,11 @@ #include +#include #include -#include +#include + +#include #include #include @@ -55,28 +58,72 @@ namespace return true; } + struct EdgePolyhedra + { + // Vertices of the edge as first found in a polyhedron + std::array< geode::index_t, 2 > vertices; + absl::InlinedVector< geode::index_t, 8 > polyhedra; + }; + + // Unique edges in order of first appearance, with their polyhedra template < geode::index_t dimension > - absl::flat_hash_map< Edge, std::vector< geode::index_t > > - edges_to_polyhedra_around( const geode::SolidMesh< dimension >& mesh ) + std::vector< EdgePolyhedra > edges_to_polyhedra_around( + const geode::SolidMesh< dimension >& mesh ) { - absl::flat_hash_map< Edge, std::vector< geode::index_t > > - polyhedra_around_edges; + std::vector< EdgePolyhedra > edges; + absl::flat_hash_map< Edge, geode::index_t > edge_ids; + edge_ids.reserve( 2 * mesh.nb_polyhedra() ); for( const auto polyhedron_id : geode::Range{ mesh.nb_polyhedra() } ) { - for( const auto polyhedron_edge_vertices : + for( const auto& polyhedron_edge_vertices : mesh.polyhedron_edges_vertices( polyhedron_id ) ) { - const Edge polyhedron_edge{ polyhedron_edge_vertices }; - if( !polyhedra_around_edges - .try_emplace( polyhedron_edge, 1, polyhedron_id ) - .second ) + const auto [it, inserted] = edge_ids.try_emplace( + Edge{ polyhedron_edge_vertices }, edges.size() ); + if( inserted ) { - polyhedra_around_edges[polyhedron_edge].push_back( - polyhedron_id ); + edges.push_back( { polyhedron_edge_vertices, {} } ); } + edges[it->second].polyhedra.push_back( polyhedron_id ); } } - return polyhedra_around_edges; + return edges; + } + + enum struct EDGE_STATUS : char + { + manifold, + non_manifold, + broken_adjacencies + }; + + template < geode::index_t dimension > + std::vector< EDGE_STATUS > edges_status( + const geode::SolidMesh< dimension >& mesh, + absl::Span< const EdgePolyhedra > edges ) + { + std::vector< EDGE_STATUS > status( edges.size() ); + async::parallel_for( + async::irange( geode::index_t{ 0 }, + static_cast< geode::index_t >( edges.size() ) ), + [&mesh, &edges, &status]( geode::index_t edge_id ) { + const auto& edge = edges[edge_id]; + try + { + status[edge_id] = + polyhedra_around_edge_are_the_same( + mesh.polyhedra_around_edge( + edge.vertices, edge.polyhedra.front() ), + edge.polyhedra ) + ? EDGE_STATUS::manifold + : EDGE_STATUS::non_manifold; + } + catch( const geode::OpenGeodeException& ) + { + status[edge_id] = EDGE_STATUS::broken_adjacencies; + } + } ); + return status; } } // namespace @@ -86,35 +133,15 @@ namespace geode class SolidMeshEdgeManifold< dimension >::Impl { public: - Impl( const SolidMesh< dimension >& mesh ) - : mesh_( mesh ), - polyhedra_around_edges_( edges_to_polyhedra_around( mesh ) ) - { - } + Impl( const SolidMesh< dimension >& mesh ) : mesh_( mesh ) {} bool mesh_edges_are_manifold() const { - absl::flat_hash_set< Edge > checked_edges; - for( const auto polyhedron_id : Range{ mesh_.nb_polyhedra() } ) - { - for( const auto polyhedron_edge_vertices : - mesh_.polyhedron_edges_vertices( polyhedron_id ) ) - { - Edge polyhedron_edge{ polyhedron_edge_vertices }; - if( !checked_edges.insert( polyhedron_edge ).second ) - { - continue; - } - const auto polyhedra_around = mesh_.polyhedra_around_edge( - polyhedron_edge_vertices, polyhedron_id ); - if( !polyhedra_around_edge_are_the_same( polyhedra_around, - polyhedra_around_edges_.at( polyhedron_edge ) ) ) - { - return false; - } - } - } - return true; + const auto edges = edges_to_polyhedra_around( mesh_ ); + return absl::c_all_of( + edges_status( mesh_, edges ), []( EDGE_STATUS status ) { + return status == EDGE_STATUS::manifold; + } ); } InspectionIssues< std::array< index_t, 2 > > non_manifold_edges() const @@ -122,42 +149,24 @@ namespace geode InspectionIssues< std::array< index_t, 2 > > non_manifold_edges{ "Non manifold edges" }; - absl::flat_hash_set< Edge > checked_edges; - for( const auto polyhedron_id : Range{ mesh_.nb_polyhedra() } ) + const auto edges = edges_to_polyhedra_around( mesh_ ); + const auto status = edges_status( mesh_, edges ); + for( const auto edge_id : Indices{ edges } ) { - for( const auto polyhedron_edge_vertices : - mesh_.polyhedron_edges_vertices( polyhedron_id ) ) + const auto& vertices = edges[edge_id].vertices; + if( status[edge_id] == EDGE_STATUS::non_manifold ) + { + non_manifold_edges.add_issue( vertices, + absl::StrCat( "Non manifold edge between vertices ", + vertices[0], " and ", vertices[1] ) ); + } + else if( status[edge_id] == EDGE_STATUS::broken_adjacencies ) { - Edge polyhedron_edge{ polyhedron_edge_vertices }; - if( !checked_edges.insert( polyhedron_edge ).second ) - { - continue; - } - try - { - if( !polyhedra_around_edge_are_the_same( - mesh_.polyhedra_around_edge( - polyhedron_edge_vertices, polyhedron_id ), - polyhedra_around_edges_.at( - polyhedron_edge ) ) ) - { - non_manifold_edges.add_issue( - polyhedron_edge_vertices, - absl::StrCat( - "Non manifold edge between vertices ", - polyhedron_edge_vertices[0], " and ", - polyhedron_edge_vertices[1] ) ); - } - } - catch( const OpenGeodeException& ) - { - non_manifold_edges.add_issue( polyhedron_edge_vertices, - absl::StrCat( "Could not check manifold on edge " - "between vertices with index ", - polyhedron_edge_vertices[0], " and index ", - polyhedron_edge_vertices[1], - "; Check issues with solid adjacencies." ) ); - } + non_manifold_edges.add_issue( vertices, + absl::StrCat( "Could not check manifold on edge " + "between vertices with index ", + vertices[0], " and index ", vertices[1], + "; Check issues with solid adjacencies." ) ); } } return non_manifold_edges; @@ -165,8 +174,6 @@ namespace geode private: const SolidMesh< dimension >& mesh_; - absl::flat_hash_map< Edge, std::vector< geode::index_t > > - polyhedra_around_edges_; }; template < index_t dimension > diff --git a/src/geode/inspector/inspection/criterion/manifold/solid_facet_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/solid_facet_manifold.cpp index 23e16818..a01e4a03 100644 --- a/src/geode/inspector/inspection/criterion/manifold/solid_facet_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/solid_facet_manifold.cpp @@ -23,8 +23,8 @@ #include -#include -#include +#include +#include #include #include @@ -36,28 +36,52 @@ namespace { using Facet = geode::detail::VertexCycle< geode::PolyhedronFacetVertices >; + struct FacetCount + { + geode::index_t first_occurrence; + geode::index_t nb_polyhedra; + }; + + // Facets shared by more than two polyhedra, in order of first appearance template < geode::index_t dimension > - [[nodiscard]] absl::linked_hash_map< Facet, geode::index_t > - facets_to_nb_adjacent_polyhedra( - const geode::SolidMesh< dimension >& mesh ) + [[nodiscard]] std::vector< Facet > non_manifold_facets_list( + const geode::SolidMesh< dimension >& mesh ) { - absl::linked_hash_map< Facet, geode::index_t > - nb_polyhedra_adjacent_to_facets; + absl::flat_hash_map< Facet, FacetCount > facet_counts; + facet_counts.reserve( 3 * mesh.nb_polyhedra() ); + geode::index_t occurrence{ 0 }; for( const auto polyhedron_id : geode::Range{ mesh.nb_polyhedra() } ) { for( const auto facet_id : geode::LRange{ mesh.nb_polyhedron_facets( polyhedron_id ) } ) { - const Facet facet{ mesh.polyhedron_facet_vertices( - { polyhedron_id, facet_id } ) }; - if( !nb_polyhedra_adjacent_to_facets.try_emplace( facet, 1 ) - .second ) + auto [it, inserted] = facet_counts.try_emplace( + Facet{ mesh.polyhedron_facet_vertices( + { polyhedron_id, facet_id } ) }, + FacetCount{ occurrence, 1 } ); + if( !inserted ) { - nb_polyhedra_adjacent_to_facets[facet] += 1; + it->second.nb_polyhedra++; } + occurrence++; + } + } + std::vector< std::pair< geode::index_t, const Facet* > > non_manifold; + for( const auto& [facet, count] : facet_counts ) + { + if( count.nb_polyhedra > 2 ) + { + non_manifold.emplace_back( count.first_occurrence, &facet ); } } - return nb_polyhedra_adjacent_to_facets; + absl::c_sort( non_manifold ); + std::vector< Facet > result; + result.reserve( non_manifold.size() ); + for( const auto& facet : non_manifold ) + { + result.push_back( *facet.second ); + } + return result; } } // namespace @@ -71,42 +95,24 @@ namespace geode [[nodiscard]] bool mesh_facets_are_manifold() const { - const auto nb_polyhedra_adjacent_to_facets = - facets_to_nb_adjacent_polyhedra( mesh_ ); - for( const auto& nb_adjacent_polyhedra : - nb_polyhedra_adjacent_to_facets ) - { - if( nb_adjacent_polyhedra.second > 2 ) - { - return false; - } - } - return true; + return non_manifold_facets_list( mesh_ ).empty(); } [[nodiscard]] InspectionIssues< PolyhedronFacetVertices > non_manifold_facets() const { - const auto nb_polyhedra_adjacent_to_facets = - facets_to_nb_adjacent_polyhedra( mesh_ ); InspectionIssues< PolyhedronFacetVertices > non_manifold_facets{ "non manifold facets" }; - for( const auto& nb_adjacent_polyhedra : - nb_polyhedra_adjacent_to_facets ) + for( const auto& facet : non_manifold_facets_list( mesh_ ) ) { - if( nb_adjacent_polyhedra.second > 2 ) + std::string message{ "facet made of vertices with index " }; + for( const auto vertex_id : facet.vertices() ) { - std::string message{ "facet made of vertices with index " }; - for( const auto vertex_id : - nb_adjacent_polyhedra.first.vertices() ) - { - absl::StrAppend( &message, vertex_id, ", " ); - } - absl::StrAppend( &message, "is not manifold." ); - non_manifold_facets.add_issue( - nb_adjacent_polyhedra.first.vertices(), message ); + absl::StrAppend( &message, vertex_id, ", " ); } + absl::StrAppend( &message, "is not manifold." ); + non_manifold_facets.add_issue( facet.vertices(), message ); } return non_manifold_facets; } diff --git a/src/geode/inspector/inspection/criterion/manifold/solid_vertex_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/solid_vertex_manifold.cpp index 7d767281..7c78a404 100644 --- a/src/geode/inspector/inspection/criterion/manifold/solid_vertex_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/solid_vertex_manifold.cpp @@ -24,6 +24,9 @@ #include #include +#include + +#include #include @@ -34,8 +37,8 @@ namespace { bool polyhedra_around_vertex_are_the_same( - const geode::PolyhedraAroundVertex& pav1, - const geode::PolyhedraAroundVertex& pav2 ) + absl::Span< const geode::PolyhedronVertex > pav1, + absl::Span< const geode::PolyhedronVertex > pav2 ) { if( pav1.size() != pav2.size() ) { @@ -70,6 +73,81 @@ namespace } return polyhedra_around_vertices_list; } + + // Propagates around the vertex from its first polyhedron through the + // adjacencies of the facets containing it, like + // SolidMesh::polyhedra_around_vertex but without its cache: the vertex is + // manifold if every polyhedron containing it is reached. + template < geode::index_t dimension > + bool vertex_is_manifold( const geode::SolidMesh< dimension >& mesh, + geode::index_t vertex_id, + absl::Span< const geode::PolyhedronVertex > polyhedron_vertices ) + { + if( polyhedron_vertices.empty() ) + { + return true; + } + absl::InlinedVector< geode::PolyhedronVertex, 32 > reached; + absl::InlinedVector< geode::index_t, 32 > visited; + absl::InlinedVector< geode::PolyhedronVertex, 32 > to_visit; + to_visit.push_back( polyhedron_vertices.front() ); + visited.push_back( polyhedron_vertices.front().polyhedron_id ); + while( !to_visit.empty() ) + { + const auto polyhedron_vertex = to_visit.back(); + to_visit.pop_back(); + reached.push_back( polyhedron_vertex ); + if( reached.size() > polyhedron_vertices.size() ) + { + return false; + } + for( const auto& polyhedron_facet : + mesh.polyhedron_vertex_facets( polyhedron_vertex ) ) + { + const auto adjacent = + mesh.polyhedron_adjacent( polyhedron_facet ); + if( !adjacent ) + { + continue; + } + const auto adjacent_id = adjacent.value(); + if( adjacent_id >= mesh.nb_polyhedra() ) + { + return false; + } + if( absl::c_contains( visited, adjacent_id ) ) + { + continue; + } + visited.push_back( adjacent_id ); + if( const auto vertex_in_adjacent = + mesh.vertex_in_polyhedron( adjacent_id, vertex_id ) ) + { + to_visit.emplace_back( + adjacent_id, vertex_in_adjacent.value() ); + } + } + } + return polyhedra_around_vertex_are_the_same( + polyhedron_vertices, reached ); + } + + template < geode::index_t dimension > + std::vector< char > non_manifold_vertices_flags( + const geode::SolidMesh< dimension >& mesh ) + { + const auto polyhedra_around_vertices_list = + polyhedra_around_vertices( mesh ); + std::vector< char > non_manifold( mesh.nb_vertices(), false ); + async::parallel_for( + async::irange( geode::index_t{ 0 }, mesh.nb_vertices() ), + [&mesh, &polyhedra_around_vertices_list, &non_manifold]( + geode::index_t vertex_id ) { + non_manifold[vertex_id] = !vertex_is_manifold( mesh, vertex_id, + polyhedra_around_vertices_list[vertex_id] ); + } ); + return non_manifold; + } } // namespace namespace geode @@ -82,32 +160,18 @@ namespace geode bool mesh_vertices_are_manifold() const { - const auto polyhedra_around_vertices_list = - polyhedra_around_vertices( mesh_ ); - for( const auto vertex_id : geode::Range{ mesh_.nb_vertices() } ) - { - if( !polyhedra_around_vertex_are_the_same( - polyhedra_around_vertices_list[vertex_id], - mesh_.polyhedra_around_vertex( vertex_id ) ) ) - { - return false; - } - } - return true; + return !absl::c_contains( non_manifold_vertices_flags( mesh_ ), 1 ); } InspectionIssues< index_t > non_manifold_vertices() const { - const auto polyhedra_around_vertices_list = - polyhedra_around_vertices( mesh_ ); + const auto non_manifold = non_manifold_vertices_flags( mesh_ ); InspectionIssues< geode::index_t > non_manifold_vertices{ "non manifold vertices" }; for( const auto vertex_id : geode::Range{ mesh_.nb_vertices() } ) { - if( !polyhedra_around_vertex_are_the_same( - polyhedra_around_vertices_list[vertex_id], - mesh_.polyhedra_around_vertex( vertex_id ) ) ) + if( non_manifold[vertex_id] ) { non_manifold_vertices.add_issue( vertex_id, absl::StrCat( "vertex ", vertex_id, ", at position [", diff --git a/src/geode/inspector/inspection/criterion/manifold/surface_edge_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/surface_edge_manifold.cpp index bc798394..5467ba18 100644 --- a/src/geode/inspector/inspection/criterion/manifold/surface_edge_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/surface_edge_manifold.cpp @@ -37,10 +37,10 @@ namespace template < geode::index_t dimension > [[nodiscard]] absl::linked_hash_map< Edge, - std::pair< geode::local_index_t, bool > > + std::pair< geode::index_t, bool > > edge_to_polygons_around( const geode::SurfaceMesh< dimension >& mesh ) { - absl::linked_hash_map< Edge, std::pair< geode::local_index_t, bool > > + absl::linked_hash_map< Edge, std::pair< geode::index_t, bool > > polygons_around_edges; for( const auto polygon_id : geode::Range{ mesh.nb_polygons() } ) { @@ -53,8 +53,7 @@ namespace }; if( !polygons_around_edges .try_emplace( polygon_edge_vertex_cycle, - std::pair< geode::local_index_t, bool >{ - 1, false } ) + std::pair< geode::index_t, bool >{ 1, false } ) .second ) { polygons_around_edges[polygon_edge_vertex_cycle].first += 1; diff --git a/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp b/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp index 8d179d77..bc332405 100644 --- a/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp +++ b/src/geode/inspector/inspection/criterion/manifold/surface_vertex_manifold.cpp @@ -24,6 +24,9 @@ #include #include +#include + +#include #include @@ -34,8 +37,8 @@ namespace { bool polygons_around_vertex_are_the_same( - const geode::PolygonsAroundVertex& pav1, - const geode::PolygonsAroundVertex& pav2 ) + absl::Span< const geode::PolygonVertex > pav1, + absl::Span< const geode::PolygonVertex > pav2 ) { if( pav1.size() != pav2.size() ) { @@ -69,6 +72,143 @@ namespace } return polygons_list; } + + enum struct VERTEX_STATUS : char + { + manifold, + non_manifold, + broken_adjacencies + }; + + // Same walk as SurfaceMesh::polygons_around_vertex, without its cache. + // Returns std::nullopt on broken adjacencies. + template < geode::index_t dimension > + std::optional< std::optional< geode::PolygonVertex > > + next_polygon_vertex_around( const geode::SurfaceMesh< dimension >& mesh, + const geode::PolygonVertex& polygon_vertex, + bool& vertex_is_next ) + { + const auto exit_vertex = + vertex_is_next ? polygon_vertex + : mesh.previous_polygon_vertex( polygon_vertex ); + const geode::PolygonEdge exit_edge{ exit_vertex }; + const auto adjacent = mesh.polygon_adjacent( exit_edge ); + if( !adjacent ) + { + return std::optional< geode::PolygonVertex >{}; + } + if( adjacent.value() >= mesh.nb_polygons() ) + { + return std::nullopt; + } + const auto adjacent_edge = mesh.polygon_adjacent_edge( exit_edge ); + if( !adjacent_edge ) + { + return std::nullopt; + } + if( mesh.polygon_edge_vertex( exit_edge, 0 ) + == mesh.polygon_edge_vertex( adjacent_edge.value(), 0 ) + && mesh.polygon_edge_vertex( exit_edge, 1 ) + == mesh.polygon_edge_vertex( adjacent_edge.value(), 1 ) ) + { + vertex_is_next = !vertex_is_next; + } + return std::optional< geode::PolygonVertex >{ + vertex_is_next ? mesh.next_polygon_vertex( + geode::PolygonVertex{ adjacent_edge.value() } ) + : geode::PolygonVertex{ adjacent_edge.value() } + }; + } + + template < geode::index_t dimension > + VERTEX_STATUS vertex_status( const geode::SurfaceMesh< dimension >& mesh, + absl::Span< const geode::PolygonVertex > polygon_vertices ) + { + if( polygon_vertices.empty() ) + { + return VERTEX_STATUS::manifold; + } + const auto& first = polygon_vertices.front(); + absl::InlinedVector< geode::PolygonVertex, 16 > reached{ first }; + try + { + bool vertex_is_next{ false }; + auto current = first; + bool closed{ false }; + while( true ) + { + const auto next = + next_polygon_vertex_around( mesh, current, vertex_is_next ); + if( !next ) + { + return VERTEX_STATUS::broken_adjacencies; + } + if( !next.value() ) + { + break; + } + if( next.value() == first ) + { + closed = true; + break; + } + reached.push_back( next.value().value() ); + if( reached.size() > polygon_vertices.size() ) + { + return VERTEX_STATUS::non_manifold; + } + current = next.value().value(); + } + if( !closed ) + { + vertex_is_next = true; + current = first; + while( true ) + { + const auto next = next_polygon_vertex_around( + mesh, current, vertex_is_next ); + if( !next ) + { + return VERTEX_STATUS::broken_adjacencies; + } + if( !next.value() ) + { + break; + } + reached.push_back( next.value().value() ); + if( reached.size() > polygon_vertices.size() ) + { + return VERTEX_STATUS::non_manifold; + } + current = next.value().value(); + } + } + } + catch( const geode::OpenGeodeException& ) + { + return VERTEX_STATUS::broken_adjacencies; + } + return polygons_around_vertex_are_the_same( polygon_vertices, reached ) + ? VERTEX_STATUS::manifold + : VERTEX_STATUS::non_manifold; + } + + template < geode::index_t dimension > + std::vector< VERTEX_STATUS > vertices_status( + const geode::SurfaceMesh< dimension >& mesh ) + { + const auto polygons_around_vertices_list = + polygons_around_vertices( mesh ); + std::vector< VERTEX_STATUS > status( mesh.nb_vertices() ); + async::parallel_for( + async::irange( geode::index_t{ 0 }, mesh.nb_vertices() ), + [&mesh, &polygons_around_vertices_list, &status]( + geode::index_t vertex_id ) { + status[vertex_id] = vertex_status( + mesh, polygons_around_vertices_list[vertex_id] ); + } ); + return status; + } } // namespace namespace geode @@ -81,51 +221,34 @@ namespace geode bool mesh_vertices_are_manifold() const { - const auto polygons_around_vertices_list = - polygons_around_vertices( mesh_ ); - for( const auto vertex_id : geode::Range{ mesh_.nb_vertices() } ) - { - try - { - if( !polygons_around_vertex_are_the_same( - polygons_around_vertices_list[vertex_id], - mesh_.polygons_around_vertex( vertex_id ) ) ) - { - return false; - } - } - catch( const geode::OpenGeodeException& ) - { - continue; - } - } - return true; + return absl::c_all_of( + vertices_status( mesh_ ), []( VERTEX_STATUS status ) { + return status == VERTEX_STATUS::manifold; + } ); } InspectionIssues< index_t > non_manifold_vertices() const { - const auto polygons_around_vertices_list = - polygons_around_vertices( mesh_ ); + const auto status = vertices_status( mesh_ ); InspectionIssues< geode::index_t > non_manifold_vertices{ "non manifold vertices" }; for( const auto vertex_id : geode::Range{ mesh_.nb_vertices() } ) { - try + if( status[vertex_id] == VERTEX_STATUS::non_manifold ) { - if( !polygons_around_vertex_are_the_same( - polygons_around_vertices_list[vertex_id], - mesh_.polygons_around_vertex( vertex_id ) ) ) - { - non_manifold_vertices.add_issue( vertex_id, - absl::StrCat( "vertex ", vertex_id, - ", at position [", - mesh_.point( vertex_id ).string(), "]" ) ); - } + non_manifold_vertices.add_issue( vertex_id, + absl::StrCat( "vertex ", vertex_id, ", at position [", + mesh_.point( vertex_id ).string(), "]" ) ); } - catch( const geode::OpenGeodeException& ) + else if( status[vertex_id] + == VERTEX_STATUS::broken_adjacencies ) { - continue; + non_manifold_vertices.add_issue( vertex_id, + absl::StrCat( "vertex ", vertex_id, ", at position [", + mesh_.point( vertex_id ).string(), + "] could not be checked; check issues with " + "surface adjacencies" ) ); } } return non_manifold_vertices; diff --git a/src/geode/inspector/inspection/criterion/negative_elements/surface_negative_elements.cpp b/src/geode/inspector/inspection/criterion/negative_elements/surface_negative_elements.cpp index 9ca2c615..e8921ce2 100644 --- a/src/geode/inspector/inspection/criterion/negative_elements/surface_negative_elements.cpp +++ b/src/geode/inspector/inspection/criterion/negative_elements/surface_negative_elements.cpp @@ -48,8 +48,7 @@ namespace geode { for( const auto polygon_id : Range{ mesh_.nb_polygons() } ) { - if( polygon_area_sign( mesh_.polygon( polygon_id ) ) - == SIGN::negative ) + if( polygon_is_negative( polygon_id ) ) { return true; } @@ -67,8 +66,7 @@ namespace geode { for( const auto polygon_id : Range{ mesh_.nb_polygons() } ) { - if( polygon_area_sign( mesh_.polygon( polygon_id ) ) - == SIGN::negative ) + if( polygon_is_negative( polygon_id ) ) { wrong_polygons.add_issue( polygon_id, absl::StrCat( "negative polygon ", polygon_id ) ); @@ -78,6 +76,26 @@ namespace geode return wrong_polygons; } + private: + bool polygon_is_negative( index_t polygon_id ) const + { + if constexpr( dimension == 2 ) + { + if( mesh_.nb_polygon_vertices( polygon_id ) == 3 ) + { + const auto vertices = mesh_.polygon_vertices( polygon_id ); + return triangle_area_sign( + Triangle2D{ mesh_.point( vertices[0] ), + mesh_.point( vertices[1] ), + mesh_.point( vertices[2] ) } ) + == SIGN::negative; + } + return polygon_area_sign( mesh_.polygon( polygon_id ) ) + == SIGN::negative; + } + return false; + } + private: const SurfaceMesh< dimension >& mesh_; }; diff --git a/tests/inspection/CMakeLists.txt b/tests/inspection/CMakeLists.txt index 05091f0b..6df876a7 100755 --- a/tests/inspection/CMakeLists.txt +++ b/tests/inspection/CMakeLists.txt @@ -171,6 +171,16 @@ add_geode_test( ${PROJECT_NAME}::inspection ) +add_geode_test( + SOURCE "test-brep-meshes-criteria.cpp" + DEPENDENCIES + OpenGeode::basic + OpenGeode::geometry + OpenGeode::mesh + OpenGeode::model + ${PROJECT_NAME}::inspection +) + add_geode_test( SOURCE "test-solid-negative-elements.cpp" DEPENDENCIES diff --git a/tests/inspection/test-brep-meshes-criteria.cpp b/tests/inspection/test-brep-meshes-criteria.cpp new file mode 100644 index 00000000..1e3a4bc6 --- /dev/null +++ b/tests/inspection/test-brep-meshes-criteria.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + // Two triangles sharing an edge, vertices not linked to unique vertices + const geode::Surface3D& add_square_surface( + const geode::BRep& brep, geode::BRepBuilder& builder, double z ) + { + const auto& surface = brep.surface( builder.add_surface() ); + auto mesh_builder = builder.surface_mesh_builder( surface ); + mesh_builder->create_point( geode::Point3D{ { 0, 0, z } } ); + mesh_builder->create_point( geode::Point3D{ { 1, 0, z } } ); + mesh_builder->create_point( geode::Point3D{ { 0, 1, z } } ); + mesh_builder->create_point( geode::Point3D{ { 1, 1, z } } ); + mesh_builder->create_polygon( { 0, 1, 2 } ); + mesh_builder->create_polygon( { 1, 3, 2 } ); + mesh_builder->compute_polygon_adjacencies(); + return surface; + } +} // namespace + +void check_model_edges_without_unique_vertices() +{ + geode::BRep brep; + geode::BRepBuilder builder{ brep }; + add_square_surface( brep, builder, 0 ); + add_square_surface( brep, builder, 1 ); + const geode::BRepComponentMeshesManifold inspector{ brep }; + const auto result = inspector.inspect_brep_manifold(); + geode::OpenGeodeInspectorInspectionException::test( + result.brep_non_manifold_edges.nb_issues() == 0, + "[Test] Edges without unique vertices should not be seen as shared " + "by several surfaces: ", + result.brep_non_manifold_edges.string() ); +} + +void check_single_edge_line_without_unique_vertices() +{ + geode::BRep brep; + geode::BRepBuilder builder{ brep }; + const auto& surface = add_square_surface( brep, builder, 0 ); + const auto& line = brep.line( builder.add_line() ); + auto mesh_builder = builder.line_mesh_builder( line ); + mesh_builder->create_point( geode::Point3D{ { 0, 0, 0 } } ); + mesh_builder->create_point( geode::Point3D{ { 1, 1, 0 } } ); + mesh_builder->create_edge( 0, 1 ); + builder.add_line_surface_internal_relationship( line, surface ); + builder.create_unique_vertices( 1 ); + const geode::BRepComponentMeshesManifold inspector{ brep }; + const auto result = inspector.inspect_brep_manifold(); + geode::OpenGeodeInspectorInspectionException::test( + result.brep_non_manifold_edges.nb_issues() == 0, + "[Test] Single edge line without unique vertices should not be " + "checked: ", + result.brep_non_manifold_edges.string() ); +} + +void check_block_facets_without_unique_vertices() +{ + geode::BRep brep; + geode::BRepBuilder builder{ brep }; + const auto& block = brep.block( builder.add_block() ); + auto mesh_builder = builder.block_mesh_builder( block ); + mesh_builder->create_point( geode::Point3D{ { 0, 0, 0 } } ); + mesh_builder->create_point( geode::Point3D{ { 1, 0, 0 } } ); + mesh_builder->create_point( geode::Point3D{ { 0, 1, 0 } } ); + mesh_builder->create_point( geode::Point3D{ { 0, 0, 1 } } ); + mesh_builder->create_polyhedron( { 0, 1, 2, 3 }, + { { 1, 3, 2 }, { 0, 2, 3 }, { 3, 1, 0 }, { 0, 1, 2 } } ); + const geode::BRepComponentMeshesAdjacency inspector{ brep }; + const auto result = inspector.inspect_brep_meshes_adjacencies(); + const auto& issues_map = + result.blocks_facets_with_wrong_adjacencies.issues_map(); + geode::OpenGeodeInspectorInspectionException::test( + issues_map.contains( block.id() ) + && issues_map.at( block.id() ).nb_issues() == 4, + "[Test] Block border facets without unique vertices should be " + "reported as not on a Surface" ); +} + +int main() +{ + try + { + geode::OpenGeodeInspectorInspectionLibrary::initialize(); + check_model_edges_without_unique_vertices(); + check_single_edge_line_without_unique_vertices(); + check_block_facets_without_unique_vertices(); + + geode::Logger::info( "TEST SUCCESS" ); + return 0; + } + catch( ... ) + { + return geode::geode_lippincott(); + } +} diff --git a/tests/inspection/test-solid-adjacency.cpp b/tests/inspection/test-solid-adjacency.cpp index 76fba129..c8e8f2d4 100644 --- a/tests/inspection/test-solid-adjacency.cpp +++ b/tests/inspection/test-solid-adjacency.cpp @@ -30,6 +30,7 @@ #include #include +#include #include void check_adjacency() @@ -166,6 +167,29 @@ void check_non_adjacency_inversed_tetrahedron() "an inversed tetrahedron." ); } +void check_adjacency_out_of_range() +{ + auto solid = geode::TetrahedralSolid3D::create(); + auto builder = geode::TetrahedralSolidBuilder3D::create( *solid ); + builder->create_vertices( 4 ); + builder->set_point( 0, geode::Point3D{ { 0., 0., 2. } } ); + builder->set_point( 1, geode::Point3D{ { 3., .5, 0. } } ); + builder->set_point( 2, geode::Point3D{ { .5, 3., .5 } } ); + builder->set_point( 3, geode::Point3D{ { 2., 1.5, 3. } } ); + builder->create_tetrahedron( { 0, 1, 2, 3 } ); + // Stale adjacency to a polyhedron which does not exist + builder->set_polyhedron_adjacent( { 0, 0 }, 1000000 ); + + const geode::SolidMeshAdjacency3D adjacency_inspector{ *solid }; + geode::OpenGeodeInspectorInspectionException::test( + adjacency_inspector.mesh_has_wrong_adjacencies(), + "Solid with out of range adjacency should have wrong adjacencies." ); + geode::OpenGeodeInspectorInspectionException::test( + adjacency_inspector.polyhedron_facets_with_wrong_adjacency().nb_issues() + == 1, + "Solid with out of range adjacency should have one wrong facet." ); +} + int main() { try @@ -177,6 +201,8 @@ int main() check_non_adjacency_wrong_facet(); check_non_adjacency_inversed_tetrahedron(); + check_adjacency_out_of_range(); + geode::Logger::info( "TEST SUCCESS" ); return 0; } diff --git a/tests/inspection/test-solid-manifold.cpp b/tests/inspection/test-solid-manifold.cpp index 7d3c6143..278c36fe 100644 --- a/tests/inspection/test-solid-manifold.cpp +++ b/tests/inspection/test-solid-manifold.cpp @@ -186,6 +186,27 @@ void check_facet_non_manifold() "Solid shows wrong non manifold facet id." ); } +void check_edge_manifold_broken_adjacency() +{ + auto solid = geode::TetrahedralSolid3D::create(); + auto builder = geode::TetrahedralSolidBuilder3D::create( *solid ); + builder->create_vertices( 5 ); + builder->set_point( 0, geode::Point3D{ { 0., 0., 2. } } ); + builder->set_point( 1, geode::Point3D{ { 3., .5, 0. } } ); + builder->set_point( 2, geode::Point3D{ { .5, 3., .5 } } ); + builder->set_point( 3, geode::Point3D{ { 2., 1.5, 3. } } ); + builder->set_point( 4, geode::Point3D{ { 3.5, 2.5, -.5 } } ); + builder->create_tetrahedron( { 0, 1, 2, 3 } ); + builder->create_tetrahedron( { 1, 4, 2, 3 } ); + // One-way adjacency + builder->set_polyhedron_adjacent( { 0, 0 }, 1 ); + + const geode::SolidMeshEdgeManifold3D manifold_inspector{ *solid }; + geode::OpenGeodeInspectorInspectionException::test( + !manifold_inspector.mesh_edges_are_manifold(), + "[Test] Solid with broken adjacency is shown as manifold." ); +} + int main() { try @@ -198,6 +219,8 @@ int main() check_facet_manifold(); check_facet_non_manifold(); + check_edge_manifold_broken_adjacency(); + geode::Logger::info( "TEST SUCCESS" ); return 0; } diff --git a/tests/inspection/test-surface-adjacency.cpp b/tests/inspection/test-surface-adjacency.cpp index 8cc7a74e..56e0da86 100644 --- a/tests/inspection/test-surface-adjacency.cpp +++ b/tests/inspection/test-surface-adjacency.cpp @@ -294,6 +294,27 @@ void check_non_adjacency_inversed_triangle3D() "to an inversed triangle.." ); } +void check_adjacency_out_of_range() +{ + auto surface = geode::TriangulatedSurface2D::create(); + auto builder = geode::TriangulatedSurfaceBuilder2D::create( *surface ); + builder->create_point( geode::Point2D{ { 0., 0. } } ); + builder->create_point( geode::Point2D{ { 1., 0. } } ); + builder->create_point( geode::Point2D{ { 0., 1. } } ); + builder->create_triangle( { 0, 1, 2 } ); + // Stale adjacency to a polygon which does not exist + builder->set_polygon_adjacent( { 0, 0 }, 1000000 ); + + const geode::SurfaceMeshAdjacency2D adjacency_inspector{ *surface }; + geode::OpenGeodeInspectorInspectionException::test( + adjacency_inspector.mesh_has_wrong_adjacencies(), + "Surface with out of range adjacency should have wrong adjacencies." ); + geode::OpenGeodeInspectorInspectionException::test( + adjacency_inspector.polygon_edges_with_wrong_adjacency().nb_issues() + == 1, + "Surface with out of range adjacency should have one wrong edge." ); +} + int main() { try @@ -308,6 +329,8 @@ int main() check_non_adjacency_wrong_edge3D(); check_non_adjacency_inversed_triangle3D(); + check_adjacency_out_of_range(); + geode::Logger::info( "TEST SUCCESS" ); return 0; } diff --git a/tests/inspection/test-surface-manifold.cpp b/tests/inspection/test-surface-manifold.cpp index a11f34f9..7041f09a 100644 --- a/tests/inspection/test-surface-manifold.cpp +++ b/tests/inspection/test-surface-manifold.cpp @@ -137,6 +137,57 @@ void check_edge_non_manifold2D() "Surface edges are shown non manifold whereas they are." ); } +void check_vertex_broken_adjacency() +{ + auto surface = geode::TriangulatedSurface2D::create(); + auto builder = geode::TriangulatedSurfaceBuilder2D::create( *surface ); + builder->create_point( geode::Point2D{ { 0., 0. } } ); + builder->create_point( geode::Point2D{ { 1., 0. } } ); + builder->create_point( geode::Point2D{ { 0., 1. } } ); + builder->create_point( geode::Point2D{ { 5., 0. } } ); + builder->create_point( geode::Point2D{ { 6., 0. } } ); + builder->create_point( geode::Point2D{ { 5., 1. } } ); + builder->create_triangle( { 0, 1, 2 } ); + builder->create_triangle( { 3, 4, 5 } ); + // Adjacency between triangles without common vertices + builder->set_polygon_adjacent( { 0, 0 }, 1 ); + builder->set_polygon_adjacent( { 1, 0 }, 0 ); + + const geode::SurfaceMeshVertexManifold2D manifold_inspector{ *surface }; + geode::OpenGeodeInspectorInspectionException::test( + !manifold_inspector.mesh_vertices_are_manifold(), + "[Test] Surface with broken adjacency is shown as manifold." ); + geode::OpenGeodeInspectorInspectionException::test( + manifold_inspector.non_manifold_vertices().nb_issues() != 0, + "[Test] Surface with broken adjacency should have vertices which " + "could not be checked." ); +} + +void check_edge_with_many_polygons() +{ + // 257 triangles around edge (0, 1) + auto surface = geode::TriangulatedSurface2D::create(); + auto builder = geode::TriangulatedSurfaceBuilder2D::create( *surface ); + builder->create_point( geode::Point2D{ { 0., 0. } } ); + builder->create_point( geode::Point2D{ { 1., 0. } } ); + constexpr geode::index_t NB_TRIANGLES{ 257 }; + for( const auto t : geode::Range{ NB_TRIANGLES } ) + { + const auto vertex = builder->create_point( + geode::Point2D{ { 0.5, 1. + static_cast< double >( t ) } } ); + builder->create_triangle( { 0, 1, vertex } ); + } + const geode::SurfaceMeshEdgeManifold2D manifold_inspector{ *surface }; + geode::OpenGeodeInspectorInspectionException::test( + !manifold_inspector.mesh_edges_are_manifold(), + "[Test] Surface with an edge shared by ", NB_TRIANGLES, + " triangles is shown as manifold." ); + geode::OpenGeodeInspectorInspectionException::test( + manifold_inspector.non_manifold_edges().nb_issues() == 1, + "[Test] Surface with an edge shared by ", NB_TRIANGLES, + " triangles should have one non manifold edge." ); +} + int main() { try @@ -146,6 +197,8 @@ int main() check_vertex_non_manifold2D(); check_edge_manifold2D(); check_edge_non_manifold2D(); + check_edge_with_many_polygons(); + check_vertex_broken_adjacency(); geode::Logger::info( "TEST SUCCESS" ); return 0;