Explorar o código

Check in unittest directory setup and a few tests.

Former-commit-id: 6c287b132f355f278129c975b2c59ce9595d4d84
Qingnan Zhou %!s(int64=9) %!d(string=hai) anos
pai
achega
6536c33d1c

+ 28 - 0
tests/CMakeLists.txt

@@ -0,0 +1,28 @@
+# This file is part of libigl, a simple c++ geometry processing library.
+# 
+# Copyright (C) 2015 Qingnan Zhou <qnzhou@gmail.com>
+# 
+# This Source Code Form is subject to the terms of the Mozilla Public License 
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+# obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file is based on PyMesh's unit test setup.
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+
+# Set compiler
+IF(APPLE)
+    SET(CMAKE_C_COMPILER "clang")
+    SET(CMAKE_CXX_COMPILER "clang++")
+ELSEIF(UNIX)
+    SET(CMAKE_C_COMPILER "gcc")
+    SET(CMAKE_CXX_COMPILER "g++")
+ELSEIF(WIN32)
+    # TODO: not how the code would work on Windows.
+ENDIF()
+
+PROJECT(libigl_unit_tests)
+
+INCLUDE(Settings.cmake)
+
+# Process code in each subdirectories
+ADD_SUBDIRECTORY(${PROJECT_SOURCE_DIR}/include/igl)

+ 54 - 0
tests/Settings.cmake

@@ -0,0 +1,54 @@
+# This file is part of libigl, a simple c++ geometry processing library.
+# 
+# Copyright (C) 2015 Qingnan Zhou <qnzhou@gmail.com>
+# 
+# This Source Code Form is subject to the terms of the Mozilla Public License 
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+# obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file is based on PyMesh's unit test setup.
+
+# Include directories to search for source.
+INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src)
+GET_FILENAME_COMPONENT(LIBIGL_PATH ${PROJECT_SOURCE_DIR} DIRECTORY)
+INCLUDE_DIRECTORIES(${LIBIGL_PATH}/include/)
+
+# Set build type.
+SET(CMAKE_BUILD_TYPE Debug)
+#SET(CMAKE_BUILD_TYPE Release)
+
+# Create 64 bits binary.  32 bits support is dropped.
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
+SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os")
+SET(CMAKE_LIBRARY_PATH /opt/local/lib ${CMAKE_LIBRARY_PATH})
+
+# Set output directories
+SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
+MAKE_DIRECTORY(${EXECUTABLE_OUTPUT_PATH})
+
+LINK_DIRECTORIES(/opt/local/lib)
+
+SET(CMAKE_MACOSX_RPATH ON)
+
+# Set TEST_DIR definition
+ADD_DEFINITIONS(-DTEST_DIR="${PROJECT_SOURCE_DIR}")
+
+# Include current directory
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+
+# Include Eigen
+#SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
+SET(CMAKE_MODULE_PATH ${LIBIGL_PATH}/tutorial/cmake)
+FIND_PACKAGE(Eigen REQUIRED)
+INCLUDE_DIRECTORIES(${EIGEN_INCLUDE_DIRS})
+INCLUDE_DIRECTORIES(${EIGEN_INCLUDE_DIRS}/unsupported)
+ADD_DEFINITIONS(-DEIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET)
+
+# Add googletest googlemock support
+ADD_SUBDIRECTORY(${PROJECT_SOURCE_DIR}/googletest/googlemock)
+SET(GTEST_BOTH_LIBRARIES gtest gmock)
+INCLUDE_DIRECTORIES(${gmock_SOURCE_DIR})
+INCLUDE_DIRECTORIES(${gmock_SOURCE_DIR}/include)
+INCLUDE_DIRECTORIES(${gtest_SOURCE_DIR})
+INCLUDE_DIRECTORIES(${gtest_SOURCE_DIR}/include)

+ 21 - 0
tests/data/cube.obj

@@ -0,0 +1,21 @@
+# Generated with PyMesh
+v -0.5 -0.5 -0.5 
+v 0.5 -0.5 -0.5 
+v 0.5 0.5 -0.5 
+v -0.5 0.5 -0.5 
+v -0.5 -0.5 0.5 
+v 0.5 -0.5 0.5 
+v 0.5 0.5 0.5 
+v -0.5 0.5 0.5 
+f 1 3 2 
+f 1 4 3 
+f 6 1 2 
+f 6 5 1 
+f 3 7 2 
+f 4 7 3 
+f 6 2 7 
+f 6 7 5 
+f 1 8 4 
+f 1 5 8 
+f 4 8 7 
+f 7 8 5 

