kzampog / cilantro

A lean C++ library for working with point cloud data
MIT License
1.01k stars 206 forks source link

ExternalProject and Linking, is Cilantro header-only library? #65

Closed petrasvestartas closed 2 years ago

petrasvestartas commented 2 years ago

Hi,

I would like to ask if Cilantro is a header-only library?

I clone the repository using ExternalProject via CMake. How you would like to an executable with Cilantro and Eigen? Do I need to compile the cilantro as a library and then link to executable or it is possible to link like header libraries? Example with CMake would be much appreciated.

########################################################################
# REFERENCES AND BASH COMMANDS
########################################################################
# bash commands
# cmake -DGET_LIBS=ON -DBUILD_MY_PROJECTS=OFF  -DBUILD_SHARED_LIBS=ON -G "Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
# cmake -DGET_LIBS=OFF -DBUILD_MY_PROJECTS=ON  -DBUILD_SHARED_LIBS=ON -G "Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
# Release\my_exe

########################################################################
#PROJECT INTIALIZATION
########################################################################
project(superbuild LANGUAGES CXX)
cmake_minimum_required(VERSION 3.19)

set(CMAKE_BUILD_TYPE_INIT "Release")

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

########################################################################
# START SUPERBUILD
########################################################################

SET(GET_LIBS "" CACHE STRING "Set option to download dependencies")

cmake_policy(SET CMP0097 NEW)
include(ExternalProject)

set(LIB_DEBUG_SUFFIX "")
if (MSVC)
  set(LIB_DEBUG_SUFFIX "d")
endif ()

SET(GET_LIBS "" CACHE STRING "Set option to download dependencies")
if (GET_LIBS)
  message(AUTHOR_WARNING "GET_LIBS_" ${GET_LIBS})

  ########################################################################
  # EIGEN
  ########################################################################
  ExternalProject_Add(eigen
    GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
    GIT_TAG 3.4.0
    CMAKE_ARGS
      -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
      -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
      -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install/eigen #has not effect
    SOURCE_DIR   "${CMAKE_BINARY_DIR}/install/eigen" #install directory is in build/install/eigen
    BUILD_COMMAND "" #do not build
    INSTALL_COMMAND "" #do not install
  )

  ########################################################################
  # CILANTRO
  ########################################################################
  ExternalProject_Add(cilantro
    GIT_REPOSITORY https://github.com/kzampog/cilantro.git
    CMAKE_ARGS
      -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
      -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
      -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install/eigen #has not effect
    SOURCE_DIR   "${CMAKE_BINARY_DIR}/install/cilantro" #install directory is in build/install/eigen
    BUILD_COMMAND "" #do not build
    INSTALL_COMMAND "" #do not install
  )

endif ()

###############################################################################
#EXECUTABLE THANK LINK HEADER ONLY DIRECTORY
###############################################################################
SET(BUILD_MY_PROJECTS "" CACHE STRING "Build Project")
if (BUILD_MY_PROJECTS)
  message(AUTHOR_WARNING "BUILD_MY_PROJECTS_" ${BUILD_MY_PROJECTS})
  add_executable(my_exe main.cpp)

list(APPEND includePath 
"${CMAKE_BINARY_DIR}/install/cilantro/"
"${CMAKE_BINARY_DIR}/install/eigen/"
)

  #target_include_directories(my_exe PRIVATE "${CMAKE_BINARY_DIR}/install/boost/include/boost-1_78/") #add directory of the EIGEN header-only library 
  target_include_directories(my_exe PRIVATE "$<BUILD_INTERFACE:${includePath}>") #add directory of the EIGEN header-only library 

endif ()
kzampog commented 2 years ago

Hello!

The visualization and convex hull components are not header-only (and the former will only build if Pangolin is found), so you'd need to build the library first (i.e. as in here). Installing is optional (find_package should be able to directly discover the library's build folder under the default settings).

After cilantro has been built (and optionally installed), something like this should work:

cmake_minimum_required(VERSION 3.9)
project(my_project)

find_package(cilantro)

add_executable(my_app my_app.cpp)
target_link_libraries(my_app cilantro)

