Lagrange
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Modules Pages
load_mesh_ext.h
1/*
2 * Copyright 2020 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 <fstream>
15#include <limits>
16#include <string>
17
18#include <lagrange/MeshTrait.h>
19#include <lagrange/attributes/attribute_utils.h>
20#include <lagrange/common.h>
21#include <lagrange/create_mesh.h>
22#include <lagrange/normalize_meshes.h>
23#include <lagrange/utils/range.h>
24#include <lagrange/utils/safe_cast.h>
25#include <lagrange/legacy/inline.h>
26
27#include <lagrange/fs/filesystem.h>
28#include <tiny_obj_loader.h>
29
30namespace lagrange::io {
31LAGRANGE_LEGACY_INLINE
32namespace legacy {
33
35{
42 bool triangulate = false;
43
45 bool normalize = false;
46
47 bool load_normals = true;
48 bool load_uvs = true;
49 bool load_materials = true;
50
52 bool as_one_mesh = false;
53};
54
55template <typename MeshType>
57{
58 bool success = true;
59 std::vector<std::unique_ptr<MeshType>> meshes;
60 std::vector<tinyobj::material_t> materials;
61 std::vector<std::string> mesh_names;
62};
63
64inline int fix_index(int index, size_t n, size_t global_offset)
65{
66 // Shifted absolute index
67 if (index > 0) return index - static_cast<int>(global_offset) - 1;
68 // Relative index
69 if (index < 0) return static_cast<int>(n) + index - static_cast<int>(global_offset);
70 // 0 index is invalid in obj files
71 return -1;
72}
73
85template <typename MeshType>
86auto load_mesh_ext(
87 std::istream& input_stream,
88 const MeshLoaderParams& params = {},
89 tinyobj::MaterialReader* material_reader = nullptr) -> MeshLoaderResult<MeshType>
90{
91 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
92 using VertexArray = typename MeshType::VertexArray;
93 using FacetArray = typename MeshType::FacetArray;
94
95 MeshLoaderResult<MeshType> result;
96
97 if (!input_stream.good()) {
98 logger().error("Invalid input stream given");
99 result.success = false;
100 return result;
101 }
102
105 struct Loader
106 {
107 Loader(const MeshLoaderParams& _params, MeshLoaderResult<MeshType>& _result)
108 : params(_params)
109 , result(_result)
110 {}
111
112 const MeshLoaderParams& params;
113 MeshLoaderResult<MeshType>& result;
114
115 std::vector<tinyobj::real_t> vertices;
116 std::vector<tinyobj::real_t> normals;
117 std::vector<tinyobj::real_t> uvs;
118 std::vector<tinyobj::index_t> indices;
119 std::vector<int> material_ids;
120
121 size_t vertex_offset = 0;
122 size_t normal_offset = 0;
123 size_t uv_offset = 0;
124
125 std::string object_name = "";
126 int current_material_id = 0;
127
128 std::vector<unsigned char> face_sizes;
129 int max_face_size = 0;
130 bool is_face_size_constant = false;
131 bool is_last_object = false;
132
133 void triangulate()
134 {
135 std::vector<tinyobj::index_t> new_indices;
136 std::vector<unsigned char> new_face_sizes;
137 std::vector<int> new_material_ids;
138 size_t indices_i = 0;
139
140 // Loop over faces
141 for (auto face_index : range(face_sizes.size())) {
142 const auto vertex_in_face_num = face_sizes[face_index];
143 const auto new_triangle_num = (vertex_in_face_num - 3) + 1;
144
145 // Triangle fan conversion
146 for (auto i : range(new_triangle_num)) {
147 // Always use first vertex of origin polygon
148 tinyobj::index_t a;
149 a.vertex_index = indices[indices_i + 0].vertex_index;
150 a.normal_index = indices[indices_i + 0].normal_index;
151 a.texcoord_index = indices[indices_i + 0].texcoord_index;
152
153 // Next two vertices shift by one in every iteration
154 tinyobj::index_t b;
155 b.vertex_index = indices[indices_i + i + 1].vertex_index;
156 b.normal_index = indices[indices_i + i + 1].normal_index;
157 b.texcoord_index = indices[indices_i + i + 1].texcoord_index;
158 tinyobj::index_t c;
159 c.vertex_index = indices[indices_i + i + 2].vertex_index;
160 c.normal_index = indices[indices_i + i + 2].normal_index;
161 c.texcoord_index = indices[indices_i + i + 2].texcoord_index;
162
163 new_indices.push_back(a);
164 new_indices.push_back(b);
165 new_indices.push_back(c);
166 new_face_sizes.push_back(3);
167
168 if (face_index < material_ids.size()) {
169 new_material_ids.push_back(material_ids[face_index]);
170 }
171 }
172
173 indices_i += vertex_in_face_num;
174 }
175
176 // Replace old polygon indices with triangle fan indices
177 indices = new_indices;
178 face_sizes = new_face_sizes;
179 material_ids = new_material_ids;
180 max_face_size = 3;
181 is_face_size_constant = true;
182 }
183
184 } mesh_loader(params, result);
185
186
187 // Tinyobj callbacks (triggered when a line is parsed)
188 // callbacks are function pointers with void * user_data, hence no capture.
189 const auto vertex_cb = [](void* user_data,
190 tinyobj::real_t x,
191 tinyobj::real_t y,
192 tinyobj::real_t z,
193 tinyobj::real_t w) {
194 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
195 loader.vertices.insert(loader.vertices.end(), {x, y, z});
196 (void)(w); // Avoid unused parameter warning.
197 };
198
199 const auto normal_cb =
200 [](void* user_data, tinyobj::real_t x, tinyobj::real_t y, tinyobj::real_t z) {
201 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
202 if (loader.params.load_normals) {
203 loader.normals.insert(loader.normals.end(), {x, y, z});
204 }
205 };
206
207 const auto texcoord_cb =
208 [](void* user_data, tinyobj::real_t x, tinyobj::real_t y, tinyobj::real_t z) {
209 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
210 if (loader.params.load_uvs) {
211 loader.uvs.insert(loader.uvs.end(), {x, y});
212 }
213 (void)(z); // Avoid unused parameter warning.
214 };
215
216 const auto usemtl_cb = [](void* user_data, const char* name, int material_id) {
217 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
218 loader.current_material_id = material_id;
219 (void)(name); // Avoid unused parameter warning.
220 };
221
222 const auto mtllib_cb =
223 [](void* user_data, const tinyobj::material_t* materials, int num_materials) {
224 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
225 loader.result.materials.insert(
226 loader.result.materials.end(),
227 materials,
228 materials + num_materials);
229 };
230
231 const auto index_cb = [](void* user_data, tinyobj::index_t* indices, int num_indices) {
232 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
233 auto num_vertices = loader.vertices.size() / 3;
234 auto num_normals = loader.normals.size() / 3;
235 auto num_uvs = loader.uvs.size() / 2;
236 for (auto i = 0; i < num_indices; i++) {
237 auto index = indices[i];
238 index.vertex_index = fix_index(index.vertex_index, num_vertices, loader.vertex_offset);
239 assert(index.vertex_index >= 0 && index.vertex_index < static_cast<int>(num_vertices));
240 if (loader.params.load_normals && num_normals) {
241 index.normal_index =
242 fix_index(index.normal_index, num_normals, loader.normal_offset);
243 assert(
244 index.normal_index >= 0 && index.normal_index < static_cast<int>(num_normals));
245 } else {
246 index.normal_index = -1;
247 }
248 if (loader.params.load_uvs && num_uvs) {
249 index.texcoord_index = fix_index(index.texcoord_index, num_uvs, loader.uv_offset);
250 assert(
251 index.texcoord_index >= 0 && index.texcoord_index < static_cast<int>(num_uvs));
252 } else {
253 index.texcoord_index = -1;
254 }
255 loader.indices.push_back(index);
256 }
257
258 loader.face_sizes.push_back(static_cast<unsigned char>(num_indices));
259 if (loader.params.load_materials) {
260 loader.material_ids.push_back(loader.current_material_id);
261 }
262
263 if (loader.max_face_size != 0 && num_indices != loader.max_face_size) {
264 loader.is_face_size_constant = false;
265 }
266 loader.max_face_size = std::max(num_indices, loader.max_face_size);
267 };
268
269 const auto object_cb = [](void* user_data, const char* name) {
270 Loader& loader = *(reinterpret_cast<Loader*>(user_data));
271
272 size_t num_coords;
273 size_t max_face_size;
274 const auto DIM = 3;
275 const auto UV_DIM = 2;
276
277 if (VertexArray::ColsAtCompileTime == Eigen::Dynamic) {
278 num_coords = DIM;
279 } else {
280 num_coords = VertexArray::ColsAtCompileTime;
281 }
282 if (FacetArray::ColsAtCompileTime == Eigen::Dynamic) {
283 max_face_size = size_t(loader.max_face_size);
284 if (!loader.is_face_size_constant && loader.params.triangulate) {
285 max_face_size = 3;
286 }
287 } else {
288 max_face_size = size_t(FacetArray::ColsAtCompileTime);
289 }
290
291 if (loader.params.as_one_mesh && !loader.is_last_object) {
292 return;
293 }
294
295 // First object begins
296 if (loader.vertices.size() == 0) {
297 loader.object_name = name;
298 return;
299 }
300
301 // Triangulate if needed
302 if (max_face_size < safe_cast<size_t>(loader.max_face_size)) {
303 loader.triangulate();
304 }
305
306 auto num_faces = loader.face_sizes.size();
307 VertexArray vertices(loader.vertices.size() / DIM, num_coords);
308 FacetArray faces(num_faces, max_face_size);
309
310 typename MeshType::UVArray uvs;
311 typename MeshType::UVIndices uv_indices;
312 typename MeshType::AttributeArray corner_normals;
313 if (!loader.uvs.empty()) {
314 uvs.resize(loader.uvs.size() / UV_DIM, UV_DIM);
315 uv_indices.resize(num_faces, max_face_size);
316 }
317 if (!loader.normals.empty()) {
318 corner_normals.resize(num_faces * max_face_size, num_coords);
319 }
320
321 // Copy vertices
322 for (auto i : range(loader.vertices.size() / DIM)) {
323 for (auto k : range(num_coords)) {
324 vertices(i, k) = safe_cast<typename MeshType::Scalar>(loader.vertices[DIM * i + k]);
325 }
326 }
327
328 // Copy uvs
329 for (auto i : range(loader.uvs.size() / UV_DIM)) {
330 for (auto k : range(UV_DIM)) {
331 uvs(i, k) = safe_cast<typename MeshType::Scalar>(loader.uvs[UV_DIM * i + k]);
332 }
333 }
334
335 // Copy indices
336 size_t indices_i = 0;
337 for (auto face_index : range(loader.face_sizes.size())) {
338 const auto face_size = loader.face_sizes[face_index];
339
340 for (auto vertex_in_face : range(face_size)) {
341 const auto& index = loader.indices[indices_i];
342 faces(face_index, vertex_in_face) =
343 safe_cast<typename MeshType::Index>(index.vertex_index);
344
345 if (index.normal_index != -1) {
346 const auto row_index = face_index * max_face_size + vertex_in_face;
347 for (auto k : range(num_coords)) {
348 corner_normals(row_index, k) = safe_cast<typename MeshType::Scalar>(
349 loader.normals[DIM * index.normal_index + k]);
350 }
351 }
352
353 if (!loader.uvs.empty()) {
354 uv_indices(face_index, vertex_in_face) =
355 safe_cast<typename MeshType::Index>(index.texcoord_index);
356 }
357
358 indices_i++;
359 }
360
361 // Padding with invalid<Index> for mixed triangle/quad or arbitrary polygon meshes
362 for (auto pad = size_t(face_size); pad < size_t(max_face_size); pad++) {
363 faces(face_index, pad) = invalid<typename MeshType::Index>();
364 if (!loader.uvs.empty()) {
365 uv_indices(face_index, pad) = invalid<typename MeshType::UVIndices::Scalar>();
366 }
367 if (!loader.normals.empty()) {
368 corner_normals.row(face_index * max_face_size + pad).setZero();
369 }
370 }
371 }
372
373 auto mesh = create_mesh(vertices, faces);
374
375 if (!loader.uvs.empty()) {
376 mesh->initialize_uv(uvs, uv_indices);
377
378 // TODO: The loader should not do this mapping index -> corner
379 map_indexed_attribute_to_corner_attribute(*mesh, "uv");
380 }
381
382 if (!loader.normals.empty()) {
383 mesh->add_corner_attribute("normal");
384 mesh->import_corner_attribute("normal", corner_normals);
385 }
386
387 if (loader.result.materials.size() > 0 && loader.material_ids.size() == num_faces) {
388 // Because AttributeArray is not integral type, this converts to float type
389 Eigen::Map<Eigen::VectorXi> map(loader.material_ids.data(), loader.material_ids.size());
390 mesh->add_facet_attribute("material_id");
391 mesh->set_facet_attribute("material_id", map.cast<typename MeshType::Scalar>());
392 }
393
394 loader.result.meshes.emplace_back(std::move(mesh));
395
396 loader.result.mesh_names.push_back(loader.object_name);
397
398 loader.vertex_offset += loader.vertices.size() / DIM;
399 loader.normal_offset += loader.normals.size() / DIM;
400 loader.uv_offset += loader.uvs.size() / UV_DIM;
401
402 loader.vertices.clear();
403 loader.uvs.clear();
404 loader.normals.clear();
405 loader.indices.clear();
406 loader.face_sizes.clear();
407 loader.material_ids.clear();
408 loader.max_face_size = 0;
409 loader.is_face_size_constant = true;
410 loader.object_name = name;
411 };
412
413 tinyobj::callback_t callback;
414 callback.vertex_cb = vertex_cb;
415 callback.index_cb = index_cb;
416 callback.object_cb = object_cb;
417
418 if (params.load_normals) callback.normal_cb = normal_cb;
419
420 if (params.load_uvs) callback.texcoord_cb = texcoord_cb;
421
422 if (params.load_materials) {
423 callback.mtllib_cb = mtllib_cb;
424 callback.usemtl_cb = usemtl_cb;
425 }
426
427 std::string warning_message;
428 std::string error_message;
429 result.success = tinyobj::LoadObjWithCallback(
430 input_stream,
431 callback,
432 &mesh_loader,
433 params.load_materials ? material_reader : nullptr,
434 &warning_message,
435 &error_message);
436
437 mesh_loader.is_last_object = true;
438 object_cb(&mesh_loader, "");
439
440 if (!error_message.empty()) {
441 logger().error("Load mesh warning:\n{}", error_message);
442 }
443 if (!warning_message.empty()) {
444 logger().warn("Load mesh error:\n{}", warning_message);
445 }
446
447 if (params.normalize) {
448 normalize_meshes(mesh_loader.result.meshes);
449 }
450
451 return result;
452}
453
455template <typename MeshType>
456MeshLoaderResult<MeshType> load_mesh_ext(
457 const lagrange::fs::path& filename,
458 const MeshLoaderParams& params = {},
459 tinyobj::MaterialReader* material_reader = nullptr)
460{
461 lagrange::fs::ifstream stream(filename);
462 if (!stream.good()) {
463 MeshLoaderResult<MeshType> result;
464 logger().error("Cannot open file: \"{}\"", filename.string());
465 result.success = false;
466 return result;
467 }
468 tinyobj::MaterialFileReader default_reader(filename.parent_path().string());
469 if (params.load_materials && !material_reader) {
470 material_reader = &default_reader;
471 }
472 return load_mesh_ext<MeshType>(stream, params, material_reader);
473}
474
475} // namespace legacy
476} // namespace lagrange::io
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:40
void normalize_meshes(span< SurfaceMesh< Scalar, Index > * > meshes, const TransformOptions &options={})
Normalize a list of meshes to fit in a unit box centered at the origin.
Definition: normalize_meshes.cpp:102
internal::Range< Index > range(Index end)
Returns an iterable object representing the range [0, end).
Definition: range.h:176
Mesh input/output.
Definition: detect_file_format.h:18
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
MeshTrait class provide compiler check for different mesh types.
Definition: MeshTrait.h:108
Definition: load_mesh_ext.h:35
bool as_one_mesh
Combines individual objects into a single mesh. Result contains a vector of size 1.
Definition: load_mesh_ext.h:52
bool triangulate
When loading a mesh with mixed facet sizes, this parameter controls whether the polygonal faces will ...
Definition: load_mesh_ext.h:42
bool normalize
Normalize each object to a unit box around the origin.
Definition: load_mesh_ext.h:45
Definition: load_mesh_ext.h:57