BIN=BIN
tests/data/duplicated_faces_F.dmat


BIN=BIN
tests/data/duplicated_faces_N1.dmat


BIN=BIN
tests/data/duplicated_faces_N2.dmat


BIN=BIN
tests/data/duplicated_faces_V.dmat


BIN=BIN
tests/data/two-boxes-bad-self-union.ply


+ 9 - 0
tests/include/igl/CMakeLists.txt

@@ -0,0 +1,9 @@
+FILE(GLOB TEST_SRC_FILES *.cpp main.cpp)
+FILE(GLOB TEST_INC_FILES *.h *.inl)
+
+ADD_EXECUTABLE(igl_tests ${TEST_SRC_FILES} ${TEST_INC_FILES})
+TARGET_LINK_LIBRARIES(igl_tests ${GTEST_BOTH_LIBRARIES})
+ADD_CUSTOM_COMMAND(TARGET igl_tests POST_BUILD COMMAND igl_tests)
+
+ADD_SUBDIRECTORY(cgal)
+ADD_SUBDIRECTORY(boolean)

+ 19 - 0
tests/include/igl/boolean/CMakeLists.txt

@@ -0,0 +1,19 @@
+# Check if CGAL is available
+IF (NOT CGAL_FOUND)
+    IF (DEFINED ENV{CGAL_PATH} AND NOT DEFINED ENV{CGAL_DIR})
+        SET(CGAL_DIR $ENV{CGAL_PATH})
+    ENDIF (DEFINED ENV{CGAL_PATH} AND NOT DEFINED ENV{CGAL_DIR})
+    SET(CGAL_DONT_OVERRIDE_CMAKE_FLAGS TRUE CACHE BOOL
+        "Disable CGAL from overwriting my cmake flags")
+    FIND_PACKAGE(CGAL QUIET)
+    INCLUDE(${CGAL_USE_FILE})
+ENDIF (NOT CGAL_FOUND)
+
+IF (CGAL_FOUND)
+    FILE(GLOB TEST_SRC_FILES *.cpp main.cpp)
+    FILE(GLOB TEST_INC_FILES *.h *.inl)
+
+    ADD_EXECUTABLE(igl_boolean_tests ${TEST_SRC_FILES} ${TEST_INC_FILES})
+    TARGET_LINK_LIBRARIES(igl_boolean_tests ${GTEST_BOTH_LIBRARIES} ${CGAL_LIBRARIES})
+    ADD_CUSTOM_COMMAND(TARGET igl_boolean_tests POST_BUILD COMMAND igl_boolean_tests)
+ENDIF (CGAL_FOUND)

+ 6 - 0
tests/include/igl/boolean/main.cpp

@@ -0,0 +1,6 @@
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+      ::testing::InitGoogleTest(&argc, argv);
+        return RUN_ALL_TESTS();
+}

+ 26 - 0
tests/include/igl/boolean/mesh_boolean.cpp

@@ -0,0 +1,26 @@
+#include <test_common.h>
+
+#include <igl/boolean/mesh_boolean.h>
+#include <igl/boolean/MeshBooleanType.h>
+#include <igl/exterior_edges.h>
+
+TEST(MeshBoolean, TwoCubes) {
+    Eigen::MatrixXd V1;
+    Eigen::MatrixXi F1;
+    test_common::load_mesh("two-boxes-bad-self-union.ply", V1, F1);
+
+    Eigen::MatrixXd V2(0, 3);
+    Eigen::MatrixXi F2(0, 3);
+
+    Eigen::MatrixXd Vo;
+    Eigen::MatrixXi Fo;
+
+    igl::boolean::mesh_boolean(V1, F1, V2, F2,
+            igl::boolean::MESH_BOOLEAN_TYPE_UNION,
+            Vo, Fo);
+
+    Eigen::MatrixXi Eb;
+    igl::exterior_edges(Fo, Eb);
+
+    ASSERT_EQ(0, Eb.rows());
+}

+ 17 - 0
tests/include/igl/cgal/CMakeLists.txt