as a minimal CMake example for a project using the library.

I have no experience using ExternalProject_Add, but I would be happy to try to help if you run into any issues!

petrasvestartas commented 2 years ago

Add_ExternalProject is just a command to clone github repo to current project build/install folder. I do the same for Eigen. Meaning find_package command is not needed.

I do not need Pangolin, but I would like to learn how to compile Cilantro into a library using cmake. Since I am beginner in CMake.

Would it be possible for you to test the example above and adapt it so that it compiles Cilantro as a library?

kzampog commented 2 years ago

I'm attaching a minimal CMake project that uses ExternalProject_Add, based on these: https://crascit.com/2015/07/25/cmake-gtest/ https://github.com/Crascit/DownloadProject It downloads/configures external projects at configuration time.

This should work:

mkdir build
cd build
cmake -DGET_LIBS=ON -DBUILD_MY_PROJECTS=ON ..
make -j

I don't know what would be a good way to pass down configuration parameters, as this relies on add_subdirectory... There may be better ways to do this (I'm not really experienced with CMake either!). (An alternative would be to build cilantro externally and use the tiny snippet I shared above :D)

external_project.tar.gz

petrasvestartas commented 2 years ago

Thank you for reply,

I am wondering if it is possible to simplify the example. Wondering how the library can be directly compiled after repository is downloaded using "add_externalproject" command at build time. Do you have any example for this command too?

kzampog commented 2 years ago

I have no prior experience using ExternalProject_Add, but using it directly seems to be pretty cumbersome! :sweat_smile: This looks like a decent example if you want to go down that route: https://stackoverflow.com/questions/15175318/cmake-how-to-build-external-projects-and-include-their-targets

Alternatively you could try FetchContent:

cmake_minimum_required(VERSION 3.19)
project(superbuild LANGUAGES CXX)

cmake_policy(SET CMP0097 NEW)

set(CMAKE_BUILD_TYPE_INIT "Release")

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Download deps
include(FetchContent)

FetchContent_Declare(
  eigen
  GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
  GIT_TAG 3.4.0
)
FetchContent_MakeAvailable(eigen)

FetchContent_Declare(
  cilantro
  GIT_REPOSITORY https://github.com/kzampog/cilantro.git
  GIT_TAG master
)
FetchContent_MakeAvailable(cilantro)

# Build app
add_executable(my_exe main.cpp)
target_link_libraries(my_exe PRIVATE Eigen3::Eigen cilantro)
petrasvestartas commented 2 years ago

Thank you for the help. It works for examples that only employs header-only methods like kd-tree.cpp.

But it does not work for other of your examples, like the one below. Do you have any ideas what causes the error?

image


#include <cilantro/visualization.hpp>
#include <cilantro/spatial/convex_polytope.hpp>
#include <cilantro/utilities/point_cloud.hpp>

void run_demo() {
    std::vector<Eigen::Vector3f> points;
    points.emplace_back(0,0,0);
    points.emplace_back(1,0,0);
    points.emplace_back(0,1,0);
    points.emplace_back(0,0,1);
    points.emplace_back(0,1,1);
    points.emplace_back(1,0,1);
    points.emplace_back(1,1,0);
    points.emplace_back(1,1,1);
    points.emplace_back(0.5,0.5,0.5);

    cilantro::ConvexHull3f ch(points, true);

    std::cout << "Vertices:" << std::endl;
    for (size_t i = 0; i < ch.getVertices().cols(); i++) {
        std::cout << ch.getVertices().col(i).transpose() << std::endl;
    }

    std::cout << "Faces:" << std::endl;
    for (size_t i = 0; i < ch.getFacetVertexIndices().size(); i++) {
        for (size_t j = 0; j < ch.getFacetVertexIndices()[i].size(); j++) {
            std::cout << ch.getFacetVertexIndices()[i][j] << " ";
        }
        std::cout << std::endl;
    }

    std::cout << "Vertex neighbor faces:" << std::endl;
    for (size_t i = 0; i < ch.getVertexNeighborFacets().size(); i++) {
        for (size_t j = 0; j < ch.getVertexNeighborFacets()[i].size(); j++) {
            std::cout << ch.getVertexNeighborFacets()[i][j] << " ";
        }
        std::cout << std::endl;
    }

    std::cout << "Face neighbor faces:" << std::endl;
    for (size_t i = 0; i < ch.getFacetNeighborFacets().size(); i++) {
        for (size_t j = 0; j < ch.getFacetNeighborFacets()[i].size(); j++) {
            std::cout << ch.getFacetNeighborFacets()[i][j] << " ";
        }
        std::cout << std::endl;
    }
}

