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"
48 using namespace DGtal;
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() );
85 SCENARIO(
"SurfaceMesh< RealPoint3 > concept check tests",
"[surfmesh][concepts]" )
93 SCENARIO(
"SurfaceMesh< RealPoint3 > build tests",
"[surfmesh][build]" )
98 typedef PolygonMesh::Vertices Vertices;
102 GIVEN(
"A box with an open side" ) {
103 PolygonMesh polymesh =
makeBox();
104 THEN(
"The mesh has 10 vertices, v0 has 3 neighbors, v1 has 3 neighbors, etc" ) {
105 REQUIRE( polymesh.size() == 10 );
106 REQUIRE( polymesh.degree( 0 ) == 3 );
107 REQUIRE( polymesh.degree( 1 ) == 3 );
108 REQUIRE( polymesh.degree( 2 ) == 3 );
109 REQUIRE( polymesh.degree( 3 ) == 3 );
110 REQUIRE( polymesh.degree( 4 ) == 4 );
111 REQUIRE( polymesh.degree( 5 ) == 4 );
112 REQUIRE( polymesh.degree( 6 ) == 3 );
113 REQUIRE( polymesh.degree( 7 ) == 3 );
114 REQUIRE( polymesh.degree( 8 ) == 2 );
115 REQUIRE( polymesh.degree( 9 ) == 2 );
117 THEN(
"Euler number is 1 as is the Euler number of a disk." )
119 REQUIRE( polymesh.nbVertices() == 10 );
120 REQUIRE( polymesh.nbEdges() == 15 );
121 REQUIRE( polymesh.nbFaces() == 6 );
122 REQUIRE( polymesh.Euler() == 1 );
124 THEN(
"Breadth-first visiting the mesh from vertex 0, visit {0}, then {1,2,4}, then {3,5,6,9}, then {7,8}." )
127 std::vector<unsigned long> vertices;
128 std::vector<unsigned long> distances;
131 vertices.push_back( visitor.
current().first );
132 distances.push_back( visitor.
current().second );
135 REQUIRE( vertices.size() == 10 );
136 REQUIRE( distances.size() == 10 );
137 int expected_vertices[] = { 0, 1, 2, 4, 3, 5, 6, 9, 7, 8 };
138 int expected_distance[] = { 0, 1, 1, 1, 2, 2, 2, 2, 3, 3 };
139 auto itb = vertices.begin();
140 std::sort( itb+1, itb+4 );
141 std::sort( itb+4, itb+8 );
142 std::sort( itb+8, itb+10 );
144 = std::equal( vertices.begin(), vertices.end(), expected_vertices );
147 = std::equal( distances.begin(), distances.end(), expected_distance );
150 THEN(
"The mesh has 6 boundary edges and 9 manifold inner consistent edges" ) {
151 auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
152 auto mani_inner = polymesh.computeManifoldInnerEdges();
153 auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
154 auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
155 auto non_mani = polymesh.computeNonManifoldEdges();
157 REQUIRE( mani_bdry.size() == 6 );
158 REQUIRE( mani_inner.size() == 9 );
159 REQUIRE( mani_inner_c.size() == 9 );
160 REQUIRE( mani_inner_u.size() == 0 );
161 REQUIRE( non_mani.size() == 0 );
163 THEN(
"The face along (1,3) is a quadrangle (1,3,7,5)" ) {
164 Edge e13 = polymesh.makeEdge( 1, 3 );
165 auto lfs = polymesh.edgeLeftFaces( e13 );
166 Vertices T = polymesh.incidentVertices( lfs[ 0 ] );
168 std::sort( T.begin(), T.end() );
174 THEN(
"The face along (3,1) is a quadrangle (3,1,0,2)" ) {
175 Edge e13 = polymesh.makeEdge( 1, 3 );
176 auto rfs = polymesh.edgeRightFaces( e13 );
177 Vertices T = polymesh.incidentVertices( rfs[ 0 ] );
179 std::sort( T.begin(), T.end() );
185 THEN(
"The lower part of the mesh has the barycenter (0.5, 0.5, 0.5) " ) {
186 auto positions = polymesh.positions();
188 for (
Vertex v = 0; v < 8; ++v )
195 THEN(
"We can iterate over the vertices" ) {
196 auto positions = polymesh.positions();
197 RealPoint exp_positions[] = { { 0,0,0 }, { 1,0,0 }, { 0,1,0 }, { 1,1,0 },
198 { 0,0,1 }, { 1,0,1 }, { 0,1,1 }, { 1,1,1 },
199 { 1,0,2 }, { 0,0,2 } };
200 for (
auto it = polymesh.begin(), itE = polymesh.end(); it != itE; ++it ) {
201 REQUIRE( positions[ *it ] == exp_positions[ *it ] );
208 SCENARIO(
"SurfaceMesh< RealPoint3 > mesh helper tests",
"[surfmesh][helper]" )
214 typedef PolygonMeshHelper::NormalsType NormalsType;
215 GIVEN(
"A sphere of radius 10" ) {
216 auto polymesh = PolygonMeshHelper::makeSphere( 3.0, RealPoint::zero,
217 10, 10, NormalsType::NO_NORMALS );
218 THEN(
"The mesh has Euler characteristic 2" ) {
219 REQUIRE( polymesh.Euler() == 2 );
221 THEN(
"It is a consistent manifold without boundary" ) {
222 auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
223 auto mani_inner = polymesh.computeManifoldInnerEdges();
224 auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
225 auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
226 auto non_mani = polymesh.computeNonManifoldEdges();
228 REQUIRE( mani_bdry.size() == 0 );
229 REQUIRE( mani_inner.size() == mani_inner_c.size() );
230 REQUIRE( mani_inner_u.size() == 0 );
231 REQUIRE( non_mani.size() == 0 );
234 GIVEN(
"A torus with radii 3 and 1" ) {
235 auto polymesh = PolygonMeshHelper::makeTorus( 3.0, 1.0, RealPoint::zero,
236 10, 10, 0, NormalsType::NO_NORMALS );
237 THEN(
"The mesh has Euler characteristic 0" ) {
238 REQUIRE( polymesh.Euler() == 0 );
240 THEN(
"It is a consistent manifold without boundary" ) {
241 auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
242 auto mani_inner = polymesh.computeManifoldInnerEdges();
243 auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
244 auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
245 auto non_mani = polymesh.computeNonManifoldEdges();
247 REQUIRE( mani_bdry.size() == 0 );
248 REQUIRE( mani_inner.size() == mani_inner_c.size() );
249 REQUIRE( mani_inner_u.size() == 0 );
250 REQUIRE( non_mani.size() == 0 );
253 GIVEN(
"A lantern with radii 3" ) {
254 auto polymesh = PolygonMeshHelper::makeLantern( 3.0, 3.0, RealPoint::zero,
255 10, 10, NormalsType::NO_NORMALS );
256 THEN(
"The mesh has Euler characteristic 0" ) {
257 REQUIRE( polymesh.Euler() == 0 );
259 THEN(
"It is a consistent manifold with boundary" ) {
260 auto mani_bdry = polymesh.computeManifoldBoundaryEdges();
261 auto mani_inner = polymesh.computeManifoldInnerEdges();
262 auto mani_inner_c = polymesh.computeManifoldInnerConsistentEdges();
263 auto mani_inner_u = polymesh.computeManifoldInnerUnconsistentEdges();
264 auto non_mani = polymesh.computeNonManifoldEdges();
266 REQUIRE( mani_bdry.size() == 20 );
267 REQUIRE( mani_inner.size() == mani_inner_c.size() );
268 REQUIRE( mani_inner_u.size() == 0 );
269 REQUIRE( non_mani.size() == 0 );
274 SCENARIO(
"SurfaceMesh< RealPoint3 > reader/writer tests",
"[surfmesh][io]" )
282 typedef PolygonMeshHelper::NormalsType NormalsType;
283 auto polymesh = PolygonMeshHelper::makeSphere( 3.0, RealPoint::zero,
284 10, 10, NormalsType::VERTEX_NORMALS );
285 WHEN(
"Writing the mesh as an OBJ file and reading into another mesh" ) {
286 PolygonMesh readmesh;
287 std::ostringstream output;
288 bool okw = PolygonMeshWriter::writeOBJ( output, polymesh );
289 std::string file = output.str();
290 std::istringstream input( file );
291 bool okr = PolygonMeshReader::readOBJ ( input, readmesh );
292 THEN(
"The read mesh is the same as the original one" ) {
298 REQUIRE( polymesh.Euler() == readmesh.Euler() );
299 REQUIRE( polymesh.nbVertices() == readmesh.nbVertices() );
300 REQUIRE( polymesh.nbEdges() == readmesh.nbEdges() );
301 REQUIRE( polymesh.nbFaces() == readmesh.nbFaces() );
302 REQUIRE( polymesh.neighborVertices( 0 ).size()
303 == readmesh.neighborVertices( 0 ).size() );
304 REQUIRE( polymesh.neighborVertices( 20 ).size()
305 == readmesh.neighborVertices( 20 ).size() );
306 REQUIRE( polymesh.vertexNormals().size() == readmesh.vertexNormals().size() );
Aim: This class is useful to perform a breadth-first exploration of a graph given a starting point or...
const Node & current() const
DGtal is the top-level namespace which contains all DGtal functions and types.
Z3i::RealVector RealVector
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 ...
Aim: Represents the concept of local graph: each vertex has neighboring vertices, but we do not neces...
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()