DGtal 1.4.0
Loading...
Searching...
No Matches
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
47using namespace std;
48using namespace DGtal;
49
51// Functions for testing class SurfaceMesh.
53
54
55
58{
61 typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
62 typedef PolygonMesh::Vertices Vertices;
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;
92 typedef PolygonMesh::Vertices Vertices;
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;
113 typedef PolygonMesh::Vertices Vertices;
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
128SCENARIO( "SurfaceMesh< RealPoint3 > concept check tests", "[surfmesh][concepts]" )
129{
132 typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
133 BOOST_CONCEPT_ASSERT(( concepts::CUndirectedSimpleGraph< PolygonMesh > ));
134}
135
136SCENARIO( "SurfaceMesh< RealPoint3 > build tests", "[surfmesh][build]" )
137{
140 typedef SurfaceMesh< RealPoint, RealVector > PolygonMesh;
141 typedef PolygonMesh::Vertices Vertices;
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
255SCENARIO( "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
320SCENARIO( "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
357SCENARIO( "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
376SCENARIO( "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
435SCENARIO( "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
472SCENARIO( "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
Aim: Implements basic operations that will be used in Point and Vector classes.
SMesh::Vertices Vertices
DGtal is the top-level namespace which contains all DGtal functions and types.
STL namespace.
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))
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
SCENARIO("UnorderedSetByBlock< PointVector< 2, int > unit tests with 32 bits blocks", "[unorderedsetbyblock][2d]")