Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
topomesh.cpp
1#include <Core/Geometry/MeshPrimitives.hpp>
2#include <Core/Geometry/StandardAttribNames.hpp>
3#include <Core/Geometry/TopologicalMesh.hpp>
4#include <Core/Geometry/TriangleMesh.hpp>
5#include <catch2/catch_test_macros.hpp>
6
7#include <OpenMesh/Tools/Subdivider/Uniform/CatmullClarkT.hh>
8
9using namespace Ra::Core;
10using namespace Ra::Core::Utils;
11using namespace Ra::Core::Geometry;
12
13bool isSameMesh( const Ra::Core::Geometry::TriangleMesh& meshOne,
14 const Ra::Core::Geometry::TriangleMesh& meshTwo,
15 bool expected = true ) {
16
17 bool result = true;
18 int i = 0;
19 // Check length
20 if ( meshOne.vertices().size() != meshTwo.vertices().size() ) {
21 if ( expected ) {
22 LOG( logINFO ) << "isSameMesh failed vertices.size()" << meshOne.vertices().size()
23 << " " << meshTwo.vertices().size();
24 }
25 return false;
26 }
27 if ( meshOne.normals().size() != meshTwo.normals().size() ) {
28 if ( expected ) { LOG( logINFO ) << "isSameMesh failed normals.size()"; }
29 return false;
30 }
31
32 if ( meshOne.getIndices().size() != meshTwo.getIndices().size() ) {
33 if ( expected ) { LOG( logINFO ) << "isSameMesh failed getIndices().size()"; }
34 return false;
35 }
36
37 // Check triangles
38 std::vector<Vector3> stackVertices;
39 std::vector<Vector3> stackNormals;
40
41 bool hasNormals = meshOne.normals().size() > 0;
42
43 i = 0;
44 while ( result && i < int( meshOne.getIndices().size() ) ) {
45 std::vector<Ra::Core::Vector3>::iterator it;
46 stackVertices.clear();
47 stackVertices.push_back( meshOne.vertices()[meshOne.getIndices()[i][0]] );
48 stackVertices.push_back( meshOne.vertices()[meshOne.getIndices()[i][1]] );
49 stackVertices.push_back( meshOne.vertices()[meshOne.getIndices()[i][2]] );
50
51 if ( hasNormals ) {
52 stackNormals.clear();
53 stackNormals.push_back( meshOne.normals()[meshOne.getIndices()[i][0]] );
54 stackNormals.push_back( meshOne.normals()[meshOne.getIndices()[i][1]] );
55 stackNormals.push_back( meshOne.normals()[meshOne.getIndices()[i][2]] );
56 }
57 for ( int j = 0; j < 3; ++j ) {
58 it = find( stackVertices.begin(),
59 stackVertices.end(),
60 meshTwo.vertices()[meshTwo.getIndices()[i][j]] );
61 if ( it != stackVertices.end() ) { stackVertices.erase( it ); }
62 else {
63
64 if ( expected ) { LOG( logINFO ) << "isSameMesh failed face not found"; }
65
66 result = false;
67 }
68 }
69
70 if ( hasNormals ) {
71 for ( int j = 0; j < 3; ++j ) {
72 it = find( stackNormals.begin(),
73 stackNormals.end(),
74 meshTwo.normals()[meshTwo.getIndices()[i][j]] );
75 if ( it != stackNormals.end() ) { stackNormals.erase( it ); }
76 else {
77
78 if ( expected ) { LOG( logINFO ) << "isSameMesh failed normal not found"; }
79
80 result = false;
81 }
82 }
83 }
84 ++i;
85 }
86 return result;
87}
88
89class WedgeDataAndIdx
90{
91 public:
93
94 size_t m_idx;
95
96 bool operator<( const WedgeDataAndIdx& lhs ) const { return m_data < lhs.m_data; }
97 bool operator==( const WedgeDataAndIdx& lhs ) const { return !( m_data != lhs.m_data ); }
98 bool operator!=( const WedgeDataAndIdx& lhs ) const { return !( *this == lhs ); }
99};
100
101#define COPY_TO_WEDGES_VECTOR_HELPER( UPTYPE, REALTYPE ) \
102 if ( attr->is##UPTYPE() ) { \
103 auto data = \
104 meshOne.getAttrib( meshOne.template getAttribHandle<REALTYPE>( attr->getName() ) ) \
105 .data(); \
106 for ( size_t i = 0; i < size; ++i ) { \
107 wedgesMeshOne[i].m_data.getAttribArray<REALTYPE>().push_back( data[i] ); \
108 } \
109 }
110
111template <typename T>
112void copyToWedgesVector( size_t size,
113 const IndexedGeometry<T>& meshOne,
115 AttribBase* attr ) {
116
117 if ( attr->getSize() != meshOne.vertices().size() ) {
118 LOG( logWARNING ) << "[TopologicalMesh test] Skip badly sized attribute "
119 << attr->getName();
120 }
121 else if ( attr->getName() != getAttribName( VERTEX_POSITION ) ) {
122 {
123 auto data = meshOne.vertices();
124 for ( size_t i = 0; i < size; ++i ) {
125 wedgesMeshOne[i].m_data.m_position = data[i];
126 }
127 }
128
129 COPY_TO_WEDGES_VECTOR_HELPER( Float, Scalar );
130 COPY_TO_WEDGES_VECTOR_HELPER( Vector2, Vector2 );
131 COPY_TO_WEDGES_VECTOR_HELPER( Vector3, Vector3 );
132 COPY_TO_WEDGES_VECTOR_HELPER( Vector4, Vector4 );
133 }
134}
135#undef COPY_TO_WEDGES_VECTOR_HELPER
136
137template <typename T>
138bool isSameMeshWedge( const Ra::Core::Geometry::IndexedGeometry<T>& meshOne,
140
141 using namespace Ra::Core;
142 using namespace Ra::Core::Geometry;
143
144 // Check length
145 // LOG( logDEBUG ) << meshOne.vertices().size() << " / " << meshTwo.vertices().size();
146 // LOG( logDEBUG ) << meshOne.normals().size() << " / " << meshTwo.normals().size();
147 // LOG( logDEBUG ) << meshOne.getIndices().size() << " / " << meshTwo.getIndices().size();
148
149 if ( meshOne.vertices().size() != meshTwo.vertices().size() ) return false;
150 if ( meshOne.normals().size() != meshTwo.normals().size() ) return false;
151 if ( meshOne.getIndices().size() != meshTwo.getIndices().size() ) return false;
152
155
156 auto size = meshOne.vertices().size();
157 for ( size_t i = 0; i < size; ++i ) {
158 WedgeDataAndIdx wd;
159 wd.m_idx = i;
160 wedgesMeshOne.push_back( wd );
161 wedgesMeshTwo.push_back( wd );
162 }
163 using namespace std::placeholders;
164 auto f1 = std::bind(
165 copyToWedgesVector<T>, size, std::cref( meshOne ), std::ref( wedgesMeshOne ), _1 );
166 meshOne.vertexAttribs().for_each_attrib( f1 );
167
168 auto f2 = std::bind(
169 copyToWedgesVector<T>, size, std::cref( meshTwo ), std::ref( wedgesMeshTwo ), _1 );
170 meshTwo.vertexAttribs().for_each_attrib( f2 );
171
172 std::sort( wedgesMeshOne.begin(), wedgesMeshOne.end() );
173 std::sort( wedgesMeshTwo.begin(), wedgesMeshTwo.end() );
174
175 if ( wedgesMeshOne != wedgesMeshTwo ) {
176 // LOG( logDEBUG ) << "not same wedges";
177 return false;
178 }
179
180 std::vector<int> newMeshOneIdx( wedgesMeshOne.size() );
181 std::vector<int> newMeshTwoIdx( wedgesMeshOne.size() );
182
183 size_t curIdx = 0;
184
185 newMeshOneIdx[wedgesMeshOne[0].m_idx] = 0;
186 newMeshTwoIdx[wedgesMeshTwo[0].m_idx] = 0;
187
188 for ( size_t i = 1; i < wedgesMeshOne.size(); ++i ) {
189 if ( wedgesMeshOne[i] != wedgesMeshOne[i - 1] ) ++curIdx;
190 newMeshOneIdx[wedgesMeshOne[i].m_idx] = curIdx;
191 // std::cout << wedgesMeshOne[i].m_idx << " : " << curIdx << "\n";
192 }
193 // std::cout << "***\n";
194 curIdx = 0;
195 for ( size_t i = 1; i < wedgesMeshTwo.size(); ++i ) {
196 if ( wedgesMeshTwo[i] != wedgesMeshTwo[i - 1] ) ++curIdx;
197 newMeshTwoIdx[wedgesMeshTwo[i].m_idx] = curIdx;
198 // std::cout << wedgesMeshTwo[i].m_idx << " : " << curIdx << "\n";
199 }
200
202 meshOne.getIndices();
204 meshTwo.getIndices();
205
206 for ( auto& face : indices1 ) {
207 // std::cout << "face ";
208 for ( int i = 0; i < face.size(); ++i ) {
209 face( i ) = newMeshOneIdx[face( i )];
210 // std::cout << face( i ) << " ";
211 }
212 // std::cout << "\n";
213 }
214 // std::cout << "***\n";
215 for ( auto& face : indices2 ) {
216
217 // std::cout << "face ";
218 for ( int i = 0; i < face.size(); ++i ) {
219 face( i ) = newMeshTwoIdx[face( i )];
220 // std::cout << face( i ) << " ";
221 }
222 // std::cout << "\n";
223 }
224 if ( indices1 != indices2 ) {
225 // LOG( logDEBUG ) << "not same indices";
226 return false;
227 }
228 return true;
229}
230
231TEST_CASE( "Core/Geometry/TopologicalMesh", "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
232 using Ra::Core::Vector3;
234 using Ra::Core::Geometry::TriangleMesh;
235
236 auto testConverter = []( const TriangleMesh& mesh ) {
237 auto topologicalMesh = TopologicalMesh( mesh );
238 auto newMesh = topologicalMesh.toTriangleMesh();
239 REQUIRE( isSameMesh( mesh, newMesh ) );
240 REQUIRE( topologicalMesh.checkIntegrity() );
241 };
242
243 SECTION( "Closed mesh" ) {
244 testConverter( Ra::Core::Geometry::makeBox() );
245 testConverter( Ra::Core::Geometry::makeSharpBox() );
246 }
247
248 SECTION( "Mesh with boundaries" ) {
249 testConverter( Ra::Core::Geometry::makePlaneGrid( 2, 2 ) );
250 }
251
252 SECTION( "With user def attribs" ) {
253 using Vector5 = Eigen::Matrix<Scalar, 5, 1>;
254 VectorArray<Vector5> array5 { { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
255 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
256 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
257 { 0_ra, -1_ra, 0_ra, 0_ra, 0_ra },
258 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
259 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
260 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
261 { 0_ra, -1_ra, 0_ra, 0_ra, 0_ra } };
262 VectorArray<Vector4> array4 { { 0_ra, 0_ra, 0_ra, 1_ra },
263 { 0_ra, 0_ra, 0_ra, 1_ra },
264 { 0_ra, 0_ra, 0_ra, 1_ra },
265 { -1_ra, 0_ra, 0_ra, 0_ra },
266 { 0_ra, 0_ra, 0_ra, 1_ra },
267 { 0_ra, 0_ra, 0_ra, 1_ra },
268 { 0_ra, 0_ra, 0_ra, 1_ra },
269 { -1_ra, 0_ra, 0_ra, 0_ra } };
270 VectorArray<Vector2> array2 { { 0_ra, 1_ra },
271 { 0_ra, 1_ra },
272 { 0_ra, 1_ra },
273 { 0_ra, 0_ra },
274 { 0_ra, 1_ra },
275 { 0_ra, 1_ra },
276 { 0_ra, 1_ra },
277 { 0_ra, 0_ra } };
278
279 auto mesh = Ra::Core::Geometry::makeBox();
280 auto handle2 = mesh.addAttrib<Vector2>( "vector2_attrib" );
281 auto handle4 = mesh.addAttrib<Vector4>( "vector4_attrib" );
282 auto handle5 = mesh.addAttrib<Vector5>( "vector5_attrib" );
283 auto ehandle2 = mesh.addAttrib<Vector2>( "evector2_attrib" );
284 auto ehandle4 = mesh.addAttrib<Vector4>( "evector4_attrib" );
285 auto ehandle5 = mesh.addAttrib<Vector5>( "evector5_attrib" );
286
287 auto& attrib2 = mesh.getAttrib( handle2 );
288 auto& attrib4 = mesh.getAttrib( handle4 );
289 auto& attrib5 = mesh.getAttrib( handle5 );
290 auto& buf2 = attrib2.getDataWithLock();
291 auto& buf4 = attrib4.getDataWithLock();
292 auto& buf5 = attrib5.getDataWithLock();
293 buf2 = array2;
294 buf4 = array4;
295 buf5 = array5;
296 attrib2.unlock();
297 attrib4.unlock();
298 attrib5.unlock();
299
300 auto topologicalMesh = TopologicalMesh( mesh );
301 auto newMesh = topologicalMesh.toTriangleMesh();
302 REQUIRE( isSameMesh( mesh, newMesh ) );
303 REQUIRE( topologicalMesh.checkIntegrity() );
304
305 // oversize attrib not suported
306 REQUIRE( !newMesh.hasAttrib( "vector5_attrib" ) );
307
308 REQUIRE( newMesh.hasAttrib( "vector2_attrib" ) );
309
310 REQUIRE( newMesh.hasAttrib( "vector4_attrib" ) );
311
312 // empty attrib not converted
313 REQUIRE( !newMesh.hasAttrib( "evector2_attrib" ) );
314 REQUIRE( !newMesh.hasAttrib( "evector4_attrib" ) );
315 REQUIRE( !newMesh.hasAttrib( "evector5_attrib" ) );
316 }
317
318 SECTION( "Edit topo mesh" ) {
319 auto mesh = Ra::Core::Geometry::makeCylinder( Vector3( 0, 0, 0 ), Vector3( 0, 0, 1 ), 1 );
320
321 auto topologicalMesh = TopologicalMesh( mesh );
322 auto newMesh = topologicalMesh.toTriangleMesh();
323 topologicalMesh.setWedgeData(
324 TopologicalMesh::WedgeIndex { 0 }, getAttribName( VERTEX_NORMAL ), Vector3( 0, 0, 0 ) );
325 auto newMeshModified = topologicalMesh.toTriangleMesh();
326
327 REQUIRE( isSameMesh( mesh, newMesh ) );
328 REQUIRE( isSameMeshWedge( mesh, newMesh ) );
329 REQUIRE( !isSameMeshWedge( mesh, newMeshModified ) );
330 REQUIRE( topologicalMesh.checkIntegrity() );
331 }
332
333 SECTION( "Test skip empty attributes" ) {
334 auto mesh = Ra::Core::Geometry::makeCylinder( Vector3( 0, 0, 0 ), Vector3( 0, 0, 1 ), 1 );
335 mesh.addAttrib<Scalar>( "empty" );
336 auto topologicalMesh = TopologicalMesh( mesh );
337 auto newMesh = topologicalMesh.toTriangleMesh();
338 REQUIRE( !newMesh.hasAttrib( "empty" ) );
339 REQUIRE( topologicalMesh.checkIntegrity() );
340 }
341
342 SECTION( "Test normals" ) {
343 auto mesh = Ra::Core::Geometry::makeBox();
344 auto topologicalMesh = TopologicalMesh( mesh );
345
346 for ( TopologicalMesh::ConstVertexIter v_it = topologicalMesh.vertices_begin();
347 v_it != topologicalMesh.vertices_end();
348 ++v_it ) {
349 topologicalMesh.set_normal(
350 *v_it, TopologicalMesh::Normal( Scalar( 1. ), Scalar( 0. ), Scalar( 0. ) ) );
351 }
352
353 for ( TopologicalMesh::ConstVertexIter v_it = topologicalMesh.vertices_begin();
354 v_it != topologicalMesh.vertices_end();
355 ++v_it ) {
356 topologicalMesh.propagate_normal_to_wedges( *v_it );
357 }
358
359 auto newMesh = topologicalMesh.toTriangleMesh();
360 bool check1 = true;
361 bool check2 = true;
362 for ( auto n : newMesh.normals() ) {
364 n.dot( Vector3( Scalar( 1. ), Scalar( 0. ), Scalar( 0. ) ) ),
365 Scalar( 1. ) ) ) {
366 check1 = false;
367 }
368 if ( n.dot( Vector3( Scalar( 0.5 ), Scalar( 0. ), Scalar( 0. ) ) ) > Scalar( 0.8 ) ) {
369 check2 = false;
370 }
371 }
372 REQUIRE( check1 );
373 REQUIRE( check2 );
374 REQUIRE( topologicalMesh.checkIntegrity() );
375 }
376
377 SECTION( "Test without normals" ) {
378 VectorArray<Vector3> vertices = { { 0_ra, 0_ra, 0_ra },
379 { 0_ra, 1_ra, 0_ra },
380 { 1_ra, 1_ra, 0_ra },
381 { 1_ra, 0_ra, 0_ra } };
382 VectorArray<Vector3ui> indices { { 0, 2, 1 }, { 0, 3, 2 } };
383 // well formed mesh
384
385 TriangleMesh mesh;
386 mesh.setVertices( std::move( vertices ) );
387 mesh.setIndices( std::move( indices ) );
388 TopologicalMesh topo1 { mesh };
389 REQUIRE( topo1.checkIntegrity() );
390 TriangleMesh mesh1 = topo1.toTriangleMesh();
391
392 // there is no normals at all.
393 REQUIRE( !topo1.has_halfedge_normals() );
394 REQUIRE( !topo1.has_face_normals() );
395 for ( auto vitr = topo1.vertices_begin(), vend = topo1.vertices_end(); vitr != vend;
396 ++vitr ) {
397 topo1.propagate_normal_to_wedges( *vitr );
398 REQUIRE( !topo1.has_halfedge_normals() );
399 REQUIRE( !topo1.has_face_normals() );
400 }
401
402 // nor on faces nor if we try to create them
403 REQUIRE( !topo1.has_face_normals() );
404 OpenMesh::FPropHandleT<TopologicalMesh::Normal> fProp;
405
406 REQUIRE( mesh.vertexAttribs().hasSameAttribs( mesh1.vertexAttribs() ) );
407 REQUIRE( isSameMesh( mesh, mesh1 ) );
408
409 REQUIRE( mesh1.normals().size() == 0 );
410 }
411}
412
413void test_split( TopologicalMesh& topo, TopologicalMesh::EdgeHandle eh, Scalar f ) {
414
415 auto he0 = topo.halfedge_handle( eh, 0 );
416 auto he1 = topo.halfedge_handle( eh, 1 );
417 auto v0 = topo.from_vertex_handle( he0 ); // i.e. to_vertex_handle(he1)
418 REQUIRE( v0 == topo.to_vertex_handle( he1 ) );
419 auto v1 = topo.to_vertex_handle( he0 );
420 auto p0 = topo.point( v0 );
421 Scalar f0 = topo.getWedgeData( *( topo.getVertexWedges( v0 ) ).begin() ).m_floatAttrib[0];
422 auto p1 = topo.point( v1 );
423 Scalar f1 = topo.getWedgeData( *( topo.getVertexWedges( v1 ) ).begin() ).m_floatAttrib[0];
424 topo.splitEdge( eh, f );
425
426 // check validity
427 REQUIRE( topo.is_valid_handle( he0 ) );
428 REQUIRE( topo.is_valid_handle( he1 ) );
429
430 // he0 is untouched
431 REQUIRE( v1 == topo.to_vertex_handle( he0 ) );
432 REQUIRE( Math::areApproxEqual( ( p1 - topo.point( v1 ) ).squaredNorm(), 0_ra ) );
433
434 // he1 point to inserted vertex
435 auto vsplit = topo.to_vertex_handle( he1 ); // i.e. from_vertex_handle(he0)
436 REQUIRE( vsplit == topo.from_vertex_handle( he0 ) );
437
438 auto psplit = topo.point( vsplit );
439 auto vcheck = ( f * p1 + ( 1_ra - f ) * p0 );
440 REQUIRE( Math::areApproxEqual( ( psplit - vcheck ).squaredNorm(), 0_ra ) );
441
442 auto wedges = topo.getVertexWedges( vsplit );
443 REQUIRE( wedges.size() == 1 );
444
445 auto wd = topo.getWedgeData( *wedges.begin() );
446 auto fsplit = wd.m_floatAttrib[0];
447 auto fcheck = ( f * f1 + ( 1_ra - f ) * f0 );
448 REQUIRE( Math::areApproxEqual( fsplit, fcheck ) );
449 REQUIRE( Math::areApproxEqual( ( psplit - wd.m_position ).squaredNorm(), 0_ra ) );
450}
451
452void test_poly() {
453 Ra::Core::Geometry::PolyMesh polyMesh;
454 polyMesh.setVertices( {
455 // quad
456 { -1.1_ra, -0_ra, 0_ra },
457 { 1.1_ra, -0_ra, 0_ra },
458 { 1_ra, 1_ra, 0_ra },
459 { -1_ra, 1_ra, 0_ra },
460 // hepta
461 { 2_ra, 2_ra, 0_ra },
462 { 2_ra, 3_ra, 0_ra },
463 { 0_ra, 4_ra, 0_ra },
464 { -2_ra, 3_ra, 0_ra },
465 { -2_ra, 2_ra, 0_ra },
466 // degen
467 { -1.1_ra, -2_ra, 0_ra },
468 { -0.5_ra, -2_ra, 0_ra },
469 { -0.3_ra, -2_ra, 0_ra },
470 { 0.0_ra, -2_ra, 0_ra },
471 { 0.001_ra, -2_ra, 0_ra },
472 { 0.3_ra, -2_ra, 0_ra },
473 { 0.5_ra, -2_ra, 0_ra },
474 { 1.1_ra, -2_ra, 0_ra },
475 // degen2
476 { -1_ra, -3_ra, 0_ra },
477 { 1_ra, -3_ra, 0_ra },
478
479 } );
480
481 Vector3Array normals;
482 normals.resize( polyMesh.vertices().size() );
484 polyMesh.vertices().cbegin(),
485 polyMesh.vertices().cend(),
486 normals.begin(),
487 []( const Vector3& v ) { return ( v + Vector3( 0_ra, 0_ra, 1_ra ) ).normalized(); } );
488 polyMesh.setNormals( normals );
489
490 auto quad = VectorNui( 4 );
491 quad << 0, 1, 2, 3;
492 auto hepta = VectorNui( 7 );
493 hepta << 3, 2, 4, 5, 6, 7, 8;
494 auto degen = VectorNui( 10 );
495 degen << 1, 0, 9, 10, 11, 12, 13, 14, 15, 16;
496 auto degen2 = VectorNui( 10 );
497 degen2 << 14, 13, 12, 11, 10, 9, 17, 18, 16, 15;
498 polyMesh.setIndices( { quad, hepta, degen, degen2 } );
499
500 TopologicalMesh topologicalMesh;
501 topologicalMesh.initWithWedge( polyMesh, polyMesh.getLayerKey() );
502 auto newMesh = topologicalMesh.toPolyMesh();
503 REQUIRE( isSameMeshWedge( newMesh, polyMesh ) );
504}
505
506TEST_CASE( "Core/Geometry/TopologicalMesh/PolyMesh",
507 "[unittests][Core][Core/Geometry][TopologicalMesh][PolyMesh]" ) {
508
509 test_poly();
510}
511
514// using Catmull =
515// OpenMesh::Subdivider::Uniform::CatmullClarkT<Ra::Core::Geometry::TopologicalMesh>;
516// using Loop = OpenMesh::Subdivider::Uniform::LoopT<Ra::Core::Geometry::TopologicalMesh>;
517// using Decimater = OpenMesh::Decimater::DecimaterT<Ra::Core::Geometry::TopologicalMesh>;
518// using HModQuadric =
519// OpenMesh::Decimater::ModQuadricT<Ra::Core::Geometry::TopologicalMesh>::Handle;
520//}
521
522TEST_CASE( "Core/Geometry/TopologicalMesh/EdgeSplit",
523 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
524 using Ra::Core::Vector3;
526 using Ra::Core::Geometry::TriangleMesh;
527
528 // create a triangle mesh with 4 vertices
529 TriangleMesh meshSplit;
530 meshSplit.setVertices( { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 } } );
531 meshSplit.setNormals( { { -1, -1, 1 }, { 1, -1, 1 }, { 1, 1, 1 }, { -1, 1, 1 } } );
532 meshSplit.setIndices( { Vector3ui( 0, 1, 2 ), Vector3ui( 0, 2, 3 ) } );
533 // add a float attrib
534 auto handle = meshSplit.addAttrib<Scalar>( "test", { 0_ra, 1_ra, 2_ra, 3_ra } );
535 CORE_UNUSED( handle ); // until unit test is finished.
536
537 // convert to topomesh
538 TopologicalMesh topo = TopologicalMesh( meshSplit );
539
540 // split middle edge
541 TopologicalMesh::EdgeHandle eh;
542 // iterate over all to find the inner one
543 int innerEdgeCount = 0;
544 for ( TopologicalMesh::EdgeIter e_it = topo.edges_begin(); e_it != topo.edges_end(); ++e_it ) {
545 if ( !topo.is_boundary( *e_it ) ) {
546 eh = *e_it;
547 ++innerEdgeCount;
548 }
549 }
550
551 REQUIRE( innerEdgeCount == 1 );
552 Scalar f = .3_ra;
553
554 test_split( topo, eh, f );
556}
557
558TEST_CASE( "Core/Geometry/TopologicalMesh/Manifold",
559 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
560 SECTION( "Non manifold faces" ) {
561 struct MyNonManifoldCommand {
562 explicit inline MyNonManifoldCommand( int target ) : targetNonManifoldFaces( target ) {}
563 inline void initialize( const MultiIndexedGeometry& ) {}
564 inline void process( const std::vector<TopologicalMesh::VertexHandle>& ) {
565 LOG( logINFO ) << "Non Manifold face found";
566 nonManifoldFaces++;
567 }
568 inline void postProcess( TopologicalMesh& ) {
569 REQUIRE( nonManifoldFaces == targetNonManifoldFaces );
570 LOG( logINFO ) << "Process non-manifold faces";
571 }
572
573 int nonManifoldFaces { 0 };
574 const int targetNonManifoldFaces;
575 };
576
577 auto buildMesh = []( const VectorArray<Vector3>& v,
578 const VectorArray<Vector3>& n,
579 const VectorArray<Vector3ui>& i ) {
580 TriangleMesh m;
581 m.setVertices( v );
582 m.setNormals( n );
583 auto& idx = m.getIndicesWithLock();
584 std::copy( i.begin(), i.end(), std::back_inserter( idx ) );
585 m.indicesUnlock();
586
587 LOG( logINFO ) << " Built a mesh with " << m.vertices().size() << " vertices, "
588 << m.normals().size() << " normals and " << m.getIndices().size()
589 << " indices.";
590
591 return m;
592 };
593
594 // test if candidateMesh -> TopologicalMesh -> TriangleMesh isSameMesh than referenceMesh,
595 // with and without the command.
596 auto testConverter = []( const TriangleMesh& referenceMesh,
597 const TriangleMesh& candidateMesh,
598 MyNonManifoldCommand command ) {
599 // test with functor
600 TopologicalMesh topoWithCommand { candidateMesh, command };
601 auto convertedMeshWithCommand = topoWithCommand.toTriangleMesh();
602 REQUIRE( isSameMesh( referenceMesh, convertedMeshWithCommand ) );
603 // test without functor
604 TopologicalMesh topoWithoutCommand { candidateMesh };
605 auto convertedMeshWithoutCommand = topoWithoutCommand.toTriangleMesh();
606 REQUIRE( isSameMesh( referenceMesh, convertedMeshWithoutCommand ) );
607 return convertedMeshWithoutCommand;
608 };
609
610 VectorArray<Vector3> vertices = { { 0_ra, 0_ra, 0_ra },
611 { 0_ra, 1_ra, 0_ra },
612 { 1_ra, 1_ra, 0_ra },
613 { 1_ra, 0_ra, 0_ra } };
614 VectorArray<Vector3> normals { { 0_ra, 0_ra, 1_ra },
615 { 0_ra, 0_ra, 1_ra },
616 { 0_ra, 0_ra, 1_ra },
617 { 0_ra, 0_ra, 1_ra } };
618 VectorArray<Vector3ui> indices { { 0, 2, 1 }, { 0, 3, 2 } };
619
620 VectorArray<Vector3> vertices_2 = { { 0_ra, 0_ra, 0_ra },
621 { 0_ra, 1_ra, 0_ra },
622 { 1_ra, 1_ra, 0_ra },
623 { 1_ra, 0_ra, 0_ra },
624 { 1_ra, 0_ra, 1_ra } };
625 VectorArray<Vector3> normals_2 {
626 { 0_ra, 0_ra, 1_ra },
627 { 0_ra, 0_ra, 1_ra },
628 { 0_ra, 0_ra, 1_ra },
629 { 0_ra, 0_ra, 1_ra },
630 { 0_ra, -1_ra, 0_ra },
631 };
632
633 VectorArray<Vector3ui> indices_2 { { 0, 2, 1 }, { 0, 3, 2 }, { 0, 2, 4 } };
634
635 using Vector5 = Eigen::Matrix<Scalar, 5, 1>;
636 VectorArray<Vector5> attrib_array {
637 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
638 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
639 { 0_ra, 0_ra, 0_ra, 0_ra, 1_ra },
640 { 0_ra, -1_ra, 0_ra, 0_ra, 0_ra },
641 };
642
643 // well formed mesh
644 auto mesh = buildMesh( vertices, normals, indices );
645
646 // edge shared by three faces
647 LOG( logINFO ) << "Test with edge shared by three faces";
648 auto mesh2 = buildMesh( vertices_2, normals_2, indices_2 );
649
650 testConverter(
651 mesh, mesh2, MyNonManifoldCommand( 1 ) ); // we should find 1 non-manifold face
652
653 // test with unsupported attribute type
654 LOG( logINFO ) << "Test with unsupported attribute (all faces are manifold)";
655 auto mesh3 { mesh }, mesh4 { mesh };
656 auto handle = mesh3.addAttrib<Vector5>( "vector5_attrib" );
657 auto& attrib = mesh3.getAttrib( handle );
658 auto& buf = attrib.getDataWithLock();
659 buf = attrib_array;
660 attrib.unlock();
661
662 REQUIRE( mesh4.vertexAttribs().hasSameAttribs( mesh.vertexAttribs() ) );
663 REQUIRE( mesh.vertexAttribs().hasSameAttribs( mesh4.vertexAttribs() ) );
664 REQUIRE( !mesh4.vertexAttribs().hasSameAttribs( mesh3.vertexAttribs() ) );
665 REQUIRE( !mesh3.vertexAttribs().hasSameAttribs( mesh4.vertexAttribs() ) );
666 mesh4 = testConverter(
667 mesh, mesh3, MyNonManifoldCommand( 0 ) ); // we should find 0 non-manifold face
668 REQUIRE( mesh4.vertexAttribs().hasSameAttribs( mesh.vertexAttribs() ) );
669 REQUIRE( mesh.vertexAttribs().hasSameAttribs( mesh4.vertexAttribs() ) );
670 REQUIRE( !mesh4.vertexAttribs().hasSameAttribs( mesh3.vertexAttribs() ) );
671 REQUIRE( !mesh3.vertexAttribs().hasSameAttribs( mesh4.vertexAttribs() ) );
672
673 // TODO : build a functor that add the faces as independant faces in the topomesh and
674 // define a manifold mesh that is similar to the result of processing of this non manifold.
675 //
676 }
677 SECTION( "Non manifold vertex : Bow tie" ) {
678 VectorArray<Vector3> vertices = {
679 { -1_ra, -1_ra, 0_ra },
680 { -1_ra, 1_ra, 0_ra },
681 { 0_ra, 0_ra, 0_ra }, // non manifold vertex
682 { 1_ra, -1_ra, 0_ra },
683 { 1_ra, 1_ra, 0_ra },
684 };
685
686 VectorArray<Vector3ui> indices { { 0, 2, 1 }, { 2, 3, 4 } };
687 TriangleMesh mesh;
688 // do not move vertices, we need to compare afterward
689 mesh.setVertices( vertices );
690 mesh.setIndices( std::move( indices ) );
691
692 TopologicalMesh topo { mesh };
693
694 for ( auto itr = topo.vertices_begin(); itr != topo.vertices_end(); ++itr ) {
695 if ( Ra::Core::Math::areApproxEqual( ( topo.point( *itr ) - vertices[2] ).squaredNorm(),
696 0_ra ) ) {
697 REQUIRE( !topo.isManifold( *itr ) );
698 }
699 else { REQUIRE( topo.isManifold( *itr ) ); }
700 }
701 }
702 SECTION( "Non manifold vertex : Double pyramid" ) {
703
704 struct MyNonManifoldCommand {
705 explicit inline MyNonManifoldCommand(
707 m_faulty( faulty ) {}
708 inline void initialize( const MultiIndexedGeometry& ) {}
709 inline void process( const std::vector<TopologicalMesh::VertexHandle>& face_vhandles ) {
710 m_faulty.push_back( face_vhandles );
711 nonManifoldFaces++;
712 }
713 inline void postProcess( TopologicalMesh& ) {}
715 int nonManifoldFaces { 0 };
716 };
717
718 VectorArray<Vector3> vertices = { { 0_ra, 1_ra, 1_ra },
719 { 1_ra, 1_ra, 1_ra },
720 { 0.5_ra, 1_ra, 0_ra },
721 { 0.5_ra, 0.5_ra, 0.5_ra }, // non manifold vertex
722 { 0_ra, 0_ra, 0_ra },
723 { 1_ra, 0_ra, 0_ra },
724 { 0.5_ra, 0_ra, 1_ra } };
725 VectorArray<Vector3ui> indices { { 0, 1, 2 },
726 { 2, 1, 3 },
727 { 1, 0, 3 },
728 { 0, 2, 3 },
729 { 4, 5, 6 },
730 { 5, 4, 3 },
731 { 4, 6, 3 },
732 { 6, 5, 3 } };
733
734 TriangleMesh mesh;
735 mesh.setVertices( std::move( vertices ) );
736 mesh.setIndices( std::move( indices ) );
738
739 MyNonManifoldCommand command { faulty };
740 TopologicalMesh topo { mesh, command };
741
742 for ( auto itr = faulty.begin(); itr != faulty.end(); ++itr ) {
743 int cpt = 0;
744 for ( auto pitr = itr->begin(); pitr != itr->end(); ++pitr ) {
745 // vertex handle is part of the mesh
746 REQUIRE( topo.is_valid_handle( *pitr ) );
747
748 // each of the faulty face has one time the non manifold vertex
750 ( topo.point( *pitr ) - vertices[3] ).squaredNorm(), 0_ra ) ) {
751 cpt++;
752 // this vertex is not a boundary (since the faulty face is complex)
753 REQUIRE( !topo.is_boundary( *pitr ) );
754 }
755 }
756 REQUIRE( cpt == 1 );
757 }
758
759 for ( auto itr = topo.vertices_begin(); itr != topo.vertices_end(); ++itr ) {
760 REQUIRE( topo.isManifold( *itr ) );
761 }
762 }
763}
764
765TEST_CASE( "Core/Geometry/TopologicalMesh/Initialization",
766 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
767 TopologicalMesh topo;
768 TopologicalMesh::VertexHandle vhandle[3];
769 TopologicalMesh::FaceHandle fhandle;
770
771 vhandle[0] = topo.add_vertex( TopologicalMesh::Point( 1, -1, -1 ) );
772 vhandle[1] = topo.add_vertex( TopologicalMesh::Point( 1, -1, 1 ) );
773 vhandle[2] = topo.add_vertex( TopologicalMesh::Point( -1, -1, 1 ) );
774
776 face_vhandles.push_back( vhandle[0] );
777 face_vhandles.push_back( vhandle[1] );
778 face_vhandles.push_back( vhandle[2] );
779 fhandle = topo.add_face( face_vhandles );
780
781 // newly created face have invalid wedges on halfedges
782 auto heh = topo.halfedge_handle( fhandle );
783 REQUIRE( topo.property( topo.getWedgeIndexPph(), heh ).isInvalid() );
784 heh = topo.next_halfedge_handle( heh );
785 REQUIRE( topo.property( topo.getWedgeIndexPph(), heh ).isInvalid() );
786 heh = topo.next_halfedge_handle( heh );
787 REQUIRE( topo.property( topo.getWedgeIndexPph(), heh ).isInvalid() );
788 REQUIRE( topo.n_faces() == 1 );
789
790 topo.request_face_status();
791 topo.delete_face( fhandle, false );
792 topo.garbage_collection();
793 REQUIRE( topo.n_faces() == 0 );
794}
795
796TEST_CASE( "Core/Geometry/TopologicalMesh/MergeWedges",
797 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
798
799 auto mesh = Ra::Core::Geometry::makeSharpBox();
800 auto topo = TopologicalMesh { mesh };
801
803 for ( auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
804 wedgesIndices.insert( topo.getWedgeIndex( *itr ) );
805 }
806 // each 8 vertices of the cube has 3 wedges
807 REQUIRE( wedgesIndices.size() == 8 * 3 );
808 REQUIRE( topo.checkIntegrity() );
809 auto wdRef = topo.getWedgeData( topo.getWedgeIndex( *topo.halfedges_begin() ) );
810 for ( auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
811 auto wdCur = topo.getWedgeData( topo.getWedgeIndex( *itr ) );
812 auto wdNew = wdRef;
813 wdNew.m_position = wdCur.m_position;
814 topo.setWedgeData( topo.getWedgeIndex( *itr ), wdNew );
815 }
816
817 wedgesIndices.clear();
818 for ( auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
819 wedgesIndices.insert( topo.getWedgeIndex( *itr ) );
820 }
821 // each 8 vertices of the cube still has 3 wedges
822 REQUIRE( wedgesIndices.size() == 8 * 3 );
823 REQUIRE( topo.checkIntegrity() );
824
825 topo.mergeEqualWedges();
826 wedgesIndices.clear();
827 for ( auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
828 wedgesIndices.insert( topo.getWedgeIndex( *itr ) );
829 }
830 // after merge, each vertex has only on wedge
831 REQUIRE( wedgesIndices.size() == 8 );
832 REQUIRE( topo.checkIntegrity() );
833}
834
835template <typename T>
836void testAttrib( const IndexedGeometry<T>& mesh, const std::string& name, Scalar value ) {
837
838 auto attribHandle = mesh.template getAttribHandle<Scalar>( name );
839 REQUIRE( attribHandle.idx().isValid() );
840 auto& attrib = mesh.getAttrib( attribHandle );
841 for ( const auto& v : attrib.data() ) {
842 REQUIRE( v == value );
843 }
844}
845
846TEST_CASE( "Core/Geometry/TopologicalMesh/Triangulate",
847 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
848 TopologicalMesh topo {};
849 TopologicalMesh::VertexHandle vhandle[4];
850 TopologicalMesh::FaceHandle fhandle;
851
852 vhandle[0] = topo.add_vertex( TopologicalMesh::Point( -1, -1, 1 ) );
853 vhandle[1] = topo.add_vertex( TopologicalMesh::Point( 1, -1, 1 ) );
854 vhandle[2] = topo.add_vertex( TopologicalMesh::Point( 1, 1, 1 ) );
855 vhandle[3] = topo.add_vertex( TopologicalMesh::Point( -1, 1, 1 ) );
856
858 face_vhandles.push_back( vhandle[0] );
859 face_vhandles.push_back( vhandle[1] );
860 face_vhandles.push_back( vhandle[2] );
861 face_vhandles.push_back( vhandle[3] );
862 fhandle = topo.add_face( face_vhandles );
863
864 REQUIRE( topo.n_faces() == 1 );
865
866 auto index1 = topo.addWedgeAttrib<Scalar>( "test1", 1_ra );
867 REQUIRE( index1 == 0 );
868 REQUIRE( topo.getFloatAttribNames().size() == 1 );
869 REQUIRE( topo.getFloatAttribNames()[0] == "test1" );
870
871 for ( const auto& he : topo.halfedges() ) {
872 if ( topo.is_boundary( he ) ) continue;
873
874 auto wd = topo.newWedgeData();
875
876 // need to set position and vertex handle for new wedges
877 wd.m_vertexHandle = topo.to_vertex_handle( he );
878 wd.m_position = topo.point( wd.m_vertexHandle );
879
880 REQUIRE( wd.m_floatAttrib.size() == 1 );
881 wd.m_floatAttrib[index1] = 2_ra;
882 topo.replaceWedge( he, wd );
883 }
884 REQUIRE( topo.checkIntegrity() );
885
886 auto index2 = topo.addWedgeAttrib<Scalar>( "test2", 2_ra );
887 REQUIRE( index2 == 1 );
888 for ( const auto& he : topo.halfedges() ) {
889 if ( topo.is_boundary( he ) ) continue;
890 // our we require the wedge to be already set for this he
891 auto wd = topo.newWedgeData( he );
892
893 REQUIRE( wd.m_vertexHandle == topo.to_vertex_handle( he ) );
894 REQUIRE( wd.m_position == topo.point( wd.m_vertexHandle ) );
895
896 REQUIRE( wd.m_floatAttrib.size() == 2 );
897 wd.m_floatAttrib[index2] = 3_ra;
898 topo.replaceWedge( he, wd );
899 }
900
901 auto index3 = topo.addWedgeAttrib<Scalar>( "test3", 3_ra );
902 REQUIRE( index3 == 2 );
903 for ( const auto& he : topo.halfedges() ) {
904 if ( topo.is_boundary( he ) ) continue;
905
906 auto wedgeIndex = topo.getWedgeIndex( he );
907 REQUIRE( topo.getWedgeRefCount( wedgeIndex ) == 1 );
908 auto wedgeData = topo.getWedgeData( wedgeIndex );
909
910 REQUIRE( wedgeData.m_floatAttrib[index1] == 0_ra );
911 REQUIRE( wedgeData.m_floatAttrib[index2] == 3_ra );
912 REQUIRE( wedgeData.m_floatAttrib[index3] == 3_ra );
913 }
914
915 auto poly = topo.toPolyMesh();
916 REQUIRE( poly.vertices().size() == 4 );
917 REQUIRE( poly.getIndices().size() == 1 );
918 REQUIRE( poly.getIndices()[0].size() == 4 );
919 testAttrib( poly, "test1", 0_ra );
920 testAttrib( poly, "test2", 3_ra );
921 testAttrib( poly, "test3", 3_ra );
922
923 topo.triangulate();
924 topo.checkIntegrity();
925 auto tri = topo.toTriangleMesh();
926 REQUIRE( tri.vertices().size() == 4 );
927 REQUIRE( tri.getIndices().size() == 2 );
928 REQUIRE( tri.getIndices()[0].size() == 3 );
929 REQUIRE( tri.getIndices()[1].size() == 3 );
930 testAttrib( tri, "test1", 0_ra );
931 testAttrib( tri, "test2", 3_ra );
932 testAttrib( tri, "test3", 3_ra );
933}
934
935optional<TopologicalMesh::HalfedgeHandle>
936findHalfedge( TopologicalMesh& topo, const Vector3& from, const Vector3& to ) {
937 bool found;
938 TopologicalMesh::HalfedgeHandle he;
939 for ( auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); ++he_iter ) {
940
941 if ( topo.point( topo.to_vertex_handle( he_iter ) ) == from &&
942 topo.point( topo.from_vertex_handle( he_iter ) ) == to ) {
943 found = true;
944 he = *he_iter;
945 }
946 }
947 if ( found ) return he;
948 return {};
949}
950
951TEST_CASE( "Core/TopologicalMesh/CollapseWedge", "[unittests]" ) {
952 using namespace Ra::Core;
953 using namespace Ra::Core::Utils;
954 using namespace Ra::Core::Geometry;
955 auto findHalfedge = []( TopologicalMesh& topo,
956 const Vector3& from,
957 const Vector3& to ) -> optional<TopologicalMesh::HalfedgeHandle> {
958 bool found;
959 TopologicalMesh::HalfedgeHandle he;
960 for ( auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); ++he_iter ) {
961
962 if ( topo.point( topo.to_vertex_handle( he_iter ) ) == to &&
963 topo.point( topo.from_vertex_handle( he_iter ) ) == from ) {
964 found = true;
965 he = *he_iter;
966 }
967 }
968 if ( found ) return he;
969 return {};
970 };
971
972 Vector3Array points1 {
973 { 00._ra, 00._ra, 00._ra },
974 { 10._ra, 00._ra, 00._ra },
975 { 05._ra, 05._ra, 00._ra },
976 { 05._ra, 10._ra, 00._ra },
977 { 15._ra, 05._ra, 00._ra },
978 { 10._ra, 08._ra, 00._ra },
979 { 10._ra, 12._ra, 00._ra },
980 { 15._ra, 10._ra, 00._ra },
981 };
982 Vector3Array points2 = { points1[0], points1[0], points1[1], points1[1], points1[1],
983 points1[2], points1[2], points1[2], points1[2], points1[3],
984 points1[3], points1[3], points1[4], points1[4], points1[5],
985 points1[5], points1[5], points1[5], points1[5], points1[5],
986 points1[6], points1[6], points1[7], points1[7] };
987
988 Vector4Array colors1 = {
989 { 0_ra, 0_ra, 0_ra, 1_ra }, { 1_ra, 1_ra, 1_ra, 1_ra }, { 2_ra, 2_ra, 2_ra, 1_ra },
990 { 3_ra, 3_ra, 3_ra, 1_ra }, { 4_ra, 4_ra, 4_ra, 1_ra }, { 5_ra, 5_ra, 5_ra, 1_ra },
991 { 6_ra, 6_ra, 6_ra, 1_ra }, { 7_ra, 7_ra, 7_ra, 1_ra }, { 8_ra, 8_ra, 8_ra, 1_ra },
992 { 9_ra, 9_ra, 9_ra, 1_ra }, { 10_ra, 10_ra, 10_ra, 1_ra }, { 11_ra, 11_ra, 11_ra, 1_ra },
993 { 12_ra, 12_ra, 12_ra, 1_ra }, { 13_ra, 13_ra, 13_ra, 1_ra }, { 14_ra, 14_ra, 14_ra, 1_ra },
994 { 15_ra, 15_ra, 15_ra, 1_ra }, { 16_ra, 16_ra, 16_ra, 1_ra }, { 17_ra, 17_ra, 17_ra, 1_ra },
995 { 18_ra, 18_ra, 18_ra, 1_ra }, { 19_ra, 19_ra, 19_ra, 1_ra }, { 20_ra, 20_ra, 20_ra, 1_ra },
996 { 21_ra, 21_ra, 21_ra, 1_ra }, { 22_ra, 22_ra, 22_ra, 1_ra }, { 23_ra, 23_ra, 23_ra, 1_ra },
997 };
998
999 Vector3uArray indices1 { { 0, 2, 1 },
1000 { 0, 3, 2 },
1001 { 1, 2, 5 },
1002 { 2, 3, 5 },
1003 { 1, 5, 4 },
1004 { 3, 6, 5 },
1005 { 5, 6, 7 },
1006 { 4, 5, 7 } };
1007
1008 Vector3uArray indices3 = {
1009 { 0, 2, 1 }, { 1, 2, 5 }, { 1, 5, 4 }, { 3, 6, 5 }, { 5, 6, 7 }, { 4, 5, 7 } };
1010
1011 Vector3uArray indices4 = {
1012 { 0, 2, 5 }, { 3, 14, 6 }, { 4, 12, 15 }, { 11, 18, 20 }, { 17, 22, 21 }, { 16, 13, 23 } };
1013
1014 Vector3uArray indices2 { { 0, 5, 2 },
1015 { 1, 9, 8 },
1016 { 3, 6, 14 },
1017 { 7, 10, 19 },
1018 { 4, 15, 12 },
1019 { 11, 20, 18 },
1020 { 17, 21, 22 },
1021 { 16, 23, 13 } };
1022 Vector4Array colors2 { 24, Color::White() };
1023 for ( const auto& face : indices2 ) {
1024 colors2[face[0]] = colors1[face[0]];
1025 colors2[face[1]] = colors1[face[0]];
1026 colors2[face[2]] = colors1[face[0]];
1027 }
1028
1029 Vector4Array colors3 { 24, Color::White() };
1030 std::vector<int> topFaceIndices { 1, 3, 5, 6 };
1031 std::vector<int> bottomFaceIndices { 0, 2, 4, 7 };
1032
1033 for ( const auto& faceIndex : topFaceIndices ) {
1034 colors3[indices2[faceIndex][0]] = colors1[0];
1035 colors3[indices2[faceIndex][1]] = colors1[0];
1036 colors3[indices2[faceIndex][2]] = colors1[0];
1037 }
1038 for ( const auto& faceIndex : bottomFaceIndices ) {
1039 colors3[indices2[faceIndex][0]] = colors1[1];
1040 colors3[indices2[faceIndex][1]] = colors1[1];
1041 colors3[indices2[faceIndex][2]] = colors1[1];
1042 }
1043
1044 Vector4Array colors4 { 24, Color::White() };
1045
1046 std::vector<std::vector<int>> splitContinuousWedges { // 0
1047 { 0 },
1048 { 1 },
1049 // 1
1050 { 2, 3, 4 },
1051 // 2
1052 { 8, 7 },
1053 { 5, 6 },
1054 // 3
1055 { 9, 10, 11 },
1056 // 4
1057 { 12, 13 },
1058 // 5
1059 { 14, 15, 16 },
1060 { 17, 18, 19 },
1061 // 6
1062 { 20, 21 },
1063 // 7
1064 { 22, 23 } };
1065
1066 for ( size_t i = 0; i < splitContinuousWedges.size(); ++i ) {
1067 for ( const auto& widx : splitContinuousWedges[i] ) {
1068 colors4[widx] = colors1[i];
1069 }
1070 }
1071
1073 auto addMergeScene = [findHalfedge]( const Vector3Array& points,
1074 const Vector4Array& colors,
1075 const Vector3uArray& indices,
1076 const Vector3& inFrom,
1077 const Vector3& inTo ) {
1078 Vector3 from { inFrom };
1079 Vector3 to { inTo };
1080 TriangleMesh mesh1;
1081 TopologicalMesh topo1;
1082 optional<TopologicalMesh::HalfedgeHandle> optHe;
1083
1084 mesh1.setVertices( points );
1085 mesh1.addAttrib( "color", Vector4Array { colors.begin(), colors.begin() + points.size() } );
1086 mesh1.setIndices( indices );
1087
1088 topo1 = TopologicalMesh { mesh1 };
1089 topo1.mergeEqualWedges();
1090 topo1.garbage_collection();
1091
1092 topo1.checkIntegrity();
1093 optHe = findHalfedge( topo1, from, to );
1094 REQUIRE( optHe );
1095
1096 topo1.collapse( *optHe );
1097 REQUIRE( topo1.checkIntegrity() );
1098
1099 topo1 = TopologicalMesh { mesh1 };
1100 optHe = findHalfedge( topo1, from, to );
1101 REQUIRE( optHe );
1102
1103 topo1.collapse( *optHe, true );
1104 REQUIRE( topo1.checkIntegrity() );
1105
1106 std::swap( from, to );
1107
1108 topo1 = TopologicalMesh { mesh1 };
1109 topo1.mergeEqualWedges();
1110 topo1.garbage_collection();
1111 optHe = findHalfedge( topo1, from, to );
1112 REQUIRE( optHe );
1113
1114 topo1.collapse( *optHe );
1115 REQUIRE( topo1.checkIntegrity() );
1116
1117 topo1 = TopologicalMesh { mesh1 };
1118 optHe = findHalfedge( topo1, from, to );
1119 REQUIRE( optHe );
1120
1121 topo1.collapse( *optHe, true );
1122 REQUIRE( topo1.checkIntegrity() );
1123 };
1124
1125 SECTION( "With continuous wedges." ) {
1126 addMergeScene( points1, colors1, indices1, points1[5], points1[2] );
1127 }
1128
1129 SECTION( "with top/bottom wedges" ) {
1130 addMergeScene( points2, colors3, indices2, points1[5], points1[2] );
1131 }
1132
1133 SECTION( "with continuous top/bottom wedges" ) {
1134 addMergeScene( points2, colors4, indices2, points1[5], points1[2] );
1135 }
1136 SECTION( "with flat face wedges" ) {
1137 addMergeScene( points2, colors2, indices2, points1[5], points1[2] );
1138 }
1139 SECTION( "boundary With continuous wedges." ) {
1140 addMergeScene( points1, colors1, indices3, points1[5], points1[2] );
1141 }
1142 SECTION( "boundary with top/bottom wedges" ) {
1143 addMergeScene( points2, colors3, indices4, points1[5], points1[2] );
1144 }
1145 SECTION( "boundary with continuous top/bottom wedges" ) {
1146 addMergeScene( points2, colors4, indices4, points1[5], points1[2] );
1147 }
1148 SECTION( "boundary with flat face wedges" ) {
1149 addMergeScene( points2, colors2, indices4, points1[5], points1[2] );
1150 }
1151
1152 auto addSplitScene = [findHalfedge]( const Vector3Array& points,
1153 const Vector4Array& colors,
1154 const Vector3uArray& indices,
1155 Vector3 from,
1156 Vector3 to ) {
1157 TriangleMesh mesh;
1158 TopologicalMesh topo;
1159 optional<TopologicalMesh::HalfedgeHandle> optHe;
1160
1161 mesh.setVertices( points );
1162 mesh.addAttrib( "color", Vector4Array { colors.begin(), colors.begin() + points.size() } );
1163 mesh.setIndices( indices );
1164
1165 topo = TopologicalMesh { mesh };
1166 topo.mergeEqualWedges();
1167 topo.garbage_collection();
1168
1169 for ( int i = 0; i < 2; ++i ) {
1170 for ( auto f : { 0.25_ra, 0.5_ra, 0.75_ra } ) {
1171 topo = TopologicalMesh { mesh };
1172 topo.mergeEqualWedges();
1173 optHe = findHalfedge( topo, from, to );
1174 auto eh = topo.edge_handle( *optHe );
1175
1176 auto he0 = topo.halfedge_handle( eh, 0 );
1177 auto he0boundary = topo.is_boundary( he0 );
1178 auto he1 = topo.halfedge_handle( eh, 1 );
1179 auto he1boundary = topo.is_boundary( he1 );
1180 auto v0 = topo.from_vertex_handle( he0 ); // i.e. to_vertex_handle(he1)
1181 REQUIRE( v0 == topo.to_vertex_handle( he1 ) );
1182 auto v1 = topo.to_vertex_handle( he0 );
1183 auto p0 = topo.point( v0 );
1184 auto p1 = topo.point( v1 );
1185
1186 auto widx01 = topo.getWedgeIndex( he0 );
1187 auto widx00 = topo.getWedgeIndex( topo.prev_halfedge_handle( he0 ) );
1188 auto widx10 = topo.getWedgeIndex( he1 );
1189 auto widx11 = topo.getWedgeIndex( topo.prev_halfedge_handle( he1 ) );
1190
1191 topo.splitEdge( eh, f );
1192
1193 auto vsplit = topo.to_vertex_handle( he1 ); // i.e. from_vertex_handle(he0)
1194 REQUIRE( vsplit == topo.from_vertex_handle( he0 ) );
1195
1196 auto psplit = topo.point( vsplit );
1197 auto vcheck = ( f * p1 + ( 1_ra - f ) * p0 );
1198 REQUIRE( Math::areApproxEqual( ( psplit - vcheck ).squaredNorm(), 0_ra ) );
1199 REQUIRE( he0boundary == topo.is_boundary( he0 ) );
1200 REQUIRE( he1boundary == topo.is_boundary( he1 ) );
1201
1203 if ( !he0boundary ) {
1204 auto wd0 = topo.getWedgeData( widx00 );
1205 auto wd1 = topo.getWedgeData( widx01 );
1206 auto f0 = topo.getWedgeData( widx00 ).m_vector4Attrib[0];
1207 auto f1 = topo.getWedgeData( widx01 ).m_vector4Attrib[0];
1208 auto wd =
1209 topo.getWedgeData( topo.getWedgeIndex( topo.prev_halfedge_handle( he0 ) ) );
1210 auto fsplit = wd.m_vector4Attrib[0];
1211 auto fcheck = ( f * f1 + ( 1_ra - f ) * f0 );
1212 REQUIRE( Math::areApproxEqual( ( fsplit - fcheck ).squaredNorm(), 0_ra ) );
1213 REQUIRE(
1214 Math::areApproxEqual( ( psplit - wd.m_position ).squaredNorm(), 0_ra ) );
1215 }
1216 else {
1217 REQUIRE( topo.getWedgeIndex( topo.prev_halfedge_handle( he0 ) ).isInvalid() );
1218 }
1219
1220 if ( !he1boundary ) {
1221 auto wd0 = topo.getWedgeData( widx10 );
1222 auto wd1 = topo.getWedgeData( widx11 );
1223 auto f0 = topo.getWedgeData( widx10 ).m_vector4Attrib[0];
1224 auto f1 = topo.getWedgeData( widx11 ).m_vector4Attrib[0];
1225 auto wd = topo.getWedgeData( topo.getWedgeIndex( he1 ) );
1226 auto fsplit = wd.m_vector4Attrib[0];
1227 auto fcheck = ( f * f1 + ( 1_ra - f ) * f0 );
1228 REQUIRE( Math::areApproxEqual( ( fsplit - fcheck ).squaredNorm(), 0_ra ) );
1229 REQUIRE(
1230 Math::areApproxEqual( ( psplit - wd.m_position ).squaredNorm(), 0_ra ) );
1231 }
1232 else { REQUIRE( topo.getWedgeIndex( he1 ).isInvalid() ); }
1233 }
1234 std::swap( from, to );
1235 }
1236 };
1237
1238 SECTION( "With continuous wedges." ) {
1239 addSplitScene( points1, colors1, indices1, points1[5], points1[2] );
1240 }
1241
1242 SECTION( "with top/bottom wedges" ) {
1243 addSplitScene( points2, colors3, indices2, points1[5], points1[2] );
1244 }
1245
1246 SECTION( "with continuous top/bottom wedges" ) {
1247 addSplitScene( points2, colors4, indices2, points1[5], points1[2] );
1248 }
1249 SECTION( "with flat face wedges" ) {
1250 addSplitScene( points2, colors2, indices2, points1[5], points1[2] );
1251 }
1252 SECTION( "boundary With continuous wedges." ) {
1253 addSplitScene( points1, colors1, indices3, points1[5], points1[2] );
1254 }
1255 SECTION( "boundary with top/bottom wedges" ) {
1256 addSplitScene( points2, colors3, indices4, points1[5], points1[2] );
1257 }
1258 SECTION( "boundary with continuous top/bottom wedges" ) {
1259 addSplitScene( points2, colors4, indices4, points1[5], points1[2] );
1260 }
1261 SECTION( "boundary with flat face wedges" ) {
1262 addSplitScene( points2, colors2, indices4, points1[5], points1[2] );
1263 }
1264}
1265
1266TEST_CASE( "Core/Geometry/TopologicalMesh/Updates",
1267 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
1268 using Ra::Core::Vector3;
1270 using Ra::Core::Geometry::TriangleMesh;
1271
1272 auto testConverter = []( TriangleMesh mesh ) {
1273 auto topologicalMesh = TopologicalMesh( mesh );
1274 auto& vertices = mesh.verticesWithLock();
1275 for ( auto& v : vertices ) {
1276 v = TriangleMesh::Point( 0_ra, 1_ra, 2_ra );
1277 }
1278 mesh.verticesUnlock();
1279
1280 // update topo mesh positions from mesh
1281 topologicalMesh.updatePositions( mesh.vertices() );
1282 for ( auto itr = topologicalMesh.vertices_begin(); itr != topologicalMesh.vertices_end();
1283 ++itr ) {
1284 REQUIRE(
1285 topologicalMesh.point( *itr ).isApprox( TriangleMesh::Point( 0_ra, 1_ra, 2_ra ) ) );
1286 // modify for next test.
1287 topologicalMesh.point( *itr ) = TopologicalMesh::Point( 3_ra, 4_ra, 5_ra );
1288 }
1289
1290 // the other way round
1291 topologicalMesh.updateTriangleMesh( mesh );
1292
1293 // not update since wedges are not updated yet
1294 for ( auto itr = mesh.vertices().begin(); itr != mesh.vertices().end(); ++itr ) {
1295 REQUIRE( itr->isApprox( TriangleMesh::Point( 0_ra, 1_ra, 2_ra ) ) );
1296 }
1297
1298 topologicalMesh.copyPointsPositionToWedges();
1299 topologicalMesh.updateTriangleMesh( mesh );
1300
1301 // not update since wedges are not updated yet
1302 for ( auto itr = mesh.vertices().begin(); itr != mesh.vertices().end(); ++itr ) {
1303 REQUIRE( itr->isApprox( TriangleMesh::Point( 3_ra, 4_ra, 5_ra ) ) );
1304 }
1305 };
1306
1307 SECTION( "Closed mesh" ) {
1308 testConverter( Ra::Core::Geometry::makeBox() );
1309 testConverter( Ra::Core::Geometry::makeSharpBox() );
1310 }
1311
1312 SECTION( "Mesh with boundaries" ) {
1313 testConverter( Ra::Core::Geometry::makePlaneGrid( 2, 2 ) );
1314 }
1315}
T back_inserter(T... args)
T begin(T... args)
T bind(T... args)
void setNormals(PointAttribHandle::Container &&normals)
Set normals.
PointAttribHandle::Container & verticesWithLock()
void verticesUnlock()
Release lock on vertices positions.
void setVertices(PointAttribHandle::Container &&vertices)
Set vertices.
const NormalAttribHandle::Container & normals() const
Access the vertices normals.
const PointAttribHandle::Container & vertices() const
Access the vertices positions.
Utils::AttribHandle< T > addAttrib(const std::string &name)
Utils::Attrib< T > & getAttrib(const Utils::AttribHandle< T > &h)
A single layer MultiIndexedGeometry.
IndexContainerType & getIndicesWithLock()
void indicesUnlock()
unlock previously read write acces, notify observers of the update.
void setIndices(IndexContainerType &&indices)
AbstractGeometry with per-vertex attributes and layers of indices. Each layer represents a different ...
bool isManifold(VertexHandle vh) const
void updateTriangleMesh(Ra::Core::Geometry::MultiIndexedGeometry &mesh)
WedgeIndex getWedgeIndex(OpenMesh::HalfedgeHandle heh) const
WedgeIndex replaceWedge(OpenMesh::HalfedgeHandle he, const WedgeData &wd)
void collapse(HalfedgeHandle he, bool keepFromVertex=false)
bool splitEdge(TopologicalMesh::EdgeHandle eh, Scalar f)
void setWedgeData(WedgeIndex widx, const WedgeData &wd)
std::set< WedgeIndex > getVertexWedges(OpenMesh::VertexHandle vh) const
unsigned int getWedgeRefCount(const WedgeIndex &idx) const
void garbage_collection()
Remove deleted element from the mesh, including wedges.
const WedgeData & getWedgeData(const WedgeIndex &idx) const
HalfedgeHandle halfedge_handle(VertexHandle vh, FaceHandle fh) const
std::string getName() const
Return the attribute's name.
Definition Attribs.hpp:456
bool hasSameAttribs(const AttribManager &other)
Definition Attribs.cpp:33
void for_each_attrib(const F &func) const
Definition Attribs.hpp:762
T clear(T... args)
T copy(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T insert(T... args)
T move(T... args)
std::enable_if<!std::numeric_limits< T >::is_integer, bool >::type areApproxEqual(T x, T y, T espilonBoostFactor=T(10))
Compare two numbers such that |x-y| < espilon*epsilonBoostFactor.
Definition Math.hpp:42
This namespace contains everything "low level", related to data, datastuctures, and computation.
Definition Cage.cpp:5
T push_back(T... args)
T cref(T... args)
T size(T... args)
T sort(T... args)
T swap(T... args)
T transform(T... args)