@@ -0,0 +1,17 @@
+# Check if CGAL is available
+IF (DEFINED ENV{CGAL_PATH} AND NOT DEFINED ENV{CGAL_DIR})
+    SET(CGAL_DIR $ENV{CGAL_PATH})
+ENDIF (DEFINED ENV{CGAL_PATH} AND NOT DEFINED ENV{CGAL_DIR})
+SET(CGAL_DONT_OVERRIDE_CMAKE_FLAGS TRUE CACHE BOOL
+    "Disable CGAL from overwriting my cmake flags")
+FIND_PACKAGE(CGAL QUIET)
+INCLUDE(${CGAL_USE_FILE})
+
+IF (CGAL_FOUND)
+    FILE(GLOB TEST_SRC_FILES *.cpp main.cpp)
+    FILE(GLOB TEST_INC_FILES *.h *.inl)
+
+    ADD_EXECUTABLE(igl_cgal_tests ${TEST_SRC_FILES} ${TEST_INC_FILES})
+    TARGET_LINK_LIBRARIES(igl_cgal_tests ${GTEST_BOTH_LIBRARIES} ${CGAL_LIBRARIES})
+    ADD_CUSTOM_COMMAND(TARGET igl_cgal_tests POST_BUILD COMMAND igl_cgal_tests)
+ENDIF (CGAL_FOUND)

+ 6 - 0
tests/include/igl/cgal/main.cpp

@@ -0,0 +1,6 @@
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+      ::testing::InitGoogleTest(&argc, argv);
+        return RUN_ALL_TESTS();
+}

+ 187 - 0
tests/include/igl/cgal/order_facets_around_edges.cpp

