├── .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