int main(int argc, char ** argv) {
    if (argc < 2) {
        std::cout << "No input PLY file path provided, running simple demo." << std::endl;
        run_demo();
        return 0;
    }

    /*

    cilantro::PointCloud3f cloud(argv[1]);

    if (cloud.isEmpty()) {
        std::cout << "Input cloud is empty!" << std::endl;
        return 0;
    }

    cilantro::ConvexHull3f ch(cloud.points, true, true);
    cilantro::PointCloud3f hull_cloud(cloud, ch.getVertexPointIndices());

    cilantro::VectorSet3f vertex_colors(3, ch.getVertices().cols());
    std::vector<float> vertex_values(ch.getVertices().cols());
    for (size_t i = 0; i < ch.getVertices().cols(); i++) {
        if (i%3 == 0) vertex_colors.col(i) = Eigen::Vector3f(1,0,0);
        if (i%3 == 1) vertex_colors.col(i) = Eigen::Vector3f(0,1,0);
        if (i%3 == 2) vertex_colors.col(i) = Eigen::Vector3f(0,0,1);
        vertex_values[i] = ch.getVertices().col(i).norm();
    }

    cilantro::VectorSet3f face_colors(3, ch.getFacetVertexIndices().size());
    std::vector<float> face_values(ch.getFacetVertexIndices().size());
    for (size_t i = 0; i < ch.getFacetVertexIndices().size(); i++) {
        if (i%3 == 0) face_colors.col(i) = Eigen::Vector3f(1,0,0);
        if (i%3 == 1) face_colors.col(i) = Eigen::Vector3f(0,1,0);
        if (i%3 == 2) face_colors.col(i) = Eigen::Vector3f(0,0,1);
        face_values[i] = (ch.getVertices().col(ch.getFacetVertexIndices()[i][0]) +
                          ch.getVertices().col(ch.getFacetVertexIndices()[i][1]) +
                          ch.getVertices().col(ch.getFacetVertexIndices()[i][2])).rowwise().mean().norm();
    }

    const std::string window_name = "3D convex hull";
    pangolin::CreateWindowAndBind(window_name, 640, 480);
    cilantro::Visualizer viz(window_name, "disp");
    viz.addObject<cilantro::PointCloudRenderable>("cloud", cloud, cilantro::RenderingProperties().setOpacity(1.0));
    viz.addObject<cilantro::TriangleMeshRenderable>("mesh", ch.getVertices(), ch.getFacetVertexIndices())
            ->setVertexNormals(hull_cloud.normals).setVertexColors(vertex_colors).setVertexValues(vertex_values)
             .setFaceColors(face_colors).setFaceValues(face_values);

    cilantro::RenderingProperties rp = viz.getRenderingProperties("mesh");
    rp.setUseFaceNormals(true).setUseFaceColors(false).setOpacity(0.8).setColormapType(cilantro::ColormapType::BLUE2RED);
    viz.setRenderingProperties("mesh", rp);

    viz.spin();
*/
    return 0;
}
kzampog commented 2 years ago

I think what you are seeing is because of MSVC only supporting a (very) outdated version of OpenMP (2.0). cilantro requires a more recent version (>= 4.0 IIRC). If that would be an option, it might be worth trying other compilers on Windows (clang sounds promising).

FWIW, the above seems to work as expected on recent Ubuntu versions!

BTW, I'd welcome PRs adding OpenMP 2.0 support for building with MSVC :D

petrasvestartas commented 2 years ago

Thank you, I will try to use header version which works for now;)