@@ -0,0 +1,187 @@
+#include <test_common.h>
+
+#include <algorithm>
+#include <iostream>
+#include <vector>
+
+#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
+#include <igl/cgal/order_facets_around_edges.h>
+#include <igl/unique_edge_map.h>
+#include <igl/readDMAT.h>
+#include <igl/per_face_normals.h>
+
+namespace order_facets_around_edges_test {
+
+typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
+
+template<typename T>
+size_t index_of(const std::vector<T>& array, T val) {
+    auto loc = std::find(array.begin(), array.end(), val);
+    assert(loc != array.end());
+    return loc - array.begin();
+}
+
+void assert_consistently_oriented(size_t num_faces,
+        const std::vector<int>& expected_face_order,
+        const std::vector<int>& e_order) {
+    const size_t num_items = expected_face_order.size();
+    ASSERT_EQ(num_items, e_order.size());
+
+    std::vector<int> order(num_items);
+    std::transform(e_order.begin(), e_order.end(), order.begin(),
+            [=](int val) { return val % num_faces; });
+
+    size_t ref_start = index_of(order, expected_face_order[0]);
+    for (size_t i=0; i<num_items; i++) {
+        ASSERT_EQ(expected_face_order[i], order[(ref_start + i) % num_items]);
+    }
+}
+
+template<typename DerivedV, typename DerivedF>
+void assert_order(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        size_t v0, size_t v1,
+        std::vector<int> expected_order, const std::string& normal="") {
+    Eigen::MatrixXi E, uE, EMAP;
+    std::vector<std::vector<int> > uE2E;
+    igl::unique_edge_map(F, E, uE, EMAP, uE2E);
+
+    std::vector<std::vector<int> > uE2oE;
+    std::vector<std::vector<bool> > uE2C;
+
+    if (normal != "") {
+        Eigen::MatrixXd N;
+        //igl::per_face_normals_stable(V, F, N);
+        //igl::per_face_normals(V, F, N);
+        test_common::load_matrix(normal, N);
+        igl::cgal::order_facets_around_edges(V, F, N, E, uE, EMAP, uE2E, uE2oE, uE2C);
+    } else {
+        igl::cgal::order_facets_around_edges(V, F, E, uE, EMAP, uE2E, uE2oE, uE2C);
+    }
+
+    const size_t num_uE = uE.rows();
+    for (size_t i=0; i<num_uE; i++) {
+        const auto& order = uE2oE[i];
+        const auto& cons  = uE2C[i];
+        Eigen::VectorXi e = uE.row(i);
+        if (order.size() <= 1) continue;
+        if (e[0] != v0 && e[0] != v1) continue;
+        if (e[1] != v0 && e[1] != v1) continue;
+        if (e[0] == v1 && e[1] == v0) {
+            std::reverse(expected_order.begin(), expected_order.end());
+        }
+        assert_consistently_oriented(F.rows(), expected_order, order);
+    }
+}
+
+TEST(OrderFacetsAroundEdges, Simple) {
+    Eigen::MatrixXd V(4, 3);
+    V << 0.0, 0.0, 0.0,
+         1.0, 0.0, 0.0,
+         0.0, 1.0, 0.0,
+         1.0, 1.0, 0.0;
+    Eigen::MatrixXi F(2, 3);
+    F << 0, 1, 2,
+         2, 1, 3;
+
+    assert_order(V, F, 1, 2, {0, 1});
+}
+
+TEST(OrderFacetsAroundEdges, TripletFaces) {
+    Eigen::MatrixXd V(5, 3);
+    V << 0.0, 0.0, 0.0,
+         1.0, 0.0, 0.0,
+         0.0, 1.0, 0.0,
+         1.0, 1.0, 0.0,
+         0.0, 0.0, 1.0;
+    Eigen::MatrixXi F(3, 3);
+    F << 0, 1, 2,
+         2, 1, 3,
+         1, 2, 4;
+
+    assert_order(V, F, 1, 2, {0, 1, 2});
+}
+
+TEST(OrderFacetsAroundEdges, DuplicatedFaces) {
+    Eigen::MatrixXd V(5, 3);
+    V << 0.0, 0.0, 0.0,
+         1.0, 0.0, 0.0,
+         0.0, 1.0, 0.0,
+         1.0, 1.0, 0.0,
+         0.0, 0.0, 1.0;
+    Eigen::MatrixXi F(4, 3);
+    F << 0, 1, 2,
+         2, 1, 3,
+         1, 2, 4,
+         4, 1, 2;
+
+    assert_order(V, F, 1, 2, {0, 1, 3, 2});
+}
+
+TEST(OrderFacetsAroundEdges, MultipleDuplicatedFaces) {
+    Eigen::MatrixXd V(5, 3);
+    V << 0.0, 0.0, 0.0,
+         1.0, 0.0, 0.0,
+         0.0, 1.0, 0.0,
+         1.0, 1.0, 0.0,
+         0.0, 0.0, 1.0;
+    Eigen::MatrixXi F(6, 3);
+    F << 0, 1, 2,
+         1, 2, 0,
+         2, 1, 3,
+         1, 3, 2,
+         1, 2, 4,
+         4, 1, 2;
+
+    assert_order(V, F, 1, 2, {1, 0, 2, 3, 5, 4});
+}
+
+TEST(OrderFacetsAroundEdges, Debug) {
+    Eigen::MatrixXd V(5, 3);
+    V <<
+        -44.3205080756887781, 4.22994972382184579e-15, 75,
+        -27.933756729740665, -48.382685902179837, 75,
+        -55.8675134594812945, -2.81996648254789745e-15, 75,
+        -27.933756729740665, -48.382685902179837, 70,
+        -31.4903810567666049, -42.2224318643354408, 85;
+
+    Eigen::MatrixXi F(3, 3);
+    F << 1, 0, 2,
+         2, 3, 1,
+         4, 1, 2;
+
+    assert_order(V, F, 1, 2, {0, 2, 1});
+}
+
+TEST(OrderFacetsAroundEdges, Debug2) {
+    Eigen::MatrixXd V(5, 3);
+    V <<
+        -22.160254037844382, 38.3826859021798441, 75,
+        -27.9337567297406331, 48.3826859021798654, 75,
+        27.9337567297406544, 48.3826859021798512, 75,
+        27.9337567297406544, 48.3826859021798512, 70,
+        20.8205080756887924, 48.3826859021798512, 85;
+    Eigen::MatrixXi F(3, 3);
+    F << 1, 0, 2,
+         3, 1, 2,
+         2, 4, 1;
+
+    assert_order(V, F, 1, 2, {1, 0, 2});
+}
+
+TEST(OrderFacetsAroundEdges, NormalSensitivity) {
+    // This example shows that epsilon difference in normal vectors could
+    // results in very different ordering of facets.
+
+    Eigen::MatrixXd V;
+    test_common::load_matrix("duplicated_faces_V.dmat", V);
+    Eigen::MatrixXi F;
+    test_common::load_matrix("duplicated_faces_F.dmat", F);
+
+    assert_order(V, F, 223, 224, {2, 0, 3, 1}, "duplicated_faces_N1.dmat");
+    assert_order(V, F, 223, 224, {0, 3, 2, 1}, "duplicated_faces_N2.dmat");
+}
+
+
+}

+ 51 - 0
tests/include/igl/cgal/peel_outer_hull_layers.cpp

