From 6b1596e6bdfdc3347f3a8ee246ea2b22d7302179 Mon Sep 17 00:00:00 2001 From: Ramzi Sabra Date: Fri, 3 Jun 2022 00:03:30 +0300 Subject: [PATCH] added AABB tree with tests; updated CropHull to use AABB tree" --- aabb_tree/CMakeLists.txt | 55 +++++ aabb_tree/aabb_tree.doxy | 13 ++ aabb_tree/include/pcl/aabb_tree/aabb_tree.h | 118 ++++++++++ .../include/pcl/aabb_tree/aabb_tree_cgal.h | 169 ++++++++++++++ .../pcl/aabb_tree/impl/aabb_tree_cgal.hpp | 190 +++++++++++++++ aabb_tree/src/aabb_tree_cgal.cpp | 47 ++++ filters/CMakeLists.txt | 4 +- filters/include/pcl/filters/crop_hull.h | 47 ++-- .../include/pcl/filters/impl/crop_hull.hpp | 129 +++++----- test/CMakeLists.txt | 1 + test/aabb_tree/CMakeLists.txt | 18 ++ test/aabb_tree/test_aabb_tree.cpp | 220 ++++++++++++++++++ test/filters/test_crop_hull.cpp | 42 ++++ tools/CMakeLists.txt | 4 +- 14 files changed, 963 insertions(+), 94 deletions(-) create mode 100644 aabb_tree/CMakeLists.txt create mode 100644 aabb_tree/aabb_tree.doxy create mode 100644 aabb_tree/include/pcl/aabb_tree/aabb_tree.h create mode 100644 aabb_tree/include/pcl/aabb_tree/aabb_tree_cgal.h create mode 100644 aabb_tree/include/pcl/aabb_tree/impl/aabb_tree_cgal.hpp create mode 100644 aabb_tree/src/aabb_tree_cgal.cpp create mode 100644 test/aabb_tree/CMakeLists.txt create mode 100644 test/aabb_tree/test_aabb_tree.cpp diff --git a/aabb_tree/CMakeLists.txt b/aabb_tree/CMakeLists.txt new file mode 100644 index 00000000000..a6fb9ad81fa --- /dev/null +++ b/aabb_tree/CMakeLists.txt @@ -0,0 +1,55 @@ +set(SUBSYS_NAME aabb_tree) +set(SUBSYS_DESC "Geometry AABB tree library") +set(SUBSYS_DEPS common) +set(EXT_DEPS "") + +option(BUILD_aabb_tree "Build AABB tree making use of CGAL" ON) + +if (NOT BUILD_aabb_tree) + return() +endif() + +set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE TRUE) +find_package(CGAL) +if (NOT CGAL_FOUND) + return() +endif() + +set(build TRUE) + +PCL_SUBSYS_OPTION(build "${SUBSYS_NAME}" "${SUBSYS_DESC}" ON) +PCL_SUBSYS_DEPEND(build "${SUBSYS_NAME}" DEPS ${SUBSYS_DEPS} EXT_DEPS ${EXT_DEPS}) + +PCL_ADD_DOC("${SUBSYS_NAME}") + +if(NOT build) + return() +endif() + +set(srcs + src/aabb_tree_cgal.cpp +) + +set(incs + "include/pcl/${SUBSYS_NAME}/aabb_tree.h" + "include/pcl/${SUBSYS_NAME}/aabb_tree_cgal.h" +) + +set(impl_incs + "include/pcl/${SUBSYS_NAME}/impl/aabb_tree_cgal.hpp" +) + +set(libs + pcl_common + CGAL::CGAL +) + +set(LIB_NAME "pcl_${SUBSYS_NAME}") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include") +PCL_ADD_LIBRARY(${LIB_NAME} COMPONENT ${SUBSYS_NAME} SOURCES ${srcs} ${incs} ${impl_incs}) +target_link_libraries("${LIB_NAME}" ${libs}) +PCL_MAKE_PKGCONFIG(${LIB_NAME} COMPONENT ${SUBSYS_NAME} DESC ${SUBSYS_DESC} PCL_DEPS ${SUBSYS_DEPS} EXT_DEPS ${EXT_DEPS}) + +# Install include files +PCL_ADD_INCLUDES("${SUBSYS_NAME}" "${SUBSYS_NAME}" ${incs}) +PCL_ADD_INCLUDES("${SUBSYS_NAME}" "${SUBSYS_NAME}/impl" ${impl_incs}) diff --git a/aabb_tree/aabb_tree.doxy b/aabb_tree/aabb_tree.doxy new file mode 100644 index 00000000000..bc7126fcb7c --- /dev/null +++ b/aabb_tree/aabb_tree.doxy @@ -0,0 +1,13 @@ +/** + \addtogroup aabb_tree Module aabb_tree + + \section secAABBTreePresentation Overview + + The pcl_aabb_tree library provides the AABB tree data structure, using + CGAL, + that allows for fast ray intersection testing. + + \section secAABBTreeRequirements Requirements + - \ref common "common" + +*/ diff --git a/aabb_tree/include/pcl/aabb_tree/aabb_tree.h b/aabb_tree/include/pcl/aabb_tree/aabb_tree.h new file mode 100644 index 00000000000..96d980be65a --- /dev/null +++ b/aabb_tree/include/pcl/aabb_tree/aabb_tree.h @@ -0,0 +1,118 @@ +/* + * Software License Agreement (BSD License) + * + * Point Cloud Library (PCL) - www.pointclouds.org + * Copyright (c) 2009-2011, Willow Garage, Inc. + * Copyright (c) 2012-, Open Perception, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the copyright holder(s) 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 OWNER 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. + * + * aabb_tree.h + * Created on: Jun 02, 2022 + * Author: Ramzi Sabra + */ + +#pragma once + +#include +#include +#include + +namespace pcl { +/** \brief AABBTree represents the base ray intersection testing class for AABB tree + * implementations. \author Ramzi Sabra \ingroup aabb_tree + */ +template +class AABBTree { +public: + using PointCloud = pcl::PointCloud; + using PointCloudPtr = typename PointCloud::Ptr; + using PointCloudConstPtr = std::shared_ptr; + + virtual ~AABBTree(){}; + + /** \brief Provide an input mesh to construct the tree. + * \param[in] mesh the polygon mesh + */ + virtual void + setInputMesh(const pcl::PolygonMesh& mesh) = 0; + + /** \brief Provide an input mesh to construct the tree. + * \param[in] mesh_cloud the mesh cloud + * \param[in] mesh_polygons the mesh polygons + */ + virtual void + setInputMesh(PointCloudConstPtr mesh_cloud, + const std::vector& mesh_polygons) = 0; + + /** \brief Check for the presence of intersection(s) for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + virtual bool + checkForIntersection(const PointT& source, + const Eigen::Vector3f& direction) const = 0; + + /** \brief Check for the number of intersections for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + virtual size_t + numberOfIntersections(const PointT& source, + const Eigen::Vector3f& direction) const = 0; + + /** \brief Get all intersections for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + virtual std::vector + getAllIntersections(const PointT& source, const Eigen::Vector3f& direction) const = 0; + + /** \brief Get any intersection for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + virtual index_t + getAnyIntersection(const PointT& source, const Eigen::Vector3f& direction) const = 0; + + /** \brief Get the nearest intersection for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + virtual index_t + getNearestIntersection(const PointT& source, + const Eigen::Vector3f& direction) const = 0; + +protected: + /** \brief Class getName method. */ + virtual std::string + getName() const = 0; +}; +} // namespace pcl diff --git a/aabb_tree/include/pcl/aabb_tree/aabb_tree_cgal.h b/aabb_tree/include/pcl/aabb_tree/aabb_tree_cgal.h new file mode 100644 index 00000000000..86ff7f23ae9 --- /dev/null +++ b/aabb_tree/include/pcl/aabb_tree/aabb_tree_cgal.h @@ -0,0 +1,169 @@ +/* + * Software License Agreement (BSD License) + * + * Point Cloud Library (PCL) - www.pointclouds.org + * Copyright (c) 2009-2011, Willow Garage, Inc. + * Copyright (c) 2012-, Open Perception, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the copyright holder(s) 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 OWNER 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. + * + * aabb_tree_cgal.h + * Created on: Jun 02, 2022 + * Author: Ramzi Sabra + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace pcl { +/** \brief AABBTreeCGAL is a ray intersection testing class using AABB tree + * structures. The class is making use of the CGAL (The Computational Geometry Algorithms + * Library) project. \author Ramzi Sabra \ingroup aabb_tree + */ +template +class AABBTreeCGAL : public AABBTree { +protected: + using K = CGAL::Simple_cartesian; + using CGALPoint = K::Point_3; + using CGALVector = K::Vector_3; + using Ray = K::Ray_3; + using Triangle = K::Triangle_3; + + using Iterator = std::vector::const_iterator; + using Primitive = CGAL::AABB_triangle_primitive; + using AABB_triangle_traits = CGAL::AABB_traits; + +public: + using Tree = CGAL::AABB_tree; + using TreePtr = std::shared_ptr; + using TreeConstPtr = std::shared_ptr; + + using PointCloud = typename AABBTree::PointCloud; + using PointCloudPtr = typename AABBTree::PointCloudPtr; + using PointCloudConstPtr = typename AABBTree::PointCloudConstPtr; + + /** \brief Default constructor for AABBTreeCGAL. + * \param[in] tree already-built CGAL AABB tree. Set to empty by default. + */ + AABBTreeCGAL(TreeConstPtr tree = TreeConstPtr()); + + /** \brief Set an already-build CGAL AABB tree as the tree. + * \param[in] tree already-built CGAL AABB tree + */ + void + setTree(TreeConstPtr tree); + + /** \brief Provide an input mesh to construct the tree. + * \param[in] mesh the polygon mesh + */ + void + setInputMesh(const pcl::PolygonMesh& mesh) override; + + /** \brief Provide an input mesh to construct the tree. + * \param[in] mesh_cloud the mesh cloud + * \param[in] mesh_polygons the mesh polygons + */ + void + setInputMesh(PointCloudConstPtr mesh_cloud, + const std::vector& mesh_polygons) override; + + /** \brief Check for the presence of intersection(s) for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + bool + checkForIntersection(const PointT& source, + const Eigen::Vector3f& direction) const override; + + /** \brief Check for the number of intersections for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + size_t + numberOfIntersections(const PointT& source, + const Eigen::Vector3f& direction) const override; + + /** \brief Get all intersections for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + std::vector + getAllIntersections(const PointT& source, + const Eigen::Vector3f& direction) const override; + + /** \brief Get any intersection for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + index_t + getAnyIntersection(const PointT& source, + const Eigen::Vector3f& direction) const override; + + /** \brief Get the nearest intersection for the given ray + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + index_t + getNearestIntersection(const PointT& source, + const Eigen::Vector3f& direction) const override; + +protected: + /** \brief Class getName method. */ + std::string + getName() const override + { + return ("AABBTreeCGAL"); + } + + /** \brief A CGAL AABB tree object. */ + TreeConstPtr tree_; + + /** \brief CGAL triangles used for building the tree. */ + std::vector triangles_; + +private: + /** \brief Generate a CGAL ray from a source point and a direction vector + * \param[in] source the ray source point + * \param[in] direction the ray direction vector + */ + static Ray + generateRay(const PointT& source, const Eigen::Vector3f& direction); +}; +} // namespace pcl + +#ifdef PCL_NO_PRECOMPILE +#include +#endif diff --git a/aabb_tree/include/pcl/aabb_tree/impl/aabb_tree_cgal.hpp b/aabb_tree/include/pcl/aabb_tree/impl/aabb_tree_cgal.hpp new file mode 100644 index 00000000000..880570e6982 --- /dev/null +++ b/aabb_tree/include/pcl/aabb_tree/impl/aabb_tree_cgal.hpp @@ -0,0 +1,190 @@ +/* + * Software License Agreement (BSD License) + * + * Point Cloud Library (PCL) - www.pointclouds.org + * Copyright (c) 2009-2011, Willow Garage, Inc. + * Copyright (c) 2012-, Open Perception, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the copyright holder(s) 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 OWNER 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. + * + * aabb_tree_cgal.cpp + * Created on: Jun 02, 2022 + * Author: Ramzi Sabra + */ + +#ifndef PCL_AABB_TREE_AABB_TREE_IMPL_CGAL_H_ +#define PCL_AABB_TREE_AABB_TREE_IMPL_CGAL_H_ + +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////////////////// +template +pcl::AABBTreeCGAL::AABBTreeCGAL(TreeConstPtr tree) : tree_(tree) +{} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +void +pcl::AABBTreeCGAL::setTree(TreeConstPtr tree) +{ + tree_ = tree; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +void +pcl::AABBTreeCGAL::setInputMesh(const pcl::PolygonMesh& mesh) +{ + PointCloudPtr mesh_cloud = std::make_shared(); + pcl::fromPCLPointCloud2(mesh.cloud, *mesh_cloud); + setInputMesh(mesh_cloud, mesh.polygons); +} + +template +void +pcl::AABBTreeCGAL::setInputMesh(PointCloudConstPtr mesh_cloud, + const std::vector& mesh_polygons) +{ +#if DEBUG + for (const pcl::Vertices& vertices : mesh_polygons) + assert(vertices.vertices.size() == 3 && + "Invalid number of vertices for polygon (should be 3)!"); +#endif + + triangles_.resize(mesh_polygons.size()); + std::transform(mesh_polygons.cbegin(), + mesh_polygons.cend(), + triangles_.begin(), + [mesh_cloud](const Vertices& vertices) { + const pcl::Indices& indices = vertices.vertices; + + std::array cgal_points; + std::transform(indices.cbegin(), + indices.cend(), + cgal_points.begin(), + [mesh_cloud](const index_t& index) { + const PointT& point = mesh_cloud->points[index]; + return CGALPoint(point.x, point.y, point.z); + }); + + return Triangle(cgal_points[0], cgal_points[1], cgal_points[2]); + }); + + tree_.reset(new Tree(triangles_.cbegin(), triangles_.cend())); +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +bool +pcl::AABBTreeCGAL::checkForIntersection(const PointT& source, + const Eigen::Vector3f& direction) const +{ + Ray ray = generateRay(source, direction); + return tree_->do_intersect(ray); +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +size_t +pcl::AABBTreeCGAL::numberOfIntersections(const PointT& source, + const Eigen::Vector3f& direction) const +{ + Ray ray = generateRay(source, direction); + return tree_->number_of_intersected_primitives(ray); +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +std::vector +pcl::AABBTreeCGAL::getAllIntersections(const PointT& source, + const Eigen::Vector3f& direction) const +{ + Ray ray = generateRay(source, direction); + std::vector primitive_ids; + tree_->all_intersected_primitives(ray, std::back_inserter(primitive_ids)); + + std::vector triangle_indices(primitive_ids.size()); + Iterator first_primitive_id = triangles_.cbegin(); + + std::transform(primitive_ids.cbegin(), + primitive_ids.cend(), + triangle_indices.begin(), + [&first_primitive_id](const Iterator& primitive_id) { + return std::distance(first_primitive_id, primitive_id); + }); + + return triangle_indices; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +pcl::index_t +pcl::AABBTreeCGAL::getAnyIntersection(const PointT& source, + const Eigen::Vector3f& direction) const +{ + Ray ray = generateRay(source, direction); + auto primitive_id = tree_->any_intersected_primitive(ray); + + if (primitive_id.has_value()) + return std::distance(triangles_.cbegin(), primitive_id.value()); + return -1; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +pcl::index_t +pcl::AABBTreeCGAL::getNearestIntersection( + const PointT& source, const Eigen::Vector3f& direction) const +{ + Ray ray = generateRay(source, direction); + auto primitive_id = tree_->first_intersected_primitive(ray); + + if (primitive_id.has_value()) + return std::distance(triangles_.cbegin(), primitive_id.value()); + return -1; +} + +/////////////////////////////////////////////////////////////////////////////////////////// +template +typename pcl::AABBTreeCGAL::Ray +pcl::AABBTreeCGAL::generateRay(const PointT& source, + const Eigen::Vector3f& direction) +{ + CGALPoint cgal_source(source.x, source.y, source.z); + CGALVector cgal_direction(direction[0], direction[1], direction[2]); + return Ray(cgal_source, cgal_direction); +} + +#define PCL_INSTANTIATE_AABBTreeCGAL(T) template class PCL_EXPORTS pcl::AABBTreeCGAL; + +#endif //#ifndef _PCL_AABB_TREE_AABB_TREE_IMPL_CGAL_H_ diff --git a/aabb_tree/src/aabb_tree_cgal.cpp b/aabb_tree/src/aabb_tree_cgal.cpp new file mode 100644 index 00000000000..0ae09b29c18 --- /dev/null +++ b/aabb_tree/src/aabb_tree_cgal.cpp @@ -0,0 +1,47 @@ +/* + * Software License Agreement (BSD License) + * + * Point Cloud Library (PCL) - www.pointclouds.org + * Copyright (c) 2010, Willow Garage, Inc. + * Copyright (c) 2012-, Open Perception, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the copyright holder(s) 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 OWNER 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. + * + */ + +#include + +#ifndef PCL_NO_PRECOMPILE +#include +#include +// Instantiations of specific point types +PCL_INSTANTIATE(AABBTreeCGAL, PCL_XYZ_POINT_TYPES) +#endif // PCL_NO_PRECOMPILE + diff --git a/filters/CMakeLists.txt b/filters/CMakeLists.txt index bb91d7bb179..9e436019325 100644 --- a/filters/CMakeLists.txt +++ b/filters/CMakeLists.txt @@ -1,6 +1,6 @@ set(SUBSYS_NAME filters) set(SUBSYS_DESC "Point cloud filters library") -set(SUBSYS_DEPS common sample_consensus search kdtree octree) +set(SUBSYS_DEPS common sample_consensus search kdtree octree aabb_tree) set(build TRUE) PCL_SUBSYS_OPTION(build "${SUBSYS_NAME}" "${SUBSYS_DESC}" ON) @@ -130,7 +130,7 @@ set(impl_incs set(LIB_NAME "pcl_${SUBSYS_NAME}") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include") PCL_ADD_LIBRARY(${LIB_NAME} COMPONENT ${SUBSYS_NAME} SOURCES ${srcs} ${incs} ${impl_incs}) -target_link_libraries("${LIB_NAME}" pcl_common pcl_sample_consensus pcl_search pcl_kdtree pcl_octree) +target_link_libraries("${LIB_NAME}" pcl_common pcl_sample_consensus pcl_search pcl_kdtree pcl_octree pcl_aabb_tree) PCL_MAKE_PKGCONFIG(${LIB_NAME} COMPONENT ${SUBSYS_NAME} DESC ${SUBSYS_DESC} PCL_DEPS ${SUBSYS_DEPS}) # Install include files diff --git a/filters/include/pcl/filters/crop_hull.h b/filters/include/pcl/filters/crop_hull.h index 59ed4b5bcc9..07e66d5a37c 100644 --- a/filters/include/pcl/filters/crop_hull.h +++ b/filters/include/pcl/filters/crop_hull.h @@ -39,6 +39,7 @@ #include #include +#include namespace pcl { @@ -67,8 +68,10 @@ namespace pcl /** \brief Empty Constructor. */ CropHull () : hull_cloud_(), + tree_dirty_(true), dim_(3), - crop_outside_(true) + crop_outside_(true), + num_threads_(1) { filter_name_ = "CropHull"; } @@ -81,6 +84,7 @@ namespace pcl setHullIndices (const std::vector& polygons) { hull_polygons_ = polygons; + tree_dirty_ = true; } /** \brief Get the vertices of the hull used to filter points. @@ -98,6 +102,7 @@ namespace pcl setHullCloud (PointCloudPtr points) { hull_cloud_ = points; + tree_dirty_ = true; } /** \brief Get the point cloud that the hull indices refer to. */ @@ -106,6 +111,10 @@ namespace pcl { return (hull_cloud_); } + + /** \brief Build the AABB tree. */ + void + buildTree (); /** \brief Set the dimensionality of the hull to be used. * This should be set to correspond to the dimensionality of the @@ -129,6 +138,22 @@ namespace pcl crop_outside_ = crop_outside; } + /** \brief Set the number of threads to be used. + * \param[in] num_threads The number of threads + */ + inline void + setNumberOfThreads(unsigned int num_threads) + { + if (num_threads == 0) +#ifdef _OPENMP + num_threads_ = omp_get_num_procs(); +#else + num_threads_ = 1; +#endif + else + num_threads_ = num_threads; + } + protected: /** \brief Filter the input points using the 2D or 3D polygon hull. * \param[out] output The set of points that passed the filter @@ -182,20 +207,6 @@ namespace pcl const Vertices& verts, const PointCloud& cloud); - /** \brief Does a ray cast from a point intersect with an arbitrary - * triangle in 3D? - * See: http://softsurfer.com/Archive/algorithm_0105/algorithm_0105.htm#intersect_RayTriangle() - * \param[in] point Point from which the ray is cast. - * \param[in] ray Vector in direction of ray. - * \param[in] verts Indices of vertices making the polygon. - * \param[in] cloud Cloud from which the vertex indices are drawn. - */ - inline static bool - rayTriangleIntersect (const PointT& point, - const Eigen::Vector3f& ray, - const Vertices& verts, - const PointCloud& cloud); - /** \brief The vertices of the hull used to filter points. */ std::vector hull_polygons_; @@ -203,6 +214,10 @@ namespace pcl /** \brief The point cloud that the hull indices refer to. */ PointCloudPtr hull_cloud_; + AABBTreeCGAL tree_; + + bool tree_dirty_; + /** \brief The dimensionality of the hull to be used. */ int dim_; @@ -210,6 +225,8 @@ namespace pcl * false, those inside will be removed. */ bool crop_outside_; + + unsigned int num_threads_; }; } // namespace pcl diff --git a/filters/include/pcl/filters/impl/crop_hull.hpp b/filters/include/pcl/filters/impl/crop_hull.hpp index 72d5000dc48..b97c8667c16 100644 --- a/filters/include/pcl/filters/impl/crop_hull.hpp +++ b/filters/include/pcl/filters/impl/crop_hull.hpp @@ -40,6 +40,17 @@ #include +#include + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template +void +pcl::CropHull::buildTree () +{ + tree_.setInputMesh(hull_cloud_, hull_polygons_); + tree_dirty_ = false; +} ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template @@ -140,39 +151,56 @@ pcl::CropHull::applyFilter2D (Indices &indices) } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template void -pcl::CropHull::applyFilter3D (Indices &indices) +template +void +pcl::CropHull::applyFilter3D(Indices& indices) { - // This algorithm could definitely be sped up using kdtree/octree - // information, if that is available! + if (tree_dirty_) + buildTree(); - for (std::size_t index = 0; index < indices_->size (); index++) - { - // test ray-crossings for three random rays, and take vote of crossings - // counts to determine if each point is inside the hull: the vote avoids - // tricky edge and corner cases when rays might fluke through the edge - // between two polygons - // 'random' rays are arbitrary - basically anything that is less likely to - // hit the edge between polygons than coordinate-axis aligned rays would - // be. - std::size_t crossings[3] = {0,0,0}; - Eigen::Vector3f rays[3] = - { - Eigen::Vector3f(0.264882f, 0.688399f, 0.675237f), + std::array directions = { + Eigen::Vector3f(0.264882f, 0.688399f, 0.675237f), Eigen::Vector3f(0.0145419f, 0.732901f, 0.68018f), - Eigen::Vector3f(0.856514f, 0.508771f, 0.0868081f) - }; + Eigen::Vector3f(0.856514f, 0.508771f, 0.0868081f)}; + + std::vector> crosses_vec(indices_->size()); + + { + // clang-format off +#pragma omp parallel for \ + default(none) \ + shared(directions, crosses_vec) \ + num_threads(num_threads_) + // clang-format on + for (int i = 0; i < static_cast(indices_->size()); ++i) { + const index_t& index = (*indices_)[i]; + std::atomic& crosses = crosses_vec[i]; + + const PointT& point = (*input_)[index]; + + std::array crossings; + std::transform(directions.cbegin(), + directions.cend(), + crossings.begin(), + [this, &point](const auto& direction) { + return tree_.numberOfIntersections(point, direction); + }); + + crosses = (crossings[0] & 1) + (crossings[1] & 1) + (crossings[2] & 1) > 1; + } + } + + auto index_itr = indices_->cbegin(); + auto crosses_itr = crosses_vec.cbegin(); - for (std::size_t poly = 0; poly < hull_polygons_.size (); poly++) - for (std::size_t ray = 0; ray < 3; ray++) - crossings[ray] += rayTriangleIntersect - ((*input_)[(*indices_)[index]], rays[ray], hull_polygons_[poly], *hull_cloud_); + for (; index_itr != indices_->cend(); ++index_itr, ++crosses_itr) { + const auto& index = *index_itr; + const auto& crosses = *crosses_itr; - bool crosses = (crossings[0]&1) + (crossings[1]&1) + (crossings[2]&1) > 1; if ((crop_outside_ && crosses) || (!crop_outside_ && !crosses)) - indices.push_back ((*indices_)[index]); + indices.push_back(index); else - removed_indices_->push_back ((*indices_)[index]); + removed_indices_->push_back(index); } } @@ -218,55 +246,6 @@ pcl::CropHull::isPointIn2DPolyWithVertIndices ( return (in_poly); } -////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template bool -pcl::CropHull::rayTriangleIntersect (const PointT& point, - const Eigen::Vector3f& ray, - const Vertices& verts, - const PointCloud& cloud) -{ - // Algorithm here is adapted from: - // http://softsurfer.com/Archive/algorithm_0105/algorithm_0105.htm#intersect_RayTriangle() - // - // Original copyright notice: - // Copyright 2001, softSurfer (www.softsurfer.com) - // This code may be freely used and modified for any purpose - // providing that this copyright notice is included with it. - // - assert (verts.vertices.size () == 3); - - const Eigen::Vector3f p = point.getVector3fMap (); - const Eigen::Vector3f a = cloud[verts.vertices[0]].getVector3fMap (); - const Eigen::Vector3f b = cloud[verts.vertices[1]].getVector3fMap (); - const Eigen::Vector3f c = cloud[verts.vertices[2]].getVector3fMap (); - const Eigen::Vector3f u = b - a; - const Eigen::Vector3f v = c - a; - const Eigen::Vector3f n = u.cross (v); - const float n_dot_ray = n.dot (ray); - - if (std::fabs (n_dot_ray) < 1e-9) - return (false); - - const float r = n.dot (a - p) / n_dot_ray; - - if (r < 0) - return (false); - - const Eigen::Vector3f w = p + r * ray - a; - const float denominator = u.dot (v) * u.dot (v) - u.dot (u) * v.dot (v); - const float s_numerator = u.dot (v) * w.dot (v) - v.dot (v) * w.dot (u); - const float s = s_numerator / denominator; - if (s < 0 || s > 1) - return (false); - - const float t_numerator = u.dot (v) * w.dot (u) - u.dot (u) * w.dot (v); - const float t = t_numerator / denominator; - if (t < 0 || s+t > 1) - return (false); - - return (true); -} - #define PCL_INSTANTIATE_CropHull(T) template class PCL_EXPORTS pcl::CropHull; #endif // PCL_FILTERS_IMPL_CROP_HULL_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cb7f77530de..9842d7ef251 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -47,6 +47,7 @@ set_target_properties(tests PROPERTIES FOLDER "Tests") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include") add_subdirectory(2d) +add_subdirectory(aabb_tree) add_subdirectory(common) add_subdirectory(features) add_subdirectory(filters) diff --git a/test/aabb_tree/CMakeLists.txt b/test/aabb_tree/CMakeLists.txt new file mode 100644 index 00000000000..fe7210e2d5f --- /dev/null +++ b/test/aabb_tree/CMakeLists.txt @@ -0,0 +1,18 @@ +set(SUBSYS_NAME tests_aabb_tree) +set(SUBSYS_DESC "Point cloud library AABB tree module unit tests") +PCL_SET_TEST_DEPENDENCIES(SUBSYS_DEPS aabb_tree vtk) +set(OPT_DEPS io) # io is not a mandatory dependency in aabb_tree + +set(DEFAULT ON) +set(build TRUE) +PCL_SUBSYS_OPTION(build "${SUBSYS_NAME}" "${SUBSYS_DESC}" ${DEFAULT} "${REASON}") +PCL_SUBSYS_DEPEND(build "${SUBSYS_NAME}" DEPS ${SUBSYS_DEPS} OPT_DEPS ${OPT_DEPS}) + +if(NOT (build AND BUILD_io)) + return() +endif() + +PCL_ADD_TEST (aabb_tree_aabb_tree test_aabb_tree + FILES test_aabb_tree.cpp + LINK_WITH pcl_gtest pcl_aabb_tree pcl_io pcl_common + ARGUMENTS "${PCL_SOURCE_DIR}/test/tum_rabbit.vtk") diff --git a/test/aabb_tree/test_aabb_tree.cpp b/test/aabb_tree/test_aabb_tree.cpp new file mode 100644 index 00000000000..6bfef9f8518 --- /dev/null +++ b/test/aabb_tree/test_aabb_tree.cpp @@ -0,0 +1,220 @@ +/* + * Software License Agreement (BSD License) + * + * Point Cloud Library (PCL) - www.pointclouds.org + * Copyright (c) 2009-2011, Willow Garage, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the copyright holder(s) 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 OWNER 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. + * + * test_aabb_tree.cpp + * Adapted from: test/kdtree/test_kdtree.cpp + * filters/impl/crop_hull.hpp + * Created on: Jun 05, 2022 + * Author: Ramzi Sabra + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include // For debug +#include +#include + +using namespace pcl; + +using Point = PointXYZ; +using Cloud = PointCloud; + +boost::property_tree::ptree xml_property_tree; + +PolygonMesh mesh_in; +Cloud mesh_cloud; + +bool +rayTriangleIntersect(const Point& point, + const Eigen::Vector3f& ray, + const Vertices& verts, + const Cloud& cloud) +{ + // Algorithm here is adapted from: + // http://softsurfer.com/Archive/algorithm_0105/algorithm_0105.htm#intersect_RayTriangle() + // + // Original copyright notice: + // Copyright 2001, softSurfer (www.softsurfer.com) + // This code may be freely used and modified for any purpose + // providing that this copyright notice is included with it. + // + assert(verts.vertices.size() == 3); + + const Eigen::Vector3f p = point.getVector3fMap(); + const Eigen::Vector3f a = cloud[verts.vertices[0]].getVector3fMap(); + const Eigen::Vector3f b = cloud[verts.vertices[1]].getVector3fMap(); + const Eigen::Vector3f c = cloud[verts.vertices[2]].getVector3fMap(); + const Eigen::Vector3f u = b - a; + const Eigen::Vector3f v = c - a; + const Eigen::Vector3f n = u.cross(v); + const float n_dot_ray = n.dot(ray); + + if (std::fabs(n_dot_ray) < 1e-9) + return (false); + + const float r = n.dot(a - p) / n_dot_ray; + + if (r < 0) + return (false); + + const Eigen::Vector3f w = p + r * ray - a; + const float denominator = u.dot(v) * u.dot(v) - u.dot(u) * v.dot(v); + const float s_numerator = u.dot(v) * w.dot(v) - v.dot(v) * w.dot(u); + const float s = s_numerator / denominator; + if (s < 0 || s > 1) + return (false); + + const float t_numerator = u.dot(v) * w.dot(u) - u.dot(u) * w.dot(v); + const float t = t_numerator / denominator; + if (t < 0 || s + t > 1) + return (false); + + return (true); +} + +template +class PCLAABBTreeTestFixture : public ::testing::Test { +public: + using Tree = T; +}; + +using AABBTreeTestTypes = ::testing::Types>; +TYPED_TEST_SUITE(PCLAABBTreeTestFixture, AABBTreeTestTypes); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TYPED_TEST(PCLAABBTreeTestFixture, AABBTree_intersections) +{ + using Tree = typename TestFixture::Tree; + using Ray = std::pair; + + std::vector rays{ + {Point(0.0f, 70.0f, 0.0f), Eigen::Vector3f(1.0f, 0.0f, 0.0f)}, // 0 intersections + {Point(0.0f, 70.0f, 0.0f), Eigen::Vector3f(1.0f, 1.0f, 1.0f)}, // 2 intersections + {Point(70.0f, 100.0f, 80.0f), Eigen::Vector3f(1.0f, 0.0f, 0.0f)} // 1 intersection + }; + + std::vector triangle_indices_vec(rays.size()); + + std::transform( + rays.cbegin(), rays.cend(), triangle_indices_vec.begin(), [](const Ray& ray) { + const Point& source = ray.first; + const Eigen::Vector3f& direction = ray.second; + + Indices indices; + + for (index_t index = 0; index < mesh_in.polygons.size(); ++index) { + const Vertices& vertices = mesh_in.polygons[index]; + if (rayTriangleIntersect(source, direction, vertices, mesh_cloud)) + indices.push_back(index); + } + + return indices; + }); + + Tree tree; + tree.setInputMesh(mesh_in); + + auto ray_itr = rays.cbegin(); + auto triangle_indices_itr = triangle_indices_vec.cbegin(); + + for (; ray_itr != rays.cend(); ++ray_itr, ++triangle_indices_itr) { + const Ray& ray = *ray_itr; + const Indices& triangle_indices = *triangle_indices_itr; + + const Point& source = ray.first; + const Eigen::Vector3f& direction = ray.second; + + EXPECT_EQ(tree.checkForIntersection(source, direction), + triangle_indices.size() != 0); + EXPECT_EQ(tree.numberOfIntersections(source, direction), triangle_indices.size()); + + std::unordered_set triangle_indices_set, tree_triangle_indices_set; + + { + auto inserter = std::inserter(triangle_indices_set, triangle_indices_set.end()); + std::copy(triangle_indices.cbegin(), triangle_indices.cend(), inserter); + } + + { + Indices tree_triangle_indices = tree.getAllIntersections(source, direction); + auto inserter = + std::inserter(tree_triangle_indices_set, tree_triangle_indices_set.end()); + std::copy(tree_triangle_indices.cbegin(), tree_triangle_indices.cend(), inserter); + } + + EXPECT_EQ(tree_triangle_indices_set, triangle_indices_set); + + index_t any_triangle_index = tree.getAnyIntersection(source, direction); + index_t nearest_triangle_index = tree.getNearestIntersection(source, direction); + + if (triangle_indices_set.size() == 0) { + EXPECT_EQ(any_triangle_index, -1); + EXPECT_EQ(nearest_triangle_index, -1); + } + else { + EXPECT_TRUE(triangle_indices_set.count(any_triangle_index) == 1); + EXPECT_TRUE(triangle_indices_set.count(nearest_triangle_index) == 1); + } + } +} + +/* ---[ */ +int +main(int argc, char** argv) +{ + // Load the standard PLY file from disk + if (argc < 2) { + std::cerr << "No test file given. Please download `tum_rabbit.vtk` and pass its " + "path to the test." + << std::endl; + return (-1); + } + + // Load in the mesh + io::loadPolygonFileVTK(argv[1], mesh_in); + pcl::fromPCLPointCloud2(mesh_in.cloud, mesh_cloud); + + testing::InitGoogleTest(&argc, argv); + + return (RUN_ALL_TESTS()); +} +/* ]--- */ diff --git a/test/filters/test_crop_hull.cpp b/test/filters/test_crop_hull.cpp index ebe169ae65c..1fdf83e6018 100644 --- a/test/filters/test_crop_hull.cpp +++ b/test/filters/test_crop_hull.cpp @@ -314,6 +314,48 @@ TYPED_TEST (PCLCropHullTestFixture, simple_test) } } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TYPED_TEST (PCLCropHullTestFixture, build_tree) +{ + for (auto & entry : this->data_) + { + auto & crop_hull_filter = entry.first; + for (TestData const & test_data : entry.second) + { + crop_hull_filter.setInputCloud(test_data.input_cloud_); + crop_hull_filter.buildTree(); + pcl::Indices filtered_indices; + crop_hull_filter.filter(filtered_indices); + ASSERT_EQ(test_data.inside_indices_.size(), filtered_indices.size()); + pcl::test::EXPECT_EQ_VECTORS(test_data.inside_indices_, filtered_indices); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TYPED_TEST (PCLCropHullTestFixture, build_tree_change_hull_cloud_and_indices) +{ + auto & entry = this->data_[0]; + auto & crop_hull_filter = entry.first; + + auto crop_hull_filter_copy = crop_hull_filter; + auto empty_hull_cloud = std::make_shared>(); + std::vector empty_hull_indices; + + { + crop_hull_filter.setHullCloud(empty_hull_cloud); + pcl::Indices filtered_indices; + crop_hull_filter.filter(filtered_indices); + ASSERT_EQ(filtered_indices.size(), 0); + } + + { + crop_hull_filter_copy.setHullIndices(empty_hull_indices); + pcl::Indices filtered_indices; + crop_hull_filter_copy.filter(filtered_indices); + ASSERT_EQ(filtered_indices.size(), 0); + } +} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // checking that the result is independent of the original state of the output_indices diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 7f61854aee0..90b843a73c1 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,6 +1,6 @@ set(SUBSYS_NAME tools) set(SUBSYS_DESC "Useful PCL-based command line tools") -set(SUBSYS_DEPS common io filters sample_consensus segmentation search kdtree features surface octree registration recognition geometry keypoints ml) +set(SUBSYS_DEPS common io filters sample_consensus segmentation search kdtree features surface octree aabb_tree registration recognition geometry keypoints ml) set(SUBSYS_OPT_DEPS vtk visualization) set(DEFAULT ON) set(REASON "") @@ -163,7 +163,7 @@ endif() if(QHULL_FOUND) PCL_ADD_EXECUTABLE(pcl_crop_to_hull COMPONENT ${SUBSYS_NAME} SOURCES crop_to_hull.cpp) - target_link_libraries(pcl_crop_to_hull pcl_common pcl_io pcl_filters pcl_surface) + target_link_libraries(pcl_crop_to_hull pcl_common pcl_io pcl_filters pcl_surface pcl_aabb_tree) PCL_ADD_EXECUTABLE(pcl_compute_hull COMPONENT ${SUBSYS_NAME} SOURCES compute_hull.cpp) target_link_libraries(pcl_compute_hull pcl_common pcl_io pcl_surface)