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>
7#include <OpenMesh/Tools/Subdivider/Uniform/CatmullClarkT.hh>
10using namespace Ra::Core::Utils;
11using namespace Ra::Core::Geometry;
13bool isSameMesh(
const Ra::Core::Geometry::TriangleMesh& meshOne,
14 const Ra::Core::Geometry::TriangleMesh& meshTwo,
15 bool expected =
true ) {
22 LOG( logINFO ) <<
"isSameMesh failed vertices.size()" << meshOne.
vertices().size()
28 if ( expected ) { LOG( logINFO ) <<
"isSameMesh failed normals.size()"; }
32 if ( meshOne.getIndices().
size() != meshTwo.getIndices().
size() ) {
33 if ( expected ) { LOG( logINFO ) <<
"isSameMesh failed getIndices().size()"; }
41 bool hasNormals = meshOne.
normals().size() > 0;
44 while ( result && i <
int( meshOne.getIndices().
size() ) ) {
45 std::vector<Ra::Core::Vector3>::iterator it;
46 stackVertices.
clear();
57 for (
int j = 0; j < 3; ++j ) {
60 meshTwo.
vertices()[meshTwo.getIndices()[i][j]] );
61 if ( it != stackVertices.
end() ) { stackVertices.
erase( it ); }
64 if ( expected ) { LOG( logINFO ) <<
"isSameMesh failed face not found"; }
71 for (
int j = 0; j < 3; ++j ) {
74 meshTwo.
normals()[meshTwo.getIndices()[i][j]] );
75 if ( it != stackNormals.
end() ) { stackNormals.
erase( it ); }
78 if ( expected ) { LOG( logINFO ) <<
"isSameMesh failed normal not found"; }
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 ); }
101#define COPY_TO_WEDGES_VECTOR_HELPER( UPTYPE, REALTYPE ) \
102 if ( attr->is##UPTYPE() ) { \
104 meshOne.getAttrib( meshOne.template getAttribHandle<REALTYPE>( attr->getName() ) ) \
106 for ( size_t i = 0; i < size; ++i ) { \
107 wedgesMeshOne[i].m_data.getAttribArray<REALTYPE>().push_back( data[i] ); \
112void copyToWedgesVector(
size_t size,
118 LOG( logWARNING ) <<
"[TopologicalMesh test] Skip badly sized attribute "
121 else if ( attr->
getName() != getAttribName( VERTEX_POSITION ) ) {
124 for (
size_t i = 0; i < size; ++i ) {
125 wedgesMeshOne[i].m_data.m_position = data[i];
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 );
135#undef COPY_TO_WEDGES_VECTOR_HELPER
142 using namespace Ra::Core::Geometry;
150 if ( meshOne.
normals().size() != meshTwo.
normals().size() )
return false;
151 if ( meshOne.getIndices().
size() != meshTwo.getIndices().
size() )
return false;
156 auto size = meshOne.
vertices().size();
157 for (
size_t i = 0; i < size; ++i ) {
165 copyToWedgesVector<T>, size,
std::cref( meshOne ),
std::ref( wedgesMeshOne ), _1 );
169 copyToWedgesVector<T>, size,
std::cref( meshTwo ),
std::ref( wedgesMeshTwo ), _1 );
175 if ( wedgesMeshOne != wedgesMeshTwo ) {
185 newMeshOneIdx[wedgesMeshOne[0].m_idx] = 0;
186 newMeshTwoIdx[wedgesMeshTwo[0].m_idx] = 0;
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;
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;
202 meshOne.getIndices();
204 meshTwo.getIndices();
206 for (
auto& face : indices1 ) {
208 for (
int i = 0; i < face.size(); ++i ) {
209 face( i ) = newMeshOneIdx[face( i )];
215 for (
auto& face : indices2 ) {
218 for (
int i = 0; i < face.size(); ++i ) {
219 face( i ) = newMeshTwoIdx[face( i )];
224 if ( indices1 != indices2 ) {
231TEST_CASE(
"Core/Geometry/TopologicalMesh",
"[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
232 using Ra::Core::Vector3;
234 using Ra::Core::Geometry::TriangleMesh;
236 auto testConverter = [](
const TriangleMesh& mesh ) {
238 auto newMesh = topologicalMesh.toTriangleMesh();
239 REQUIRE( isSameMesh( mesh, newMesh ) );
240 REQUIRE( topologicalMesh.checkIntegrity() );
243 SECTION(
"Closed mesh" ) {
244 testConverter( Ra::Core::Geometry::makeBox() );
245 testConverter( Ra::Core::Geometry::makeSharpBox() );
248 SECTION(
"Mesh with boundaries" ) {
249 testConverter( Ra::Core::Geometry::makePlaneGrid( 2, 2 ) );
252 SECTION(
"With user def attribs" ) {
253 using Vector5 = Eigen::Matrix<Scalar, 5, 1>;
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 } };
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 } };
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" );
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();
301 auto newMesh = topologicalMesh.toTriangleMesh();
302 REQUIRE( isSameMesh( mesh, newMesh ) );
303 REQUIRE( topologicalMesh.checkIntegrity() );
306 REQUIRE( !newMesh.hasAttrib(
"vector5_attrib" ) );
308 REQUIRE( newMesh.hasAttrib(
"vector2_attrib" ) );
310 REQUIRE( newMesh.hasAttrib(
"vector4_attrib" ) );
313 REQUIRE( !newMesh.hasAttrib(
"evector2_attrib" ) );
314 REQUIRE( !newMesh.hasAttrib(
"evector4_attrib" ) );
315 REQUIRE( !newMesh.hasAttrib(
"evector5_attrib" ) );
318 SECTION(
"Edit topo mesh" ) {
319 auto mesh = Ra::Core::Geometry::makeCylinder( Vector3( 0, 0, 0 ), Vector3( 0, 0, 1 ), 1 );
322 auto newMesh = topologicalMesh.toTriangleMesh();
323 topologicalMesh.setWedgeData(
324 TopologicalMesh::WedgeIndex { 0 }, getAttribName( VERTEX_NORMAL ), Vector3( 0, 0, 0 ) );
325 auto newMeshModified = topologicalMesh.toTriangleMesh();
327 REQUIRE( isSameMesh( mesh, newMesh ) );
328 REQUIRE( isSameMeshWedge( mesh, newMesh ) );
329 REQUIRE( !isSameMeshWedge( mesh, newMeshModified ) );
330 REQUIRE( topologicalMesh.checkIntegrity() );
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" );
337 auto newMesh = topologicalMesh.toTriangleMesh();
338 REQUIRE( !newMesh.hasAttrib(
"empty" ) );
339 REQUIRE( topologicalMesh.checkIntegrity() );
342 SECTION(
"Test normals" ) {
343 auto mesh = Ra::Core::Geometry::makeBox();
346 for ( TopologicalMesh::ConstVertexIter v_it = topologicalMesh.vertices_begin();
347 v_it != topologicalMesh.vertices_end();
349 topologicalMesh.set_normal(
350 *v_it, TopologicalMesh::Normal( Scalar( 1. ), Scalar( 0. ), Scalar( 0. ) ) );
353 for ( TopologicalMesh::ConstVertexIter v_it = topologicalMesh.vertices_begin();
354 v_it != topologicalMesh.vertices_end();
356 topologicalMesh.propagate_normal_to_wedges( *v_it );
359 auto newMesh = topologicalMesh.toTriangleMesh();
362 for (
auto n : newMesh.normals() ) {
364 n.dot( Vector3( Scalar( 1. ), Scalar( 0. ), Scalar( 0. ) ) ),
368 if ( n.dot( Vector3( Scalar( 0.5 ), Scalar( 0. ), Scalar( 0. ) ) ) > Scalar( 0.8 ) ) {
374 REQUIRE( topologicalMesh.checkIntegrity() );
377 SECTION(
"Test without normals" ) {
379 { 0_ra, 1_ra, 0_ra },
380 { 1_ra, 1_ra, 0_ra },
381 { 1_ra, 0_ra, 0_ra } };
389 REQUIRE( topo1.checkIntegrity() );
390 TriangleMesh mesh1 = topo1.toTriangleMesh();
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;
397 topo1.propagate_normal_to_wedges( *vitr );
398 REQUIRE( !topo1.has_halfedge_normals() );
399 REQUIRE( !topo1.has_face_normals() );
403 REQUIRE( !topo1.has_face_normals() );
404 OpenMesh::FPropHandleT<TopologicalMesh::Normal> fProp;
407 REQUIRE( isSameMesh( mesh, mesh1 ) );
409 REQUIRE( mesh1.
normals().size() == 0 );
413void test_split(
TopologicalMesh& topo, TopologicalMesh::EdgeHandle eh, Scalar f ) {
417 auto v0 = topo.from_vertex_handle( he0 );
418 REQUIRE( v0 == topo.to_vertex_handle( he1 ) );
419 auto v1 = topo.to_vertex_handle( he0 );
420 auto p0 = topo.point( v0 );
422 auto p1 = topo.point( v1 );
427 REQUIRE( topo.is_valid_handle( he0 ) );
428 REQUIRE( topo.is_valid_handle( he1 ) );
431 REQUIRE( v1 == topo.to_vertex_handle( he0 ) );
435 auto vsplit = topo.to_vertex_handle( he1 );
436 REQUIRE( vsplit == topo.from_vertex_handle( he0 ) );
438 auto psplit = topo.point( vsplit );
439 auto vcheck = ( f * p1 + ( 1_ra - f ) * p0 );
443 REQUIRE( wedges.size() == 1 );
446 auto fsplit = wd.m_floatAttrib[0];
447 auto fcheck = ( f * f1 + ( 1_ra - f ) * f0 );
453 Ra::Core::Geometry::PolyMesh polyMesh;
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 },
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 },
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 },
476 { -1_ra, -3_ra, 0_ra },
477 { 1_ra, -3_ra, 0_ra },
481 Vector3Array normals;
482 normals.resize( polyMesh.
vertices().size() );
487 [](
const Vector3& v ) { return ( v + Vector3( 0_ra, 0_ra, 1_ra ) ).normalized(); } );
490 auto quad = VectorNui( 4 );
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 } );
501 topologicalMesh.initWithWedge( polyMesh, polyMesh.getLayerKey() );
503 REQUIRE( isSameMeshWedge( newMesh, polyMesh ) );
506TEST_CASE(
"Core/Geometry/TopologicalMesh/PolyMesh",
507 "[unittests][Core][Core/Geometry][TopologicalMesh][PolyMesh]" ) {
522TEST_CASE(
"Core/Geometry/TopologicalMesh/EdgeSplit",
523 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
524 using Ra::Core::Vector3;
526 using Ra::Core::Geometry::TriangleMesh;
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 ) } );
534 auto handle = meshSplit.
addAttrib<Scalar>(
"test", { 0_ra, 1_ra, 2_ra, 3_ra } );
535 CORE_UNUSED( handle );
541 TopologicalMesh::EdgeHandle eh;
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 ) ) {
551 REQUIRE( innerEdgeCount == 1 );
554 test_split( topo, eh, f );
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 ) {}
565 LOG( logINFO ) <<
"Non Manifold face found";
569 REQUIRE( nonManifoldFaces == targetNonManifoldFaces );
570 LOG( logINFO ) <<
"Process non-manifold faces";
573 int nonManifoldFaces { 0 };
574 const int targetNonManifoldFaces;
587 LOG( logINFO ) <<
" Built a mesh with " << m.
vertices().size() <<
" vertices, "
588 << m.
normals().size() <<
" normals and " << m.getIndices().
size()
596 auto testConverter = [](
const TriangleMesh& referenceMesh,
597 const TriangleMesh& candidateMesh,
598 MyNonManifoldCommand command ) {
602 REQUIRE( isSameMesh( referenceMesh, convertedMeshWithCommand ) );
605 auto convertedMeshWithoutCommand = topoWithoutCommand.
toTriangleMesh();
606 REQUIRE( isSameMesh( referenceMesh, convertedMeshWithoutCommand ) );
607 return convertedMeshWithoutCommand;
611 { 0_ra, 1_ra, 0_ra },
612 { 1_ra, 1_ra, 0_ra },
613 { 1_ra, 0_ra, 0_ra } };
615 { 0_ra, 0_ra, 1_ra },
616 { 0_ra, 0_ra, 1_ra },
617 { 0_ra, 0_ra, 1_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 } };
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 },
635 using Vector5 = Eigen::Matrix<Scalar, 5, 1>;
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 },
644 auto mesh = buildMesh( vertices, normals, indices );
647 LOG( logINFO ) <<
"Test with edge shared by three faces";
648 auto mesh2 = buildMesh( vertices_2, normals_2, indices_2 );
651 mesh, mesh2, MyNonManifoldCommand( 1 ) );
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();
662 REQUIRE( mesh4.vertexAttribs().hasSameAttribs( mesh.
vertexAttribs() ) );
664 REQUIRE( !mesh4.vertexAttribs().hasSameAttribs( mesh3.vertexAttribs() ) );
665 REQUIRE( !mesh3.vertexAttribs().hasSameAttribs( mesh4.vertexAttribs() ) );
666 mesh4 = testConverter(
667 mesh, mesh3, MyNonManifoldCommand( 0 ) );
668 REQUIRE( mesh4.vertexAttribs().hasSameAttribs( mesh.
vertexAttribs() ) );
670 REQUIRE( !mesh4.vertexAttribs().hasSameAttribs( mesh3.vertexAttribs() ) );
671 REQUIRE( !mesh3.vertexAttribs().hasSameAttribs( mesh4.vertexAttribs() ) );
677 SECTION(
"Non manifold vertex : Bow tie" ) {
679 { -1_ra, -1_ra, 0_ra },
680 { -1_ra, 1_ra, 0_ra },
681 { 0_ra, 0_ra, 0_ra },
682 { 1_ra, -1_ra, 0_ra },
683 { 1_ra, 1_ra, 0_ra },
694 for (
auto itr = topo.vertices_begin(); itr != topo.vertices_end(); ++itr ) {
702 SECTION(
"Non manifold vertex : Double pyramid" ) {
704 struct MyNonManifoldCommand {
705 explicit inline MyNonManifoldCommand(
707 m_faulty( faulty ) {}
710 m_faulty.push_back( face_vhandles );
715 int nonManifoldFaces { 0 };
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 },
722 { 0_ra, 0_ra, 0_ra },
723 { 1_ra, 0_ra, 0_ra },
724 { 0.5_ra, 0_ra, 1_ra } };
739 MyNonManifoldCommand command { faulty };
742 for (
auto itr = faulty.
begin(); itr != faulty.
end(); ++itr ) {
744 for (
auto pitr = itr->begin(); pitr != itr->end(); ++pitr ) {
746 REQUIRE( topo.is_valid_handle( *pitr ) );
750 ( topo.point( *pitr ) - vertices[3] ).squaredNorm(), 0_ra ) ) {
753 REQUIRE( !topo.is_boundary( *pitr ) );
759 for (
auto itr = topo.vertices_begin(); itr != topo.vertices_end(); ++itr ) {
765TEST_CASE(
"Core/Geometry/TopologicalMesh/Initialization",
766 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
768 TopologicalMesh::VertexHandle vhandle[3];
769 TopologicalMesh::FaceHandle fhandle;
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 ) );
779 fhandle = topo.add_face( face_vhandles );
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 );
790 topo.request_face_status();
791 topo.delete_face( fhandle,
false );
793 REQUIRE( topo.n_faces() == 0 );
796TEST_CASE(
"Core/Geometry/TopologicalMesh/MergeWedges",
797 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
799 auto mesh = Ra::Core::Geometry::makeSharpBox();
803 for (
auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
807 REQUIRE( wedgesIndices.
size() == 8 * 3 );
810 for (
auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
813 wdNew.m_position = wdCur.m_position;
817 wedgesIndices.
clear();
818 for (
auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
822 REQUIRE( wedgesIndices.
size() == 8 * 3 );
826 wedgesIndices.
clear();
827 for (
auto itr = topo.halfedges_begin(), stop = topo.halfedges_end(); itr != stop; ++itr ) {
831 REQUIRE( wedgesIndices.
size() == 8 );
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 );
846TEST_CASE(
"Core/Geometry/TopologicalMesh/Triangulate",
847 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
849 TopologicalMesh::VertexHandle vhandle[4];
850 TopologicalMesh::FaceHandle fhandle;
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 ) );
862 fhandle = topo.add_face( face_vhandles );
864 REQUIRE( topo.n_faces() == 1 );
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" );
871 for (
const auto& he : topo.halfedges() ) {
872 if ( topo.is_boundary( he ) )
continue;
877 wd.m_vertexHandle = topo.to_vertex_handle( he );
878 wd.m_position = topo.point( wd.m_vertexHandle );
880 REQUIRE( wd.m_floatAttrib.size() == 1 );
881 wd.m_floatAttrib[index1] = 2_ra;
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;
893 REQUIRE( wd.m_vertexHandle == topo.to_vertex_handle( he ) );
894 REQUIRE( wd.m_position == topo.point( wd.m_vertexHandle ) );
896 REQUIRE( wd.m_floatAttrib.size() == 2 );
897 wd.m_floatAttrib[index2] = 3_ra;
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;
910 REQUIRE( wedgeData.m_floatAttrib[index1] == 0_ra );
911 REQUIRE( wedgeData.m_floatAttrib[index2] == 3_ra );
912 REQUIRE( wedgeData.m_floatAttrib[index3] == 3_ra );
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 );
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 );
935optional<TopologicalMesh::HalfedgeHandle>
936findHalfedge(
TopologicalMesh& topo,
const Vector3& from,
const Vector3& to ) {
938 TopologicalMesh::HalfedgeHandle he;
939 for (
auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); ++he_iter ) {
941 if ( topo.point( topo.to_vertex_handle( he_iter ) ) == from &&
942 topo.point( topo.from_vertex_handle( he_iter ) ) == to ) {
947 if ( found )
return he;
951TEST_CASE(
"Core/TopologicalMesh/CollapseWedge",
"[unittests]" ) {
953 using namespace Ra::Core::Utils;
954 using namespace Ra::Core::Geometry;
957 const Vector3& to ) -> optional<TopologicalMesh::HalfedgeHandle> {
959 TopologicalMesh::HalfedgeHandle he;
960 for (
auto he_iter = topo.halfedges_begin(); he_iter != topo.halfedges_end(); ++he_iter ) {
962 if ( topo.point( topo.to_vertex_handle( he_iter ) ) == to &&
963 topo.point( topo.from_vertex_handle( he_iter ) ) == from ) {
968 if ( found )
return he;
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 },
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] };
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 },
999 Vector3uArray indices1 { { 0, 2, 1 },
1008 Vector3uArray indices3 = {
1009 { 0, 2, 1 }, { 1, 2, 5 }, { 1, 5, 4 }, { 3, 6, 5 }, { 5, 6, 7 }, { 4, 5, 7 } };
1011 Vector3uArray indices4 = {
1012 { 0, 2, 5 }, { 3, 14, 6 }, { 4, 12, 15 }, { 11, 18, 20 }, { 17, 22, 21 }, { 16, 13, 23 } };
1014 Vector3uArray indices2 { { 0, 5, 2 },
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]];
1029 Vector4Array colors3 { 24, Color::White() };
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];
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];
1044 Vector4Array colors4 { 24, Color::White() };
1066 for (
size_t i = 0; i < splitContinuousWedges.size(); ++i ) {
1067 for (
const auto& widx : splitContinuousWedges[i] ) {
1068 colors4[widx] = colors1[i];
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 };
1082 optional<TopologicalMesh::HalfedgeHandle> optHe;
1085 mesh1.
addAttrib(
"color", Vector4Array { colors.begin(), colors.begin() + points.size() } );
1093 optHe = findHalfedge( topo1, from, to );
1100 optHe = findHalfedge( topo1, from, to );
1111 optHe = findHalfedge( topo1, from, to );
1118 optHe = findHalfedge( topo1, from, to );
1125 SECTION(
"With continuous wedges." ) {
1126 addMergeScene( points1, colors1, indices1, points1[5], points1[2] );
1129 SECTION(
"with top/bottom wedges" ) {
1130 addMergeScene( points2, colors3, indices2, points1[5], points1[2] );
1133 SECTION(
"with continuous top/bottom wedges" ) {
1134 addMergeScene( points2, colors4, indices2, points1[5], points1[2] );
1136 SECTION(
"with flat face wedges" ) {
1137 addMergeScene( points2, colors2, indices2, points1[5], points1[2] );
1139 SECTION(
"boundary With continuous wedges." ) {
1140 addMergeScene( points1, colors1, indices3, points1[5], points1[2] );
1142 SECTION(
"boundary with top/bottom wedges" ) {
1143 addMergeScene( points2, colors3, indices4, points1[5], points1[2] );
1145 SECTION(
"boundary with continuous top/bottom wedges" ) {
1146 addMergeScene( points2, colors4, indices4, points1[5], points1[2] );
1148 SECTION(
"boundary with flat face wedges" ) {
1149 addMergeScene( points2, colors2, indices4, points1[5], points1[2] );
1152 auto addSplitScene = [findHalfedge](
const Vector3Array& points,
1153 const Vector4Array& colors,
1154 const Vector3uArray& indices,
1159 optional<TopologicalMesh::HalfedgeHandle> optHe;
1162 mesh.
addAttrib(
"color", Vector4Array { colors.begin(), colors.begin() + points.size() } );
1169 for (
int i = 0; i < 2; ++i ) {
1170 for (
auto f : { 0.25_ra, 0.5_ra, 0.75_ra } ) {
1173 optHe = findHalfedge( topo, from, to );
1174 auto eh = topo.edge_handle( *optHe );
1177 auto he0boundary = topo.is_boundary( he0 );
1179 auto he1boundary = topo.is_boundary( he1 );
1180 auto v0 = topo.from_vertex_handle( he0 );
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 );
1187 auto widx00 = topo.
getWedgeIndex( topo.prev_halfedge_handle( he0 ) );
1189 auto widx11 = topo.
getWedgeIndex( topo.prev_halfedge_handle( he1 ) );
1193 auto vsplit = topo.to_vertex_handle( he1 );
1194 REQUIRE( vsplit == topo.from_vertex_handle( he0 ) );
1196 auto psplit = topo.point( vsplit );
1197 auto vcheck = ( f * p1 + ( 1_ra - f ) * p0 );
1199 REQUIRE( he0boundary == topo.is_boundary( he0 ) );
1200 REQUIRE( he1boundary == topo.is_boundary( he1 ) );
1203 if ( !he0boundary ) {
1206 auto f0 = topo.
getWedgeData( widx00 ).m_vector4Attrib[0];
1207 auto f1 = topo.
getWedgeData( widx01 ).m_vector4Attrib[0];
1210 auto fsplit = wd.m_vector4Attrib[0];
1211 auto fcheck = ( f * f1 + ( 1_ra - f ) * f0 );
1217 REQUIRE( topo.
getWedgeIndex( topo.prev_halfedge_handle( he0 ) ).isInvalid() );
1220 if ( !he1boundary ) {
1223 auto f0 = topo.
getWedgeData( widx10 ).m_vector4Attrib[0];
1224 auto f1 = topo.
getWedgeData( widx11 ).m_vector4Attrib[0];
1226 auto fsplit = wd.m_vector4Attrib[0];
1227 auto fcheck = ( f * f1 + ( 1_ra - f ) * f0 );
1238 SECTION(
"With continuous wedges." ) {
1239 addSplitScene( points1, colors1, indices1, points1[5], points1[2] );
1242 SECTION(
"with top/bottom wedges" ) {
1243 addSplitScene( points2, colors3, indices2, points1[5], points1[2] );
1246 SECTION(
"with continuous top/bottom wedges" ) {
1247 addSplitScene( points2, colors4, indices2, points1[5], points1[2] );
1249 SECTION(
"with flat face wedges" ) {
1250 addSplitScene( points2, colors2, indices2, points1[5], points1[2] );
1252 SECTION(
"boundary With continuous wedges." ) {
1253 addSplitScene( points1, colors1, indices3, points1[5], points1[2] );
1255 SECTION(
"boundary with top/bottom wedges" ) {
1256 addSplitScene( points2, colors3, indices4, points1[5], points1[2] );
1258 SECTION(
"boundary with continuous top/bottom wedges" ) {
1259 addSplitScene( points2, colors4, indices4, points1[5], points1[2] );
1261 SECTION(
"boundary with flat face wedges" ) {
1262 addSplitScene( points2, colors2, indices4, points1[5], points1[2] );
1266TEST_CASE(
"Core/Geometry/TopologicalMesh/Updates",
1267 "[unittests][Core][Core/Geometry][TopologicalMesh]" ) {
1268 using Ra::Core::Vector3;
1270 using Ra::Core::Geometry::TriangleMesh;
1272 auto testConverter = []( TriangleMesh mesh ) {
1275 for (
auto& v : vertices ) {
1276 v = TriangleMesh::Point( 0_ra, 1_ra, 2_ra );
1281 topologicalMesh.updatePositions( mesh.
vertices() );
1282 for (
auto itr = topologicalMesh.vertices_begin(); itr != topologicalMesh.vertices_end();
1285 topologicalMesh.point( *itr ).isApprox( TriangleMesh::Point( 0_ra, 1_ra, 2_ra ) ) );
1287 topologicalMesh.point( *itr ) = TopologicalMesh::Point( 3_ra, 4_ra, 5_ra );
1294 for (
auto itr = mesh.
vertices().begin(); itr != mesh.
vertices().end(); ++itr ) {
1295 REQUIRE( itr->isApprox( TriangleMesh::Point( 0_ra, 1_ra, 2_ra ) ) );
1302 for (
auto itr = mesh.
vertices().begin(); itr != mesh.
vertices().end(); ++itr ) {
1303 REQUIRE( itr->isApprox( TriangleMesh::Point( 3_ra, 4_ra, 5_ra ) ) );
1307 SECTION(
"Closed mesh" ) {
1308 testConverter( Ra::Core::Geometry::makeBox() );
1309 testConverter( Ra::Core::Geometry::makeSharpBox() );
1312 SECTION(
"Mesh with boundaries" ) {
1313 testConverter( Ra::Core::Geometry::makePlaneGrid( 2, 2 ) );
T back_inserter(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)
Utils::AttribManager & vertexAttribs()
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
bool checkIntegrity() 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
WedgeData newWedgeData() const
unsigned int getWedgeRefCount(const WedgeIndex &idx) const
TriangleMesh toTriangleMesh()
void garbage_collection()
Remove deleted element from the mesh, including wedges.
const WedgeData & getWedgeData(const WedgeIndex &idx) const
void copyPointsPositionToWedges()
HalfedgeHandle halfedge_handle(VertexHandle vh, FaceHandle fh) const
std::string getName() const
Return the attribute's name.
bool hasSameAttribs(const AttribManager &other)
void for_each_attrib(const F &func) const
virtual size_t getSize() const =0
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.
This namespace contains everything "low level", related to data, datastuctures, and computation.