Lagrange
mesh_convert.impl.h
1/*
2 * Copyright 2022 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_convert.h>
15
16#include <lagrange/IndexedAttribute.h>
17#include <lagrange/MeshTrait.h>
18#include <lagrange/SurfaceMeshTypes.h>
19#include <lagrange/create_mesh.h>
20#include <lagrange/foreach_attribute.h>
21#include <lagrange/utils/Error.h>
22#include <lagrange/utils/assert.h>
23#include <lagrange/utils/strings.h>
24#include <lagrange/views.h>
25#include <lagrange/attribute_names.h>
26#include <lagrange/internal/fast_edge_sort.h>
27
28#include <type_traits>
29
30namespace lagrange {
31
32namespace mesh_convert_detail {
33
39enum Policy {
40 Copy,
41 Wrap,
43};
44
45template <Policy policy, typename Scalar, typename Index, typename InputMeshType>
46SurfaceMesh<Scalar, Index> to_surface_mesh_internal(InputMeshType&& mesh)
47{
48 using CMeshType = std::remove_reference_t<InputMeshType>;
49 using MeshType = std::decay_t<InputMeshType>;
50 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
51 using AttributeArray = typename MeshType::AttributeArray;
52 using IndexArray = typename MeshType::IndexArray;
53 using MeshScalar = typename MeshType::Scalar;
54 using MeshIndex = typename MeshType::Index;
55
56 if constexpr (policy == Policy::Wrap) {
57 using VertexArray = typename MeshType::VertexArray;
58 using FacetArray = typename MeshType::FacetArray;
59 static_assert(
60 std::is_lvalue_reference_v<InputMeshType&&>,
61 "Input mesh type must be a lvalue reference when wrapping a mesh object.");
62 static_assert(VertexArray::IsRowMajor, "Input vertex array must have row major storage");
63 static_assert(FacetArray::IsRowMajor, "Input facet array must have row major storage");
64 }
65
66#define LA_X_to_surface_mesh_scalar(_, S) || std::is_same_v<Scalar, S>
67 static_assert(
68 false LA_SURFACE_MESH_SCALAR_X(to_surface_mesh_scalar, 0),
69 "Scalar type should be float or double");
70#undef LA_X_to_surface_mesh_scalar
71
72#define LA_X_to_surface_mesh_index(_, I) || std::is_same_v<Index, I>
73 static_assert(
74 false LA_SURFACE_MESH_INDEX_X(to_surface_mesh_index, 0),
75 "Index type should be uint32_t or uint64_t");
76#undef LA_X_to_surface_mesh_index
77
78 // 1st - Transfer vertices positions and facets indices
79 SurfaceMesh<Scalar, Index> new_mesh(static_cast<Index>(mesh.get_dim()));
80 if constexpr (policy == Policy::Copy) {
81 // Copy vertex/facet buffer
82 new_mesh.add_vertices(static_cast<Index>(mesh.get_num_vertices()));
83 new_mesh.add_polygons(static_cast<Index>(mesh.get_num_facets()),
84 static_cast<Index>(mesh.get_vertex_per_facet()));
85 if (mesh.get_num_vertices() > 0) {
86 vertex_ref(new_mesh) = mesh.get_vertices().template cast<Scalar>();
87 }
88 if (mesh.get_num_facets() > 0) {
89 facet_ref(new_mesh) = mesh.get_facets().template cast<Index>();
90 }
91 } else if constexpr (policy == Policy::Wrap) {
92 // Wrap vertex/facet buffer, but only if types are compatible
93 static_assert(std::is_same_v<Scalar, MeshScalar>, "Mesh scalar type mismatch");
94 static_assert(std::is_same_v<Index, MeshIndex>, "Mesh index type mismatch");
95 if constexpr (std::is_const_v<CMeshType>) {
96 const auto& vertices = mesh.get_vertices();
97 const auto& facets = mesh.get_facets();
98 new_mesh.wrap_as_const_vertices(
99 {vertices.data(), safe_cast<size_t>(vertices.size())},
100 mesh.get_num_vertices());
101 new_mesh.wrap_as_const_facets(
102 {facets.data(), safe_cast<size_t>(facets.size())},
103 mesh.get_num_facets(),
104 mesh.get_vertex_per_facet());
105 } else {
106 auto& vertices = mesh.ref_vertices();
107 auto& facets = mesh.ref_facets();
108 new_mesh.wrap_as_vertices(
109 {vertices.data(), safe_cast<size_t>(vertices.size())},
110 mesh.get_num_vertices());
111 new_mesh.wrap_as_facets(
112 {facets.data(), safe_cast<size_t>(facets.size())},
113 mesh.get_num_facets(),
114 mesh.get_vertex_per_facet());
115 }
116 }
117
118 // 2nd - Transfer edge indices
119 if (mesh.is_edge_data_initialized()) {
120 new_mesh.initialize_edges(
121 static_cast<Index>(mesh.get_num_edges()),
122 [&](Index e) -> std::array<Index, 2> {
123 auto v = mesh.get_edge_vertices(static_cast<typename MeshType::Index>(e));
124 return {static_cast<Index>(v[0]), static_cast<Index>(v[1])};
125 });
126 }
127
128 // 3rd - Transfer attributes
129 auto usage_from_name = [](std::string_view name) {
132 }
133 // "uv" is for legacy code with hardcoded "uv" name.
134 if (starts_with(name, AttributeName::texcoord) || starts_with(name, "uv")) {
135 return AttributeUsage::UV;
136 }
139 }
141 };
142
143 auto get_unique_name = [&](auto name) -> std::string {
144 if (!new_mesh.has_attribute(name)) {
145 return name;
146 } else {
147 std::string new_name;
148 for (int cnt = 0; cnt < 1000; ++cnt) {
149 new_name = fmt::format("{}.{}", name, cnt);
150 if (!new_mesh.has_attribute(new_name)) {
151 return new_name;
152 }
153 }
154 throw Error(fmt::format("Could not assign a unique attribute name for: {}", name));
155 }
156 };
157
158 auto transfer_attribute = [&](auto&& name, auto&& array, AttributeElement elem) {
159 std::string new_name = get_unique_name(name);
160 decltype(auto) attr = array->template get<AttributeArray>();
161 if constexpr (policy == Policy::Copy) {
162 new_mesh.template create_attribute<MeshScalar>(
163 new_name,
164 elem,
165 usage_from_name(name),
166 attr.cols());
167 attribute_matrix_ref<MeshScalar>(new_mesh, new_name) = attr;
168 } else if constexpr (policy == Policy::Wrap) {
169 if constexpr (std::is_const_v<CMeshType>) {
170 new_mesh.template wrap_as_const_attribute<MeshScalar>(
171 new_name,
172 elem,
173 usage_from_name(name),
174 attr.cols(),
175 {attr.data(), static_cast<size_t>(attr.size())});
176
177 } else {
178 new_mesh.template wrap_as_attribute<MeshScalar>(
179 new_name,
180 elem,
181 usage_from_name(name),
182 attr.cols(),
183 {attr.data(), static_cast<size_t>(attr.size())});
184 }
185 }
186 };
187
188 // Transfer vertex attributes
189 for (const auto& name : mesh.get_vertex_attribute_names()) {
190 transfer_attribute(name, mesh.get_vertex_attribute_array(name), AttributeElement::Vertex);
191 }
192
193 // Transfer facet attributes
194 for (const auto& name : mesh.get_facet_attribute_names()) {
195 transfer_attribute(name, mesh.get_facet_attribute_array(name), AttributeElement::Facet);
196 }
197
198 // Transfer corner attributes
199 for (const auto& name : mesh.get_corner_attribute_names()) {
200 transfer_attribute(name, mesh.get_corner_attribute_array(name), AttributeElement::Corner);
201 }
202
203 // Transfer edge attributes
204 for (const auto& name : mesh.get_edge_attribute_names()) {
205 transfer_attribute(name, mesh.get_edge_attribute_array(name), AttributeElement::Edge);
206 }
207
208 // Transfer indexed attributes
209 for (const auto& name : mesh.get_indexed_attribute_names()) {
210 decltype(auto) attr = mesh.get_indexed_attribute_array(name);
211 decltype(auto) values = std::get<0>(attr)->template get<AttributeArray>();
212 decltype(auto) indices = std::get<1>(attr)->template get<IndexArray>();
213 std::string new_name = get_unique_name(name);
214
215 if constexpr (policy == Policy::Wrap) {
216 static_assert(std::is_same_v<Index, MeshIndex>, "Mesh attribute index type mismatch");
217 if (std::is_const_v<CMeshType>) {
218 new_mesh.template wrap_as_const_indexed_attribute<MeshScalar>(
219 new_name,
220 usage_from_name(name),
221 values.rows(),
222 values.cols(),
223 {values.data(), static_cast<size_t>(values.size())},
224 {indices.data(), static_cast<size_t>(indices.size())});
225 } else {
226 new_mesh.template wrap_as_indexed_attribute<MeshScalar>(
227 new_name,
228 usage_from_name(name),
229 values.rows(),
230 values.cols(),
231 {values.data(), static_cast<size_t>(values.size())},
232 {indices.data(), static_cast<size_t>(indices.size())});
233 }
234 } else if constexpr (policy == Policy::Copy) {
235 auto id = new_mesh.template create_attribute<MeshScalar>(
236 new_name,
238 usage_from_name(name),
239 values.cols());
240 auto& new_attr = new_mesh.template ref_indexed_attribute<MeshScalar>(id);
241 new_attr.values().resize_elements(values.rows());
242 matrix_ref(new_attr.values()) = values;
243 reshaped_ref(new_attr.indices(), indices.cols()) = indices.template cast<Index>();
244 }
245 }
246
247 return new_mesh;
248}
249
250} // namespace mesh_convert_detail
251
252template <typename Scalar, typename Index, typename MeshType>
254{
255 return mesh_convert_detail::
256 to_surface_mesh_internal<mesh_convert_detail::Policy::Copy, Scalar, Index>(mesh);
257}
258
259template <typename Scalar, typename Index, typename MeshType>
261{
262 return mesh_convert_detail::
263 to_surface_mesh_internal<mesh_convert_detail::Policy::Wrap, Scalar, Index>(
264 std::forward<MeshType>(mesh));
265}
266
267template <typename MeshType, typename Scalar, typename Index>
268std::unique_ptr<MeshType> to_legacy_mesh(const SurfaceMesh<Scalar, Index>& mesh)
269{
270 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
271 using VertexArray = typename MeshType::VertexArray;
272 using FacetArray = typename MeshType::FacetArray;
273 using IndexArray = typename MeshType::IndexArray;
274 using AttributeArray = typename MeshType::AttributeArray;
275 using MeshScalar = typename MeshType::Scalar;
276 using MeshIndex = typename MeshType::Index;
277
278#define LA_X_to_legacy_mesh_scalar(_, S) || std::is_same_v<Scalar, S>
279 static_assert(
280 false LA_SURFACE_MESH_SCALAR_X(to_legacy_mesh_scalar, 0),
281 "Scalar type should be float or double");
282#undef LA_X_to_legacy_mesh_scalar
283
284#define LA_X_to_legacy_mesh_index(_, I) || std::is_same_v<Index, I>
285 static_assert(
286 false LA_SURFACE_MESH_INDEX_X(to_legacy_mesh_index, 0),
287 "Index type should be uint32_t or uint64_t");
288#undef LA_X_to_legacy_mesh_index
289
290 la_runtime_assert(mesh.is_regular(), "Input polygonal mesh is not regular");
291
292 LA_IGNORE(mesh);
293 auto new_mesh = [&] {
294 VertexArray vertices;
295 FacetArray facets;
296 if (mesh.get_num_vertices() > 0) {
297 vertices = vertex_view(mesh).template cast<MeshScalar>();
298 }
299 if (mesh.get_num_facets() > 0) {
300 facets = facet_view(mesh).template cast<MeshIndex>();
301 }
302 return create_mesh<VertexArray, FacetArray>(std::move(vertices), std::move(facets));
303 }();
304
305 // If mesh contains edges, attempt to transfer them as well
306 std::vector<Index> old_edge_ids;
307 std::vector<MeshIndex> new_edge_ids;
308 if (mesh.has_edges()) {
309 logger().warn("Mesh contains edges information. A possible reordering may occur.");
310 new_mesh->initialize_edge_data();
312 mesh.get_num_edges() == static_cast<Index>(new_mesh->get_num_edges()),
313 "Number of edges do not match");
314 const auto num_vertices = static_cast<size_t>(mesh.get_num_vertices());
315 auto buffer = std::make_unique<uint8_t[]>(
316 (num_vertices + 1) * std::max(sizeof(Index), sizeof(MeshIndex)));
317 old_edge_ids = internal::fast_edge_sort(
318 mesh.get_num_edges(),
319 mesh.get_num_vertices(),
320 [&](Index e) -> std::array<Index, 2> { return mesh.get_edge_vertices(e); },
321 {reinterpret_cast<Index*>(buffer.get()), num_vertices + 1});
322 new_edge_ids = internal::fast_edge_sort(
323 new_mesh->get_num_edges(),
324 new_mesh->get_num_vertices(),
325 [&](MeshIndex e) -> std::array<MeshIndex, 2> { return new_mesh->get_edge_vertices(e); },
326 {reinterpret_cast<MeshIndex*>(buffer.get()), num_vertices + 1});
327 }
328
329 // Transfer non-indexed attributes
330 seq_foreach_named_attribute_read<~AttributeElement::Indexed>(
331 mesh,
332 [&](std::string_view name_view, auto&& attr) {
333 using AttributeType = std::decay_t<decltype(attr)>;
334 using ValueType = typename AttributeType::ValueType;
335 if (mesh.attr_name_is_reserved(name_view)) {
336 return;
337 }
338 std::string name(name_view);
339 AttributeArray vals =
340 attribute_matrix_view<ValueType>(mesh, name).template cast<MeshScalar>();
341 switch (attr.get_element_type()) {
343 new_mesh->add_vertex_attribute(name);
344 new_mesh->import_vertex_attribute(name, std::move(vals));
345 break;
346 }
348 new_mesh->add_facet_attribute(name);
349 new_mesh->import_facet_attribute(name, std::move(vals));
350 break;
351 }
353 new_mesh->add_corner_attribute(name);
354 new_mesh->import_corner_attribute(name, std::move(vals));
355 break;
356 }
358 // Permute rows
359 auto old_vals = attribute_matrix_view<ValueType>(mesh, name);
360 for (Index e = 0; e < mesh.get_num_edges(); ++e) {
361 vals.row(new_edge_ids[e]) =
362 old_vals.row(old_edge_ids[e]).template cast<MeshScalar>();
363 }
364 new_mesh->add_edge_attribute(name);
365 new_mesh->import_edge_attribute(name, std::move(vals));
366 break;
367 }
369 logger().warn("Cannot transfer value attribute: {}", name);
370 break;
372 // Not possible by construction
373 break;
374 }
375 });
376
377 // Transfer indexed attributes
378 const auto nvpf = static_cast<size_t>(new_mesh->get_vertex_per_facet());
379 seq_foreach_named_attribute_read<AttributeElement::Indexed>(
380 mesh,
381 [&](std::string_view name_view, auto&& attr) {
382 if (mesh.attr_name_is_reserved(name_view)) {
383 return;
384 }
385 std::string name(name_view);
386 IndexArray indices = reshaped_view(attr.indices(), nvpf).template cast<MeshIndex>();
387 AttributeArray values = matrix_view(attr.values()).template cast<MeshScalar>();
388 new_mesh->add_indexed_attribute(name);
389 new_mesh->import_indexed_attribute(name, std::move(values), std::move(indices));
390 });
391
392 return new_mesh;
393}
394
395} // namespace lagrange
Definition: Mesh.h:48
A general purpose polygonal mesh class.
Definition: SurfaceMesh.h:66
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:40
AttributeElement
Type of element to which the attribute is attached.
Definition: AttributeFwd.h:26
@ Vector
Mesh attribute can have any number of channels (including 1 channel).
@ Normal
Mesh attribute can have dim or dim + 1 channels.
@ Color
Mesh attribute can have 1, 2, 3 or 4 channels.
@ UV
Mesh attribute must have exactly 2 channels.
@ Value
Values that are not attached to a specific element.
Definition: AttributeFwd.h:42
@ Edge
Per-edge mesh attributes.
Definition: AttributeFwd.h:34
@ Indexed
Indexed mesh attributes.
Definition: AttributeFwd.h:45
@ Facet
Per-facet mesh attributes.
Definition: AttributeFwd.h:31
@ Corner
Per-corner mesh attributes.
Definition: AttributeFwd.h:37
@ Vertex
Per-vertex mesh attributes.
Definition: AttributeFwd.h:28
SurfaceMesh< Scalar, Index > to_surface_mesh_wrap(MeshType &&mesh)
Wrap a legacy mesh object as a surface mesh object.
Definition: mesh_convert.impl.h:260
SurfaceMesh< Scalar, Index > to_surface_mesh_copy(const MeshType &mesh)
Convert a legacy mesh object to a surface mesh object.
Definition: mesh_convert.impl.h:253
std::unique_ptr< MeshType > to_legacy_mesh(const SurfaceMesh< Scalar, Index > &mesh)
Convert a surface mesh object to a legacy mesh object.
Definition: mesh_convert.impl.h:268
ConstRowMatrixView< ValueType > matrix_view(const Attribute< ValueType > &attribute)
Returns a read-only view of a given attribute in the form of an Eigen matrix.
Definition: views.cpp:35
ConstRowMatrixView< Scalar > vertex_view(const SurfaceMesh< Scalar, Index > &mesh)
Returns a read-only view of the mesh vertices in the form of an Eigen matrix.
Definition: views.cpp:156
RowMatrixView< Scalar > vertex_ref(SurfaceMesh< Scalar, Index > &mesh)
Returns a writable view of the mesh vertices in the form of an Eigen matrix.
Definition: views.cpp:150
ConstRowMatrixView< ValueType > reshaped_view(const Attribute< ValueType > &attribute, size_t num_cols)
Returns a read-only view of a given single-channel attribute in the form of an Eigen matrix with a pr...
Definition: views.cpp:71
ConstRowMatrixView< Index > facet_view(const SurfaceMesh< Scalar, Index > &mesh)
Returns a read-only view of a mesh facets in the form of an Eigen matrix.
Definition: views.cpp:170
RowMatrixView< ValueType > reshaped_ref(Attribute< ValueType > &attribute, size_t num_cols)
Returns a writable view of a given single-channel attribute in the form of an Eigen matrix with a pre...
Definition: views.cpp:58
RowMatrixView< Index > facet_ref(SurfaceMesh< Scalar, Index > &mesh)
Returns a writable view of a mesh facets in the form of an Eigen matrix.
Definition: views.cpp:162
RowMatrixView< ValueType > matrix_ref(Attribute< ValueType > &attribute)
Returns a writable view of a given attribute in the form of an Eigen matrix.
Definition: views.cpp:26
#define LA_SURFACE_MESH_INDEX_X(mode, data)
X Macro arguments to iterate over index types available for the SurfaceMesh<> class.
Definition: SurfaceMeshTypes.h:71
#define LA_SURFACE_MESH_SCALAR_X(mode, data)
X Macro arguments to iterate over scalar types available for the SurfaceMesh<> class.
Definition: SurfaceMeshTypes.h:91
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
LA_CORE_API bool starts_with(std::string_view str, std::string_view prefix)
Checks if the string begins with the given prefix.
Definition: strings.cpp:38
#define LA_IGNORE(x)
Ignore x to avoid an unused variable warning.
Definition: warning.h:144
std::vector< Index > fast_edge_sort(Index num_edges, Index num_vertices, Func get_edge, span< Index > vertex_to_first_edge={})
Sort an array of edges using a parallel bucket sort.
Definition: fast_edge_sort.h:71
Main namespace for Lagrange.
Definition: AABBIGL.h:30
@ Error
Throw an error if collision is detected.
static constexpr std::string_view normal
Normal.
Definition: attribute_names.h:27
static constexpr std::string_view color
Color.
Definition: attribute_names.h:32
static constexpr std::string_view texcoord
Texture coordinates.
Definition: attribute_names.h:37
MeshTrait class provide compiler check for different mesh types.
Definition: MeshTrait.h:108