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