ReUseX  0.0.5
3D Point Cloud Processing for Building Reuse
Loading...
Searching...
No Matches
CellComplex.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 Povl Filip Sonne-Frederiksen
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#pragma once
6#include "reusex/core/logging.hpp"
7#include "reusex/geometry/Registry.hpp"
8#include "reusex/types.hpp"
9
10#include <Eigen/StdVector>
11#include <boost/graph/adjacency_list.hpp>
12#include <boost/graph/graph_traits.hpp>
13#include <boost/iterator/filter_iterator.hpp>
14#include <fmt/format.h>
15#include <pcl/pcl_base.h>
16#include <pcl/point_cloud.h>
17#include <pcl/point_types.h>
18#include <range/v3/range/concepts.hpp>
19#include <range/v3/view/zip.hpp>
20
21#include <set>
22#include <string>
23#include <vector>
24
25namespace reusex::geometry {
26
27// Tags to distinguish node types in CellComplex
28enum class CellNodeType { cell, face, vertex };
29
31struct FaceData {
33};
34struct CellData {
35 std::set<int> wall_ids{};
36};
37
38// Generic vertex bundle
40 // TODO: Refactor ID field to be type-specific instead of generic
41 // category=Geometry estimate=3h
42 // Current design stores ID at VertexProperties level, but IDs may have
43 // different semantics for different node types (vertex/face/cell). Consider:
44 // 1. Move ID into CellVertexData/FaceData/CellData variant members
45 // 2. Use std::optional<int> for types that don't need IDs
46 // 3. Update all access patterns to use std::visit for type-safe ID retrieval
47 // Trade-off: More type-safe but requires more boilerplate for access
49 int id;
50 Eigen::Vector3d pos;
51 std::variant<CellVertexData, FaceData, CellData> data;
52};
53
55 bool is_main = false;
56};
57
59 : public boost::adjacency_list<boost::vecS, // edge container
60 boost::vecS, // vertex container
61 boost::undirectedS, // cells–faces are
62 // bidirectional
63 // boost::directedS,
64 VertexProperties, // vertex bundle
65 CellEdgeData // edge bundle
66 >,
67 public Registry {
68 protected:
69 using Graph =
70 boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
72
73 public:
74 using Vertex = boost::graph_traits<Graph>::vertex_descriptor;
75 using GraphIter = boost::graph_traits<CellComplex>::vertex_iterator;
76 using AdjacencyIter = boost::graph_traits<CellComplex>::adjacency_iterator;
77
78 protected:
79 template <CellNodeType T> struct Is_Type {
80 const CellComplex *g;
81 Is_Type() = delete;
82 Is_Type(const CellComplex *g) : g(g) {}
83 bool operator()(CellComplex::vertex_descriptor vd) const {
84 auto const &prop = (*g)[vd];
85 return prop.type == T;
86 }
87 };
92 const CellComplex *g;
95 bool operator()(CellComplex::vertex_descriptor vd) const {
96 auto const &prop = (*g)[vd];
97 int n_adj = 0;
98 auto [begin, end] = boost::adjacent_vertices(vd, *g);
99 for (auto it = begin; it != end; ++it)
100 if ((*g)[*it].type == CellNodeType::cell)
101 ++n_adj;
102 return prop.type == CellNodeType::face && n_adj >= 2;
103 }
104 };
105
106 using VertexIterator = boost::filter_iterator<IsVertex, GraphIter>;
107 using FaceIterator = boost::filter_iterator<IsFace, GraphIter>;
109 boost::filter_iterator<IsFaceBetweenCells, GraphIter>;
110 using CellIterator = boost::filter_iterator<IsCell, GraphIter>;
111 using VertexOnFaceIterator = boost::filter_iterator<IsVertex, AdjacencyIter>;
112 using FaceOnCellIterator = boost::filter_iterator<IsFace, AdjacencyIter>;
113 using CellOnFaceIterator = boost::filter_iterator<IsCell, AdjacencyIter>;
114
115 private:
116 int vertex_count = 0;
117 int face_count = 0;
118 int cell_count = 0;
119
120 public:
122
123 CellComplex() = delete;
124
126 std::vector<Eigen::Vector4d, Eigen::aligned_allocator<Eigen::Vector4d>>
127 &planes,
128 std::vector<size_t> &verticals, std::vector<size_t> &horizontals,
129 std::vector<std::pair<size_t, size_t>> &pairs,
130 std::array<double, 2> min_xy, std::array<double, 2> max_xy,
131 std::optional<
132 std::function<void(size_t, std::vector<std::array<double, 3>> const &,
133 std::vector<int> const &)>>
134 viz_func = std::nullopt);
135
136 // TODO: Consider returning coverage metrics instead of void
137 // category=Geometry estimate=2h
138 // compute_face_coverage() currently returns void but computes useful metrics.
139 // Could return coverage data structure:
140 // 1. Per-face coverage percentage (points/area)
141 // 2. Grid occupancy map for each face
142 // 3. Confidence metrics for plane fitting
143 // Enables downstream quality assessment and filtering of low-coverage faces
144 template <typename PointT = pcl::PointXYZ>
145 auto compute_face_coverage(pcl::PointCloud<PointT>::ConstPtr cloud,
147 std::vector<pcl::IndicesPtr> &inliers,
148 const double grid_size = 0.2) -> void;
149
150 template <typename PointT = pcl::PointXYZ, typename PointN = pcl::Normal,
151 typename PointL = pcl::Label>
152 auto compute_room_probabilities(pcl::PointCloud<PointT>::ConstPtr cloud,
153 pcl::PointCloud<PointN>::ConstPtr normals,
154 pcl::PointCloud<PointL>::ConstPtr labels,
155 const double grid_size = 0.2) -> void;
156
157 inline Vertex add_vertex(Eigen::Vector3d pos) {
158 auto v = boost::add_vertex(*this);
159 (*this)[v].type = CellNodeType::vertex;
160 (*this)[v].id = vertex_count++;
161 (*this)[v].pos = pos;
162 (*this)[v].data = CellVertexData{};
163 return v;
164 };
165
166 template <typename T>
167 inline Vertex add_face(Eigen::Vector3d pos, T begin, T end,
168 int plane_id = -1) {
169 auto f = boost::add_vertex(*this);
170 (*this)[f].type = CellNodeType::face;
171 (*this)[f].id = face_count++;
172 (*this)[f].pos = pos;
173 (*this)[f].data = FaceData{plane_id};
174 for (auto it = begin; it != end; ++it)
175 boost::add_edge(*it, f, *this);
176 return f;
177 };
178 template <typename Range>
179 inline Vertex add_face(Eigen::Vector3d pos, Range vertices,
180 int plane_id = -1) {
181 return add_face(pos, std::begin(vertices), std::end(vertices), plane_id);
182 };
183
184 template <typename T>
185 inline Vertex add_cell(Eigen::Vector3d pos, T begin, T end) {
186 auto c = boost::add_vertex(*this);
187 (*this)[c].type = CellNodeType::cell;
188 (*this)[c].id = cell_count++;
189 (*this)[c].pos = pos;
190 (*this)[c].data = CellData{};
191 for (auto it = begin; it != end; ++it)
192 boost::add_edge(*it, c, *this);
193 return c;
194 };
195 template <typename Range>
196 inline Vertex add_cell(Eigen::Vector3d pos, Range faces) {
197 return add_cell(pos, std::begin(faces), std::end(faces));
198 };
199
200 size_t num_vertices() const;
201 size_t num_faces() const;
202 size_t num_cells() const;
203
204 std::ostream &operator<<(std::ostream &os) const;
205
208
209 auto faces_begin() const -> FaceIterator;
210 auto faces_end() const -> FaceIterator;
211
214
215 auto cells_begin() const -> CellIterator;
216 auto cells_end() const -> CellIterator;
217
218 // TODO: Rename adjacency iterators to follow CGAL naming convention
219 // category=Geometry estimate=2h
220 // Current names are unclear about what they iterate over. Adopt CGAL-style:
221 // - vertices_of_face_begin/end instead of face_vertices_begin/end
222 // - faces_of_cell_begin/end instead of cell_faces_begin/end
223 // - cells_of_face_begin/end instead of face_cells_begin/end
224 // Pattern: "elements_of_container" reads more naturally than
225 // "container_elements" Requires updating all call sites but improves API
226 // consistency
229
230 // auto cells_begin(Vertex f) const -> CellOnFaceIterator;
231 // auto cells_end(Vertex f) const -> CellOnFaceIterator;
232
235
236 auto get_a(Vertex f) const -> Vertex;
237 auto get_b(Vertex f) const -> Vertex;
238};
239} // namespace reusex::geometry
240
241template <> struct fmt::formatter<reusex::geometry::CellComplex> {
242 // Parse function (optional)
243 template <typename ParseContext> constexpr auto parse(ParseContext &ctx) {
244 return ctx.begin();
245 }
246
247 // Format function
248 template <typename FormatContext>
250 FormatContext &ctx) const {
251 return fmt::format_to(ctx.out(), "[{}c {}f {}v {}r {}w]", obj.num_cells(),
252 obj.num_faces(), obj.num_vertices(), obj.n_rooms,
253 obj.n_walls);
254 }
255};
256
257// Implementation files
258#include "reusex/geometry/CellComplexFaceCoverage.hpp"
259#include "reusex/geometry/CellComplexRoomProbabilities.hpp"
boost::graph_traits< CellComplex >::adjacency_iterator AdjacencyIter
boost::filter_iterator< IsVertex, GraphIter > VertexIterator
Is_Type< CellNodeType::vertex > IsVertex
auto get_a(Vertex f) const -> Vertex
boost::filter_iterator< IsFace, GraphIter > FaceIterator
CellComplex(std::vector< Eigen::Vector4d, Eigen::aligned_allocator< Eigen::Vector4d > > &planes, std::vector< size_t > &verticals, std::vector< size_t > &horizontals, std::vector< std::pair< size_t, size_t > > &pairs, std::array< double, 2 > min_xy, std::array< double, 2 > max_xy, std::optional< std::function< void(size_t, std::vector< std::array< double, 3 > > const &, std::vector< int > const &)> > viz_func=std::nullopt)
auto compute_face_coverage(pcl::PointCloud< PointT >::ConstPtr cloud, EigenVectorContainer< double, 4 > &planes, std::vector< pcl::IndicesPtr > &inliers, const double grid_size=0.2) -> void
auto faces_between_cells_end() const -> FaceBetweenCellIterator
boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS, VertexProperties, CellEdgeData > Graph
auto cells_begin() const -> CellIterator
std::ostream & operator<<(std::ostream &os) const
Vertex add_cell(Eigen::Vector3d pos, Range faces)
auto vertices_end() const -> VertexIterator
auto faces_end() const -> FaceIterator
boost::graph_traits< CellComplex >::vertex_iterator GraphIter
boost::graph_traits< Graph >::vertex_descriptor Vertex
auto get_b(Vertex f) const -> Vertex
boost::filter_iterator< IsFaceBetweenCells, GraphIter > FaceBetweenCellIterator
auto faces_between_cells_begin() const -> FaceBetweenCellIterator
Is_Type< CellNodeType::cell > IsCell
boost::filter_iterator< IsCell, GraphIter > CellIterator
auto faces_begin() const -> FaceIterator
Vertex add_face(Eigen::Vector3d pos, Range vertices, int plane_id=-1)
Is_Type< CellNodeType::face > IsFace
Vertex add_cell(Eigen::Vector3d pos, T begin, T end)
Vertex add_face(Eigen::Vector3d pos, T begin, T end, int plane_id=-1)
auto vertices_begin() const -> VertexIterator
boost::filter_iterator< IsFace, AdjacencyIter > FaceOnCellIterator
auto compute_room_probabilities(pcl::PointCloud< PointT >::ConstPtr cloud, pcl::PointCloud< PointN >::ConstPtr normals, pcl::PointCloud< PointL >::ConstPtr labels, const double grid_size=0.2) -> void
auto cells_end() const -> CellIterator
boost::filter_iterator< IsCell, AdjacencyIter > CellOnFaceIterator
Vertex add_vertex(Eigen::Vector3d pos)
boost::filter_iterator< IsVertex, AdjacencyIter > VertexOnFaceIterator
pcl::PointXYZRGB PointT
Definition types.hpp:17
std::vector< Eigen::Matrix< Scalar, Rows, 1 >, Eigen::aligned_allocator< Eigen::Matrix< Scalar, Rows, 1 > > > EigenVectorContainer
Definition types.hpp:43
auto format(const reusex::geometry::CellComplex &obj, FormatContext &ctx) const
constexpr auto parse(ParseContext &ctx)
bool operator()(CellComplex::vertex_descriptor vd) const
bool operator()(CellComplex::vertex_descriptor vd) const
std::variant< CellVertexData, FaceData, CellData > data