RINGMesh  Version 5.0.0
A programming library for geological model meshes
geomodel_validity.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2012-2017, Association Scientifique pour la Geologie et ses
3  * Applications (ASGA). All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of ASGA nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ASGA BE LIABLE FOR ANY DIRECT,
20  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  * http://www.ring-team.org
28  *
29  * RING Project
30  * Ecole Nationale Superieure de Geologie - GeoRessources
31  * 2 Rue du Doyen Marcel Roubault - TSA 70605
32  * 54518 VANDOEUVRE-LES-NANCY
33  * FRANCE
34  */
35 
37 
38 #include <future>
39 
40 #include <geogram/basic/file_system.h>
41 
42 #include <geogram/mesh/triangle_intersection.h>
43 
47 
50 
58 namespace
59 {
60  using namespace RINGMesh;
61 
62  template < index_t DIMENSION >
63  bool triangles_intersect( const GeoModel< DIMENSION >& geomodel,
64  const GeoModelMeshPolygons< DIMENSION >& polygons,
65  index_t triangle1,
66  index_t triangle2 )
67  {
68  ringmesh_assert( polygons.nb_vertices( triangle1 ) == 3 );
69  ringmesh_assert( polygons.nb_vertices( triangle2 ) == 3 );
70  const auto& vertices = geomodel.mesh.vertices;
71  const auto& p1 = vertices.vertex( polygons.vertex( { triangle1, 0 } ) );
72  const auto& p2 = vertices.vertex( polygons.vertex( { triangle1, 1 } ) );
73  const auto& p3 = vertices.vertex( polygons.vertex( { triangle1, 2 } ) );
74 
75  const auto& q1 = vertices.vertex( polygons.vertex( { triangle2, 0 } ) );
76  const auto& q2 = vertices.vertex( polygons.vertex( { triangle2, 1 } ) );
77  const auto& q3 = vertices.vertex( polygons.vertex( { triangle2, 2 } ) );
78  GEO::vector< GEO::TriangleIsect > sym;
79  return triangles_intersections( p1, p2, p3, q1, q2, q3, sym );
80  }
81 
82  template < index_t DIMENSION >
83  bool triangle_quad_intersect( const GeoModel< DIMENSION >& geomodel,
84  const GeoModelMeshPolygons< DIMENSION >& polygons,
85  index_t triangle,
86  index_t quad )
87  {
88  ringmesh_assert( polygons.nb_vertices( triangle ) == 3 );
89  ringmesh_assert( polygons.nb_vertices( quad ) == 4 );
90  const auto& vertices = geomodel.mesh.vertices;
91  const auto& p1 = vertices.vertex( polygons.vertex( { triangle, 0 } ) );
92  const auto& p2 = vertices.vertex( polygons.vertex( { triangle, 1 } ) );
93  const auto& p3 = vertices.vertex( polygons.vertex( { triangle, 2 } ) );
94 
95  const auto& q1 = vertices.vertex( polygons.vertex( { quad, 0 } ) );
96  const auto& q2 = vertices.vertex( polygons.vertex( { quad, 1 } ) );
97  const auto& q3 = vertices.vertex( polygons.vertex( { quad, 2 } ) );
98  const auto& q4 = vertices.vertex( polygons.vertex( { quad, 3 } ) );
99  GEO::vector< GEO::TriangleIsect > sym;
100  if( triangles_intersections( p1, p2, p3, q1, q2, q3, sym ) )
101  {
102  return true;
103  }
104  if( triangles_intersections( p1, p2, p3, q1, q3, q4, sym ) )
105  {
106  return true;
107  }
108  return false;
109  }
110 
111  template < index_t DIMENSION >
112  bool quad_quad_intersect( const GeoModel< DIMENSION >& geomodel,
113  const GeoModelMeshPolygons< DIMENSION >& polygons,
114  index_t quad1,
115  index_t quad2 )
116  {
117  ringmesh_assert( polygons.nb_vertices( quad1 ) == 4 );
118  ringmesh_assert( polygons.nb_vertices( quad2 ) == 4 );
119  const auto& vertices = geomodel.mesh.vertices;
120  const auto& p1 = vertices.vertex( polygons.vertex( { quad1, 0 } ) );
121  const auto& p2 = vertices.vertex( polygons.vertex( { quad1, 1 } ) );
122  const auto& p3 = vertices.vertex( polygons.vertex( { quad1, 2 } ) );
123  const auto& p4 = vertices.vertex( polygons.vertex( { quad1, 3 } ) );
124 
125  const auto& q1 = vertices.vertex( polygons.vertex( { quad2, 0 } ) );
126  const auto& q2 = vertices.vertex( polygons.vertex( { quad2, 1 } ) );
127  const auto& q3 = vertices.vertex( polygons.vertex( { quad2, 2 } ) );
128  const auto& q4 = vertices.vertex( polygons.vertex( { quad2, 3 } ) );
129  GEO::vector< GEO::TriangleIsect > sym;
130  if( triangles_intersections( p1, p2, p3, q1, q2, q3, sym ) )
131  {
132  return true;
133  }
134  if( triangles_intersections( p1, p2, p3, q1, q3, q4, sym ) )
135  {
136  return true;
137  }
138  if( triangles_intersections( p1, p3, p4, q1, q2, q3, sym ) )
139  {
140  return true;
141  }
142  if( triangles_intersections( p1, p3, p4, q1, q3, q4, sym ) )
143  {
144  return true;
145  }
146  return false;
147  }
148 
149  template < index_t DIMENSION >
150  bool is_edge_on_line(
151  const Line< DIMENSION >& line, index_t v0, index_t v1 )
152  {
153  if( v0 > v1 )
154  {
155  std::swap( v0, v1 );
156  }
157  index_t delta_i{ v1 - v0 };
158 
159  if( delta_i == 1 )
160  {
161  // There is an edge if their indices in the Line are i and i+1
162  return true;
163  }
164  if( line.is_closed() && delta_i == line.nb_vertices() - 2 )
165  {
166  // If the Line is closed we can also have 0; n-2 or n-1; 1
167  return true;
168  }
169  // The two points are on the same line but
170  // do not define an edge
171  return false;
172  }
173 
181  template < index_t DIMENSION >
182  bool is_edge_on_line(
183  const GeoModel< DIMENSION >& geomodel, index_t v0, index_t v1 )
184  {
185  auto line_type = Line< DIMENSION >::type_name_static();
186  auto v0_line_bme =
187  geomodel.mesh.vertices.gme_type_vertices( line_type, v0 );
188  if( v0_line_bme.empty() )
189  {
190  return false;
191  }
192  auto v1_line_bme =
193  geomodel.mesh.vertices.gme_type_vertices( line_type, v1 );
194  if( v1_line_bme.empty() )
195  {
196  return false;
197  }
198 
199  bool found_line{ false };
200  for( const auto& vertex0 : v0_line_bme )
201  {
202  index_t line0_id{ vertex0.gmme.index() };
203  for( const auto& vertex1 : v1_line_bme )
204  {
205  if( line0_id == vertex1.gmme.index() )
206  {
207  if( !is_edge_on_line( geomodel.line( line0_id ),
208  vertex0.v_index, vertex1.v_index ) )
209  {
210  return false;
211  }
212  found_line = true;
213  break;
214  }
215  }
216  }
217  return found_line;
218  }
219 
226  template < index_t DIMENSION >
227  bool polygons_share_line_edge( const GeoModel< DIMENSION >& geomodel,
228  const GeoModelMeshPolygons< DIMENSION >& polygons,
229  index_t p1,
230  index_t p2 )
231  {
232  // Only test the edges on boundary
233  for( auto v1 : range( polygons.nb_vertices( p1 ) ) )
234  {
235  if( polygons.adjacent( { p1, v1 } ) != NO_ID )
236  {
237  continue;
238  }
239  index_t v10{ polygons.vertex( { p1, v1 } ) };
240  index_t v11{ polygons.vertex(
241  { p1, ( v1 + 1 ) % polygons.nb_vertices( p1 ) } ) };
242  for( auto v2 : range( polygons.nb_vertices( p2 ) ) )
243  {
244  if( polygons.adjacent( { p2, v2 } ) != NO_ID )
245  {
246  continue;
247  }
248  index_t v20{ polygons.vertex( { p2, v2 } ) };
249  index_t v21{ polygons.vertex(
250  { p2, ( v2 + 1 ) % polygons.nb_vertices( p2 ) } ) };
251 
252  if( ( v10 == v20 && v11 == v21 )
253  || ( v10 == v21 && v11 == v20 ) )
254  {
255  if( is_edge_on_line( geomodel, v20, v21 ) )
256  {
257  return true;
258  }
259  }
260  }
261  }
262 
263  return false;
264  }
265 
266  template < index_t DIMENSION >
267  bool polygons_are_adjacent(
268  const GeoModelMeshPolygons< DIMENSION >& polygons,
269  index_t p1,
270  index_t p2 )
271  {
272  if( p1 == p2 )
273  {
274  return true;
275  }
276  for( auto v : range( polygons.nb_vertices( p1 ) ) )
277  {
278  if( polygons.adjacent( { p1, v } ) == p2 )
279  {
280  return true;
281  }
282  }
283  return false;
284  }
285 
290  template < index_t DIMENSION >
291  class StoreIntersections
292  {
293  public:
300  StoreIntersections( const GeoModel< DIMENSION >& geomodel,
301  std::vector< bool >& has_isect )
302  : geomodel_( geomodel ),
303  polygons_( geomodel.mesh.polygons ),
304  has_intersection_( has_isect )
305  {
306  has_intersection_.assign( polygons_.nb(), 0 );
307  }
308 
315  void operator()( index_t p1, index_t p2 )
316  {
317  if( p1 == p2 || polygons_are_adjacent( polygons_, p1, p2 )
318  || polygons_share_line_edge( geomodel_, polygons_, p1, p2 ) )
319  {
320  return;
321  }
322 
323  if( is_triangle( p1 ) )
324  {
325  if( is_triangle( p2 ) )
326  {
327  if( triangles_intersect( geomodel_, polygons_, p1, p2 ) )
328  {
329  has_intersection_[p1] = 1;
330  has_intersection_[p2] = 1;
331  }
332  }
333  else if( is_quad( p2 ) )
334  {
335  if( triangle_quad_intersect(
336  geomodel_, polygons_, p1, p2 ) )
337  {
338  has_intersection_[p1] = 1;
339  has_intersection_[p2] = 1;
340  }
341  }
342  else
343  {
345  }
346  }
347  else if( is_quad( p1 ) )
348  {
349  if( is_triangle( p2 ) )
350  {
351  if( triangle_quad_intersect(
352  geomodel_, polygons_, p2, p1 ) )
353  {
354  has_intersection_[p1] = 1;
355  has_intersection_[p2] = 1;
356  }
357  }
358  else if( is_quad( p2 ) )
359  {
360  if( quad_quad_intersect( geomodel_, polygons_, p1, p2 ) )
361  {
362  has_intersection_[p1] = 1;
363  has_intersection_[p2] = 1;
364  }
365  }
366  else
367  {
369  }
370  }
371  else
372  {
374  }
375  }
376 
377  bool is_triangle( index_t p ) const
378  {
379  PolygonType type;
380  std::tie( type, std::ignore ) = polygons_.type( p );
381  return type == PolygonType::TRIANGLE;
382  }
383  bool is_quad( index_t p ) const
384  {
385  PolygonType type;
386  std::tie( type, std::ignore ) = polygons_.type( p );
387  return type == PolygonType::QUAD;
388  }
389 
390  private:
391  const GeoModel< DIMENSION >& geomodel_;
392  const GeoModelMeshPolygons< DIMENSION >& polygons_;
393  std::vector< bool >& has_intersection_;
394  };
395 
396  void save_mesh_locating_geomodel_inconsistencies(
397  const GEO::Mesh& mesh, const std::ostringstream& file )
398  {
399  if( GEO::CmdLine::get_arg_bool( "validity_save" ) )
400  {
401  GEO::mesh_save( mesh, file.str() );
402  }
403  }
404 
405  /***************************************************************************/
406 
411  template < index_t DIMENSION >
412  bool is_boundary_entity( const GeoModel< DIMENSION >& geomodel,
413  const gmme_id& entity,
414  const gmme_id& boundary )
415  {
416  const auto& E = geomodel.mesh_entity( entity );
417  for( auto i : range( E.nb_boundaries() ) )
418  {
419  if( E.boundary_gmme( i ) == boundary )
420  {
421  return true;
422  }
423  }
424  return false;
425  }
426 
427  template < index_t DIMENSION >
428  void save_invalid_points( const std::ostringstream& file,
429  const GeoModel< DIMENSION >& geomodel,
430  const std::vector< bool >& valid )
431  {
432  GEO::Mesh point_mesh;
433  for( auto i : range( valid.size() ) )
434  {
435  if( !valid[i] )
436  {
437  const auto& V = geomodel.mesh.vertices.vertex( i );
438  point_mesh.vertices.create_vertex( V.data() );
439  }
440  }
441  save_mesh_locating_geomodel_inconsistencies( point_mesh, file );
442  }
443 
444  template < index_t DIMENSION >
445  std::map< MeshEntityType, std::vector< index_t > > get_entities(
446  const GeoModel< DIMENSION >& geomodel, index_t i )
447  {
448  std::map< MeshEntityType, std::vector< index_t > > entities;
449  const auto& types = geomodel.entity_type_manager()
450  .mesh_entity_manager.mesh_entity_types();
451  for( const auto& type : types )
452  {
453  entities[type];
454  }
455 
456  const auto& bmes = geomodel.mesh.vertices.gme_vertices( i );
457  for( const auto& vertex : bmes )
458  {
459  const auto& T = vertex.gmme.type();
460  index_t id{ vertex.gmme.index() };
461  entities[T].push_back( id );
462  }
463  return entities;
464  }
465 
466  void print_error(
467  const std::vector< index_t >& entities, const std::string& entity_name )
468  {
469  std::ostringstream oss;
470  oss << " Vertex is in " << entities.size() << " " << entity_name
471  << ": ";
472  for( auto entity : entities )
473  {
474  oss << entity << " ; ";
475  }
476  Logger::warn( "GeoModel", oss.str() );
477  }
478 
479  template < template < index_t > class ENTITY, index_t DIMENSION >
480  bool is_vertex_valid( const GeoModel< DIMENSION >& geomodel,
481  const std::map< MeshEntityType, std::vector< index_t > >& entities )
482  {
483  auto type = ENTITY< DIMENSION >::type_name_static();
484  auto boundary_type =
485  geomodel.entity_type_manager()
486  .mesh_entity_manager.boundary_entity_type( type );
487  const auto& type_entities = entities.find( type )->second;
488  if( entities.find( boundary_type )->second.empty() )
489  {
490  if( !type_entities.empty() )
491  {
492  if( type_entities.size() != 1 )
493  {
494  print_error( type_entities, type.string() + "s" );
495  Logger::warn( "GeoModel", "It should be in only one ",
496  boundary_type );
497  return false;
498  }
499  return true;
500  }
501  return true;
502  }
503  if( type_entities.empty() )
504  {
505  Logger::warn( "GeoModel", " Vertex is in a ", boundary_type,
506  " but in no ", type );
507  return false;
508  }
509  const auto& boundary_entities = entities.find( boundary_type )->second;
510  // Check that one point is no more than twice in a SURFACE
511  for( auto entity : type_entities )
512  {
513  auto nb = std::count(
514  type_entities.begin(), type_entities.end(), entity );
515  if( nb > 2 )
516  {
517  Logger::warn( "GeoModel", " Vertex is ", nb, " times in ",
518  geomodel.mesh_entity( type, entity ).gmme() );
519  return false;
520  }
521  if( nb == 2 )
522  {
523  // If a point is twice in a SURFACE, it must be
524  // on an internal boundary Line.
525  bool internal_boundary{ false };
526  for( auto line : boundary_entities )
527  {
528  if( geomodel.mesh_entity( boundary_type, line )
529  .is_inside_border(
530  geomodel.mesh_entity( type, entity ) ) )
531  {
532  internal_boundary = true;
533  break;
534  }
535  }
536  if( !internal_boundary )
537  {
538  Logger::warn( "GeoModel", " Vertex appears ", nb,
539  " times in ",
540  geomodel.mesh_entity( type, entity ).gmme() );
541  return false;
542  }
543  }
544  }
545  return true;
546  }
547 
548  template < index_t DIMENSION >
549  bool is_region_vertex_valid( const GeoModel< DIMENSION >& geomodel,
550  const std::map< MeshEntityType, std::vector< index_t > >& entities )
551  {
552  if( geomodel.nb_regions() > 0 && geomodel.region( 0 ).is_meshed() )
553  {
554  return is_vertex_valid< Region >( geomodel, entities );
555  }
556  return true;
557  }
558 
559  template < index_t DIMENSION >
560  bool is_surface_vertex_valid( const GeoModel< DIMENSION >& geomodel,
561  const std::map< MeshEntityType, std::vector< index_t > >& entities )
562  {
563  return is_vertex_valid< Surface >( geomodel, entities );
564  }
565 
566  template < index_t DIMENSION >
567  bool is_line_vertex_valid( const GeoModel< DIMENSION >& geomodel,
568  const std::map< MeshEntityType, std::vector< index_t > >& entities );
569 
570  template <>
571  bool is_line_vertex_valid( const GeoModel3D& geomodel,
572  const std::map< MeshEntityType, std::vector< index_t > >& entities )
573  {
574  const auto& lines = entities.find( Line3D::type_name_static() )->second;
575  if( entities.find( Corner3D::type_name_static() )->second.empty() )
576  {
577  if( !lines.empty() )
578  {
579  if( lines.size() != 1 )
580  {
581  print_error( lines, "Lines" );
582  Logger::warn( "GeoModel", "It should be in only one Line" );
583  return false;
584  }
585  return true;
586  }
587  return true;
588  }
589  if( lines.size() < 2 )
590  {
591  print_error( lines, "Line" );
592  Logger::warn( "GeoModel", "It should be in at least 2 Lines" );
593  return false;
594  }
595  for( const auto line : lines )
596  {
597  auto nb = std::count( lines.begin(), lines.end(), line );
598  if( nb == 2 )
599  {
600  if( !geomodel.line( line ).is_closed() )
601  {
602  Logger::warn( "GeoModel", " Vertex"
603  " is twice in Line ",
604  line );
605  return false;
606  }
607  }
608  else if( nb > 2 )
609  {
610  Logger::warn( "GeoModel", " Vertex appears ", nb,
611  " times in Line ", line );
612  return false;
613  }
614  }
615  // Check that all the lines are in incident_entity of this corner
616  gmme_id corner_id( Corner3D::type_name_static(),
617  entities.find( Corner3D::type_name_static() )->second.front() );
618  for( const auto line : lines )
619  {
620  gmme_id line_id( Line3D::type_name_static(), line );
621  if( !is_boundary_entity( geomodel, line_id, corner_id ) )
622  {
623  Logger::warn( "GeoModel",
624  " Inconsistent Line-Corner connectivity ",
625  " vertex shows that ", line_id,
626  " must be in the boundary of ", corner_id );
627  return false;
628  }
629  }
630  return true;
631  }
632 
633  template <>
634  bool is_line_vertex_valid( const GeoModel2D& geomodel,
635  const std::map< MeshEntityType, std::vector< index_t > >& entities )
636  {
637  const auto& lines = entities.find( Line2D::type_name_static() )->second;
638  if( entities.find( Corner2D::type_name_static() )->second.empty() )
639  {
640  if( !lines.empty() )
641  {
642  if( lines.size() != 1 )
643  {
644  print_error( lines, "Lines" );
645  Logger::warn( "GeoModel", "It should be in only one Line" );
646  return false;
647  }
648  return true;
649  }
650  return true;
651  }
652  if( lines.empty() )
653  {
654  print_error( lines, "Lines" );
655  Logger::warn( "GeoModel", "It should be in at least one Line" );
656  return false;
657  }
658  for( auto line : lines )
659  {
660  auto nb = std::count( lines.begin(), lines.end(), line );
661  if( nb == 2 )
662  {
663  if( !geomodel.line( line ).is_closed() )
664  {
665  Logger::warn( "GeoModel", " Vertex"
666  " is twice in Line ",
667  line );
668  return false;
669  }
670  }
671  else if( nb > 2 )
672  {
673  Logger::warn( "GeoModel", " Vertex appears ", nb,
674  " times in Line ", line );
675  return false;
676  }
677  }
678  // Check that all the lines are in incident_entity of this corner
679  gmme_id corner_id( Corner2D::type_name_static(),
680  entities.find( Corner2D::type_name_static() )->second.front() );
681  for( auto line : lines )
682  {
683  gmme_id line_id( Line2D::type_name_static(), line );
684  if( !is_boundary_entity( geomodel, line_id, corner_id ) )
685  {
686  Logger::warn( "GeoModel",
687  " Inconsistent Line-Corner connectivity ",
688  " vertex shows that ", line_id,
689  " must be in the boundary of ", corner_id );
690  return false;
691  }
692  }
693  return true;
694  }
695 
696  template < index_t DIMENSION >
697  bool is_corner_valid(
698  const std::map< MeshEntityType, std::vector< index_t > >& entities )
699  {
700  const auto& corners =
701  entities.find( Corner< DIMENSION >::type_name_static() )->second;
702  if( corners.size() > 1 )
703  {
704  print_error( corners, "Corners" );
705  Logger::warn( "GeoModel", "It should be in only one Corner" );
706  return false;
707  }
708  return true;
709  }
710 
711  template < index_t DIMENSION >
712  bool is_geomodel_vertex_valid_base( const GeoModel< DIMENSION >& geomodel,
713  std::map< MeshEntityType, std::vector< index_t > >& entities )
714  {
715  if( !is_corner_valid< DIMENSION >( entities ) )
716  {
717  return false;
718  }
719  if( !is_line_vertex_valid< DIMENSION >( geomodel, entities ) )
720  {
721  return false;
722  }
723  if( !is_surface_vertex_valid< DIMENSION >( geomodel, entities ) )
724  {
725  return false;
726  }
727 
728  return true;
729  }
730 
731  template < index_t DIMENSION >
732  bool is_geomodel_vertex_valid(
733  const GeoModel< DIMENSION >& geomodel, index_t i );
734 
735  template <>
736  bool is_geomodel_vertex_valid( const GeoModel3D& geomodel, index_t i )
737  {
738  // Get the mesh entities in which this vertex is
739  std::map< MeshEntityType, std::vector< index_t > > entities =
740  get_entities( geomodel, i );
741 
742  if( !is_geomodel_vertex_valid_base( geomodel, entities ) )
743  {
744  return false;
745  }
746  if( !is_region_vertex_valid< 3 >( geomodel, entities ) )
747  {
748  return false;
749  }
750 
751  return true;
752  }
753 
754  template <>
755  bool is_geomodel_vertex_valid( const GeoModel2D& geomodel, index_t i )
756  {
757  // Get the mesh entities in which this vertex is
758  std::map< MeshEntityType, std::vector< index_t > > entities =
759  get_entities( geomodel, i );
760 
761  return is_geomodel_vertex_valid_base( geomodel, entities );
762  }
763 
775  template < index_t DIMENSION >
776  bool check_model_points_validity( const GeoModel< DIMENSION >& geomodel )
777  {
778  // For all the vertices of the geomodel
779  // We check that the entities in which they are are consistent
780  // to have a valid B-Rep geomodel
781  std::vector< bool > valid( geomodel.mesh.vertices.nb(), true );
782  for( auto i : range( geomodel.mesh.vertices.nb() ) )
783  {
784  valid[i] = is_geomodel_vertex_valid( geomodel, i );
785  if( !valid[i] )
786  {
787  Logger::warn( "GeoModel", " Vertex ", i, " is not valid" );
788  }
789  }
790  auto nb_invalid = std::count( valid.begin(), valid.end(), false );
791 
792  if( nb_invalid > 0 )
793  {
794  std::ostringstream file;
796  << "/invalid_global_vertices.geogram";
797  save_invalid_points( file, geomodel, valid );
798 
799  if( GEO::CmdLine::get_arg_bool( "validity_save" ) )
800  {
801  Logger::warn( "GeoModel", nb_invalid, " invalid vertices" );
802  Logger::warn( "GeoModel", "Saved in file: ", file.str() );
803  }
804 
805  return false;
806  }
807  return true;
808  }
809 
810  template < index_t DIMENSION >
811  void save_edges( const std::ostringstream& file,
812  const GeoModel< DIMENSION >& geomodel,
813  const std::vector< index_t >& e )
814  {
815  GEO::Mesh edge_mesh;
816  index_t previous_vertex_id{ NO_ID };
817  for( auto i : range( e.size() ) )
818  {
819  index_t cur_vertex_id{ edge_mesh.vertices.create_vertex(
820  geomodel.mesh.vertices.vertex( e[i] ).data() ) };
821  if( i % 2 == 0 )
822  {
823  ringmesh_assert( previous_vertex_id == NO_ID );
824  previous_vertex_id = cur_vertex_id;
825  continue;
826  }
827  ringmesh_assert( previous_vertex_id != NO_ID );
828  edge_mesh.edges.create_edge( previous_vertex_id, cur_vertex_id );
829  previous_vertex_id = NO_ID;
830  }
831  save_mesh_locating_geomodel_inconsistencies( edge_mesh, file );
832  }
833 
834  template < index_t DIMENSION >
835  void save_polygons( const std::string& file,
836  const Surface< DIMENSION >& surface,
837  const std::vector< index_t >& polygons )
838  {
839  GEO::Mesh mesh;
840  for( auto cur_polygon : polygons )
841  {
842  index_t nb_vertices_in_polygon{ surface.nb_mesh_element_vertices(
843  cur_polygon ) };
844  GEO::vector< index_t > vertices;
845  vertices.reserve( nb_vertices_in_polygon );
846  for( auto v : range( nb_vertices_in_polygon ) )
847  {
848  index_t new_vertex{ mesh.vertices.create_vertex(
849  surface.mesh_element_vertex( { cur_polygon, v } )
850  .data() ) };
851  vertices.push_back( new_vertex );
852  }
853  mesh.facets.create_polygon( vertices );
854  }
855  GEO::mesh_save( mesh, file );
856  }
857 
864  template < index_t DIMENSION >
865  bool surface_boundary_valid( const Surface< DIMENSION >& surface )
866  {
867  const auto& geomodel_vertices = surface.geomodel().mesh.vertices;
868  std::vector< index_t > invalid_corners;
869  auto S_id = surface.gmme();
870  for( auto p : range( surface.nb_mesh_elements() ) )
871  {
872  for( auto v : range( surface.nb_mesh_element_vertices( p ) ) )
873  {
874  if( surface.polygon_adjacent_index( { p, v } ) == NO_ID
875  && !is_edge_on_line( surface.geomodel(),
876  geomodel_vertices.geomodel_vertex_id(
877  S_id, { p, v } ),
878  geomodel_vertices.geomodel_vertex_id(
879  S_id, surface.mesh().next_polygon_vertex(
880  { p, v } ) ) ) )
881  {
882  invalid_corners.push_back(
883  geomodel_vertices.geomodel_vertex_id(
884  S_id, { p, v } ) );
885  invalid_corners.push_back(
886  geomodel_vertices.geomodel_vertex_id( S_id,
887  surface.mesh().next_polygon_vertex( { p, v } ) ) );
888  }
889  }
890  }
891  if( !invalid_corners.empty() )
892  {
893  std::ostringstream file;
895  << "/invalid_boundary_surface_" << surface.index()
896  << ".geogram";
897  save_edges( file, surface.geomodel(), invalid_corners );
898 
899  if( GEO::CmdLine::get_arg_bool( "validity_save" ) )
900  {
901  Logger::warn( "GeoModel", " Invalid surface boundary: ",
902  invalid_corners.size() / 2, " boundary edges of ", S_id,
903  " are in no line of the geomodel " );
904  Logger::warn( "GeoModel", " Saved in file: ", file.str() );
905  }
906  return false;
907  }
908  return true;
909  }
910 
914  template < index_t DIMENSION >
915  void debug_save_non_manifold_edges( const GeoModel< DIMENSION >& geomodel,
916  const std::vector< index_t >& edge_indices,
917  const std::vector< index_t >& non_manifold_edges )
918  {
920  GeogramLineMeshBuilder< DIMENSION > builder( mesh );
921  index_t nb_edges{ static_cast< index_t >( non_manifold_edges.size() ) };
922  builder.create_vertices( 2 * nb_edges );
923  builder.create_edges( nb_edges );
924  const auto& vertices = geomodel.mesh.vertices;
925  for( auto e : range( non_manifold_edges.size() ) )
926  {
927  index_t edge_id{ non_manifold_edges[e] };
928  const auto& v0 = vertices.vertex( edge_indices[edge_id] );
929  const auto& v1 = vertices.vertex( edge_indices[edge_id + 1] );
930  builder.set_vertex( 2 * e, v0 );
931  builder.set_vertex( 2 * e + 1, v1 );
932  builder.set_edge_vertex( EdgeLocalVertex( e, 0 ), 2 * e );
933  builder.set_edge_vertex( EdgeLocalVertex( e, 1 ), 2 * e + 1 );
934  }
935  mesh.save_mesh(
936  get_validity_errors_directory() + "/non_manifold_edges.geogram" );
937  }
938 
939  template < index_t DIMENSION >
940  bool is_surface_conformal_to_volume( const Surface< DIMENSION >& surface,
941  const NNSearch< DIMENSION >& cell_facet_barycenter_nn_search )
942  {
943  std::vector< index_t > unconformal_polygons;
944  for( auto p : range( surface.nb_mesh_elements() ) )
945  {
946  auto center = surface.mesh_element_barycenter( p );
947  auto result = cell_facet_barycenter_nn_search.get_neighbors(
948  center, surface.geomodel().epsilon() );
949  if( result.empty() )
950  {
951  unconformal_polygons.push_back( p );
952  }
953  }
954  if( !unconformal_polygons.empty() )
955  {
956  std::ostringstream file;
957  file << get_validity_errors_directory() << "/unconformal_surface_"
958  << surface.index() << ".geogram";
959  save_polygons( file.str(), surface, unconformal_polygons );
960 
961  if( GEO::CmdLine::get_arg_bool( "validity_save" ) )
962  {
963  Logger::warn( "GeoModel", " Unconformal surface: ",
964  unconformal_polygons.size(), " polygons of ",
965  surface.gmme(),
966  " are unconformal with the geomodel cells " );
967  Logger::warn( "GeoModel", " Saved in file: ", file.str() );
968  }
969 
970  return false;
971  }
972  return true;
973  }
974 
975  template < index_t DIMENSION >
976  std::vector< index_t > compute_border_edges(
977  const GeoModel< DIMENSION >& geomodel )
978  {
979  std::vector< index_t > edge_indices;
980  const auto& polygons = geomodel.mesh.polygons;
981  for( const auto& surface : geomodel.surfaces() )
982  {
983  for( auto p : range( polygons.nb_polygons( surface.index() ) ) )
984  {
985  index_t polygon_id{ polygons.polygon( surface.index(), p ) };
986  for( auto v : range( polygons.nb_vertices( polygon_id ) ) )
987  {
988  index_t adj{ polygons.adjacent( { polygon_id, v } ) };
989  if( adj == NO_ID )
990  {
991  edge_indices.push_back(
992  polygons.vertex( { polygon_id, v } ) );
993  index_t next_v{ ( v + 1 )
994  % polygons.nb_vertices( polygon_id ) };
995  edge_indices.push_back(
996  polygons.vertex( { polygon_id, next_v } ) );
997  }
998  }
999  }
1000  }
1001  return edge_indices;
1002  }
1003 
1004  template < index_t DIMENSION >
1005  std::vector< vecn< DIMENSION > > compute_border_edge_barycenters(
1006  const GeoModel< DIMENSION >& geomodel,
1007  const std::vector< index_t >& edge_indices )
1008  {
1009  const auto& vertices = geomodel.mesh.vertices;
1010  std::vector< vecn< DIMENSION > > edge_barycenters;
1011  edge_barycenters.reserve( edge_indices.size() / 2 );
1012  for( index_t e = 0; e < edge_indices.size(); e += 2 )
1013  {
1014  const auto& v0 = vertices.vertex( edge_indices[e] );
1015  const auto& v1 = vertices.vertex( edge_indices[e + 1] );
1016  edge_barycenters.push_back( ( v0 + v1 ) * 0.5 );
1017  }
1018  return edge_barycenters;
1019  }
1020 
1021  template < index_t DIMENSION >
1022  std::vector< bool > are_border_edges_on_line(
1023  const GeoModel< DIMENSION >& geomodel,
1024  const std::vector< vecn< DIMENSION > >& barycenters )
1025  {
1026  const auto& line_abbb = geomodel.mesh.edges.aabb();
1027  std::vector< bool > border_edges_on_line( barycenters.size(), true );
1028  double distance{ 0 };
1029 
1030  for( auto border_edge : range( barycenters.size() ) )
1031  {
1032  const auto& barycenter = barycenters[border_edge];
1033  std::tie( std::ignore, std::ignore, distance ) =
1034  line_abbb.closest_edge( barycenter );
1035  if( distance > geomodel.epsilon() )
1036  {
1037  border_edges_on_line[border_edge] = false;
1038  }
1039  }
1040  return border_edges_on_line;
1041  }
1042 
1043  std::vector< index_t > compute_non_manifold_edges(
1044  const std::vector< bool >& edge_on_lines )
1045  {
1046  std::vector< index_t > non_manifold_edges;
1047  for( auto e : range( edge_on_lines.size() ) )
1048  {
1049  if( !edge_on_lines[e] )
1050  {
1051  non_manifold_edges.push_back( e );
1052  }
1053  }
1054  return non_manifold_edges;
1055  }
1056 
1060  template < index_t DIMENSION >
1061  class GeoModelValidityCheck
1062  {
1063  public:
1064  GeoModelValidityCheck( const GeoModel< DIMENSION >& geomodel,
1065  const ValidityCheckMode validity_check_mode )
1066  : geomodel_( geomodel ),
1067  valid_( true ),
1068  mode_( validity_check_mode )
1069  {
1070  // Ensure that the geomodel vertices are computed and up-to-date
1071  // Without that we cannot do anything
1072  geomodel_.mesh.vertices.test_and_initialize();
1073  geomodel_.mesh.polygons.test_and_initialize();
1074  }
1075 
1080  bool is_geomodel_valid()
1081  {
1082  do_check_validity();
1083  return valid_;
1084  }
1085 
1086  private:
1087  void add_base_checks( std::vector< std::future< void > >& tasks )
1088  {
1089  if( enum_contains(
1091  {
1092  tasks.emplace_back( std::async( std::launch::async,
1093  &GeoModelValidityCheck::test_geomodel_connectivity_validity,
1094  this ) );
1095  }
1097  {
1098  tasks.emplace_back( std::async( std::launch::async,
1099  &GeoModelValidityCheck::test_geomodel_geological_validity,
1100  this ) );
1101  }
1102  if( enum_contains(
1104  {
1105  tasks.emplace_back( std::async( std::launch::async,
1106  &GeoModelValidityCheck::test_surface_line_mesh_conformity,
1107  this ) );
1108  }
1110  {
1111  tasks.emplace_back( std::async( std::launch::async,
1112  &GeoModelValidityCheck::
1113  test_geomodel_mesh_entities_validity,
1114  this ) );
1116  // threads.emplace_back(
1117  // &GeoModelValidityCheck::test_non_free_line_at_two_interfaces_intersection,
1118  // this );
1119  }
1120  }
1121 
1122  void add_checks( std::vector< std::future< void > >& tasks )
1123  {
1124  add_base_checks( tasks );
1125  }
1126 
1127  void do_check_validity()
1128  {
1129  std::vector< std::future< void > > tasks;
1130  tasks.reserve( 8 );
1131  add_checks( tasks );
1132 
1133  for( auto& task : tasks )
1134  {
1135  task.get();
1136  }
1137  }
1138 
1142  void test_geomodel_mesh_entities_validity()
1143  {
1144  if( !are_geomodel_mesh_entities_mesh_valid( geomodel_ ) )
1145  {
1146  set_invalid_model();
1147  }
1148  }
1149 
1153  void test_geomodel_connectivity_validity()
1154  {
1156  {
1157  set_invalid_model();
1158  }
1159  else if( !has_geomodel_finite_extension( geomodel_ ) )
1160  {
1161  set_invalid_model();
1162  }
1163  }
1164 
1168  void test_geomodel_geological_validity()
1169  {
1170  if( !are_geomodel_geological_entities_valid( geomodel_ ) )
1171  {
1172  set_invalid_model();
1173  }
1174  if( !are_geomodel_mesh_entities_parent_valid( geomodel_ ) )
1175  {
1176  set_invalid_model();
1177  }
1178  }
1179 
1185  void test_surface_line_mesh_conformity()
1186  {
1187  // Check relationships between GeoModelEntities
1188  // sharing the same point of the geomodel
1189  if( !check_model_points_validity( geomodel_ ) )
1190  {
1191  set_invalid_model();
1192  }
1193  // Check on that Surface edges are in a Line
1194  for( const auto& surface : geomodel_.surfaces() )
1195  {
1196  if( !surface_boundary_valid( surface ) )
1197  {
1198  set_invalid_model();
1199  }
1200  }
1201  }
1202 
1203  void test_region_surface_mesh_conformity()
1204  {
1205  if( geomodel_.mesh.cells.nb() > 0 )
1206  {
1207  // Check the consistency between Surface polygons and Region
1208  // cell facets
1209  const auto& nn_search =
1210  geomodel_.mesh.cells.cell_facet_nn_search();
1211  for( const auto& surface : geomodel_.surfaces() )
1212  {
1213  if( !is_surface_conformal_to_volume( surface, nn_search ) )
1214  {
1215  set_invalid_model();
1216  }
1217  }
1218  }
1219  }
1220 
1221  void test_non_free_line_at_two_interfaces_intersection()
1222  {
1223  if( !geomodel_.entity_type_manager()
1224  .geological_entity_manager.is_valid_type(
1226  {
1227  return;
1228  }
1229  for( const auto& line : geomodel_.lines() )
1230  {
1231  if( line.nb_incident_entities() == 1 )
1232  {
1233  continue;
1234  }
1235 
1236  const index_t first_interface_id{
1237  line.incident_entity( 0 )
1238  .parent_gmge(
1240  .index()
1241  };
1242  ringmesh_assert( first_interface_id != NO_ID );
1243  bool at_least_two_different_interfaces{ false };
1244  for( auto in_boundary_i :
1245  range( 1, line.nb_incident_entities() ) )
1246  {
1247  const index_t cur_interface_id{
1248  line.incident_entity( in_boundary_i )
1249  .parent_gmge(
1251  .index()
1252  };
1253  ringmesh_assert( cur_interface_id != NO_ID );
1254  if( cur_interface_id != first_interface_id )
1255  {
1256  at_least_two_different_interfaces = true;
1257  break;
1258  }
1259  }
1260 
1261  if( !at_least_two_different_interfaces )
1262  {
1263  Logger::warn( "GeoModel",
1264  "All in boundaries (surfaces) of line ", line.index(),
1265  " are children of a same interface." );
1266  set_invalid_model();
1267  }
1268  }
1269  }
1270 
1277  void test_non_manifold_edges()
1278  {
1279  auto edge_indices = compute_border_edges( geomodel_ );
1280  auto barycenters =
1281  compute_border_edge_barycenters( geomodel_, edge_indices );
1282  auto border_edges_on_line =
1283  are_border_edges_on_line( geomodel_, barycenters );
1284  auto non_manifold_edges =
1285  compute_non_manifold_edges( border_edges_on_line );
1286 
1287  if( !non_manifold_edges.empty() )
1288  {
1289  Logger::warn( "GeoModel", non_manifold_edges.size(),
1290  " non-manifold edges " );
1291  debug_save_non_manifold_edges(
1292  geomodel_, edge_indices, non_manifold_edges );
1293 
1294  set_invalid_model();
1295  }
1296  }
1297 
1303  void test_polygon_intersections()
1304  {
1305  if( geomodel_.mesh.polygons.nb()
1306  == geomodel_.mesh.polygons.nb_triangle()
1307  + geomodel_.mesh.polygons.nb_quad() )
1308  {
1309  std::vector< bool > has_intersection;
1310  StoreIntersections< DIMENSION > action(
1311  geomodel_, has_intersection );
1312  const auto& AABB = geomodel_.mesh.polygons.aabb();
1313  AABB.compute_self_element_bbox_intersections( action );
1314 
1315  auto nb_intersections = std::count(
1316  has_intersection.begin(), has_intersection.end(), 1 );
1317 
1318  if( nb_intersections > 0 )
1319  {
1320  GEO::Mesh mesh;
1321  for( auto p : range( has_intersection.size() ) )
1322  {
1323  if( !has_intersection[p] )
1324  {
1325  continue;
1326  }
1327  GEO::vector< index_t > vertices;
1328  vertices.reserve(
1329  geomodel_.mesh.polygons.nb_vertices( p ) );
1330  for( auto v :
1331  range( geomodel_.mesh.polygons.nb_vertices( p ) ) )
1332  {
1333  index_t id = mesh.vertices.create_vertex(
1334  geomodel_.mesh.vertices
1335  .vertex( geomodel_.mesh.polygons.vertex(
1336  { p, v } ) )
1337  .data() );
1338  vertices.push_back( id );
1339  }
1340  mesh.facets.create_polygon( vertices );
1341  }
1342  std::ostringstream file;
1344  << "/intersected_polygons.geogram";
1345  save_mesh_locating_geomodel_inconsistencies( mesh, file );
1346  Logger::out( "I/O" );
1347  Logger::warn( "GeoModel", nb_intersections,
1348  " polygon intersections " );
1349  set_invalid_model();
1350  }
1351  }
1352  else
1353  {
1354  Logger::warn( "GeoModel",
1355  "Polygonal intersection check not implemented yet" );
1356  }
1357  }
1358 
1359  void set_invalid_model()
1360  {
1361  valid_ = false;
1362  }
1363 
1364  private:
1365  const GeoModel< DIMENSION >& geomodel_;
1366  bool valid_;
1367  ValidityCheckMode mode_;
1368  };
1369  template <>
1370  void GeoModelValidityCheck< 3 >::add_checks(
1371  std::vector< std::future< void > >& tasks )
1372  {
1374  {
1375  tasks.push_back( std::async( std::launch::async,
1376  &GeoModelValidityCheck::test_polygon_intersections, this ) );
1377  }
1378  if( enum_contains(
1380  {
1381  tasks.emplace_back( std::async( std::launch::async,
1382  &GeoModelValidityCheck::test_region_surface_mesh_conformity,
1383  this ) );
1384  }
1386  {
1387  tasks.emplace_back( std::async( std::launch::async,
1388  &GeoModelValidityCheck::test_non_manifold_edges, this ) );
1389  }
1390  add_base_checks( tasks );
1391  }
1392 
1393 } // namespace
1394 
1395 namespace RINGMesh
1396 {
1397  void set_validity_errors_directory( const std::string& directory )
1398  {
1399  // If trailing / or \ is not removed, the test fails on Windows
1400  std::string copy( directory );
1401  if( *copy.rbegin() == '/' || *copy.rbegin() == '\\' )
1402  {
1403  copy.erase( copy.end() - 1 );
1404  }
1405  if( GEO::FileSystem::is_directory( copy ) )
1406  {
1407  GEO::CmdLine::set_arg( "validity_directory", copy + '/' );
1408  }
1409  }
1410 
1412  {
1413  return GEO::CmdLine::get_arg( "validity_directory" );
1414  }
1415 
1416  template < index_t DIMENSION >
1418  const GeoModel< DIMENSION >& geomodel )
1419  {
1420  const auto& meshed_types = geomodel.entity_type_manager()
1421  .mesh_entity_manager.mesh_entity_types();
1422  index_t count_invalid{ 0 };
1423  for( const auto& type : meshed_types )
1424  {
1425  index_t nb_entities{ geomodel.nb_mesh_entities( type ) };
1426  for( auto i : range( nb_entities ) )
1427  {
1428  const auto& entity = geomodel.mesh_entity( type, i );
1429  if( !entity.is_valid() )
1430  {
1431  count_invalid++;
1432  }
1433  }
1434  }
1435  if( count_invalid != 0 )
1436  {
1437  Logger::warn( "GeoModel", count_invalid,
1438  " mesh entities of the geomodel have an invalid mesh." );
1439  }
1440  return count_invalid == 0;
1441  }
1442 
1443  template < index_t DIMENSION >
1445  const GeoModel< DIMENSION >& geomodel )
1446  {
1447  const auto& meshed_types = geomodel.entity_type_manager()
1448  .mesh_entity_manager.mesh_entity_types();
1449  index_t count_invalid{ 0 };
1450  for( const auto& type : meshed_types )
1451  {
1452  index_t nb_entities{ geomodel.nb_mesh_entities( type ) };
1453  for( auto i : range( nb_entities ) )
1454  {
1455  const auto& entity = geomodel.mesh_entity( type, i );
1456  if( !entity.is_connectivity_valid() )
1457  {
1458  count_invalid++;
1459  }
1460  }
1461  }
1462  if( count_invalid != 0 )
1463  {
1464  Logger::warn( "GeoModel", count_invalid, " mesh entities of the "
1465  "geomodel have an invalid "
1466  "connectivity." );
1467  }
1468  return count_invalid == 0;
1469  }
1470 
1471  template < index_t DIMENSION >
1473  const GeoModel< DIMENSION >& geomodel )
1474  {
1475  const auto& geological_types =
1476  geomodel.entity_type_manager()
1477  .geological_entity_manager.geological_entity_types();
1478  index_t count_invalid{ 0 };
1479  for( const auto& type : geological_types )
1480  {
1481  for( auto& geol_entity : geomodel.geol_entities( type ) )
1482  {
1483  if( !geol_entity.is_valid() )
1484  {
1485  count_invalid++;
1486  }
1487  }
1488  }
1489  if( count_invalid != 0 )
1490  {
1491  Logger::warn( "GeoModel", count_invalid,
1492  " geological entities of the geomodel are invalid " );
1493  }
1494  return count_invalid == 0;
1495  }
1496 
1497  template < index_t DIMENSION >
1499  const GeoModel< DIMENSION >& geomodel )
1500  {
1501  const auto& meshed_types = geomodel.entity_type_manager()
1502  .mesh_entity_manager.mesh_entity_types();
1503  index_t count_invalid{ 0 };
1504  for( const auto& type : meshed_types )
1505  {
1506  index_t nb_entities{ geomodel.nb_mesh_entities( type ) };
1507  for( auto i : range( nb_entities ) )
1508  {
1509  const auto& geol_entity = geomodel.mesh_entity( type, i );
1510  if( !geol_entity.is_parent_connectivity_valid() )
1511  {
1512  count_invalid++;
1513  }
1514  }
1515  }
1516  if( count_invalid != 0 )
1517  {
1518  Logger::warn( "GeoModel", count_invalid,
1519  " mesh entities of the geomodel have an invalid ",
1520  "parent connectivity (geological relationships)." );
1521  }
1522  return count_invalid == 0;
1523  }
1524 
1525  template < index_t DIMENSION >
1527  ValidityCheckMode validity_check_mode )
1528  {
1529  if( !GEO::CmdLine::get_arg_bool( "in:intersection_check" ) )
1530  {
1531  validity_check_mode =
1532  validity_check_mode ^ ValidityCheckMode::POLYGON_INTERSECTIONS;
1533  }
1534 
1535  GeoModelValidityCheck< DIMENSION > validity_checker(
1536  geomodel, validity_check_mode );
1537 
1538  bool valid{ validity_checker.is_geomodel_valid() };
1539 
1540  if( valid )
1541  {
1542  Logger::out( "GeoModel", "Model ", geomodel.name(), " is valid " );
1543  }
1544  else
1545  {
1546  Logger::warn(
1547  "GeoModel", "Model ", geomodel.name(), " is invalid " );
1548  if( !GEO::CmdLine::get_arg_bool( "validity_save" ) )
1549  {
1550  Logger::out( "Info", "To save geomodel invalidities in files ",
1551  "(.geogram) set \"validity_save\" to true in the command "
1552  "line." );
1553  }
1554  }
1555  return valid;
1556  }
1557 
1564  template < index_t DIMENSION >
1565  bool has_geomodel_finite_extension( const GeoModel< DIMENSION >& geomodel );
1566 
1567  template <>
1569  const GeoModel2D& geomodel )
1570  {
1571  const auto& geomodelmesh = geomodel.mesh;
1572  const auto& geomodelmesh_vertices = geomodelmesh.vertices;
1573 
1574  std::vector< vec2 > all_points;
1575  all_points.reserve( geomodelmesh_vertices.nb() );
1576  for( auto v_i : range( geomodelmesh_vertices.nb() ) )
1577  {
1578  all_points.push_back( geomodelmesh_vertices.vertex( v_i ) );
1579  }
1580 
1581  auto line = LineMesh2D::create_mesh();
1582  ringmesh_assert( line != nullptr );
1583  auto builder = LineMeshBuilder2D::create_builder( *line );
1584  ringmesh_assert( builder != nullptr );
1585  auto start = builder->create_vertices( geomodelmesh_vertices.nb() );
1586  ringmesh_unused( start );
1587  ringmesh_assert( start == 0 );
1588  for( auto v_i : range( geomodelmesh_vertices.nb() ) )
1589  {
1590  builder->set_vertex( v_i, all_points[v_i] );
1591  }
1592 
1593  const auto voi_lines = geomodel.voi_lines();
1594  const auto& geomodelmesh_edges = geomodelmesh.edges;
1595  for( auto edge_i : range( geomodelmesh_edges.nb() ) )
1596  {
1597  const auto cur_line_id = geomodelmesh_edges.line( edge_i );
1598  if( contains( voi_lines.lines_, cur_line_id ) )
1599  {
1600  auto v0 = geomodelmesh_edges.vertex( { edge_i, 0 } );
1601  auto v1 = geomodelmesh_edges.vertex( { edge_i, 1 } );
1602  builder->create_edge( v0, v1 );
1603  }
1604  }
1605 
1606  // As the geomodel lines are independent meshes, they have different
1607  // orientations (normal directions). So, the merge of these surfaces may
1608  // produce several connected components with colocated vertices.
1609  // The following repair merges such vertices and enables a homogeneous
1610  // line orientation [BC].
1611  builder->repair( GEO::MESH_REPAIR_TOPOLOGY, global_epsilon );
1612  index_t nb_connected_components;
1613  std::tie( nb_connected_components, std::ignore ) =
1614  line->connected_components();
1615 
1616  return nb_connected_components == 1;
1617  }
1618 
1619  template <>
1621  const GeoModel3D& geomodel )
1622  {
1623  const auto& geomodelmesh = geomodel.mesh;
1624  const auto& geomodelmesh_vertices = geomodelmesh.vertices;
1625 
1626  std::vector< vec3 > all_points;
1627  all_points.reserve( geomodelmesh_vertices.nb() );
1628  for( auto v_i : range( geomodelmesh_vertices.nb() ) )
1629  {
1630  all_points.push_back( geomodelmesh_vertices.vertex( v_i ) );
1631  }
1632 
1633  auto surface = SurfaceMesh3D::create_mesh();
1634  ringmesh_assert( surface != nullptr );
1635  auto builder = SurfaceMeshBuilder3D::create_builder( *surface );
1636  ringmesh_assert( builder != nullptr );
1637  auto start = builder->create_vertices( geomodelmesh_vertices.nb() );
1638  ringmesh_unused( start );
1639  ringmesh_assert( start == 0 );
1640  for( auto v_i : range( geomodelmesh_vertices.nb() ) )
1641  {
1642  builder->set_vertex( v_i, all_points[v_i] );
1643  }
1644 
1645  const auto voi_surfaces = geomodel.voi_surfaces();
1646  const auto& geomodelmesh_polygons = geomodelmesh.polygons;
1647  for( auto polygon_i : range( geomodelmesh_polygons.nb() ) )
1648  {
1649  const auto cur_surface_id =
1650  geomodelmesh_polygons.surface( polygon_i );
1651  if( contains( voi_surfaces.surfaces_, cur_surface_id ) )
1652  {
1653  std::vector< index_t > polygon_vertices(
1654  geomodelmesh_polygons.nb_vertices( polygon_i ) );
1655  for( auto v_i :
1656  range( geomodelmesh_polygons.nb_vertices( polygon_i ) ) )
1657  {
1658  polygon_vertices[v_i] =
1659  geomodelmesh_polygons.vertex( { polygon_i, v_i } );
1660  }
1661  builder->create_polygon( polygon_vertices );
1662  }
1663  }
1664 
1665  for( auto p : range( surface->nb_polygons() ) )
1666  {
1667  for( auto v : range( surface->nb_polygon_vertices( p ) ) )
1668  {
1669  builder->set_polygon_adjacent( { p, v }, NO_ID );
1670  }
1671  }
1672 
1673  builder->connect_polygons();
1674  // As the geomodel surfaces are independent meshes, they have different
1675  // orientations (normal directions). So, the merge of these surfaces may
1676  // produce several connected components with colocated vertices.
1677  // The following repair merges such vertices and enables a homogeneous
1678  // surface orientation [BC].
1679  builder->repair( GEO::MESH_REPAIR_TOPOLOGY, global_epsilon );
1680  index_t nb_connected_components;
1681  std::tie( nb_connected_components, std::ignore ) =
1682  surface->connected_components();
1683 
1684  return nb_connected_components == 1;
1685  }
1686 
1687  template bool RINGMESH_API is_geomodel_valid< 2 >(
1688  const GeoModel2D&, ValidityCheckMode );
1689  template bool RINGMESH_API are_geomodel_mesh_entities_mesh_valid(
1690  const GeoModel2D& );
1691  template bool RINGMESH_API are_geomodel_mesh_entities_connectivity_valid(
1692  const GeoModel2D& );
1693  template bool RINGMESH_API are_geomodel_mesh_entities_parent_valid(
1694  const GeoModel2D& );
1695  template bool RINGMESH_API are_geomodel_geological_entities_valid(
1696  const GeoModel2D& );
1697 
1698  template bool RINGMESH_API is_geomodel_valid< 3 >(
1699  const GeoModel3D&, ValidityCheckMode );
1700  template bool RINGMESH_API are_geomodel_mesh_entities_mesh_valid(
1701  const GeoModel3D& );
1702  template bool RINGMESH_API are_geomodel_mesh_entities_connectivity_valid(
1703  const GeoModel3D& );
1704  template bool RINGMESH_API are_geomodel_mesh_entities_parent_valid(
1705  const GeoModel3D& );
1706  template bool RINGMESH_API are_geomodel_geological_entities_valid(
1707  const GeoModel3D& );
1708 
1709 } // namespace RINGMesh
const GeoModel< DIMENSION > & geomodel() const
template bool RINGMESH_API is_geomodel_valid< 2 >(const GeoModel2D &, ValidityCheckMode)
GeoModelMesh< DIMENSION > mesh
Definition: geomodel.h:241
bool has_geomodel_finite_extension(const GeoModel< DIMENSION > &geomodel)
Check that the geomodel has a finite extension.
GEO::vecng< DIMENSION, double > vecn
Definition: types.h:74
std::string RINGMESH_API get_validity_errors_directory()
Get the directory where debugging information on invalid entities shall be stored.
virtual void save_mesh(const std::string &filename) const =0
bool is_geomodel_valid(const GeoModel< DIMENSION > &geomodel, ValidityCheckMode validity_check_mode=ValidityCheckMode::ALL)
Check global geomodel validity.
const EntityTypeManager< DIMENSION > & entity_type_manager() const
Gets the EntityTypeManager associated to the GeoModel.
Definition: geomodel.h:119
index_t nb_mesh_element_vertices(index_t polygon_index) const final
virtual const GeoModelMeshEntity< DIMENSION > & mesh_entity(const gmme_id &id) const
Generic access to a meshed entity.
Definition: geomodel.cpp:131
void ringmesh_unused(const T &)
Definition: common.h:105
index_t adjacent(const PolygonLocalEdge &polygon_local_edge) const
bool RINGMESH_API has_geomodel_finite_extension< 3 >(const GeoModel3D &geomodel)
A GeoModelEntity of type CORNER.
static void warn(const std::string &feature, const Args &... args)
Definition: logger.h:75
bool is_closed() const
A Line is closed if its two extremities are identitical.
bool are_geomodel_mesh_entities_connectivity_valid(const GeoModel< DIMENSION > &geomodel)
Check the connectivity of mesh entities.
const std::string & name() const
Gets the name of the GeoModel.
Definition: geomodel.h:111
const SurfaceMesh< DIMENSION > & mesh() const
Get the low level mesh data structure.
vecn< DIMENSION > mesh_element_barycenter(index_t polygon_index) const final
static MeshEntityType type_name_static()
geol_entity_range< DIMENSION > geol_entities(const GeologicalEntityType &geol_type) const
Definition: geomodel.h:341
bool RINGMESH_API has_geomodel_finite_extension< 2 >(const GeoModel2D &geomodel)
bool are_geomodel_mesh_entities_parent_valid(const GeoModel< DIMENSION > &geomodel)
index_t nb_mesh_elements() const final
bool enum_contains(Enum lhs, Enum rhs)
Definition: types.h:157
double epsilon() const
Definition: geomodel.cpp:208
template bool RINGMESH_API is_geomodel_valid< 3 >(const GeoModel3D &, ValidityCheckMode)
static void out(const std::string &feature, const Args &... args)
Definition: logger.h:61
const Line< DIMENSION > & line(index_t index) const
Definition: geomodel.cpp:185
PolygonType
Definition: types.h:105
const Surface< DIMENSION > & surface(index_t index) const
Definition: geomodel.cpp:192
bool are_geomodel_mesh_entities_mesh_valid(const GeoModel< DIMENSION > &geomodel)
Check the validity of all individual entity meshes.
const Surface< DIMENSION > & incident_entity(index_t x) const
ValidityCheckMode
Option to select what are checked.
bool are_geomodel_geological_entities_valid(const GeoModel< DIMENSION > &geomodel)
index_t vertex(const ElementLocalVertex &polygon_local_vertex) const
#define ringmesh_assert(x)
bool contains(const container &in, const T &value)
Definition: algorithm.h:87
The MeshEntityType described the type of the meshed entities There are 4 MeshEntityTypes correspondin...
Definition: entity_type.h:117
void RINGMESH_API set_validity_errors_directory(const std::string &directory)
Set the directory where debugging information on invalid entities shall be stored.
Classes to build GeoModel from various inputs.
Definition: algorithm.h:48
index_t polygon_adjacent_index(const PolygonLocalEdge &polygon_local_edge) const
Gets the polygon adjacent along an edge of a polygon.
surface_range< DIMENSION > surfaces() const
Definition: geomodel.h:337
A GeoModelEntity of type LINE.
const vecn< DIMENSION > & mesh_element_vertex(const ElementLocalVertex &element_local_vertex) const
Coordinates of a vertex of a mesh element.
std::vector< index_t > get_neighbors(const vecn< DIMENSION > &v, double threshold_distance) const
Definition: nn_search.cpp:205
virtual index_t nb_mesh_entities(const MeshEntityType &type) const
Returns the number of mesh entities of the given type.
Definition: geomodel.cpp:108
index_t nb_vertices(index_t polygon) const
This template is a specialization of a gme_id to the GeoModelMeshEntity.
Definition: entity_type.h:285
#define ringmesh_assert_not_reached