Lagrange
select_facets_by_normal_similarity.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
13#pragma once
14
15#include <lagrange/common.h>
16#include <lagrange/compute_triangle_normal.h>
17#include <lagrange/utils/assert.h>
18#include <lagrange/utils/range.h>
19
20#include <limits>
21
22namespace lagrange {
23LAGRANGE_LEGACY_INLINE
24namespace legacy {
25
31//
32// The parameters for the function
33//
34template <typename MeshType>
36{
37 using Scalar = typename MeshType::Scalar;
38 using Index = typename MeshType::Index;
39
40 //
41 // This are input and have to be set
42 //
43 // Increasing this would select a larger region
44 Scalar flood_error_limit = std::numeric_limits<Scalar>::max();
45 // This tries to smooth the selection boundary (reduce ears)
46 bool should_smooth_boundary = true;
47
48 //
49 // These parameters have default values
50 // Default value comes from segmentation/Constants.h
51 //
52 // If this is not empty, then only faces are considered which are marked as true
53 std::vector<bool> is_facet_selectable = std::vector<bool>();
54 // Internal param...
55 Scalar flood_second_to_first_order_limit_ratio = 1.0 / 6.0;
56 // Number of iterations for smoothing the boundary
57 Index num_smooth_iterations = 3;
58 // Use DFS or BFS search
59 enum class SearchType { BFS = 0, DFS = 1 };
60 SearchType search_type = SearchType::DFS;
61
62 // Set selectable facets using a vector of indices
63 void set_selectable_facets(MeshType& mesh_ref, const std::vector<Index>& selectable_facets)
64 {
65 if (selectable_facets.empty()) {
66 is_facet_selectable.clear();
67 } else {
68 is_facet_selectable.resize(mesh_ref.get_num_facets(), false);
69 for (auto facet_id : range_sparse(mesh_ref.get_num_facets(), selectable_facets)) {
70 is_facet_selectable[facet_id] = true;
71 }
72 }
73 }
74
75}; // SelectFaceByNormalSimilarityParameters
76
77//
78// Perform the selection
79// mesh, input: the mesh
80// seed_facet_id, input: the index of the seed
81// parameters, input: the parameters above
82// returns a vector that contains true for facets that are selected
83//
84template <typename MeshType>
86 MeshType& mesh,
87 const typename MeshType::Index seed_facet_id,
89{
90 // ===================================================
91 // Helper typedef and lambdas
92 // ===================================================
93
94 using VertexType = typename MeshType::VertexType;
95 using Index = typename MeshType::Index;
96 using Scalar = typename MeshType::Scalar;
97 using IndexList = typename MeshType::IndexList;
98 using AttributeArray = typename MeshType::AttributeArray;
100
101 // Check if a face is selectable
102 auto is_facet_selectable = [&mesh, &parameters](const Index facet_id) -> bool {
103 if (parameters.is_facet_selectable.empty()) {
104 return true;
105 } else {
106 la_runtime_assert(facet_id < mesh.get_num_facets());
107 return parameters.is_facet_selectable[facet_id];
108 }
109 };
110
111 // The energy between two normals
112 auto normal_direction_error = [](const VertexType& n1, const VertexType& n2) -> Scalar {
113 return static_cast<Scalar>(1.0) - std::abs(n1.dot(n2));
114 };
115
116 // ===================================================
117 // Let's begin
118 // current code just copied from
119 // Segmentation/SuperTriangulation/SingleFloodFromSeed()
120 // ===================================================
121
122 if (mesh.get_vertex_per_facet() != 3) {
123 throw std::runtime_error("Input mesh must be a triangle mesh.");
124 }
125
126 // We need this things
127 if (!mesh.is_connectivity_initialized()) {
128 mesh.initialize_connectivity();
129 }
130 if (!mesh.has_facet_attribute("normal")) {
131 compute_triangle_normal(mesh);
132 }
133
134 // This would be the final value that will be returned
135 std::vector<bool> is_facet_selected(mesh.get_num_facets(), false);
136
137 // Normals (and the seed normal)
138 const AttributeArray& facet_normals = mesh.get_facet_attribute("normal");
139 const VertexType seed_n = facet_normals.row(seed_facet_id);
140
141 // DFS search utility
142 std::vector<bool> is_facet_processed(mesh.get_num_facets(), false);
143 std::deque<Index> search_queue;
144
145 //
146 // (0): Add the seed neighbors to the queue to initialize the queue
147 //
148 is_facet_processed[seed_facet_id] = true;
149 is_facet_selected[seed_facet_id] = true;
150 {
151 const IndexList& facets_adjacent_to_seed = mesh.get_facets_adjacent_to_facet(seed_facet_id);
152 for (Index j = 0; j < static_cast<Index>(facets_adjacent_to_seed.size()); j++) {
153 const Index ne_fid = facets_adjacent_to_seed[j];
154 const VertexType ne_n = facet_normals.row(ne_fid);
155
156 if ((!is_facet_processed[ne_fid]) && is_facet_selectable(ne_fid)) {
157 Scalar error = normal_direction_error(seed_n, ne_n);
158 if (error < parameters.flood_error_limit) {
159 is_facet_selected[ne_fid] = true;
160 search_queue.push_back(ne_fid);
161 }
162 }
163 } // End of for over neighbours of seed
164 } // end of step 0
165
166 //
167 // (1): Run through all neighbors, process them, and push them into a queue
168 // for further flood
169 //
170 while (!search_queue.empty()) {
171 Index fid = invalid<Index>();
172 if (parameters.search_type == Parameters::SearchType::DFS) {
173 fid = search_queue.front();
174 search_queue.pop_front();
175 } else {
176 fid = search_queue.back();
177 search_queue.pop_back();
178 }
179
180 const VertexType center_n = facet_normals.row(fid);
181
182
183 const IndexList& facets_adjacent_to_candidate = mesh.get_facets_adjacent_to_facet(fid);
184 for (Index j = 0; j < static_cast<Index>(facets_adjacent_to_candidate.size()); j++) {
185 const Index ne_fid = facets_adjacent_to_candidate[j];
186 const VertexType ne_n = facet_normals.row(ne_fid);
187
188 if ((!is_facet_processed[ne_fid]) && is_facet_selectable(ne_fid)) {
189 const Scalar error_1 = normal_direction_error(seed_n, ne_n);
190 const Scalar error_2 = normal_direction_error(center_n, ne_n);
191 is_facet_processed[ne_fid] = true;
192
193 if ((error_1 < parameters.flood_error_limit) &&
194 (error_2 < parameters.flood_error_limit)) {
195 is_facet_selected[ne_fid] = true;
196 search_queue.push_back(ne_fid);
197 } else if (
198 error_2 < parameters.flood_error_limit *
199 parameters.flood_second_to_first_order_limit_ratio) {
200 // Second order approximation
201 is_facet_selected[ne_fid] = true;
202 search_queue.push_back(ne_fid);
203 }
204 } // end of is neighbour selectable
205 } // end of loops over neighbours
206 } // end of dfs stack
207
208 //
209 // Smooth the selection
210 //
211 if (parameters.should_smooth_boundary) {
212 for (Index st_iter_2 = 0; st_iter_2 < parameters.num_smooth_iterations; st_iter_2++) {
213 // Go through boundary faces - check spikes
214 for (Index i = 0; i < mesh.get_num_facets(); i++) {
215 const IndexList& facets_adjacent_to_candidate =
216 mesh.get_facets_adjacent_to_facet(i);
217
218 if (is_facet_selectable(i) && (facets_adjacent_to_candidate.size() > 2)) {
219 const bool select_flag = is_facet_selected[i];
220 Index neighbor_diff_flag = 0;
221
222 const Index ne_fid_0 = facets_adjacent_to_candidate[0];
223 const bool ne_select_0 = is_facet_selected[ne_fid_0];
224 if (select_flag != ne_select_0) neighbor_diff_flag++;
225
226 const Index ne_fid_1 = facets_adjacent_to_candidate[1];
227 const bool ne_select_1 = is_facet_selected[ne_fid_1];
228 if (select_flag != ne_select_1) neighbor_diff_flag++;
229
230 const Index ne_fid_2 = facets_adjacent_to_candidate[2];
231 const bool ne_select_2 = is_facet_selected[ne_fid_2];
232 if (select_flag != ne_select_2) neighbor_diff_flag++;
233
234 if (neighbor_diff_flag > 1) {
235 const VertexType self_n = facet_normals.row(i);
236 const VertexType ne_n_0 = facet_normals.row(ne_fid_0);
237 const VertexType ne_n_1 = facet_normals.row(ne_fid_1);
238 const VertexType ne_n_2 = facet_normals.row(ne_fid_2);
239
240 const Scalar e_0 = normal_direction_error(self_n, ne_n_0);
241 const Scalar e_1 = normal_direction_error(self_n, ne_n_1);
242 const Scalar e_2 = normal_direction_error(self_n, ne_n_2);
243
244 const bool e_0_ok = e_0 < parameters.flood_error_limit;
245 const bool e_1_ok = e_1 < parameters.flood_error_limit;
246 const bool e_2_ok = e_2 < parameters.flood_error_limit;
247
248 if (ne_select_0 && ne_select_1 && (e_0_ok || e_1_ok)) {
249 is_facet_selected[i] = true;
250 } else if (ne_select_1 && ne_select_2 && (e_1_ok || e_2_ok)) {
251 is_facet_selected[i] = true;
252 } else if (ne_select_2 && ne_select_0 && (e_2_ok || e_0_ok)) {
253 is_facet_selected[i] = true;
254 }
255 }
256 } // end of non selected facet that has two selectable neighbours
257 } // end of loop over faces
258 } // end of for(num_smooth_iterations)
259 } // end of should smooth boundary
260
261
262 return is_facet_selected;
263
264} // select_faces_by_normal_similarity
265
266
267
268} // namespace legacy
269} // namespace lagrange
Definition: Mesh.h:48
AttributeId select_facets_by_normal_similarity(SurfaceMesh< Scalar, Index > &mesh, const Index seed_facet_id, const SelectFacetsByNormalSimilarityOptions &options={})
Given a seed facet, selects facets around it based on the change in triangle normals.
Definition: select_facets_by_normal_similarity.cpp:27
#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
Main namespace for Lagrange.
Definition: AABBIGL.h:30
Given a seed vertex, selects faces around it based on the change in triangle normals.
Definition: select_facets_by_normal_similarity.h:36