├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── register.hpp ├── src ├── Alpha_expansion_MaxFlow_tag.h ├── ClusterPointCloudNode.cpp ├── ContourRegulariserNode.cpp ├── MaxInscribedCircleNode.cpp ├── MaxInscribedCircleNode.hpp ├── Mesh2TriangleCollectionNode.cpp ├── MeshClipperNode.cpp ├── MeshGridSimplify.cpp ├── MeshProcessingNodes.hpp ├── MeshSimplify.cpp ├── MeshSimplifyFastQuadNode.cpp ├── MeshSimplifyFastQuadNode.hpp ├── Raster.cpp ├── Raster.h ├── alpha_expansion_graphcut.h ├── alpha_shape.cpp ├── arrangement.cpp ├── arrangement.hpp ├── build_arrangement_lines.cpp ├── build_arrangement_rings.cpp ├── cdt_util.cpp ├── cdt_util.hpp ├── data_coverage_node.cpp ├── dbscan.cpp ├── dbscan.h ├── detect_planes_node.cpp ├── geos_nodes.cpp ├── graph.h ├── heightfield_nodes.cpp ├── interval.hpp ├── line_regulariser.cpp ├── line_regulariser.hpp ├── node_optimise_arrangement.cpp ├── node_pcmesh_quality.cpp ├── pip_util.cpp ├── pip_util.hpp ├── plane_detect.hpp ├── point_edge.cpp ├── point_edge.h ├── points_in_polygons.cpp ├── polygon_offset.cpp ├── polygon_triangulate.cpp ├── polygon_util.cpp ├── polygon_util.hpp ├── region_growing.cpp ├── region_growing.h ├── snap_round.cpp ├── stepedge_nodes.cpp ├── stepedge_nodes.hpp ├── stepedge_register.hpp ├── tinsimp.cpp ├── tinsimp.hpp └── trisnap.cpp └── thirdparty ├── earcut └── earcut.hpp ├── fast-quadric-mesh-simplification └── Simplify.h └── ptinpoly ├── ptinpoly.c └── ptinpoly.h /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/region-grower"] 2 | path = thirdparty/region-grower 3 | url = https://github.com/Ylannl/region-grower.git 4 | [submodule "thirdparty/glm"] 5 | path = thirdparty/glm 6 | url = https://github.com/g-truc/glm.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project (building-reconstruction VERSION 0.4.5) 3 | 4 | option(GFP_WITH_PDAL "Build parts dependent on PDAL" OFF) 5 | 6 | add_definitions(-DGF_PLUGIN_NAME=\"${CMAKE_PROJECT_NAME}\") 7 | 8 | if(PROJECT_IS_TOP_LEVEL) 9 | find_package(geoflow REQUIRED) 10 | endif() 11 | 12 | if(GFP_WITH_PDAL) 13 | add_definitions(-DGFP_WITH_PDAL) 14 | endif(GFP_WITH_PDAL) 15 | 16 | # ptinpoly 17 | add_library(ptinpoly STATIC thirdparty/ptinpoly/ptinpoly.c) 18 | set_target_properties( 19 | ptinpoly PROPERTIES 20 | C_STANDARD 90 21 | POSITION_INDEPENDENT_CODE ON 22 | ) 23 | 24 | if(EXISTS "${PROJECT_SOURCE_DIR}/.gitmodules") 25 | execute_process( 26 | COMMAND git submodule update --init region-grower glm 27 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/thirdparty 28 | ) 29 | endif() 30 | 31 | set(GLM_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/thirdparty/glm) 32 | 33 | find_package(laslib CONFIG REQUIRED) 34 | find_package(Eigen3 CONFIG REQUIRED) 35 | add_definitions(-DCGAL_EIGEN3_ENABLED) 36 | find_package(CGAL 5.4 QUIET COMPONENTS Core REQUIRED) 37 | # PDAL 38 | if(GFP_WITH_PDAL) 39 | find_package(PDAL REQUIRED) 40 | endif(GFP_WITH_PDAL) 41 | 42 | if (MSVC) 43 | # windows.h breaks std::min/std::max, fix by define 44 | add_definitions(-DNOMINMAX) 45 | # enable permissive compiling and/or statements 46 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") 47 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") 48 | endif() 49 | 50 | set(GF_PLUGIN_NAME ${PROJECT_NAME}) 51 | set(GF_PLUGIN_TARGET_NAME "gfp_buildingreconstruction") 52 | set(GF_PLUGIN_REGISTER ${PROJECT_SOURCE_DIR}/register.hpp) 53 | geoflow_create_plugin( 54 | src/stepedge_nodes.cpp 55 | src/node_optimise_arrangement.cpp 56 | src/points_in_polygons.cpp 57 | src/point_edge.cpp 58 | src/region_growing.cpp 59 | src/node_pcmesh_quality.cpp 60 | src/Raster.cpp 61 | src/heightfield_nodes.cpp 62 | src/polygon_triangulate.cpp 63 | src/line_regulariser.cpp 64 | src/build_arrangement_lines.cpp 65 | # src/build_arrangement_rings.cpp 66 | src/arrangement.cpp 67 | src/snap_round.cpp 68 | src/polygon_offset.cpp 69 | src/alpha_shape.cpp 70 | src/data_coverage_node.cpp 71 | src/detect_planes_node.cpp 72 | src/polygon_util.cpp 73 | src/cdt_util.cpp 74 | src/trisnap.cpp 75 | src/pip_util.cpp 76 | src/ClusterPointCloudNode.cpp 77 | src/ContourRegulariserNode.cpp 78 | src/MaxInscribedCircleNode.cpp 79 | src/MeshSimplifyFastQuadNode.cpp 80 | src/MeshClipperNode.cpp 81 | src/MeshSimplify.cpp 82 | src/MeshGridSimplify.cpp 83 | src/Mesh2TriangleCollectionNode.cpp 84 | src/tinsimp.cpp 85 | ) 86 | 87 | target_include_directories(gfp_buildingreconstruction PRIVATE 88 | src 89 | thirdparty/ptinpoly 90 | thirdparty/earcut 91 | thirdparty/region-grower 92 | thirdparty/fast-quadric-mesh-simplification 93 | ${PDAL_INCLUDE_DIRS} 94 | ${GLM_INCLUDE_DIRECTORIES} 95 | ) 96 | target_link_libraries( gfp_buildingreconstruction PRIVATE 97 | geoflow-core 98 | ptinpoly 99 | CGAL::CGAL CGAL::CGAL_Core Eigen3::Eigen 100 | LASlib 101 | ${PDAL_LIBRARIES} 102 | ) 103 | target_link_directories( gfp_buildingreconstruction PRIVATE 104 | ${PDAL_LIBRARY_DIRS} 105 | ) 106 | 107 | if (MSVC) 108 | # collect dll's required for runtime 109 | INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/ DESTINATION bin 110 | FILES_MATCHING 111 | PATTERN "*.dll" 112 | PATTERN "gfp*" EXCLUDE) 113 | endif() 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfp-building-reconstruction 2 | Geoflow plugin for building LoD2 reconstruction from a point cloud 3 | 4 | ## Installation 5 | 6 | ### As part of geoflow-bundle 7 | This the recommended way since it will also include the commonly used plugins and flowcharts to get you started quickly. Also binary pacakges are available. 8 | 9 | ### Building from source 10 | Need to install first [geoflow](https://github.com/geoflow3d/geoflow). 11 | Requires compiler with c++17 support (see https://en.cppreference.com/w/cpp/compiler_support). 12 | 13 | ``` 14 | mkdir build 15 | cd build 16 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local 17 | cmake --build . --parallel 4 --config Release 18 | cmake --build . --target install 19 | ``` 20 | 21 | Dependencies: 22 | 23 | * [LASlib](https://github.com/LAStools/LAStools) 24 | * [CGAL](https://github.com/CGAL/cgal) 25 | -------------------------------------------------------------------------------- /register.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "stepedge_nodes.hpp" 17 | #include "MaxInscribedCircleNode.hpp" 18 | #include "MeshSimplifyFastQuadNode.hpp" 19 | #include "MeshProcessingNodes.hpp" 20 | 21 | using namespace geoflow::nodes::stepedge; 22 | 23 | void register_nodes(geoflow::NodeRegister& node_register) { 24 | node_register.register_node("AlphaShape"); 25 | node_register.register_node("PolygonExtruder"); 26 | node_register.register_node("Arr2LinearRings"); 27 | // node_register.register_node("Extruder"); 28 | node_register.register_node("LinearRingtoRings"); 29 | // node_register.register_node("BuildArrFromRings"); 30 | node_register.register_node("BuildArrFromLines"); 31 | node_register.register_node("DetectLines"); 32 | node_register.register_node("DetectPlanes"); 33 | node_register.register_node("LASInPolygons"); 34 | #ifdef GFP_WITH_PDAL 35 | node_register.register_node("EptInPolygons"); 36 | #endif 37 | node_register.register_node("BuildingSelector"); 38 | node_register.register_node("ClusterLines"); 39 | node_register.register_node("RegulariseLines"); 40 | node_register.register_node("RegulariseRings"); 41 | node_register.register_node("SimplifyPolygon"); 42 | node_register.register_node("Ring2Segments"); 43 | node_register.register_node("PrintResult"); 44 | node_register.register_node("PolygonGrower"); 45 | node_register.register_node("PlaneIntersect"); 46 | node_register.register_node("OptimiseArrangment"); 47 | node_register.register_node("OptimiseArrangmentGrid"); 48 | node_register.register_node("ArrDissolve"); 49 | node_register.register_node("PC2MeshQuality"); 50 | node_register.register_node("PCRasterise"); 51 | node_register.register_node("SegmentRasterise"); 52 | node_register.register_node("PolygonUnion"); 53 | node_register.register_node("Filter25D"); 54 | node_register.register_node("ArrExtruder"); 55 | node_register.register_node("LOD1Extruder"); 56 | node_register.register_node("PolygonTriangulator"); 57 | node_register.register_node("FacesSelector"); 58 | node_register.register_node("AttributeTester"); 59 | node_register.register_node("SkipReplaceTester"); 60 | node_register.register_node("AttrRingsSelector"); 61 | node_register.register_node("PolygonOffsetter"); 62 | node_register.register_node("Arr2LinearRingsDebug"); 63 | node_register.register_node("DataCoverageCalc"); 64 | node_register.register_node("SnapRound"); 65 | node_register.register_node("TriSnap"); 66 | node_register.register_node("BuildingRasterise"); 67 | node_register.register_node("PointCloudMerger"); 68 | node_register.register_node("PC2PCDistancesCalculator"); 69 | node_register.register_node("SegmentExtend"); 70 | node_register.register_node("PCFilter"); 71 | node_register.register_node("PlaneIntersectAll"); 72 | node_register.register_node("RasterMerger"); 73 | node_register.register_node("RoofPartitionRasterise"); 74 | node_register.register_node("ClusterPointCloud"); 75 | node_register.register_node("ContourRegulariser"); 76 | node_register.register_node("GridTiler"); 77 | node_register.register_node("MTC2MM"); 78 | node_register.register_node("MaxInscribedCircle"); 79 | node_register.register_node("MeshSimplifyFastQuad"); 80 | node_register.register_node("MeshSimplify"); 81 | node_register.register_node("SurfaceMesh2OFF"); 82 | node_register.register_node("MeshGridSimplify"); 83 | node_register.register_node("MeshClipper"); 84 | node_register.register_node("Mesh2TriangleCollection"); 85 | node_register.register_node("Mesh2CGALSurfaceMesh"); 86 | node_register.register_node("RoofPartition3DBAGRasterise"); 87 | node_register.register_node("MeshSimplify2D"); 88 | } -------------------------------------------------------------------------------- /src/Alpha_expansion_MaxFlow_tag.h: -------------------------------------------------------------------------------- 1 | #ifndef CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_MAXFLOW_H 2 | // Copyright (c) 2014 GeometryFactory Sarl (France). 3 | // All rights reserved. 4 | // 5 | // This file is part of CGAL (www.cgal.org). 6 | // You can redistribute it and/or modify it under the terms of the GNU 7 | // General Public License as published by the Free Software Foundation, 8 | // either version 3 of the License, or (at your option) any later version. 9 | // 10 | // Licensees holding a valid commercial license may use this file in 11 | // accordance with the commercial license agreement provided with the software. 12 | // 13 | // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 14 | // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 15 | // 16 | // $URL$ 17 | // $Id$ 18 | // SPDX-License-Identifier: GPL-3.0+ 19 | // 20 | // Author(s) : Ilker O. Yaz, Simon Giraudot 21 | 22 | #define CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_MAXFLOW_H 23 | 24 | #include 25 | 26 | /// \cond SKIP_IN_MANUAL 27 | 28 | #include "alpha_expansion_graphcut.h" 29 | 30 | namespace MaxFlow 31 | { 32 | #include "graph.h" 33 | } 34 | 35 | 36 | namespace CGAL 37 | { 38 | 39 | /** 40 | * @brief Implements alpha-expansion graph cut algorithm. 41 | * 42 | * For underlying max-flow algorithm, it uses the MAXFLOW software implemented by Boykov & Kolmogorov. 43 | * Also no pre-allocation is made. 44 | */ 45 | class Alpha_expansion_MaxFlow_tag 46 | { 47 | public: 48 | 49 | typedef MaxFlow::Graph::node_id Vertex_descriptor; 50 | 51 | private: 52 | 53 | MaxFlow::Graph graph; 54 | 55 | public: 56 | 57 | void clear_graph() 58 | { 59 | graph = MaxFlow::Graph(); 60 | } 61 | 62 | Vertex_descriptor add_vertex() 63 | { 64 | return graph.add_node(); 65 | } 66 | 67 | void add_tweight (Vertex_descriptor& v, double w1, double w2) 68 | { 69 | graph.add_tweights(v, w1, w2); 70 | } 71 | 72 | void init_vertices() 73 | { 74 | } 75 | 76 | double max_flow() 77 | { 78 | return graph.maxflow(); 79 | } 80 | 81 | template 82 | void update(VertexLabelMap vertex_label_map, 83 | const std::vector& inserted_vertices, 84 | InputVertexDescriptor vd, 85 | std::size_t vertex_i, 86 | std::size_t alpha) 87 | { 88 | if(get(vertex_label_map, vd) != alpha 89 | && graph.what_segment(inserted_vertices[vertex_i]) == MaxFlow::Graph::SINK) 90 | put(vertex_label_map, vd, alpha); 91 | } 92 | 93 | void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2) 94 | { 95 | graph.add_edge(v1, v2, w1, w2); 96 | } 97 | }; 98 | 99 | }//namespace CGAL 100 | 101 | /// \endcond 102 | 103 | #endif //CGAL_BOOST_GRAPH_ALPHA_EXPANSION_MAXFLOW_TAG_H 104 | -------------------------------------------------------------------------------- /src/ClusterPointCloudNode.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "stepedge_nodes.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | namespace geoflow::nodes::stepedge { 31 | 32 | void ClusterPointCloudNode::process() { 33 | using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; 34 | using Point_3 = Kernel::Point_3; 35 | using Point_set = CGAL::Point_set_3; 36 | 37 | 38 | auto& points = input("points").get(); 39 | 40 | Point_set point_set; 41 | 42 | for(auto& p : points) { 43 | if (flatten) { 44 | point_set.insert(Point_3(p[0], p[1], 0)); 45 | } else { 46 | point_set.insert(Point_3(p[0], p[1], p[2])); 47 | } 48 | } 49 | 50 | // Add a cluster map 51 | Point_set::Property_map cluster_map = point_set.add_property_map("cluster", -1).first; 52 | 53 | // Compute average spacing 54 | // double spacing = CGAL::compute_average_spacing (point_set, 12); 55 | // std::cerr << "Spacing = " << spacing << std::endl; 56 | 57 | // Adjacencies stored in vector 58 | std::vector > adjacencies; 59 | // Compute clusters 60 | CGAL::Real_timer t; 61 | t.start(); 62 | // unsigned int k=15; 63 | std::size_t nb_clusters 64 | = CGAL::cluster_point_set(point_set, cluster_map, 65 | point_set.parameters().neighbor_radius(spacing) 66 | .adjacencies(std::back_inserter(adjacencies))); 67 | t.stop(); 68 | std::cerr << "Found " << nb_clusters << " clusters with " << adjacencies.size() 69 | << " adjacencies in " << t.time() << " seconds" << std::endl; 70 | 71 | // Output a colored PLY file 72 | Point_set::Property_map red = point_set.add_property_map("red", 0).first; 73 | Point_set::Property_map green = point_set.add_property_map("green", 0).first; 74 | Point_set::Property_map blue = point_set.add_property_map("blue", 0).first; 75 | vec1i cluster_idx(points.size()); 76 | size_t i=0; 77 | IndexedPlanesWithPoints pts_per_roofplane; 78 | for(Point_set::const_iterator it = point_set.begin(); 79 | it != point_set.end(); ++ it) 80 | { 81 | pts_per_roofplane[cluster_map[i]].second.push_back( 82 | // point_set.point(*it) 83 | Point_3(points[i][0], points[i][1], points[i][2]) 84 | ); 85 | // One color per cluster 86 | cluster_idx[i] = cluster_map[i]; 87 | CGAL::Random rand (cluster_map[i]); 88 | red[i] = rand.get_int(64, 192); 89 | green[i] = rand.get_int(64, 192); 90 | blue[i] = rand.get_int(64, 192); 91 | ++i; 92 | } 93 | std::ofstream ofile("out.ply", std::ios_base::binary); 94 | CGAL::IO::set_binary_mode(ofile); 95 | ofile << point_set; 96 | 97 | output("cluster_id").set(cluster_idx); 98 | output("pts_per_roofplane").set(pts_per_roofplane); 99 | 100 | } 101 | } -------------------------------------------------------------------------------- /src/ContourRegulariserNode.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "stepedge_nodes.hpp" 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | namespace geoflow::nodes::stepedge { 25 | 26 | void ContourRegulariserNode::process() { 27 | // Typedefs. 28 | using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; 29 | using FT = typename Kernel::FT; 30 | using Point_2 = typename Kernel::Point_2; 31 | using Contour = std::vector; 32 | using Contour_directions = 33 | CGAL::Shape_regularization::Contours::Multiple_directions_2; 34 | 35 | 36 | auto& polygons = vector_input("polygons"); 37 | auto& opolygons = vector_output("regularised_polygons"); 38 | 39 | // Set parameters. 40 | const FT min_length_2 = FT(min_length); 41 | const FT max_angle_2 = FT(max_angle); 42 | const FT max_offset_2 = FT(max_offset); 43 | 44 | for (size_t i = 0; i < polygons.size(); ++i) { 45 | std::vector contour; 46 | auto& polygon = polygons.get(i); 47 | for (auto& p : polygon) { 48 | contour.push_back(Point_2(p[0], p[1])); 49 | } 50 | 51 | // Regularize. 52 | const bool is_closed = true; 53 | Contour_directions directions( 54 | contour, is_closed, CGAL::parameters:: 55 | minimum_length(min_length_2).maximum_angle(max_angle_2)); 56 | std::vector regularized; 57 | CGAL::Shape_regularization::Contours::regularize_closed_contour( 58 | contour, directions, std::back_inserter(regularized), 59 | CGAL::parameters::maximum_offset(max_offset_2)); 60 | std::cout << "* number of directions = " << 61 | directions.number_of_directions() << std::endl; 62 | 63 | LinearRing opoly; 64 | for (auto& point : regularized) { 65 | opoly.push_back( 66 | { 67 | float(point.x()), 68 | float(point.y()), 69 | 0 70 | } 71 | ); 72 | } 73 | opolygons.push_back(opoly); 74 | } 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /src/MaxInscribedCircleNode.cpp: -------------------------------------------------------------------------------- 1 | #include "MaxInscribedCircleNode.hpp" 2 | 3 | // includes for defining the Voronoi diagram adaptor 4 | #include 5 | #include 6 | // #include 7 | // #include 8 | // #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | // #include 16 | 17 | #include 18 | 19 | #include "ptinpoly.h" 20 | 21 | static const double PI = 3.141592653589793238462643383279502884; 22 | 23 | namespace geoflow::nodes::stepedge { 24 | typedef CGAL::Exact_predicates_inexact_constructions_kernel K; 25 | typedef CGAL::Delaunay_triangulation_2 Triangulation; 26 | typedef Triangulation::Edge_iterator Edge_iterator; 27 | typedef Triangulation::Point Point; 28 | typedef CGAL::Polygon_2 Polygon; 29 | typedef CGAL::Polygon_with_holes_2 Polygon_with_holes; 30 | 31 | void insert_edges(Triangulation& t, const Polygon& polygon, const float& interval) { 32 | for (auto ei = polygon.edges_begin(); ei != polygon.edges_end(); ++ei) { 33 | auto e_l = CGAL::sqrt(ei->squared_length()); 34 | auto e_v = ei->to_vector()/e_l; 35 | auto n = std::ceil(e_l / interval); 36 | auto s = ei->source(); 37 | t.insert(s); 38 | for(size_t i=0; i hole_gridsets; 49 | int Grid_Resolution = 20; 50 | 51 | pGridSet build_grid(const Polygon& ring) { 52 | 53 | int size = ring.size(); 54 | std::vector pgon; 55 | for (auto pi = ring.vertices_begin(); pi != ring.vertices_end(); ++pi) { 56 | pgon.push_back(new Pipoint{ pi->x(),pi->y() }); 57 | } 58 | pGridSet grid_set = new GridSet(); 59 | // skip last point in the ring, ie the repetition of the first vertex 60 | GridSetup(&pgon[0], pgon.size(), Grid_Resolution, grid_set); 61 | for (int i = 0; i < size; i++) { 62 | delete pgon[i]; 63 | } 64 | return grid_set; 65 | } 66 | 67 | public: 68 | GridPIPTester(const Polygon_with_holes& polygon) { 69 | ext_gridset = build_grid(polygon.outer_boundary()); 70 | for (auto& hole : polygon.holes()) { 71 | hole_gridsets.push_back(build_grid(hole)); 72 | } 73 | } 74 | ~GridPIPTester() { 75 | delete ext_gridset; 76 | for (auto& h : hole_gridsets) { 77 | delete h; 78 | } 79 | } 80 | 81 | bool test(const Point& p) { 82 | pPipoint pipoint = new Pipoint{p.x(),p.y()}; 83 | bool inside = GridTest(ext_gridset, pipoint); 84 | if (inside) { 85 | for (auto& hole_gridset : hole_gridsets) { 86 | inside = inside && !GridTest(hole_gridset, pipoint); 87 | if (!inside) break; 88 | } 89 | } 90 | delete pipoint; 91 | return inside; 92 | } 93 | 94 | }; 95 | 96 | 97 | void MaxInscribedCircleNode::process() { 98 | std::clock_t c_start = std::clock(); // CPU time 99 | 100 | // build grid 101 | 102 | // build VD/DT 103 | Triangulation t; 104 | 105 | // insert points point cloud 106 | auto& pointcloud = input("pointcloud").get(); 107 | for (auto& p : pointcloud) { 108 | t.insert(Point(p[0], p[1])); 109 | } 110 | 111 | // insert pts on footprint boundary 112 | auto& lr = input("polygon").get(); 113 | 114 | Polygon poly2; 115 | for (auto& p : lr) { 116 | poly2.push_back(Point(p[0], p[1])); 117 | } 118 | std::vector holes; 119 | for (auto& lr_hole : lr.interior_rings()) { 120 | Polygon hole; 121 | for (auto& p : lr_hole) { 122 | hole.push_back(Point(p[0], p[1])); 123 | } 124 | holes.push_back(hole); 125 | } 126 | auto polygon = Polygon_with_holes(poly2, holes.begin(), holes.end()); 127 | 128 | // double l = 0; 129 | insert_edges(t, polygon.outer_boundary(), polygon_densify_); 130 | for (auto& hole : polygon.holes()) { 131 | insert_edges(t, hole, polygon_densify_); 132 | } 133 | // build gridset for point in polygon checks 134 | auto pip_tester = GridPIPTester(polygon); 135 | 136 | // std::cout << 1000.0 * (std::clock()-c_start) / CLOCKS_PER_SEC << "ms 1\n"; 137 | 138 | // find VD node with largest circle 139 | double r_max = 0; 140 | Point c_max; 141 | for(const auto& face : t.finite_face_handles()) { 142 | // get the voronoi node 143 | auto c = t.dual(face); 144 | // check it is inside footprint polygon 145 | if(pip_tester.test(c)) { 146 | for(size_t i=0; i<3; ++i) { 147 | auto r = CGAL::squared_distance(c, face->vertex(i)->point()); 148 | if (r>r_max) { 149 | r_max = r; 150 | c_max = c; 151 | } 152 | } 153 | } 154 | } 155 | r_max = std::sqrt(r_max); 156 | // std::cout << 1000.0 * (std::clock()-c_start) / CLOCKS_PER_SEC << "ms 1\n"; 157 | 158 | // std::cout << "Max radius: " << r_max << std::endl; 159 | // std::cout << "Max radius center: " << c_max << std::endl; 160 | 161 | LinearRing circle; 162 | const double angle_step = PI/5; 163 | for (float a=0; a<2*PI; a+=angle_step) { 164 | circle.push_back({ 165 | float(c_max.x() + std::cos(a)*r_max), 166 | float(c_max.y() + std::sin(a)*r_max), 167 | 0 168 | }); 169 | } 170 | 171 | PointCollection vd_pts; 172 | for(const auto& vertex : t.finite_vertex_handles()) { 173 | vd_pts.push_back( 174 | arr3f{ 175 | float(vertex->point().x()), 176 | float(vertex->point().y()), 177 | 0 178 | } 179 | ); 180 | } 181 | 182 | // PointCollection mc; mc.push_back({float(c_max.x()), float(c_max.y()), 0}); 183 | output("vd_pts").set(vd_pts); 184 | output("max_circle").set(circle); 185 | output("max_diameter").set(float(2*r_max)); 186 | } 187 | 188 | } -------------------------------------------------------------------------------- /src/MaxInscribedCircleNode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace geoflow::nodes::stepedge { 6 | 7 | class MaxInscribedCircleNode:public Node { 8 | float polygon_densify_ = 0.5; 9 | 10 | public: 11 | using Node::Node; 12 | void init() override { 13 | add_input("polygon", typeid(LinearRing)); 14 | add_input("pointcloud", typeid(PointCollection)); 15 | 16 | add_output("max_diameter", typeid(float)); 17 | add_output("max_circle", typeid(LinearRing)); 18 | add_output("vd_pts", typeid(PointCollection)); 19 | 20 | add_param(ParamFloat(polygon_densify_, "polygon_densify", "Densify polygon edges using this threshold.")); 21 | } 22 | void process() override; 23 | }; 24 | 25 | } -------------------------------------------------------------------------------- /src/Mesh2TriangleCollectionNode.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshProcessingNodes.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace geoflow::nodes::stepedge { 13 | 14 | void Mesh2TriangleCollectionNode::process() { 15 | 16 | typedef SurfaceMesh::Vertex_index VertexIndex; 17 | typedef SurfaceMesh::Face_index FaceIndex; 18 | namespace PMP = CGAL::Polygon_mesh_processing; 19 | 20 | auto smesh = input("cgal_surface_mesh").get(); 21 | 22 | if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); 23 | 24 | // normal computation: 25 | // https://doc.cgal.org/latest/Polygon_mesh_processing/Polygon_mesh_processing_2compute_normals_example_8cpp-example.html#a4 26 | // auto vnormals = smesh.add_property_map("v:normals", CGAL::NULL_VECTOR).first; 27 | auto fnormals = smesh.add_property_map("f:normals", CGAL::NULL_VECTOR).first; 28 | PMP::compute_face_normals(smesh, fnormals); 29 | // PMP::compute_normals(smesh, vnormals, fnormals); 30 | // std::cout << "Vertex normals :" << std::endl; 31 | // for(VertexIndex vd: vertices(smesh)) 32 | // std::cout << vnormals[vd] << std::endl; 33 | // std::cout << "Face normals :" << std::endl; 34 | // for(FaceIndex fd: faces(smesh)) 35 | // std::cout << fnormals[fd] << std::endl; 36 | 37 | TriangleCollection triangleCollection; 38 | vec3f normals; 39 | for (auto f : smesh.faces()) { 40 | Triangle t; 41 | unsigned i = 0; 42 | 43 | for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 44 | auto& p = smesh.point(vi); 45 | t[i++] = arr3f{ 46 | (float) p.x(), 47 | (float) p.y(), 48 | (float) p.z() 49 | }; 50 | } 51 | auto& n = fnormals[f]; 52 | normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); 53 | normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); 54 | normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); 55 | triangleCollection.push_back(t); 56 | } 57 | 58 | output("triangles").set(triangleCollection); 59 | output("normals").set(normals); 60 | } 61 | 62 | void Mesh2CGALSurfaceMeshNode::process() { 63 | typedef SurfaceMesh::Vertex_index VertexIndex; 64 | namespace PMP = CGAL::Polygon_mesh_processing; 65 | 66 | auto gfmesh = input("mesh").get(); 67 | 68 | SurfaceMesh smesh; 69 | { 70 | std::map vertex_map; 71 | std::set vertex_set; 72 | std::vector points; 73 | for (const auto &ring : gfmesh.get_polygons()) 74 | { 75 | for (auto &v : ring) 76 | { 77 | auto [it, did_insert] = vertex_set.insert(v); 78 | if (did_insert) 79 | { 80 | vertex_map[v] = points.size(); 81 | points.push_back(K::Point_3(v[0],v[1],v[2])); 82 | } 83 | } 84 | } 85 | 86 | // First build a polygon soup 87 | std::vector > polygons; 88 | for (auto& ring : gfmesh.get_polygons()) { 89 | std::vector rindices; 90 | rindices.reserve(ring.size()); 91 | for(auto& p : ring) { 92 | rindices.push_back(vertex_map[p]); 93 | } 94 | polygons.push_back(rindices); 95 | } 96 | 97 | // Do CGAL mesh repair magic, see https://github.com/CGAL/cgal/issues/7529 98 | 99 | // remove all kinds of typical issues in a polygon soup (degenerate polygons, isolated points, etc.) 100 | CGAL::Polygon_mesh_processing::repair_polygon_soup(points, polygons); 101 | 102 | // duplicate non-manifold edges (but does not re-orient faces) 103 | CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup(points, polygons); 104 | 105 | CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, smesh); 106 | 107 | if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); 108 | 109 | // this prevents potentially getting stuck in infinite loop (see https://github.com/CGAL/cgal/issues/7529) 110 | CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices( smesh ); 111 | 112 | // this is not needed since PMP::repair_polygon_soup() will perform this repair 113 | // CGAL::Polygon_mesh_processing::remove_isolated_vertices( smesh ); 114 | 115 | } 116 | 117 | 118 | 119 | output("cgal_surface_mesh").set(smesh); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /src/MeshClipperNode.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshProcessingNodes.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace geoflow::nodes::stepedge { 10 | 11 | Triangle create_gf_triangle(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { 12 | Triangle t; 13 | t[0] = arr3f{ 14 | (float) p0.x(), 15 | (float) p0.y(), 16 | (float) p0.z() 17 | }; 18 | t[1] = arr3f{ 19 | (float) p1.x(), 20 | (float) p1.y(), 21 | (float) p1.z() 22 | }; 23 | t[2] = arr3f{ 24 | (float) p2.x(), 25 | (float) p2.y(), 26 | (float) p2.z() 27 | }; 28 | return t; 29 | } 30 | 31 | struct MeshBuilder { 32 | std::map vertex_map; 33 | std::set vertex_set; 34 | std::vector points; 35 | 36 | std::vector > polygons; 37 | 38 | void add_point(const K::Point_3& p) { 39 | auto [it, did_insert] = vertex_set.insert(p); 40 | if (did_insert) 41 | { 42 | vertex_map[p] = points.size(); 43 | points.push_back(p); 44 | } 45 | } 46 | 47 | void add_triangle(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { 48 | add_point(p0); 49 | add_point(p1); 50 | add_point(p2); 51 | 52 | // First build a polygon soup 53 | std::vector rindices; 54 | rindices.reserve(3); 55 | rindices.push_back(vertex_map[p0]); 56 | rindices.push_back(vertex_map[p1]); 57 | rindices.push_back(vertex_map[p2]); 58 | polygons.push_back(rindices); 59 | } 60 | 61 | void get_mesh(SurfaceMesh& smesh) { 62 | smesh.clear(); 63 | CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, smesh); 64 | } 65 | 66 | }; 67 | 68 | void MeshClipperNode::process() { 69 | 70 | typedef SurfaceMesh::Vertex_index VertexIndex; 71 | typedef SurfaceMesh::Face_index FaceIndex; 72 | typedef K::Triangle_3 Triangle_3; 73 | typedef K::Point_3 Point_3; 74 | typedef std::vector Poly_3; 75 | namespace PMP = CGAL::Polygon_mesh_processing; 76 | 77 | auto smesh = input("mesh").get(); 78 | 79 | // clip 80 | if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); 81 | 82 | auto& gfbox = input("bbox").get(); 83 | const auto& pmin = gfbox.min(); 84 | const auto& pmax = gfbox.max(); 85 | K::Iso_cuboid_3 cuboid( 86 | Point_3(pmin[0], pmin[1], pmin[2]), 87 | Point_3(pmax[0], pmax[1], pmax[2]) 88 | ); 89 | 90 | if (!skip_clip_) { 91 | if (cgal_clip_) { // use cgal mesh_processing 92 | if (!PMP::does_self_intersect(smesh)) { 93 | PMP::clip( 94 | smesh, 95 | cuboid 96 | ); 97 | } 98 | } else { 99 | MeshBuilder mb; 100 | CGAL::Vertex_around_face_iterator vit, vend; 101 | for (auto f : smesh.faces()) { 102 | boost::tie(vit, vend) = CGAL::vertices_around_face(smesh.halfedge(f), smesh); 103 | auto end = vit; 104 | K::Point_3 p1 = smesh.point(*vit); vit++; 105 | K::Point_3 p2 = smesh.point(*vit); vit++; 106 | K::Point_3 p3 = smesh.point(*vit); 107 | K::Triangle_3 triangle( 108 | p1, 109 | p2, 110 | p3 111 | ); 112 | 113 | const auto result = CGAL::intersection(triangle, cuboid); 114 | if (result) { 115 | // auto& n = fnormals[f]; 116 | if (const Triangle_3* tri = boost::get(&*result)) { 117 | mb.add_triangle( 118 | tri->vertex(0), 119 | tri->vertex(1), 120 | tri->vertex(2) 121 | ); 122 | } else if (const Poly_3* poly = boost::get(&*result)) { 123 | // std::cout << "polygon! [" << poly->size() << std::endl; 124 | for(unsigned i=0; isize()-2; ++i) { 125 | // std::cout << i << " "; 126 | mb.add_triangle( 127 | (*poly)[0], 128 | (*poly)[i+1], 129 | (*poly)[i+2] 130 | ); 131 | } 132 | 133 | } 134 | } 135 | } 136 | mb.get_mesh(smesh); 137 | } 138 | } 139 | 140 | auto fnormals = smesh.add_property_map("f:normals", CGAL::NULL_VECTOR).first; 141 | auto vnormals = smesh.add_property_map("v:normals", CGAL::NULL_VECTOR).first; 142 | 143 | if (smooth_normals_) { 144 | PMP::compute_vertex_normals(smesh, vnormals); 145 | } else { 146 | PMP::compute_face_normals(smesh, fnormals); 147 | } 148 | 149 | // convert to triangleCollection 150 | TriangleCollection triangleCollection; 151 | vec3f normals; 152 | for (auto f : smesh.faces()) { 153 | Triangle t; 154 | 155 | unsigned i = 0; 156 | for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 157 | if(i==3) { 158 | std::cout << "WARNING: skipping triangulated SurfaceMesh face with more than 3 vertices\n"; 159 | continue; 160 | } 161 | auto& p = smesh.point(vi); 162 | t[i++] = arr3f{ 163 | (float) p.x(), 164 | (float) p.y(), 165 | (float) p.z() 166 | }; 167 | // if (!smesh.is_border(vi)) { 168 | if (smooth_normals_) { 169 | normals.push_back(arr3f{ 170 | float(vnormals[vi].x()), 171 | float(vnormals[vi].y()), 172 | float(vnormals[vi].z()) }); 173 | } else { 174 | normals.push_back(arr3f{ 175 | float(fnormals[f].x()), 176 | float(fnormals[f].y()), 177 | float(fnormals[f].z()) }); 178 | } 179 | } 180 | // if (!smooth_normals_) { 181 | 182 | 183 | // normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); 184 | // normals.push_back(arr3f{ float(n.x()), float(n.y()), float(n.z()) }); 185 | // } 186 | triangleCollection.push_back(t); 187 | } 188 | 189 | output("triangles").set(triangleCollection); 190 | output("normals").set(normals); 191 | // output("cgal_surface_mesh").set(smesh); 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/MeshGridSimplify.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshProcessingNodes.hpp" 2 | #include 3 | 4 | namespace geoflow::nodes::stepedge { 5 | 6 | struct Grid3D { 7 | Box box; 8 | // bool x = true; 9 | unsigned dim[3]; 10 | float cell_size_xy, cell_size_z; 11 | Grid3D(Box& box, float& cell_size_xy, float& cell_size_z) : box(box), cell_size_xy(cell_size_xy), cell_size_z(cell_size_z) { 12 | dim[0] = (box.max()[0]-box.min()[0])/cell_size_xy + 1; 13 | dim[1] = (box.max()[1]-box.min()[1])/cell_size_xy + 1; 14 | dim[2] = (box.max()[2]-box.min()[2])/cell_size_z + 1; 15 | // std::cout << "dim[0]: " << dim[0] << "[" << box.min()[0] << ", " << box.max()[0] << "]" << std::endl; 16 | // std::cout << "dim[1]: " << dim[1] << "[" << box.min()[1] << ", " << box.max()[1] << "]" << std::endl; 17 | // std::cout << "dim[2]: " << dim[2] << "[" << box.min()[2] << ", " << box.max()[2] << "]" << std::endl; 18 | } 19 | 20 | unsigned getCellCoordinate(const K::Point_3& p) { 21 | unsigned c = static_cast( floor((p.x()-box.min()[0]) / cell_size_xy) ); 22 | unsigned r = static_cast( floor((p.y()-box.min()[1]) / cell_size_xy) ); 23 | unsigned s = static_cast( floor((p.z()-box.min()[2]) / cell_size_z) ); 24 | 25 | return s * dim[0]*dim[1] + r * dim[0] + c; 26 | } 27 | 28 | arr3f getCellCenterPoint(const unsigned& cell_coordinate) { 29 | unsigned slice = cell_coordinate / (dim[0]*dim[1]); 30 | unsigned rest = cell_coordinate % (dim[0]*dim[1]); 31 | unsigned row = rest / dim[0]; 32 | unsigned col = rest % dim[0]; 33 | arr3f p; 34 | p[0] = box.min()[0] + col*cell_size_xy + cell_size_xy/2; 35 | p[1] = box.min()[1] + row*cell_size_xy + cell_size_xy/2; 36 | p[2] = box.min()[2] + slice*cell_size_z + cell_size_z/2; 37 | // if (x) { 38 | // std::cout << "cell_coordinate: " << cell_coordinate << std::endl; 39 | // std::cout << "slice: " << slice << std::endl; 40 | // std::cout << "row: " << row << std::endl; 41 | // std::cout << "col: " << col << std::endl; 42 | // x=false; 43 | // } 44 | return p; 45 | } 46 | }; 47 | struct Grid2D { 48 | struct SumCount { 49 | float sum; 50 | unsigned cnt; 51 | SumCount() : sum(0), cnt(0) {}; 52 | SumCount(float val) : sum(val), cnt(1) {}; 53 | void add(float val) { sum+=val; ++cnt; }; 54 | float get_avg() { return sum/cnt; }; 55 | }; 56 | Box box; 57 | // bool x = true; 58 | unsigned dim[2]; 59 | 60 | std::unordered_map elevations; 61 | float cell_size_xy, cell_size_z; 62 | Grid2D(Box& box, float& cell_size_xy, float& cell_size_z) : box(box), cell_size_xy(cell_size_xy), cell_size_z(cell_size_z) { 63 | auto dx = fmod(box.min()[0], cell_size_xy); 64 | auto dy = fmod(box.min()[1], cell_size_xy); 65 | box.add(arr3f{box.min()[0]-dx, box.min()[1]-dy, box.min()[2]}); 66 | dim[0] = (box.max()[0]-box.min()[0])/cell_size_xy + 1; 67 | dim[1] = (box.max()[1]-box.min()[1])/cell_size_xy + 1; 68 | // dim[2] = (box.max()[2]-box.min()[2])/cell_size_z + 1; 69 | // std::cout << "dim[0]: " << dim[0] << "[" << box.min()[0] << ", " << box.max()[0] << "]" << std::endl; 70 | // std::cout << "dim[1]: " << dim[1] << "[" << box.min()[1] << ", " << box.max()[1] << "]" << std::endl; 71 | // std::cout << "dim[2]: " << dim[2] << "[" << box.min()[2] << ", " << box.max()[2] << "]" << std::endl; 72 | } 73 | 74 | unsigned getCellCoordinate(const K::Point_3& p) { 75 | unsigned c = static_cast( floor((p.x()-box.min()[0]) / cell_size_xy) ); 76 | unsigned r = static_cast( floor((p.y()-box.min()[1]) / cell_size_xy) ); 77 | unsigned coord = r * dim[0] + c; 78 | if(elevations.count(coord)) { 79 | elevations[coord].add(p.z()); 80 | } else { 81 | elevations[coord] = SumCount(p.z()); 82 | } 83 | return coord; 84 | } 85 | 86 | arr3f getCellCenterPoint(const unsigned& cell_coordinate) { 87 | unsigned row = cell_coordinate / dim[0]; 88 | unsigned col = cell_coordinate % dim[0]; 89 | arr3f p; 90 | p[0] = box.min()[0] + col*cell_size_xy + cell_size_xy/2; 91 | p[1] = box.min()[1] + row*cell_size_xy + cell_size_xy/2; 92 | p[2] = elevations[cell_coordinate].get_avg(); 93 | // if (x) { 94 | // std::cout << "cell_coordinate: " << cell_coordinate << std::endl; 95 | // std::cout << "slice: " << slice << std::endl; 96 | // std::cout << "row: " << row << std::endl; 97 | // std::cout << "col: " << col << std::endl; 98 | // x=false; 99 | // } 100 | return p; 101 | } 102 | }; 103 | 104 | void MeshGridSimplifyNode::process() { 105 | typedef SurfaceMesh::Vertex_index VertexIndex; 106 | namespace PMP = CGAL::Polygon_mesh_processing; 107 | SurfaceMesh smesh; 108 | 109 | // if(input("mesh").is_connected_type(typeid(Mesh))) 110 | auto gfmesh = input("mesh").get(); 111 | 112 | Box box; 113 | { 114 | std::map vertex_map; 115 | std::set vertex_set; 116 | for (const auto &ring : gfmesh.get_polygons()) 117 | { 118 | for (auto &v : ring) 119 | { 120 | auto [it, did_insert] = vertex_set.insert(v); 121 | if (did_insert) 122 | { 123 | vertex_map[v] = smesh.add_vertex(K::Point_3(v[0],v[1],v[2]));; 124 | box.add(v); 125 | } 126 | } 127 | } 128 | 129 | for (auto& ring : gfmesh.get_polygons()) { 130 | std::vector rindices; 131 | rindices.reserve(ring.size()); 132 | for(auto& p : ring) { 133 | rindices.push_back(vertex_map[p]); 134 | } 135 | smesh.add_face(rindices); 136 | } 137 | } 138 | 139 | if(!CGAL::is_triangle_mesh(smesh)) PMP::triangulate_faces(smesh); 140 | 141 | // separate vertices and faces 142 | 143 | // assign vertices to 3D grid cell 144 | // merge vertices in each grid cell, and create a mapping (old vertex indices -> new vertex indices, std::map) 145 | Grid2D G(box, cell_size_xy_, cell_size_z_); 146 | std::unordered_map vertex_map; 147 | for (auto vi : smesh.vertices()) { 148 | unsigned c = G.getCellCoordinate( smesh.point(vi) ); 149 | vertex_map[vi] = c; 150 | } 151 | 152 | // map for each face from old to new vertices and check how many are left, if <3 remove the face] 153 | 154 | std::unordered_map new_mesh_vertices; 155 | SurfaceMesh smesh_new; 156 | { 157 | // struct Cluster { 158 | // float x,y; 159 | // std::vector zs; 160 | // Cluster() : x(0), y(0) {}; 161 | // Cluster(float x, float y) : x(x), y(y) {}; 162 | // }; 163 | std::unordered_map new_vertices; 164 | for (auto f : smesh.faces()) { 165 | for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 166 | auto p = G.getCellCenterPoint(vertex_map[vi]); 167 | new_mesh_vertices[vertex_map[vi]] = smesh_new.add_vertex(K::Point_3(p[0], p[1], p[2])); 168 | } 169 | } 170 | } 171 | { 172 | for (auto f : smesh.faces()) { 173 | std::set face_set; 174 | for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 175 | face_set.insert(vertex_map[vi]); 176 | } 177 | if (face_set.size() == 3) { 178 | std::vector rindices; 179 | rindices.reserve(3); 180 | for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 181 | rindices.push_back(new_mesh_vertices[vertex_map[vi]]); 182 | } 183 | smesh_new.add_face(rindices); 184 | } 185 | } 186 | } 187 | // map for each face from old to new vertices and check how many are left, if <3 remove the face 188 | // TriangleCollection triangleCollection; 189 | // vec3f normals; 190 | // for (auto& f : smesh.faces()) { 191 | // std::set face_set; 192 | // for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 193 | // face_set.insert(vertex_map[vi]); 194 | // } 195 | // if (face_set.size() == 3) { 196 | // Triangle t; 197 | // unsigned i=0; 198 | // for(VertexIndex vi : vertices_around_face(smesh.halfedge(f), smesh)) { 199 | // // auto& p = smesh.point(vi); 200 | // auto p = G.getCellCenterPoint(vertex_map[vi]); 201 | // t[i++] = p; 202 | // } 203 | // triangleCollection.push_back(t); 204 | // } 205 | 206 | // } 207 | 208 | // output("tri").set(triangleCollection); 209 | output("mesh").set(smesh_new); 210 | } 211 | 212 | } -------------------------------------------------------------------------------- /src/MeshProcessingNodes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace geoflow::nodes::stepedge { 11 | typedef CGAL::Simple_cartesian K; 12 | typedef CGAL::Surface_mesh SurfaceMesh; 13 | 14 | class MeshClipperNode:public Node { 15 | bool skip_clip_ = false; 16 | bool cgal_clip_ = false; 17 | bool smooth_normals_ = false; 18 | // float reduce_fraction_ = 0.5; 19 | // float agressiveness_ = 7.0; 20 | 21 | public: 22 | using Node::Node; 23 | void init() override { 24 | add_input("mesh", typeid(SurfaceMesh)); 25 | add_input("bbox", typeid(Box)); 26 | 27 | add_output("cgal_surface_mesh", typeid(SurfaceMesh)); 28 | add_output("normals", typeid(vec3f)); 29 | add_output("triangles", typeid(TriangleCollection)); 30 | 31 | add_param(ParamBool(skip_clip_, "skip_clip", "Skip the clip")); 32 | add_param(ParamBool(cgal_clip_, "cgal_clip", "Use CGAL::Polygon_mesh_processing::clip instead of simpler but more robust triangle by triangle clip.")); 33 | add_param(ParamBool(smooth_normals_, "smooth_normals", "Use use smooth vertex normals instead of flat face normals.")); 34 | // add_param(ParamFloat(reduce_fraction_, "reduce_fraction", "Target reduction in nr of triangles")); 35 | // add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); 36 | // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); 37 | } 38 | void process() override; 39 | }; 40 | 41 | class MeshSimplifyNode:public Node { 42 | float stop_ratio_ = 0.5; 43 | bool border_correction_ = true; 44 | // float agressiveness_ = 7.0; 45 | 46 | public: 47 | using Node::Node; 48 | void init() override { 49 | add_input("cgal_surface_mesh", typeid(SurfaceMesh)); 50 | 51 | add_output("cgal_surface_mesh", typeid(SurfaceMesh)); 52 | 53 | add_param(ParamBool(border_correction_, "border_correction", "Correct ratio for border edges")); 54 | add_param(ParamBoundedFloat(stop_ratio_, 0, 1, "stop_ratio", "Target reduction ratio in nr of edges")); 55 | // add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); 56 | // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); 57 | } 58 | void process() override; 59 | }; 60 | 61 | class MeshSimplify2DNode:public Node { 62 | float error_ = 0.5; 63 | float minpts_ = 0.5; 64 | 65 | public: 66 | using Node::Node; 67 | void init() override { 68 | add_input("cgal_surface_mesh", typeid(SurfaceMesh)); 69 | add_output("cgal_surface_mesh", typeid(SurfaceMesh)); 70 | // add_output("wall_triangles", typeid(TriangleCollection)); 71 | 72 | add_param(ParamBoundedFloat(error_, 0, 5, "error", "Target maximum eror after simplification")); 73 | add_param(ParamBoundedFloat(minpts_, 0, 10, "minpts", "Minimum number of elevation points per m2 inside a polygon")); 74 | } 75 | void process() override; 76 | }; 77 | 78 | class MeshGridSimplifyNode:public Node { 79 | float cell_size_xy_ = 0.5; 80 | float cell_size_z_ = 0.5; 81 | // float agressiveness_ = 7.0; 82 | 83 | public: 84 | using Node::Node; 85 | void init() override { 86 | add_input("mesh", typeid(Mesh)); 87 | add_input("bbox", typeid(Box)); 88 | 89 | // add_output("tri", typeid(TriangleCollection)); 90 | add_output("mesh", typeid(SurfaceMesh)); 91 | 92 | // add_param(ParamBool(flatten, "flatten", "Ignore Z coordinates in clustering")); 93 | add_param(ParamBoundedFloat(cell_size_xy_, 0, 1, "cell_size_xy", "Cellsize for x and y")); 94 | add_param(ParamBoundedFloat(cell_size_z_, 0, 1, "cell_size_z", "Cellsize for z")); 95 | // add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); 96 | // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); 97 | } 98 | void process() override; 99 | }; 100 | 101 | class Mesh2TriangleCollectionNode:public Node { 102 | // bool stop_ratio_; 103 | // float agressiveness_ = 7.0; 104 | 105 | public: 106 | using Node::Node; 107 | void init() override { 108 | add_input("cgal_surface_mesh", typeid(SurfaceMesh)); 109 | 110 | add_output("triangles", typeid(TriangleCollection)); 111 | add_output("normals", typeid(vec3f)); 112 | 113 | // add_param(ParamBool(flatten, "flatten", "Ignore Z coordinates in clustering")); 114 | // add_param(ParamBoundedFloat(stop_ratio_, 0, 1, "stop_ratio", "Target reduction ratio in nr of edges")); 115 | // add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); 116 | // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); 117 | } 118 | void process() override; 119 | }; 120 | class Mesh2CGALSurfaceMeshNode:public Node { 121 | 122 | public: 123 | using Node::Node; 124 | void init() override { 125 | add_input("mesh", typeid(Mesh)); 126 | add_output("cgal_surface_mesh", typeid(SurfaceMesh)); 127 | 128 | // add_param(ParamBool(flatten, "flatten", "Ignore Z coordinates in clustering")); 129 | // add_param(ParamBoundedFloat(stop_ratio_, 0, 1, "stop_ratio", "Target reduction ratio in nr of edges")); 130 | // add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); 131 | // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); 132 | } 133 | void process() override; 134 | }; 135 | class SurfaceMesh2OFFNode:public Node { 136 | std::string filepath_=""; 137 | 138 | public: 139 | using Node::Node; 140 | void init() override { 141 | add_input("cgal_surface_mesh", typeid(SurfaceMesh)); 142 | 143 | add_param(ParamPath(filepath_, "filepath", "File path")); 144 | } 145 | void process() override; 146 | }; 147 | 148 | } -------------------------------------------------------------------------------- /src/MeshSimplify.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshProcessingNodes.hpp" 2 | 3 | // Simplification function 4 | #include 5 | // Midpoint placement policy 6 | #include 7 | //Placement wrapper 8 | #include 9 | // Stop-condition policy 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "tinsimp.hpp" 29 | 30 | namespace geoflow::nodes::stepedge { 31 | 32 | typedef SurfaceMesh::Vertex_index VertexIndex; 33 | typedef SurfaceMesh::Face_index FaceIndex; 34 | 35 | typedef boost::graph_traits::vertex_descriptor vertex_descriptor; 36 | typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; 37 | typedef boost::graph_traits::edge_descriptor edge_descriptor; 38 | namespace SMS = CGAL::Surface_mesh_simplification; 39 | // BGL property map which indicates whether an edge is marked as non-removable 40 | struct Border_is_constrained_edge_map 41 | { 42 | const SurfaceMesh* sm_ptr; 43 | typedef edge_descriptor key_type; 44 | typedef bool value_type; 45 | typedef value_type reference; 46 | typedef boost::readable_property_map_tag category; 47 | Border_is_constrained_edge_map(const SurfaceMesh& sm) : sm_ptr(&sm) {} 48 | friend value_type get(const Border_is_constrained_edge_map& m, const key_type& edge) { 49 | return CGAL::is_border(edge, *m.sm_ptr); 50 | } 51 | }; 52 | // Placement class 53 | typedef SMS::Constrained_placement, 54 | Border_is_constrained_edge_map > Placement; 55 | // namespace d = CGAL::Polygon_mesh_processing; 56 | 57 | struct MeshBuilder { 58 | std::map vertex_map; 59 | std::set vertex_set; 60 | std::vector points; 61 | 62 | std::vector > polygons; 63 | 64 | void add_point(const K::Point_3& p) { 65 | auto [it, did_insert] = vertex_set.insert(p); 66 | if (did_insert) 67 | { 68 | vertex_map[p] = points.size(); 69 | points.push_back(p); 70 | } 71 | } 72 | void add_points(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { 73 | add_point(p0); 74 | add_point(p1); 75 | add_point(p2); 76 | } 77 | 78 | void add_triangle(const K::Point_3& p0, const K::Point_3& p1, const K::Point_3& p2) { 79 | // First build a polygon soup 80 | add_points(p0, p1, p2); 81 | std::vector rindices; 82 | rindices.reserve(3); 83 | rindices.push_back(vertex_map[p0]); 84 | rindices.push_back(vertex_map[p1]); 85 | rindices.push_back(vertex_map[p2]); 86 | polygons.push_back(rindices); 87 | } 88 | 89 | void get_mesh(geoflow::nodes::stepedge::SurfaceMesh& smesh) { 90 | smesh.clear(); 91 | CGAL::Polygon_mesh_processing::repair_polygon_soup(points, polygons); 92 | 93 | // duplicate non-manifold edges (but does not re-orient faces) 94 | CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup(points, polygons); 95 | CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, smesh); 96 | } 97 | 98 | }; 99 | 100 | const K::Vector_3 up(0,0,1); 101 | 102 | bool is_vertical(const K::Point_3& a, const K::Point_3& b, const K::Point_3& c) { 103 | auto n = CGAL::orthogonal_vector(a, b, c); 104 | // check if face is vertical by checking angle of it's normal. Only add constraints for non-vertical faces 105 | // std::cout << CGAL::abs(CGAL::approximate_angle(n, up))<< std::endl; 106 | return (CGAL::abs(90 - CGAL::abs(CGAL::approximate_angle(n, up))) == 0); 107 | } 108 | 109 | void MeshSimplify2DNode::process() { 110 | auto smesh = input("cgal_surface_mesh").get(); 111 | 112 | // !d::does_self_intersect(smesh) 113 | if (error_ > 0){ 114 | tinsimp::CDT t; 115 | 116 | // collect vertical faces that make up the vertical wall 117 | std::vector> wall_triangles; 118 | for (auto face : smesh.faces()) { 119 | std::vector triangle; 120 | for(vertex_descriptor vd : vertices_around_face(smesh.halfedge(face), smesh)){ 121 | triangle.push_back(smesh.point(vd)); 122 | } 123 | if (is_vertical(triangle[0], triangle[1], triangle[2])) { 124 | wall_triangles.push_back(triangle); 125 | CGAL::Euler::remove_face(smesh.halfedge(face), smesh); 126 | } 127 | } 128 | smesh.collect_garbage(); 129 | 130 | for(halfedge_descriptor hd : halfedges(smesh)) 131 | { 132 | if(CGAL::is_border(hd, smesh)) 133 | { 134 | auto a = smesh.point(source(hd, smesh)); 135 | auto b = smesh.point(target(hd, smesh)); 136 | 137 | t.insert_constraint( 138 | tinsimp::Point( 139 | a.x(), 140 | a.y(), 141 | a.z() 142 | ), 143 | tinsimp::Point( 144 | b.x(), 145 | b.y(), 146 | b.z() 147 | ) 148 | ); 149 | } 150 | } 151 | 152 | std::vector zpts; 153 | for(vertex_descriptor vd : smesh.vertices()) 154 | { 155 | if(!CGAL::is_border(vd, smesh)) 156 | { 157 | auto p = smesh.point(vd); 158 | zpts.push_back( 159 | tinsimp::Point( p.x(), p.y(), p.z() ) 160 | ); 161 | } 162 | } 163 | 164 | tinsimp::mark_domains(t); 165 | float sq_area = 0; 166 | for (auto& fh : t.finite_face_handles()) { 167 | if(fh->info().in_domain()) { 168 | sq_area += t.triangle(fh).squared_area(); 169 | } 170 | } 171 | float total_area = std::sqrt(sq_area); 172 | 173 | tinsimp::greedy_insert(t, zpts, error_, minpts_*total_area); 174 | 175 | // reset and recompute nesting levels 176 | for (auto& fh : t.all_face_handles()) { 177 | fh->info().nesting_level = -1; 178 | } 179 | tinsimp::mark_domains(t); 180 | // std::cout << "\nFinished!\n" << r << " edges removed.\n" 181 | // << smesh.number_of_edges() << " final edges.\n"; 182 | 183 | smesh.clear(); 184 | MeshBuilder mb; 185 | for (auto& fh : t.finite_face_handles()) { 186 | if(fh->info().in_domain()) { 187 | mb.add_triangle( 188 | K::Point_3( 189 | fh->vertex(0)->point().x(), 190 | fh->vertex(0)->point().y(), 191 | fh->vertex(0)->point().z() 192 | ), 193 | K::Point_3( 194 | fh->vertex(1)->point().x(), 195 | fh->vertex(1)->point().y(), 196 | fh->vertex(1)->point().z() 197 | ), 198 | K::Point_3( 199 | fh->vertex(2)->point().x(), 200 | fh->vertex(2)->point().y(), 201 | fh->vertex(2)->point().z() 202 | ) 203 | ); 204 | } 205 | } 206 | for (auto& triangle : wall_triangles) { 207 | mb.add_triangle(triangle[0], triangle[1], triangle[2]); 208 | } 209 | mb.get_mesh(smesh); 210 | 211 | // TriangleCollection tc; 212 | // for ( auto& t : wall_triangles ) { 213 | // Triangle gft; 214 | // gft[0] = arr3f{static_cast(t[0].x()), static_cast(t[0].y()), static_cast(t[0].z())}; 215 | // gft[1] = arr3f{static_cast(t[1].x()), static_cast(t[1].y()), static_cast(t[1].z())}; 216 | // gft[2] = arr3f{static_cast(t[2].x()), static_cast(t[2].y()), static_cast(t[2].z())}; 217 | // tc.push_back(gft); 218 | // } 219 | // output("wall_triangles").set(tc); 220 | } 221 | output("cgal_surface_mesh").set(smesh); 222 | } 223 | 224 | void MeshSimplifyNode::process() { 225 | auto smesh = input("cgal_surface_mesh").get(); 226 | 227 | // !d::does_self_intersect(smesh) 228 | if (stop_ratio_ < 1.){ 229 | if(!CGAL::is_triangle_mesh(smesh)) CGAL::Polygon_mesh_processing::triangulate_faces(smesh); 230 | 231 | SurfaceMesh::Property_map > constrained_halfedges; 232 | constrained_halfedges = smesh.add_property_map >("h:vertices").first; 233 | size_t n_border=0; 234 | for(halfedge_descriptor hd : halfedges(smesh)) 235 | { 236 | if(CGAL::is_border(hd, smesh)) 237 | { 238 | constrained_halfedges[hd] = std::make_pair(smesh.point(source(hd, smesh)), 239 | smesh.point(target(hd, smesh))); 240 | ++n_border; 241 | } 242 | } 243 | float stop_ratio = stop_ratio_; 244 | if(border_correction_) { 245 | size_t n_all = smesh.number_of_halfedges(); 246 | stop_ratio = ((n_all-n_border) * stop_ratio_) / n_all; 247 | } 248 | // Contract the surface mesh as much as possible. Correct for the border edges that will not be removed 249 | SMS::Count_ratio_stop_predicate stop( stop_ratio ); 250 | Border_is_constrained_edge_map bem(smesh); 251 | 252 | // filter that checks if a placement would invert the normal of a face 253 | SMS::Bounded_normal_change_filter<> filter; 254 | // This the actual call to the simplification algorithm. 255 | // The surface mesh and stop conditions are mandatory arguments. 256 | 257 | int r = SMS::edge_collapse(smesh, stop, 258 | CGAL::parameters::edge_is_constrained_map(bem) 259 | .filter(filter) 260 | .get_placement(Placement(bem))); 261 | // std::cout << "\nFinished!\n" << r << " edges removed.\n" 262 | // << smesh.number_of_edges() << " final edges.\n"; 263 | } 264 | output("cgal_surface_mesh").set(smesh); 265 | } 266 | 267 | void SurfaceMesh2OFFNode::process() { 268 | 269 | auto smesh = input("cgal_surface_mesh").get(); 270 | auto fname = manager.substitute_globals(filepath_); 271 | 272 | std::ofstream ofs; 273 | ofs << std::fixed << std::setprecision(5); 274 | ofs.open(fname); 275 | CGAL::IO::write_OFF(ofs, smesh); 276 | ofs.close(); 277 | } 278 | 279 | } -------------------------------------------------------------------------------- /src/MeshSimplifyFastQuadNode.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshSimplifyFastQuadNode.hpp" 2 | #include "Simplify.h" 3 | 4 | namespace geoflow::nodes::stepedge { 5 | 6 | void MeshSimplifyFastQuadNode::process() { 7 | auto& triangles = input("triangles").get(); 8 | 9 | Simplify::vertices.clear(); 10 | Simplify::triangles.clear(); 11 | 12 | std::map vertex_map; 13 | // std::vector vertex_vec; 14 | { 15 | size_t v_cntr = 0; 16 | std::set vertex_set; 17 | for (auto &triangle : triangles) 18 | { 19 | for (auto &vertex : triangle) 20 | { 21 | auto [it, did_insert] = vertex_set.insert(vertex); 22 | if (did_insert) 23 | { 24 | Simplify::Vertex vo; 25 | vo.p.x = vertex[0]; 26 | vo.p.y = vertex[1]; 27 | vo.p.z = vertex[2]; 28 | vertex_map[vertex] = v_cntr++; 29 | // vertex_vec.push_back(vertex); 30 | Simplify::vertices.push_back(vo); 31 | } 32 | } 33 | } 34 | } 35 | 36 | for (auto& triangle : triangles) { 37 | Simplify::Triangle t; 38 | t.v[0] = vertex_map[triangle[0]]; 39 | t.v[1] = vertex_map[triangle[1]]; 40 | t.v[2] = vertex_map[triangle[2]]; 41 | t.attr = 0; 42 | Simplify::triangles.push_back(t); 43 | } 44 | 45 | int target_count = round((float)Simplify::triangles.size() * reduce_fraction_); 46 | 47 | Simplify::simplify_mesh(target_count, agressiveness_, true); 48 | 49 | TriangleCollection triangles_simplified; 50 | 51 | for (auto& triangle : Simplify::triangles) { 52 | Triangle t; 53 | t[0] = arr3f{ 54 | (float) Simplify::vertices[triangle.v[0]].p.x, 55 | (float) Simplify::vertices[triangle.v[0]].p.y, 56 | (float) Simplify::vertices[triangle.v[0]].p.z 57 | }; 58 | t[1] = arr3f{ 59 | (float) Simplify::vertices[triangle.v[1]].p.x, 60 | (float) Simplify::vertices[triangle.v[1]].p.y, 61 | (float) Simplify::vertices[triangle.v[1]].p.z 62 | }; 63 | t[2] = arr3f{ 64 | (float) Simplify::vertices[triangle.v[2]].p.x, 65 | (float) Simplify::vertices[triangle.v[2]].p.y, 66 | (float) Simplify::vertices[triangle.v[2]].p.z 67 | }; 68 | triangles_simplified.push_back(t); 69 | } 70 | 71 | output("triangles").set(triangles_simplified); 72 | 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/MeshSimplifyFastQuadNode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace geoflow::nodes::stepedge { 6 | 7 | class MeshSimplifyFastQuadNode:public Node { 8 | float reduce_fraction_ = 0.5; 9 | float agressiveness_ = 7.0; 10 | 11 | public: 12 | using Node::Node; 13 | void init() override { 14 | add_input("triangles", typeid(TriangleCollection)); 15 | 16 | add_output("triangles", typeid(TriangleCollection)); 17 | 18 | // add_param(ParamBool(flatten, "flatten", "Ignore Z coordinates in clustering")); 19 | add_param(ParamFloat(reduce_fraction_, "reduce_fraction", "Target reduction in nr of triangles")); 20 | add_param(ParamFloat(agressiveness_, "agressiveness", "Agressiveness")); 21 | // add_param(ParamInt(metrics_normal_k, "metrics_normal_k", "Number of neighbours used for normal estimation")); 22 | } 23 | void process() override; 24 | }; 25 | 26 | } -------------------------------------------------------------------------------- /src/Raster.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Raster.h" 18 | #include 19 | 20 | namespace RasterTools { 21 | 22 | Raster::Raster(double cellsize, double min_x, double max_x, double min_y, double max_y): 23 | cellSize_(cellsize), minx_(min_x), maxx_(max_x), miny_(min_y), maxy_(max_y) 24 | { 25 | dimx_ = (maxx_-minx_)/cellSize_ + 1; 26 | dimy_ = (maxy_-miny_)/cellSize_ + 1; 27 | vals_ = std::make_unique>(); 28 | vals_->resize(dimx_*dimy_); 29 | // counts_ = std::make_unique>(); 30 | // counts_->resize(dimx_*dimy_); 31 | } 32 | Raster::Raster(const Raster& r) 33 | { 34 | cellSize_ = r.cellSize_; 35 | maxx_ = r.maxx_; 36 | minx_ = r.minx_; 37 | maxy_ = r.maxy_; 38 | miny_ = r.miny_; 39 | noDataVal_ = r.noDataVal_; 40 | dimx_ = (maxx_-minx_)/cellSize_ + 1; 41 | dimy_ = (maxy_-miny_)/cellSize_ + 1; 42 | vals_ = std::make_unique>(*r.vals_); 43 | // counts_ = std::make_unique>(*r.counts_); 44 | } 45 | void Raster::operator=(const Raster& r) 46 | { 47 | cellSize_ = r.cellSize_; 48 | maxx_ = r.maxx_; 49 | minx_ = r.minx_; 50 | maxy_ = r.maxy_; 51 | miny_ = r.miny_; 52 | noDataVal_ = r.noDataVal_; 53 | dimx_ = (maxx_-minx_)/cellSize_ + 1; 54 | dimy_ = (maxy_-miny_)/cellSize_ + 1; 55 | vals_ = std::make_unique>(*r.vals_); 56 | // counts_ = std::make_unique>(*r.counts_); 57 | } 58 | 59 | void Raster::prefill_arrays(alg a){ 60 | if (a==MIN) 61 | noDataVal_ = std::numeric_limits::max(); 62 | else 63 | noDataVal_ = -std::numeric_limits::max(); 64 | 65 | std::fill(vals_->begin(), vals_->end(), noDataVal_); 66 | // std::fill(counts_->begin(), counts_->end(), 0); 67 | } 68 | 69 | bool Raster::add_point(double x, double y, double z, alg a) 70 | { 71 | bool first = (*vals_)[getLinearCoord(x,y)]==noDataVal_; 72 | if (a==MIN) { 73 | min(x,y,z); 74 | } else if (a==MAX) { 75 | max(x,y,z); 76 | } 77 | return first; 78 | } 79 | bool Raster::check_point(double x, double y) 80 | { 81 | auto col = getCol(x,y); 82 | if (col >= dimx_ || col < 0) return false; 83 | auto row = getRow(x,y); 84 | if (row >= dimy_ || row < 0) return false; 85 | 86 | return true; 87 | } 88 | 89 | // inline void Raster::avg(double &x, double &y, double &val) 90 | // { 91 | // size_t c = getLinearCoord(x,y); 92 | // (*vals_)[c]= ((*vals_)[c]*(*counts_)[c]+val)/((*counts_)[c]+1); 93 | // ++(*counts_)[c]; 94 | // } 95 | 96 | inline void Raster::min(double &x, double &y, double &val) 97 | { 98 | size_t c = getLinearCoord(x,y); 99 | if ((*vals_)[c]>val) (*vals_)[c] = val; 100 | } 101 | 102 | inline void Raster::max(double &x, double &y, double &val) 103 | { 104 | size_t c = getLinearCoord(x,y); 105 | if ((*vals_)[c] Raster::getColRowCoord(double x, double y) const 115 | { 116 | double r = (y-miny_) / cellSize_; 117 | double c = (x-minx_) / cellSize_; 118 | 119 | return {c,r}; 120 | } 121 | 122 | size_t Raster::getRow(double x, double y) const 123 | { 124 | return static_cast( floor((y-miny_) / cellSize_) ); 125 | } 126 | size_t Raster::getCol(double x, double y) const 127 | { 128 | return static_cast( floor((x-minx_) / cellSize_) ); 129 | } 130 | 131 | size_t Raster::getLinearCoord(double x, double y) const 132 | { 133 | size_t r = static_cast( floor((y-miny_) / cellSize_) ); 134 | size_t c = static_cast( floor((x-minx_) / cellSize_) ); 135 | 136 | return r * dimx_ + c; 137 | } 138 | 139 | size_t Raster::getLinearCoord(size_t r, size_t c) const 140 | { 141 | return r * dimx_ + c; 142 | } 143 | 144 | std::array Raster::getPointFromRasterCoords(size_t col, size_t row) const 145 | { 146 | std::array p; 147 | p[0] = minx_ + col*cellSize_ + cellSize_/2; 148 | p[1] = miny_ + row*cellSize_ + cellSize_/2; 149 | p[2] = (*vals_)[col+row*dimx_]; 150 | return p; 151 | } 152 | 153 | double Raster::sample(double &x, double &y) 154 | { 155 | return (*vals_)[getLinearCoord(x,y)]; 156 | } 157 | 158 | void Raster::set_val(size_t col, size_t row, double val) { 159 | (*vals_)[col+row*dimx_] = val; 160 | } 161 | 162 | double Raster::get_val(size_t col, size_t row) { 163 | return (*vals_)[col+row*dimx_]; 164 | } 165 | 166 | bool Raster::isNoData(size_t col, size_t row) { 167 | return get_val(col, row) == noDataVal_; 168 | } 169 | bool Raster::isNoData(double &x, double &y) { 170 | return (*vals_)[getLinearCoord(x,y)] == noDataVal_; 171 | } 172 | 173 | void Raster::set_nodata(double new_nodata_val) { 174 | for (size_t i=0; i new_vals(dimx_*dimy_); 185 | 186 | set_nodata(std::numeric_limits::max()); 187 | // iterate though raster pixels 188 | for(size_t col=0; col < dimx_; ++col) { 189 | for(size_t row=0; row < dimy_; ++row) { 190 | // if there is nodata here 191 | if (get_val(col, row) == noDataVal_) { 192 | // look in window of size radius around this pixel and collect the minimal value 193 | size_t left = std::max(int(0), int(col)-int(window_size)); 194 | size_t right = std::min(int(dimx_), int(col)+int(window_size)); 195 | size_t bottom = std::max(int(0), int(row)-int(window_size)); 196 | size_t top = std::min(int(dimy_), int(row)+int(window_size)); 197 | double min_val = noDataVal_; 198 | for (size_t wc = left; wc < right; ++wc ) { 199 | for (size_t wr = bottom; wr < top; ++wr ) { 200 | min_val = std::min(min_val, get_val(wc, wr)); 201 | } 202 | } 203 | new_vals[col+row*dimx_] = min_val; 204 | } else { 205 | new_vals[col+row*dimx_] = get_val(col, row); 206 | } 207 | } 208 | } 209 | (*vals_) = new_vals; 210 | } 211 | 212 | // void Raster::write(const char* WKGCS, alg a, void * dataPtr, const char* outFile) 213 | // { 214 | // if( EQUALN(WKGCS, "EPSG:",5) ) { 215 | // oSRS.importFromEPSG( atoi(WKGCS+5) ); 216 | // } else if (EQUALN(WKGCS, "EPSGA:",6)) { 217 | // oSRS.importFromEPSGA( atoi(WKGCS+6) ); 218 | // } 219 | // GDALAllRegister(); 220 | // GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("GTiff"); 221 | // GDALDataset *poDstDS; 222 | // GDALDataType dataType; 223 | 224 | // if (a == CNT) 225 | // dataType = GDT_UInt16; 226 | // else 227 | // dataType = GDT_Float64; 228 | 229 | // char **papszOptions = NULL; 230 | // poDstDS = poDriver->Create( outFile, dimx_, dimy_, 1, dataType, 231 | // papszOptions ); 232 | // double adfGeoTransform[6] = { minx_, cellSize_, 0, miny_, 0, cellSize_ }; 233 | // GDALRasterBand *poBand; 234 | 235 | // poDstDS->SetGeoTransform( adfGeoTransform ); 236 | 237 | // // std::cout << oSRS.SetWellKnownGeogCS( WKGCS ); 238 | // // std::cout << pszSRS_WKT <SetProjection( pszSRS_WKT ); 243 | // CPLFree( pszSRS_WKT ); 244 | 245 | // poBand = poDstDS->GetRasterBand(1); 246 | // poBand->RasterIO( GF_Write, 0, 0, dimx_, dimy_, 247 | // dataPtr, dimx_, dimy_, dataType, 0, 0 ); 248 | // poBand->SetNoDataValue(noDataVal); 249 | // /* Once we're done, close properly the dataset */ 250 | // GDALClose( (GDALDatasetH) poDstDS ); 251 | // } 252 | 253 | } -------------------------------------------------------------------------------- /src/Raster.h: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | // #include 29 | // #include 30 | // #include 31 | // #include 32 | namespace RasterTools { 33 | enum alg {MIN,MAX}; 34 | class Raster 35 | { 36 | public: 37 | typedef std::array point3d; 38 | typedef std::array point2d; 39 | Raster(double cellsize, double min_x, double max_x, double min_y, double max_y); 40 | Raster(const Raster&); 41 | void operator=(const Raster& r); 42 | Raster(){}; 43 | ~Raster(){}; 44 | void prefill_arrays(alg a); 45 | bool add_point(double x, double y, double z, alg a); 46 | bool check_point(double x, double y); 47 | void add_raster(double x, double y, double z, alg a); 48 | size_t getRow(double x, double y) const; 49 | size_t getCol(double x, double y) const; 50 | size_t getLinearCoord(double x, double y) const; 51 | size_t getLinearCoord(size_t r, size_t c) const; 52 | std::array getColRowCoord(double x, double y) const; 53 | point3d getPointFromRasterCoords(size_t col, size_t row) const; 54 | double getNoDataVal() {return noDataVal_;}; 55 | double sample(double &x, double &y); 56 | void set_val(size_t col, size_t row, double val); 57 | double get_val(size_t col, size_t row); 58 | bool isNoData(size_t col, size_t row); 59 | bool isNoData(double &x, double &y); 60 | void set_nodata(double new_nodata_val); 61 | void fill_nn(size_t window_size); 62 | // void write(const char* WKGCS, alg a, void * dataPtr, const char* outFile); 63 | 64 | // rasterise a polygon and return a list with points - one in the center of each pixel inside the polygon 65 | // in the polygon first point is *not* repeated as last 66 | // T should be a vector of arr or arr 67 | template std::vector rasterise_polygon(T& polygon, std::array cr_min, std::array cr_max, bool returnNoData=true) const { 68 | // code adapted from http://alienryderflex.com/polygon_fill/ 69 | int n_nodes, pixelX, pixelY, i, j, swap ; 70 | int n_vertices = polygon.size(); 71 | std::vector result; 72 | 73 | 74 | // perhaps we can specialise these to the bounding box of the polygon 75 | int IMAGE_TOP = std::floor(cr_min[1]), 76 | IMAGE_BOT = std::ceil(cr_max[1]), 77 | IMAGE_LEFT = std::ceil(cr_min[0]), 78 | IMAGE_RIGHT = std::floor(cr_max[0]); 79 | 80 | // Loop through the rows of the image. 81 | for (pixelY=IMAGE_TOP; pixelY intersect_x; // vector to hold the x-coordinates where the scanline intersects the polygon 83 | 84 | // Build a list of nodes. 85 | n_nodes=0; j=n_vertices-1; 86 | for (i=0; i=(double) pixelY) 92 | || (pj[1]<(double) pixelY && pi[1]>=(double) pixelY)) { 93 | intersect_x.push_back((int) (pi[0]+(pixelY-pi[1])/(pj[1]-pi[1]) 94 | *(pj[0]-pi[0]))); 95 | ++n_nodes; 96 | } 97 | j=i; 98 | } 99 | 100 | // Sort the nodes, via a simple “Bubble” sort. 101 | i=0; 102 | while (iintersect_x[i+1]) { 104 | swap=intersect_x[i]; intersect_x[i]=intersect_x[i+1]; intersect_x[i+1]=swap; if (i) i--; 105 | } else { 106 | i++; 107 | } 108 | } 109 | 110 | // Fill the pixels between node pairs. 111 | for (i=0; i=IMAGE_RIGHT) 113 | break; 114 | if (intersect_x[i+1]> IMAGE_LEFT ) { 115 | if (intersect_x[i ]< IMAGE_LEFT ) 116 | intersect_x[i ]=IMAGE_LEFT ; 117 | if (intersect_x[i+1]> IMAGE_RIGHT) 118 | intersect_x[i+1]=IMAGE_RIGHT; 119 | for (pixelX=intersect_x[i]; pixelX<=intersect_x[i+1]; pixelX++) { 120 | auto p = getPointFromRasterCoords(pixelX,pixelY); 121 | if(p[2] == noDataVal_) { 122 | if (returnNoData) { 123 | result.push_back(p); 124 | } 125 | } else { 126 | result.push_back(p); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | 133 | return result; 134 | }; 135 | template std::vector rasterise_polygon(T& polygon, bool returnNoData=true) const { 136 | return rasterise_polygon(polygon, {0,0}, {double(dimx_),double(dimy_)}, returnNoData); 137 | }; 138 | // template<> std::vector rasterise_polygon(std::vector& polygon) const; 139 | // template<> std::vector rasterise_polygon(std::vector& polygon) const; 140 | 141 | double cellSize_, minx_, miny_, maxx_, maxy_; 142 | size_t dimx_, dimy_; 143 | double noDataVal_; 144 | // std::unique_ptr> counts_; 145 | std::unique_ptr> vals_; 146 | private: 147 | void avg(double &x, double &y, double &val); 148 | void min(double &x, double &y, double &val); 149 | void max(double &x, double &y, double &val); 150 | // void cnt(double &x, double &y); 151 | // OGRSpatialReference oSRS; 152 | }; 153 | } -------------------------------------------------------------------------------- /src/arrangement.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "arrangement.hpp" 17 | 18 | template bool ccb_to_polygon_3(E he, P& polygon, double h=0) { 19 | auto first = he; 20 | 21 | while(true){ 22 | if (he->is_fictitious()) return false; 23 | polygon.push_back({ 24 | float(CGAL::to_double(he->source()->point().x())), 25 | float(CGAL::to_double(he->source()->point().y())), 26 | float(h) 27 | }); 28 | 29 | he = he->next(); 30 | if (he==first) break; 31 | // } 32 | } 33 | return true; 34 | } 35 | void arrangementface_to_polygon(Face_handle face, vec2f& polygons){ 36 | // if(extract_face){ // ie it is a face on the interior of the footprint 37 | auto he = face->outer_ccb(); 38 | auto first = he; 39 | 40 | while(true){ 41 | // if (!he->source()- at_infinity()) 42 | polygons.push_back({ 43 | float(CGAL::to_double(he->source()->point().x())), 44 | float(CGAL::to_double(he->source()->point().y())) 45 | }); 46 | 47 | he = he->next(); 48 | if (he==first) break; 49 | // } 50 | } 51 | } 52 | bool arrangementface_to_polygon(Face_handle face, geoflow::LinearRing& polygon, double h){ 53 | // if(extract_face){ // ie it is a face on the interior of the footprint 54 | auto he = face->outer_ccb(); 55 | if (!ccb_to_polygon_3(he, polygon, h)) return false; 56 | 57 | for (auto ccb = face->inner_ccbs_begin(); ccb != face->inner_ccbs_end(); ++ccb) { 58 | geoflow::vec3f ring; 59 | if (!ccb_to_polygon_3(*ccb, ring, h)) return false; 60 | polygon.interior_rings().push_back(ring); 61 | } 62 | return true; 63 | } 64 | 65 | // helper functions 66 | void arr_dissolve_seg_edges(Arrangement_2& arr) 67 | { 68 | std::vector to_remove; 69 | for (auto he : arr.edge_handles()) { 70 | auto d1 = he->face()->data(); 71 | auto d2 = he->twin()->face()->data(); 72 | if ((d1.segid == d2.segid ) && (d1.in_footprint && d2.in_footprint) && d1.segid != 0) 73 | to_remove.push_back(he); 74 | } 75 | for (auto he : to_remove) { 76 | arr.remove_edge(he); 77 | } 78 | } 79 | void arr_remove_redundant_vertices(Arrangement_2& arr) 80 | { 81 | // cleanup vertices with degree==2 82 | std::vector to_remove; 83 | for (auto v : arr.vertex_handles()) { 84 | if(v->degree()==2) 85 | to_remove.push_back(v); 86 | } 87 | for (auto v : to_remove) { 88 | CGAL::remove_vertex(arr, v); 89 | } 90 | } 91 | 92 | void arr_dissolve_step_edges_naive(Arrangement_2& arr, float step_height_threshold, bool compute_on_edge) 93 | { 94 | std::vector to_remove; 95 | for (auto& edge : arr.edge_handles()) { 96 | auto f1 = edge->face(); 97 | auto f2 = edge->twin()->face(); 98 | 99 | if((f1->data().in_footprint && f2->data().in_footprint) && (f1->data().segid!=0 && f2->data().segid!=0)) { 100 | double d; 101 | if (compute_on_edge) { 102 | auto& s = edge->source()->point(); 103 | auto& t = edge->target()->point(); 104 | auto& pl1 = f1->data().plane; 105 | auto& pl2 = f2->data().plane; 106 | double h1_pl1 = CGAL::to_double((pl1.a()*s.x() + pl1.b()*s.y() + pl1.d()) / (-pl1.c())); 107 | double h2_pl1 = CGAL::to_double((pl1.a()*t.x() + pl1.b()*t.y() + pl1.d()) / (-pl1.c())); 108 | double h1_pl2 = CGAL::to_double((pl2.a()*s.x() + pl2.b()*s.y() + pl2.d()) / (-pl2.c())); 109 | double h2_pl2 = CGAL::to_double((pl2.a()*t.x() + pl2.b()*t.y() + pl2.d()) / (-pl2.c())); 110 | d = std::max(std::abs(h1_pl1-h1_pl2), std::abs(h2_pl1-h2_pl2)); 111 | } else { 112 | d = std::abs(f1->data().elevation_70p - f2->data().elevation_70p); 113 | } 114 | if(d < step_height_threshold){ 115 | // Face_merge_observer takes care of data merge 116 | // if (f2->data().elevation_avg < f1->data().elevation_avg) { 117 | // f2->data()= f1->data(); 118 | // } else { 119 | // f1->data() = f2->data(); 120 | // } 121 | to_remove.push_back(edge); 122 | } 123 | } 124 | } 125 | for (auto edge : to_remove) { 126 | arr.remove_edge(edge); 127 | } 128 | } 129 | 130 | auto HandleHash = CGAL::Handle_hash_function{}; 131 | void arr_dissolve_step_edges(Arrangement_2& arr, float step_height_threshold) 132 | { 133 | struct FacePair { 134 | Arrangement_2::Face_handle f_lo; 135 | Arrangement_2::Face_handle f_hi; 136 | 137 | FacePair(){}; 138 | FacePair(Arrangement_2::Face_handle f1, Arrangement_2::Face_handle f2) { 139 | if (HandleHash(f1) < HandleHash(f1)) { 140 | f_lo = f1; 141 | f_hi = f2; 142 | } else { 143 | f_lo = f2; 144 | f_hi = f1; 145 | } 146 | }; 147 | }; 148 | 149 | struct KeyEqual { 150 | bool operator()(const FacePair& lhs, const FacePair& rhs) const 151 | { 152 | return lhs.f_hi == rhs.f_hi && lhs.f_lo == rhs.f_lo; 153 | } 154 | }; 155 | struct KeyHash 156 | { 157 | std::size_t operator()(FacePair const& p) const 158 | { 159 | std::size_t h1 = HandleHash(p.f_lo); 160 | std::size_t h2 = HandleHash(p.f_hi); 161 | return h1 ^ (h2 << 1); // or use boost::hash_combine (see Discussion) 162 | } 163 | }; 164 | 165 | std::unordered_map< 166 | FacePair, 167 | std::vector, 168 | KeyHash, 169 | KeyEqual 170 | > step_boundaries; 171 | 172 | while (true) { 173 | double d_min = step_height_threshold; 174 | step_boundaries.clear(); 175 | for (auto& edge : arr.edge_handles()) { 176 | auto f1 = edge->face(); 177 | auto f2 = edge->twin()->face(); 178 | if((f1->data().in_footprint && f2->data().in_footprint) && (f1->data().segid!=0 && f2->data().segid!=0)) { 179 | step_boundaries[FacePair(f1,f2)].push_back(edge); 180 | } 181 | } 182 | FacePair facepair_min; 183 | for (auto& [faces, edges] : step_boundaries) { 184 | double d = std::abs(faces.f_hi->data().elevation_50p - faces.f_lo->data().elevation_50p); 185 | if (d < d_min) { 186 | d_min = d; 187 | facepair_min = faces; 188 | } 189 | } 190 | if (d_min == step_height_threshold) break; 191 | std::vector to_remove; 192 | for (auto& edge : step_boundaries[facepair_min]) { 193 | arr.remove_edge(edge); 194 | } 195 | } 196 | } 197 | 198 | void arr_snap_duplicates(Arrangement_2& arr, double dupe_threshold) { 199 | std::vector to_remove; 200 | double dupe_threshold_sq = dupe_threshold*dupe_threshold; 201 | for (auto he : arr.edge_handles()) { 202 | auto source = he->source(); 203 | auto target = he->target(); 204 | if (CGAL::squared_distance(source->point(), target->point()) < dupe_threshold_sq) { 205 | if ((source->degree()==2 && target->degree()>2) || (target->degree()==2 && source->degree()>2)) 206 | to_remove.push_back(he); 207 | else 208 | std::cout << "skipping and edge in duplicate snapping. Degrees are " << target->degree() << " and " << source->degree() << "\n"; 209 | } 210 | } 211 | for (auto he : to_remove) { 212 | Vertex_handle vy, v_other; 213 | Halfedge_handle he_other; 214 | auto source = he->source(); 215 | auto target = he->target(); 216 | if (source->degree()==2 && target->degree()>2) { 217 | vy = target; 218 | he_other = he->prev(); 219 | v_other = he_other->source(); 220 | } else if (target->degree()==2 && source->degree()>2) { 221 | vy = source; 222 | he_other = he->next(); 223 | v_other = he_other->target(); 224 | } 225 | arr.merge_edge(he, he_other, Segment_2(vy->point(), v_other->point())); 226 | } 227 | } 228 | 229 | 230 | // { 231 | // for (auto& v : arr.vertex_handles()){ 232 | // auto vhe = v->incident_halfedges(); 233 | // auto vdone = vhe; 234 | // // check if v is not on the fp boundary 235 | // bool on_fp = false; 236 | // do { 237 | // on_fp |= (!vhe->face()->data().in_footprint) || (!vhe->twin()->face()->data().in_footprint); 238 | // } while (++vhe!=vdone); 239 | // if (!on_fp) 240 | // vertices_to_snap.push_back(v); 241 | // } 242 | // } 243 | 244 | void arr_dissolve_fp(Arrangement_2& arr, bool inside, bool outside) { 245 | { 246 | std::vector to_remove; 247 | for (auto he : arr.edge_handles()) { 248 | auto d1 = he->face()->data(); 249 | auto d2 = he->twin()->face()->data(); 250 | if(outside) 251 | if (!d1.in_footprint && !d2.in_footprint) 252 | to_remove.push_back(he); 253 | if(inside) 254 | if (d1.in_footprint && d2.in_footprint) 255 | to_remove.push_back(he); 256 | } 257 | for (auto he : to_remove) { 258 | arr.remove_edge(he); 259 | } 260 | } 261 | } 262 | 263 | void arr_filter_biggest_face(Arrangement_2& arr, const float& rel_area_thres) { 264 | // check number of faces 265 | typedef std::pair polyar; 266 | std::vector polygons; 267 | double total_area=0; 268 | for (auto& fh : arr.face_handles()) { 269 | if (fh->data().segid != 0 || fh->data().in_footprint == true) { 270 | auto poly = arr_cell2polygon(fh); 271 | double area = CGAL::to_double(CGAL::abs(poly.area())); 272 | total_area += area; 273 | polygons.push_back(std::make_pair(poly, area)); 274 | } 275 | } 276 | std::sort(polygons.begin(), polygons.end(), [](const polyar& a, const polyar& b) { 277 | return a.second < b.second; 278 | }); 279 | arr.clear(); 280 | for (auto& poly_a : polygons) { 281 | if (poly_a.second > rel_area_thres * total_area) 282 | insert_non_intersecting_curves(arr, poly_a.first.edges_begin(), poly_a.first.edges_end()); 283 | } 284 | } 285 | 286 | Polygon_2 arr_cell2polygon(const Face_handle& fh) { 287 | Polygon_2 poly; 288 | auto he = fh->outer_ccb(); 289 | auto first = he; 290 | do { 291 | poly.push_back(he->target()->point()); 292 | he = he->next(); 293 | } while (he!=first); 294 | return poly; 295 | } 296 | 297 | void arr_label_buildingparts(Arrangement_2& arr) { 298 | std::stack seeds; 299 | 300 | for (auto& fh : arr.face_handles()) { 301 | if(fh->data().in_footprint) seeds.push(fh); 302 | } 303 | 304 | int part_counter = 0; 305 | while (seeds.size()) { 306 | auto f_seed = seeds.top(); seeds.pop(); 307 | if (f_seed->data().part_id == -1) { 308 | f_seed->data().part_id = part_counter; 309 | std::stack candidates; 310 | candidates.push(f_seed); 311 | 312 | while(candidates.size()) { 313 | auto f_cand = candidates.top(); candidates.pop(); 314 | auto he = f_cand->outer_ccb(); 315 | auto first = he; 316 | 317 | // collect neighbouring faces 318 | while(true){ 319 | auto f_cand_new = he->twin()->face(); 320 | if (f_cand_new->data().in_footprint && f_cand_new->data().part_id == -1) { 321 | f_cand_new->data().part_id = part_counter; 322 | candidates.push(f_cand_new); 323 | } 324 | 325 | he = he->next(); 326 | if (he==first) break; 327 | } 328 | // also look at holes 329 | for (auto ccb = f_cand->inner_ccbs_begin(); ccb != f_cand->inner_ccbs_end(); ++ccb) { 330 | auto he = (*ccb); 331 | auto first = he; 332 | 333 | // walk along entire hole ccb 334 | while(true){ 335 | auto f_cand_new = he->twin()->face(); 336 | if (f_cand_new->data().in_footprint && f_cand_new->data().part_id == -1) { 337 | f_cand_new->data().part_id = part_counter; 338 | candidates.push(f_cand_new); 339 | } 340 | 341 | he = he->next(); 342 | if (he==first) break; 343 | } 344 | } 345 | } 346 | ++part_counter; 347 | } 348 | } 349 | } -------------------------------------------------------------------------------- /src/arrangement.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #pragma once 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "point_edge.h" 27 | 28 | // #include 29 | // 2D arrangement 30 | // typedef CGAL::Exact_rational Number_type; 31 | // typedef double Number_type; 32 | // typedef CGAL::Cartesian AK; 33 | // typedef Kernel::Point_2 PolygonPoint; 34 | typedef CGAL::Arr_linear_traits_2 Traits_2; 35 | typedef Traits_2::Segment_2 Segment_2; 36 | typedef Traits_2::Ray_2 Ray_2; 37 | typedef Traits_2::Line_2 Line_2; 38 | typedef Traits_2::X_monotone_curve_2 X_monotone_curve_2; 39 | typedef Traits_2::Point_2 Point_2; 40 | 41 | struct FaceInfo { 42 | bool is_finite=false; 43 | bool is_ground=false; 44 | bool in_footprint=false; 45 | bool is_footprint_hole=false; 46 | float elevation_50p=0; 47 | float elevation_70p=0; 48 | float elevation_97p=0; 49 | float elevation_min=0, elevation_max=0; 50 | float data_coverage=0; 51 | int pixel_count=0; 52 | int segid=0; 53 | int part_id=-1; 54 | float rms_error_to_avg=0; 55 | 56 | Plane plane; 57 | std::vector points; 58 | 59 | 60 | // graph-cut optimisation 61 | size_t label=0; 62 | size_t v_index; 63 | std::vector vertex_label_cost; 64 | }; 65 | struct EdgeInfo { 66 | bool blocks = false; 67 | 68 | // graph-cut optimisation 69 | double edge_weight; 70 | }; 71 | typedef CGAL::Arr_extended_dcel Dcel; 72 | typedef CGAL::Arrangement_2 Arrangement_2; 73 | typedef Arrangement_2::Vertex_handle Vertex_handle; 74 | typedef Arrangement_2::Halfedge_handle Halfedge_handle; 75 | typedef Arrangement_2::Face_handle Face_handle; 76 | typedef Arrangement_2::Vertex_const_handle Vertex_const_handle; 77 | typedef Arrangement_2::Halfedge_const_handle Halfedge_const_handle; 78 | typedef Arrangement_2::Face_const_handle Face_const_handle; 79 | typedef Arrangement_2::Ccb_halfedge_circulator Ccb_halfedge_circulator; 80 | typedef CGAL::Arr_accessor Arr_accessor; 81 | typedef Arr_accessor::Dcel_vertex DVertex; 82 | typedef Arrangement_2::Face Face; 83 | struct overlay_functor { 84 | FaceInfo operator()(const FaceInfo a, const FaceInfo b) const { 85 | auto r = FaceInfo(); 86 | r.segid=0; 87 | // if (a.is_finite && b.is_finite) 88 | r.is_finite = true; 89 | 90 | if (a.segid!=0 && b.segid==0) { 91 | r = a; 92 | } else if (a.segid==0 && b.segid!=0) { 93 | r = b; 94 | } else if (a.segid!=0 && b.segid!=0) { // we need to merge 2 faces with a plane 95 | if (a.elevation_70p > b.elevation_70p) { 96 | r=a; 97 | } else { 98 | r=b; 99 | } 100 | } 101 | 102 | if (a.in_footprint || b.in_footprint) { 103 | r.in_footprint = true; 104 | } 105 | 106 | return r; 107 | } 108 | }; 109 | typedef CGAL::Arr_face_overlay_traits Overlay_traits; 113 | // typedef CGAL::Cartesian_converter IK_to_EK; 114 | // typedef CGAL::Cartesian_converter ToInexact; 115 | 116 | // inline Number_type arr_s(double v){return static_cast(100*v);} 117 | 118 | // An arrangement observer, used to receive notifications of face splits and 119 | // to update the indices of the newly created faces. 120 | class Face_index_observer : public CGAL::Arr_observer 121 | { 122 | private: 123 | int n_faces; // The current number of faces. 124 | size_t plane_id; 125 | bool in_footprint; 126 | float elevation=0; 127 | Plane plane; 128 | public: 129 | Face_index_observer (Arrangement_2& arr, bool is_footprint, size_t pid, float elevation, Plane plane) : 130 | CGAL::Arr_observer (arr), 131 | n_faces (0), in_footprint(is_footprint), plane_id(pid), elevation(elevation), plane(plane) 132 | { 133 | CGAL_precondition (arr.is_empty()); 134 | arr.unbounded_face()->data().is_finite=false; 135 | n_faces++; 136 | }; 137 | virtual void after_split_face (Face_handle old_face, 138 | Face_handle new_face, bool ) 139 | { 140 | // Assign index to the new face. 141 | new_face->data().in_footprint = in_footprint; 142 | new_face->data().is_finite = true; 143 | new_face->data().segid = plane_id; 144 | new_face->data().elevation_70p = elevation; 145 | new_face->data().plane = plane; 146 | n_faces++; 147 | } 148 | }; 149 | class Face_split_observer : public CGAL::Arr_observer 150 | { 151 | private: 152 | int n_faces; // The current number of faces. 153 | bool hole_mode=false; 154 | public: 155 | Face_split_observer (Arrangement_2& arr) : 156 | CGAL::Arr_observer (arr), 157 | n_faces (0) 158 | { 159 | CGAL_precondition (arr.is_empty()); 160 | arr.unbounded_face()->data().in_footprint=false; 161 | n_faces++; 162 | } 163 | virtual void after_split_face (Face_handle old_face, 164 | Face_handle new_face, bool is_hole) 165 | { 166 | // Assign index to the new face. 167 | if(n_faces == 1) 168 | new_face->data().in_footprint = true; 169 | else if(old_face->data().in_footprint) { 170 | if (!is_hole && hole_mode) { // detect case where a `hole` is added that touches surrounding outer_ccb (in one vertex) 171 | // these holes are ignored 172 | new_face->data().in_footprint = true; 173 | new_face->data().is_footprint_hole = false; 174 | old_face->data().in_footprint = true; 175 | old_face->data().is_footprint_hole = false; 176 | std::cout << "Ignored input footprint hole that is touching footprint exterior\n"; 177 | } else { // normal holes that do not touch outer_ccb of existing face 178 | new_face->data().in_footprint = !hole_mode; 179 | new_face->data().is_footprint_hole = hole_mode; 180 | } 181 | } else { 182 | new_face->data().in_footprint = false; 183 | new_face->data().is_footprint_hole = old_face->data().is_footprint_hole; 184 | } 185 | n_faces++; 186 | } 187 | void set_hole_mode(bool mode) { 188 | hole_mode = mode; 189 | } 190 | }; 191 | class Face_merge_observer : public CGAL::Arr_observer 192 | { 193 | public: 194 | Face_merge_observer (Arrangement_2& arr) : 195 | CGAL::Arr_observer (arr) {}; 196 | 197 | virtual void before_merge_face (Face_handle remaining_face, 198 | Face_handle discarded_face, Halfedge_handle e ) 199 | { 200 | auto count1 = float(remaining_face->data().pixel_count); 201 | auto count2 = float(discarded_face->data().pixel_count); 202 | auto sum_count = count1+count2; 203 | if (sum_count!=0){ 204 | auto w1 = (count1/sum_count); 205 | auto w2 = (count2/sum_count); 206 | remaining_face->data().elevation_50p = 207 | remaining_face->data().elevation_50p * w1 + discarded_face->data().elevation_50p * w2; 208 | remaining_face->data().elevation_70p = 209 | remaining_face->data().elevation_70p * w1 + discarded_face->data().elevation_70p * w2; 210 | remaining_face->data().elevation_97p = 211 | std::max(remaining_face->data().elevation_97p, discarded_face->data().elevation_97p); 212 | remaining_face->data().data_coverage = 213 | remaining_face->data().data_coverage * w1 + discarded_face->data().data_coverage * w2; 214 | // and sum the counts 215 | remaining_face->data().pixel_count = sum_count; 216 | } 217 | remaining_face->data().elevation_min = std::min(remaining_face->data().elevation_min, discarded_face->data().elevation_min); 218 | remaining_face->data().elevation_max = std::max(remaining_face->data().elevation_max, discarded_face->data().elevation_max); 219 | // merge the point lists 220 | // if (remaining_face==discarded_face){ 221 | // std::cout << "merging the same face!?\n"; 222 | // return; 223 | // } 224 | // remaining_face->data().points.insert(remaining_face->data().points.end(), discarded_face->data().points.begin(), discarded_face->data().points.end() ); 225 | } 226 | }; 227 | 228 | class Snap_observer : public CGAL::Arr_observer 229 | { 230 | public: 231 | Snap_observer (Arrangement_2& arr) : 232 | CGAL::Arr_observer (arr) {}; 233 | 234 | virtual void after_create_edge (Halfedge_handle e) { 235 | e->data().blocks = true; 236 | } 237 | virtual void after_split_face (Face_handle old_face, Face_handle new_face, bool ) { 238 | if (old_face->data().segid==0) 239 | new_face->set_data(old_face->data()); 240 | } 241 | }; 242 | 243 | typedef std::vector> vec2f; 244 | 245 | void arr_filter_biggest_face(Arrangement_2& arr, const float& rel_area_thres); 246 | 247 | void arrangementface_to_polygon(Face_handle face, vec2f& polygons); 248 | bool arrangementface_to_polygon(Face_handle face, geoflow::LinearRing& polygons, double h=0); 249 | 250 | Polygon_2 arr_cell2polygon(const Face_handle& fh); 251 | 252 | void arr_dissolve_seg_edges(Arrangement_2& arr); 253 | void arr_remove_redundant_vertices(Arrangement_2& arr); 254 | void arr_dissolve_step_edges_naive(Arrangement_2& arr, float step_height_threshold, bool compute_on_edge); 255 | void arr_dissolve_step_edges(Arrangement_2& arr, float step_height_threshold); 256 | void arr_dissolve_fp(Arrangement_2& arr, bool inside, bool outside); 257 | void arr_snap_duplicates(Arrangement_2& arr, double dupe_threshold); 258 | void arr_label_buildingparts(Arrangement_2& arr); -------------------------------------------------------------------------------- /src/cdt_util.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "cdt_util.hpp" 17 | 18 | namespace tri_util { 19 | 20 | void mark_domains(CDT& ct, 21 | CDT::Face_handle start, 22 | int index, 23 | std::list& border) { 24 | if (start->info().nesting_level != -1) { 25 | return; 26 | } 27 | std::list queue; 28 | queue.push_back(start); 29 | while (!queue.empty()) { 30 | CDT::Face_handle fh = queue.front(); 31 | queue.pop_front(); 32 | if (fh->info().nesting_level == -1) { 33 | fh->info().nesting_level = index; 34 | for (int i = 0; i < 3; i++) { 35 | CDT::Edge e(fh, i); 36 | CDT::Face_handle n = fh->neighbor(i); 37 | if (n->info().nesting_level == -1) { 38 | if (ct.is_constrained(e)) border.push_back(e); 39 | else queue.push_back(n); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | /** 46 | * mark the triangles that are inside the original 2D polygon. 47 | * explore set of facets connected with non constrained edges, 48 | * and attribute to each such set a nesting level. 49 | * start from facets incident to the infinite vertex, with a nesting 50 | * level of 0. Then recursively consider the non-explored facets incident 51 | * to constrained edges bounding the former set and increase the nesting level by 1. 52 | * facets in the domain are those with an odd nesting level. 53 | */ 54 | void mark_domains(CDT& cdt) { 55 | // for (CDT::All_faces_iterator it = cdt.all_faces_begin(); it != cdt.all_faces_end(); ++it) { 56 | // it->info().nesting_level = -1; 57 | // } 58 | std::list border; 59 | mark_domains(cdt, cdt.infinite_face(), 0, border); 60 | while (!border.empty()) { 61 | CDT::Edge e = border.front(); 62 | border.pop_front(); 63 | CDT::Face_handle n = e.first->neighbor(e.second); 64 | if (n->info().nesting_level == -1) { 65 | mark_domains(cdt, n, e.first->info().nesting_level + 1, border); 66 | } 67 | } 68 | } 69 | 70 | void insert_ring(geoflow::vec3f& ring, CDT& cdt) { 71 | auto pit_last = ring.end()-1; 72 | CDT::Vertex_handle vh_next, vh_last, vh = cdt.insert(K::Point_2((*pit_last)[0], (*pit_last)[1])); 73 | vh_last = vh; 74 | vh->info().set_point(*pit_last); 75 | for (auto pit = ring.begin(); pit != ring.end(); ++pit) { 76 | if(pit==pit_last){ 77 | vh_next=vh_last; 78 | } else { 79 | vh_next = cdt.insert(K::Point_2((*pit)[0], (*pit)[1])); 80 | vh_next->info().set_point(*pit); 81 | } 82 | cdt.insert_constraint(vh, vh_next); 83 | vh = vh_next; 84 | } 85 | } 86 | 87 | CDT create_from_polygon(geoflow::LinearRing& poly) { 88 | CDT triangulation; 89 | 90 | insert_ring(poly, triangulation); 91 | for (auto& ring : poly.interior_rings()) { 92 | insert_ring(ring, triangulation); 93 | } 94 | 95 | if (triangulation.number_of_faces()==0) 96 | return triangulation; 97 | 98 | mark_domains(triangulation); 99 | 100 | return triangulation; 101 | } 102 | } -------------------------------------------------------------------------------- /src/cdt_util.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace tri_util { 24 | 25 | typedef CGAL::Exact_predicates_inexact_constructions_kernel K; 26 | typedef CGAL::Exact_predicates_inexact_constructions_kernel Epeck; 27 | typedef CGAL::Exact_predicates_tag Tag; 28 | struct VertexInfo { 29 | bool hasPoint = false; 30 | geoflow::arr3f point; 31 | VertexInfo() : hasPoint(){}; 32 | void set_point(geoflow::arr3f& p) { 33 | hasPoint=true; 34 | point = p; 35 | }; 36 | }; 37 | struct FaceInfo { 38 | bool interior = false, visited = false; 39 | int nesting_level = -1; 40 | void set_interior(bool is_interior) { 41 | visited = true; 42 | interior = is_interior; 43 | } 44 | bool in_domain() { 45 | return nesting_level % 2 == 1; 46 | } 47 | }; 48 | typedef CGAL::Triangulation_vertex_base_with_info_2 VertexBase; 49 | typedef CGAL::Constrained_triangulation_face_base_2 FaceBase; 50 | typedef CGAL::Triangulation_face_base_with_info_2 FaceBaseWithInfo; 51 | typedef CGAL::Triangulation_data_structure_2 TriangulationDataStructure; 52 | typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; 53 | 54 | void mark_domains(CDT& ct, 55 | CDT::Face_handle start, 56 | int index, 57 | std::list& border); 58 | 59 | void mark_domains(CDT& cdt); 60 | 61 | void insert_ring(geoflow::vec3f& ring, CDT& cdt); 62 | 63 | CDT create_from_polygon(geoflow::LinearRing& poly); 64 | } -------------------------------------------------------------------------------- /src/data_coverage_node.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | // Let 'vertices' be an array of N pairs (x,y), indexed from 0 18 | // Let 'area' = 0.0 19 | // for i = 0 to N-1, do 20 | // Let j = (i+1) mod N 21 | // Let area = area + vertices[i].x * vertices[j].y 22 | // Let area = area - vertices[i].y * vertices[j].x 23 | // end for 24 | // Return 'area' 25 | 26 | #include "stepedge_nodes.hpp" 27 | #include 28 | 29 | namespace geoflow::nodes::stepedge { 30 | 31 | template float compute_ring_area(const T& ring) { 32 | 33 | size_t n = ring.size(); 34 | float area = 0; 35 | for(size_t i=0; i(); 60 | auto data_area = input("data_area").get(); 61 | auto& groundparts = vector_input("ground_parts"); 62 | 63 | // non_ground_area is sum of the area of the roofparts, thus excluding all groundparts 64 | auto non_ground_area = compute_polygon_area(footprint_polygon); 65 | 66 | for(size_t i=0; i< groundparts.size(); ++i) { 67 | auto& groundpart = groundparts.get(i); 68 | non_ground_area -= compute_polygon_area(groundpart); 69 | } 70 | 71 | output("data_coverage").set( data_area / non_ground_area ); 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/dbscan.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | #include 18 | #include "dbscan.h" 19 | 20 | using namespace std; 21 | using namespace dbscan; 22 | 23 | dbscanClusterer::dbscanClusterer(vector &points) { 24 | auto size = points.size(); 25 | indexed_points.reserve(size); 26 | size_t i=0; 27 | for(auto p: points) 28 | indexed_points.push_back(std::make_pair(p,i++)); 29 | point_segment_idx.resize(size, 0); 30 | point_seed_flags.resize(size, true); 31 | tree.insert(indexed_points.begin(), indexed_points.end()); 32 | } 33 | 34 | vector dbscanClusterer::get_point_indices(size_t shape_id) { 35 | vector result; 36 | for (auto pi:indexed_points){ 37 | auto idx = pi.second; 38 | if (point_segment_idx[idx] == shape_id) 39 | result.push_back(idx); 40 | } 41 | return result; 42 | } 43 | 44 | void dbscanClusterer::detect(){ 45 | // seed generation 46 | // typedef pair index_dist_pair; 47 | // auto cmp = [](index_dist_pair left, index_dist_pair right) {return left.second < right.second;}; 48 | priority_queue, decltype(cmp)> pq(cmp); 49 | 50 | // size_t i=0; 51 | // for(auto pi : indexed_points){ 52 | // auto p = pi.first; 53 | // Neighbor_search search(tree, p, N); 54 | // auto line = fit_line(search); 55 | // auto line_dist = CGAL::squared_distance(line, p); 56 | // pq.push(index_dist_pair(i++, line_dist)); 57 | // } 58 | 59 | // region growing from seed points 60 | while(pq.size()>0){ 61 | auto idx = pq.top().first; pq.pop(); 62 | // if (point_seed_flags[idx]){ 63 | if (point_segment_idx[idx]==0){ 64 | grow_region(idx); 65 | region_counter++; 66 | } 67 | } 68 | } 69 | 70 | void dbscanClusterer::grow_region(size_t seed_idx){ 71 | auto p = indexed_points[seed_idx]; 72 | Neighbor_search search_init(tree, p.first, N); 73 | auto line = fit_line(search_init); 74 | segment_shapes[region_counter] = line; 75 | 76 | vector points_in_region; 77 | vector idx_in_region; 78 | stack candidates; 79 | candidates.push(seed_idx); 80 | point_segment_idx[seed_idx] = region_counter; 81 | points_in_region.push_back(p.first); 82 | idx_in_region.push_back(p.second); 83 | 84 | while (candidates.size()>0){ 85 | auto seed_id = candidates.top(); candidates.pop(); 86 | auto cp = indexed_points[seed_id].first; 87 | Neighbor_search search(tree, cp, N); 88 | for (auto neighbor: search){ 89 | auto n_id = neighbor.first.second; 90 | if (point_segment_idx[n_id]!=0) 91 | continue; 92 | // point_seed_flags[n_id] = false; // this point can no longer be used as seed 93 | if (valid_candidate(segment_shapes[region_counter], neighbor.first.first)){ 94 | point_segment_idx[n_id] = region_counter; 95 | candidates.push(n_id); 96 | points_in_region.push_back(neighbor.first.first); 97 | idx_in_region.push_back(n_id); 98 | Line line; 99 | linear_least_squares_fitting_3(points_in_region.begin(),points_in_region.end(),line,CGAL::Dimension_tag<0>()); 100 | segment_shapes[region_counter] = line; 101 | } 102 | 103 | } 104 | } 105 | // undo region if it doesn't satisfy quality criteria 106 | // if (points_in_region.size(). 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | namespace dbscan { 27 | typedef CGAL::Exact_predicates_inexact_constructions_kernel cgal_kernel; 28 | typedef cgal_kernel::Point_3 Point; 29 | typedef cgal_kernel::Line_3 Line; 30 | 31 | using namespace std; 32 | 33 | class dbscanClusterer { 34 | 35 | typedef std::pair point_index; 36 | typedef CGAL::Search_traits_3 Traits_base; 37 | typedef CGAL::Search_traits_adapter, 39 | Traits_base> TreeTraits; 40 | typedef CGAL::Kd_tree Tree; 41 | typedef CGAL::Fuzzy_sphere Fuzzy_sphere; 42 | 43 | vector indexed_points; 44 | Tree tree; 45 | vector point_seed_flags; 46 | size_t region_counter=1; 47 | 48 | public: 49 | vector point_segment_idx; // 0=unsegmented, maybe put this on the heap... 50 | unordered_map segment_shapes; 51 | double dist_thres = 0.2*0.2; //epsilon 52 | size_t min_segment_count = 20; 53 | 54 | LineDetector(vector &points); 55 | vector get_point_indices(size_t shape_id); 56 | void detect(); 57 | 58 | private: 59 | void grow_region(size_t seed_idx); 60 | }; 61 | } -------------------------------------------------------------------------------- /src/geos_nodes.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "stepedge_nodes.hpp" 25 | 26 | 27 | namespace geoflow::nodes::stepedge { 28 | 29 | geos::geom::GeometryFactory::Ptr geos_global_factory; 30 | 31 | std::unique_ptr to_geos_polygon(const LinearRing& lr) { 32 | auto coordSeq = std::make_unique(); 33 | for (auto&p : lr) { 34 | coordSeq->add(geos::geom::Coordinate(p[0], p[1])); 35 | } 36 | coordSeq->add(geos::geom::Coordinate(lr[0][0], lr[0][1])); 37 | if (!geos::algorithm::Orientation::isCCW(coordSeq.get())){ 38 | geos::geom::CoordinateArraySequence::reverse(coordSeq.get()); 39 | } 40 | auto linearRing = geos_global_factory->createLinearRing(std::move(coordSeq)); 41 | return geos_global_factory->createPolygon(std::move(linearRing)); 42 | } 43 | 44 | void PolygonSimplifyGEOSNode::process() { 45 | auto& ipolygons = vector_input("polygons"); 46 | auto& opolygons = vector_output("simplified_polygons"); 47 | 48 | geos_global_factory = geos::geom::GeometryFactory::create(); 49 | 50 | for (size_t i=0; i(i); 52 | 53 | auto polygon = to_geos_polygon(lr); 54 | 55 | auto simplified_geom = geos::simplify::DouglasPeuckerSimplifier::simplify(polygon.get(), tolerance).release(); 56 | // auto buf_geom = polygon->buffer(offset).release(); 57 | 58 | if(!simplified_geom->isValid()) { 59 | std::cout << "feature not simplified\n"; 60 | opolygons.push_back(lr); 61 | } else if (auto buf_poly = dynamic_cast(simplified_geom)) { 62 | auto polygon_ring = buf_poly->getExteriorRing(); 63 | 64 | LinearRing lr_offset; 65 | for (size_t i=0; igetNumPoints()-1; ++i) { 66 | auto& p = polygon_ring->getCoordinateN(i); 67 | lr_offset.push_back(arr3f{float(p.x), float(p.y), 0}); 68 | } 69 | opolygons.push_back(lr_offset); 70 | } else { 71 | std::cout << "feature not simplified\n"; 72 | opolygons.push_back(lr); 73 | } 74 | 75 | } 76 | } 77 | 78 | void PolygonBufferGEOSNode::process() { 79 | auto& ipolygons = vector_input("polygons"); 80 | auto& opolygons = vector_output("offset_polygons"); 81 | 82 | geos_global_factory = geos::geom::GeometryFactory::create(); 83 | 84 | for (size_t i=0; i(i); 86 | 87 | auto polygon = to_geos_polygon(lr); 88 | 89 | auto buf_geom = polygon->buffer(offset).release(); 90 | 91 | if(!buf_geom->isValid()) { 92 | std::cout << "feature not buffered\n"; 93 | opolygons.push_back(lr); 94 | } else if (auto buf_poly = dynamic_cast(buf_geom)) { 95 | auto polygon_ring = buf_poly->getExteriorRing(); 96 | 97 | LinearRing lr_offset; 98 | for (size_t i=0; igetNumPoints()-1; ++i) { 99 | auto& p = polygon_ring->getCoordinateN(i); 100 | lr_offset.push_back(arr3f{float(p.x), float(p.y), 0}); 101 | } 102 | // } 103 | opolygons.push_back(lr_offset); 104 | } else { 105 | std::cout << "feature not buffered\n"; 106 | opolygons.push_back(lr); 107 | } 108 | delete buf_geom; 109 | } 110 | } 111 | 112 | } //namespace geoflow::nodes::stepedge -------------------------------------------------------------------------------- /src/interval.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | class IntervalList { 23 | // list of all vertices: distance, segment_id 24 | typedef std::multimap ValueMap; 25 | typedef std::vector Component; 26 | typedef std::array Interval; 27 | ValueMap values; 28 | size_t id = 1; //id of 0 is reserved for clip_segments 29 | 30 | public: 31 | 32 | void insert(Interval i) { 33 | values.insert({i[0], id}); 34 | values.insert({i[1], id++}); 35 | } 36 | 37 | size_t size() { 38 | return values.size()/2; 39 | } 40 | 41 | std::vector merge_overlap() { 42 | std::vector merged_intervals; 43 | if (size()==0) 44 | return merged_intervals; 45 | else if (size()==1) 46 | return {{values.begin()->first, values.rbegin()->first}}; 47 | // find connected components 48 | std::unordered_set current; 49 | std::vector< Component > components; 50 | 51 | // iterate over all values (in sorted order) 52 | for (auto it=values.begin(); it!=values.end(); ++it) { 53 | // keep track of current segments 54 | if (current.find(it->second) == current.end()) { 55 | current.emplace(it->second); 56 | } else { 57 | current.erase(it->second); 58 | } 59 | 60 | // make a new component if current is empty, store it in current component otherwise 61 | if (current.empty()) { 62 | components.resize(components.size()+1); 63 | } else { 64 | auto x = it->first; 65 | std::cout << it-> first << "\n"; 66 | // CRASH!! push_back is not working.. 67 | components.back().push_back(it); 68 | } 69 | } 70 | // remove al values in each component and replace by just the first and last vertex in the component 71 | 72 | for (auto& comp : components) { 73 | double new_source = (*comp.begin())->first; 74 | // size_t new_id = (*comp.begin())->second; 75 | double new_target = (*comp.rbegin())->first; 76 | for (auto it : comp) { 77 | values.erase(it); 78 | } 79 | insert({new_source, new_target}); 80 | merged_intervals.push_back({new_source, new_target}); 81 | } 82 | 83 | return merged_intervals; 84 | } 85 | 86 | // void clip(std::vector> clip_segments) { 87 | // // ensures that there are no intervals that overlap with source-target 88 | // // we assume the clip segments do not overlap (could be guaranteed by first doing the merge_connected_component() function above) 89 | // std::unordered_set current; 90 | 91 | // for (auto& clipseg : clip_segments) { 92 | // values.insert({clipseg[0], 0}); 93 | // values.insert({clipseg[1], 0}); 94 | // } 95 | 96 | // // iterate over all values (in sorted order) 97 | // bool inside_clipseg = false; 98 | // for (auto it=values.begin(); it!=values.end(); ++it) { 99 | // if (it->second == 0) 100 | // inside_clipseg = !inside_clipseg; 101 | 102 | // if (current.find(it->second) == current.end()) { 103 | // current.emplace(it->second); 104 | // } else { 105 | // current.erase(it->second); 106 | // } 107 | // } 108 | // } 109 | }; -------------------------------------------------------------------------------- /src/line_regulariser.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "line_regulariser.hpp" 17 | #include 18 | 19 | namespace linereg { 20 | 21 | double calc_mean_angle(const std::vector& lines) { 22 | // length-weighted mean angle 23 | double angle_sum=0, sqlenth_sum=0; 24 | for (auto& line : lines) { 25 | sqlenth_sum += line->sqlength; 26 | } 27 | for (auto& line : lines) { 28 | angle_sum += line->angle * line->sqlength; 29 | } 30 | return angle_sum/sqlenth_sum; 31 | } 32 | 33 | Point_2 calc_centroid(const std::vector& lines) { 34 | double cx=0, cy=0; 35 | for (auto& line : lines) { 36 | auto p = line->segment.source(); 37 | cx += CGAL::to_double(p.x()); 38 | cy += CGAL::to_double(p.y()); 39 | p = line->segment.target(); 40 | cx += CGAL::to_double(p.x()); 41 | cy += CGAL::to_double(p.y()); 42 | } 43 | size_t np = 2*lines.size(); 44 | return Point_2(cx/np, cy/np); 45 | } 46 | 47 | // construct a line L with centroid and mean_angle, then project all the segments from lines on L to bound it to a segment 48 | Segment_2 calc_segment(Point_2 centroid, double mean_angle, const std::vector& lines, double extension) { 49 | auto lv = Vector_2(std::tan(mean_angle), 1.0); 50 | lv = lv / CGAL::sqrt(CGAL::to_double(lv.squared_length())); 51 | 52 | bool setminmax=false; 53 | Point_2 pmin, pmax; 54 | double dmin, dmax; 55 | for (auto& line : lines) { 56 | auto p = line->segment.source(); 57 | auto d = CGAL::to_double(Vector_2(p.x(),p.y())*lv); 58 | if (!setminmax) { 59 | setminmax=true; 60 | dmin=dmax=d; 61 | pmin=pmax=p; 62 | } 63 | if (d < dmin){ 64 | dmin = d; 65 | pmin = p; 66 | } 67 | if (d > dmax) { 68 | dmax = d; 69 | pmax = p; 70 | } 71 | 72 | p = line->segment.target(); 73 | d = CGAL::to_double(Vector_2(p.x(),p.y())*lv); 74 | if (d < dmin){ 75 | dmin = d; 76 | pmin = p; 77 | } 78 | if (d > dmax) { 79 | dmax = d; 80 | pmax = p; 81 | } 82 | } 83 | Line_2 l(centroid, lv); 84 | pmin = l.projection(pmin) - lv*extension; 85 | pmax = l.projection(pmax) + lv*extension; 86 | return Segment_2(pmin, pmax); 87 | } 88 | 89 | double AngleCluster::distance(Cluster* other_cluster) { 90 | return std::fabs(value - other_cluster->value); 91 | } 92 | void AngleCluster::calc_mean_value() { 93 | value = calc_mean_angle(lines); 94 | } 95 | 96 | double DistCluster::distance(Cluster* other_cluster) { 97 | return CGAL::to_double(CGAL::squared_distance(value, other_cluster->value)); 98 | } 99 | void DistCluster::calc_mean_value() { 100 | // a segment through the length-weighted mean centroid and elongated to 'cover' all the segments 101 | double mean_angle = calc_mean_angle(lines); 102 | Point_2 centroid = calc_centroid(lines); 103 | value = calc_segment(centroid, mean_angle, lines); 104 | } 105 | 106 | template DistanceTable::DistanceTable(std::set& clusters) : clusters(clusters) { 107 | // compute only half of the distance table, since the other half will be exactly the same 108 | for(auto cit_a = clusters.begin(); cit_a != clusters.end(); ++cit_a) { 109 | for(auto cit_b = std::next(cit_a); cit_b != clusters.end(); ++cit_b) { 110 | auto cluster_a = *cit_a; 111 | auto cluster_b = *cit_b; 112 | // do not create entries for cluster pairs that both have an intersection line, these are not to be merged 113 | // if (cluster_a->has_intersection_line && cluster_b->has_intersection_line) continue; 114 | // push distance to heap 115 | auto hhandle = distances.push(ClusterPairDist( 116 | std::make_pair(cluster_a, cluster_b), 117 | cluster_a->distance(cluster_b.get()) 118 | )); 119 | // store handle for both clusters 120 | auto hh = std::make_shared(hhandle); 121 | cluster_to_dist_pairs[cluster_a].push_back(hh); 122 | cluster_to_dist_pairs[cluster_b].push_back(hh); 123 | } 124 | } 125 | } 126 | template void DistanceTable::merge(ClusterH lhs, ClusterH rhs) { 127 | // merges two clusters, then removes one from the distances map and update the affected distances 128 | // merge 129 | lhs->lines.insert(lhs->lines.end(), rhs->lines.begin(), rhs->lines.end()); 130 | // lhs->has_intersection_line = lhs->has_intersection_line || rhs->has_intersection_line; 131 | lhs->calc_mean_value(); 132 | 133 | // iterate distancetable 134 | // if rhs in pair: remove pair 135 | // if lhs has intersection line and lhs in a pair: remove pair if the other cluster also has an intersection line. Since clusters that both have intersection line are not te be merged. 136 | auto& rhs_hhandles = cluster_to_dist_pairs[rhs]; 137 | for (auto& hhandle : cluster_to_dist_pairs[rhs]) { 138 | if (hhandle.use_count()==2) { 139 | distances.erase(*hhandle); 140 | } 141 | } 142 | clusters.erase(rhs); 143 | cluster_to_dist_pairs.erase(rhs); 144 | 145 | auto& lhs_hhandles = cluster_to_dist_pairs[lhs]; 146 | auto i = lhs_hhandles.begin(); 147 | while (i != lhs_hhandles.end()) { 148 | // we check the use count of the shared ptr to our heap handle. There should be exactly 2 for the case where both clusters still exist, otherwise one has been merged before and the heap handle was also erased before 149 | if (i->use_count()!=2) 150 | { 151 | lhs_hhandles.erase(i++); 152 | } else { 153 | // 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 154 | try { 155 | (***i).dist = (***i).clusters.first->distance((***i).clusters.second.get()); 156 | } catch (CGAL::Uncertain_conversion_exception e) { 157 | // this happes sometimes, assuming here that this means the distance was close to 0 158 | (***i).dist = 0; 159 | } 160 | distances.update(*(*i)); 161 | ++i; 162 | } 163 | } 164 | } 165 | template typename DistanceTable::ClusterPairDist DistanceTable::get_closest_pair() { 166 | ClusterPairDist min_pair = distances.top(); //distances.pop(); <- do not pop here; the node will be removed later in the merge function! 167 | return min_pair; 168 | } 169 | 170 | template class DistanceTable; 171 | template class DistanceTable; 172 | 173 | void LineRegulariser::perform_angle_clustering() { 174 | //make clusters 175 | angle_clusters.clear(); 176 | for(auto& line : lines) { 177 | auto aclusterh = std::make_shared(); 178 | aclusterh->value = line.angle; 179 | // aclusterh->has_intersection_line = line.priority==2; 180 | aclusterh->lines.push_back(&line); 181 | angle_clusters.insert(aclusterh); 182 | } 183 | 184 | if (angle_clusters.size()>1) { 185 | // make distance table 186 | DistanceTable adt(angle_clusters); 187 | 188 | auto apair = adt.get_closest_pair(); 189 | while (apair.dist < angle_threshold) { 190 | adt.merge(apair.clusters.first, apair.clusters.second); 191 | if (adt.distances.size()==0) break; 192 | apair = adt.get_closest_pair(); 193 | } 194 | } 195 | 196 | size_t id_cntr=0; 197 | for(auto& aclusterh : angle_clusters) { 198 | for(auto line : aclusterh->lines){ 199 | line->angle_cluster = aclusterh; 200 | line->angle_cluster_id = id_cntr; 201 | } 202 | ++id_cntr; 203 | } 204 | } 205 | void LineRegulariser::perform_distance_clustering() { 206 | dist_clusters.clear(); 207 | 208 | // perform distance clustering for each angle cluster 209 | for(auto& aclusterh : angle_clusters) { 210 | std::set dclusters; 211 | for(auto& line : aclusterh->lines) { 212 | auto dclusterh = std::make_shared(); 213 | dclusterh->value = line->segment; 214 | // dclusterh->has_intersection_line = line->priority==2; 215 | dclusterh->lines.push_back(line); 216 | dclusters.insert(dclusterh); 217 | } 218 | 219 | if (dclusters.size()>1) { 220 | // make distance table 221 | DistanceTable ddt(dclusters); 222 | 223 | // do clustering 224 | auto dpair = ddt.get_closest_pair(); 225 | while (dpair.dist < dist_threshold) { 226 | ddt.merge(dpair.clusters.first, dpair.clusters.second); 227 | if (ddt.distances.size()==0) break; 228 | dpair = ddt.get_closest_pair(); 229 | } 230 | } 231 | dist_clusters.insert(dclusters.begin(), dclusters.end()); 232 | } 233 | 234 | size_t id_cntr=0; 235 | for(auto& dclusterh : dist_clusters) { 236 | for(auto line : dclusterh->lines) { 237 | line->dist_cluster = dclusterh; 238 | line->dist_cluster_id = id_cntr; 239 | } 240 | ++id_cntr; 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /src/node_pcmesh_quality.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "stepedge_nodes.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | typedef CGAL::Simple_cartesian K; 28 | 29 | // custom triangle type with 30 | // three pointers to points 31 | struct My_triangle { 32 | geoflow::Triangle m_t; 33 | size_t m_face_id; 34 | My_triangle(geoflow::Triangle t, size_t face_id) 35 | : m_t(t), m_face_id(face_id) {} 36 | }; 37 | // the custom triangles are stored into a vector 38 | typedef std::list::const_iterator Iterator; 39 | // The following primitive provides the conversion facilities between 40 | // the custom triangle and point types and the CGAL ones 41 | struct My_triangle_primitive { 42 | public: 43 | // this is the type of data that the queries returns. For this example 44 | // we imagine that, for some reasons, we do not want to store the iterators 45 | // of the vector, but raw pointers. This is to show that the Id type 46 | // does not have to be the same as the one of the input parameter of the 47 | // constructor. 48 | typedef const My_triangle* Id; 49 | // CGAL types returned 50 | typedef K::Point_3 Point; // CGAL 3D point type 51 | typedef K::Triangle_3 Datum; // CGAL 3D triangle type 52 | private: 53 | Id m_mytri; // this is what the AABB tree stores internally 54 | public: 55 | My_triangle_primitive() {} // default constructor needed 56 | // the following constructor is the one that receives the iterators from the 57 | // iterator range given as input to the AABB_tree 58 | My_triangle_primitive(Iterator it) 59 | : m_mytri(&(*it)) {} 60 | const Id& id() const { return m_mytri; } 61 | // on the fly conversion from the internal data to the CGAL types 62 | Point convert(const geoflow::arr3f& p) const { 63 | return Point(p[0], p[1], p[2]); 64 | } 65 | Datum datum() const { 66 | return Datum( 67 | convert(m_mytri->m_t[0]), 68 | convert(m_mytri->m_t[1]), 69 | convert(m_mytri->m_t[2]) 70 | ); 71 | } 72 | // returns a reference point which must be on the primitive 73 | Point reference_point() const { 74 | return convert(m_mytri->m_t[0]); 75 | } 76 | }; 77 | 78 | namespace geoflow::nodes::stepedge { 79 | 80 | vec1f compute_mesh2pc_errors(const TriangleCollection& triangles, std::vector points) { 81 | // KD tree 82 | typedef CGAL::Search_traits_3 TreeTraits; 83 | typedef CGAL::Orthogonal_k_neighbor_search Neighbor_search; 84 | typedef Neighbor_search::Tree Tree; 85 | 86 | Tree tree(points.begin(), points.end()); 87 | vec1f errors; 88 | const unsigned int N = 1; 89 | for(const auto& triangle : triangles) { 90 | for (const auto& p : triangle) { 91 | Neighbor_search search(tree, K::Point_3(p[0],p[1],p[2]), N); 92 | for(Neighbor_search::iterator it = search.begin(); it != search.end(); ++it){ 93 | errors.push_back(std::sqrt(it->second)); 94 | } 95 | } 96 | } 97 | return errors; 98 | } 99 | 100 | vec1f compute_pc2pc_errors(PointCollection points_from, PointCollection points_to) { 101 | // KD tree 102 | typedef CGAL::Search_traits_3 TreeTraits; 103 | typedef CGAL::Orthogonal_k_neighbor_search Neighbor_search; 104 | typedef Neighbor_search::Tree Tree; 105 | 106 | std::vector points_to_; 107 | for (auto& p : points_to) { 108 | points_to_.push_back(K::Point_3(p[0],p[1],p[2])); 109 | } 110 | Tree tree(points_to_.begin(), points_to_.end()); 111 | vec1f errors; 112 | const unsigned int N = 1; 113 | for(const auto& p : points_from) { 114 | Neighbor_search search(tree, K::Point_3(p[0],p[1],p[2]), N); 115 | for(Neighbor_search::iterator it = search.begin(); it != search.end(); ++it) { 116 | errors.push_back(std::sqrt(it->second)); 117 | } 118 | } 119 | return errors; 120 | } 121 | 122 | std::string get_json_histogram(vec1f& values) { 123 | std::sort(values.begin(), values.end(), [](auto& p1, auto& p2) { 124 | return p1 < p2; 125 | }); 126 | const std::vector limits = {0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.5,2.0,2.5,3.0,4.0,5.0,6.0}; 127 | std::vector counts(limits.size(), 0); 128 | bool do_count; 129 | for (auto&e : values) { 130 | for( size_t i = 0; i=limits[i]; 133 | } else { 134 | do_count = e>=limits[i] && e My_AABB_traits; 161 | typedef CGAL::AABB_tree Tree; 162 | 163 | std::vector points; 164 | if(input("ipoints").get_connected_type() == typeid(PointCollection)) { 165 | auto ipoints = input("ipoints").get(); 166 | for (auto& p : ipoints) { 167 | points.push_back(K::Point_3(p[0], p[1], p[2])); 168 | } 169 | } else { 170 | auto& points_per_plane = input("ipoints").get(); 171 | for (auto& [plane_id, plane_pts] : points_per_plane) { 172 | if(plane_id>0) { 173 | for(auto& p : plane_pts.second) { 174 | points.push_back(K::Point_3(p.x(), p.y(), p.z())); 175 | } 176 | } 177 | } 178 | } 179 | auto& mtcs = input("triangles").get(); 180 | 181 | TriangleCollection trin; 182 | for (size_t j=0; j(labels[i]) == 1) 187 | trin.push_back(tc[i]); 188 | } 189 | } 190 | 191 | // do not run if one of the inputs is empty 192 | if(points.size()==0 || trin.size()==0) { 193 | std::cout << "Either input points or triangles are empty. Stopping execution."; 194 | return; 195 | } 196 | 197 | auto& face_ids = input("face_ids").get(); 198 | std::list triangles; 199 | for (size_t i=0; i < trin.size(); ++i) { 200 | triangles.push_back(My_triangle(trin[i], face_ids[i*3])); 201 | } 202 | 203 | // constructs AABB tree 204 | Tree tree(triangles.begin(), triangles.end()); 205 | tree.accelerate_distance_queries(); 206 | 207 | std::map> distances; 208 | vec1f point_errors; 209 | size_t i = 0; 210 | for(auto& p : points) { 211 | auto pt_and_id = tree.closest_point_and_primitive(p); 212 | auto sqd = CGAL::squared_distance(pt_and_id.first, p); 213 | auto fid = pt_and_id.second->m_face_id; 214 | distances[fid].push_back(sqd); 215 | point_errors.push_back(sqd); 216 | } 217 | 218 | double sum_total = 0; 219 | size_t len = 0; 220 | std::map face_error_map; 221 | for (auto& [fid, errors] : distances) { 222 | double sum_face = 0; 223 | len += errors.size(); 224 | for(double& error : errors) { 225 | sum_face += error; 226 | } 227 | face_error_map[fid] = float(CGAL::sqrt(sum_face/errors.size())); 228 | sum_total += sum_face; 229 | } 230 | float rms_error = float(CGAL::sqrt(sum_total/len)); 231 | 232 | vec1f face_errors; 233 | for (size_t i=0; i < trin.size(); ++i) { 234 | size_t fid = face_ids[i*3]; 235 | // push zero error if this face has no points/no error defined 236 | if(face_error_map.find(fid) == face_error_map.end()) { 237 | face_errors.push_back(0); 238 | face_errors.push_back(0); 239 | face_errors.push_back(0); 240 | } else { 241 | face_errors.push_back(face_error_map[fid]); 242 | face_errors.push_back(face_error_map[fid]); 243 | face_errors.push_back(face_error_map[fid]); 244 | } 245 | } 246 | 247 | vec1f mesh_error; 248 | for (auto& t : triangles) { 249 | mesh_error.push_back(rms_error); 250 | mesh_error.push_back(rms_error); 251 | mesh_error.push_back(rms_error); 252 | } 253 | 254 | // compute point error stats 255 | { 256 | auto json = get_json_histogram(point_errors); 257 | output("error_hist").set(json); 258 | } 259 | 260 | // compute mesh to point cloud errors 261 | { 262 | auto m2pc_errors = compute_mesh2pc_errors(trin, points); 263 | auto json = get_json_histogram(m2pc_errors); 264 | output("m2pc_error_hist").set(json); 265 | output("m2pc_error_max").set(m2pc_errors.back()); 266 | } 267 | 268 | output("point_errors").set(point_errors); 269 | output("face_errors").set(face_errors); 270 | output("mesh_error_f").set(rms_error); 271 | output("mesh_error").set(mesh_error); 272 | } 273 | 274 | void PC2PCDistancesCalculatorNode::process() { 275 | auto& pc_a = input("pointcloud_a").get(); 276 | auto& pc_b = input("pointcloud_b").get(); 277 | 278 | auto errors_from_a = compute_pc2pc_errors(pc_a, pc_b); 279 | 280 | auto& oterm = output("errors_a_to_b_"); 281 | for (auto& e: errors_from_a) { 282 | oterm.push_back(e); 283 | } 284 | 285 | output("errors_a_to_b").set(errors_from_a); 286 | } 287 | } -------------------------------------------------------------------------------- /src/pip_util.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "pip_util.hpp" 17 | 18 | pGridSet build_grid(const geoflow::vec3f& ring) { 19 | int Grid_Resolution = 20; 20 | 21 | int size = ring.size(); 22 | std::vector pgon; 23 | for (auto& p : ring) { 24 | pgon.push_back(new Pipoint{ p[0],p[1] }); 25 | } 26 | pGridSet grid_set = new GridSet(); 27 | // skip last point in the ring, ie the repetition of the first vertex 28 | GridSetup(&pgon[0], pgon.size(), Grid_Resolution, grid_set); 29 | for (int i = 0; i < size; i++) { 30 | delete pgon[i]; 31 | } 32 | return grid_set; 33 | } -------------------------------------------------------------------------------- /src/pip_util.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #pragma once 17 | 18 | #include "ptinpoly.h" 19 | #include 20 | #include 21 | 22 | pGridSet build_grid(const geoflow::vec3f& ring); -------------------------------------------------------------------------------- /src/plane_detect.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #pragma once 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | namespace planedect { 30 | 31 | typedef CGAL::Exact_predicates_inexact_constructions_kernel cgal_kernel; 32 | typedef cgal_kernel::Point_3 Point; 33 | typedef cgal_kernel::Vector_3 Vector; 34 | typedef cgal_kernel::Plane_3 Plane; 35 | 36 | class PlaneDS : public regiongrower::CGAL_RegionGrowerDS { 37 | public: 38 | geoflow::vec3f& normals; 39 | std::vector seed_planes; 40 | 41 | PlaneDS(geoflow::PointCollection& points, geoflow::vec3f& normals, size_t N=15) 42 | : CGAL_RegionGrowerDS(points, N), normals(normals) {}; 43 | 44 | // Note this crashes when idx.size()==1; 45 | inline double fit_plane(std::vector& idx, Plane& plane){ 46 | std::vector neighbor_points; 47 | for (auto i: idx) 48 | neighbor_points.push_back(Point(points[i][0], points[i][1], points[i][2])); 49 | double quality = linear_least_squares_fitting_3(neighbor_points.begin(),neighbor_points.end(),plane,CGAL::Dimension_tag<0>()); 50 | return quality; 51 | } 52 | 53 | virtual std::deque get_seeds() override { 54 | // seed generation 55 | typedef std::pair index_dist_pair; 56 | auto cmp = [](index_dist_pair left, index_dist_pair right) {return left.second < right.second;}; 57 | std::priority_queue, decltype(cmp)> pq(cmp); 58 | 59 | size_t i=0; 60 | Plane plane; 61 | for(size_t pi=0; pi seed_order; 68 | while (pq.size()>0) { 69 | seed_order.push_back(pq.top().first); pq.pop(); 70 | } 71 | return seed_order; 72 | } 73 | }; 74 | 75 | class PlaneRegion : public regiongrower::Region { 76 | public: 77 | using regiongrower::Region::Region; 78 | Plane plane; 79 | std::vector inliers; 80 | }; 81 | 82 | class DistAndNormalTester { 83 | public: 84 | float dist_thres; 85 | float normal_thres; 86 | size_t n_refit, refit_counter=0; 87 | 88 | DistAndNormalTester(float dist_thres=0.04, float normal_thres=0.9, size_t n_refit=5) : 89 | dist_thres(dist_thres), normal_thres(normal_thres), n_refit(n_refit) {}; 90 | 91 | bool is_valid(PlaneDS& cds, size_t candidate, size_t neighbour, PlaneRegion& shape) { 92 | Point p_c = Point(cds.points[candidate][0], cds.points[candidate][1], cds.points[candidate][2]); 93 | Vector n_c = Vector(cds.normals[candidate][0], cds.normals[candidate][1], cds.normals[candidate][2]); 94 | Point p = Point(cds.points[neighbour][0], cds.points[neighbour][1], cds.points[neighbour][2]); 95 | Vector n = Vector(cds.normals[neighbour][0], cds.normals[neighbour][1], cds.normals[neighbour][2]); 96 | 97 | if (shape.inliers.size()==0) { 98 | shape.plane = Plane(p_c,n_c); 99 | shape.inliers.push_back(candidate); 100 | } 101 | 102 | bool valid = (CGAL::squared_distance(shape.plane, p) < dist_thres) && (std::abs(shape.plane.orthogonal_vector()*n) > normal_thres); 103 | if (valid) { 104 | shape.inliers.push_back(neighbour); 105 | if (shape.inliers.size() % n_refit ==0) { 106 | cds.fit_plane(shape.inliers, shape.plane); 107 | } 108 | } 109 | return valid; 110 | } 111 | }; 112 | }; -------------------------------------------------------------------------------- /src/point_edge.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "point_edge.h" 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | Polygon_2 ring_to_cgal_polygon(geoflow::LinearRing& ring) { 24 | typedef AK::Point_2 Point_2; 25 | std::vector footprint_pts; 26 | for (auto p : ring) { 27 | footprint_pts.push_back(Point_2(p[0], p[1])); 28 | } 29 | return Polygon_2(footprint_pts.begin(), footprint_pts.end()); 30 | } -------------------------------------------------------------------------------- /src/point_edge.h: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #pragma once 17 | 18 | #include 19 | #include 20 | 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | // CGAL 29 | #include 30 | #include 31 | // #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | typedef CGAL::Exact_predicates_exact_constructions_kernel AK; 46 | typedef CGAL::Polygon_2 Polygon_2; 47 | 48 | #include // defines std::pair 49 | 50 | // #include "line_shape.cpp" 51 | #include "region_growing.h" 52 | // Types 53 | typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; 54 | typedef Kernel::Point_3 Point; 55 | typedef Kernel::Vector_3 Vector; 56 | typedef Kernel::Vector_2 Vector_2; 57 | typedef Kernel::Plane_3 Plane; 58 | typedef Kernel::Line_3 Line; 59 | // Point with normal vector stored in a std::pair. 60 | typedef boost::tuple PNL; 61 | typedef CGAL::Nth_of_tuple_property_map<0, PNL> Point_map; 62 | typedef CGAL::Nth_of_tuple_property_map<1, PNL> Normal_map; 63 | typedef CGAL::Nth_of_tuple_property_map<2, PNL> Label_map; 64 | typedef CGAL::Nth_of_tuple_property_map<3, PNL> IsWall_map; 65 | typedef CGAL::Nth_of_tuple_property_map<4, PNL> LineFit_map; 66 | typedef CGAL::Nth_of_tuple_property_map<5, PNL> JumpCount_map; 67 | typedef CGAL::Nth_of_tuple_property_map<6, PNL> IsStep_map; 68 | typedef CGAL::Nth_of_tuple_property_map<7, PNL> JumpEle_map; 69 | typedef CGAL::Nth_of_tuple_property_map<8, PNL> Id_map; 70 | typedef CGAL::Nth_of_tuple_property_map<9, PNL> IsHorizontal_map; 71 | typedef std::vector PNL_vector; 72 | // Concurrency 73 | #ifdef CGAL_LINKED_WITH_TBB 74 | typedef CGAL::Parallel_tag Concurrency_tag; 75 | #else 76 | typedef CGAL::Sequential_tag Concurrency_tag; 77 | #endif 78 | // search tree 79 | // typedef boost::tuple Point_and_int; 80 | // typedef CGAL::Random_points_in_cube_3 Random_points_iterator; 81 | typedef CGAL::Search_traits_3 Traits_base; 82 | typedef CGAL::Search_traits_adapter, 84 | Traits_base> TreeTraits; 85 | // typedef CGAL::Search_traits_3 TreeTraits; 86 | typedef CGAL::Orthogonal_k_neighbor_search Neighbor_search; 87 | typedef Neighbor_search::Tree Tree; 88 | 89 | // least squares stuff 90 | // typedef CGAL::Simple_cartesian SCK; 91 | typedef CGAL::Cartesian SCK; 92 | typedef SCK::Point_3 Point_SCK; 93 | typedef SCK::Line_3 Line_SCK; 94 | 95 | 96 | 97 | 98 | namespace bg = boost::geometry; 99 | typedef bg::model::d2::point_xy point_type; 100 | typedef bg::model::point point_type_3d; 101 | typedef bg::model::segment segment; 102 | 103 | struct config { 104 | public: 105 | int metrics_normal_k = 10; 106 | int metrics_plane_min_points = 25; 107 | float metrics_plane_epsilon = 0.2; 108 | float metrics_plane_normal_threshold = 0.75; 109 | float metrics_is_wall_threshold = 0.3; 110 | float metrics_is_horizontal_threshold = 0.9; 111 | int metrics_k_linefit = 15; 112 | int metrics_k_jumpcnt_elediff = 10; 113 | 114 | int classify_jump_count_min = 1; 115 | int classify_jump_count_max = 5; 116 | float classify_line_dist = 0.005; 117 | float classify_jump_ele = 1.0; 118 | 119 | float linedetect_dist_threshold = 0.3; 120 | int linedetect_min_segment_count = 8; 121 | int linedetect_k = 10; 122 | 123 | float step_height_threshold = 1.0; 124 | float zrange_threshold = 0.2; 125 | bool merge_segid = true; 126 | bool merge_zrange = false; 127 | bool merge_step_height = true; 128 | bool merge_unsegmented = false; 129 | bool merge_dangling_egdes = false; 130 | }; 131 | 132 | 133 | Polygon_2 ring_to_cgal_polygon(geoflow::LinearRing& ring); -------------------------------------------------------------------------------- /src/polygon_offset.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "stepedge_nodes.hpp" 22 | 23 | 24 | namespace geoflow::nodes::stepedge { 25 | 26 | void PolygonOffsetNode::process() { 27 | typedef CGAL::Exact_predicates_inexact_constructions_kernel K ; 28 | typedef K::FT FT; 29 | typedef K::Point_2 Point ; 30 | typedef CGAL::Polygon_2 Polygon_2 ; 31 | typedef CGAL::Straight_skeleton_2 Ss ; 32 | auto& ipolygons = vector_input("polygons"); 33 | auto& opolygons = vector_output("offset_polygons"); 34 | 35 | // offset 0 will crash the cgal algorithm 36 | if (offset==0) { 37 | opolygons = ipolygons.get_data_vec(); 38 | return; 39 | } 40 | 41 | for (size_t i=0; i(i); 43 | Polygon_2 poly; 44 | for (auto&p : lr) { 45 | poly.push_back(Point(p[0], p[1])); 46 | } 47 | if (poly.is_clockwise_oriented()){ 48 | poly.reverse_orientation(); 49 | } 50 | auto oss = CGAL::create_exterior_straight_skeleton_2(FT(offset), poly); 51 | auto offset_polygons = CGAL::create_offset_polygons_2(FT(offset), *oss); 52 | 53 | // std::cout << "there are " << offset_polygons.size() << " offset polygons\n"; 54 | // for (auto offset_poly : offset_polygons) { 55 | // the first offset_polygons appears to be some kind of bounding box, the second (and last) is the one we are looking for (at least it was in my test data) 56 | auto offset_poly = *offset_polygons.rbegin(); 57 | LinearRing lr_offset; 58 | for (auto& p : *offset_poly) { 59 | lr_offset.push_back(arr3f{float(p.x()), float(p.y()), 0}); 60 | } 61 | // } 62 | opolygons.push_back(lr_offset); 63 | } 64 | } 65 | 66 | } //namespace geoflow::nodes::stepedge -------------------------------------------------------------------------------- /src/polygon_triangulate.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include "point_edge.h" 25 | #include "polygon_util.hpp" 26 | #include "cdt_util.hpp" 27 | 28 | namespace geoflow::nodes::stepedge { 29 | 30 | namespace tri = tri_util; 31 | 32 | typedef CGAL::Polygon_2 Polygon_2; 33 | typedef CGAL::Plane_3 Plane_3; 34 | 35 | glm::vec3 calculate_normal(const LinearRing ring) 36 | { 37 | glm::vec3 normal(0, 0, 0); 38 | for (size_t i = 0; i < ring.size(); ++i) { 39 | const auto &curr = ring[i]; 40 | const auto &next = ring[(i + 1) % ring.size()]; 41 | normal[0] += (curr[1] - next[1]) * (curr[2] + next[2]); 42 | normal[1] += (curr[2] - next[2]) * (curr[0] + next[0]); 43 | normal[2] += (curr[0] - next[0]) * (curr[1] + next[1]); 44 | } 45 | return glm::normalize(normal); 46 | } 47 | 48 | double calculate_volume(const TriangleCollection& triangle_collection) { 49 | double sum = 0; 50 | for(const auto& t : triangle_collection) { 51 | auto a = Vector(t[0][0], t[0][1], t[0][2]); 52 | auto b = Vector(t[1][0], t[1][1], t[1][2]); 53 | auto c = Vector(t[2][0], t[2][1], t[2][2]); 54 | sum += CGAL::scalar_product(a, CGAL::cross_product(b, c)); 55 | } 56 | return sum/6; 57 | } 58 | 59 | // void mark_domains(CDT& cdt) { 60 | 61 | // std::list explorables; 62 | // auto fh_inf = cdt.infinite_face(); 63 | // fh_inf->info().set_interior(false); 64 | // explorables.push_back(fh_inf); 65 | 66 | // while (!explorables.empty()) { 67 | // auto fh = explorables.front(); 68 | // explorables.pop_front(); 69 | // // check the three neighbours 70 | // for (int i = 0; i < 3; ++i) { 71 | // CDT::Edge e(fh, i); 72 | // CDT::Face_handle fh_n = fh->neighbor(i); 73 | // if (fh_n->info().visited == false) { 74 | // if (cdt.is_constrained(e)) { 75 | // fh_n->info().set_interior(!fh_n->info().interior); 76 | // } else { 77 | // fh_n->info().set_interior(fh_n->info().interior); 78 | // } 79 | // explorables.push_back(fh_n); 80 | // } 81 | // } 82 | // } 83 | // } 84 | 85 | Polygon_2 project(geoflow::vec3f& ring, Plane_3& plane) { 86 | Polygon_2 poly_2d; 87 | for (auto& p : ring) { 88 | poly_2d.push_back(plane.to_2d(tri::K::Point_3(p[0], p[1], p[2]))); 89 | } 90 | return poly_2d; 91 | } 92 | 93 | void project_and_insert(geoflow::vec3f& ring, Plane_3& plane, tri::CDT& cdt) { 94 | auto pit_last = ring.end()-1; 95 | tri::CDT::Vertex_handle vh_next, vh_last, vh = cdt.insert(plane.to_2d(tri::K::Point_3((*pit_last)[0], (*pit_last)[1], (*pit_last)[2]))); 96 | vh_last = vh; 97 | vh->info().set_point(*pit_last); 98 | for (auto pit = ring.begin(); pit != ring.end(); ++pit) { 99 | if(pit==pit_last){ 100 | vh_next=vh_last; 101 | } else { 102 | vh_next = cdt.insert(plane.to_2d(tri::K::Point_3((*pit)[0], (*pit)[1], (*pit)[2]))); 103 | vh_next->info().set_point(*pit); 104 | } 105 | cdt.insert_constraint(vh, vh_next); 106 | vh = vh_next; 107 | } 108 | } 109 | 110 | void PolygonTriangulatorNode::triangulate_polygon(LinearRing& poly, vec3f& normals, TriangleCollection& triangles, size_t& ring_id, vec1i& ring_ids) { 111 | 112 | float dupe_threshold = (float) std::pow(10,-dupe_threshold_exp); 113 | if (is_degenerate(poly, dupe_threshold)) { 114 | LinearRing new_poly = fix_duplicates(poly, dupe_threshold); 115 | if(is_degenerate(new_poly, dupe_threshold)) { 116 | std::cout << "skipping ring with duplicates\n"; 117 | // dupe_rings.push_back(poly); 118 | return; 119 | } 120 | std::cout << "fixed ring with duplicates\n"; 121 | poly = new_poly; 122 | } 123 | auto normal = calculate_normal(poly); 124 | if (std::isnan(normal.x) || std::isnan(normal.y) || std::isnan(normal.z)){ 125 | std::cout << "degenerate normal: " << normal[0] << " " << normal[1] << " " << normal[2] << "\n"; 126 | return; 127 | } 128 | auto& p0 = poly[0]; 129 | Plane_3 plane(tri::K::Point_3(p0[0], p0[1], p0[2]), tri::K::Vector_3(normal.x, normal.y, normal.z)); 130 | 131 | // project and triangulate 132 | tri::CDT triangulation; 133 | // Polygon_2 poly_2d = project(poly, plane); 134 | // if(CGAL::abs(poly_2d.area())<1E-4) { 135 | // return; 136 | // } 137 | project_and_insert(poly, plane, triangulation); 138 | // triangulation.insert_constraint(poly_2d.vertices_begin(), poly_2d.vertices_end(), true); 139 | for (auto& ring : poly.interior_rings()) { 140 | project_and_insert(ring, plane, triangulation); 141 | // poly_2d = project(poly, plane); 142 | // triangulation.insert_constraint(poly_2d.vertices_begin(), poly_2d.vertices_end(), true); 143 | } 144 | 145 | if (triangulation.number_of_faces()==0) 146 | return; 147 | 148 | mark_domains(triangulation); 149 | 150 | // for (auto& e : triangulation.finite_edges()) { 151 | // auto source = e.first->vertex(triangulation.cw(e.second))->info().point; 152 | // auto target = e.first->vertex(triangulation.ccw(e.second))->info().point; 153 | // edges.push_back({ 154 | // arr3f{source}, 155 | // arr3f{target} 156 | // }); 157 | // bool constr = triangulation.is_constrained(e); 158 | // edges_constr.push_back(constr); 159 | // edges_constr.push_back(constr); 160 | // } 161 | 162 | for (tri::CDT::Finite_faces_iterator fit = triangulation.finite_faces_begin(); 163 | fit != triangulation.finite_faces_end(); ++fit) { 164 | 165 | if (!output_all_triangles && !fit->info().in_domain()) continue; 166 | 167 | Triangle triangle; 168 | triangle = { 169 | fit->vertex(0)->info().point, 170 | fit->vertex(1)->info().point, 171 | fit->vertex(2)->info().point 172 | }; 173 | for (size_t j = 0; j < 3; ++j) 174 | { 175 | normals.push_back({normal.x, normal.y, normal.z}); 176 | // values_out.push_back(values_in[vi]); 177 | ring_ids.push_back(ring_id); 178 | // nesting_levels.push_back(fit->info().nesting_level); 179 | } 180 | triangles.push_back(triangle); 181 | } 182 | } 183 | 184 | void PolygonTriangulatorNode::process() 185 | { 186 | auto &rings = vector_input("polygons"); 187 | // const auto &values_in = input("valuesf").get(); 188 | typedef uint32_t N; 189 | 190 | TriangleCollection triangles; 191 | MultiTriangleCollection multitrianglecols; 192 | vec3f normals; 193 | // vec1f values_out; 194 | vec1i ring_ids; 195 | auto& volumes = output("volumes"); 196 | // vec1i nesting_levels; 197 | // SegmentCollection edges; 198 | // vec1i edges_constr; 199 | // size_t vi = 0; 200 | // auto& dupe_rings = vector_output("dupe_rings"); 201 | if (rings.is_connected_type(typeid(LinearRing))) { 202 | for (size_t ri = 0; ri < rings.size(); ++ri) 203 | { 204 | auto poly_3d = rings.get(ri); 205 | TriangleCollection tc; 206 | triangulate_polygon(poly_3d, normals, tc, ri, ring_ids); 207 | triangles.insert(triangles.end(), tc.begin(), tc.end()); 208 | } 209 | volumes.push_back((float)calculate_volume(triangles)); 210 | multitrianglecols.push_back(triangles); 211 | output("multi_triangle_collections").set(multitrianglecols); 212 | } else if (rings.is_connected_type(typeid(std::unordered_map))) { 213 | // We are processing a building part here. We get a building part when we 214 | // cut a footprint into parts because of cutting off the underground part. 215 | for (size_t mi = 0; mi < rings.size(); ++mi) { 216 | auto meshmap = rings.get>(mi); 217 | double volume_sum = 0; 218 | for(auto& [sid, mesh] : meshmap) { 219 | TriangleCollection mesh_triangles; 220 | AttributeMap mesh_attributes; 221 | std::vector tri_labels; 222 | for (size_t ri = 0; ri(mi); 248 | TriangleCollection mesh_triangles; 249 | AttributeMap mesh_attributes; 250 | std::vector tri_labels; 251 | for (size_t ri = 0; ri(); 288 | std::unordered_map meshmap; 289 | 290 | auto& attrmap = mtc.get_attributes(); 291 | size_t i=0; 292 | for(auto& tc : mtc.get_tricollections()) { 293 | Mesh mesh; 294 | size_t j=0; 295 | for (auto& triangle : tc) { 296 | LinearRing lr; 297 | lr.push_back(triangle[0]); 298 | lr.push_back(triangle[1]); 299 | lr.push_back(triangle[2]); 300 | mesh.push_polygon( lr, std::get( attrmap[i]["labels"][j++] ) ); 301 | } 302 | 303 | meshmap[mtc.building_part_ids_[i++]] = mesh; 304 | } 305 | output("meshmap").set(meshmap); 306 | 307 | } 308 | 309 | } // namespace geoflow::nodes::stepedge 310 | -------------------------------------------------------------------------------- /src/polygon_util.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "polygon_util.hpp" 17 | 18 | namespace geoflow::nodes::stepedge { 19 | bool has_duplicates_ring(vec3f& poly, float& dupe_threshold) { 20 | auto pl = *poly.rbegin(); 21 | for (auto& p : poly) { 22 | 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) { 23 | return true; 24 | } 25 | pl=p; 26 | } 27 | return false; 28 | } 29 | 30 | bool is_degenerate(LinearRing& poly, float& dupe_threshold) { 31 | if (poly.size() < 3) return true; 32 | if (has_duplicates_ring(poly, dupe_threshold)) return true; 33 | 34 | for (auto& ring : poly.interior_rings()) { 35 | if (ring.size() < 3) return true; 36 | if (has_duplicates_ring(ring, dupe_threshold)) return true; 37 | } 38 | return false; 39 | } 40 | 41 | 42 | void fix_duplicates_ring(vec3f& poly, vec3f& new_ring, float& dupe_threshold) { 43 | auto pl = *poly.rbegin(); 44 | for (auto& p : poly) { 45 | 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)) { 46 | new_ring.push_back(p); 47 | } 48 | pl=p; 49 | } 50 | } 51 | 52 | LinearRing fix_duplicates(LinearRing& poly, float& dupe_threshold) { 53 | LinearRing new_lr; 54 | fix_duplicates_ring(poly, new_lr, dupe_threshold); 55 | 56 | for (auto& ring : poly.interior_rings()) { 57 | vec3f new_ring; 58 | fix_duplicates_ring(ring, new_ring, dupe_threshold); 59 | new_lr.interior_rings().push_back(new_ring); 60 | } 61 | return new_lr; 62 | } 63 | } -------------------------------------------------------------------------------- /src/polygon_util.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include 17 | 18 | namespace geoflow::nodes::stepedge { 19 | bool has_duplicates_ring(vec3f& poly, float& dupe_threshold); 20 | bool is_degenerate(LinearRing& poly, float& dupe_threshold); 21 | LinearRing fix_duplicates(LinearRing& poly, float& dupe_threshold); 22 | } -------------------------------------------------------------------------------- /src/region_growing.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | #include 19 | #include "region_growing.h" 20 | 21 | using namespace std; 22 | using namespace linedect; 23 | 24 | LineDetector::LineDetector(vector &points) { 25 | auto size = points.size(); 26 | indexed_points.reserve(size); 27 | size_t i=0; 28 | for(auto p: points) 29 | indexed_points.push_back(std::make_pair(p,i++)); 30 | point_segment_idx.resize(size, 0); 31 | // point_seed_flags.resize(size, true); 32 | Tree tree; 33 | tree.insert(indexed_points.begin(), indexed_points.end()); 34 | neighbours.resize(size); 35 | for(auto pi : indexed_points){ 36 | auto p = pi.first; 37 | neighbours[pi.second].reserve(N); 38 | Neighbor_search search(tree, p, N); 39 | for (auto neighbour : search) { 40 | neighbours[pi.second].push_back(neighbour.first.second); 41 | } 42 | } 43 | } 44 | 45 | LineDetector::LineDetector(vector &points, vector> neighbours):neighbours(neighbours) { 46 | auto size = points.size(); 47 | indexed_points.reserve(size); 48 | size_t i=0; 49 | for(auto p: points) 50 | indexed_points.push_back(std::make_pair(p,i++)); 51 | point_segment_idx.resize(size, 0); 52 | // point_seed_flags.resize(size, true); 53 | } 54 | 55 | vector LineDetector::get_point_indices(size_t shape_id) { 56 | vector result; 57 | for (auto pi:indexed_points){ 58 | auto idx = pi.second; 59 | if (point_segment_idx[idx] == shape_id) 60 | result.push_back(idx); 61 | } 62 | return result; 63 | } 64 | 65 | geoflow::Segment LineDetector::project(const size_t i1, const size_t i2) { 66 | const auto& l = segment_shapes[point_segment_idx[i1]]; 67 | const auto& p1 = indexed_points[i1].first; 68 | const auto& p2 = indexed_points[i2].first; 69 | auto p1n = l.projection(p1); 70 | auto p2n = l.projection(p2); 71 | return geoflow::Segment( 72 | geoflow::arr3f{float(p1n.x()), float(p1n.y()), float(p1n.z())}, 73 | geoflow::arr3f{float(p2n.x()), float(p2n.y()), float(p2n.z())} 74 | ); 75 | } 76 | SCK::Segment_3 LineDetector::project_cgal(const size_t i1, const size_t i2, float extension) { 77 | const auto& l = segment_shapes[point_segment_idx[i1]]; 78 | const auto& p1 = indexed_points[i1].first; 79 | const auto& p2 = indexed_points[i2].first; 80 | auto p1n = l.projection(p1); 81 | auto p2n = l.projection(p2); 82 | // extend the linesegment a bit in both directions 83 | auto v = (p2n-p1n); 84 | v = v/CGAL::sqrt(v.squared_length()); 85 | p1n = p1n - v*extension; 86 | p2n = p2n + v*extension; 87 | return SCK::Segment_3( 88 | SCK::Point_3(CGAL::to_double(p1n.x()), CGAL::to_double(p1n.y()), CGAL::to_double(p1n.z())), 89 | SCK::Point_3(CGAL::to_double(p2n.x()), CGAL::to_double(p2n.y()), CGAL::to_double(p2n.z())) 90 | ); 91 | } 92 | 93 | size_t LineDetector::get_bounded_edges(geoflow::SegmentCollection& edges) { 94 | std::vector id_mins; 95 | std::map ordered_segments; 96 | for(auto seg: segment_shapes){ 97 | auto l = seg.second; 98 | auto l_idx = get_point_indices(seg.first); 99 | // std::cout << l.direction() << ", #Pts: " << l_idx.size() < maxpl) { 121 | maxpl=pl; 122 | maxpl_id = id; 123 | } 124 | } 125 | // keep track of lowest point id 126 | id_min = std::min(id_min, id); 127 | } 128 | Point p0,p1; 129 | if (minpl_id < maxpl_id) { 130 | p0 = (point_on_line + minpl*l_normal); 131 | p1 = (point_on_line + maxpl*l_normal); 132 | } else { 133 | p1 = (point_on_line + minpl*l_normal); 134 | p0 = (point_on_line + maxpl*l_normal); 135 | } 136 | ordered_segments[id_min] = geoflow::Segment(); 137 | ordered_segments[id_min][0] = { 138 | float(p0.x()), 139 | float(p0.y()), 140 | float(p0.z()) 141 | }; 142 | ordered_segments[id_min][1] = { 143 | float(p1.x()), 144 | float(p1.y()), 145 | float(p1.z()) 146 | }; 147 | } 148 | // deliver the edges in order 149 | for (auto& kv : ordered_segments) { 150 | edges.push_back(kv.second); 151 | } 152 | return ordered_segments.size(); 153 | } 154 | inline Line LineDetector::fit_line(vector& neighbour_idx){ 155 | vector neighbor_points; 156 | for (auto neighbor_id: neighbour_idx){ 157 | neighbor_points.push_back(indexed_points[neighbor_id].first); 158 | } 159 | Line line; 160 | linear_least_squares_fitting_3(neighbor_points.begin(),neighbor_points.end(),line,CGAL::Dimension_tag<0>()); 161 | return line; 162 | } 163 | 164 | std::vector LineDetector::detect(){ 165 | // seed generation 166 | typedef pair index_dist_pair; 167 | auto cmp = [](index_dist_pair left, index_dist_pair right) {return left.second < right.second;}; 168 | priority_queue, decltype(cmp)> pq(cmp); 169 | 170 | // size_t i=0; 171 | for(auto pi : indexed_points){ 172 | if (point_segment_idx[pi.second]==0) { 173 | auto p = pi.first; 174 | auto line = fit_line(neighbours[pi.second]); 175 | auto line_dist = CGAL::squared_distance(line, p); 176 | pq.push(index_dist_pair(pi.second, line_dist)); 177 | } 178 | } 179 | 180 | std::vector new_regions; 181 | 182 | // region growing from seed points 183 | while(pq.size()>0) { 184 | auto idx = pq.top().first; pq.pop(); 185 | // if (point_seed_flags[idx]){ 186 | if (point_segment_idx[idx]==0){ 187 | if (grow_region(idx)) 188 | new_regions.push_back(region_counter); 189 | region_counter++; 190 | } 191 | } 192 | return new_regions; 193 | } 194 | 195 | inline bool LineDetector::valid_candidate(Line &line, Point &p) { 196 | return CGAL::squared_distance(line, p) < dist_thres; 197 | } 198 | 199 | bool LineDetector::grow_region(size_t seed_idx) { 200 | auto p = indexed_points[seed_idx]; 201 | auto line = fit_line(neighbours[seed_idx]); 202 | segment_shapes[region_counter] = line; 203 | 204 | vector points_in_region; 205 | vector idx_in_region; 206 | stack candidates; 207 | candidates.push(seed_idx); 208 | point_segment_idx[seed_idx] = region_counter; 209 | points_in_region.push_back(p.first); 210 | idx_in_region.push_back(p.second); 211 | 212 | while (candidates.size()>0){ 213 | auto candidate_id = candidates.top(); candidates.pop(); 214 | auto cp = indexed_points[candidate_id].first; 215 | for (auto n_id: neighbours[candidate_id]){ 216 | if (point_segment_idx[n_id]!=0) 217 | continue; 218 | // point_seed_flags[n_id] = false; // this point can no longer be used as seed 219 | if (valid_candidate(segment_shapes[region_counter], indexed_points[n_id].first)){ 220 | point_segment_idx[n_id] = region_counter; 221 | candidates.push(n_id); 222 | points_in_region.push_back(indexed_points[n_id].first); 223 | idx_in_region.push_back(n_id); 224 | Line line; 225 | linear_least_squares_fitting_3(points_in_region.begin(),points_in_region.end(),line,CGAL::Dimension_tag<0>()); 226 | segment_shapes[region_counter] = line; 227 | } 228 | 229 | } 230 | } 231 | // undo region if it doesn't satisfy quality criteria 232 | if (points_in_region.size(). 16 | #pragma once 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | typedef CGAL::Cartesian SCK; 29 | 30 | #include 31 | 32 | namespace linedect { 33 | typedef CGAL::Exact_predicates_inexact_constructions_kernel cgal_kernel; 34 | typedef cgal_kernel::Vector_3 Vector; 35 | typedef cgal_kernel::Point_3 Point; 36 | typedef cgal_kernel::Line_3 Line; 37 | 38 | using namespace std; 39 | 40 | typedef vector> NeighbourVec; 41 | 42 | class LineDetector { 43 | 44 | typedef std::pair point_index; 45 | typedef CGAL::Search_traits_3 Traits_base; 46 | typedef CGAL::Search_traits_adapter, 48 | Traits_base> TreeTraits; 49 | typedef CGAL::Orthogonal_k_neighbor_search Neighbor_search; 50 | typedef Neighbor_search::Tree Tree; 51 | 52 | typedef unordered_map SegShapes; 53 | typedef SegShapes::iterator SSIterator; 54 | 55 | vector indexed_points; 56 | // Tree tree; 57 | // vector point_seed_flags; 58 | NeighbourVec neighbours; 59 | size_t region_counter=1; 60 | 61 | public: 62 | vector point_segment_idx; // 0=unsegmented, maybe put this on the heap... 63 | SegShapes segment_shapes; 64 | int N = 5; 65 | double dist_thres = 0.2*0.2; 66 | size_t min_segment_count = 4; 67 | 68 | LineDetector(vector &points); 69 | LineDetector(vector &points, vector> neighbours); 70 | vector get_point_indices(size_t shape_id); 71 | geoflow::Segment project(const size_t i1, const size_t i2); 72 | SCK::Segment_3 project_cgal(const size_t i1, const size_t i2, float extension); 73 | size_t get_bounded_edges(geoflow::SegmentCollection& edges); 74 | std::vector detect(); 75 | 76 | private: 77 | inline Line fit_line(vector& neighbour_idx); 78 | inline bool valid_candidate(Line &line, Point &p); 79 | bool grow_region(size_t seed_idx); 80 | }; 81 | } -------------------------------------------------------------------------------- /src/snap_round.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of gfp-building-reconstruction 2 | // Copyright (C) 2018-2022 Ravi Peters 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | #include "stepedge_nodes.hpp" 17 | #include "polygon_util.hpp" 18 | #include "cdt_util.hpp" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | 29 | 30 | namespace geoflow::nodes::stepedge { 31 | 32 | typedef tri_util::CDT CDT; 33 | 34 | void SnapRoundNode::process() { 35 | typedef CGAL::Quotient Number_type; 36 | typedef CGAL::Cartesian QKernel; 37 | typedef CGAL::Snap_rounding_traits_2 QTraits; 38 | typedef std::list Segment_list_2; 39 | typedef std::list Polyline_2; 40 | typedef std::list Polyline_list_2; 41 | 42 | typedef CGAL::Arr_segment_traits_2 Traits_2; 43 | typedef Traits_2::X_monotone_curve_2 ARSegment_2; 44 | typedef CGAL::Arrangement_2 QArrangement_2; 45 | 46 | typedef CGAL::Arr_walk_along_line_point_location Walk_pl; 47 | 48 | struct overlay_functor { 49 | FaceInfo operator()(const FaceInfo& a, FaceInfo& b) { 50 | b=a; 51 | return a; 52 | } 53 | }; 54 | typedef CGAL::Arr_face_overlay_traits Overlay_traits; 58 | 59 | auto arr = input("arrangement").get(); 60 | 61 | Segment_list_2 seg_list; 62 | Polyline_list_2 output_list; 63 | for (auto he : arr.edge_handles()) { 64 | auto& s = he->source()->point(); 65 | auto& t = he->target()->point(); 66 | seg_list.push_back ( 67 | QTraits::Segment_2( 68 | QTraits::Point_2(CGAL::to_double(s.x()), CGAL::to_double(s.y())), 69 | QTraits::Point_2(CGAL::to_double(t.x()), CGAL::to_double(t.y()))) 70 | ); 71 | } 72 | 73 | CGAL::snap_rounding_2 74 | (seg_list.begin(), seg_list.end(), output_list, pixel_size, true, false, 1); 75 | 76 | // QArrangement_2 arrSR; 77 | Arrangement_2 arrSR; 78 | // int counter = 0; 79 | Polyline_list_2::const_iterator polyline_it; 80 | for (polyline_it = output_list.begin(); polyline_it != output_list.end(); ++polyline_it) { 81 | // std::cout << "Polyline number " << ++counter << ":\n"; 82 | 83 | if (polyline_it->size() > 1) { 84 | Polyline_2::const_iterator p = polyline_it->begin(); 85 | Polyline_2::const_iterator p_prev = p; 86 | for (++p; p != polyline_it->end(); ++p) { 87 | // std::cout << " (" << CGAL::to_double(p->x()) << ":" << CGAL::to_double(p->y()) << ")\n"; 88 | insert(arrSR, Segment_2( 89 | Point_2(CGAL::to_double(p->x()), CGAL::to_double(p->y())), 90 | Point_2(CGAL::to_double(p_prev->x()), CGAL::to_double(p_prev->y())) 91 | )); 92 | p_prev = p; 93 | } 94 | } 95 | } 96 | std::cout << "vcount before: " << arr.number_of_vertices() << "\n"; 97 | std::cout << "vcount after: " << arrSR.number_of_vertices() << "\n"; 98 | 99 | Walk_pl walk_pl (arr); 100 | for (auto& arrFace : arrSR.face_handles()) { 101 | LinearRing poly; 102 | if(arrFace->is_fictitious()) continue; 103 | 104 | if (arrangementface_to_polygon(arrFace, poly)) { 105 | 106 | CDT cdt = tri_util::create_from_polygon(poly); 107 | for (CDT::Finite_faces_iterator fit = cdt.finite_faces_begin(); 108 | fit != cdt.finite_faces_end(); ++fit) { 109 | 110 | if (!fit->info().in_domain()) continue; 111 | auto p = CGAL::centroid(cdt.triangle(fit)); 112 | auto obj = walk_pl.locate( Walk_pl::Arrangement_2::Point_2(p.x(), p.y()) ); 113 | 114 | // std::cout << "The point (" << p << ") is located "; 115 | if (auto f = boost::get(&obj)) { // located inside a face 116 | // std::cout << "inside " 117 | // << (((*f)->is_unbounded()) ? "the unbounded" : "a bounded") 118 | // << " face." << std::endl; 119 | arrFace->data() = (*f)->data(); 120 | } 121 | // else if (auto e = boost::get(&obj)) // located on an edge 122 | // std::cout << "on an edge: " << (*e)->curve() << std::endl; 123 | // else if (auto v = boost::get(&obj)) // located on a vertex 124 | // std::cout << "on " << (((*v)->is_isolated()) ? "an isolated" : "a") 125 | // << " vertex: " << (*v)->point() << std::endl; 126 | // else CGAL_error_msg("Invalid object."); 127 | } 128 | } 129 | 130 | } 131 | 132 | // Arrangement_2 arr_overlay; 133 | // Overlay_traits overlay_traits; 134 | // overlay(arr, arrSR, arr_overlay, overlay_traits); 135 | 136 | output("arrangement").set(arrSR); 137 | 138 | } 139 | } -------------------------------------------------------------------------------- /src/tinsimp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "tinsimp.hpp" 14 | 15 | namespace tinsimp { 16 | 17 | void mark_domains(CDT& ct, 18 | CDT::Face_handle start, 19 | int index, 20 | std::list& border) { 21 | if (start->info().nesting_level != -1) { 22 | return; 23 | } 24 | std::list queue; 25 | queue.push_back(start); 26 | while (!queue.empty()) { 27 | CDT::Face_handle fh = queue.front(); 28 | queue.pop_front(); 29 | if (fh->info().nesting_level == -1) { 30 | fh->info().nesting_level = index; 31 | for (int i = 0; i < 3; i++) { 32 | CDT::Edge e(fh, i); 33 | CDT::Face_handle n = fh->neighbor(i); 34 | if (n->info().nesting_level == -1) { 35 | if (ct.is_constrained(e)) border.push_back(e); 36 | else queue.push_back(n); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | /** 43 | * mark the triangles that are inside the original 2D polygon. 44 | * explore set of facets connected with non constrained edges, 45 | * and attribute to each such set a nesting level. 46 | * start from facets incident to the infinite vertex, with a nesting 47 | * level of 0. Then recursively consider the non-explored facets incident 48 | * to constrained edges bounding the former set and increase the nesting level by 1. 49 | * facets in the domain are those with an odd nesting level. 50 | */ 51 | void mark_domains(CDT& cdt) { 52 | // for (CDT::All_faces_iterator it = cdt.all_faces_begin(); it != cdt.all_faces_end(); ++it) { 53 | // it->info().nesting_level = -1; 54 | // } 55 | std::list border; 56 | mark_domains(cdt, cdt.infinite_face(), 0, border); 57 | while (!border.empty()) { 58 | CDT::Edge e = border.front(); 59 | border.pop_front(); 60 | CDT::Face_handle n = e.first->neighbor(e.second); 61 | if (n->info().nesting_level == -1) { 62 | mark_domains(cdt, n, e.first->info().nesting_level + 1, border); 63 | } 64 | } 65 | } 66 | 67 | struct PointXYHash { 68 | std::size_t operator()(Point const& p) const noexcept { 69 | std::size_t h1 = std::hash{}(p.x()); 70 | std::size_t h2 = std::hash{}(p.y()); 71 | return h1 ^ (h2 << 1); 72 | } 73 | }; 74 | struct PointXYEqual { 75 | bool operator()(Point const& p1, Point const& p2) const noexcept { 76 | auto ex = p1.x() == p2.x(); 77 | auto ey = p1.y() == p2.y(); 78 | return ex && ey; 79 | } 80 | }; 81 | 82 | inline double compute_error(Point &p, CDT::Face_handle &face); 83 | 84 | //--- TIN Simplification 85 | // Greedy insertion/incremental refinement algorithm adapted from "Fast polygonal approximation of terrain and height fields" by Garland, Michael and Heckbert, Paul S. 86 | inline double compute_error(Point &p, CDT::Face_handle &face) { 87 | if(!face->info().plane) 88 | face->info().plane = new CGAL::Plane_3( 89 | face->vertex(0)->point(), 90 | face->vertex(1)->point(), 91 | face->vertex(2)->point()); 92 | if(!face->info().points_inside) 93 | face->info().points_inside = new std::vector(); 94 | 95 | auto plane = face->info().plane; 96 | auto interpolate = -plane->a()/plane->c() * p.x() - plane->b()/plane->c()*p.y() - plane->d()/plane->c(); 97 | double error = std::fabs(interpolate - p.z()); 98 | return error; 99 | } 100 | 101 | // void greedy_insert(CDT &T, const std::vector> &pts, double threshold) { 102 | // // Convert all elevation points to CGAL points 103 | // std::vector cpts; 104 | // cpts.reserve(pts.size()); 105 | // for (auto& p : pts) { 106 | // cpts.push_back(Point(p[0], p[1], p[2])); 107 | // } 108 | 109 | // greedy_insert(T, cpts, threshold); 110 | // } 111 | 112 | void greedy_insert(CDT &T, std::vector &cpts, double threshold, size_t minpts) { 113 | // assumes all lidar points are inside a triangle 114 | Heap heap; 115 | 116 | // compute initial point errors, build heap, store point indices in triangles 117 | { 118 | std::unordered_set set; 119 | for (int i = 0; i < cpts.size(); i++) { 120 | auto p = cpts[i]; 121 | CDT::Locate_type lt; 122 | int li; 123 | auto not_duplicate = set.insert(p).second; 124 | if(not_duplicate){ 125 | CDT::Face_handle face = T.locate(p, lt, li); 126 | if (lt == CDT::EDGE || lt == CDT::FACE) { 127 | auto e = compute_error(p, face); 128 | auto handle = heap.push(point_error(i, e)); 129 | face->info().points_inside->push_back(handle); 130 | } 131 | } 132 | } 133 | } 134 | std::cout << "prepared tinsimp...\n"; 135 | 136 | // insert points, update errors of affected triangles until threshold error is reached 137 | size_t insert_cnt = 0; 138 | // while ( (!heap.empty() && heap.top().error > threshold) || insert_cnt < minpts ) { 139 | while (!heap.empty() && heap.top().error > threshold) { 140 | // get top element (with largest error) from heap 141 | auto maxelement = heap.top(); 142 | auto max_p = cpts[maxelement.index]; 143 | 144 | // get triangles that will change after inserting this max_p 145 | std::vector faces; 146 | T.get_conflicts(max_p, std::back_inserter(faces)); 147 | 148 | // insert max_p in triangulation 149 | auto face_hint = faces[0]; 150 | auto v = T.insert(max_p, face_hint); 151 | face_hint = v->face(); 152 | ++insert_cnt; 153 | 154 | // update clear info of triangles that just changed, collect points that were inside these triangles 155 | std::vector points_to_update; 156 | for (auto face : faces) { 157 | if (face->info().plane) { 158 | delete face->info().plane; 159 | face->info().plane = nullptr; 160 | } 161 | if (face->info().points_inside) { 162 | for (auto h : *face->info().points_inside) { 163 | if (maxelement.index != (*h).index) 164 | points_to_update.push_back(h); 165 | } 166 | std::vector().swap((*face->info().points_inside)); 167 | } 168 | } 169 | 170 | // remove the point we just inserted in the triangulation from the heap 171 | heap.pop(); 172 | 173 | // update the errors of affected elevation points 174 | for (auto curelement : points_to_update) { 175 | auto p = cpts[(*curelement).index]; 176 | //auto containing_face = T.locate(p, face_hint); 177 | CDT::Locate_type lt; 178 | int li; 179 | CDT::Face_handle containing_face = T.locate(p, lt, li, face_hint); 180 | if (lt == CDT::EDGE || lt == CDT::FACE) { 181 | const double e = compute_error(p, containing_face); 182 | const point_error new_pe = point_error((*curelement).index, e); 183 | heap.update(curelement, new_pe); 184 | containing_face->info().points_inside->push_back(curelement); 185 | } 186 | } 187 | } 188 | 189 | // insert more points so we can guarantee the minpts number of interior points 190 | if (heap.size() < minpts) minpts = heap.size(); 191 | if(insert_cnt < minpts) { 192 | std::list> remaining_pts; 193 | for (auto hit = heap.ordered_begin(); hit != heap.ordered_end(); ++hit) { 194 | auto& p = cpts[(*hit).index]; 195 | remaining_pts.push_back(p); 196 | } 197 | 198 | auto rand = CGAL::Random(13374269); 199 | while (insert_cnt < minpts) { 200 | auto it = remaining_pts.begin(); 201 | auto r = rand.get_int(0, remaining_pts.size()); 202 | while (r > 0) { 203 | ++it; 204 | --r; 205 | } 206 | T.insert(*it); 207 | ++insert_cnt; 208 | remaining_pts.erase(it); 209 | } 210 | } 211 | 212 | //cleanup the stuff I put in face info of triangles 213 | for (CDT::Finite_faces_iterator fit = T.finite_faces_begin(); 214 | fit != T.finite_faces_end(); ++fit) { 215 | if (fit->info().plane) { 216 | delete fit->info().plane; 217 | fit->info().plane = nullptr; 218 | } 219 | if (fit->info().points_inside) { 220 | delete fit->info().points_inside; 221 | fit->info().points_inside = nullptr; 222 | } 223 | } 224 | 225 | } 226 | 227 | } -------------------------------------------------------------------------------- /src/tinsimp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | namespace tinsimp { 15 | 16 | // fibonacci heap for greedy insertion code 17 | struct point_error { 18 | point_error(size_t i, double e) : index(i), error(e){} 19 | size_t index; 20 | double error; 21 | size_t line_id; 22 | 23 | bool operator<(point_error const & rhs) const 24 | { 25 | return error < rhs.error; 26 | } 27 | }; 28 | typedef boost::heap::fibonacci_heap Heap; 29 | typedef Heap::handle_type heap_handle; 30 | 31 | typedef CGAL::Exact_predicates_inexact_constructions_kernel K; 32 | typedef CGAL::Projection_traits_xy_3 Gt; 33 | typedef CGAL::Triangulation_vertex_base_with_id_2 Vb; 34 | struct FaceInfo2 35 | { 36 | FaceInfo2() {} 37 | std::vector* points_inside = nullptr; 38 | CGAL::Plane_3* plane = nullptr; 39 | int nesting_level = -1; 40 | bool in_domain() { 41 | return nesting_level % 2 == 1; 42 | } 43 | }; 44 | typedef CGAL::Triangulation_face_base_with_info_2 Fbb; 45 | typedef CGAL::Constrained_triangulation_face_base_2 Fb; 46 | typedef CGAL::Triangulation_data_structure_2 Tds; 47 | typedef CGAL::Exact_predicates_tag Itag; 48 | typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; 49 | typedef CGAL::Constrained_triangulation_plus_2 CT; 50 | typedef CDT::Point Point; 51 | 52 | // void greedy_insert(CDT &T, const std::vector> &pts, double threshold); 53 | void greedy_insert(CDT &T, std::vector &cpts, double threshold, size_t minpts=0); 54 | void mark_domains(CDT& cdt); 55 | } -------------------------------------------------------------------------------- /thirdparty/ptinpoly/ptinpoly.h: -------------------------------------------------------------------------------- 1 | /* ptinpoly.h - point in polygon inside/outside algorithms header file. 2 | * 3 | * by Eric Haines, 3D/Eye Inc, erich@eye.com 4 | */ 5 | 6 | #ifdef __cplusplus 7 | extern "C" 8 | { 9 | #endif 10 | /* =========================== System Related ============================= */ 11 | /* SRAN initializes random number generator, if needed */ 12 | #define SRAN() srand(1) 13 | /* RAN01 returns a double from [0..1) */ 14 | #define RAN01() ((double)rand() / 32768.0) 15 | 16 | /* =========== Grid stuff ================================================= */ 17 | 18 | #define GR_FULL_VERT 0x01 /* line crosses vertically */ 19 | #define GR_FULL_HORZ 0x02 /* line crosses horizontally */ 20 | 21 | typedef struct { 22 | double xa,ya ; 23 | double minx, maxx, miny, maxy ; 24 | double ax, ay ; 25 | double slope, inv_slope ; 26 | } GridRec, *pGridRec; 27 | 28 | #define GC_BL_IN 0x0001 /* bottom left corner is in (else out) */ 29 | #define GC_BR_IN 0x0002 /* bottom right corner is in (else out) */ 30 | #define GC_TL_IN 0x0004 /* top left corner is in (else out) */ 31 | #define GC_TR_IN 0x0008 /* top right corner is in (else out) */ 32 | #define GC_L_EDGE_HIT 0x0010 /* left edge is crossed */ 33 | #define GC_R_EDGE_HIT 0x0020 /* right edge is crossed */ 34 | #define GC_B_EDGE_HIT 0x0040 /* bottom edge is crossed */ 35 | #define GC_T_EDGE_HIT 0x0080 /* top edge is crossed */ 36 | #define GC_B_EDGE_PARITY 0x0100 /* bottom edge parity */ 37 | #define GC_T_EDGE_PARITY 0x0200 /* top edge parity */ 38 | #define GC_AIM_L (0<<10) /* aim towards left edge */ 39 | #define GC_AIM_B (1<<10) /* aim towards bottom edge */ 40 | #define GC_AIM_R (2<<10) /* aim towards right edge */ 41 | #define GC_AIM_T (3<<10) /* aim towards top edge */ 42 | #define GC_AIM_C (4<<10) /* aim towards a corner */ 43 | #define GC_AIM 0x1c00 44 | 45 | #define GC_L_EDGE_CLEAR GC_L_EDGE_HIT 46 | #define GC_R_EDGE_CLEAR GC_R_EDGE_HIT 47 | #define GC_B_EDGE_CLEAR GC_B_EDGE_HIT 48 | #define GC_T_EDGE_CLEAR GC_T_EDGE_HIT 49 | 50 | #define GC_ALL_EDGE_CLEAR (GC_L_EDGE_HIT | \ 51 | GC_R_EDGE_HIT | \ 52 | GC_B_EDGE_HIT | \ 53 | GC_T_EDGE_HIT ) 54 | 55 | typedef struct { 56 | short tot_edges ; 57 | unsigned short gc_flags ; 58 | GridRec *gr ; 59 | } GridCell, *pGridCell; 60 | 61 | typedef struct { 62 | int xres, yres ; /* grid size */ 63 | int tot_cells ; /* xres * yres */ 64 | double minx, maxx, miny, maxy ; /* bounding box */ 65 | double xdelta, ydelta ; 66 | double inv_xdelta, inv_ydelta ; 67 | double *glx, *gly ; 68 | GridCell *gc ; 69 | } GridSet, *pGridSet ; 70 | 71 | typedef struct { 72 | double x, y; 73 | } Pipoint, *pPipoint; 74 | 75 | /* add a little to the limits of the polygon bounding box to avoid precision 76 | * problems. 77 | */ 78 | #define EPSILON 0.00001 79 | 80 | /* The following structure is associated with a polygon */ 81 | typedef struct { 82 | int id ; /* vertex number of edge */ 83 | int full_cross ; /* 1 if extends from top to bottom */ 84 | double minx, maxx ; /* X bounds for bin */ 85 | } Edge, *pEdge ; 86 | 87 | void GridSetup(pPipoint pgon[], int numverts, int resolution, pGridSet p_gs); 88 | int AddGridRecAlloc(pGridCell p_gc, double xa, double ya, double xb, double yb, double eps); 89 | void GridCleanup(pGridSet p_gs); 90 | int GridTest(pGridSet p_gs, pPipoint point); 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif --------------------------------------------------------------------------------