Lagrange
thicken_and_close_mesh.h
1/*
2 * Copyright 2021 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 <lagrange/Mesh.h>
15#include <lagrange/MeshTrait.h>
16#include <lagrange/attributes/map_indexed_attributes.h>
17#include <lagrange/compute_vertex_normal.h>
18#include <lagrange/create_mesh.h>
19#include <lagrange/legacy/inline.h>
20
21#include <unordered_map>
22
23namespace lagrange {
24LAGRANGE_LEGACY_INLINE
25namespace legacy {
26
27namespace {
28
47
48template <typename MeshType>
49std::unique_ptr<MeshType> thicken_and_close_mesh(
50 const MeshType& input_mesh,
51 bool use_direction_and_mirror,
52 Eigen::Matrix<ScalarOf<MeshType>, 3, 1> direction,
53 ScalarOf<MeshType> offset_amount,
54 ScalarOf<MeshType> mirror_amount,
55 typename MeshType::Index num_segments = 1)
56{
57 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
58
59 using Scalar = typename MeshType::Scalar;
60 using Index = typename MeshType::Index;
61 using VertexArray = typename MeshType::VertexArray;
62 using AttributeArray = typename MeshType::AttributeArray;
63 using UVArray = typename MeshType::UVArray;
64 using UVIndices = typename MeshType::UVIndices;
65 using FacetArray = typename MeshType::FacetArray;
66 using Vector3s = Eigen::Matrix<Scalar, 3, 1>;
67
68 la_runtime_assert(input_mesh.get_dim() == 3, "This function only supports 3D meshes.");
70 input_mesh.get_vertex_per_facet() == 3,
71 "This function only supports triangle meshes.");
73 input_mesh.is_edge_data_initialized(),
74 "This function requires the mesh to have edge data pre-initialized.");
75
76 // compute offset vertex
77 auto compute_vertex = [](const Vector3s& vertex,
78 const Vector3s& offset_vector,
79 const Vector3s& mirror_vector,
80 const Vector3s& target_direction,
81 bool with_direction_and_mirror,
82 Scalar amount) {
83 Vector3s offset_vertex = vertex + amount * offset_vector;
84 if (with_direction_and_mirror) {
85 offset_vertex -= (offset_vertex.dot(target_direction) * amount) * mirror_vector;
86 }
87 return offset_vertex;
88 };
89
90 // Sanitize parameters
91 if (num_segments < 1) num_segments = 1;
92 direction.stableNormalize();
93
94 // Prepare ancillary data
95 Vector3s offset_vector(0.0, 1.0, 0.0);
96 Vector3s mirror_vector(0.0, 0.0, 0.0);
97 AttributeArray vertex_normal;
98 if (use_direction_and_mirror) {
99 offset_vector = offset_amount * direction;
100 mirror_vector = (Scalar(1) - mirror_amount) * direction;
101 } else {
102 // compute vertex normal vector
103 std::unique_ptr<MeshType> copied_mesh = wrap_with_mesh<VertexArray, FacetArray>(
104 input_mesh.get_vertices(),
105 input_mesh.get_facets());
106 compute_vertex_normal(*copied_mesh);
107 copied_mesh->export_vertex_attribute("normal", vertex_normal);
108 la_runtime_assert(vertex_normal.rows() == input_mesh.get_num_vertices());
109 }
110 const Index num_input_vertices = input_mesh.get_num_vertices();
111 const Index num_input_facets = input_mesh.get_num_facets();
112 const bool has_uvs = input_mesh.is_uv_initialized();
113 const Index num_input_uvs = static_cast<Index>(has_uvs ? input_mesh.get_uv().rows() : 0);
114
115 // Count boundary edges and vertices in the input mesh
116 Index num_boundary_edges = 0;
117 Index num_boundary_vertices = 0;
118 std::unordered_map<Index, Index>
119 boundary_vertices; // (key is vertex index, value is a boundary-relative index)
120 for (Index e = 0; e < input_mesh.get_num_edges(); ++e) {
121 if (input_mesh.is_boundary_edge(e)) {
122 ++num_boundary_edges;
123 for (auto v : input_mesh.get_edge_vertices(e)) { // for each of two vertices
124 boundary_vertices.emplace(v, Index(boundary_vertices.size()));
125 }
126 }
127 }
128 num_boundary_vertices = static_cast<Index>(boundary_vertices.size());
129
130 // Vertices
131 // output vertices are packed as follows:
132 // 1. original vertices (num_input_vertices)
133 // 2. offset vertices (num_input_vertices)
134 // 3. stitch vertices ((num_segments - 1) * num_boundary_vertices), packed by segment
135 VertexArray offset_vertices(
136 num_input_vertices * 2 + (num_segments - 1) * num_boundary_vertices,
137 3);
138 for (Index v = 0; v < num_input_vertices; ++v) {
139 if (!use_direction_and_mirror) {
140 // compute per vertex normal and use it as offset direction
141 // TODO: a smarter version could compensate for tight angles with disjoint
142 // indexed normals and amplify this vector to preserve the apparent thickness
143 // of the resulting solid.
144 offset_vector = -vertex_normal.row(v).template head<3>() * offset_amount;
145 }
146 Vector3s vertex = input_mesh.get_vertices().row(v).template head<3>();
147 // copy original vertices
148 offset_vertices.row(v) = vertex;
149 // also opposite face
150 auto offset_vertex = compute_vertex(
151 vertex,
152 offset_vector,
153 mirror_vector,
154 direction,
155 use_direction_and_mirror,
156 1.0);
157 offset_vertices.row(num_input_vertices + v) = offset_vertex;
158
159 // is this a boundary vertex? Add intermediate vertices for the stitch
160 Scalar segment_increment = Scalar(1) / static_cast<Scalar>(num_segments);
161 if (num_segments > 1) {
162 auto vb = boundary_vertices.find(v);
163 if (vb != boundary_vertices.end()) {
164 for (Index is = 1; is < num_segments; ++is) {
165 Scalar offset_ratio = static_cast<Scalar>(is) * segment_increment;
166 assert(offset_ratio < 1.0);
167 offset_vertices.row(
168 num_input_vertices * 2 + // original + offset vertices
169 (is - 1) * num_boundary_vertices + // segment row
170 vb->second // index in the boundary
171 ) =
172 compute_vertex(
173 vertex,
174 offset_vector,
175 mirror_vector,
176 direction,
177 use_direction_and_mirror,
178 offset_ratio);
179 }
180 }
181 }
182 }
183
184
185 // Facets
186 // output facets are packed as follows:
187 // 1. original facets interleaved with flipped facets (num_input_facets*2)
188 // 2. stitch vertices (num_boundary_edges * num_boundary_vertices), packed by segment
189 FacetArray offset_facets((num_input_facets + num_boundary_edges * num_segments) * 2, 3);
190 for (Index f = 0; f < num_input_facets; ++f) {
191 const auto& facet = input_mesh.get_facets().row(f);
192 offset_facets.row(2 * f) = facet;
193 offset_facets.row(2 * f + 1) << facet[0] + num_input_vertices,
194 facet[2] + num_input_vertices,
195 facet[1] + num_input_vertices; // this face is flipped: order of vertices is reversed
196 }
197
198 // 2. Stitch
199 Index vbstart = num_input_vertices * 2;
200 for (Index e = 0, f = 2 * num_input_facets; e < input_mesh.get_num_edges(); ++e) {
201 if (input_mesh.is_boundary_edge(e)) {
202 auto edge_vertices = input_mesh.get_edge_vertices(e);
203 assert(boundary_vertices.find(edge_vertices[0]) != boundary_vertices.end());
204 assert(boundary_vertices.find(edge_vertices[1]) != boundary_vertices.end());
205 assert(f + 1 < offset_facets.rows());
206
207 for (Index is = 0; is < num_segments; ++is) {
208 Index vbstart_on_this_segment = vbstart + (is - 1) * num_boundary_vertices;
209 Index vbstart_on_next_segment = vbstart + is * num_boundary_vertices;
210 bool is_first_segment = (is == 0);
211 bool is_last_segment = (is == num_segments - 1);
212 Index v0 = is_first_segment
213 ? edge_vertices[0]
214 : vbstart_on_this_segment + boundary_vertices[edge_vertices[0]];
215 Index v1 = is_first_segment
216 ? edge_vertices[1]
217 : vbstart_on_this_segment + boundary_vertices[edge_vertices[1]];
218 Index v2 = is_last_segment
219 ? edge_vertices[0] + num_input_vertices
220 : vbstart_on_next_segment + boundary_vertices[edge_vertices[0]];
221 Index v3 = is_last_segment
222 ? edge_vertices[1] + num_input_vertices
223 : vbstart_on_next_segment + boundary_vertices[edge_vertices[1]];
224
225 assert(v0 < offset_vertices.rows());
226 assert(v1 < offset_vertices.rows());
227 assert(v2 < offset_vertices.rows());
228 assert(v3 < offset_vertices.rows());
229
230 offset_facets.row(f++) << v0, v2, v1;
231 offset_facets.row(f++) << v1, v2, v3;
232 }
233 }
234 }
235
236 auto offset_mesh = lagrange::create_mesh(std::move(offset_vertices), std::move(offset_facets));
237
238 if (has_uvs) {
239 const auto& input_uv_values = input_mesh.get_uv();
240 const auto& input_uv_indices = input_mesh.get_uv_indices();
241
242 // UV values
243 UVArray uv_values(num_input_uvs * 2 + (num_segments - 1) * num_boundary_vertices, 2);
244 for (Index u = 0; u < num_input_uvs; ++u) {
245 // copy original uv and opposite face
246 uv_values.row(u) = uv_values.row(u + num_input_uvs) = input_uv_values.row(u);
247 }
248 // additional uv values for num_segments
249 for (Index f = 0; f < input_mesh.get_facets().rows(); ++f) {
250 // find individual uvs indices
251 const auto& uv_facet = input_mesh.get_uv_indices().row(f);
252 // get face indices for this same facet
253 const auto& facet = input_mesh.get_facets().row(f);
254 // for each uv on the face:
255 for (Index fv = 0; fv < 3; ++fv) {
256 Index v = facet[fv];
257 auto vb = boundary_vertices.find(v);
258 if (vb != boundary_vertices.end()) {
259 // this is a boundary vertex.
260 Index uv_index = uv_facet[fv];
261 auto uv_value = input_mesh.get_uv().row(uv_index);
262
263 // add overlapping identical values for uvs at the stitch...
264 // at some point we might want to have non overlapping uvs. Not sure if it's in
265 // scope for this.
266 for (Index is = 1; is < num_segments; ++is) {
267 uv_values.row(
268 num_input_vertices * 2 + // original + offset vertices
269 (is - 1) * num_boundary_vertices + // segment row
270 vb->second) = uv_value;
271 }
272 }
273 }
274 }
275
276 // UV facets
277 UVIndices uv_facets((input_uv_indices.rows() + num_boundary_edges * num_segments) * 2, 3);
278 for (Index u = 0; u < input_uv_indices.rows(); ++u) {
279 const auto& uv_facet = input_uv_indices.row(u);
280 uv_facets.row(2 * u) = uv_facet;
281 uv_facets.row(2 * u + 1) << uv_facet[0] + num_input_uvs, uv_facet[2] + num_input_uvs,
282 uv_facet[1] + num_input_uvs;
283 }
284
285 // Stitch
286 Index uvbstart = static_cast<Index>(2 * input_uv_values.rows());
287 for (Index e = 0, f_uv = 2 * static_cast<Index>(input_uv_indices.rows());
288 e < input_mesh.get_num_edges();
289 ++e) {
290 if (input_mesh.is_boundary_edge(e)) {
291 // Find first and only face on this edge
292 const Index f = input_mesh.get_one_facet_around_edge(e);
293 assert(f != lagrange::invalid<Index>());
294 const auto& facet = input_mesh.get_facets().row(f);
295 assert((facet.array() < num_input_vertices).all());
296 const auto& uv_facet = input_uv_indices.row(f);
297 assert((uv_facet.array() < num_input_uvs).all());
298 // Find vertices on this edge
299 auto edge_vertices = input_mesh.get_edge_vertices(e);
300 // Now find corresponding uvs on this edge
301 Index uv_index_0 = lagrange::invalid<Index>();
302 Index uv_index_1 = lagrange::invalid<Index>();
303 for (Index i = 0; i < 3; ++i) {
304 Index vtx_index = facet[i];
305 Index uv_index = uv_facet[i];
306 if (vtx_index == edge_vertices[0]) {
307 uv_index_0 = uv_index;
308 } else if (vtx_index == edge_vertices[1]) {
309 uv_index_1 = uv_index;
310 }
311 }
312 assert(uv_index_0 != lagrange::invalid<Index>());
313 assert(uv_index_1 != lagrange::invalid<Index>());
314
315 assert(boundary_vertices.find(edge_vertices[0]) != boundary_vertices.end());
316 assert(boundary_vertices.find(edge_vertices[1]) != boundary_vertices.end());
317 assert(f_uv + 1 < uv_facets.rows());
318
319 for (Index is = 0; is < num_segments; ++is) {
320 Index uvbstart_on_this_segment = uvbstart + (is - 1) * num_boundary_vertices;
321 Index uvbstart_on_next_segment = uvbstart + is * num_boundary_vertices;
322 bool is_first_segment = (is == 0);
323 bool is_last_segment = (is == num_segments - 1);
324
325 Index uv0 = is_first_segment ? uv_index_0
326 : uvbstart_on_this_segment +
327 boundary_vertices[edge_vertices[0]];
328 Index uv1 = is_first_segment ? uv_index_1
329 : uvbstart_on_this_segment +
330 boundary_vertices[edge_vertices[1]];
331 Index uv2 = is_last_segment ? uv_index_0 + num_input_vertices
332 : uvbstart_on_next_segment +
333 boundary_vertices[edge_vertices[0]];
334 Index uv3 = is_last_segment ? uv_index_1 + num_input_vertices
335 : uvbstart_on_next_segment +
336 boundary_vertices[edge_vertices[1]];
337
338 assert(uv0 < uv_values.rows());
339 assert(uv1 < uv_values.rows());
340 assert(uv2 < uv_values.rows());
341 assert(uv3 < uv_values.rows());
342
343 uv_facets.row(f_uv++) << uv0, uv2, uv1;
344 uv_facets.row(f_uv++) << uv1, uv2, uv3;
345 }
346 }
347 }
348
349 // Apply UVs
350 assert(uv_values.rows() == num_input_uvs * 2 + (num_segments - 1) * num_boundary_vertices);
351 offset_mesh->initialize_uv(std::move(uv_values), std::move(uv_facets));
352 }
353
354 // sanity check
355 assert(
356 offset_mesh->get_num_vertices() ==
357 num_input_vertices * 2 + num_boundary_vertices * (num_segments - 1));
358
359 return offset_mesh;
360}
361
362} // namespace
363
382template <typename MeshType>
383std::unique_ptr<MeshType> thicken_and_close_mesh(
384 const MeshType& input_mesh,
385 Eigen::Matrix<ScalarOf<MeshType>, 3, 1> direction,
386 ScalarOf<MeshType> offset_amount,
387 ScalarOf<MeshType> mirror_amount,
388 typename MeshType::Index num_segments = 1)
389{
391 input_mesh,
392 true,
393 direction,
394 offset_amount,
395 mirror_amount,
396 num_segments);
397}
398
412template <typename MeshType>
413std::unique_ptr<MeshType> thicken_and_close_mesh(
414 const MeshType& input_mesh,
415 ScalarOf<MeshType> offset_amount,
416 typename MeshType::Index num_segments = 1)
417{
419 input_mesh,
420 false,
421 Eigen::Matrix<ScalarOf<MeshType>, 3, 1>(0.0, 1.0, 0.0),
422 offset_amount,
423 0.f,
424 num_segments);
425}
426
427} // namespace legacy
428} // namespace lagrange
Definition: Mesh.h:48
bool is_boundary_edge(Index e) const
Determines whether the specified edge e is a boundary edge.
Definition: Mesh.h:821
bool is_edge_data_initialized() const
Edge data accessors (const)
Definition: Mesh.h:643
std::array< Index, 2 > get_edge_vertices(Index e) const
Retrieve edge endpoints.
Definition: Mesh.h:730
Index get_one_facet_around_edge(Index e) const
Get the index of one facet around a given edge.
Definition: Mesh.h:782
Index get_num_edges() const
Gets the number of edges.
Definition: Mesh.h:650
AttributeId compute_vertex_normal(SurfaceMesh< Scalar, Index > &mesh, VertexNormalOptions options={})
Compute per-vertex normals based on specified weighting type.
Definition: compute_vertex_normal.cpp:34
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
Main namespace for Lagrange.
Definition: AABBIGL.h:30
auto create_mesh(const Eigen::MatrixBase< DerivedV > &vertices, const Eigen::MatrixBase< DerivedF > &facets)
This function create a new mesh given the vertex and facet arrays by copying data into the Mesh objec...
Definition: create_mesh.h:39
SurfaceMesh< Scalar, Index > thicken_and_close_mesh(SurfaceMesh< Scalar, Index > input_mesh, const ThickenAndCloseOptions &options={})
Thicken a mesh by offsetting it, and close the shape into a thick 3D solid.
Definition: thicken_and_close_mesh.cpp:270