@@ -0,0 +1,51 @@
+#include <test_common.h>
+#include <iostream>
+#include <Eigen/Dense>
+
+#include <igl/cgal/peel_outer_hull_layers.h>
+#include <igl/cgal/remesh_self_intersections.h>
+#include <igl/cgal/RemeshSelfIntersectionsParam.h>
+#include <igl/per_face_normals.h>
+#include <igl/remove_unreferenced.h>
+#include <igl/writeOBJ.h>
+
+#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
+
+TEST(PeelOuterHullLayers, TwoCubes) {
+    Eigen::MatrixXd V;
+    Eigen::MatrixXi F;
+    test_common::load_mesh("two-boxes-bad-self-union.ply", V, F);
+    ASSERT_EQ(486, V.rows());
+    ASSERT_EQ(708, F.rows());
+
+    typedef CGAL::Exact_predicates_exact_constructions_kernel K;
+    typedef K::FT Scalar;
+    typedef Eigen::Matrix<Scalar,
+            Eigen::Dynamic,
+            Eigen::Dynamic> MatrixXe;
+
+    MatrixXe Vs;
+    Eigen::MatrixXi Fs, IF;
+    Eigen::VectorXi J, IM;
+    igl::cgal::RemeshSelfIntersectionsParam param;
+    igl::cgal::remesh_self_intersections(V, F, param, Vs, Fs, IF, J, IM);
+
+    std::for_each(Fs.data(),Fs.data()+Fs.size(),
+            [&IM](int & a){ a=IM(a); });
+    MatrixXe Vt;
+    Eigen::MatrixXi Ft;
+    igl::remove_unreferenced(Vs,Fs,Vt,Ft,IM);
+    const size_t num_faces = Ft.rows();
+
+    Eigen::VectorXi I, flipped;
+    size_t num_peels = igl::cgal::peel_outer_hull_layers(Vt, Ft, I, flipped);
+
+    Eigen::MatrixXd vertices(Vt.rows(), Vt.cols());
+    std::transform(Vt.data(), Vt.data() + Vt.rows() * Vt.cols(),
+            vertices.data(), [](Scalar v) { return CGAL::to_double(v); });
+    igl::writeOBJ("debug.obj", vertices, Ft);
+
+    ASSERT_EQ(num_faces, I.rows());
+    ASSERT_EQ(0, I.minCoeff());
+    ASSERT_EQ(1, I.maxCoeff());
+}

+ 6 - 0
tests/include/igl/main.cpp

@@ -0,0 +1,6 @@
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+      ::testing::InitGoogleTest(&argc, argv);
+        return RUN_ALL_TESTS();
+}

+ 19 - 0
tests/include/igl/readDMAT.cpp

@@ -0,0 +1,19 @@
+#include <test_common.h>
+
+TEST(readDMAT, Comp) {
+    Eigen::MatrixXd N1, N2;
+    test_common::load_matrix("duplicated_faces_N1.dmat", N1);
+    test_common::load_matrix("duplicated_faces_N2.dmat", N2);
+
+    ASSERT_EQ(N1.rows(), N2.rows());
+    ASSERT_EQ(N1.cols(), N2.cols());
+    ASSERT_FALSE(((N1-N2).array() != 0.0).all());
+
+    const size_t rows = N1.rows();
+    const size_t cols = N1.cols();
+    for (size_t i=0; i<rows; i++) {
+        for (size_t j=0; j<cols; j++) {
+            ASSERT_FLOAT_EQ(N1(i,j), N2(i,j));
+        }
+    }
+}

+ 9 - 0
tests/include/igl/readOBJ.cpp

@@ -0,0 +1,9 @@
+#include <test_common.h>
+
+TEST(readOBJ, simple) {
+    Eigen::MatrixXd V;
+    Eigen::MatrixXi F;
+    test_common::load_mesh("cube.obj", V, F);
+    ASSERT_EQ(8, V.rows());
+    ASSERT_EQ(12, F.rows());
+}

+ 27 - 0
tests/test_common.h

@@ -0,0 +1,27 @@
+#pragma once
+#include <string>
+
+#include <Eigen/Core>
+#include <gtest/gtest.h>
+
+#include <igl/read_triangle_mesh.h>
+#include <igl/readDMAT.h>
+
+namespace test_common {
+    template<typename DerivedV, typename DerivedF>
+    void load_mesh(
+            const std::string& filename, 
+            Eigen::PlainObjectBase<DerivedV>& V,
+            Eigen::PlainObjectBase<DerivedF>& F) {
+        auto find_file = [&](const std::string& val) {
+            return std::string(TEST_DIR) + "/data/" + val;
+        };
+        igl::read_triangle_mesh(find_file(filename), V, F);
+    }
+
+    template<typename Derived>
+    void load_matrix(const std::string& filename,
+            Eigen::PlainObjectBase<Derived>& M) {
+        igl::readDMAT(std::string(TEST_DIR) + "/data/" + filename, M);
+    }
+}