// Copyright (c) 2015 INRIA Sophia-Antipolis (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // You can redistribute it and/or modify it under the terms of the GNU // General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // Licensees holding a valid commercial license may use this file in // accordance with the commercial license agreement provided with the software. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. // // $URL$ // $Id$ // SPDX-License-Identifier: GPL-3.0+ // // // Author(s) : Florent Lafarge, Simon Giraudot // /** * \ingroup PkgPointSetShapeDetection3 * \file CGAL/regularize_planes.h * */ #ifndef CGAL_REGULARIZE_PLANES_H #define CGAL_REGULARIZE_PLANES_H #include #include #include #include #include namespace CGAL { // ---------------------------------------------------------------------------- // Private section // ---------------------------------------------------------------------------- /// \cond SKIP_IN_MANUAL namespace internal { namespace PlaneRegularization { template struct Plane_cluster { bool is_free; std::vector planes; std::vector coplanar_group; std::vector orthogonal_clusters; typename Traits::Vector_3 normal; typename Traits::FT cosangle_symmetry; typename Traits::FT area; typename Traits::FT cosangle_centroid; Plane_cluster() : is_free (true) , normal (typename Traits::FT(0.), typename Traits::FT(0.), typename Traits::FT(1.)) , cosangle_symmetry (typename Traits::FT(0.)) , area (typename Traits::FT(0.)) , cosangle_centroid (typename Traits::FT(0.)) { } }; template typename Traits::Vector_3 regularize_normal (const typename Traits::Vector_3& n, const typename Traits::Vector_3& symmetry_direction, typename Traits::FT cos_symmetry) { typedef typename Traits::FT FT; typedef typename Traits::Point_3 Point; typedef typename Traits::Vector_3 Vector; typedef typename Traits::Line_3 Line; typedef typename Traits::Plane_3 Plane; Point pt_symmetry = CGAL::ORIGIN + cos_symmetry* symmetry_direction; Plane plane_symmetry (pt_symmetry, symmetry_direction); Point pt_normal = CGAL::ORIGIN + n; if (n != symmetry_direction || n != -symmetry_direction) { Plane plane_cut (CGAL::ORIGIN, pt_normal, CGAL::ORIGIN + symmetry_direction); Line line; CGAL::Object ob_1 = CGAL::intersection(plane_cut, plane_symmetry); if (!assign(line, ob_1)) return n; FT delta = std::sqrt ((FT)1. - cos_symmetry * cos_symmetry); Point projected_origin = line.projection (CGAL::ORIGIN); Vector line_vector (line); line_vector = line_vector / std::sqrt (line_vector * line_vector); Point pt1 = projected_origin + delta * line_vector; Point pt2 = projected_origin - delta * line_vector; if (CGAL::squared_distance (pt_normal, pt1) <= CGAL::squared_distance (pt_normal, pt2)) return Vector (CGAL::ORIGIN, pt1); else return Vector (CGAL::ORIGIN, pt2); } else return n; } template typename Traits::Vector_3 regularize_normals_from_prior (const typename Traits::Vector_3& np, const typename Traits::Vector_3& n, const typename Traits::Vector_3& symmetry_direction, typename Traits::FT cos_symmetry) { typedef typename Traits::FT FT; typedef typename Traits::Point_3 Point; typedef typename Traits::Vector_3 Vector; typedef typename Traits::Line_3 Line; typedef typename Traits::Plane_3 Plane; Plane plane_orthogonality (CGAL::ORIGIN, np); Point pt_symmetry = CGAL::ORIGIN + cos_symmetry* symmetry_direction; Plane plane_symmetry (pt_symmetry, symmetry_direction); Line line; CGAL::Object ob_1 = CGAL::intersection (plane_orthogonality, plane_symmetry); if (!assign(line, ob_1)) return regularize_normal (n, symmetry_direction, cos_symmetry); Point projected_origin = line.projection (CGAL::ORIGIN); FT R = CGAL::squared_distance (Point (CGAL::ORIGIN), projected_origin); if (R <= 1) // 2 (or 1) possible points intersecting the unit sphere and line { FT delta = std::sqrt ((FT)1. - R); Vector line_vector(line); line_vector = line_vector / std::sqrt (line_vector * line_vector); Point pt1 = projected_origin + delta * line_vector; Point pt2 = projected_origin - delta * line_vector; Point pt_n = CGAL::ORIGIN + n; if (CGAL::squared_distance (pt_n, pt1) <= CGAL::squared_distance (pt_n, pt2)) return Vector (CGAL::ORIGIN, pt1); else return Vector (CGAL::ORIGIN, pt2); } else //no point intersecting the unit sphere and line return regularize_normal (n,symmetry_direction, cos_symmetry); } template void compute_centroids_and_areas (const PointRange& points, PointMap point_map, std::size_t nb_planes, IndexMap index_map, std::vector& centroids, std::vector& areas) { typedef typename Traits::FT FT; typedef typename Traits::Point_3 Point; std::vector > listp (nb_planes); for (std::size_t i = 0; i < points.size(); ++ i) { int idx = get (index_map, i); if (idx != -1) listp[std::size_t(idx)].push_back (get(point_map, *(points.begin() + i))); } centroids.reserve (nb_planes); areas.reserve (nb_planes); for (std::size_t i = 0; i < nb_planes; ++ i) { centroids.push_back (CGAL::centroid (listp[i].begin (), listp[i].end ())); areas.push_back ((FT)(listp[i].size() / (FT)100.)); } } template void compute_parallel_clusters (PlaneRange& planes, PlaneMap plane_map, std::vector >& clusters, std::vector& areas, typename Traits::FT tolerance_cosangle, const typename Traits::Vector_3& symmetry_direction) { typedef typename Traits::FT FT; typedef typename Traits::Vector_3 Vector; // find pairs of epsilon-parallel primitives and store them in parallel_planes std::vector > parallel_planes (planes.size ()); for (std::size_t i = 0; i < std::size_t(planes.size ()); ++ i) { typename PlaneRange::iterator it = planes.begin() + i; Vector v1 = get(plane_map, *it).orthogonal_vector(); for (std::size_t j = 0; j < std::size_t(planes.size()); ++ j) { if (i == j) continue; typename PlaneRange::iterator it2 = planes.begin() + j; Vector v2 = get(plane_map, *it2).orthogonal_vector(); if (std::fabs (v1 * v2) > 1. - tolerance_cosangle) parallel_planes[i].push_back (j); } } std::vector is_available (planes.size (), true); for (std::size_t i = 0; i < std::size_t(planes.size()); ++ i) { if(is_available[i]) { const typename Traits::Plane_3& plane = get(plane_map, *(planes.begin() + i)); is_available[i] = false; clusters.push_back (Plane_cluster()); Plane_cluster& clu = clusters.back (); //initialization containers clu.planes.push_back (i); std::vector index_container_former_ring_parallel; index_container_former_ring_parallel.push_back(i); std::list index_container_current_ring_parallel; //propagation over the pairs of epsilon-parallel primitives bool propagation=true; clu.normal = plane.orthogonal_vector (); clu.area = areas[i]; do { propagation = false; for (std::size_t k = 0; k < index_container_former_ring_parallel.size(); ++ k) { std::size_t plane_index = index_container_former_ring_parallel[k]; for (std::size_t l = 0; l < parallel_planes[plane_index].size(); ++ l) { std::size_t it = parallel_planes[plane_index][l]; Vector normal_it = get(plane_map, *(planes.begin() + it)).orthogonal_vector (); if(is_available[it] && std::fabs (normal_it*clu.normal) > 1. - tolerance_cosangle ) { propagation = true; index_container_current_ring_parallel.push_back(it); is_available[it]=false; if(clu.normal * normal_it <0) normal_it = -normal_it; clu.normal = (FT)clu.area * clu.normal + (FT)areas[it] * normal_it; FT norm = (FT)1. / std::sqrt (clu.normal.squared_length()); clu.normal = norm * clu.normal; clu.area += areas[it]; } } } //update containers index_container_former_ring_parallel.clear(); for (std::list::iterator it = index_container_current_ring_parallel.begin(); it != index_container_current_ring_parallel.end(); ++it) { index_container_former_ring_parallel.push_back(*it); clu.planes.push_back(*it); } index_container_current_ring_parallel.clear(); } while(propagation); if (symmetry_direction != CGAL::NULL_VECTOR) { clu.cosangle_symmetry = symmetry_direction * clu.normal; if (clu.cosangle_symmetry < 0.) { clu.normal = -clu.normal; clu.cosangle_symmetry = -clu.cosangle_symmetry; } } } } is_available.clear(); } template void cluster_symmetric_cosangles (std::vector >& clusters, typename Traits::FT tolerance_cosangle, typename Traits::FT tolerance_cosangle_ortho) { typedef typename Traits::FT FT; std::vector < FT > cosangle_centroids; std::vector < std::size_t> list_cluster_index; for( std::size_t i = 0; i < clusters.size(); ++ i) list_cluster_index.push_back(static_cast(-1)); std::size_t mean_index = 0; for (std::size_t i = 0; i < clusters.size(); ++ i) { if(list_cluster_index[i] == static_cast(-1)) { list_cluster_index[i] = mean_index; FT mean = clusters[i].area * clusters[i].cosangle_symmetry; FT mean_area = clusters[i].area; for (std::size_t j = i+1; j < clusters.size(); ++ j) { if (list_cluster_index[j] == static_cast(-1) && std::fabs (clusters[j].cosangle_symmetry - mean / mean_area) < tolerance_cosangle_ortho) { list_cluster_index[j] = mean_index; mean_area += clusters[j].area; mean += clusters[j].area * clusters[j].cosangle_symmetry; } } ++ mean_index; mean /= mean_area; cosangle_centroids.push_back (mean); } } for (std::size_t i = 0; i < cosangle_centroids.size(); ++ i) { if (cosangle_centroids[i] < tolerance_cosangle_ortho) cosangle_centroids[i] = 0; else if (cosangle_centroids[i] > 1. - tolerance_cosangle) cosangle_centroids[i] = 1; } for (std::size_t i = 0; i < clusters.size(); ++ i) clusters[i].cosangle_symmetry = cosangle_centroids[list_cluster_index[i]]; } template void subgraph_mutually_orthogonal_clusters (std::vector >& clusters, const typename Traits::Vector_3& symmetry_direction) { typedef typename Traits::FT FT; typedef typename Traits::Vector_3 Vector; std::vector < std::vector < std::size_t> > subgraph_clusters; std::vector < std::size_t> subgraph_clusters_max_area_index; for (std::size_t i = 0; i < clusters.size(); ++ i) clusters[i].is_free = true; for (std::size_t i = 0; i < clusters.size(); ++ i) { if(clusters[i].is_free) { clusters[i].is_free = false; FT max_area = clusters[i].area; std::size_t index_max_area = i; //initialization containers std::vector < std::size_t > index_container; index_container.push_back(i); std::vector < std::size_t > index_container_former_ring; index_container_former_ring.push_back(i); std::list < std::size_t > index_container_current_ring; //propagation bool propagation=true; do { propagation=false; //neighbors for (std::size_t k=0;k::iterator it = index_container_current_ring.begin(); it != index_container_current_ring.end(); ++it) { index_container_former_ring.push_back(*it); index_container.push_back(*it); } index_container_current_ring.clear(); } while(propagation); subgraph_clusters.push_back(index_container); subgraph_clusters_max_area_index.push_back(index_max_area); } } //create subgraphs of mutually orthogonal clusters in which the //largest cluster is excluded and store in //subgraph_clusters_prop std::vector < std::vector < std::size_t> > subgraph_clusters_prop; for (std::size_t i=0;i subgraph_clusters_prop_temp; for (std::size_t j=0;j (clusters[index_current].normal, symmetry_direction, clusters[index_current].cosangle_symmetry); clusters[index_current].normal = vec_current; clusters[index_current].is_free = false; //initialization containers std::vector < std::size_t> index_container; index_container.push_back(index_current); std::vector < std::size_t> index_container_former_ring; index_container_former_ring.push_back(index_current); std::list < std::size_t> index_container_current_ring; //propagation bool propagation=true; do { propagation=false; //neighbors for (std::size_t k=0;k (clusters[cluster_index].normal, clusters[cluster_index_2].normal, symmetry_direction, clusters[cluster_index_2].cosangle_symmetry); clusters[cluster_index_2].normal = new_vect; } } } //update containers index_container_former_ring.clear(); for(std::list < std::size_t>::iterator it = index_container_current_ring.begin(); it != index_container_current_ring.end(); ++it) { index_container_former_ring.push_back(*it); index_container.push_back(*it); } index_container_current_ring.clear(); }while(propagation); } } } // namespace PlaneRegularization } // namespace internal /// \endcond // ---------------------------------------------------------------------------- // Public section // ---------------------------------------------------------------------------- /// \ingroup PkgPointSetShapeDetection3 /*! Given a set of detected planes with their respective inlier sets, this function enables to regularize the planes: - Planes near parallel can be made exactly parallel. - Planes near orthogonal can be made exactly orthogonal. - Planes parallel and near coplanar can be made exactly coplanar. - Planes near symmetrical with a user-defined axis can be made exactly symmetrical. Planes are directly modified. Points are left unaltered, as well as their relationships to planes (no transfer of point from a primitive plane to another). The implementation follows \cgalCite{cgal:vla-lod-15}. \tparam PointRange range of points, model of `ConstRange` \tparam PointPMap is a model of `ReadablePropertyMap` with value type `Kernel::Point_3`. It can be omitted if the value type of the iterator of `PointRange` is convertible to `Point_3`. \tparam PlaneRange range of planes, model of `Range` \tparam PlaneMap is a model of `WritablePropertyMap` with value type `Kernel::Plane_3`. It can be omitted if the value type of the iterator of `PlaneRange` is convertible to `Plane_3`. \tparam IndexMap is a model of `ReadablePropertyMap` with value type `int`. \tparam Kernel Geometric traits class. It can be omitted and deduced automatically from the value type of `PointMap`. \param points range of points. \param point_map property map: value_type of `typename PointRange::const_iterator` -> `Point_3` \param planes range of planes. \param plane_map property map: value_type of `typename PlaneRange::iterator` -> `Plane_3` \param index_map property map: index of point `std::size_t` -> index of plane `int` (-1 if the point is not assigned to a plane) \param regularize_parallelism Select whether parallelism is regularized or not. \param regularize_orthogonality Select whether orthogonality is regularized or not. \param regularize_coplanarity Select whether coplanarity is regularized or not. \param regularize_axis_symmetry Select whether axis symmetry is regularized or not. \param tolerance_angle Tolerance of deviation between normal vectors of planes (in degrees) used for parallelism, orthogonality and axis symmetry. Default value is 25 degrees. \param tolerance_coplanarity Maximal distance between two parallel planes such that they are considered coplanar. Default value is 0.01. \param symmetry_direction Chosen axis for symmetry regularization. Default value is the Z axis. */ // This variant requires all parameters template void regularize_planes (const PointRange& points, PointMap point_map, PlaneRange& planes, PlaneMap plane_map, IndexMap index_map, const Kernel&, bool regularize_parallelism, bool regularize_orthogonality, bool regularize_coplanarity, bool regularize_axis_symmetry, double tolerance_angle = 25.0, double tolerance_coplanarity = 0.01, typename Kernel::Vector_3 symmetry_direction = typename Kernel::Vector_3 (0., 0., 1.)) { typedef typename Kernel::FT FT; typedef typename Kernel::Point_3 Point; typedef typename Kernel::Vector_3 Vector; typedef typename Kernel::Plane_3 Plane; typedef typename internal::PlaneRegularization::Plane_cluster Plane_cluster; /* * Compute centroids and areas */ std::vector centroids; std::vector areas; internal::PlaneRegularization::compute_centroids_and_areas (points, point_map, planes.size(), index_map, centroids, areas); tolerance_angle = tolerance_angle * (FT)CGAL_PI / (FT)(180); FT tolerance_cosangle = (FT)(1. - std::cos (tolerance_angle)); FT tolerance_cosangle_ortho = (FT)(std::cos ((FT)0.5 * (FT)CGAL_PI - (FT)tolerance_angle)); // clustering the parallel primitives and store them in clusters // & compute the normal, size and cos angle to the symmetry // direction of each cluster std::vector clusters; internal::PlaneRegularization::compute_parallel_clusters (planes, plane_map, clusters, areas, (regularize_parallelism ? tolerance_cosangle : (FT)0.0), (regularize_axis_symmetry ? symmetry_direction : CGAL::NULL_VECTOR)); if (regularize_orthogonality) { //discovery orthogonal relationship between clusters for (std::size_t i = 0; i < clusters.size(); ++ i) { for (std::size_t j = i + 1; j < clusters.size(); ++ j) { if (std::fabs (clusters[i].normal * clusters[j].normal) < tolerance_cosangle_ortho) { clusters[i].orthogonal_clusters.push_back (j); clusters[j].orthogonal_clusters.push_back (i); } } } } if (regularize_axis_symmetry) { //clustering the symmetry cosangle and store their centroids in //cosangle_centroids and the centroid index of each cluster in //list_cluster_index internal::PlaneRegularization::cluster_symmetric_cosangles (clusters, tolerance_cosangle, tolerance_cosangle_ortho); } //find subgraphs of mutually orthogonal clusters (store index of //clusters in subgraph_clusters), and select the cluster of //largest area if (regularize_orthogonality || regularize_axis_symmetry) internal::PlaneRegularization::subgraph_mutually_orthogonal_clusters (clusters, (regularize_axis_symmetry ? symmetry_direction : CGAL::NULL_VECTOR)); //recompute optimal plane for each primitive after normal regularization for (std::size_t i=0; i < clusters.size(); ++ i) { Vector vec_reg = clusters[i].normal; for (std::size_t j = 0; j < clusters[i].planes.size(); ++ j) { std::size_t index_prim = clusters[i].planes[j]; const Plane& plane = get(plane_map, *(planes.begin() + index_prim)); Point pt_reg = plane.projection (centroids[index_prim]); if(plane.orthogonal_vector () * vec_reg < 0) vec_reg=-vec_reg; Plane plane_reg(pt_reg,vec_reg); if( std::fabs(plane.orthogonal_vector () * vec_reg) > 1. - tolerance_cosangle) put(plane_map, *(planes.begin() + index_prim), plane_reg); } } if (regularize_coplanarity) { //detecting co-planarity and store in list_coplanar_prim for (std::size_t i = 0; i < clusters.size(); ++ i) { Vector vec_reg = clusters[i].normal; for (std::size_t ip = 0; ip < clusters[i].planes.size(); ++ ip) clusters[i].coplanar_group.push_back (static_cast(-1)); std::size_t cop_index=0; for (std::size_t j = 0; j < clusters[i].planes.size(); ++ j) { std::size_t index_prim = clusters[i].planes[j]; if (clusters[i].coplanar_group[j] == static_cast(-1)) { const Plane& plane = get(plane_map, *(planes.begin() + index_prim)); clusters[i].coplanar_group[j] = cop_index; Point pt_reg = plane.projection(centroids[index_prim]); Plane plan_reg(pt_reg,vec_reg); for (std::size_t k = j + 1; k < clusters[i].planes.size(); ++ k) { if (clusters[i].coplanar_group[k] == static_cast(-1)) { std::size_t index_prim_next = clusters[i].planes[k]; const Plane& plane_next = get(plane_map, *(planes.begin() + index_prim_next)); Point pt_reg_next = plane_next.projection(centroids[index_prim_next]); Point pt_proj=plan_reg.projection(pt_reg_next); FT distance = std::sqrt (CGAL::squared_distance(pt_reg_next,pt_proj)); if (distance < tolerance_coplanarity) clusters[i].coplanar_group[k] = cop_index; } } cop_index++; } } //regularize primitive position by computing barycenter of cplanar planes std::vector pt_bary (cop_index, Point ((FT)0., (FT)0., (FT)0.)); std::vector area (cop_index, 0.); for (std::size_t j = 0; j < clusters[i].planes.size (); ++ j) { std::size_t index_prim = clusters[i].planes[j]; std::size_t group = clusters[i].coplanar_group[j]; Point pt_reg = get(plane_map, *(planes.begin()+index_prim)).projection(centroids[index_prim]); pt_bary[group] = CGAL::barycenter (pt_bary[group], area[group], pt_reg, areas[index_prim]); area[group] += areas[index_prim]; } for (std::size_t j = 0; j < clusters[i].planes.size (); ++ j) { std::size_t index_prim = clusters[i].planes[j]; std::size_t group = clusters[i].coplanar_group[j]; Plane plane_reg (pt_bary[group], vec_reg); if (get(plane_map, *(planes.begin() + index_prim)).orthogonal_vector () * plane_reg.orthogonal_vector() < 0) put(plane_map, *(planes.begin() + index_prim), plane_reg.opposite()); else put(plane_map, *(planes.begin() + index_prim), plane_reg); } } } } /// \cond SKIP_IN_MANUAL // This variant deduces the kernel from the point property map. template void regularize_planes (const PointRange& points, PointMap point_map, PlaneRange& planes, PlaneMap plane_map, IndexMap index_map, bool regularize_parallelism, bool regularize_orthogonality, bool regularize_coplanarity, bool regularize_axis_symmetry, double tolerance_angle = 25.0, double tolerance_coplanarity = 0.01, typename Kernel_traits ::value_type>::Kernel::Vector_3 symmetry_direction = typename Kernel_traits ::value_type>::Kernel::Vector_3 (typename Kernel_traits ::value_type>::Kernel::FT(0.), typename Kernel_traits ::value_type>::Kernel::FT(0.), typename Kernel_traits ::value_type>::Kernel::FT(1.))) { typedef typename boost::property_traits::value_type Point; typedef typename Kernel_traits::Kernel Kernel; regularize_planes (points, point_map, planes, plane_map, index_map, Kernel(), regularize_parallelism, regularize_orthogonality, regularize_coplanarity, regularize_axis_symmetry, tolerance_angle, tolerance_coplanarity, symmetry_direction); } /// \endcond } // namespace CGAL #endif // CGAL_REGULARIZE_PLANES_H