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