From c98027f466c7acc2bedb3097302e3ac50e505e41 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Sun, 14 May 2023 21:26:39 +0200 Subject: [PATCH 01/43] add option for RANSAC plane detection, improve polygon triangulator mesh handling --- src/detect_planes_node.cpp | 286 ++++++++++++++++++++++++++---------- src/polygon_triangulate.cpp | 34 +++-- src/stepedge_nodes.hpp | 6 + 3 files changed, 234 insertions(+), 92 deletions(-) diff --git a/src/detect_planes_node.cpp b/src/detect_planes_node.cpp index 49e3af7..480e46b 100644 --- a/src/detect_planes_node.cpp +++ b/src/detect_planes_node.cpp @@ -15,11 +15,56 @@ // along with this program. If not, see . #include "stepedge_nodes.hpp" // #include - +#include #include "plane_detect.hpp" +#include +#include +#include +#include +#include + +struct AdjacencyFinder { + + typedef CGAL::Search_traits_3 Traits_base; + typedef CGAL::Search_traits_adapter TreeTraits; + typedef CGAL::Orthogonal_k_neighbor_search Neighbor_search; + typedef Neighbor_search::Tree Tree; + + std::map> adjacencies; + + AdjacencyFinder(PNL_vector& points, size_t N=15) + { + Tree tree; + tree.insert(points.begin(), points.end()); + + for(auto& pi : points){ + auto& p = boost::get<0>(pi); + auto& l = boost::get<2>(pi); + Neighbor_search search(tree, p, N+1); + // skip the first point since it is identical to the query point + for (auto nb = search.begin()+1 ; nb < search.end(); ++nb) { + // auto& p = boost::get<0>(pi); + auto& l_nb = boost::get<2>(nb->first); + if(l > l_nb) { + adjacencies[l][l_nb]++; + } else { + adjacencies[l_nb][l]++; + } + } + } + }; +}; + namespace geoflow::nodes::stepedge { + typedef CGAL::Shape_detection::Efficient_RANSAC_traits + Traits; + typedef CGAL::Shape_detection::Efficient_RANSAC Efficient_ransac; + typedef CGAL::Shape_detection::Plane RansacPlane; + void DetectPlanesNode::process() { auto points = input("points").get(); @@ -49,32 +94,172 @@ namespace geoflow::nodes::stepedge { boost::get<1>(pv) = -n; } - // convert to lists required by the planedetector class - // size_t i=0; PointCollection points_vec; vec3f normals_vec; points_vec.reserve(points.size()); - for (auto &pt : pnl_points) { - auto& p = boost::get<0>(pt); - auto& n = boost::get<1>(pt); - points_vec.push_back( - {float(CGAL::to_double(p.x())), float(CGAL::to_double(p.y())), float(CGAL::to_double(p.z()))} - ); - normals_vec.push_back( - {float(CGAL::to_double(n.x())), float(CGAL::to_double(n.y())), float(CGAL::to_double(n.z()))} + + IndexedPlanesWithPoints pts_per_roofplane; + size_t horiz_roofplane_cnt=0; + size_t slant_roofplane_cnt=0; + if (only_horizontal) pts_per_roofplane[-1].second = std::vector(); + size_t horiz_pt_cnt=0, total_pt_cnt=0, wall_pt_cnt=0, unsegmented_pt_cnt=0, total_plane_cnt=0; + vec1f roof_elevations; + + + if (!use_ransac) { + // convert to lists required by the planedetector class + // size_t i=0; + + for (auto &pt : pnl_points) { + auto& p = boost::get<0>(pt); + auto& n = boost::get<1>(pt); + points_vec.push_back( + {float(CGAL::to_double(p.x())), float(CGAL::to_double(p.y())), float(CGAL::to_double(p.z()))} + ); + normals_vec.push_back( + {float(CGAL::to_double(n.x())), float(CGAL::to_double(n.y())), float(CGAL::to_double(n.z()))} + ); + } + // perform plane detection + planedect::PlaneDS PDS(points_vec, normals_vec, metrics_plane_k); + planedect::DistAndNormalTester DNTester( + metrics_plane_epsilon * metrics_plane_epsilon, + metrics_plane_normal_threshold, + n_refit ); + regiongrower::RegionGrower R; + R.min_segment_count = metrics_plane_min_points; + if(points.size()>metrics_plane_min_points) + R.grow_regions(PDS, DNTester); + + total_plane_cnt = R.regions.size(); + // classify horizontal/vertical planes using plane normals + for(auto region: R.regions){ + auto& plane = region.plane; + output("planes").push_back(plane); + Vector n = plane.orthogonal_vector(); + // this dot product is close to 0 for vertical planes + auto horizontality = CGAL::abs(n*Vector(0,0,1)); + bool is_wall = horizontality < metrics_is_wall_threshold; + bool is_horizontal = horizontality > metrics_is_horizontal_threshold; + // put slanted surface points at index -1 if we care only about horzontal surfaces + if (!is_wall) { + std::vector segpts; + for (auto& i : region.inliers) { + segpts.push_back(boost::get<0>(pnl_points[i])); + roof_elevations.push_back(float(boost::get<0>(pnl_points[i]).z())); + } + total_pt_cnt += segpts.size(); + if (!only_horizontal || + (only_horizontal && is_horizontal)) { + pts_per_roofplane[region.get_region_id()].second = segpts; + pts_per_roofplane[region.get_region_id()].first = plane; + } else if (!is_horizontal) { + pts_per_roofplane[-1].second.insert( + pts_per_roofplane[-1].second.end(), + segpts.begin(), + segpts.end() + ); + } + if (is_horizontal) { + horiz_pt_cnt += segpts.size(); + } + } else { // is_wall + wall_pt_cnt = region.inliers.size(); + } + if (is_horizontal) + ++horiz_roofplane_cnt; + else if (!is_wall && !is_horizontal) + ++slant_roofplane_cnt; + + for (size_t& i : region.inliers) { + boost::get<2>(pnl_points[i]) = region.get_region_id(); + boost::get<3>(pnl_points[i]) = is_wall; + boost::get<9>(pnl_points[i]) = is_horizontal; + } + } + output("plane_adj").set(R.adjacencies); + + } else { // use_ransac == true + + // Instantiate shape detection engine. + Efficient_ransac ransac; + // Provide input data. + ransac.set_input(pnl_points); + // Register planar shapes via template method. + ransac.add_shape_factory(); + + // Set parameters for shape detection. + Efficient_ransac::Parameters parameters; + // Set probability to miss the largest primitive at each iteration. + parameters.probability = metrics_probability_ransac; + // Detect shapes with at least 200 points. + parameters.min_points = metrics_plane_min_points; + // Set maximum Euclidean distance between a point and a shape. + parameters.epsilon = metrics_plane_epsilon; + // Set maximum Euclidean distance between points to be clustered. + parameters.cluster_epsilon = metrics_cluster_epsilon_ransac; + // Set maximum normal deviation. + // 0.9 < dot(surface_normal, point_normal); + parameters.normal_threshold = metrics_plane_normal_threshold; + // Detect shapes. + ransac.detect(parameters); + // Print number of detected shapes. + total_plane_cnt = ransac.shapes().end() - ransac.shapes().begin(); + + unsigned shape_id = 0; + for(auto shape: ransac.shapes()){ + ++shape_id; + RansacPlane* ransac_plane = dynamic_cast(shape.get()); + Plane plane = static_cast(*ransac_plane); + + output("planes").push_back(plane); + Vector n = plane.orthogonal_vector(); + // this dot product is close to 0 for vertical planes + auto horizontality = CGAL::abs(n*Vector(0,0,1)); + bool is_wall = horizontality < metrics_is_wall_threshold; + bool is_horizontal = horizontality > metrics_is_horizontal_threshold; + // put slanted surface points at index -1 if we care only about horzontal surfaces + if (!is_wall) { + std::vector segpts; + for (auto& i : shape->indices_of_assigned_points()) { + segpts.push_back(boost::get<0>(pnl_points[i])); + roof_elevations.push_back(float(boost::get<0>(pnl_points[i]).z())); + } + total_pt_cnt += segpts.size(); + if (!only_horizontal || + (only_horizontal && is_horizontal)) { + pts_per_roofplane[shape_id].second = segpts; + pts_per_roofplane[shape_id].first = plane; + } else if (!is_horizontal) { + pts_per_roofplane[-1].second.insert( + pts_per_roofplane[-1].second.end(), + segpts.begin(), + segpts.end() + ); + } + if (is_horizontal) { + horiz_pt_cnt += segpts.size(); + } + } else { // is_wall + wall_pt_cnt = shape->indices_of_assigned_points().size(); + } + if (is_horizontal) + ++horiz_roofplane_cnt; + else if (!is_wall && !is_horizontal) + ++slant_roofplane_cnt; + + for (const size_t& i : shape->indices_of_assigned_points()) { + boost::get<2>(pnl_points[i]) = shape_id; + boost::get<3>(pnl_points[i]) = is_wall; + boost::get<9>(pnl_points[i]) = is_horizontal; + } + } + + AdjacencyFinder adj_finder(pnl_points, metrics_plane_k); + output("plane_adj").set(adj_finder.adjacencies); + } - // perform plane detection - planedect::PlaneDS PDS(points_vec, normals_vec, metrics_plane_k); - planedect::DistAndNormalTester DNTester( - metrics_plane_epsilon * metrics_plane_epsilon, - metrics_plane_normal_threshold, - n_refit - ); - regiongrower::RegionGrower R; - R.min_segment_count = metrics_plane_min_points; - if(points.size()>metrics_plane_min_points) - R.grow_regions(PDS, DNTester); // if (regularise_planes) { @@ -91,63 +276,12 @@ namespace geoflow::nodes::stepedge { // } - // classify horizontal/vertical planes using plane normals - IndexedPlanesWithPoints pts_per_roofplane; - size_t horiz_roofplane_cnt=0; - size_t slant_roofplane_cnt=0; - if (only_horizontal) pts_per_roofplane[-1].second = std::vector(); - size_t horiz_pt_cnt=0, total_pt_cnt=0, wall_pt_cnt=0, unsegmented_pt_cnt=0; - vec1f roof_elevations; - - for(auto region: R.regions){ - auto& plane = region.plane; - output("planes").push_back(plane); - Vector n = plane.orthogonal_vector(); - // this dot product is close to 0 for vertical planes - auto horizontality = CGAL::abs(n*Vector(0,0,1)); - bool is_wall = horizontality < metrics_is_wall_threshold; - bool is_horizontal = horizontality > metrics_is_horizontal_threshold; - // put slanted surface points at index -1 if we care only about horzontal surfaces - if (!is_wall) { - std::vector segpts; - for (auto& i : region.inliers) { - segpts.push_back(boost::get<0>(pnl_points[i])); - roof_elevations.push_back(float(boost::get<0>(pnl_points[i]).z())); - } - total_pt_cnt += segpts.size(); - if (!only_horizontal || - (only_horizontal && is_horizontal)) { - pts_per_roofplane[region.get_region_id()].second = segpts; - pts_per_roofplane[region.get_region_id()].first = plane; - } else if (!is_horizontal) { - pts_per_roofplane[-1].second.insert( - pts_per_roofplane[-1].second.end(), - segpts.begin(), - segpts.end() - ); - } - if (is_horizontal) { - horiz_pt_cnt += segpts.size(); - } - } else { // is_wall - wall_pt_cnt = region.inliers.size(); - } - if (is_horizontal) - ++horiz_roofplane_cnt; - else if (!is_wall && !is_horizontal) - ++slant_roofplane_cnt; - - for (size_t& i : region.inliers) { - boost::get<2>(pnl_points[i]) = region.get_region_id(); - boost::get<3>(pnl_points[i]) = is_wall; - boost::get<9>(pnl_points[i]) = is_horizontal; - } - } + bool b_is_horizontal = float(horiz_pt_cnt)/float(total_pt_cnt) > horiz_min_count; // int roof_type=-2; // as built: -2=undefined; -1=no pts; 0=LOD1, 1=LOD1.3, 2=LOD2 std::string roof_type = "no planes"; - if (R.regions.size()==0) { + if (total_plane_cnt==0) { // roof_type=-1; roof_type = "no points"; } else if (horiz_roofplane_cnt==1 && slant_roofplane_cnt==0){ @@ -194,7 +328,7 @@ namespace geoflow::nodes::stepedge { output("is_wall").set(is_wall); output("is_horizontal").set(is_horizontal); - output("plane_adj").set(R.adjacencies); + } } \ No newline at end of file diff --git a/src/polygon_triangulate.cpp b/src/polygon_triangulate.cpp index 874828d..28ff01d 100644 --- a/src/polygon_triangulate.cpp +++ b/src/polygon_triangulate.cpp @@ -214,23 +214,25 @@ void PolygonTriangulatorNode::process() } } } else if (rings.is_connected_type(typeid(Mesh))) { - auto mesh = rings.get(); - TriangleCollection mesh_triangles; - AttributeMap mesh_attributes; - std::vector tri_labels; - for (size_t ri = 0; ri(mi); + TriangleCollection mesh_triangles; + AttributeMap mesh_attributes; + std::vector tri_labels; + for (size_t ri = 0; ri>)); + add_param(ParamBool(use_ransac, "use_ransac", "Use ransac instead of region growing plane detection")); add_param(ParamBool(only_horizontal, "only_horizontal", "Output only horizontal planes")); add_param(ParamFloat(horiz_min_count, "horiz_min_count", "Mininmal point count for horizontal planes")); add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); add_param(ParamInt(metrics_plane_k, "metrics_plane_k", "Number of neighbours used during region growing plane detection")); add_param(ParamInt(metrics_plane_min_points, "metrics_plane_min_points", "Minimum number of points in a plane")); add_param(ParamFloat(metrics_plane_epsilon, "metrics_plane_epsilon", "Plane epsilon")); + add_param(ParamFloat(metrics_cluster_epsilon_ransac, "metrics_cluster_epsilon_ransac", "Cluster epsilon RANSAC only")); + add_param(ParamFloat(metrics_probability_ransac, "metrics_probability_ransac", "Probability RANSAC only")); add_param(ParamFloat(metrics_plane_normal_threshold, "metrics_plane_normal_threshold", "Plane normal angle threshold")); add_param(ParamFloat(metrics_is_horizontal_threshold, "metrics_is_horizontal_threshold", "Threshold for horizontal plane detection (expressed as angle wrt unit verctor in +z direction)")); add_param(ParamFloat(metrics_is_wall_threshold, "metrics_is_wall_threshold", "Wall angle thres")); From 6cd53ecfc4ef53349519a58dda9620a8ba14927f Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 25 May 2023 20:46:38 +0200 Subject: [PATCH 02/43] add Regularise planes node --- register.hpp | 1 + src/detect_planes_node.cpp | 114 +++++++++++++++++++++++++++++++------ src/stepedge_nodes.hpp | 34 +++++++++++ 3 files changed, 131 insertions(+), 18 deletions(-) diff --git a/register.hpp b/register.hpp index da96719..6ad76a2 100644 --- a/register.hpp +++ b/register.hpp @@ -30,6 +30,7 @@ void register_nodes(geoflow::NodeRegister& node_register) { node_register.register_node("BuildArrFromLines"); node_register.register_node("DetectLines"); node_register.register_node("DetectPlanes"); + node_register.register_node("RegularisePlanes"); node_register.register_node("LASInPolygons"); #ifdef GFP_WITH_PDAL node_register.register_node("EptInPolygons"); diff --git a/src/detect_planes_node.cpp b/src/detect_planes_node.cpp index 480e46b..8aca3a7 100644 --- a/src/detect_planes_node.cpp +++ b/src/detect_planes_node.cpp @@ -13,8 +13,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#include "point_edge.h" #include "stepedge_nodes.hpp" -// #include #include #include "plane_detect.hpp" @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include struct AdjacencyFinder { @@ -261,23 +264,6 @@ namespace geoflow::nodes::stepedge { } - - // if (regularise_planes) { - // // Regularize detected planes. - // CGAL::regularize_planes(points, - // Point_map(), - // planes, - // CGAL::Shape_detection::Plane_map(), - // CGAL::Shape_detection::Point_to_shape_index_map(points, planes), - // true, // regularize parallelism - // true, // regularize orthogonality - // false, // do not regularize coplanarity - // true, // regularize Z-symmetry (default) - // } - - - - bool b_is_horizontal = float(horiz_pt_cnt)/float(total_pt_cnt) > horiz_min_count; // int roof_type=-2; // as built: -2=undefined; -1=no pts; 0=LOD1, 1=LOD1.3, 2=LOD2 std::string roof_type = "no planes"; @@ -331,4 +317,96 @@ namespace geoflow::nodes::stepedge { } + struct Custom_point_map + { + using key_type = arr3f; // The iterator's value type is an index + using value_type = Point; // The object manipulated by the algorithm is a Point + using reference = Point; // The object does not exist in memory, so there's no reference + using category = boost::readable_property_map_tag; // The property map is used both + // for reading and writing data + // The get() function returns the object expected by the algorithm (here, Point) + friend Point get (const Custom_point_map& map, const arr3f& p) + { + return Point (p[0], + p[1], + p[2]); + } + }; + struct Custom_plane_map + { + using key_type = Plane; // The iterator's value type is an index + using value_type = Plane; // The object manipulated by the algorithm is a Plane + using reference = Plane&; // The object does not exist in memory, so there's no reference + using category = boost::read_write_property_map_tag; // The property map is used both + // for reading and writing data + // The get() function returns the object expected by the algorithm (here, Point) + friend Plane get (const Custom_plane_map& map, Plane& plane) + { + return plane; + } + friend const Plane get (const Custom_plane_map& map, const Plane& plane) + { + return plane; + } + // The put() function updated the user's data structure from the + // object handled by the algorithm (here Plane) + friend void put (const Custom_plane_map& map, Plane& plane_old, const Plane& plane_new) + { + plane_old = plane_new; + } + }; + struct Custom_plane_index_map + { + using key_type = std::size_t; // The iterator's value type is an index + using value_type = int; // The object manipulated by the algorithm is a Plane + using reference = int; // The object does not exist in memory, so there's no reference + using category = boost::readable_property_map_tag; // The property map is used both + // for reading and writing data + vec1i* plane_id; + Custom_plane_index_map (vec1i* plane_id=nullptr) + : plane_id (plane_id) { } + // The get() function returns the object expected by the algorithm (here, Plane) + friend int get (const Custom_plane_index_map& map, const std::size_t& idx) + { + if ((*map.plane_id)[idx] == 0) + return -1; + else + return (*map.plane_id)[idx]-1; + } + }; + + void RegularisePlanesNode::process() { + + auto& points = input("points").get(); + auto& plane_id = input("plane_id").get(); + auto& planes_inp = input("planes"); + + std::vector planes; + planes.reserve(planes_inp.size()); + for (size_t i=0; i(i) ); + } + + // Regularize detected planes. + CGAL::Shape_regularization::Planes::regularize_planes( + planes, + points, + CGAL::parameters:: + plane_map(Custom_plane_map()). + point_map(Custom_point_map()). + plane_index_map(Custom_plane_index_map(&plane_id)). + maximum_angle(maximum_angle_). + maximum_offset(maximum_offset_). + regularize_parallelism(regularize_parallelism_). + regularize_orthogonality(regularize_orthogonality_). + regularize_coplanarity(regularize_coplanarity_). + regularize_axis_symmetry(regularize_axis_symmetry_) + // symmetry_direction(symmetry_direction_) + ); + + for (auto& pl : planes) { + output("planes").push_back( pl ); + } + } + } \ No newline at end of file diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index d839c24..47f7d7c 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -15,6 +15,7 @@ // along with this program. If not, see . #pragma once +#include #include #include "arrangement.hpp" @@ -540,6 +541,39 @@ namespace geoflow::nodes::stepedge { // void process() override; // }; + class RegularisePlanesNode:public Node { + float maximum_angle_ = 25; + float maximum_offset_ = 0.01; + bool regularize_parallelism_ = true; + bool regularize_orthogonality_ = true; + bool regularize_coplanarity_ = true; + bool regularize_axis_symmetry_ = true; + // symmetry_direction_ + public: + using Node::Node; + + void init() override { + + add_input("points", typeid(PointCollection)); + add_input("plane_id", typeid(vec1i)); + add_input("planes", typeid(Plane)); + // add_input("points", typeid(PointCollection)); + // add_output("is_wall", typeid(vec1i)); + // add_output("is_horizontal", typeid(vec1i)); + add_output("planes", typeid(Plane)); + + // add_output("pts_per_roofplane", typeid(IndexedPlanesWithPoints )); + + add_param(ParamBool(regularize_parallelism_, "regularize_parallelism", "regularizeparallelism")); + add_param(ParamBool(regularize_orthogonality_, "regularize_orthogonality", "regularizeorthogonality")); + add_param(ParamBool(regularize_coplanarity_, "regularize_coplanarity", "regularizecoplanarity")); + add_param(ParamFloat(maximum_angle_, "maximum_angle", "maximum allowed angle in degrees between plane normals used for parallelism, orthogonality, and axis symmetry")); + add_param(ParamFloat(maximum_offset_, "maximum_offset", "maximum allowed orthogonal distance between two parallel planes such that they are considered to be coplanar")); + } + + void process() override; + }; + class DetectPlanesNode:public Node { bool only_horizontal = true; float horiz_min_count = 0.95; From 1bb9ed089e4c728fb866884e1f17a63ae6e4d4f9 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Tue, 30 May 2023 12:03:03 +0200 Subject: [PATCH 03/43] finish regularise planes node --- src/detect_planes_node.cpp | 55 ++++++++++++++++++++++++++++++++++++-- src/stepedge_nodes.hpp | 8 +++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/detect_planes_node.cpp b/src/detect_planes_node.cpp index 8aca3a7..783a3ac 100644 --- a/src/detect_planes_node.cpp +++ b/src/detect_planes_node.cpp @@ -24,8 +24,11 @@ #include #include #include +#include #include #include +#include +#include struct AdjacencyFinder { @@ -375,6 +378,27 @@ namespace geoflow::nodes::stepedge { } }; + + // allow us to hash Plane instances + template + inline void hash_combine(std::size_t& seed, const T& v) + { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + struct PlaneHash { + std::size_t operator()(const Plane& k) const + { + size_t seed = std::hash{}(k.a()); + boost::hash_combine(seed, k.b()); + boost::hash_combine(seed, k.c()); + boost::hash_combine(seed, k.d()); + return seed; + } + }; + + inline void RegularisePlanesNode::process() { auto& points = input("points").get(); @@ -404,9 +428,36 @@ namespace geoflow::nodes::stepedge { // symmetry_direction(symmetry_direction_) ); - for (auto& pl : planes) { - output("planes").push_back( pl ); + std::unordered_map, PlaneHash> plane_merge_map; + for (int pt_i=0 ; points.size(); ++pt_i) { + auto pid = plane_id[pt_i]; + if (pid > 0){ + const auto& pl = planes[pid+1]; + if (plane_merge_map.find(pl) == plane_merge_map.end()) { + plane_merge_map[pl] = { Point(points[pt_i][0], points[pt_i][1], points[pt_i][2]) }; + } else { + plane_merge_map[pl].push_back( Point(points[pt_i][0], points[pt_i][1], points[pt_i][2]) ); + } + } } + IndexedPlanesWithPoints pts_per_roofplane; + + int plane_cnt = 1; + for(auto& [plane, ptvec] : plane_merge_map) { + Vector n = plane.orthogonal_vector(); + // this dot product is close to 0 for vertical planes + auto horizontality = CGAL::abs(n*Vector(0,0,1)); + bool is_wall = horizontality < metrics_is_wall_threshold; + + if (!is_wall) { + pts_per_roofplane[plane_cnt++] = std::make_pair(plane, ptvec); + } + output("planes").push_back( plane ); + } + + // make pts_per_roofplane + + } } \ No newline at end of file diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index 47f7d7c..936867d 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -548,6 +548,9 @@ namespace geoflow::nodes::stepedge { bool regularize_orthogonality_ = true; bool regularize_coplanarity_ = true; bool regularize_axis_symmetry_ = true; + + + float metrics_is_wall_threshold = 0.3; // symmetry_direction_ public: using Node::Node; @@ -562,13 +565,16 @@ namespace geoflow::nodes::stepedge { // add_output("is_horizontal", typeid(vec1i)); add_output("planes", typeid(Plane)); - // add_output("pts_per_roofplane", typeid(IndexedPlanesWithPoints )); + add_output("pts_per_roofplane", typeid(IndexedPlanesWithPoints )); add_param(ParamBool(regularize_parallelism_, "regularize_parallelism", "regularizeparallelism")); add_param(ParamBool(regularize_orthogonality_, "regularize_orthogonality", "regularizeorthogonality")); add_param(ParamBool(regularize_coplanarity_, "regularize_coplanarity", "regularizecoplanarity")); add_param(ParamFloat(maximum_angle_, "maximum_angle", "maximum allowed angle in degrees between plane normals used for parallelism, orthogonality, and axis symmetry")); add_param(ParamFloat(maximum_offset_, "maximum_offset", "maximum allowed orthogonal distance between two parallel planes such that they are considered to be coplanar")); + + add_param(ParamFloat(metrics_is_wall_threshold, "metrics_is_wall_threshold", "Wall angle thres")); + } void process() override; From 0a94023a6d83e501b5c415de917b651d9d554e91 Mon Sep 17 00:00:00 2001 From: Ylannl Date: Tue, 30 May 2023 13:39:25 +0200 Subject: [PATCH 04/43] detect when a triangulated CGAL SurfaceMesh face somehow seems to have more than 3 vertices and skip those faces --- src/MeshClipperNode.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/MeshClipperNode.cpp b/src/MeshClipperNode.cpp index 524374b..2256f6e 100644 --- a/src/MeshClipperNode.cpp +++ b/src/MeshClipperNode.cpp @@ -140,16 +140,20 @@ namespace geoflow::nodes::stepedge { if (skip_clip_ || cgal_clip_) { for (auto& f : smesh.faces()) { Triangle t; - unsigned i = 0; - + + unsigned i = 0; for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { + if(i==3) { + std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; + continue; + } auto& p = smesh.point(vi); t[i++] = arr3f{ (float) p.x(), (float) p.y(), (float) p.z() }; - } + } auto& n = fnormals[f]; normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); @@ -163,4 +167,4 @@ namespace geoflow::nodes::stepedge { // output("cgal_surface_mesh").set(smesh); } -} \ No newline at end of file +} From e00226512008c042b6f2d1111a227b1a59aad65f Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Tue, 30 May 2023 14:21:41 +0200 Subject: [PATCH 05/43] formatting --- src/MeshClipperNode.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MeshClipperNode.cpp b/src/MeshClipperNode.cpp index 2256f6e..f59ae26 100644 --- a/src/MeshClipperNode.cpp +++ b/src/MeshClipperNode.cpp @@ -143,10 +143,10 @@ namespace geoflow::nodes::stepedge { unsigned i = 0; for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { - if(i==3) { - std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; - continue; - } + if(i==3) { + std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; + continue; + } auto& p = smesh.point(vi); t[i++] = arr3f{ (float) p.x(), From c752fb7f2a9efda0861144c8dd6fc7dac01c9365 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 5 Jun 2023 21:26:52 +0200 Subject: [PATCH 06/43] implement triangle based volume calculation --- src/detect_planes_node.cpp | 108 +++++++++++++++++++++++++----------- src/polygon_triangulate.cpp | 20 ++++++- src/stepedge_nodes.hpp | 10 ++-- 3 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/detect_planes_node.cpp b/src/detect_planes_node.cpp index 783a3ac..196c1e2 100644 --- a/src/detect_planes_node.cpp +++ b/src/detect_planes_node.cpp @@ -140,16 +140,20 @@ namespace geoflow::nodes::stepedge { total_plane_cnt = R.regions.size(); // classify horizontal/vertical planes using plane normals + unsigned shape_id = 0; for(auto region: R.regions){ + auto& plane = region.plane; - output("planes").push_back(plane); + Vector n = plane.orthogonal_vector(); // this dot product is close to 0 for vertical planes auto horizontality = CGAL::abs(n*Vector(0,0,1)); bool is_wall = horizontality < metrics_is_wall_threshold; - bool is_horizontal = horizontality > metrics_is_horizontal_threshold; + bool is_horizontal = horizontality > metrics_is_horizontal_threshold; + // put slanted surface points at index -1 if we care only about horzontal surfaces if (!is_wall) { + ++shape_id; std::vector segpts; for (auto& i : region.inliers) { segpts.push_back(boost::get<0>(pnl_points[i])); @@ -158,8 +162,8 @@ namespace geoflow::nodes::stepedge { total_pt_cnt += segpts.size(); if (!only_horizontal || (only_horizontal && is_horizontal)) { - pts_per_roofplane[region.get_region_id()].second = segpts; - pts_per_roofplane[region.get_region_id()].first = plane; + pts_per_roofplane[shape_id].second = segpts; + pts_per_roofplane[shape_id].first = plane; } else if (!is_horizontal) { pts_per_roofplane[-1].second.insert( pts_per_roofplane[-1].second.end(), @@ -178,10 +182,13 @@ namespace geoflow::nodes::stepedge { else if (!is_wall && !is_horizontal) ++slant_roofplane_cnt; - for (size_t& i : region.inliers) { - boost::get<2>(pnl_points[i]) = region.get_region_id(); - boost::get<3>(pnl_points[i]) = is_wall; - boost::get<9>(pnl_points[i]) = is_horizontal; + if(!is_wall) { + output("planes").push_back(plane); + for (size_t& i : region.inliers) { + boost::get<2>(pnl_points[i]) = shape_id; + boost::get<3>(pnl_points[i]) = is_wall; + boost::get<9>(pnl_points[i]) = is_horizontal; + } } } output("plane_adj").set(R.adjacencies); @@ -215,11 +222,9 @@ namespace geoflow::nodes::stepedge { unsigned shape_id = 0; for(auto shape: ransac.shapes()){ - ++shape_id; + RansacPlane* ransac_plane = dynamic_cast(shape.get()); Plane plane = static_cast(*ransac_plane); - - output("planes").push_back(plane); Vector n = plane.orthogonal_vector(); // this dot product is close to 0 for vertical planes auto horizontality = CGAL::abs(n*Vector(0,0,1)); @@ -227,6 +232,7 @@ namespace geoflow::nodes::stepedge { bool is_horizontal = horizontality > metrics_is_horizontal_threshold; // put slanted surface points at index -1 if we care only about horzontal surfaces if (!is_wall) { + ++shape_id; std::vector segpts; for (auto& i : shape->indices_of_assigned_points()) { segpts.push_back(boost::get<0>(pnl_points[i])); @@ -255,10 +261,13 @@ namespace geoflow::nodes::stepedge { else if (!is_wall && !is_horizontal) ++slant_roofplane_cnt; - for (const size_t& i : shape->indices_of_assigned_points()) { - boost::get<2>(pnl_points[i]) = shape_id; - boost::get<3>(pnl_points[i]) = is_wall; - boost::get<9>(pnl_points[i]) = is_horizontal; + if(!is_wall) { + output("planes").push_back(plane); + for (const size_t& i : shape->indices_of_assigned_points()) { + boost::get<2>(pnl_points[i]) = shape_id; + boost::get<3>(pnl_points[i]) = is_wall; + boost::get<9>(pnl_points[i]) = is_horizontal; + } } } @@ -369,6 +378,7 @@ namespace geoflow::nodes::stepedge { Custom_plane_index_map (vec1i* plane_id=nullptr) : plane_id (plane_id) { } // The get() function returns the object expected by the algorithm (here, Plane) + // return plane based on point idx friend int get (const Custom_plane_index_map& map, const std::size_t& idx) { if ((*map.plane_id)[idx] == 0) @@ -411,25 +421,42 @@ namespace geoflow::nodes::stepedge { planes.emplace_back( planes_inp.get(i) ); } - // Regularize detected planes. - CGAL::Shape_regularization::Planes::regularize_planes( - planes, - points, - CGAL::parameters:: - plane_map(Custom_plane_map()). - point_map(Custom_point_map()). - plane_index_map(Custom_plane_index_map(&plane_id)). - maximum_angle(maximum_angle_). - maximum_offset(maximum_offset_). - regularize_parallelism(regularize_parallelism_). - regularize_orthogonality(regularize_orthogonality_). - regularize_coplanarity(regularize_coplanarity_). - regularize_axis_symmetry(regularize_axis_symmetry_) - // symmetry_direction(symmetry_direction_) - ); + // Custom_plane_index_map index_map(&plane_id); + // Custom_point_map point_map; + // std::vector< std::vector > listp(planes.size()); + // for (std::size_t i = 0; i < points.size(); ++i) { + // const int idx = get(index_map, i); + // std::cout << idx << std::endl; + // if (idx != -1) { + // listp[std::size_t(idx)].push_back( + // get(point_map, *(points.begin() + i))); + // } + // } + std::cout << "N planes before: " << planes.size() << std::endl; + // Regularize detected planes. + if (regularize_parallelism_ || + regularize_orthogonality_ || + regularize_coplanarity_ || + regularize_axis_symmetry_) { + CGAL::Shape_regularization::Planes::regularize_planes( + planes, + points, + CGAL::parameters:: + plane_map(Custom_plane_map()). + point_map(Custom_point_map()). + plane_index_map(Custom_plane_index_map(&plane_id)). + maximum_angle(maximum_angle_). + maximum_offset(maximum_offset_). + regularize_parallelism(regularize_parallelism_). + regularize_orthogonality(regularize_orthogonality_). + regularize_coplanarity(regularize_coplanarity_). + regularize_axis_symmetry(regularize_axis_symmetry_) + // symmetry_direction(symmetry_direction_) + ); + } std::unordered_map, PlaneHash> plane_merge_map; - for (int pt_i=0 ; points.size(); ++pt_i) { + for (int pt_i=0 ; pt_i < points.size(); ++pt_i) { auto pid = plane_id[pt_i]; if (pid > 0){ const auto& pl = planes[pid+1]; @@ -454,9 +481,24 @@ namespace geoflow::nodes::stepedge { } output("planes").push_back( plane ); } + std::cout << "N planes after: " << plane_cnt << std::endl; - // make pts_per_roofplane + + PNL_vector pnl_points; + for (auto& [pid, planepts] : pts_per_roofplane) { + for (auto& p : planepts.second) { + PNL pv; + boost::get<0>(pv) = Point(p[0], p[1], p[2]); + boost::get<2>(pv) = pid; + pnl_points.push_back(pv); + } + } + auto metrics_plane_k = 15; + AdjacencyFinder adj_finder(pnl_points, metrics_plane_k); + output("plane_adj").set(adj_finder.adjacencies); + // make pts_per_roofplane + output("pts_per_roofplane").push_back( pts_per_roofplane ); } diff --git a/src/polygon_triangulate.cpp b/src/polygon_triangulate.cpp index 28ff01d..b10953b 100644 --- a/src/polygon_triangulate.cpp +++ b/src/polygon_triangulate.cpp @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#include #include #include @@ -20,6 +21,7 @@ #include #include +#include "point_edge.h" #include "polygon_util.hpp" #include "cdt_util.hpp" @@ -33,8 +35,7 @@ typedef CGAL::Plane_3 Plane_3; glm::vec3 calculate_normal(const LinearRing ring) { glm::vec3 normal(0, 0, 0); - for (size_t i = 0; i < ring.size(); ++i) - { + for (size_t i = 0; i < ring.size(); ++i) { const auto &curr = ring[i]; const auto &next = ring[(i + 1) % ring.size()]; normal[0] += (curr[1] - next[1]) * (curr[2] + next[2]); @@ -44,6 +45,17 @@ glm::vec3 calculate_normal(const LinearRing ring) return glm::normalize(normal); } +double calculate_volume(const TriangleCollection& triangle_collection) { + double sum = 0; + for(const auto& t : triangle_collection) { + auto a = Vector(t[0][0], t[0][1], t[0][2]); + auto b = Vector(t[1][0], t[1][1], t[1][2]); + auto c = Vector(t[2][0], t[2][1], t[2][2]); + sum += CGAL::scalar_product(a, CGAL::cross_product(b, c)); + } + return sum/6; +} + // void mark_domains(CDT& cdt) { // std::list explorables; @@ -175,6 +187,7 @@ void PolygonTriangulatorNode::process() vec3f normals; // vec1f values_out; vec1i ring_ids; + vec1f volumes; // vec1i nesting_levels; // SegmentCollection edges; // vec1i edges_constr; @@ -194,6 +207,7 @@ void PolygonTriangulatorNode::process() // cut a footprint into parts because of cutting off the underground part. for (size_t mi = 0; mi < rings.size(); ++mi) { auto meshmap = rings.get>(mi); + double volume_sum = 0; for(auto& [sid, mesh] : meshmap) { TriangleCollection mesh_triangles; AttributeMap mesh_attributes; @@ -211,7 +225,9 @@ void PolygonTriangulatorNode::process() multitrianglecols.push_back(mesh_triangles); multitrianglecols.push_back(mesh_attributes); multitrianglecols.building_part_ids_.push_back(sid); + volume_sum += calculate_volume(mesh_triangles); } + volumes.push_back(volume_sum); } } else if (rings.is_connected_type(typeid(Mesh))) { for (size_t mi = 0; mi < rings.size(); ++mi) { diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index 936867d..7cff373 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -545,11 +545,11 @@ namespace geoflow::nodes::stepedge { float maximum_angle_ = 25; float maximum_offset_ = 0.01; bool regularize_parallelism_ = true; - bool regularize_orthogonality_ = true; + bool regularize_orthogonality_ = false; bool regularize_coplanarity_ = true; - bool regularize_axis_symmetry_ = true; - + bool regularize_axis_symmetry_ = false; + int metrics_plane_k = 15; float metrics_is_wall_threshold = 0.3; // symmetry_direction_ public: @@ -564,15 +564,17 @@ namespace geoflow::nodes::stepedge { // add_output("is_wall", typeid(vec1i)); // add_output("is_horizontal", typeid(vec1i)); add_output("planes", typeid(Plane)); - + add_output("plane_adj", typeid(std::map>)); add_output("pts_per_roofplane", typeid(IndexedPlanesWithPoints )); add_param(ParamBool(regularize_parallelism_, "regularize_parallelism", "regularizeparallelism")); add_param(ParamBool(regularize_orthogonality_, "regularize_orthogonality", "regularizeorthogonality")); add_param(ParamBool(regularize_coplanarity_, "regularize_coplanarity", "regularizecoplanarity")); + add_param(ParamBool(regularize_axis_symmetry_, "regularize_axis_symmetry", "regularize_axis_symmetry")); add_param(ParamFloat(maximum_angle_, "maximum_angle", "maximum allowed angle in degrees between plane normals used for parallelism, orthogonality, and axis symmetry")); add_param(ParamFloat(maximum_offset_, "maximum_offset", "maximum allowed orthogonal distance between two parallel planes such that they are considered to be coplanar")); + add_param(ParamInt(metrics_plane_k, "metrics_plane_k", "Number of neighbours used during adjacency detection")); add_param(ParamFloat(metrics_is_wall_threshold, "metrics_is_wall_threshold", "Wall angle thres")); } From 0f315150c7234b29063dd03990fc1d25ff8f775f Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Tue, 6 Jun 2023 13:21:45 +0200 Subject: [PATCH 07/43] add volume calculation to triangulation node --- src/polygon_triangulate.cpp | 6 ++++-- src/stepedge_nodes.hpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/polygon_triangulate.cpp b/src/polygon_triangulate.cpp index b10953b..6276b0e 100644 --- a/src/polygon_triangulate.cpp +++ b/src/polygon_triangulate.cpp @@ -187,7 +187,7 @@ void PolygonTriangulatorNode::process() vec3f normals; // vec1f values_out; vec1i ring_ids; - vec1f volumes; + auto& volumes = output("volumes"); // vec1i nesting_levels; // SegmentCollection edges; // vec1i edges_constr; @@ -201,6 +201,7 @@ void PolygonTriangulatorNode::process() triangulate_polygon(poly_3d, normals, tc, ri, ring_ids); triangles.insert(triangles.end(), tc.begin(), tc.end()); } + volumes.push_back((float)calculate_volume(triangles)); multitrianglecols.push_back(triangles); } else if (rings.is_connected_type(typeid(std::unordered_map))) { // We are processing a building part here. We get a building part when we @@ -227,7 +228,7 @@ void PolygonTriangulatorNode::process() multitrianglecols.building_part_ids_.push_back(sid); volume_sum += calculate_volume(mesh_triangles); } - volumes.push_back(volume_sum); + volumes.push_back((float)volume_sum); } } else if (rings.is_connected_type(typeid(Mesh))) { for (size_t mi = 0; mi < rings.size(); ++mi) { @@ -244,6 +245,7 @@ void PolygonTriangulatorNode::process() triangles.insert(triangles.end(), tc.begin(), tc.end()); mesh_triangles.insert(mesh_triangles.end(), tc.begin(), tc.end()); } + volumes.push_back((float)calculate_volume(mesh_triangles)); mesh_attributes["labels"] = tri_labels; multitrianglecols.push_back(mesh_triangles); multitrianglecols.push_back(mesh_attributes); diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index 7cff373..3802624 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -1130,6 +1130,7 @@ namespace geoflow::nodes::stepedge { add_output("triangles", typeid(TriangleCollection)); add_output("multi_triangle_collections", typeid(MultiTriangleCollection)); add_output("normals", typeid(vec3f)); + add_output("volumes", typeid(float)); add_output("ring_ids", typeid(vec1i)); // add_output("nesting_levels", typeid(vec1i)); From ba9fb118a0c689e7a9c49d295b871eccd899be21 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 7 Jun 2023 21:14:31 +0200 Subject: [PATCH 08/43] add option to output one mtc for each mesh input in triangulator node --- src/polygon_triangulate.cpp | 14 ++++++++++++-- src/stepedge_nodes.hpp | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/polygon_triangulate.cpp b/src/polygon_triangulate.cpp index 6276b0e..33b443d 100644 --- a/src/polygon_triangulate.cpp +++ b/src/polygon_triangulate.cpp @@ -203,6 +203,7 @@ void PolygonTriangulatorNode::process() } volumes.push_back((float)calculate_volume(triangles)); multitrianglecols.push_back(triangles); + output("multi_triangle_collections").set(multitrianglecols); } else if (rings.is_connected_type(typeid(std::unordered_map))) { // We are processing a building part here. We get a building part when we // cut a footprint into parts because of cutting off the underground part. @@ -230,7 +231,13 @@ void PolygonTriangulatorNode::process() } volumes.push_back((float)volume_sum); } + output("multi_triangle_collections").set(multitrianglecols); } else if (rings.is_connected_type(typeid(Mesh))) { + if(output_mtc_for_every_input) { + multitrianglecols.get_tricollections().clear(); + multitrianglecols.get_attributes().clear(); + multitrianglecols.building_part_ids_.clear(); + } for (size_t mi = 0; mi < rings.size(); ++mi) { auto mesh = rings.get(mi); TriangleCollection mesh_triangles; @@ -249,13 +256,16 @@ void PolygonTriangulatorNode::process() mesh_attributes["labels"] = tri_labels; multitrianglecols.push_back(mesh_triangles); multitrianglecols.push_back(mesh_attributes); - multitrianglecols.building_part_ids_.push_back(0); + multitrianglecols.building_part_ids_.push_back(mi); + if(output_mtc_for_every_input) { + output("multi_triangle_collections").push_back(multitrianglecols); + } } + if(!output_mtc_for_every_input) output("multi_triangle_collections").set(multitrianglecols); } // set outputs output("triangles").set(triangles); - output("multi_triangle_collections").set(multitrianglecols); output("normals").set(normals); output("ring_ids").set(ring_ids); // output("nesting_levels").set(nesting_levels); diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index 3802624..664b127 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -1120,6 +1120,7 @@ namespace geoflow::nodes::stepedge { class PolygonTriangulatorNode:public Node { int dupe_threshold_exp = 6; bool output_all_triangles = false; + bool output_mtc_for_every_input = false; void triangulate_polygon(LinearRing& ring, vec3f& normals, TriangleCollection& triangles, size_t& ring_id, vec1i& ring_ids); public: @@ -1138,6 +1139,7 @@ namespace geoflow::nodes::stepedge { // add_output("edges_constr", typeid(vec1i)); add_param(ParamBool(output_all_triangles, "output_all_triangles", "Also output triangles in holes and outside convex hull.")); + add_param(ParamBool(output_mtc_for_every_input, "output_mtc_for_every_input", "Output a MultiTriangleCollection for every input. Otherwise aggregate all inputs into one MultiTriangleCollection. Only applies to Mesh type inputs.")); add_param(ParamInt(dupe_threshold_exp, "dupe_threshold_exp", "Dupe tolerance exponent")); } From c5a05daed870959a30a370599448882f3bab6e1d Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 12 Jun 2023 20:37:24 +0200 Subject: [PATCH 09/43] add RoofPartition3DBAGRasteriseNode --- register.hpp | 1 + src/heightfield_nodes.cpp | 91 +++++++++++++++++++++++++++++++++++++++ src/stepedge_nodes.hpp | 20 +++++++++ 3 files changed, 112 insertions(+) diff --git a/register.hpp b/register.hpp index 6ad76a2..b4d36ee 100644 --- a/register.hpp +++ b/register.hpp @@ -83,4 +83,5 @@ void register_nodes(geoflow::NodeRegister& node_register) { node_register.register_node("MeshClipper"); node_register.register_node("Mesh2TriangleCollection"); node_register.register_node("Mesh2CGALSurfaceMesh"); + node_register.register_node("RoofPartition3DBAGRasterise"); } \ No newline at end of file diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index 5b96109..fc80c84 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -18,7 +18,12 @@ #include #include #include +#include +#include +#include +#include "Raster.h" +#include "point_edge.h" #include "stepedge_nodes.hpp" #include "pip_util.hpp" @@ -562,6 +567,92 @@ namespace geoflow::nodes::stepedge { output("image").set(I); } + void rasterise_ring(LinearRing& polygon, RasterTools::Raster& r) { + typedef double FT; + typedef CGAL::Simple_cartesian K; + + K::Plane_3 plane; + std::vector pts; + for (auto& p : polygon) { + pts.push_back(K::Point_3(p[0], p[1], p[2])); + } + // fit plane to polygon pts + linear_least_squares_fitting_3(pts.begin(),pts.end(),plane,CGAL::Dimension_tag<0>()); + + auto box = polygon.box(); + auto bb_min = box.min(); + auto bb_max = box.max(); + auto cr_min = r.getColRowCoord(bb_min[0], bb_min[1]); + auto cr_max = r.getColRowCoord(bb_max[0], bb_max[1]); + + auto points_inside = r.rasterise_polygon(polygon, cr_min, cr_max); + for (auto& p : points_inside) { + float z_interpolate = -plane.a()/plane.c() * p[0] - plane.b()/plane.c()*p[1] - plane.d()/plane.c(); + r.add_point(p[0], p[1], z_interpolate, RasterTools::MAX); + } + + } + + void calculate_h_attr(gfSingleFeatureInputTerminal& roofparts, gfMultiFeatureOutputTerminal& h_attr, RasterTools::Raster& r_lod22) { + h_attr.add_vector("h_min", typeid(float)); + h_attr.add_vector("h_max", typeid(float)); + h_attr.add_vector("h_50p", typeid(float)); + h_attr.add_vector("h_70p", typeid(float)); + for (size_t i=0; i< roofparts.size(); ++i) { + auto polygon = roofparts.get(i); + auto box = polygon.box(); + auto bb_min = box.min(); + auto bb_max = box.max(); + auto cr_min = r_lod22.getColRowCoord(bb_min[0], bb_min[1]); + auto cr_max = r_lod22.getColRowCoord(bb_max[0], bb_max[1]); + auto part_points = r_lod22.rasterise_polygon(polygon, cr_min, cr_max, false); + + if(part_points.size()==0) { + part_points.insert(part_points.begin(), polygon.begin(), polygon.end()); + } + std::sort(part_points.begin(), part_points.end(), [](auto& p1, auto& p2) { + return p1[2] < p2[2]; + }); + + size_t datasize = part_points.size(); + int elevation_id = std::floor(0.5*float(datasize-1)); + h_attr.sub_terminal("h_50p").push_back(part_points[elevation_id][2]); + elevation_id = std::floor(0.7*float(datasize-1)); + h_attr.sub_terminal("h_70p").push_back(part_points[elevation_id][2]); + h_attr.sub_terminal("h_min").push_back(part_points[0][2]); + h_attr.sub_terminal("h_max").push_back(part_points[datasize-1][2]); + } + } + + void RoofPartition3DBAGRasteriseNode::process(){ + auto& lod12_roofparts = input("lod12_roofparts");//.get(); + auto& lod13_roofparts = input("lod13_roofparts");//.get(); + auto& lod22_roofparts = input("lod22_roofparts");//.get(); + + auto& lod12_hattr = poly_output("lod12_hattr"); + auto& lod13_hattr = poly_output("lod13_hattr"); + auto& lod22_hattr = poly_output("lod22_hattr"); + + Box box; + for (size_t i=0; i< lod12_roofparts.size(); ++i) { + auto rpart = lod12_roofparts.get(i); + box.add(rpart.box()); + } + auto boxmin = box.min(); + auto boxmax = box.max(); + + RasterTools::Raster r_lod22 = RasterTools::Raster(cellsize, boxmin[0]-0.5, boxmax[0]+0.5, boxmin[1]-0.5, boxmax[1]+0.5); + r_lod22.prefill_arrays(RasterTools::MAX); + for (size_t i=0; i< lod22_roofparts.size(); ++i) { + auto ring = lod22_roofparts.get(i); + rasterise_ring(ring, r_lod22); + } + + calculate_h_attr(lod12_roofparts, lod12_hattr, r_lod22); + calculate_h_attr(lod13_roofparts, lod13_hattr, r_lod22); + calculate_h_attr(lod22_roofparts, lod22_hattr, r_lod22); + } + void SegmentRasteriseNode::rasterise_input(gfSingleFeatureInputTerminal& input_triangles, RasterTools::Raster& r, size_t& data_pixel_cnt) { typedef CGAL::Exact_predicates_inexact_constructions_kernel K; for(size_t i=0; i< input_triangles.size(); ++i) { diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index 664b127..c58bb59 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -968,6 +968,26 @@ namespace geoflow::nodes::stepedge { }; + class RoofPartition3DBAGRasteriseNode:public Node { + float cellsize = 0.5; + + public: + using Node::Node; + void init() override { + add_input("lod12_roofparts", typeid(geoflow::LinearRing)); + add_input("lod13_roofparts", typeid(geoflow::LinearRing)); + add_input("lod22_roofparts", typeid(geoflow::LinearRing)); + + // add_output("values", typeid(vec1f)); + add_poly_output("lod12_hattr", {typeid(float)}); + add_poly_output("lod13_hattr", {typeid(float)}); + add_poly_output("lod22_hattr", {typeid(float)}); + + add_param(ParamBoundedFloat(cellsize, 0, 50, "cellsize", "cellsize")); + } + void process() override; + }; + class BuildingRasteriseNode:public Node { float cellsize = 0.5; bool use_tin = false; From 1e05ba312402de67eb62b845f1891a240a6aa28c Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 16 Jun 2023 16:57:40 +0200 Subject: [PATCH 10/43] fix potential inf loop in surface mesh simplification node --- register.hpp | 1 + src/MeshProcessingNodes.hpp | 12 ++++++++++++ src/MeshSimplify.cpp | 21 +++++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/register.hpp b/register.hpp index b4d36ee..75498db 100644 --- a/register.hpp +++ b/register.hpp @@ -79,6 +79,7 @@ void register_nodes(geoflow::NodeRegister& node_register) { node_register.register_node("MaxInscribedCircle"); node_register.register_node("MeshSimplifyFastQuad"); node_register.register_node("MeshSimplify"); + node_register.register_node("SurfaceMesh2OFF"); node_register.register_node("MeshGridSimplify"); node_register.register_node("MeshClipper"); node_register.register_node("Mesh2TriangleCollection"); diff --git a/src/MeshProcessingNodes.hpp b/src/MeshProcessingNodes.hpp index fb43136..bbe7b49 100644 --- a/src/MeshProcessingNodes.hpp +++ b/src/MeshProcessingNodes.hpp @@ -111,5 +111,17 @@ namespace geoflow::nodes::stepedge { } void process() override; }; + class SurfaceMesh2OFFNode:public Node { + std::string filepath_=""; + + public: + using Node::Node; + void init() override { + add_input("cgal_surface_mesh", typeid(SurfaceMesh)); + + add_param(ParamPath(filepath_, "filepath", "File path")); + } + void process() override; + }; } \ No newline at end of file diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index 664df94..24696d5 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -11,6 +11,10 @@ #include #include +#include +#include + +#include namespace geoflow::nodes::stepedge { @@ -44,6 +48,10 @@ namespace geoflow::nodes::stepedge { // !PMP::does_self_intersect(smesh) if (stop_ratio_ < 1.){ if(!CGAL::is_triangle_mesh(smesh)) CGAL::Polygon_mesh_processing::triangulate_faces(smesh); + + // this prevents potentially getting stuck in infinite loop (see https://github.com/CGAL/cgal/issues/7529) + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices( smesh ); + CGAL::Polygon_mesh_processing::remove_isolated_vertices( smesh ); SurfaceMesh::Property_map > constrained_halfedges; constrained_halfedges = smesh.add_property_map >("h:vertices").first; @@ -67,6 +75,7 @@ namespace geoflow::nodes::stepedge { Border_is_constrained_edge_map bem(smesh); // This the actual call to the simplification algorithm. // The surface mesh and stop conditions are mandatory arguments. + int r = SMS::edge_collapse(smesh, stop, CGAL::parameters::edge_is_constrained_map(bem) .get_placement(Placement(bem))); @@ -76,8 +85,16 @@ namespace geoflow::nodes::stepedge { output("cgal_surface_mesh").set(smesh); } - // void MeshGridClusterSimplifyNode::process() { + void SurfaceMesh2OFFNode::process() { - // } + auto smesh = input("cgal_surface_mesh").get(); + auto fname = manager.substitute_globals(filepath_); + + std::ofstream ofs; + ofs << std::fixed << std::setprecision(5); + ofs.open(fname); + CGAL::IO::write_OFF(ofs, smesh); + ofs.close(); + } } \ No newline at end of file From a258df3624de4c127679ac10b4a384b3a4ed8810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Dukai?= Date: Mon, 19 Jun 2023 11:03:32 +0200 Subject: [PATCH 11/43] Bump version to 0.3.3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67a5ada..d647f2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.3.2) +project (building-reconstruction VERSION 0.3.3) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From 189f49a69cf7d28b5ef0aa51884660a86d1230b2 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 17 Jul 2023 15:23:38 +0200 Subject: [PATCH 12/43] remove inline keyword --- src/detect_planes_node.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/detect_planes_node.cpp b/src/detect_planes_node.cpp index 196c1e2..1b43555 100644 --- a/src/detect_planes_node.cpp +++ b/src/detect_planes_node.cpp @@ -408,7 +408,6 @@ namespace geoflow::nodes::stepedge { } }; - inline void RegularisePlanesNode::process() { auto& points = input("points").get(); From 7820f317f18b815196e549a3e3cbf1ec204b0a64 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 17 Jul 2023 15:26:30 +0200 Subject: [PATCH 13/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d647f2d..be64eca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.3.3) +project (building-reconstruction VERSION 0.3.4) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From 708dbc178bf8d07e19649fda763868411d82f5f3 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 27 Jul 2023 09:33:20 +0200 Subject: [PATCH 14/43] integrate plane regularisation code into plane detection node --- register.hpp | 1 - src/detect_planes_node.cpp | 411 +++++++++++++++++-------------------- src/stepedge_nodes.hpp | 60 ++---- 3 files changed, 202 insertions(+), 270 deletions(-) diff --git a/register.hpp b/register.hpp index 75498db..bad5888 100644 --- a/register.hpp +++ b/register.hpp @@ -30,7 +30,6 @@ void register_nodes(geoflow::NodeRegister& node_register) { node_register.register_node("BuildArrFromLines"); node_register.register_node("DetectLines"); node_register.register_node("DetectPlanes"); - node_register.register_node("RegularisePlanes"); node_register.register_node("LASInPolygons"); #ifdef GFP_WITH_PDAL node_register.register_node("EptInPolygons"); diff --git a/src/detect_planes_node.cpp b/src/detect_planes_node.cpp index 1b43555..b0264c6 100644 --- a/src/detect_planes_node.cpp +++ b/src/detect_planes_node.cpp @@ -49,11 +49,17 @@ struct AdjacencyFinder { for(auto& pi : points){ auto& p = boost::get<0>(pi); auto& l = boost::get<2>(pi); + if(l==0) continue; // skip unsegmented points + // std::cout << "pid:" << l << std::endl; Neighbor_search search(tree, p, N+1); // skip the first point since it is identical to the query point for (auto nb = search.begin()+1 ; nb < search.end(); ++nb) { // auto& p = boost::get<0>(pi); - auto& l_nb = boost::get<2>(nb->first); + auto l_nb = boost::get<2>(nb->first); + if(l_nb==0 || l_nb == l) continue; // skip unsegmented neighbours + // std::cout << "dist:" << nb->second << std::endl; + // std::cout << "p_x:" << boost::get<2>(nb->first) << std::endl; + // std::cout << "pid_:" << l_nb << std::endl; if(l > l_nb) { adjacencies[l][l_nb]++; } else { @@ -61,6 +67,12 @@ struct AdjacencyFinder { } } } + + // for(auto& [idl, value] : adjacencies) { + // for(auto& [idh, cnt] : value) { + // std::cout<< idl << ":" << idh << "-" << cnt << std::endl; + // } + // } }; }; @@ -71,6 +83,87 @@ namespace geoflow::nodes::stepedge { typedef CGAL::Shape_detection::Efficient_RANSAC Efficient_ransac; typedef CGAL::Shape_detection::Plane RansacPlane; + + // struct Custom_point_map + // { + // using key_type = arr3f; // The iterator's value type is an index + // using value_type = Point; // The object manipulated by the algorithm is a Point + // using reference = Point; // The object does not exist in memory, so there's no reference + // using category = boost::readable_property_map_tag; // The property map is used both + // // for reading and writing data + // // The get() function returns the object expected by the algorithm (here, Point) + // friend Point get (const Custom_point_map& map, const arr3f& p) + // { + // return Point (p[0], + // p[1], + // p[2]); + // } + // }; + struct Custom_plane_map + { + using key_type = Plane; // The iterator's value type is an index + using value_type = Plane; // The object manipulated by the algorithm is a Plane + using reference = Plane&; // The object does not exist in memory, so there's no reference + using category = boost::read_write_property_map_tag; // The property map is used both + // for reading and writing data + // The get() function returns the object expected by the algorithm (here, Point) + friend Plane get (const Custom_plane_map& map, Plane& plane) + { + return plane; + } + friend const Plane get (const Custom_plane_map& map, const Plane& plane) + { + return plane; + } + // The put() function updated the user's data structure from the + // object handled by the algorithm (here Plane) + friend void put (const Custom_plane_map& map, Plane& plane_old, const Plane& plane_new) + { + plane_old = plane_new; + } + }; + struct Custom_plane_index_map + { + using key_type = std::size_t; // The iterator's value type is an index + using value_type = int; // The object manipulated by the algorithm is a Plane + using reference = int; // The object does not exist in memory, so there's no reference + using category = boost::readable_property_map_tag; // The property map is used both + // for reading and writing data + PNL_vector* plane_id; + Custom_plane_index_map (PNL_vector* plane_id=nullptr) + : plane_id (plane_id) { } + // The get() function returns the object expected by the algorithm (here, Plane) + // return plane based on point idx + friend int get (const Custom_plane_index_map& map, const std::size_t& idx) + { + auto pid = boost::get<2>((*map.plane_id)[idx]); + if (pid == 0) + return -1; + else + return pid-1; + } + }; + + + // allow us to hash Plane instances + template + inline void hash_combine(std::size_t& seed, const T& v) + { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + struct PlaneHash { + std::size_t operator()(const Plane& k) const + { + size_t seed = std::hash{}(k.a()); + boost::hash_combine(seed, k.b()); + boost::hash_combine(seed, k.c()); + boost::hash_combine(seed, k.d()); + return seed; + } + }; + void DetectPlanesNode::process() { auto points = input("points").get(); @@ -107,10 +200,10 @@ namespace geoflow::nodes::stepedge { IndexedPlanesWithPoints pts_per_roofplane; size_t horiz_roofplane_cnt=0; size_t slant_roofplane_cnt=0; - if (only_horizontal) pts_per_roofplane[-1].second = std::vector(); size_t horiz_pt_cnt=0, total_pt_cnt=0, wall_pt_cnt=0, unsegmented_pt_cnt=0, total_plane_cnt=0; vec1f roof_elevations; + std::vector planes; if (!use_ransac) { // convert to lists required by the planedetector class @@ -142,6 +235,8 @@ namespace geoflow::nodes::stepedge { // classify horizontal/vertical planes using plane normals unsigned shape_id = 0; for(auto region: R.regions){ + + if(region.get_region_id() == 0) continue; auto& plane = region.plane; @@ -154,44 +249,30 @@ namespace geoflow::nodes::stepedge { // put slanted surface points at index -1 if we care only about horzontal surfaces if (!is_wall) { ++shape_id; + planes.push_back(plane); std::vector segpts; for (auto& i : region.inliers) { segpts.push_back(boost::get<0>(pnl_points[i])); roof_elevations.push_back(float(boost::get<0>(pnl_points[i]).z())); + boost::get<2>(pnl_points[i]) = shape_id; + boost::get<3>(pnl_points[i]) = is_wall; + boost::get<9>(pnl_points[i]) = is_horizontal; } total_pt_cnt += segpts.size(); - if (!only_horizontal || - (only_horizontal && is_horizontal)) { - pts_per_roofplane[shape_id].second = segpts; - pts_per_roofplane[shape_id].first = plane; - } else if (!is_horizontal) { - pts_per_roofplane[-1].second.insert( - pts_per_roofplane[-1].second.end(), - segpts.begin(), - segpts.end() - ); - } + pts_per_roofplane[shape_id].second = segpts; + pts_per_roofplane[shape_id].first = plane; + if (is_horizontal) { horiz_pt_cnt += segpts.size(); } } else { // is_wall - wall_pt_cnt = region.inliers.size(); + wall_pt_cnt += region.inliers.size(); } if (is_horizontal) ++horiz_roofplane_cnt; else if (!is_wall && !is_horizontal) ++slant_roofplane_cnt; - - if(!is_wall) { - output("planes").push_back(plane); - for (size_t& i : region.inliers) { - boost::get<2>(pnl_points[i]) = shape_id; - boost::get<3>(pnl_points[i]) = is_wall; - boost::get<9>(pnl_points[i]) = is_horizontal; - } - } } - output("plane_adj").set(R.adjacencies); } else { // use_ransac == true @@ -233,49 +314,109 @@ namespace geoflow::nodes::stepedge { // put slanted surface points at index -1 if we care only about horzontal surfaces if (!is_wall) { ++shape_id; + planes.push_back(plane); std::vector segpts; for (auto& i : shape->indices_of_assigned_points()) { segpts.push_back(boost::get<0>(pnl_points[i])); roof_elevations.push_back(float(boost::get<0>(pnl_points[i]).z())); + boost::get<2>(pnl_points[i]) = shape_id; + boost::get<3>(pnl_points[i]) = is_wall; + boost::get<9>(pnl_points[i]) = is_horizontal; } total_pt_cnt += segpts.size(); - if (!only_horizontal || - (only_horizontal && is_horizontal)) { - pts_per_roofplane[shape_id].second = segpts; - pts_per_roofplane[shape_id].first = plane; - } else if (!is_horizontal) { - pts_per_roofplane[-1].second.insert( - pts_per_roofplane[-1].second.end(), - segpts.begin(), - segpts.end() - ); - } + pts_per_roofplane[shape_id].second = segpts; + pts_per_roofplane[shape_id].first = plane; + if (is_horizontal) { horiz_pt_cnt += segpts.size(); } } else { // is_wall - wall_pt_cnt = shape->indices_of_assigned_points().size(); + wall_pt_cnt += shape->indices_of_assigned_points().size(); } if (is_horizontal) ++horiz_roofplane_cnt; else if (!is_wall && !is_horizontal) ++slant_roofplane_cnt; - if(!is_wall) { - output("planes").push_back(plane); - for (const size_t& i : shape->indices_of_assigned_points()) { - boost::get<2>(pnl_points[i]) = shape_id; - boost::get<3>(pnl_points[i]) = is_wall; - boost::get<9>(pnl_points[i]) = is_horizontal; - } + } + } + + vec1i plane_id, is_wall, is_horizontal; + for(auto& p : pnl_points) { + auto pid = boost::get<2>(p); + if (pid==0) ++unsegmented_pt_cnt; + plane_id.push_back(pid); + is_wall.push_back(boost::get<3>(p)); + is_horizontal.push_back(boost::get<9>(p)); + } + + // Plane regularisation + std::cout << "\nN planes before: " << pts_per_roofplane.size() << std::endl; + + // START Regularize detected planes. + if (regularize_parallelism_ || + regularize_orthogonality_ || + regularize_coplanarity_ || + regularize_axis_symmetry_) { + CGAL::Shape_regularization::Planes::regularize_planes( + planes, + pnl_points, + CGAL::parameters:: + plane_map(Custom_plane_map()). + point_map(Point_map()). + plane_index_map(Custom_plane_index_map(&pnl_points)). + maximum_angle(maximum_angle_). + maximum_offset(maximum_offset_). + regularize_parallelism(regularize_parallelism_). + regularize_orthogonality(regularize_orthogonality_). + regularize_coplanarity(regularize_coplanarity_). + regularize_axis_symmetry(regularize_axis_symmetry_) + // symmetry_direction(symmetry_direction_) + ); + + std::unordered_map, PlaneHash> plane_merge_map; + size_t pt_i=0; + for (auto& p :pnl_points) { + // std::cout << "pt_i=" << pt_i << std::endl; + auto pid = boost::get<2>(p); + // boost::get<2>(p) = 0; + // std::cout << "pid=" << pid << std::endl; + if (pid > 0){ + const auto& pl = planes[pid-1]; + plane_merge_map[pl].push_back( pt_i ); } + ++pt_i; } + std::cout << "plane_merge_map.size=" << plane_merge_map.size() << std::endl; + pts_per_roofplane.clear(); - AdjacencyFinder adj_finder(pnl_points, metrics_plane_k); - output("plane_adj").set(adj_finder.adjacencies); + int plane_cnt = 1; + for(auto& [plane, pt_i_vec] : plane_merge_map) { + Vector n = plane.orthogonal_vector(); + // this dot product is close to 0 for vertical planes + auto horizontality = CGAL::abs(n*Vector(0,0,1)); + bool is_wall = horizontality < metrics_is_wall_threshold; + if (!is_wall) { + std::vector ptvec; + ptvec.reserve(pt_i_vec.size()); + for (auto& pt_i : pt_i_vec) { + boost::get<2>(pnl_points[pt_i]) = plane_cnt; + plane_id[pt_i] = plane_cnt; + ptvec.push_back(boost::get<0>(pnl_points[pt_i])); + } + pts_per_roofplane[plane_cnt] = std::make_pair(plane, ptvec); + ++plane_cnt; + } + } + std::cout << "N planes after: " << plane_cnt-1 << std::endl; } + // END Regularize detected planes. + + AdjacencyFinder adj_finder(pnl_points, metrics_plane_k); + output("plane_adj").set(adj_finder.adjacencies); + bool b_is_horizontal = float(horiz_pt_cnt)/float(total_pt_cnt) > horiz_min_count; // int roof_type=-2; // as built: -2=undefined; -1=no pts; 0=LOD1, 1=LOD1.3, 2=LOD2 std::string roof_type = "no planes"; @@ -312,14 +453,6 @@ namespace geoflow::nodes::stepedge { output("roof_pt_cnt").set((int)total_pt_cnt); output("wall_pt_cnt").set((int)wall_pt_cnt); - vec1i plane_id, is_wall, is_horizontal; - for(auto& p : pnl_points) { - auto pid = boost::get<2>(p); - if (pid==0) ++unsegmented_pt_cnt; - plane_id.push_back(pid); - is_wall.push_back(boost::get<3>(p)); - is_horizontal.push_back(boost::get<9>(p)); - } output("unsegmented_pt_cnt").set((int)unsegmented_pt_cnt); output("pts_per_roofplane").set(pts_per_roofplane); output("plane_id").set(plane_id); @@ -329,176 +462,4 @@ namespace geoflow::nodes::stepedge { } - struct Custom_point_map - { - using key_type = arr3f; // The iterator's value type is an index - using value_type = Point; // The object manipulated by the algorithm is a Point - using reference = Point; // The object does not exist in memory, so there's no reference - using category = boost::readable_property_map_tag; // The property map is used both - // for reading and writing data - // The get() function returns the object expected by the algorithm (here, Point) - friend Point get (const Custom_point_map& map, const arr3f& p) - { - return Point (p[0], - p[1], - p[2]); - } - }; - struct Custom_plane_map - { - using key_type = Plane; // The iterator's value type is an index - using value_type = Plane; // The object manipulated by the algorithm is a Plane - using reference = Plane&; // The object does not exist in memory, so there's no reference - using category = boost::read_write_property_map_tag; // The property map is used both - // for reading and writing data - // The get() function returns the object expected by the algorithm (here, Point) - friend Plane get (const Custom_plane_map& map, Plane& plane) - { - return plane; - } - friend const Plane get (const Custom_plane_map& map, const Plane& plane) - { - return plane; - } - // The put() function updated the user's data structure from the - // object handled by the algorithm (here Plane) - friend void put (const Custom_plane_map& map, Plane& plane_old, const Plane& plane_new) - { - plane_old = plane_new; - } - }; - struct Custom_plane_index_map - { - using key_type = std::size_t; // The iterator's value type is an index - using value_type = int; // The object manipulated by the algorithm is a Plane - using reference = int; // The object does not exist in memory, so there's no reference - using category = boost::readable_property_map_tag; // The property map is used both - // for reading and writing data - vec1i* plane_id; - Custom_plane_index_map (vec1i* plane_id=nullptr) - : plane_id (plane_id) { } - // The get() function returns the object expected by the algorithm (here, Plane) - // return plane based on point idx - friend int get (const Custom_plane_index_map& map, const std::size_t& idx) - { - if ((*map.plane_id)[idx] == 0) - return -1; - else - return (*map.plane_id)[idx]-1; - } - }; - - - // allow us to hash Plane instances - template - inline void hash_combine(std::size_t& seed, const T& v) - { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); - } - - struct PlaneHash { - std::size_t operator()(const Plane& k) const - { - size_t seed = std::hash{}(k.a()); - boost::hash_combine(seed, k.b()); - boost::hash_combine(seed, k.c()); - boost::hash_combine(seed, k.d()); - return seed; - } - }; - - void RegularisePlanesNode::process() { - - auto& points = input("points").get(); - auto& plane_id = input("plane_id").get(); - auto& planes_inp = input("planes"); - - std::vector planes; - planes.reserve(planes_inp.size()); - for (size_t i=0; i(i) ); - } - - // Custom_plane_index_map index_map(&plane_id); - // Custom_point_map point_map; - // std::vector< std::vector > listp(planes.size()); - // for (std::size_t i = 0; i < points.size(); ++i) { - // const int idx = get(index_map, i); - // std::cout << idx << std::endl; - // if (idx != -1) { - // listp[std::size_t(idx)].push_back( - // get(point_map, *(points.begin() + i))); - // } - // } - std::cout << "N planes before: " << planes.size() << std::endl; - - // Regularize detected planes. - if (regularize_parallelism_ || - regularize_orthogonality_ || - regularize_coplanarity_ || - regularize_axis_symmetry_) { - CGAL::Shape_regularization::Planes::regularize_planes( - planes, - points, - CGAL::parameters:: - plane_map(Custom_plane_map()). - point_map(Custom_point_map()). - plane_index_map(Custom_plane_index_map(&plane_id)). - maximum_angle(maximum_angle_). - maximum_offset(maximum_offset_). - regularize_parallelism(regularize_parallelism_). - regularize_orthogonality(regularize_orthogonality_). - regularize_coplanarity(regularize_coplanarity_). - regularize_axis_symmetry(regularize_axis_symmetry_) - // symmetry_direction(symmetry_direction_) - ); - } - std::unordered_map, PlaneHash> plane_merge_map; - for (int pt_i=0 ; pt_i < points.size(); ++pt_i) { - auto pid = plane_id[pt_i]; - if (pid > 0){ - const auto& pl = planes[pid+1]; - if (plane_merge_map.find(pl) == plane_merge_map.end()) { - plane_merge_map[pl] = { Point(points[pt_i][0], points[pt_i][1], points[pt_i][2]) }; - } else { - plane_merge_map[pl].push_back( Point(points[pt_i][0], points[pt_i][1], points[pt_i][2]) ); - } - } - } - IndexedPlanesWithPoints pts_per_roofplane; - - int plane_cnt = 1; - for(auto& [plane, ptvec] : plane_merge_map) { - Vector n = plane.orthogonal_vector(); - // this dot product is close to 0 for vertical planes - auto horizontality = CGAL::abs(n*Vector(0,0,1)); - bool is_wall = horizontality < metrics_is_wall_threshold; - - if (!is_wall) { - pts_per_roofplane[plane_cnt++] = std::make_pair(plane, ptvec); - } - output("planes").push_back( plane ); - } - std::cout << "N planes after: " << plane_cnt << std::endl; - - - PNL_vector pnl_points; - for (auto& [pid, planepts] : pts_per_roofplane) { - for (auto& p : planepts.second) { - PNL pv; - boost::get<0>(pv) = Point(p[0], p[1], p[2]); - boost::get<2>(pv) = pid; - pnl_points.push_back(pv); - } - } - auto metrics_plane_k = 15; - AdjacencyFinder adj_finder(pnl_points, metrics_plane_k); - output("plane_adj").set(adj_finder.adjacencies); - - // make pts_per_roofplane - output("pts_per_roofplane").push_back( pts_per_roofplane ); - - } - } \ No newline at end of file diff --git a/src/stepedge_nodes.hpp b/src/stepedge_nodes.hpp index c58bb59..b9f6777 100644 --- a/src/stepedge_nodes.hpp +++ b/src/stepedge_nodes.hpp @@ -541,49 +541,7 @@ namespace geoflow::nodes::stepedge { // void process() override; // }; - class RegularisePlanesNode:public Node { - float maximum_angle_ = 25; - float maximum_offset_ = 0.01; - bool regularize_parallelism_ = true; - bool regularize_orthogonality_ = false; - bool regularize_coplanarity_ = true; - bool regularize_axis_symmetry_ = false; - - int metrics_plane_k = 15; - float metrics_is_wall_threshold = 0.3; - // symmetry_direction_ - public: - using Node::Node; - - void init() override { - - add_input("points", typeid(PointCollection)); - add_input("plane_id", typeid(vec1i)); - add_input("planes", typeid(Plane)); - // add_input("points", typeid(PointCollection)); - // add_output("is_wall", typeid(vec1i)); - // add_output("is_horizontal", typeid(vec1i)); - add_output("planes", typeid(Plane)); - add_output("plane_adj", typeid(std::map>)); - add_output("pts_per_roofplane", typeid(IndexedPlanesWithPoints )); - - add_param(ParamBool(regularize_parallelism_, "regularize_parallelism", "regularizeparallelism")); - add_param(ParamBool(regularize_orthogonality_, "regularize_orthogonality", "regularizeorthogonality")); - add_param(ParamBool(regularize_coplanarity_, "regularize_coplanarity", "regularizecoplanarity")); - add_param(ParamBool(regularize_axis_symmetry_, "regularize_axis_symmetry", "regularize_axis_symmetry")); - add_param(ParamFloat(maximum_angle_, "maximum_angle", "maximum allowed angle in degrees between plane normals used for parallelism, orthogonality, and axis symmetry")); - add_param(ParamFloat(maximum_offset_, "maximum_offset", "maximum allowed orthogonal distance between two parallel planes such that they are considered to be coplanar")); - - add_param(ParamInt(metrics_plane_k, "metrics_plane_k", "Number of neighbours used during adjacency detection")); - add_param(ParamFloat(metrics_is_wall_threshold, "metrics_is_wall_threshold", "Wall angle thres")); - - } - - void process() override; - }; - class DetectPlanesNode:public Node { - bool only_horizontal = true; float horiz_min_count = 0.95; int metrics_normal_k = 5; int metrics_plane_k = 15; @@ -597,6 +555,15 @@ namespace geoflow::nodes::stepedge { int n_refit = 5; bool use_ransac = false; // float roof_percentile=0.5; + + // plane regularisation + float maximum_angle_ = 25; + float maximum_offset_ = 0.5; + bool regularize_parallelism_ = false; + bool regularize_orthogonality_ = false; + bool regularize_coplanarity_ = false; + bool regularize_axis_symmetry_ = false; + public: using Node::Node; void init() override { @@ -604,7 +571,6 @@ namespace geoflow::nodes::stepedge { add_output("plane_id", typeid(vec1i)); add_output("is_wall", typeid(vec1i)); add_output("is_horizontal", typeid(vec1i)); - add_output("planes", typeid(Plane)); add_output("pts_per_roofplane", typeid(IndexedPlanesWithPoints )); @@ -622,7 +588,6 @@ namespace geoflow::nodes::stepedge { add_output("plane_adj", typeid(std::map>)); add_param(ParamBool(use_ransac, "use_ransac", "Use ransac instead of region growing plane detection")); - add_param(ParamBool(only_horizontal, "only_horizontal", "Output only horizontal planes")); add_param(ParamFloat(horiz_min_count, "horiz_min_count", "Mininmal point count for horizontal planes")); add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); add_param(ParamInt(metrics_plane_k, "metrics_plane_k", "Number of neighbours used during region growing plane detection")); @@ -634,6 +599,13 @@ namespace geoflow::nodes::stepedge { add_param(ParamFloat(metrics_is_horizontal_threshold, "metrics_is_horizontal_threshold", "Threshold for horizontal plane detection (expressed as angle wrt unit verctor in +z direction)")); add_param(ParamFloat(metrics_is_wall_threshold, "metrics_is_wall_threshold", "Wall angle thres")); add_param(ParamInt(n_refit, "n_refit", "Refit every n points")); + + add_param(ParamBool(regularize_parallelism_, "regularize_parallelism", "regularizeparallelism")); + add_param(ParamBool(regularize_orthogonality_, "regularize_orthogonality", "regularizeorthogonality")); + add_param(ParamBool(regularize_coplanarity_, "regularize_coplanarity", "regularizecoplanarity")); + add_param(ParamBool(regularize_axis_symmetry_, "regularize_axis_symmetry", "regularize_axis_symmetry")); + add_param(ParamFloat(maximum_angle_, "maximum_angle", "Plane regularisation: maximum allowed angle in degrees between plane normals used for parallelism, orthogonality, and axis symmetry")); + add_param(ParamFloat(maximum_offset_, "maximum_offset", "Plane regularisation: maximum allowed orthogonal distance between two parallel planes such that they are considered to be coplanar")); // add_param(ParamBoundedFloat(roof_percentile, 0, 1, "roof_percentile", "Roof elevation percentile")); } // void before_gui(){ From 80ffe8c5530bb483c950d08b5c8f89c8bf53993e Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 7 Aug 2023 13:42:55 +0200 Subject: [PATCH 15/43] correct for z offset in RoofPartition3DBAGRasteriseNode --- src/heightfield_nodes.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index fc80c84..c5d3a47 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -593,7 +593,7 @@ namespace geoflow::nodes::stepedge { } - void calculate_h_attr(gfSingleFeatureInputTerminal& roofparts, gfMultiFeatureOutputTerminal& h_attr, RasterTools::Raster& r_lod22) { + void calculate_h_attr(gfSingleFeatureInputTerminal& roofparts, gfMultiFeatureOutputTerminal& h_attr, RasterTools::Raster& r_lod22, float z_offset) { h_attr.add_vector("h_min", typeid(float)); h_attr.add_vector("h_max", typeid(float)); h_attr.add_vector("h_50p", typeid(float)); @@ -616,11 +616,11 @@ namespace geoflow::nodes::stepedge { size_t datasize = part_points.size(); int elevation_id = std::floor(0.5*float(datasize-1)); - h_attr.sub_terminal("h_50p").push_back(part_points[elevation_id][2]); + h_attr.sub_terminal("h_50p").push_back(part_points[elevation_id][2] + z_offset); elevation_id = std::floor(0.7*float(datasize-1)); - h_attr.sub_terminal("h_70p").push_back(part_points[elevation_id][2]); - h_attr.sub_terminal("h_min").push_back(part_points[0][2]); - h_attr.sub_terminal("h_max").push_back(part_points[datasize-1][2]); + h_attr.sub_terminal("h_70p").push_back(part_points[elevation_id][2] + z_offset); + h_attr.sub_terminal("h_min").push_back(part_points[0][2] + z_offset); + h_attr.sub_terminal("h_max").push_back(part_points[datasize-1][2] + z_offset); } } @@ -647,10 +647,10 @@ namespace geoflow::nodes::stepedge { auto ring = lod22_roofparts.get(i); rasterise_ring(ring, r_lod22); } - - calculate_h_attr(lod12_roofparts, lod12_hattr, r_lod22); - calculate_h_attr(lod13_roofparts, lod13_hattr, r_lod22); - calculate_h_attr(lod22_roofparts, lod22_hattr, r_lod22); + auto z_offset = (*manager.data_offset())[2]; + calculate_h_attr(lod12_roofparts, lod12_hattr, r_lod22, z_offset); + calculate_h_attr(lod13_roofparts, lod13_hattr, r_lod22, z_offset); + calculate_h_attr(lod22_roofparts, lod22_hattr, r_lod22, z_offset); } void SegmentRasteriseNode::rasterise_input(gfSingleFeatureInputTerminal& input_triangles, RasterTools::Raster& r, size_t& data_pixel_cnt) { From 32c6488413c7a9f0cbde94621d0f883e3a89968d Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 14 Aug 2023 15:36:48 +0200 Subject: [PATCH 16/43] bump version number --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be64eca..709a5ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.3.4) +project (building-reconstruction VERSION 0.3.5) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From b063ac896bc9b6cd97eb991a40fe9643256ad18a Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 16 Aug 2023 21:43:37 +0200 Subject: [PATCH 17/43] more robust mesh repair for CGAL Surface mesh to fix infinite loop in mesh simplification --- src/Mesh2TriangleCollectionNode.cpp | 41 ++++++++++++++++++++++++----- src/MeshSimplify.cpp | 6 ----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Mesh2TriangleCollectionNode.cpp b/src/Mesh2TriangleCollectionNode.cpp index 892bb2c..4e29b20 100644 --- a/src/Mesh2TriangleCollectionNode.cpp +++ b/src/Mesh2TriangleCollectionNode.cpp @@ -2,6 +2,12 @@ #include #include +#include + +#include +#include +#include +#include namespace geoflow::nodes::stepedge { @@ -61,8 +67,9 @@ namespace geoflow::nodes::stepedge { SurfaceMesh smesh; { - std::map vertex_map; + std::map vertex_map; std::set vertex_set; + std::vector points; for (const auto &ring : gfmesh.get_polygons()) { for (auto &v : ring) @@ -70,22 +77,44 @@ namespace geoflow::nodes::stepedge { auto [it, did_insert] = vertex_set.insert(v); if (did_insert) { - vertex_map[v] = smesh.add_vertex(K::Point_3(v[0],v[1],v[2]));; + vertex_map[v] = points.size(); + points.push_back(K::Point_3(v[0],v[1],v[2])); } } } - + + // First build a polygon soup + std::vector > polygons; for (auto& ring : gfmesh.get_polygons()) { - std::vector rindices; + std::vector rindices; rindices.reserve(ring.size()); for(auto& p : ring) { rindices.push_back(vertex_map[p]); } - smesh.add_face(rindices); + polygons.push_back(rindices); } + + // Do CGAL mesh repair magic, see https://github.com/CGAL/cgal/issues/7529 + + // remove all kinds of typical issues in a polygon soup (degenerate polygons, isolated points, etc.) + CGAL::Polygon_mesh_processing::repair_polygon_soup(points, polygons); + + // duplicate non-manifold edges (but does not re-orient faces) + CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup(points, polygons); + + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, smesh); + + if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); + + // this prevents potentially getting stuck in infinite loop (see https://github.com/CGAL/cgal/issues/7529) + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices( smesh ); + + // this is not needed since PMP::repair_polygon_soup() will perform this repair + // CGAL::Polygon_mesh_processing::remove_isolated_vertices( smesh ); + } - if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); + output("cgal_surface_mesh").set(smesh); } diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index 24696d5..bfa32b9 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include @@ -48,10 +46,6 @@ namespace geoflow::nodes::stepedge { // !PMP::does_self_intersect(smesh) if (stop_ratio_ < 1.){ if(!CGAL::is_triangle_mesh(smesh)) CGAL::Polygon_mesh_processing::triangulate_faces(smesh); - - // this prevents potentially getting stuck in infinite loop (see https://github.com/CGAL/cgal/issues/7529) - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices( smesh ); - CGAL::Polygon_mesh_processing::remove_isolated_vertices( smesh ); SurfaceMesh::Property_map > constrained_halfedges; constrained_halfedges = smesh.add_property_map >("h:vertices").first; From 629a26ba2bbb9209afd66c2b3ab0be41d2f0d71f Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 16 Aug 2023 21:45:49 +0200 Subject: [PATCH 18/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 709a5ed..3d73269 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.3.5) +project (building-reconstruction VERSION 0.3.6) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From 062fe5fd4ec11175a7ac4a5575093e8aa21ce892 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 5 Oct 2023 21:34:15 +0200 Subject: [PATCH 19/43] always write mtc in PolygonTriangulatorNode --- src/polygon_triangulate.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/polygon_triangulate.cpp b/src/polygon_triangulate.cpp index 33b443d..673b272 100644 --- a/src/polygon_triangulate.cpp +++ b/src/polygon_triangulate.cpp @@ -261,7 +261,10 @@ void PolygonTriangulatorNode::process() output("multi_triangle_collections").push_back(multitrianglecols); } } - if(!output_mtc_for_every_input) output("multi_triangle_collections").set(multitrianglecols); + if(!output_mtc_for_every_input || + output("multi_triangle_collections").size()==0) { + output("multi_triangle_collections").set(multitrianglecols); + } } // set outputs From a6664752596fa4a112c4786a82295011a1a51cac Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 14 Dec 2023 16:09:33 +0100 Subject: [PATCH 20/43] option for smooth normals in MeshClipperNode --- src/MeshClipperNode.cpp | 132 ++++++++++++++++++++---------------- src/MeshProcessingNodes.hpp | 2 + 2 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src/MeshClipperNode.cpp b/src/MeshClipperNode.cpp index f59ae26..507862b 100644 --- a/src/MeshClipperNode.cpp +++ b/src/MeshClipperNode.cpp @@ -28,6 +28,43 @@ namespace geoflow::nodes::stepedge { return t; } + struct MeshBuilder { + std::map vertex_map; + std::set vertex_set; + std::vector points; + + std::vector > polygons; + + void add_point(const K::Point_3& p) { + auto [it, did_insert] = vertex_set.insert(p); + if (did_insert) + { + vertex_map[p] = points.size(); + points.push_back(p); + } + } + + void add_triangle(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { + add_point(p0); + add_point(p1); + add_point(p2); + + // First build a polygon soup + std::vector rindices; + rindices.reserve(3); + rindices.push_back(vertex_map[p0]); + rindices.push_back(vertex_map[p1]); + rindices.push_back(vertex_map[p2]); + polygons.push_back(rindices); + } + + void get_mesh(SurfaceMesh& smesh) { + smesh.clear(); + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, smesh); + } + + }; + void MeshClipperNode::process() { typedef SurfaceMesh::Vertex_index VertexIndex; @@ -38,32 +75,6 @@ namespace geoflow::nodes::stepedge { namespace PMP = CGAL::Polygon_mesh_processing; auto smesh = input("mesh").get(); - - // SurfaceMesh smesh; - // { - // std::map vertex_map; - // std::set vertex_set; - // for (const auto &ring : gfmesh.get_polygons()) - // { - // for (auto &v : ring) - // { - // auto [it, did_insert] = vertex_set.insert(v); - // if (did_insert) - // { - // vertex_map[v] = smesh.add_vertex(Point_3(v[0],v[1],v[2]));; - // } - // } - // } - - // for (auto& ring : gfmesh.get_polygons()) { - // std::vector rindices; - // rindices.reserve(ring.size()); - // for(auto& p : ring) { - // rindices.push_back(vertex_map[p]); - // } - // smesh.add_face(rindices); - // } - // } // clip if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); @@ -76,12 +87,6 @@ namespace geoflow::nodes::stepedge { Point_3(pmax[0], pmax[1], pmax[2]) ); - TriangleCollection triangleCollection; - vec3f normals; - - auto fnormals = smesh.add_property_map("f:normals", CGAL::NULL_VECTOR).first; - PMP::compute_face_normals(smesh, fnormals); - if (!skip_clip_) { if (cgal_clip_) { // use cgal mesh_processing if (!PMP::does_self_intersect(smesh)) { @@ -91,6 +96,7 @@ namespace geoflow::nodes::stepedge { ); } } else { + MeshBuilder mb; CGAL::Vertex_around_face_iterator vit, vend; for (auto& f : smesh.faces()) { boost::tie(vit, vend) = CGAL::vertices_around_face(smesh.halfedge(f), smesh); @@ -106,60 +112,70 @@ namespace geoflow::nodes::stepedge { const auto result = CGAL::intersection(triangle, cuboid); if (result) { - auto& n = fnormals[f]; + // auto& n = fnormals[f]; if (const Triangle_3* tri = boost::get(&*result)) { - triangleCollection.push_back(create_gf_triangle( + mb.add_triangle( tri->vertex(0), tri->vertex(1), tri->vertex(2) - )); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); + ); } else if (const Poly_3* poly = boost::get(&*result)) { // std::cout << "polygon! [" << poly->size() << std::endl; - for(unsigned i=0; isize()-2; ++i) { // std::cout << i << " "; - triangleCollection.push_back(create_gf_triangle( + mb.add_triangle( (*poly)[0], (*poly)[i+1], (*poly)[i+2] - )); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); + ); } } } } + mb.get_mesh(smesh); } } - if (skip_clip_ || cgal_clip_) { - for (auto& f : smesh.faces()) { - Triangle t; + auto fnormals = smesh.add_property_map("f:normals", CGAL::NULL_VECTOR).first; + auto vnormals = smesh.add_property_map("v:normals", CGAL::NULL_VECTOR).first; - unsigned i = 0; - for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { - if(i==3) { - std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; - continue; - } - auto& p = smesh.point(vi); - t[i++] = arr3f{ + if (smooth_normals_) { + PMP::compute_vertex_normals(smesh, vnormals); + } else { + PMP::compute_face_normals(smesh, fnormals); + } + + // convert to triangleCollection + TriangleCollection triangleCollection; + vec3f normals; + for (auto& f : smesh.faces()) { + Triangle t; + + unsigned i = 0; + for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { + if(i==3) { + std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; + continue; + } + auto& p = smesh.point(vi); + t[i++] = arr3f{ (float) p.x(), (float) p.y(), (float) p.z() - }; - } + }; + if (smooth_normals_) { + auto& n = vnormals[vi]; + normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); + } + } + if (!smooth_normals_) { auto& n = fnormals[f]; normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - triangleCollection.push_back(t); } + triangleCollection.push_back(t); } output("triangles").set(triangleCollection); diff --git a/src/MeshProcessingNodes.hpp b/src/MeshProcessingNodes.hpp index bbe7b49..72eed5d 100644 --- a/src/MeshProcessingNodes.hpp +++ b/src/MeshProcessingNodes.hpp @@ -12,6 +12,7 @@ namespace geoflow::nodes::stepedge { class MeshClipperNode:public Node { bool skip_clip_ = false; bool cgal_clip_ = false; + bool smooth_normals_ = false; // float reduce_fraction_ = 0.5; // float agressiveness_ = 7.0; @@ -27,6 +28,7 @@ namespace geoflow::nodes::stepedge { add_param(ParamBool(skip_clip_, "skip_clip", "Skip the clip")); add_param(ParamBool(cgal_clip_, "cgal_clip", "Use CGAL::Polygon_mesh_processing::clip instead of simpler but more robust triangle by triangle clip.")); + add_param(ParamBool(smooth_normals_, "smooth_normals", "Use use smooth vertex normals instead of flat face normals.")); // add_param(ParamFloat(reduce_fraction_, "reduce_fraction", "Target reduction in nr of triangles")); // add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); From 9f5d9c1833a0c495653a88587cdc8fe93529302d Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 19 Jan 2024 16:10:09 +0100 Subject: [PATCH 21/43] add MeshSimplify2DNode --- CMakeLists.txt | 1 + register.hpp | 1 + src/MeshClipperNode.cpp | 26 +++-- src/MeshProcessingNodes.hpp | 14 +++ src/MeshSimplify.cpp | 110 +++++++++++++++++++- src/tinsimp.cpp | 198 ++++++++++++++++++++++++++++++++++++ src/tinsimp.hpp | 55 ++++++++++ 7 files changed, 394 insertions(+), 11 deletions(-) create mode 100644 src/tinsimp.cpp create mode 100644 src/tinsimp.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 709a5ed..439ab8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ geoflow_create_plugin( src/MeshSimplify.cpp src/MeshGridSimplify.cpp src/Mesh2TriangleCollectionNode.cpp + src/tinsimp.cpp ) target_include_directories(gfp_buildingreconstruction PRIVATE diff --git a/register.hpp b/register.hpp index bad5888..a745bff 100644 --- a/register.hpp +++ b/register.hpp @@ -84,4 +84,5 @@ void register_nodes(geoflow::NodeRegister& node_register) { node_register.register_node("Mesh2TriangleCollection"); node_register.register_node("Mesh2CGALSurfaceMesh"); node_register.register_node("RoofPartition3DBAGRasterise"); + node_register.register_node("MeshSimplify2D"); } \ No newline at end of file diff --git a/src/MeshClipperNode.cpp b/src/MeshClipperNode.cpp index 507862b..9853327 100644 --- a/src/MeshClipperNode.cpp +++ b/src/MeshClipperNode.cpp @@ -152,7 +152,7 @@ namespace geoflow::nodes::stepedge { for (auto& f : smesh.faces()) { Triangle t; - unsigned i = 0; + unsigned i = 0; for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { if(i==3) { std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; @@ -164,17 +164,25 @@ namespace geoflow::nodes::stepedge { (float) p.y(), (float) p.z() }; + // if (!smesh.is_border(vi)) { if (smooth_normals_) { - auto& n = vnormals[vi]; - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); + normals.push_back(arr3f{ + float(vnormals[vi].x()), + float(vnormals[vi].y()), + float(vnormals[vi].z()) }); + } else { + normals.push_back(arr3f{ + float(fnormals[f].x()), + float(fnormals[f].y()), + float(fnormals[f].z()) }); } } - if (!smooth_normals_) { - auto& n = fnormals[f]; - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); - } + // if (!smooth_normals_) { + + + // normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); + // normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); + // } triangleCollection.push_back(t); } diff --git a/src/MeshProcessingNodes.hpp b/src/MeshProcessingNodes.hpp index 72eed5d..06b04d6 100644 --- a/src/MeshProcessingNodes.hpp +++ b/src/MeshProcessingNodes.hpp @@ -56,6 +56,20 @@ namespace geoflow::nodes::stepedge { void process() override; }; + class MeshSimplify2DNode:public Node { + float error_ = 0.5; + + public: + using Node::Node; + void init() override { + add_input("cgal_surface_mesh", typeid(SurfaceMesh)); + add_output("cgal_surface_mesh", typeid(SurfaceMesh)); + + add_param(ParamBoundedFloat(error_, 0, 5, "error", "Target maximum eror after simplification")); + } + void process() override; + }; + class MeshGridSimplifyNode:public Node { float cell_size_xy_ = 0.5; float cell_size_z_ = 0.5; diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index bfa32b9..734f586 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -9,16 +9,21 @@ // Stop-condition policy #include +#include + #include #include #include +#include "tinsimp.hpp" + namespace geoflow::nodes::stepedge { typedef SurfaceMesh::Vertex_index VertexIndex; typedef SurfaceMesh::Face_index FaceIndex; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef boost::graph_traits::edge_descriptor edge_descriptor; namespace SMS = CGAL::Surface_mesh_simplification; @@ -38,12 +43,109 @@ namespace geoflow::nodes::stepedge { // Placement class typedef SMS::Constrained_placement, Border_is_constrained_edge_map > Placement; - namespace PMP = CGAL::Polygon_mesh_processing; + // namespace d = CGAL::Polygon_mesh_processing; + const K::Vector_3 up(0,0,1); + + bool is_vertical(const K::Point_3& a, const K::Point_3& b, const K::Point_3& c) { + auto n = CGAL::orthogonal_vector(a, b, c); + // check if face is vertical by checking angle of it's normal. Only add constraints for non-vertical faces + // std::cout << CGAL::abs(CGAL::approximate_angle(n, up))<< std::endl; + return (CGAL::abs(90 - CGAL::abs(CGAL::approximate_angle(n, up))) == 0); + } + + void MeshSimplify2DNode::process() { + auto smesh = input("cgal_surface_mesh").get(); + + // !d::does_self_intersect(smesh) + if (error_ > 0){ + tinsimp::CDT t; + + // collect vertical faces + std::vector> wall_triangles; + for (auto& face : smesh.faces()) { + std::vector triangle; + for(vertex_descriptor vd : vertices_around_face(smesh.halfedge(face), smesh)){ + triangle.push_back(smesh.point(vd)); + } + if (is_vertical(triangle[0], triangle[1], triangle[2])) { + wall_triangles.push_back(triangle); + smesh.remove_face(face); + } + } + smesh.collect_garbage(); + + for(halfedge_descriptor hd : halfedges(smesh)) + { + if(CGAL::is_border(hd, smesh)) + { + auto a = smesh.point(source(hd, smesh)); + auto b = smesh.point(target(hd, smesh)); + + t.insert_constraint( + tinsimp::Point( + a.x(), + a.y(), + a.z() + ), + tinsimp::Point( + b.x(), + b.y(), + b.z() + ) + ); + } + } + + std::vector zpts; + for(vertex_descriptor vd : smesh.vertices()) + { + if(!CGAL::is_border(vd, smesh)) + { + auto p = smesh.point(vd); + zpts.push_back( + tinsimp::Point( p.x(), p.y(), p.z() ) + ); + } + } + + tinsimp::greedy_insert(t, zpts, error_); + // std::cout << "\nFinished!\n" << r << " edges removed.\n" + // << smesh.number_of_edges() << " final edges.\n"; + tinsimp::mark_domains(t); + + smesh.clear(); + std::map vertex_map; + for (auto& vh : t.finite_vertex_handles()) { + auto vindex = smesh.add_vertex(K::Point_3( + vh->point().x(), + vh->point().y(), + vh->point().z() + )); + vertex_map[vh] = vindex; + } + for (auto& fh : t.finite_face_handles()) { + if(fh->info().in_domain()) { + smesh.add_face( + vertex_map[fh->vertex(0)], + vertex_map[fh->vertex(1)], + vertex_map[fh->vertex(2)] + ); + } + } + for (auto& triangle : wall_triangles) { + auto ia = smesh.add_vertex(triangle[0]); + auto ib = smesh.add_vertex(triangle[1]); + auto ic = smesh.add_vertex(triangle[2]); + smesh.add_face(ia, ib, ic); + } + } + output("cgal_surface_mesh").set(smesh); + } void MeshSimplifyNode::process() { auto smesh = input("cgal_surface_mesh").get(); - // !PMP::does_self_intersect(smesh) + // !d::does_self_intersect(smesh) if (stop_ratio_ < 1.){ if(!CGAL::is_triangle_mesh(smesh)) CGAL::Polygon_mesh_processing::triangulate_faces(smesh); @@ -67,11 +169,15 @@ namespace geoflow::nodes::stepedge { // Contract the surface mesh as much as possible. Correct for the border edges that will not be removed SMS::Count_ratio_stop_predicate stop( stop_ratio ); Border_is_constrained_edge_map bem(smesh); + + // filter that checks if a placement would invert the normal of a face + SMS::Bounded_normal_change_filter<> filter; // This the actual call to the simplification algorithm. // The surface mesh and stop conditions are mandatory arguments. int r = SMS::edge_collapse(smesh, stop, CGAL::parameters::edge_is_constrained_map(bem) + .filter(filter) .get_placement(Placement(bem))); // std::cout << "\nFinished!\n" << r << " edges removed.\n" // << smesh.number_of_edges() << " final edges.\n"; diff --git a/src/tinsimp.cpp b/src/tinsimp.cpp new file mode 100644 index 0000000..59b8ff6 --- /dev/null +++ b/src/tinsimp.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "tinsimp.hpp" + +namespace tinsimp { + +void mark_domains(CDT& ct, + CDT::Face_handle start, + int index, + std::list& border) { + if (start->info().nesting_level != -1) { + return; + } + std::list queue; + queue.push_back(start); + while (!queue.empty()) { + CDT::Face_handle fh = queue.front(); + queue.pop_front(); + if (fh->info().nesting_level == -1) { + fh->info().nesting_level = index; + for (int i = 0; i < 3; i++) { + CDT::Edge e(fh, i); + CDT::Face_handle n = fh->neighbor(i); + if (n->info().nesting_level == -1) { + if (ct.is_constrained(e)) border.push_back(e); + else queue.push_back(n); + } + } + } + } +} +/** +* mark the triangles that are inside the original 2D polygon. +* explore set of facets connected with non constrained edges, +* and attribute to each such set a nesting level. +* start from facets incident to the infinite vertex, with a nesting +* level of 0. Then recursively consider the non-explored facets incident +* to constrained edges bounding the former set and increase the nesting level by 1. +* facets in the domain are those with an odd nesting level. +*/ +void mark_domains(CDT& cdt) { + // for (CDT::All_faces_iterator it = cdt.all_faces_begin(); it != cdt.all_faces_end(); ++it) { + // it->info().nesting_level = -1; + // } + std::list border; + mark_domains(cdt, cdt.infinite_face(), 0, border); + while (!border.empty()) { + CDT::Edge e = border.front(); + border.pop_front(); + CDT::Face_handle n = e.first->neighbor(e.second); + if (n->info().nesting_level == -1) { + mark_domains(cdt, n, e.first->info().nesting_level + 1, border); + } + } +} + +struct PointXYHash { + std::size_t operator()(Point const& p) const noexcept { + std::size_t h1 = std::hash{}(p.x()); + std::size_t h2 = std::hash{}(p.y()); + return h1 ^ (h2 << 1); + } +}; +struct PointXYEqual { + bool operator()(Point const& p1, Point const& p2) const noexcept { + auto ex = p1.x() == p2.x(); + auto ey = p1.y() == p2.y(); + return ex && ey; + } +}; + +inline double compute_error(Point &p, CDT::Face_handle &face); + +//--- TIN Simplification +// Greedy insertion/incremental refinement algorithm adapted from "Fast polygonal approximation of terrain and height fields" by Garland, Michael and Heckbert, Paul S. +inline double compute_error(Point &p, CDT::Face_handle &face) { + if(!face->info().plane) + face->info().plane = new CGAL::Plane_3( + face->vertex(0)->point(), + face->vertex(1)->point(), + face->vertex(2)->point()); + if(!face->info().points_inside) + face->info().points_inside = new std::vector(); + + auto plane = face->info().plane; + auto interpolate = -plane->a()/plane->c() * p.x() - plane->b()/plane->c()*p.y() - plane->d()/plane->c(); + double error = std::fabs(interpolate - p.z()); + return error; +} + +// void greedy_insert(CDT &T, const std::vector> &pts, double threshold) { +// // Convert all elevation points to CGAL points +// std::vector cpts; +// cpts.reserve(pts.size()); +// for (auto& p : pts) { +// cpts.push_back(Point(p[0], p[1], p[2])); +// } + +// greedy_insert(T, cpts, threshold); +// } + +void greedy_insert(CDT &T, std::vector &cpts, double threshold) { + // assumes all lidar points are inside a triangle + Heap heap; + + // compute initial point errors, build heap, store point indices in triangles + { + std::unordered_set set; + for (int i = 0; i < cpts.size(); i++) { + auto p = cpts[i]; + CDT::Locate_type lt; + int li; + auto not_duplicate = set.insert(p).second; + if(not_duplicate){ + CDT::Face_handle face = T.locate(p, lt, li); + if (lt == CDT::EDGE || lt == CDT::FACE) { + auto e = compute_error(p, face); + auto handle = heap.push(point_error(i, e)); + face->info().points_inside->push_back(handle); + } + } + } + } + std::cout << "prepared tinsimp...\n"; + + // insert points, update errors of affected triangles until threshold error is reached + while (!heap.empty() && heap.top().error > threshold) { + // get top element (with largest error) from heap + auto maxelement = heap.top(); + auto max_p = cpts[maxelement.index]; + + // get triangles that will change after inserting this max_p + std::vector faces; + T.get_conflicts(max_p, std::back_inserter(faces)); + + // insert max_p in triangulation + auto face_hint = faces[0]; + auto v = T.insert(max_p, face_hint); + face_hint = v->face(); + + // update clear info of triangles that just changed, collect points that were inside these triangles + std::vector points_to_update; + for (auto face : faces) { + if (face->info().plane) { + delete face->info().plane; + face->info().plane = nullptr; + } + if (face->info().points_inside) { + for (auto h : *face->info().points_inside) { + if (maxelement.index != (*h).index) + points_to_update.push_back(h); + } + std::vector().swap((*face->info().points_inside)); + } + } + + // remove the point we just inserted in the triangulation from the heap + heap.pop(); + + // update the errors of affected elevation points + for (auto curelement : points_to_update) { + auto p = cpts[(*curelement).index]; + //auto containing_face = T.locate(p, face_hint); + CDT::Locate_type lt; + int li; + CDT::Face_handle containing_face = T.locate(p, lt, li, face_hint); + if (lt == CDT::EDGE || lt == CDT::FACE) { + const double e = compute_error(p, containing_face); + const point_error new_pe = point_error((*curelement).index, e); + heap.update(curelement, new_pe); + containing_face->info().points_inside->push_back(curelement); + } + } + } + + //cleanup the stuff I put in face info of triangles + for (CDT::Finite_faces_iterator fit = T.finite_faces_begin(); + fit != T.finite_faces_end(); ++fit) { + if (fit->info().plane) { + delete fit->info().plane; + fit->info().plane = nullptr; + } + if (fit->info().points_inside) { + delete fit->info().points_inside; + fit->info().points_inside = nullptr; + } + } + +} + +} \ No newline at end of file diff --git a/src/tinsimp.hpp b/src/tinsimp.hpp new file mode 100644 index 0000000..85a61df --- /dev/null +++ b/src/tinsimp.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace tinsimp { + +// fibonacci heap for greedy insertion code +struct point_error { + point_error(size_t i, double e) : index(i), error(e){} + size_t index; + double error; + size_t line_id; + + bool operator<(point_error const & rhs) const + { + return error < rhs.error; + } +}; +typedef boost::heap::fibonacci_heap Heap; +typedef Heap::handle_type heap_handle; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Projection_traits_xy_3 Gt; +typedef CGAL::Triangulation_vertex_base_with_id_2 Vb; +struct FaceInfo2 +{ + FaceInfo2() {} + std::vector* points_inside = nullptr; + CGAL::Plane_3* plane = nullptr; + int nesting_level = -1; + bool in_domain() { + return nesting_level % 2 == 1; + } +}; +typedef CGAL::Triangulation_face_base_with_info_2 Fbb; +typedef CGAL::Constrained_triangulation_face_base_2 Fb; +typedef CGAL::Triangulation_data_structure_2 Tds; +typedef CGAL::Exact_predicates_tag Itag; +typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; +typedef CGAL::Constrained_triangulation_plus_2 CT; +typedef CDT::Point Point; + +// void greedy_insert(CDT &T, const std::vector> &pts, double threshold); +void greedy_insert(CDT &T, std::vector &cpts, double threshold); +void mark_domains(CDT& cdt); +} \ No newline at end of file From 55f5cdb49a65998024f39955bb34c3c98dfdda38 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 26 Jan 2024 17:52:39 +0100 Subject: [PATCH 22/43] fix handling of vertical faces in MeshSimplify2D Node --- CMakeLists.txt | 2 +- src/MeshProcessingNodes.hpp | 2 + src/MeshSimplify.cpp | 104 +++++++++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61e3906..3afe69c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.3.6) +project (building-reconstruction VERSION 0.3.7) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) diff --git a/src/MeshProcessingNodes.hpp b/src/MeshProcessingNodes.hpp index 06b04d6..f173d81 100644 --- a/src/MeshProcessingNodes.hpp +++ b/src/MeshProcessingNodes.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -64,6 +65,7 @@ namespace geoflow::nodes::stepedge { void init() override { add_input("cgal_surface_mesh", typeid(SurfaceMesh)); add_output("cgal_surface_mesh", typeid(SurfaceMesh)); + // add_output("wall_triangles", typeid(TriangleCollection)); add_param(ParamBoundedFloat(error_, 0, 5, "error", "Target maximum eror after simplification")); } diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index 734f586..9d56284 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -15,6 +15,14 @@ #include #include +#include +#include + +#include +#include +#include +#include +#include #include "tinsimp.hpp" @@ -44,6 +52,50 @@ namespace geoflow::nodes::stepedge { typedef SMS::Constrained_placement, Border_is_constrained_edge_map > Placement; // namespace d = CGAL::Polygon_mesh_processing; + + struct MeshBuilder { + std::map vertex_map; + std::set vertex_set; + std::vector points; + + std::vector > polygons; + + void add_point(const K::Point_3& p) { + auto [it, did_insert] = vertex_set.insert(p); + if (did_insert) + { + vertex_map[p] = points.size(); + points.push_back(p); + } + } + void add_points(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { + add_point(p0); + add_point(p1); + add_point(p2); + } + + void add_triangle(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { + // First build a polygon soup + add_points(p0, p1, p2); + std::vector rindices; + rindices.reserve(3); + rindices.push_back(vertex_map[p0]); + rindices.push_back(vertex_map[p1]); + rindices.push_back(vertex_map[p2]); + polygons.push_back(rindices); + } + + void get_mesh(geoflow::nodes::stepedge::SurfaceMesh& smesh) { + smesh.clear(); + CGAL::Polygon_mesh_processing::repair_polygon_soup(points, polygons); + + // duplicate non-manifold edges (but does not re-orient faces) + CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup(points, polygons); + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, smesh); + } + + }; + const K::Vector_3 up(0,0,1); bool is_vertical(const K::Point_3& a, const K::Point_3& b, const K::Point_3& c) { @@ -60,7 +112,7 @@ namespace geoflow::nodes::stepedge { if (error_ > 0){ tinsimp::CDT t; - // collect vertical faces + // collect vertical faces that make up the vertical wall std::vector> wall_triangles; for (auto& face : smesh.faces()) { std::vector triangle; @@ -69,7 +121,7 @@ namespace geoflow::nodes::stepedge { } if (is_vertical(triangle[0], triangle[1], triangle[2])) { wall_triangles.push_back(triangle); - smesh.remove_face(face); + CGAL::Euler::remove_face(smesh.halfedge(face), smesh); } } smesh.collect_garbage(); @@ -114,30 +166,42 @@ namespace geoflow::nodes::stepedge { tinsimp::mark_domains(t); smesh.clear(); - std::map vertex_map; - for (auto& vh : t.finite_vertex_handles()) { - auto vindex = smesh.add_vertex(K::Point_3( - vh->point().x(), - vh->point().y(), - vh->point().z() - )); - vertex_map[vh] = vindex; - } + MeshBuilder mb; for (auto& fh : t.finite_face_handles()) { if(fh->info().in_domain()) { - smesh.add_face( - vertex_map[fh->vertex(0)], - vertex_map[fh->vertex(1)], - vertex_map[fh->vertex(2)] - ); + mb.add_triangle( + K::Point_3( + fh->vertex(0)->point().x(), + fh->vertex(0)->point().y(), + fh->vertex(0)->point().z() + ), + K::Point_3( + fh->vertex(1)->point().x(), + fh->vertex(1)->point().y(), + fh->vertex(1)->point().z() + ), + K::Point_3( + fh->vertex(2)->point().x(), + fh->vertex(2)->point().y(), + fh->vertex(2)->point().z() + ) + ); } } for (auto& triangle : wall_triangles) { - auto ia = smesh.add_vertex(triangle[0]); - auto ib = smesh.add_vertex(triangle[1]); - auto ic = smesh.add_vertex(triangle[2]); - smesh.add_face(ia, ib, ic); + mb.add_triangle(triangle[0], triangle[1], triangle[2]); } + mb.get_mesh(smesh); + + // TriangleCollection tc; + // for ( auto& t : wall_triangles ) { + // Triangle gft; + // gft[0] = arr3f{static_cast(t[0].x()), static_cast(t[0].y()), static_cast(t[0].z())}; + // gft[1] = arr3f{static_cast(t[1].x()), static_cast(t[1].y()), static_cast(t[1].z())}; + // gft[2] = arr3f{static_cast(t[2].x()), static_cast(t[2].y()), static_cast(t[2].z())}; + // tc.push_back(gft); + // } + // output("wall_triangles").set(tc); } output("cgal_surface_mesh").set(smesh); } From 30b2913e46855e776710d1229e8ed67a9897c74e Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 12 Feb 2024 18:01:43 +0100 Subject: [PATCH 23/43] implement slope/azimuth calculation for roofparts --- src/heightfield_nodes.cpp | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index c5d3a47..16d406e 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -15,11 +15,15 @@ // along with this program. If not, see . // triangulation #include +#include #include #include #include #include +#include +#include #include +#include #include #include "Raster.h" @@ -624,6 +628,22 @@ namespace geoflow::nodes::stepedge { } } + AK::Vector_3 calculate_normal_ak(const LinearRing ring) + { + AK::Vector_3 normal(0, 0, 0); + for (size_t i = 0; i < ring.size(); ++i) { + const auto &curr = ring[i]; + const auto &next = ring[(i + 1) % ring.size()]; + normal.x() += (curr[1] - next[1]) * (curr[2] + next[2]); + normal.y() += (curr[2] - next[2]) * (curr[0] + next[0]); + normal.z() += (curr[0] - next[0]) * (curr[1] + next[1]); + } + return normal / CGAL::approximate_sqrt(normal.squared_length()); + } + + static constexpr double pi = 3.14159265358979323846; + static const AK::Vector_3 up = AK::Vector_3(0,0,1); + void RoofPartition3DBAGRasteriseNode::process(){ auto& lod12_roofparts = input("lod12_roofparts");//.get(); auto& lod13_roofparts = input("lod13_roofparts");//.get(); @@ -651,6 +671,29 @@ namespace geoflow::nodes::stepedge { calculate_h_attr(lod12_roofparts, lod12_hattr, r_lod22, z_offset); calculate_h_attr(lod13_roofparts, lod13_hattr, r_lod22, z_offset); calculate_h_attr(lod22_roofparts, lod22_hattr, r_lod22, z_offset); + + // compute inclination and azimuth. Both in degrees. + lod22_hattr.add_vector("b3_hellingshoek", typeid(float)); + lod22_hattr.add_vector("b3_azimut", typeid(float)); + for (size_t i=0; i(i); + auto n = calculate_normal_ak(ring); + auto slope = CGAL::approximate_angle(n, up); + auto x = CGAL::to_double(n.x()); + auto y = CGAL::to_double(n.y()); + // calculate azimuth from arctan2 (https://en.cppreference.com/w/cpp/numeric/math/atan2) + // ie. subtract pi/2, multiply by -1 and then add 2 pi if result is negative (4th quadrant) + double azimuth = -1 * ( std::atan2(y, x) - pi/2 ); + if (azimuth<0) { + azimuth = 2*pi + azimuth; + } + // convert to degrees + azimuth = azimuth / (180/pi); + + // push attributes + lod22_hattr.sub_terminal("b3_hellingshoek").push_back(slope); + lod22_hattr.sub_terminal("b3_azimut").push_back(azimuth); + } } void SegmentRasteriseNode::rasterise_input(gfSingleFeatureInputTerminal& input_triangles, RasterTools::Raster& r, size_t& data_pixel_cnt) { From 2b4f4f30ca76d3d8bd2f299b2545b6c2e71ef550 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 21 Feb 2024 11:37:39 +0100 Subject: [PATCH 24/43] MeshSimplify2DNode: add minpts parameter --- src/MeshProcessingNodes.hpp | 3 +++ src/MeshSimplify.cpp | 2 +- src/tinsimp.cpp | 9 +++++++-- src/tinsimp.hpp | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/MeshProcessingNodes.hpp b/src/MeshProcessingNodes.hpp index f173d81..d7ebc4d 100644 --- a/src/MeshProcessingNodes.hpp +++ b/src/MeshProcessingNodes.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace geoflow::nodes::stepedge { typedef CGAL::Simple_cartesian K; @@ -59,6 +60,7 @@ namespace geoflow::nodes::stepedge { class MeshSimplify2DNode:public Node { float error_ = 0.5; + int minpts_ = 10; public: using Node::Node; @@ -68,6 +70,7 @@ namespace geoflow::nodes::stepedge { // add_output("wall_triangles", typeid(TriangleCollection)); add_param(ParamBoundedFloat(error_, 0, 5, "error", "Target maximum eror after simplification")); + add_param(ParamBoundedInt(minpts_, 0, 100, "error", "Minimum number of elevation points inside a polygon")); } void process() override; }; diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index 9d56284..f93c9ee 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -160,7 +160,7 @@ namespace geoflow::nodes::stepedge { } } - tinsimp::greedy_insert(t, zpts, error_); + tinsimp::greedy_insert(t, zpts, error_, minpts_); // std::cout << "\nFinished!\n" << r << " edges removed.\n" // << smesh.number_of_edges() << " final edges.\n"; tinsimp::mark_domains(t); diff --git a/src/tinsimp.cpp b/src/tinsimp.cpp index 59b8ff6..8ccdd3c 100644 --- a/src/tinsimp.cpp +++ b/src/tinsimp.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -106,7 +107,7 @@ inline double compute_error(Point &p, CDT::Face_handle &face) { // greedy_insert(T, cpts, threshold); // } -void greedy_insert(CDT &T, std::vector &cpts, double threshold) { +void greedy_insert(CDT &T, std::vector &cpts, double threshold, size_t minpts) { // assumes all lidar points are inside a triangle Heap heap; @@ -130,8 +131,11 @@ void greedy_insert(CDT &T, std::vector &cpts, double threshold) { } std::cout << "prepared tinsimp...\n"; + if (heap.size() < minpts) minpts = heap.size(); + // insert points, update errors of affected triangles until threshold error is reached - while (!heap.empty() && heap.top().error > threshold) { + size_t insert_cnt = 0; + while ( (!heap.empty() && heap.top().error > threshold) || insert_cnt < minpts ) { // get top element (with largest error) from heap auto maxelement = heap.top(); auto max_p = cpts[maxelement.index]; @@ -144,6 +148,7 @@ void greedy_insert(CDT &T, std::vector &cpts, double threshold) { auto face_hint = faces[0]; auto v = T.insert(max_p, face_hint); face_hint = v->face(); + ++insert_cnt; // update clear info of triangles that just changed, collect points that were inside these triangles std::vector points_to_update; diff --git a/src/tinsimp.hpp b/src/tinsimp.hpp index 85a61df..cbb5e93 100644 --- a/src/tinsimp.hpp +++ b/src/tinsimp.hpp @@ -50,6 +50,6 @@ typedef CGAL::Constrained_triangulation_plus_2 CT; typedef CDT::Point Point; // void greedy_insert(CDT &T, const std::vector> &pts, double threshold); -void greedy_insert(CDT &T, std::vector &cpts, double threshold); +void greedy_insert(CDT &T, std::vector &cpts, double threshold, size_t minpts=0); void mark_domains(CDT& cdt); } \ No newline at end of file From 8a8b2f67e0072b14deab7d1049baf957d0e7568a Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 21 Feb 2024 17:12:02 +0100 Subject: [PATCH 25/43] MeshSimplify2DNode: further improvements minpts feature --- src/MeshProcessingNodes.hpp | 4 ++-- src/MeshSimplify.cpp | 19 +++++++++++++++++-- src/tinsimp.cpp | 30 +++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/MeshProcessingNodes.hpp b/src/MeshProcessingNodes.hpp index d7ebc4d..6036610 100644 --- a/src/MeshProcessingNodes.hpp +++ b/src/MeshProcessingNodes.hpp @@ -60,7 +60,7 @@ namespace geoflow::nodes::stepedge { class MeshSimplify2DNode:public Node { float error_ = 0.5; - int minpts_ = 10; + float minpts_ = 0.5; public: using Node::Node; @@ -70,7 +70,7 @@ namespace geoflow::nodes::stepedge { // add_output("wall_triangles", typeid(TriangleCollection)); add_param(ParamBoundedFloat(error_, 0, 5, "error", "Target maximum eror after simplification")); - add_param(ParamBoundedInt(minpts_, 0, 100, "error", "Minimum number of elevation points inside a polygon")); + add_param(ParamBoundedFloat(minpts_, 0, 10, "minpts", "Minimum number of elevation points per m2 inside a polygon")); } void process() override; }; diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index f93c9ee..a59f2fb 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -159,11 +160,25 @@ namespace geoflow::nodes::stepedge { ); } } + + tinsimp::mark_domains(t); + float sq_area = 0; + for (auto& fh : t.finite_face_handles()) { + if(fh->info().in_domain()) { + sq_area += t.triangle(fh).squared_area(); + } + } + float total_area = std::sqrt(sq_area); - tinsimp::greedy_insert(t, zpts, error_, minpts_); + tinsimp::greedy_insert(t, zpts, error_, minpts_*total_area); + + // reset and recompute nesting levels + for (auto& fh : t.all_face_handles()) { + fh->info().nesting_level = -1; + } + tinsimp::mark_domains(t); // std::cout << "\nFinished!\n" << r << " edges removed.\n" // << smesh.number_of_edges() << " final edges.\n"; - tinsimp::mark_domains(t); smesh.clear(); MeshBuilder mb; diff --git a/src/tinsimp.cpp b/src/tinsimp.cpp index 8ccdd3c..de10aaa 100644 --- a/src/tinsimp.cpp +++ b/src/tinsimp.cpp @@ -3,8 +3,10 @@ #include #include #include +#include #include +#include #include #include @@ -131,11 +133,10 @@ void greedy_insert(CDT &T, std::vector &cpts, double threshold, size_t mi } std::cout << "prepared tinsimp...\n"; - if (heap.size() < minpts) minpts = heap.size(); - // insert points, update errors of affected triangles until threshold error is reached size_t insert_cnt = 0; - while ( (!heap.empty() && heap.top().error > threshold) || insert_cnt < minpts ) { + // while ( (!heap.empty() && heap.top().error > threshold) || insert_cnt < minpts ) { + while (!heap.empty() && heap.top().error > threshold) { // get top element (with largest error) from heap auto maxelement = heap.top(); auto max_p = cpts[maxelement.index]; @@ -184,6 +185,29 @@ void greedy_insert(CDT &T, std::vector &cpts, double threshold, size_t mi } } } + + // insert more points so we can guarantee the minpts number of interior points + if (heap.size() < minpts) minpts = heap.size(); + if(insert_cnt < minpts) { + std::list> remaining_pts; + for (auto hit = heap.ordered_begin(); hit != heap.ordered_end(); ++hit) { + auto& p = cpts[(*hit).index]; + remaining_pts.push_back(p); + } + + auto rand = CGAL::Random(13374269); + while (insert_cnt < minpts) { + auto it = remaining_pts.begin(); + auto r = rand.get_int(0, remaining_pts.size()); + while (r > 0) { + ++it; + --r; + } + T.insert(*it); + ++insert_cnt; + remaining_pts.erase(it); + } + } //cleanup the stuff I put in face info of triangles for (CDT::Finite_faces_iterator fit = T.finite_faces_begin(); From b4d40f1698d11d01686c2cb549d21acae166a0dc Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 22 Feb 2024 17:48:07 +0100 Subject: [PATCH 26/43] update region-grower code --- thirdparty/region-grower | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/region-grower b/thirdparty/region-grower index 3f824f2..9c12bf2 160000 --- a/thirdparty/region-grower +++ b/thirdparty/region-grower @@ -1 +1 @@ -Subproject commit 3f824f2aaa63317dab5470bbe39b98a723f99149 +Subproject commit 9c12bf2bfdfe81eca4f807c305ae5059da941229 From 4b6d2b5636370253b996e8ea0aef5fd9051eee2d Mon Sep 17 00:00:00 2001 From: Ylannl Date: Fri, 23 Feb 2024 12:20:26 +0100 Subject: [PATCH 27/43] debug slope/azimuth code --- src/heightfield_nodes.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index 16d406e..51deffa 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -628,17 +628,18 @@ namespace geoflow::nodes::stepedge { } } - AK::Vector_3 calculate_normal_ak(const LinearRing ring) + AK::Vector_3 calculate_normal_ak(const LinearRing& ring) { - AK::Vector_3 normal(0, 0, 0); + float x=0, y=0, z=0; for (size_t i = 0; i < ring.size(); ++i) { const auto &curr = ring[i]; const auto &next = ring[(i + 1) % ring.size()]; - normal.x() += (curr[1] - next[1]) * (curr[2] + next[2]); - normal.y() += (curr[2] - next[2]) * (curr[0] + next[0]); - normal.z() += (curr[0] - next[0]) * (curr[1] + next[1]); + x += (curr[1] - next[1]) * (curr[2] + next[2]); + y += (curr[2] - next[2]) * (curr[0] + next[0]); + z += (curr[0] - next[0]) * (curr[1] + next[1]); } - return normal / CGAL::approximate_sqrt(normal.squared_length()); + AK::Vector_3 n(x, y, z); + return n / CGAL::approximate_sqrt(n.squared_length()); } static constexpr double pi = 3.14159265358979323846; @@ -678,12 +679,12 @@ namespace geoflow::nodes::stepedge { for (size_t i=0; i(i); auto n = calculate_normal_ak(ring); - auto slope = CGAL::approximate_angle(n, up); + float slope = CGAL::to_double(CGAL::approximate_angle(n, up)); auto x = CGAL::to_double(n.x()); auto y = CGAL::to_double(n.y()); // calculate azimuth from arctan2 (https://en.cppreference.com/w/cpp/numeric/math/atan2) // ie. subtract pi/2, multiply by -1 and then add 2 pi if result is negative (4th quadrant) - double azimuth = -1 * ( std::atan2(y, x) - pi/2 ); + float azimuth = -1 * ( std::atan2(y, x) - pi/2 ); if (azimuth<0) { azimuth = 2*pi + azimuth; } From fbe70ed0a36b1ece31c10787176d71bee9d4d69c Mon Sep 17 00:00:00 2001 From: Ylannl Date: Fri, 23 Feb 2024 13:43:26 +0100 Subject: [PATCH 28/43] fix degree conversion for azimuth --- src/heightfield_nodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index 51deffa..8b863cb 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -689,7 +689,7 @@ namespace geoflow::nodes::stepedge { azimuth = 2*pi + azimuth; } // convert to degrees - azimuth = azimuth / (180/pi); + azimuth = azimuth * (180/pi); // push attributes lod22_hattr.sub_terminal("b3_hellingshoek").push_back(slope); From d16ea6c6132a13be2f9505cd9ad700b030c381a8 Mon Sep 17 00:00:00 2001 From: Ylannl Date: Fri, 23 Feb 2024 21:51:39 +0100 Subject: [PATCH 29/43] bump version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3afe69c..2940893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.3.7) +project (building-reconstruction VERSION 0.4.0) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) @@ -110,4 +110,4 @@ if (MSVC) FILES_MATCHING PATTERN "*.dll" PATTERN "gfp*" EXCLUDE) -endif() \ No newline at end of file +endif() From dd1130231086256136bddd352ea23117ed02facb Mon Sep 17 00:00:00 2001 From: Ylannl Date: Wed, 28 Feb 2024 10:52:29 +0100 Subject: [PATCH 30/43] attempt to fix duplicate rings in polygon triangulator --- src/polygon_triangulate.cpp | 11 ++++++++--- src/polygon_util.cpp | 23 +++++++++++++++++++++++ src/polygon_util.hpp | 1 + 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/polygon_triangulate.cpp b/src/polygon_triangulate.cpp index 673b272..3b09ac4 100644 --- a/src/polygon_triangulate.cpp +++ b/src/polygon_triangulate.cpp @@ -111,9 +111,14 @@ void PolygonTriangulatorNode::triangulate_polygon(LinearRing& poly, vec3f& norma float dupe_threshold = (float) std::pow(10,-dupe_threshold_exp); if (is_degenerate(poly, dupe_threshold)) { - std::cout << "skipping ring with duplicates\n"; - // dupe_rings.push_back(poly); - return; + LinearRing new_poly = fix_duplicates(poly, dupe_threshold); + if(is_degenerate(new_poly, dupe_threshold)) { + std::cout << "skipping ring with duplicates\n"; + // dupe_rings.push_back(poly); + return; + } + std::cout << "fixed ring with duplicates\n"; + poly = new_poly; } auto normal = calculate_normal(poly); if (std::isnan(normal.x) || std::isnan(normal.y) || std::isnan(normal.z)){ diff --git a/src/polygon_util.cpp b/src/polygon_util.cpp index 2eacf3b..0e2a96d 100644 --- a/src/polygon_util.cpp +++ b/src/polygon_util.cpp @@ -37,4 +37,27 @@ namespace geoflow::nodes::stepedge { } return false; } + + + void fix_duplicates_ring(vec3f& poly, vec3f& new_ring, float& dupe_threshold) { + auto pl = *poly.rbegin(); + for (auto& p : poly) { + if (!(std::fabs(pl[0]-p[0])< dupe_threshold && std::fabs(pl[1]-p[1])< dupe_threshold && std::fabs(pl[2]-p[2])< dupe_threshold)) { + new_ring.push_back(p); + } + pl=p; + } + } + + LinearRing fix_duplicates(LinearRing& poly, float& dupe_threshold) { + LinearRing new_lr; + fix_duplicates_ring(poly, new_lr, dupe_threshold); + + for (auto& ring : poly.interior_rings()) { + vec3f new_ring; + fix_duplicates_ring(ring, new_ring, dupe_threshold); + new_lr.interior_rings().push_back(new_ring); + } + return new_lr; + } } \ No newline at end of file diff --git a/src/polygon_util.hpp b/src/polygon_util.hpp index 7d22a69..6d3a71d 100644 --- a/src/polygon_util.hpp +++ b/src/polygon_util.hpp @@ -18,4 +18,5 @@ namespace geoflow::nodes::stepedge { bool has_duplicates_ring(vec3f& poly, float& dupe_threshold); bool is_degenerate(LinearRing& poly, float& dupe_threshold); + LinearRing fix_duplicates(LinearRing& poly, float& dupe_threshold); } \ No newline at end of file From ddc11cf829ae932ddc876bc1a162b15bf2f6661e Mon Sep 17 00:00:00 2001 From: Ylannl Date: Wed, 28 Feb 2024 10:52:52 +0100 Subject: [PATCH 31/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2940893..d2f6f23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.4.0) +project (building-reconstruction VERSION 0.4.1) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From a64aa9c2af2bc05af351a7ba86643eaf6e6b1b10 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 8 Mar 2024 16:32:05 +0100 Subject: [PATCH 32/43] fix windows build --- src/Mesh2TriangleCollectionNode.cpp | 2 +- src/MeshClipperNode.cpp | 2 +- src/MeshGridSimplify.cpp | 6 +++--- thirdparty/region-grower | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mesh2TriangleCollectionNode.cpp b/src/Mesh2TriangleCollectionNode.cpp index 4e29b20..61707e5 100644 --- a/src/Mesh2TriangleCollectionNode.cpp +++ b/src/Mesh2TriangleCollectionNode.cpp @@ -36,7 +36,7 @@ namespace geoflow::nodes::stepedge { TriangleCollection triangleCollection; vec3f normals; - for (auto& f : smesh.faces()) { + for (auto f : smesh.faces()) { Triangle t; unsigned i = 0; diff --git a/src/MeshClipperNode.cpp b/src/MeshClipperNode.cpp index 9853327..dc38b16 100644 --- a/src/MeshClipperNode.cpp +++ b/src/MeshClipperNode.cpp @@ -98,7 +98,7 @@ namespace geoflow::nodes::stepedge { } else { MeshBuilder mb; CGAL::Vertex_around_face_iterator vit, vend; - for (auto& f : smesh.faces()) { + for (auto f : smesh.faces()) { boost::tie(vit, vend) = CGAL::vertices_around_face(smesh.halfedge(f), smesh); auto end = vit; K::Point_3 p1 = smesh.point(*vit); vit++; diff --git a/src/MeshGridSimplify.cpp b/src/MeshGridSimplify.cpp index 8b00f6b..887588a 100644 --- a/src/MeshGridSimplify.cpp +++ b/src/MeshGridSimplify.cpp @@ -144,7 +144,7 @@ namespace geoflow::nodes::stepedge { // merge vertices in each grid cell, and create a mapping (old vertex indices -> new vertex indices, std::map) Grid2D G(box, cell_size_xy_, cell_size_z_); std::unordered_map vertex_map; - for (auto& vi : smesh.vertices()) { + for (auto vi : smesh.vertices()) { unsigned c = G.getCellCoordinate( smesh.point(vi) ); vertex_map[vi] = c; } @@ -161,7 +161,7 @@ namespace geoflow::nodes::stepedge { // Cluster(float x, float y) : x(x), y(y) {}; // }; std::unordered_map new_vertices; - for (auto& f : smesh.faces()) { + for (auto f : smesh.faces()) { for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { auto p = G.getCellCenterPoint(vertex_map[vi]); new_mesh_vertices[vertex_map[vi]] = smesh_new.add_vertex(K::Point_3(p[0], p[1], p[2])); @@ -169,7 +169,7 @@ namespace geoflow::nodes::stepedge { } } { - for (auto& f : smesh.faces()) { + for (auto f : smesh.faces()) { std::set face_set; for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { face_set.insert(vertex_map[vi]); diff --git a/thirdparty/region-grower b/thirdparty/region-grower index 9c12bf2..3f824f2 160000 --- a/thirdparty/region-grower +++ b/thirdparty/region-grower @@ -1 +1 @@ -Subproject commit 9c12bf2bfdfe81eca4f807c305ae5059da941229 +Subproject commit 3f824f2aaa63317dab5470bbe39b98a723f99149 From 1e9de989aff68f8457721ccd51b7b0cd76f0913d Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 8 Mar 2024 16:32:48 +0100 Subject: [PATCH 33/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2f6f23..a171cd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.4.1) +project (building-reconstruction VERSION 0.4.2) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From 61bbb6c6f53e9b700b1a09944685ecf51d1f257a Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 8 Mar 2024 17:18:17 +0100 Subject: [PATCH 34/43] windows build fixes --- src/MeshClipperNode.cpp | 2 +- src/MeshSimplify.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MeshClipperNode.cpp b/src/MeshClipperNode.cpp index dc38b16..64f31fe 100644 --- a/src/MeshClipperNode.cpp +++ b/src/MeshClipperNode.cpp @@ -149,7 +149,7 @@ namespace geoflow::nodes::stepedge { // convert to triangleCollection TriangleCollection triangleCollection; vec3f normals; - for (auto& f : smesh.faces()) { + for (auto f : smesh.faces()) { Triangle t; unsigned i = 0; diff --git a/src/MeshSimplify.cpp b/src/MeshSimplify.cpp index a59f2fb..28dbd28 100644 --- a/src/MeshSimplify.cpp +++ b/src/MeshSimplify.cpp @@ -115,7 +115,7 @@ namespace geoflow::nodes::stepedge { // collect vertical faces that make up the vertical wall std::vector> wall_triangles; - for (auto& face : smesh.faces()) { + for (auto face : smesh.faces()) { std::vector triangle; for(vertex_descriptor vd : vertices_around_face(smesh.halfedge(face), smesh)){ triangle.push_back(smesh.point(vd)); From 2cc58f43072823ceb59d19b0d8042a10e02b641e Mon Sep 17 00:00:00 2001 From: Ylannl Date: Sun, 14 Apr 2024 12:13:46 +0200 Subject: [PATCH 35/43] fix potential crash with height calculation --- src/heightfield_nodes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index 8b863cb..0fa7df8 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -655,8 +655,8 @@ namespace geoflow::nodes::stepedge { auto& lod22_hattr = poly_output("lod22_hattr"); Box box; - for (size_t i=0; i< lod12_roofparts.size(); ++i) { - auto rpart = lod12_roofparts.get(i); + for (size_t i=0; i< lod22_roofparts.size(); ++i) { + auto rpart = lod22_roofparts.get(i); box.add(rpart.box()); } auto boxmin = box.min(); From afee28651aea5951583c05566f415fb47c7c6603 Mon Sep 17 00:00:00 2001 From: Ylannl Date: Sun, 14 Apr 2024 17:48:19 +0200 Subject: [PATCH 36/43] fix potential crash due to invalid normal --- src/heightfield_nodes.cpp | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index 0fa7df8..0fe5e1a 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -628,7 +629,8 @@ namespace geoflow::nodes::stepedge { } } - AK::Vector_3 calculate_normal_ak(const LinearRing& ring) + typedef CGAL::Simple_cartesian CF; + CF::Vector_3 calculate_normal_cf(const LinearRing& ring) { float x=0, y=0, z=0; for (size_t i = 0; i < ring.size(); ++i) { @@ -638,12 +640,12 @@ namespace geoflow::nodes::stepedge { y += (curr[2] - next[2]) * (curr[0] + next[0]); z += (curr[0] - next[0]) * (curr[1] + next[1]); } - AK::Vector_3 n(x, y, z); + CF::Vector_3 n(x, y, z); return n / CGAL::approximate_sqrt(n.squared_length()); } static constexpr double pi = 3.14159265358979323846; - static const AK::Vector_3 up = AK::Vector_3(0,0,1); + static const CF::Vector_3 up = CF::Vector_3(0,0,1); void RoofPartition3DBAGRasteriseNode::process(){ auto& lod12_roofparts = input("lod12_roofparts");//.get(); @@ -678,19 +680,24 @@ namespace geoflow::nodes::stepedge { lod22_hattr.add_vector("b3_azimut", typeid(float)); for (size_t i=0; i(i); - auto n = calculate_normal_ak(ring); - float slope = CGAL::to_double(CGAL::approximate_angle(n, up)); - auto x = CGAL::to_double(n.x()); - auto y = CGAL::to_double(n.y()); - // calculate azimuth from arctan2 (https://en.cppreference.com/w/cpp/numeric/math/atan2) - // ie. subtract pi/2, multiply by -1 and then add 2 pi if result is negative (4th quadrant) - float azimuth = -1 * ( std::atan2(y, x) - pi/2 ); - if (azimuth<0) { - azimuth = 2*pi + azimuth; - } - // convert to degrees - azimuth = azimuth * (180/pi); + auto n = calculate_normal_cf(ring); + float azimuth, slope; + if (std::isnan(n.x()) || std::isnan(n.y()) || std::isnan(n.z())){ + azimuth = std::numeric_limits::quiet_NaN(); + slope = std::numeric_limits::quiet_NaN(); + } else { + float slope = CGAL::to_double(CGAL::approximate_angle(n, up)); + + // calculate azimuth from arctan2 (https://en.cppreference.com/w/cpp/numeric/math/atan2) + // ie. subtract pi/2, multiply by -1 and then add 2 pi if result is negative (4th quadrant) + float azimuth = -1 * ( std::atan2(n.y(), n.x()) - pi/2 ); + if (azimuth<0) { + azimuth = 2*pi + azimuth; + } + // convert to degrees + azimuth = azimuth * (180/pi); + } // push attributes lod22_hattr.sub_terminal("b3_hellingshoek").push_back(slope); lod22_hattr.sub_terminal("b3_azimut").push_back(azimuth); From c20d9716394b13ad7a3a4abd54cea10b0b07920f Mon Sep 17 00:00:00 2001 From: Ylannl Date: Sat, 20 Apr 2024 09:53:06 +0200 Subject: [PATCH 37/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2f6f23..a171cd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.4.1) +project (building-reconstruction VERSION 0.4.2) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From 363dedaf1bb9902afdc49da185a32cabbd6639ea Mon Sep 17 00:00:00 2001 From: Ylannl Date: Sat, 20 Apr 2024 09:55:59 +0200 Subject: [PATCH 38/43] bump version --- CMakeLists.txt | 2 +- thirdparty/region-grower | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a171cd4..f958c4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.4.2) +project (building-reconstruction VERSION 0.4.3) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) diff --git a/thirdparty/region-grower b/thirdparty/region-grower index 3f824f2..9c12bf2 160000 --- a/thirdparty/region-grower +++ b/thirdparty/region-grower @@ -1 +1 @@ -Subproject commit 3f824f2aaa63317dab5470bbe39b98a723f99149 +Subproject commit 9c12bf2bfdfe81eca4f807c305ae5059da941229 From 19f8c9c0516ba606a3f14cfbcbfdf1a36c3c82c3 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 24 Jul 2024 12:01:20 +0200 Subject: [PATCH 39/43] set ptinpoly c_standard to 90 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f958c4d..376b9bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ endif(GFP_WITH_PDAL) add_library(ptinpoly STATIC thirdparty/ptinpoly/ptinpoly.c) set_target_properties( ptinpoly PROPERTIES - C_STANDARD 11 + C_STANDARD 90 POSITION_INDEPENDENT_CODE ON ) From 8f3284d8f768b85a4e4adee9252d290c9f5326de Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 8 Aug 2024 20:54:32 +0200 Subject: [PATCH 40/43] prevent crash due to CGAL uncertain_conversion_exception --- src/line_regulariser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/line_regulariser.cpp b/src/line_regulariser.cpp index 68093e0..29342f3 100644 --- a/src/line_regulariser.cpp +++ b/src/line_regulariser.cpp @@ -151,7 +151,12 @@ namespace linereg { lhs_hhandles.erase(i++); } else { // dereference 3 times! iterator->shared_ptr->heap_handle->heap_value Notice -> is not implemented on the heap_handle, so we must use * for the last dereference here - (***i).dist = (***i).clusters.first->distance((***i).clusters.second.get()); + try { + (***i).dist = (***i).clusters.first->distance((***i).clusters.second.get()); + } catch (CGAL::Uncertain_conversion_exception e) { + // this happes sometimes, assuming here that this means the distance was close to 0 + (***i).dist = 0; + } distances.update(*(*i)); ++i; } From f03b0d0ad72a6d716cdc473d7d7db84815499c21 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 9 Aug 2024 13:02:38 +0200 Subject: [PATCH 41/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 376b9bc..f715670 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.4.3) +project (building-reconstruction VERSION 0.4.4) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) From b3ea57d21c7fbe45bac1ed159f9911d4b90e48ce Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Tue, 3 Dec 2024 09:23:38 +0100 Subject: [PATCH 42/43] fix azimuth/slope calculation --- src/heightfield_nodes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/heightfield_nodes.cpp b/src/heightfield_nodes.cpp index 0fe5e1a..65d1fb8 100644 --- a/src/heightfield_nodes.cpp +++ b/src/heightfield_nodes.cpp @@ -686,11 +686,11 @@ namespace geoflow::nodes::stepedge { azimuth = std::numeric_limits::quiet_NaN(); slope = std::numeric_limits::quiet_NaN(); } else { - float slope = CGAL::to_double(CGAL::approximate_angle(n, up)); + slope = CGAL::to_double(CGAL::approximate_angle(n, up)); // calculate azimuth from arctan2 (https://en.cppreference.com/w/cpp/numeric/math/atan2) // ie. subtract pi/2, multiply by -1 and then add 2 pi if result is negative (4th quadrant) - float azimuth = -1 * ( std::atan2(n.y(), n.x()) - pi/2 ); + azimuth = -1 * ( std::atan2(n.y(), n.x()) - pi/2 ); if (azimuth<0) { azimuth = 2*pi + azimuth; } From 7eeee940c2ca81960f688e09c6b1be8f6afd399c Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Tue, 3 Dec 2024 09:46:00 +0100 Subject: [PATCH 43/43] bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f715670..f6b770b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project (building-reconstruction VERSION 0.4.4) +project (building-reconstruction VERSION 0.4.5) option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF)