diff --git a/Doc/changelog.docu b/Doc/changelog.docu index 00fbc7f5..b497a8b6 100644 --- a/Doc/changelog.docu +++ b/Doc/changelog.docu @@ -19,6 +19,7 @@
  • TriConnectivity: Added two functions split_edge and split_edge_copy to mask the PolyConnectivity functions of the same name (Prevents creation of valence 2 vertices on trimeshes)
  • PolyConnectivity: Fixed PolyConnectivity is_collapse_ok, missing some configurations (Thanks to Simon Flöry for the patch)
  • Connectivity type is now set at compile time
  • +
  • Added header to interface with Eigen3 vectors as the basic type (Documentation on how to use it is also integrated)
  • IO diff --git a/Doc/mainpage.docu b/Doc/mainpage.docu index 9ce3e3dd..47ddfa3f 100644 --- a/Doc/mainpage.docu +++ b/Doc/mainpage.docu @@ -109,6 +109,7 @@ repeatedly replacing each vertex' position by the center of gravity \li \subpage mesh_operations \li \subpage mesh_hierarchy \li \subpage mesh_type +\li \subpage mesh_eigen \page additional_information Additional Information on OpenMesh diff --git a/Doc/mesh.docu b/Doc/mesh.docu index 488e06da..a506f300 100644 --- a/Doc/mesh.docu +++ b/Doc/mesh.docu @@ -555,6 +555,45 @@ curvature, i.e. vertex color. That's it. +//----------------------------------------------------------------------------- + +/** \page mesh_eigen Specifying an OpenMesh using Eigen3 vectors + +This section will show how to build your own custom mesh type using +Eigen3 vectors for points, normals or other entities. + +First of all you need to include the Eigen header shipped with OpenMesh: +\code +#include +\endcode + +This header contains the external functions and vector traits used by +OpenMesh. + +Afterwards you can specify your mesh: + +\code +struct EigenTraits : OpenMesh::DefaultTraits { + using Point = Eigen::Vector3d; + using Normal = Eigen::Vector3d; + + using TexCoord2D = Eigen::Vector2d; +}; + +using EigenTriMesh = OpenMesh::TriMesh_ArrayKernelT; + +EigenTriMesh mesh; + +\endcode + +Now you can use mesh as any other OpenMesh while using Eigen vectors +as the underlying data type. + +\note OpenMesh uses stl vectors for storing its data. This might lead to errors + regarding memory alignment with sse instructions: + http://eigen.tuxfamily.org/dox/group__TopicStlContainers.html + You might need to define -DEIGEN_DONT_VECTORIZE + */ diff --git a/cmake/FindEIGEN3.cmake b/cmake/FindEIGEN3.cmake new file mode 100644 index 00000000..fa8fc4f7 --- /dev/null +++ b/cmake/FindEIGEN3.cmake @@ -0,0 +1,70 @@ +# - Try to find EIGEN3 +# Once done this will define +# EIGEN3_FOUND - System has EIGEN3 +# EIGEN3_INCLUDE_DIRS - The EIGEN3 include directories + +if (EIGEN3_INCLUDE_DIR) + # in cache already + set(EIGEN3_FOUND TRUE) + set(EIGEN3_INCLUDE_DIRS "${EIGEN3_INCLUDE_DIR}" ) +else (EIGEN3_INCLUDE_DIR) + +# Check if the base path is set +if ( NOT CMAKE_WINDOWS_LIBS_DIR ) + # This is the base directory for windows library search used in the finders we shipp. + set(CMAKE_WINDOWS_LIBS_DIR "c:/libs" CACHE STRING "Default Library search dir on windows." ) +endif() + +if ( CMAKE_GENERATOR MATCHES "^Visual Studio 11.*Win64" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2012/x64/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 11.*" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2012/x32/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 12.*Win64" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2013/x64/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 12.*" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2013/x32/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 14.*Win64" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2015/x64/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 14.*" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2015/x32/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 15.*Win64" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2017/x64/") +elseif ( CMAKE_GENERATOR MATCHES "^Visual Studio 15.*" ) + SET(VS_SEARCH_PATH "${CMAKE_WINDOWS_LIBS_DIR}/vs2017/x32/") +endif() + + +find_path( EIGEN3_INCLUDE_DIR + NAMES Eigen/Dense + PATHS $ENV{EIGEN_DIR} + /usr/include/eigen3 + /usr/local/include + /usr/local/include/eigen3/ + /opt/local/include/eigen3/ + "${CMAKE_WINDOWS_LIBS_DIR}/general/Eigen-3.3.4" + "${CMAKE_WINDOWS_LIBS_DIR}/general/Eigen-3.2.8" + "${CMAKE_WINDOWS_LIBS_DIR}/general/Eigen-3.2.6" + "${CMAKE_WINDOWS_LIBS_DIR}/Eigen-3.2.6" + "${CMAKE_WINDOWS_LIBS_DIR}/Eigen-3.2.6/include" + "${CMAKE_WINDOWS_LIBS_DIR}/Eigen-3.2.1" + "${CMAKE_WINDOWS_LIBS_DIR}/Eigen-3.2.1/include" + "${CMAKE_WINDOWS_LIBS_DIR}/Eigen-3.2/include" + "${CMAKE_WINDOWS_LIBS_DIR}/eigen3/include" + "${CMAKE_WINDOWS_LIBS_DIR}/eigen/include" + ${PROJECT_SOURCE_DIR}/MacOS/Libs/eigen3/include + ../../External/include + ${module_file_path}/../../../External/include + ) + +set(EIGEN3_INCLUDE_DIRS "${EIGEN3_INCLUDE_DIR}" ) + + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBCPLEX_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(EIGEN3 DEFAULT_MSG + EIGEN3_INCLUDE_DIR) + +mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif(EIGEN3_INCLUDE_DIR) diff --git a/src/OpenMesh/Core/Geometry/EigenVectorT.hh b/src/OpenMesh/Core/Geometry/EigenVectorT.hh new file mode 100644 index 00000000..fb7c3be3 --- /dev/null +++ b/src/OpenMesh/Core/Geometry/EigenVectorT.hh @@ -0,0 +1,104 @@ +/* ========================================================================= * + * * + * OpenMesh * + * Copyright (c) 2001-2015, RWTH-Aachen University * + * Department of Computer Graphics and Multimedia * + * All rights reserved. * + * www.openmesh.org * + * * + *---------------------------------------------------------------------------* + * This file is part of OpenMesh. * + *---------------------------------------------------------------------------* + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following conditions * + * are met: * + * * + * 1. Redistributions of source code must retain the above copyright notice, * + * this list of conditions and the following disclaimer. * + * * + * 2. Redistributions in binary form must reproduce the above copyright * + * notice, this list of conditions and the following disclaimer in the * + * documentation and/or other materials provided with the distribution. * + * * + * 3. Neither the name of the copyright holder nor the names of its * + * contributors may be used to endorse or promote products derived from * + * this software without specific prior written permission. * + * * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * + * * + * ========================================================================= */ + +/** This file contains all code required to use Eigen3 vectors as Mesh + * vectors + */ +#pragma once + +#include +#include +#include + + +namespace OpenMesh { + template + struct vector_traits> { + static_assert(_Rows != Eigen::Dynamic && _Cols != Eigen::Dynamic, + "Should not use dynamic vectors."); + static_assert(_Rows == 1 || _Cols == 1, "Should not use matrices."); + + using vector_type = Eigen::Matrix<_Scalar, _Rows, _Cols, _Options>; + using value_type = _Scalar; + static const size_t size_ = _Rows * _Cols; + static size_t size() { return size_; } +}; + +} // namespace OpenMesh + +namespace Eigen { + + template + typename Derived::Scalar dot(const MatrixBase &x, + const MatrixBase &y) { + return x.dot(y); + } + + template + typename MatrixBase< Derived >::PlainObject cross(const MatrixBase &x, const MatrixBase &y) { + return x.cross(y); + } + + template + typename Derived::Scalar norm(const MatrixBase &x) { + return x.norm(); + } + + template + typename Derived::Scalar sqrnorm(const MatrixBase &x) { + return x.dot(x); + } + + template + MatrixBase normalize(MatrixBase &x) { + x /= x.norm(); + return x; + } + + template + MatrixBase &vectorize(MatrixBase &x, + typename Derived::Scalar const &val) { + x.fill(val); + return x; + } + +} // namespace Eigen + diff --git a/src/OpenMesh/Core/Mesh/PolyMeshT.cc b/src/OpenMesh/Core/Mesh/PolyMeshT.cc index 689a2c20..78e6b004 100644 --- a/src/OpenMesh/Core/Mesh/PolyMeshT.cc +++ b/src/OpenMesh/Core/Mesh/PolyMeshT.cc @@ -154,7 +154,15 @@ typename PolyMeshT::Normal PolyMeshT::calc_face_normal_impl(FaceHandle, PointIsNot3DTag) const { // Dummy fallback implementation - return Normal(typename Normal::value_type(0)); + // Returns just an initialized all 0 normal + // This function is only used if we don't hate a matching implementation + // for normal computation with the current vector type defined in the mesh traits + + assert(false); + + Normal normal; + vectorize(normal,0); + return normal; } //----------------------------------------------------------------------------- @@ -212,7 +220,17 @@ template typename PolyMeshT::Normal PolyMeshT::calc_face_normal_impl(const Point&, const Point&, const Point&, PointIsNot3DTag) const { - return Normal(typename Normal::value_type(0)); + + // Dummy fallback implementation + // Returns just an initialized all 0 normal + // This function is only used if we don't hate a matching implementation + // for normal computation with the current vector type defined in the mesh traits + + assert(false); + + Normal normal; + vectorize(normal,0); + return normal; } //----------------------------------------------------------------------------- diff --git a/src/Unittests/CMakeLists.txt b/src/Unittests/CMakeLists.txt index f73e4ddd..7173ddde 100644 --- a/src/Unittests/CMakeLists.txt +++ b/src/Unittests/CMakeLists.txt @@ -17,25 +17,35 @@ if ( OPENMESH_BUILD_UNIT_TESTS ) enable_testing() + find_package(EIGEN3) + # Set correct include paths so that the compiler can find the headers - include_directories(${GTEST_INCLUDE_DIRS}) + include_directories(${GTEST_INCLUDE_DIRS} ) + + # set additional link directories link_directories(${GTEST_LIBRARY_DIR} ) + + if (EIGEN3_FOUND) + add_definitions( -DENABLE_EIGEN3_TEST ) + include_directories(${EIGEN3_INCLUDE_DIR}) + endif() + if ( CMAKE_GENERATOR MATCHES "^Visual Studio 11.*" ) add_definitions( /D _VARIADIC_MAX=10 ) endif() # Create new target named unittests_hexmeshing FILE(GLOB UNITTEST_SRC *.cc) - # Create unittest executable - acg_add_executable(unittests ${UNITTEST_SRC}) - acg_add_executable(unittests_customvec ${UNITTEST_SRC}) - target_compile_definitions(unittests_customvec PRIVATE TEST_CUSTOM_TRAITS) + # Create unittest executable + acg_add_executable(unittests ${UNITTEST_SRC}) + acg_add_executable(unittests_customvec ${UNITTEST_SRC}) + target_compile_definitions(unittests_customvec PRIVATE TEST_CUSTOM_TRAITS) - # For the unittest we don't want the install rpath as set by acg_add_executable - set_target_properties ( unittests PROPERTIES BUILD_WITH_INSTALL_RPATH 0 ) - set_target_properties ( unittests_customvec PROPERTIES BUILD_WITH_INSTALL_RPATH 0 ) + # For the unittest we don't want the install rpath as set by acg_add_executable + set_target_properties ( unittests PROPERTIES BUILD_WITH_INSTALL_RPATH 0 ) + set_target_properties ( unittests_customvec PROPERTIES BUILD_WITH_INSTALL_RPATH 0 ) # Set output directory to ${BINARY_DIR}/Unittests set (OUTPUT_DIR "${CMAKE_BINARY_DIR}/Unittests") diff --git a/src/Unittests/unittests_eigen3_type.cc b/src/Unittests/unittests_eigen3_type.cc new file mode 100644 index 00000000..6d263220 --- /dev/null +++ b/src/Unittests/unittests_eigen3_type.cc @@ -0,0 +1,237 @@ + +#ifdef ENABLE_EIGEN3_TEST + +#include +#include +#include + +#include +#include + +#include + +struct EigenTraits : OpenMesh::DefaultTraits { + using Point = Eigen::Vector3d; + using Normal = Eigen::Vector3d; + + using TexCoord2D = Eigen::Vector2d; +}; + +using EigenTriMesh = OpenMesh::TriMesh_ArrayKernelT; + +namespace { + + +class OpenMeshEigenTest : public testing::Test { + + protected: + + // This function is called before each test is run + virtual void SetUp() { + + // Do some initial stuff with the member data here... + } + + // This function is called after all tests are through + virtual void TearDown() { + + + } + + EigenTriMesh mesh_; +}; + +TEST_F(OpenMeshEigenTest, Test_external_vectorize) { + + + EigenTriMesh::Normal normal; + + vectorize(normal,2); + + EXPECT_EQ(normal[0],2.0f ) << "Wrong x value"; + EXPECT_EQ(normal[1],2.0f ) << "Wrong y value"; + EXPECT_EQ(normal[2],2.0f ) << "Wrong z value"; + + +} + +TEST_F(OpenMeshEigenTest, Test_external_norm) { + + + EigenTriMesh::Normal normal(1,0,0); + + EigenTriMesh::Scalar result = norm(normal); + + EXPECT_EQ(result,1.0f ) << "Wrong norm"; + + normal = EigenTriMesh::Normal(2,0,0); + + result = norm(normal); + + EXPECT_EQ(result,2.0f ) << "Wrong norm"; +} + +TEST_F(OpenMeshEigenTest, Test_external_sqrnorm) { + + + EigenTriMesh::Normal normal(1,0,0); + + EigenTriMesh::Scalar result = sqrnorm(normal); + + EXPECT_EQ(result,1.0f ) << "Wrong norm"; + + normal = EigenTriMesh::Normal(2,0,0); + + result = sqrnorm(normal); + + EXPECT_EQ(result,4.0f ) << "Wrong norm"; +} + +TEST_F(OpenMeshEigenTest, Test_external_normalize) { + + + EigenTriMesh::Normal normal(2,0,0); + + normalize(normal); + + EXPECT_EQ(norm(normal),1.0f ) << "Wrong norm after normalization"; + + normal = EigenTriMesh::Normal(2,6,9); + + normalize(normal); + + EXPECT_EQ(norm(normal),1.0f ) << "Wrong norm after normalization"; + +} + +TEST_F(OpenMeshEigenTest, Test_external_cross_Product) { + + + EigenTriMesh::Normal normal1(1,0,0); + EigenTriMesh::Normal normal2(1,1,0); + + EigenTriMesh::Normal result = cross(normal1,normal2); + + EXPECT_EQ(result[0],0.0f ) << "Wrong result x direction"; + EXPECT_EQ(result[1],0.0f ) << "Wrong result y direction"; + EXPECT_EQ(result[2],1.0f ) << "Wrong result z direction"; +} + +TEST_F(OpenMeshEigenTest, Test_external_dot_Product) { + + + EigenTriMesh::Normal normal1(1,0,0); + EigenTriMesh::Normal normal2(1,1,0); + EigenTriMesh::Normal normal3(1,1,1); + EigenTriMesh::Normal normal4(2,4,6); + + EigenTriMesh::Scalar result = dot(normal1,normal2); + EigenTriMesh::Scalar result1 = dot(normal3,normal4); + + EXPECT_EQ(result,1.0f ) << "Wrong dot product"; + EXPECT_EQ(result1,12.0f ) << "Wrong dot product"; + +} + + +TEST_F(OpenMeshEigenTest, Test_Basic_setup) { + + // Add some vertices + EigenTriMesh::VertexHandle vhandle[4]; + + vhandle[0] = mesh_.add_vertex(EigenTriMesh::Point(0, 0, 0)); + vhandle[1] = mesh_.add_vertex(EigenTriMesh::Point(0, 1, 0)); + vhandle[2] = mesh_.add_vertex(EigenTriMesh::Point(1, 1, 0)); + vhandle[3] = mesh_.add_vertex(EigenTriMesh::Point(1, 0, 0)); + + // Add two faces + std::vector face_vhandles; + + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[0]); + + mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[0]); + face_vhandles.push_back(vhandle[3]); + mesh_.add_face(face_vhandles); + + + EXPECT_EQ(mesh_.n_faces(),2) << "Wrong number of faces"; + +} + +TEST_F(OpenMeshEigenTest, test_normal_computation) { + + // Add some vertices + EigenTriMesh::VertexHandle vhandle[4]; + + mesh_.request_vertex_normals(); + mesh_.request_face_normals(); + + vhandle[0] = mesh_.add_vertex(EigenTriMesh::Point(0, 0, 0)); + vhandle[1] = mesh_.add_vertex(EigenTriMesh::Point(0, 1, 0)); + vhandle[2] = mesh_.add_vertex(EigenTriMesh::Point(1, 1, 0)); + vhandle[3] = mesh_.add_vertex(EigenTriMesh::Point(1, 0, 0)); + + // Add two faces + std::vector face_vhandles; + + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[1]); + face_vhandles.push_back(vhandle[0]); + + EigenTriMesh::FaceHandle face1 = mesh_.add_face(face_vhandles); + + face_vhandles.clear(); + + face_vhandles.push_back(vhandle[2]); + face_vhandles.push_back(vhandle[0]); + face_vhandles.push_back(vhandle[3]); + EigenTriMesh::FaceHandle face2 = mesh_.add_face(face_vhandles); + + mesh_.update_face_normals(); + + + EXPECT_EQ(mesh_.n_faces(),2) << "Wrong number of faces"; + + EigenTriMesh::Normal normal = mesh_.normal(face1); + + EXPECT_EQ(normal[0],0.0f ) << "Wrong normal x direction"; + EXPECT_EQ(normal[1],0.0f ) << "Wrong normal y direction"; + EXPECT_EQ(normal[2],1.0f ) << "Wrong normal z direction"; + + normal = mesh_.normal(face2); + + EXPECT_EQ(normal[0],0.0f ) << "Wrong normal x direction"; + EXPECT_EQ(normal[1],0.0f ) << "Wrong normal y direction"; + EXPECT_EQ(normal[2],1.0f ) << "Wrong normal z direction"; + +} + +/* Just load a simple mesh file in obj format and count whether +* the right number of entities has been loaded. Afterwards recompute +* normals +*/ +TEST_F(OpenMeshEigenTest, LoadSimpleOFFFile) { + + mesh_.clear(); + + bool ok = OpenMesh::IO::read_mesh(mesh_, "cube1.off"); + + EXPECT_TRUE(ok); + + EXPECT_EQ(7526u , mesh_.n_vertices()) << "The number of loaded vertices is not correct!"; + EXPECT_EQ(22572u, mesh_.n_edges()) << "The number of loaded edges is not correct!"; + EXPECT_EQ(15048u, mesh_.n_faces()) << "The number of loaded faces is not correct!"; + + mesh_.update_normals(); +} + +} + +#endif