Lagrange
Loading...
Searching...
No Matches
sample_points_on_surface.h
1/*
2 * Copyright 2019 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <unordered_map>
15
16#include <lagrange/common.h>
17#include <lagrange/compute_facet_area.h>
18#include <lagrange/internal/constants.h>
19#include <lagrange/utils/assert.h>
20#include <lagrange/utils/range.h>
21
22#include <list>
23
24namespace lagrange {
25
26// sample_points_on_surface()
27//
28// Samples points on a mesh as uniformly as possible by splitting edges
29// until they are smaller than a certain value and also not sampling more
30// than one point inside a grid.
31//
32// This is a Lagrangified version of segmentation/PointSample::SampleViaGrid()
33
34// Output values
35template <typename MeshType>
37{
38public:
39 using Index = typename MeshType::Index;
40 using IndexList = typename MeshType::IndexList;
41 using VertexArray = typename MeshType::VertexArray;
42 // Same as vertex array, but must have 3 cols rather than whatever vertex array has
43 using BarycentricArray = Eigen::Matrix<
44 typename VertexArray::Scalar,
45 VertexArray::RowsAtCompileTime,
46 3, // VertexArray::ColsAtCompileTime,
47 VertexArray::Options>;
48
49 // Number of samples points
50 Index num_samples = 0;
51 // The facet id of the sampled points
52 IndexList facet_ids = IndexList(0);
53 // Coordinates of the samples points
54 VertexArray positions = VertexArray();
55 // Barycentric coordinates of the samples points
56 BarycentricArray barycentrics = BarycentricArray();
57};
58
59//
60// A few version follow with different ways of initializing the
61// list of active facts.
62//
63
64// You can choose which facets to sample from by specifying an array of indices.
65// Inputs
66// - The mesh
67// - Approximate number of points to be sampled
68// - Index of the faces to be sampled from, an empty list of indices means all faces.
69template <typename MeshType>
70SamplePointsOnSurfaceOutput<MeshType> sample_points_on_surface(
71 MeshType& mesh,
72 const typename MeshType::Index approx_num_points,
73 const typename MeshType::IndexList& active_facets)
74{
75 // Helper types
76 using Index = typename MeshType::Index;
77 using Scalar = typename MeshType::Scalar;
78 using VertexType = typename MeshType::VertexType;
79 //
81 //
82 using RowVectorS = Eigen::Matrix<Scalar, 1, Eigen::Dynamic>;
83 using RowVector3S = Eigen::Matrix<Scalar, 1, 3>;
84 using RowVectorI = Eigen::Matrix<Index, 1, Eigen::Dynamic>;
85 using BoundingBox = Eigen::AlignedBox<Scalar, Eigen::Dynamic>;
86
87 // Check conditions to be met by input
88 la_runtime_assert(mesh.get_vertex_per_facet() == 3, "only works for triangle meshes");
89 la_runtime_assert(approx_num_points != invalid<Index>());
90 la_runtime_assert(active_facets.size() <= safe_cast<size_t>(mesh.get_num_facets()));
91 for (const auto facet_id : active_facets) {
92 la_runtime_assert(facet_id < mesh.get_num_facets());
93 }
94
95
96 // Information regarding the mesh that we need
97 if (!mesh.has_facet_attribute("area")) {
99 }
100 const auto& facet_area = mesh.get_facet_attribute("area");
101 const auto& facets = mesh.get_facets();
102 const auto& vertices = mesh.get_vertices();
103
104 // Get space dimensions
105 const Index n_dims = mesh.get_dim();
106
107 // Compute the total area of active part of the mesh
108 // Otherwise we could have just used total_mesh_area = facet_area.sum().
109 // In the new version of eigen, we can also use facet_area( active_facets ).sum()
110 Scalar total_mesh_area = 0;
111 for (auto facet_id : range_sparse(mesh.get_num_facets(), active_facets)) {
112 total_mesh_area += facet_area(facet_id);
113 }
114
115 // total area of zero is bad
116 la_runtime_assert(total_mesh_area != 0.0);
117
118 // Estimate the sampling length
119 // If each point is a disk, what should be the radius of disks to cover the surface
120 // this radius would be the sampling length
121 // 1.5 is probably a safety factor
122 const Scalar sampling_length =
123 1.5 * sqrt((total_mesh_area / (approx_num_points * lagrange::internal::pi)));
124
125 // Find the bounding box of the active facets on the mesh
126 BoundingBox bounding_box(n_dims);
127 for (auto facet_id : range_sparse(mesh.get_num_facets(), active_facets)) {
128 for (auto vertex_offset : range(mesh.get_vertex_per_facet())) {
129 const Index vertex_id = facets(facet_id, vertex_offset);
130 // bbox needs a column vector . Hence the transpose(). Arg...
131 bounding_box.extend(vertices.row(vertex_id).transpose());
132 }
133 }
134
135 // Find the side lengths and dimensions of the grid used for sampling
136 const RowVectorS grid_lens = bounding_box.sizes();
137 const RowVectorI grid_dims =
138 (grid_lens / sampling_length).array().ceil().template cast<Index>();
139
140 // Number of cells in the grid
141 const Index num_grid_cells = grid_dims.prod();
142
143 // Given a 2(3)-D index gives the flattened index of a cell in the grid
144 auto get_flattend_index = [grid_dims, n_dims](const RowVectorI& index_nd) -> Index {
145 // Some debug time assertions
146 assert(index_nd.minCoeff() >= 0);
147 assert((index_nd.array() < grid_dims.array()).all());
148 //
149 Index answer = invalid<Index>();
150 //
151 if (n_dims == 2) {
152 answer = index_nd(1) * grid_dims(0) + index_nd(0);
153 } else if (n_dims == 3) {
155 answer = index_nd(2) * grid_dims(1) * grid_dims(0) + index_nd(1) * grid_dims(0) +
156 index_nd(0);
157 LA_IGNORE_ARRAY_BOUNDS_END
158 } else {
159 la_runtime_assert(0, "This dimension is not supported");
160 }
161 //
162 assert(answer >= 0 && answer < grid_dims.prod());
163 //
164 return answer;
165 };
166
167 // A data structure to indicate if we have already sampled from a cell of the grid
168 // A vector, this can be too memory consuming
169 // std::vector<bool> is_grid_cell_marked_backend(num_grid_cells, false);
170 // Hash map
171 std::unordered_set<Index> is_grid_cell_marked_backend(num_grid_cells);
172 //
173 // A labmda to see if a grid cell is already marked.
174 auto is_grid_cell_marked = [&is_grid_cell_marked_backend](const Index cell_index) {
175 // for a vector
176 // return is_grid_cell_marked_backend[cell_index];
177 // for the hashmap
178 return is_grid_cell_marked_backend.count(cell_index) != 0;
179 };
180 //
181 // A lambda to mark a grid cell
182 auto mark_grid_cell = [&is_grid_cell_marked_backend](const Index cell_index) {
183 // for a vector
184 // is_grid_cell_marked_backend[cell_index] = true;
185 // for hashmap
186 is_grid_cell_marked_backend.insert(cell_index);
187 };
188
189
190 // Hold the info about the sample points in a list
191 // once the process is finished move them to the output struct
192 std::list<VertexType> sample_positions;
193 std::list<Index> sample_facet_ids;
194 std::list<RowVector3S> sample_barycentrics;
195 Index num_samples = 0;
196
197 //
198 // A queue holding the triangles generated by splitting.
199 // Each triangle is represented by a 3x6 vector.
200 // Each row represents a vertex.
201 // The first three(two) columns represent the positions in the xy(z) space.
202 // The second three columns are the barycentric coordinates of the vertices w.r.t to the
203 // mother triangle (the non splitted one).
204 //
205 const Index num_triangle_cols = 3 + n_dims;
206 using Triangle = Eigen::Matrix<Scalar, 3, Eigen::Dynamic>;
207 auto get_triangle_pos = [&n_dims](const Triangle& tri, const Index vert_offset) -> RowVectorS {
208 return tri.row(vert_offset).head(n_dims);
209 };
210 auto get_triangle_bary = [](const Triangle& tri, const Index vert_offset) -> RowVectorS {
211 return tri.row(vert_offset).tail(3);
212 };
213 auto set_triangle_pos =
214 [&n_dims](Triangle& tri, const Index vert_offset, const RowVectorS& v) -> void {
215 tri.row(vert_offset).head(n_dims) = v;
216 };
217 auto set_triangle_bary = [](Triangle& tri,
218 const Index vert_offset,
219 const RowVectorS& v) -> void { tri.row(vert_offset).tail(3) = v; };
220 std::queue<Triangle> sub_triangles;
221
222 //
223 // Loop over facets
224 // subdivide each facet to the point that you get a triangle smaller
225 // than sampling length. Then get the midpoint of that triangle if
226 // its corresponding grid cell is not marked already.
227 //
228 for (auto facet_id : range_sparse(mesh.get_num_facets(), active_facets)) {
229 //
230 // Splitting code follows.
231 // This is done rather inefficiently using a queue. There is no need for that.
232 // If a samaritan with free time was passing by, they can make it better :).
233 //
234
235 // Empty out the queue
236 sub_triangles = std::queue<Triangle>();
237
238 // Create the mother triangle
239 Triangle mother_facet = Triangle::Zero(3, num_triangle_cols);
240 set_triangle_pos(mother_facet, 0, vertices.row(facets(facet_id, 0)));
241 set_triangle_pos(mother_facet, 1, vertices.row(facets(facet_id, 1)));
242 set_triangle_pos(mother_facet, 2, vertices.row(facets(facet_id, 2)));
243 set_triangle_bary(mother_facet, 0, RowVector3S(1, 0, 0));
244 set_triangle_bary(mother_facet, 1, RowVector3S(0, 1, 0));
245 set_triangle_bary(mother_facet, 2, RowVector3S(0, 0, 1));
246
247 // Push it to the queue
248 sub_triangles.push(mother_facet);
249
250 // Now process the triagles in the queue
251 while (!sub_triangles.empty()) {
252 const Triangle current_facet = sub_triangles.front();
253 sub_triangles.pop();
254
255 // Find the longest edge in the triangle
256 Scalar longest_edge_length = -std::numeric_limits<Scalar>::max();
257 Index longest_edge_offset = invalid<Index>();
258 for (auto edge_offset : range(3)) {
259 const auto edge_offset_p1 = (edge_offset + 1) % 3;
260 const Scalar edge_length = (get_triangle_pos(current_facet, edge_offset) //
261 - get_triangle_pos(current_facet, edge_offset_p1))
262 .norm();
263 if (edge_length > longest_edge_length) {
264 longest_edge_length = edge_length;
265 longest_edge_offset = edge_offset;
266 }
267 }
268
269 // If too big, subdivide
270 if (longest_edge_length > 2 * sampling_length) {
271 Triangle f1 = Triangle::Zero(3, num_triangle_cols);
272 Triangle f2 = Triangle::Zero(3, num_triangle_cols);
273 //
274 f1.row(0) = (current_facet.row(longest_edge_offset) +
275 current_facet.row((longest_edge_offset + 1) % 3)) /
276 2;
277 f1.row(1) = current_facet.row(longest_edge_offset);
278 f1.row(2) = current_facet.row((longest_edge_offset + 2) % 3);
279 //
280 f2.row(0) = f1.row(0);
281 f2.row(1) = current_facet.row((longest_edge_offset + 1) % 3);
282 f2.row(2) = current_facet.row((longest_edge_offset + 2) % 3);
283 //
284 sub_triangles.push(f1);
285 sub_triangles.push(f2);
286 } else { // Otherwise, sample the centroid
287 const RowVector3S point_barycentric =
288 (get_triangle_bary(current_facet, 0) + get_triangle_bary(current_facet, 1) +
289 get_triangle_bary(current_facet, 2)) /
290 3.;
291 const VertexType point_position =
292 (get_triangle_pos(current_facet, 0) + get_triangle_pos(current_facet, 1) +
293 get_triangle_pos(current_facet, 2)) /
294 3.;
295
296
297 // Get the corresponding cell in the grid
298 // min() returns column vector, hence the tranpose(), arg...
299 const RowVectorI grid_cell =
300 ((point_position - bounding_box.min().transpose()) / sampling_length)
301 .array()
302 .floor()
303 .template cast<Index>();
304 const Index grid_cell_id = get_flattend_index(grid_cell);
305
306 if (!is_grid_cell_marked(grid_cell_id)) {
307 mark_grid_cell(grid_cell_id);
308 // record sample facet
309 sample_facet_ids.push_back(facet_id);
310 // record barycetric (in mother facet)
311 sample_barycentrics.push_back(point_barycentric);
312 // record the position
313 sample_positions.push_back(point_position);
314 //
315 ++num_samples;
316 } // end of grid check
317 } // end of size check
318 } // end of loop over queue
319 } // end of loop over faces
320
321
322 // Now copy the result into the output struct
323 Output output;
324 {
325 output.num_samples = num_samples;
326 //
327 output.barycentrics.resize(num_samples, 3);
328 output.facet_ids.resize(num_samples);
329 output.positions.resize(num_samples, n_dims);
330 //
331 auto it_barycentric = sample_barycentrics.begin();
332 auto it_facet_id = sample_facet_ids.begin();
333 auto it_position = sample_positions.begin();
334 //
335 for (Index i : range(num_samples)) {
336 output.barycentrics.row(i) = *it_barycentric;
337 output.facet_ids[i] = *it_facet_id;
338 output.positions.row(i) = *it_position;
339 ++it_barycentric;
340 ++it_facet_id;
341 ++it_position;
342 }
343 }
344
345 return output;
346}
347
348// Active facets are specified by an array of bools
349// Inputs
350// - The mesh
351// - Approximate number of points to be sampled
352// - Array of bools saying whether a facet should be sampled from or not.
353template <typename MeshType>
354SamplePointsOnSurfaceOutput<MeshType> sample_points_on_surface(
355 MeshType& mesh,
356 const typename MeshType::Index approx_num_points,
357 const std::vector<bool>& is_facet_active)
358{
359 typename MeshType::IndexList active_facets;
360 for (auto i : range(safe_cast<typename MeshType::Index>(is_facet_active.size()))) {
361 if (is_facet_active[i]) {
362 active_facets.push_back(i);
363 }
364 }
365 return sample_points_on_surface(mesh, approx_num_points, active_facets);
366}
367
368// All the facets will be sampled
369// Inputs
370// - The mesh
371// - Approximate number of points to be sampled
372template <typename MeshType>
373SamplePointsOnSurfaceOutput<MeshType> sample_points_on_surface(
374 MeshType& mesh,
375 const typename MeshType::Index approx_num_points)
376{
377 return sample_points_on_surface(mesh, approx_num_points, typename MeshType::IndexList());
378}
379
380} // namespace lagrange
Index get_num_facets() const
Retrieves the number of facets.
Definition SurfaceMesh.h:678
Index get_vertex_per_facet() const
Retrieves the number of vertex per facet in a regular mesh.
Definition SurfaceMesh.cpp:2130
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
AttributeId compute_facet_area(SurfaceMesh< Scalar, Index > &mesh, FacetAreaOptions options={})
Compute per-facet area.
Definition compute_area.cpp:304
SurfaceMesh< ToScalar, ToIndex > cast(const SurfaceMesh< FromScalar, FromIndex > &source_mesh, const AttributeFilter &convertible_attributes={}, std::vector< std::string > *converted_attributes_names=nullptr)
Cast a mesh to a mesh of different scalar and/or index type.
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
internal::SparseRange< Index > range_sparse(Index max, const std::vector< Index > &active)
Returns an iterable object representing a subset of the range [0, max).
Definition range.h:217
internal::Range< Index > range(Index end)
Returns an iterable object representing the range [0, end).
Definition range.h:176
constexpr T invalid()
You can use invalid<T>() to get a value that can represent "invalid" values, such as invalid indices ...
Definition invalid.h:40
constexpr auto safe_cast(SourceType value) -> std::enable_if_t<!std::is_same< SourceType, TargetType >::value, TargetType >
Perform safe cast from SourceType to TargetType, where "safe" means:
Definition safe_cast.h:50
#define LA_IGNORE_ARRAY_BOUNDS_BEGIN
Ignore warning "out of bounds subscripts or offsets into arrays" This is used to bypass the following...
Definition warning.h:157
Main namespace for Lagrange.
Definition sample_points_on_surface.h:37