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