DGtal  1.4.beta
testSurfaceMesh.cpp
Go to the documentation of this file.
1 
31 #include <iostream>
32 #include <sstream>
33 #include <algorithm>
34 #include "DGtal/base/Common.h"
35 #include "ConfigTest.h"
36 #include "DGtalCatch.h"
37 #include "DGtal/helpers/StdDefs.h"
38 #include "DGtal/kernel/PointVector.h"
39 #include "DGtal/graph/CUndirectedSimpleGraph.h"
40 #include "DGtal/graph/BreadthFirstVisitor.h"
41 #include "DGtal/shapes/SurfaceMesh.h"
42 #include "DGtal/shapes/SurfaceMeshHelper.h"
43 #include "DGtal/io/readers/SurfaceMeshReader.h"
44 #include "DGtal/io/writers/SurfaceMeshWriter.h"
46 
47 using namespace std;
48 using namespace DGtal;
49 
51 // Functions for testing class SurfaceMesh.
53 
54 
55 
58 {
61  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
63  std::vector< RealPoint > positions;
64  std::vector< Vertices > faces;
65  positions.push_back( RealPoint( 0, 0, 0 ) );
66  positions.push_back( RealPoint( 1, 0, 0 ) );
67  positions.push_back( RealPoint( 0, 1, 0 ) );
68  positions.push_back( RealPoint( 1, 1, 0 ) );
69  positions.push_back( RealPoint( 0, 0, 1 ) );
70  positions.push_back( RealPoint( 1, 0, 1 ) );
71  positions.push_back( RealPoint( 0, 1, 1 ) );
72  positions.push_back( RealPoint( 1, 1, 1 ) );
73  positions.push_back( RealPoint( 1, 0, 2 ) );
74  positions.push_back( RealPoint( 0, 0, 2 ) );
75  faces.push_back( { 1, 0, 2, 3 } );
76  faces.push_back( { 0, 1, 5, 4 } );
77  faces.push_back( { 1, 3, 7, 5 } );
78  faces.push_back( { 3, 2, 6, 7 } );
79  faces.push_back( { 2, 0, 4, 6 } );
80  faces.push_back( { 4, 5, 8, 9 } );
81  return PolygonMesh( positions.cbegin(), positions.cend(),
82  faces.cbegin(), faces.cend() );
83 }
84 
85 
88 {
91  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
93  std::vector< RealPoint > positions;
94  std::vector< Vertices > faces;
95  positions.push_back( RealPoint( 0, 0, 1 ) );
96  positions.push_back( RealPoint( 0, -1, 0 ) );
97  positions.push_back( RealPoint( 1, 0, 0 ) );
98  positions.push_back( RealPoint( 0, 1, 0 ) );
99  positions.push_back( RealPoint( 0, 0, 0 ) );
100  faces.push_back( { 0, 4, 1 } );
101  faces.push_back( { 0, 4, 2 } );
102  faces.push_back( { 0, 4, 3 } );
103  return PolygonMesh( positions.cbegin(), positions.cend(),
104  faces.cbegin(), faces.cend() );
105 }
106 
109 {
112  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
114  std::vector< RealPoint > positions;
115  std::vector< Vertices > faces;
116  positions.push_back( RealPoint( 0, 0, 0 ) );
117  positions.push_back( RealPoint( 1, 0, 0 ) );
118  positions.push_back( RealPoint( 0, 1, 0 ) );
119  positions.push_back( RealPoint( 0, 0, 1 ) );
120  faces.push_back( { 0, 1, 2 } );
121  faces.push_back( { 1, 0, 3 } );
122  faces.push_back( { 0, 2, 3 } );
123  faces.push_back( { 3, 2, 1 } );
124  return PolygonMesh( positions.cbegin(), positions.cend(),
125  faces.cbegin(), faces.cend() );
126 }
127 
128 SCENARIO( "SurfaceMesh< RealPoint3 > concept check tests", "[surfmesh][concepts]" )
129 {
132  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
133  BOOST_CONCEPT_ASSERT(( concepts::CUndirectedSimpleGraph< PolygonMesh > ));
134 }
135 
136 SCENARIO( "SurfaceMesh< RealPoint3 > build tests", "[surfmesh][build]" )
137 {
140  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
142  typedef PolygonMesh::Edge Edge;
143  typedef PolygonMesh::Vertex Vertex;
144  GIVEN( "A box with an open side" ) {
145  PolygonMesh polymesh = makeBox();
146  THEN( "The mesh has 10 vertices, v0 has 3 neighbors, v1 has 3 neighbors, etc" ) {
147  REQUIRE( polymesh.size() == 10 );
148  REQUIRE( polymesh.degree( 0 ) == 3 );
149  REQUIRE( polymesh.degree( 1 ) == 3 );
150  REQUIRE( polymesh.degree( 2 ) == 3 );
151  REQUIRE( polymesh.degree( 3 ) == 3 );
152  REQUIRE( polymesh.degree( 4 ) == 4 );
153  REQUIRE( polymesh.degree( 5 ) == 4 );
154  REQUIRE( polymesh.degree( 6 ) == 3 );
155  REQUIRE( polymesh.degree( 7 ) == 3 );
156  REQUIRE( polymesh.degree( 8 ) == 2 );
157  REQUIRE( polymesh.degree( 9 ) == 2 );
158  }
159  THEN( "Euler number is 1 as is the Euler number of a disk." )
160  {
161  REQUIRE( polymesh.nbVertices() == 10 );
162  REQUIRE( polymesh.nbEdges() == 15 );
163  REQUIRE( polymesh.nbFaces() == 6 );
164  REQUIRE( polymesh.Euler() == 1 );
165  }
166  THEN( "Checking distances." )
167  {
168  REQUIRE( polymesh.distance(0,0) == Approx(0.0) );
169  REQUIRE( polymesh.distance(0,7) == Approx(std::sqrt(3)));
170  }
171  THEN( "Breadth-first visiting the mesh from vertex 0, visit {0}, then {1,2,4}, then {3,5,6,9}, then {7,8}." )
172  {
173  BreadthFirstVisitor< PolygonMesh > visitor( polymesh, 0 );
174  std::vector<unsigned long> vertices;
175  std::vector<unsigned long> distances;
176  while ( ! visitor.finished() )
177  {
178  vertices.push_back( visitor.current().first );
179  distances.push_back( visitor.current().second );
180  visitor.expand();
181  }
182  REQUIRE( vertices.size() == 10 );
183  REQUIRE( distances.size() == 10 );
184  int expected_vertices[] = { 0, 1, 2, 4, 3, 5, 6, 9, 7, 8 };
185  int expected_distance[] = { 0, 1, 1, 1, 2, 2, 2, 2, 3, 3 };
186  auto itb = vertices.begin();
187  std::sort( itb+1, itb+4 );
188  std::sort( itb+4, itb+8 );
189  std::sort( itb+8, itb+10 );
190  bool vertices_ok
191  = std::equal( vertices.begin(), vertices.end(), expected_vertices );
192  REQUIRE( vertices_ok );
193  bool distances_ok
194  = std::equal( distances.begin(), distances.end(), expected_distance );
195  REQUIRE( distances_ok );
196  }
197  THEN( "The mesh has 6 boundary edges and 9 manifold inner consistent edges, the boundary is a 1d manifold" ) {
198  auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
199  auto mani_inner = polymesh.computeManifoldInnerEdges();
200  auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
201  auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
202  auto non_mani = polymesh.computeNonManifoldEdges();
203  CAPTURE( polymesh );
204  REQUIRE( mani_bdry.size() == 6 );
205  REQUIRE( mani_inner.size() == 9 );
206  REQUIRE( mani_inner_c.size() == 9 );
207  REQUIRE( mani_inner_u.size() == 0 );
208  REQUIRE( non_mani.size() == 0 );
209  }
210  THEN( "The face along (1,3) is a quadrangle (1,3,7,5)" ) {
211  Edge e13 = polymesh.makeEdge( 1, 3 );
212  auto lfs = polymesh.edgeLeftFaces( e13 );
213  Vertices T = polymesh.incidentVertices( lfs[ 0 ] );
214  REQUIRE( T.size() == 4 );
215  std::sort( T.begin(), T.end() );
216  REQUIRE( T[ 0 ] == 1 );
217  REQUIRE( T[ 1 ] == 3 );
218  REQUIRE( T[ 2 ] == 5 );
219  REQUIRE( T[ 3 ] == 7 );
220  }
221  THEN( "The face along (3,1) is a quadrangle (3,1,0,2)" ) {
222  Edge e13 = polymesh.makeEdge( 1, 3 );
223  auto rfs = polymesh.edgeRightFaces( e13 );
224  Vertices T = polymesh.incidentVertices( rfs[ 0 ] );
225  REQUIRE( T.size() == 4 );
226  std::sort( T.begin(), T.end() );
227  REQUIRE( T[ 0 ] == 0 );
228  REQUIRE( T[ 1 ] == 1 );
229  REQUIRE( T[ 2 ] == 2 );
230  REQUIRE( T[ 3 ] == 3 );
231  }
232  THEN( "The lower part of the mesh has the barycenter (0.5, 0.5, 0.5) " ) {
233  auto positions = polymesh.positions();
234  RealPoint b;
235  for ( Vertex v = 0; v < 8; ++v )
236  b += positions[ v ];
237  b /= 8;
238  REQUIRE( b[ 0 ] == 0.5 );
239  REQUIRE( b[ 1 ] == 0.5 );
240  REQUIRE( b[ 2 ] == 0.5 );
241  }
242  THEN( "We can iterate over the vertices" ) {
243  auto positions = polymesh.positions();
244  RealPoint exp_positions[] = { { 0,0,0 }, { 1,0,0 }, { 0,1,0 }, { 1,1,0 },
245  { 0,0,1 }, { 1,0,1 }, { 0,1,1 }, { 1,1,1 },
246  { 1,0,2 }, { 0,0,2 } };
247  for ( auto it = polymesh.begin(), itE = polymesh.end(); it != itE; ++it ) {
248  REQUIRE( positions[ *it ] == exp_positions[ *it ] );
249  }
250  }
251  }
252 }
253 
254 
255 SCENARIO( "SurfaceMesh< RealPoint3 > mesh helper tests", "[surfmesh][helper]" )
256 {
259  typedef SurfaceMeshHelper< RealPoint, RealVector > PolygonMeshHelper;
260  typedef PolygonMeshHelper::NormalsType NormalsType;
261  GIVEN( "A sphere of radius 10" ) {
262  auto polymesh = PolygonMeshHelper::makeSphere( 3.0, RealPoint::zero,
263  10, 10, NormalsType::NO_NORMALS );
264  THEN( "The mesh has Euler characteristic 2" ) {
265  REQUIRE( polymesh.Euler() == 2 );
266  }
267  THEN( "It is a consistent manifold without boundary" ) {
268  auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
269  auto mani_inner = polymesh.computeManifoldInnerEdges();
270  auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
271  auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
272  auto non_mani = polymesh.computeNonManifoldEdges();
273  CAPTURE( polymesh );
274  REQUIRE( mani_bdry.size() == 0 );
275  REQUIRE( mani_inner.size() == mani_inner_c.size() );
276  REQUIRE( mani_inner_u.size() == 0 );
277  REQUIRE( non_mani.size() == 0 );
278  }
279  }
280  GIVEN( "A torus with radii 3 and 1" ) {
281  auto polymesh = PolygonMeshHelper::makeTorus( 3.0, 1.0, RealPoint::zero,
282  10, 10, 0, NormalsType::NO_NORMALS );
283  THEN( "The mesh has Euler characteristic 0" ) {
284  REQUIRE( polymesh.Euler() == 0 );
285  }
286  THEN( "It is a consistent manifold without boundary" ) {
287  auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
288  auto mani_inner = polymesh.computeManifoldInnerEdges();
289  auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
290  auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
291  auto non_mani = polymesh.computeNonManifoldEdges();
292  CAPTURE( polymesh );
293  REQUIRE( mani_bdry.size() == 0 );
294  REQUIRE( mani_inner.size() == mani_inner_c.size() );
295  REQUIRE( mani_inner_u.size() == 0 );
296  REQUIRE( non_mani.size() == 0 );
297  }
298  }
299  GIVEN( "A lantern with radii 3" ) {
300  auto polymesh = PolygonMeshHelper::makeLantern( 3.0, 3.0, RealPoint::zero,
301  10, 10, NormalsType::NO_NORMALS );
302  THEN( "The mesh has Euler characteristic 0" ) {
303  REQUIRE( polymesh.Euler() == 0 );
304  }
305  THEN( "It is a consistent manifold with boundary" ) {
306  auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
307  auto mani_inner = polymesh.computeManifoldInnerEdges();
308  auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
309  auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
310  auto non_mani = polymesh.computeNonManifoldEdges();
311  CAPTURE( polymesh );
312  REQUIRE( mani_bdry.size() == 20 );
313  REQUIRE( mani_inner.size() == mani_inner_c.size() );
314  REQUIRE( mani_inner_u.size() == 0 );
315  REQUIRE( non_mani.size() == 0 );
316  }
317  }
318 }
319 
320 SCENARIO( "SurfaceMesh< RealPoint3 > reader/writer tests", "[surfmesh][io]" )
321 {
324  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
325  typedef SurfaceMeshHelper< RealPoint, RealVector > PolygonMeshHelper;
326  typedef SurfaceMeshReader< RealPoint, RealVector > PolygonMeshReader;
327  typedef SurfaceMeshWriter< RealPoint, RealVector > PolygonMeshWriter;
328  typedef PolygonMeshHelper::NormalsType NormalsType;
329  auto polymesh = PolygonMeshHelper::makeSphere( 3.0, RealPoint::zero,
330  10, 10, NormalsType::VERTEX_NORMALS );
331  WHEN( "Writing the mesh as an OBJ file and reading into another mesh" ) {
332  PolygonMesh readmesh;
333  std::ostringstream output;
334  bool okw = PolygonMeshWriter::writeOBJ( output, polymesh );
335  std::string file = output.str();
336  std::istringstream input( file );
337  bool okr = PolygonMeshReader::readOBJ ( input, readmesh );
338  THEN( "The read mesh is the same as the original one" ) {
339  CAPTURE( file );
340  CAPTURE( polymesh );
341  CAPTURE( readmesh );
342  REQUIRE( okw );
343  REQUIRE( okr );
344  REQUIRE( polymesh.Euler() == readmesh.Euler() );
345  REQUIRE( polymesh.nbVertices() == readmesh.nbVertices() );
346  REQUIRE( polymesh.nbEdges() == readmesh.nbEdges() );
347  REQUIRE( polymesh.nbFaces() == readmesh.nbFaces() );
348  REQUIRE( polymesh.neighborVertices( 0 ).size()
349  == readmesh.neighborVertices( 0 ).size() );
350  REQUIRE( polymesh.neighborVertices( 20 ).size()
351  == readmesh.neighborVertices( 20 ).size() );
352  REQUIRE( polymesh.vertexNormals().size() == readmesh.vertexNormals().size() );
353  }
354  }
355 }
356 
357 SCENARIO( "SurfaceMesh< RealPoint3 > boundary tests", "[surfmesh][boundary]" )
358 {
359  auto polymesh = makeNonManifoldBoundary();
360  auto polymesh2 = makeBox();
361  WHEN( "Checking the topology of the mesh boundary" ) {
362  auto chains = polymesh2.computeManifoldBoundaryChains();
363  THEN( "The box as a manifold boundary" ) {
364  CAPTURE(chains);
365  REQUIRE( polymesh2.isBoundariesManifold() == true);
366  REQUIRE( polymesh2.isBoundariesManifold(false) == true);
367  REQUIRE( chains.size() == 1);
368  REQUIRE( chains[0].size() == 6);
369  }
370  THEN( "The extra mesh does not have a manifold boundary" ) {
371  REQUIRE( polymesh.isBoundariesManifold() == false);
372  }
373  }
374 }
375 
376 SCENARIO( "SurfaceMesh< RealPoint3 > flippable tests", "[surfmesh][flip]" )
377 {
380  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
381  typedef PolygonMesh::Edge Edge;
382  typedef SurfaceMeshHelper< RealPoint, RealVector > PolygonMeshHelper;
383  typedef PolygonMeshHelper::NormalsType NormalsType;
384  auto meshBox = makeBox();
385  auto meshTetra = makeTetrahedron();
386  auto meshLantern = PolygonMeshHelper::makeLantern( 3.0, 3.0, RealPoint::zero,
387  10, 10, NormalsType::NO_NORMALS );
388  auto meshTorus = PolygonMeshHelper::makeTorus( 3.0, 1.0, RealPoint::zero,
389  10, 10, 0, NormalsType::NO_NORMALS );
390  WHEN( "Checking if one can flip box edges" ) {
391  auto nb_flippable = 0;
392  for ( Edge e = 0; e < meshBox.nbEdges(); e++ )
393  if ( meshBox.isFlippable( e ) ) nb_flippable++;
394  THEN( "No box edges are flippable (they border quads)" ) {
395  REQUIRE( nb_flippable == 0 );
396  }
397  }
398  WHEN( "Checking if one can flip tetrahedron edges" ) {
399  auto nb_flippable = 0;
400  for ( Edge e = 0; e < meshTetra.nbEdges(); e++ )
401  if ( meshTetra.isFlippable( e ) ) nb_flippable++;
402  THEN( "No tetrahedron edges are flippable (the neihgborhood is not simply connected)" ) {
403  REQUIRE( nb_flippable == 0 );
404  }
405  }
406  WHEN( "Checking if one can flip torus edges" ) {
407  Edge nb_flippable = 0;
408  for ( Edge e = 0; e < meshTorus.nbEdges(); e++ )
409  if ( meshTorus.isFlippable( e ) ) nb_flippable++;
410  THEN( "All torus edges are flippable (it is a closed triangulated surface)" ) {
411  REQUIRE( nb_flippable == meshTorus.nbEdges() );
412  }
413  }
414  WHEN( "Checking if one can flip lantern edges" ) {
415  auto bdry_edges = meshLantern.computeManifoldBoundaryEdges();
416  auto inner_edges = meshLantern.computeManifoldInnerEdges();
417  Edge nb_flippable = 0;
418  Edge nb_bdry_flippable = 0;
419  Edge nb_inner_flippable = 0;
420  for ( Edge e = 0; e < meshLantern.nbEdges(); e++ )
421  if ( meshLantern.isFlippable( e ) ) nb_flippable++;
422  for ( Edge e : bdry_edges )
423  if ( meshLantern.isFlippable( e ) ) nb_bdry_flippable++;
424  for ( Edge e : inner_edges )
425  if ( meshLantern.isFlippable( e ) ) nb_inner_flippable++;
426  THEN( "Innner lantern edges are flippable while boundary edges are not flippable" ) {
427  REQUIRE( nb_flippable == inner_edges.size() );
428  REQUIRE( nb_bdry_flippable == 0 );
429  REQUIRE( nb_flippable == nb_inner_flippable );
430  REQUIRE( nb_flippable == ( meshLantern.nbEdges() - bdry_edges.size() ) );
431  }
432  }
433 }
434 
435 SCENARIO( "SurfaceMesh< RealPoint3 > flip tests", "[surfmesh][flip]" )
436 {
439  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
440  typedef PolygonMesh::Edge Edge;
441  typedef SurfaceMeshHelper< RealPoint, RealVector > PolygonMeshHelper;
442  typedef PolygonMeshHelper::NormalsType NormalsType;
443  auto meshLantern = PolygonMeshHelper::makeLantern( 3.0, 3.0, RealPoint::zero,
444  10, 10, NormalsType::NO_NORMALS );
445  auto bdry_edges = meshLantern.computeManifoldBoundaryEdges();
446  auto euler = meshLantern.Euler();
447  auto nb_flipped = 0;
448  for ( auto i = 0; i < 100; i++ )
449  {
450  Edge e = rand() % meshLantern.nbEdges();
451  if ( meshLantern.isFlippable( e ) )
452  {
453  meshLantern.flip( e, false );
454  nb_flipped++;
455  }
456  }
457  WHEN( "Flipping 100 random edges" ) {
458  THEN( "More than 50 edges were flipped" ) {
459  REQUIRE( nb_flipped > 50 );
460  }
461  THEN( "Euler number is not changed" ) {
462  auto post_euler = meshLantern.Euler();
463  REQUIRE( euler == post_euler );
464  }
465  THEN( "Boundary is unchanged" ) {
466  auto post_bdry_edges = meshLantern.computeManifoldBoundaryEdges();
467  REQUIRE( bdry_edges.size() == post_bdry_edges.size() );
468  }
469  }
470 }
471 
472 SCENARIO( "SurfaceMesh< RealPoint3 > restore lantern with flips tests", "[surfmesh][flip]" )
473 {
476  typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
477  typedef PolygonMesh::Edge Edge;
478  typedef SurfaceMeshHelper< RealPoint, RealVector > PolygonMeshHelper;
479  typedef SurfaceMeshWriter< RealPoint, RealVector > PolygonMeshWriter;
480  typedef PolygonMeshHelper::NormalsType NormalsType;
481  auto meshLantern = PolygonMeshHelper::makeLantern( 3.0, 3.0, RealPoint::zero,
482  10, 10, NormalsType::NO_NORMALS );
483  {
484  std::ofstream output( "lantern.obj" );
485  PolygonMeshWriter::writeOBJ( output, meshLantern );
486  output.close();
487  }
488  Edge nb_flipped = 0;
489  const auto& X = meshLantern.positions();
490  for ( Edge e = 0; e < meshLantern.nbEdges(); e++ )
491  {
492  if ( meshLantern.isFlippable( e ) )
493  {
494  auto ij = meshLantern.edgeVertices ( e );
495  auto kl = meshLantern.otherDiagonal( e );
496  double l2_ij = ( X[ ij.first ] - X[ ij.second ] ).squaredNorm();
497  double l2_kl = ( X[ kl.first ] - X[ kl.second ] ).squaredNorm();
498  if ( l2_kl < l2_ij )
499  {
500  meshLantern.flip( e, false );
501  nb_flipped++;
502  }
503  }
504  }
505  {
506  std::ofstream output( "flipped-lantern.obj" );
507  PolygonMeshWriter::writeOBJ( output, meshLantern );
508  output.close();
509  }
510  WHEN( "Flipping all long edges" ) {
511  THEN( "80 edges were flipped" ) {
512  REQUIRE( nb_flipped == 80 );
513  }
514  }
515 }
516 
Aim: This class is useful to perform a breadth-first exploration of a graph given a starting point or...
const Node & current() const
Space::RealVector RealVector
SMesh::Vertices Vertices
DGtal is the top-level namespace which contains all DGtal functions and types.
Aim: An helper class for building classical meshes.
Aim: An helper class for reading mesh files (Wavefront OBJ at this point) and creating a SurfaceMesh.
Aim: An helper class for writing mesh file formats (Waverfront OBJ at this point) and creating a Surf...
Aim: Represents an embedded mesh as faces and a list of vertices. Vertices may be shared among faces ...
Definition: SurfaceMesh.h:92
Aim: Represents the concept of local graph: each vertex has neighboring vertices, but we do not neces...
CAPTURE(thicknessHV)
GIVEN("A cubical complex with random 3-cells")
HalfEdgeDataStructure::Edge Edge
REQUIRE(domain.isInside(aPoint))
SCENARIO("SurfaceMesh< RealPoint3 > concept check tests", "[surfmesh][concepts]")
SurfaceMesh< PointVector< 3, double >, PointVector< 3, double > > makeBox()
SurfaceMesh< PointVector< 3, double >, PointVector< 3, double > > makeTetrahedron()
SurfaceMesh< PointVector< 3, double >, PointVector< 3, double > > makeNonManifoldBoundary()
PointVector< 3, double > RealPoint
TriMesh::Vertex Vertex