ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
TriangleMeshIO.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "io/TriangleMeshIO.h"
9 
10 #include <Eigen/Geometry>
11 #include <fstream>
12 #include <numeric>
13 #include <unordered_map>
14 
15 // CV_CORE_LIB
16 #include <FileSystem.h>
17 #include <Helper.h>
18 #include <Logging.h>
19 #include <ProgressReporters.h>
20 
21 // CV_DB_LIB
22 #include <ecvHObjectCaster.h>
23 #include <ecvMesh.h>
24 #include <ecvPointCloud.h>
25 
26 // CV_IO_LIB
27 #include <ImageIO.h>
28 #include <rply.h>
29 #include <tiny_gltf.h>
30 #include <tiny_obj_loader.h>
31 
32 #include "io/FileFormatIO.h"
33 
34 namespace cloudViewer {
35 
36 namespace {
37 using namespace io;
38 
39 namespace ply_trianglemesh_reader {
40 
41 struct PLYReaderState {
42  utility::CountingProgressReporter* progress_bar;
45  long vertex_num;
47  long normal_num;
49  long color_num;
50  std::vector<unsigned int> face;
51  long face_index;
52  long face_num;
53  double colors[3];
54  double normals[3];
55 };
56 
57 int ReadVertexCallback(p_ply_argument argument) {
58  PLYReaderState* state_ptr;
59  long index;
60  ply_get_argument_user_data(argument, reinterpret_cast<void**>(&state_ptr),
61  &index);
62  if (state_ptr->vertex_index >= state_ptr->vertex_num) {
63  return 0;
64  }
65 
66  double value = ply_get_argument_value(argument);
67  CCVector3* P_ptr = const_cast<CCVector3*>(
68  state_ptr->mesh_ptr->getAssociatedCloud()->getPoint(
69  static_cast<unsigned int>(state_ptr->vertex_index)));
70  P_ptr->u[index] = value;
71  if (index == 2) { // reading 'z'
72  state_ptr->vertex_index++;
73  ++(*state_ptr->progress_bar);
74  }
75  return 1;
76 }
77 
78 int ReadNormalCallback(p_ply_argument argument) {
79  PLYReaderState* state_ptr;
80  long index;
81  ply_get_argument_user_data(argument, reinterpret_cast<void**>(&state_ptr),
82  &index);
83  if (state_ptr->normal_index >= state_ptr->normal_num) {
84  return 0;
85  }
86 
87  state_ptr->normals[index] = ply_get_argument_value(argument);
88 
89  if (index == 2) { // reading 'nz'
90  state_ptr->mesh_ptr->setVertexNormal(
91  state_ptr->normal_index,
92  Eigen::Vector3d(state_ptr->normals[0], state_ptr->normals[1],
93  state_ptr->normals[2]));
94  state_ptr->normal_index++;
95  }
96  return 1;
97 }
98 
99 int ReadColorCallback(p_ply_argument argument) {
100  PLYReaderState* state_ptr;
101  long index;
102  ply_get_argument_user_data(argument, reinterpret_cast<void**>(&state_ptr),
103  &index);
104  if (state_ptr->color_index >= state_ptr->color_num) {
105  return 0;
106  }
107 
108  double value = ply_get_argument_value(argument);
109  state_ptr->colors[index] = value / 255.0;
110 
111  if (index == 2) { // reading 'blue'
112  state_ptr->mesh_ptr->setVertexColor(
113  state_ptr->color_index,
114  Eigen::Vector3d(state_ptr->colors[0], state_ptr->colors[1],
115  state_ptr->colors[2]));
116  state_ptr->color_index++;
117  }
118  return 1;
119 }
120 
121 int ReadFaceCallBack(p_ply_argument argument) {
122  PLYReaderState* state_ptr;
123  long dummy, length, index;
124  ply_get_argument_user_data(argument, reinterpret_cast<void**>(&state_ptr),
125  &dummy);
126  double value = ply_get_argument_value(argument);
127  if (state_ptr->face_index >= state_ptr->face_num) {
128  return 0;
129  }
130 
131  ply_get_argument_property(argument, nullptr, &length, &index);
132  if (index == -1) {
133  state_ptr->face.clear();
134  } else {
135  state_ptr->face.push_back(int(value));
136  }
137  if (long(state_ptr->face.size()) == length) {
138  if (!AddTrianglesByEarClipping(*state_ptr->mesh_ptr, state_ptr->face)) {
140  "Read PLY failed: A polygon in the mesh could not be "
141  "decomposed into triangles.");
142  return 0;
143  }
144  state_ptr->face_index++;
145  ++(*state_ptr->progress_bar);
146  }
147  return 1;
148 }
149 
150 } // namespace ply_trianglemesh_reader
151 
152 static const std::unordered_map<
153  std::string,
154  std::function<bool(
155  const std::string&, ccMesh&, const ReadTriangleMeshOptions&)>>
157  {"ply", ReadTriangleMeshFromPLY},
160  {"off", ReadTriangleMeshFromOFF},
161  {"gltf", ReadTriangleMeshUsingASSIMP},
164  {"vtk", AutoReadMesh},
165  {"bin", AutoReadMesh},
166  };
167 
168 static const std::unordered_map<std::string,
169  std::function<bool(const std::string&,
170  const ccMesh&,
171  const bool,
172  const bool,
173  const bool,
174  const bool,
175  const bool,
176  const bool)>>
178  {"ply", WriteTriangleMeshToPLY},
179  {"stl", WriteTriangleMeshToSTL},
180  {"obj", WriteTriangleMeshToOBJ},
181  {"off", WriteTriangleMeshToOFF},
182  {"gltf", WriteTriangleMeshToGLTF},
183  {"glb", WriteTriangleMeshToGLTF},
184  {"vtk", AutoWriteMesh},
185  {"bin", AutoWriteMesh},
186  };
187 
188 } // unnamed namespace
189 
190 namespace io {
191 using namespace cloudViewer;
192 std::shared_ptr<ccMesh> CreateMeshFromFile(const std::string& filename,
193  bool print_progress) {
194  auto mesh = std::make_shared<ccMesh>();
195  if (!mesh->CreateInternalCloud()) {
196  utility::LogError("creating internal cloud failed!");
197  return nullptr;
198  }
199 
201  opt.print_progress = print_progress;
202  ReadTriangleMesh(filename, *mesh, opt);
203  return mesh;
204 }
205 
206 bool ReadTriangleMesh(const std::string& filename,
207  ccMesh& mesh,
209  std::string filename_ext =
211  if (filename_ext.empty()) {
213  "Read ccMesh failed: unknown file "
214  "extension.");
215  return false;
216  }
217  auto map_itr =
219  if (map_itr == file_extension_to_trianglemesh_read_function.end()) {
221  "Read ccMesh failed: unknown file "
222  "extension.");
223  return false;
224  }
225 
226  if (params.print_progress) {
227  auto progress_text = std::string("Reading ") +
228  utility::ToUpper(filename_ext) +
229  " file: " + filename;
230  auto pbar = utility::ConsoleProgressBar(100, progress_text, true);
231  params.update_progress = [pbar](double percent) mutable -> bool {
232  pbar.setCurrentCount(size_t(percent));
233  return true;
234  };
235  }
236 
237  if (!mesh.getAssociatedCloud()) {
238  if (!mesh.CreateInternalCloud()) {
239  utility::LogError("creating internal cloud failed!");
240  return false;
241  }
242  }
243 
244  bool success = map_itr->second(filename, mesh, params);
245  utility::LogDebug("Read ccMesh: {:d} triangles and {:d} vertices.",
246  mesh.size(), mesh.getVerticeSize());
247  if (mesh.HasVertices() && !mesh.hasTriangles()) {
249  "ccMesh appears to be a geometry::ccPointCloud "
250  "(only contains vertices, but no triangles).");
251  }
252  return success;
253 }
254 
255 bool WriteTriangleMesh(const std::string& filename,
256  const ccMesh& mesh,
257  bool write_ascii /* = false*/,
258  bool compressed /* = false*/,
259  bool write_vertex_normals /* = true*/,
260  bool write_vertex_colors /* = true*/,
261  bool write_triangle_uvs /* = true*/,
262  bool print_progress /* = false*/) {
263  std::string filename_ext =
265  if (filename_ext.empty()) {
267  "Write ccMesh failed: unknown file "
268  "extension.");
269  return false;
270  }
271  auto map_itr =
273  if (map_itr == file_extension_to_trianglemesh_write_function.end()) {
275  "Write ccMesh failed: unknown file "
276  "extension.");
277  return false;
278  }
279  bool success = map_itr->second(filename, mesh, write_ascii, compressed,
280  write_vertex_normals, write_vertex_colors,
281  write_triangle_uvs, print_progress);
282  utility::LogDebug("Write ccMesh: {:d} triangles and {:d} vertices.",
283  mesh.size(), mesh.getVerticeSize());
284  return success;
285 }
286 
287 // Reference: https://stackoverflow.com/a/43896965
288 bool IsPointInsidePolygon(const Eigen::MatrixX2d& polygon, double x, double y) {
289  bool inside = false;
290  for (int i = 0; i < polygon.rows(); ++i) {
291  // i and j are the indices of the first and second vertices.
292  int j = (i + 1) % polygon.rows();
293 
294  // The vertices of the edge that will be checked.
295  double vx0 = polygon(i, 0);
296  double vy0 = polygon(i, 1);
297  double vx1 = polygon(j, 0);
298  double vy1 = polygon(j, 1);
299 
300  // Check whether the edge intersects a line from (-inf,y) to (x,y).
301  // First, check if the line crosses the horizontal line at y in either
302  // direction.
303  if (((vy0 <= y) && (vy1 > y)) || ((vy1 <= y) && (vy0 > y))) {
304  // If so, get the point where it crosses that line.
305  double cross = (vx1 - vx0) * (y - vy0) / (vy1 - vy0) + vx0;
306 
307  // Finally, check if it crosses to the left of the test point.
308  if (cross < x) inside = !inside;
309  }
310  }
311  return inside;
312 }
313 
315  std::vector<unsigned int>& indices) {
316  int n = int(indices.size());
317  Eigen::Vector3d face_normal = Eigen::Vector3d::Zero();
318  if (n > 3) {
319  for (int i = 0; i < n; i++) {
320  const Eigen::Vector3d& v1 = mesh.getVertice(indices[(i + 1) % n]) -
321  mesh.getVertice(indices[i % n]);
322  const Eigen::Vector3d& v2 = mesh.getVertice(indices[(i + 2) % n]) -
323  mesh.getVertice(indices[(i + 1) % n]);
324  face_normal += v1.cross(v2);
325  }
326  double l = std::sqrt(face_normal.dot(face_normal));
327  face_normal *= (1.0 / l);
328  }
329 
330  bool found_ear = true;
331  while (n > 3) {
332  if (!found_ear) {
333  // If no ear is found after all indices are looped through, the
334  // polygon is not triangulable.
335  return false;
336  }
337 
338  found_ear = false;
339  for (int i = 1; i < n - 2; i++) {
340  const Eigen::Vector3d& v1 = mesh.getVertice(indices[i]) -
341  mesh.getVertice(indices[i - 1]);
342  const Eigen::Vector3d& v2 = mesh.getVertice(indices[i + 1]) -
343  mesh.getVertice(indices[i]);
344  bool is_convex = (face_normal.dot(v1.cross(v2)) > 0.0);
345  bool is_ear = true;
346  if (is_convex) {
347  // If convex, check if vertex is an ear
348  // (no vertices within triangle v[i-1], v[i], v[i+1])
349  Eigen::MatrixX2d polygon(3, 2);
350  for (int j = 0; j < 3; j++) {
351  polygon(j, 0) = mesh.getVertice(indices[i + j - 1])(0);
352  polygon(j, 1) = mesh.getVertice(indices[i + j - 1])(1);
353  }
354 
355  for (int j = 0; j < n; j++) {
356  if (j == i - 1 || j == i || j == i + 1) {
357  continue;
358  }
359  const Eigen::Vector3d& v = mesh.getVertice(indices[j]);
360  if (IsPointInsidePolygon(polygon, v(0), v(1))) {
361  is_ear = false;
362  break;
363  }
364  }
365 
366  if (is_ear) {
367  found_ear = true;
368  mesh.addTriangle(Eigen::Vector3i(indices[i - 1], indices[i],
369  indices[i + 1]));
370  indices.erase(indices.begin() + i);
371  n = int(indices.size());
372  break;
373  }
374  }
375  }
376  }
377  mesh.addTriangle(Eigen::Vector3i(indices[0], indices[1], indices[2]));
378 
379  return true;
380 }
381 
382 bool ReadTriangleMeshFromPLY(const std::string& filename,
383  ccMesh& mesh,
384  const ReadTriangleMeshOptions& params /*={}*/) {
385  using namespace ply_trianglemesh_reader;
386 
387  p_ply ply_file = ply_open(filename.c_str(), nullptr, 0, nullptr);
388  if (!ply_file) {
389  utility::LogWarning("Read PLY failed: unable to open file: {}",
390  filename);
391  return false;
392  }
393  if (!ply_read_header(ply_file)) {
394  utility::LogWarning("Read PLY failed: unable to parse header.");
395  ply_close(ply_file);
396  return false;
397  }
398 
399  PLYReaderState state;
400  state.mesh_ptr = &mesh;
401  state.vertex_num = ply_set_read_cb(ply_file, "vertex", "x",
402  ReadVertexCallback, &state, 0);
403  ply_set_read_cb(ply_file, "vertex", "y", ReadVertexCallback, &state, 1);
404  ply_set_read_cb(ply_file, "vertex", "z", ReadVertexCallback, &state, 2);
405 
406  state.normal_num = ply_set_read_cb(ply_file, "vertex", "nx",
407  ReadNormalCallback, &state, 0);
408  ply_set_read_cb(ply_file, "vertex", "ny", ReadNormalCallback, &state, 1);
409  ply_set_read_cb(ply_file, "vertex", "nz", ReadNormalCallback, &state, 2);
410 
411  state.color_num = ply_set_read_cb(ply_file, "vertex", "red",
412  ReadColorCallback, &state, 0);
413  ply_set_read_cb(ply_file, "vertex", "green", ReadColorCallback, &state, 1);
414  ply_set_read_cb(ply_file, "vertex", "blue", ReadColorCallback, &state, 2);
415 
416  if (state.vertex_num <= 0) {
417  utility::LogWarning("Read PLY failed: number of vertex <= 0.");
418  ply_close(ply_file);
419  return false;
420  }
421 
422  state.face_num = ply_set_read_cb(ply_file, "face", "vertex_indices",
423  ReadFaceCallBack, &state, 0);
424  if (state.face_num == 0) {
425  state.face_num = ply_set_read_cb(ply_file, "face", "vertex_index",
426  ReadFaceCallBack, &state, 0);
427  }
428 
429  state.vertex_index = 0;
430  state.normal_index = 0;
431  state.color_index = 0;
432  state.face_index = 0;
433 
434  mesh.clear();
435  ccPointCloud* cloud =
437  assert(cloud);
438  cloud->resize(state.vertex_num);
439  if (state.normal_num > 0) {
440  cloud->resizeTheNormsTable();
441  }
442  if (state.color_num > 0) {
443  cloud->resizeTheRGBTable();
444  }
445 
446  utility::CountingProgressReporter reporter(params.update_progress);
447  reporter.SetTotal(state.vertex_num + state.face_num);
448  state.progress_bar = &reporter;
449 
450  if (!ply_read(ply_file)) {
451  utility::LogWarning("Read PLY failed: unable to read file: {}",
452  filename);
453  ply_close(ply_file);
454  return false;
455  }
456 
457  ply_close(ply_file);
458  return true;
459 }
460 
461 bool WriteTriangleMeshToPLY(const std::string& filename,
462  const ccMesh& mesh,
463  bool write_ascii /* = false*/,
464  bool compressed /* = false*/,
465  bool write_vertex_normals /* = true*/,
466  bool write_vertex_colors /* = true*/,
467  bool write_triangle_uvs /* = true*/,
468  bool print_progress) {
469  if (write_triangle_uvs && mesh.hasTriangleUvs()) {
471  "This file format currently does not support writing textures "
472  "and uv coordinates. Consider using .obj");
473  }
474 
475  if (mesh.size() == 0) {
476  utility::LogWarning("Write PLY failed: mesh has 0 vertices.");
477  return false;
478  }
479 
480  p_ply ply_file = ply_create(filename.c_str(),
481  write_ascii ? PLY_ASCII : PLY_LITTLE_ENDIAN,
482  nullptr, 0, nullptr);
483  if (!ply_file) {
484  utility::LogWarning("Write PLY failed: unable to open file: {}",
485  filename);
486  return false;
487  }
488 
489  write_vertex_normals = write_vertex_normals && mesh.HasVertexNormals();
490  write_vertex_colors = write_vertex_colors && mesh.hasColors();
491 
492  ply_add_comment(ply_file, "Created by cloudViewer");
493  ply_add_element(ply_file, "vertex",
494  static_cast<long>(mesh.getVerticeSize()));
495  ply_add_property(ply_file, "x", PLY_DOUBLE, PLY_DOUBLE, PLY_DOUBLE);
496  ply_add_property(ply_file, "y", PLY_DOUBLE, PLY_DOUBLE, PLY_DOUBLE);
497  ply_add_property(ply_file, "z", PLY_DOUBLE, PLY_DOUBLE, PLY_DOUBLE);
498  if (write_vertex_normals) {
499  ply_add_property(ply_file, "nx", PLY_DOUBLE, PLY_DOUBLE, PLY_DOUBLE);
500  ply_add_property(ply_file, "ny", PLY_DOUBLE, PLY_DOUBLE, PLY_DOUBLE);
501  ply_add_property(ply_file, "nz", PLY_DOUBLE, PLY_DOUBLE, PLY_DOUBLE);
502  }
503  if (write_vertex_colors) {
504  ply_add_property(ply_file, "red", PLY_UCHAR, PLY_UCHAR, PLY_UCHAR);
505  ply_add_property(ply_file, "green", PLY_UCHAR, PLY_UCHAR, PLY_UCHAR);
506  ply_add_property(ply_file, "blue", PLY_UCHAR, PLY_UCHAR, PLY_UCHAR);
507  }
508  ply_add_element(ply_file, "face", static_cast<long>(mesh.size()));
509  ply_add_property(ply_file, "vertex_indices", PLY_LIST, PLY_UCHAR, PLY_UINT);
510  if (!ply_write_header(ply_file)) {
511  utility::LogWarning("Write PLY failed: unable to write header.");
512  ply_close(ply_file);
513  return false;
514  }
515 
517  static_cast<size_t>(mesh.getVerticeSize() + mesh.size()),
518  "Writing PLY: ", print_progress);
519  bool printed_color_warning = false;
520  for (size_t i = 0; i < mesh.getVerticeSize(); i++) {
521  const auto& vertex = mesh.getVertice(i);
522  ply_write(ply_file, vertex(0));
523  ply_write(ply_file, vertex(1));
524  ply_write(ply_file, vertex(2));
525  if (write_vertex_normals) {
526  const auto& normal = mesh.getVertexNormal(i);
527  ply_write(ply_file, normal(0));
528  ply_write(ply_file, normal(1));
529  ply_write(ply_file, normal(2));
530  }
531  if (write_vertex_colors) {
532  const auto& color = mesh.getVertexColor(i);
533  if (!printed_color_warning &&
534  (color(0) < 0 || color(0) > 1 || color(1) < 0 || color(1) > 1 ||
535  color(2) < 0 || color(2) > 1)) {
537  "Write Ply clamped color value to valid range");
538  printed_color_warning = true;
539  }
540  auto rgb = utility::ColorToUint8(color);
541  ply_write(ply_file, rgb(0));
542  ply_write(ply_file, rgb(1));
543  ply_write(ply_file, rgb(2));
544  }
545  ++progress_bar;
546  }
547  for (unsigned int i = 0; i < mesh.size(); i++) {
548  Eigen::Vector3i triangle;
549  mesh.getTriangleVertIndexes(i, triangle);
550  ply_write(ply_file, 3);
551  ply_write(ply_file, triangle(0));
552  ply_write(ply_file, triangle(1));
553  ply_write(ply_file, triangle(2));
554  ++progress_bar;
555  }
556 
557  ply_close(ply_file);
558  return true;
559 }
560 
563 }
564 
567 }
568 
569 bool ReadTriangleMeshFromSTL(const std::string& filename,
570  ccMesh& mesh,
571  bool print_progress) {
572  FILE* myFile = utility::filesystem::FOpen(filename.c_str(), "rb");
573 
574  if (!myFile) {
575  utility::LogWarning("Read STL failed: unable to open file.");
576  fclose(myFile);
577  return false;
578  }
579 
580  int num_of_triangles;
581  if (myFile) {
582  char header[80] = "";
583  if (fread(header, sizeof(char), 80, myFile) != 80) {
585  "[TriangleMeshIO::ReadTriangleMeshFromSTL] header IO "
586  "error!");
587  }
588  if (fread(&num_of_triangles, sizeof(unsigned int), 1, myFile) != 1) {
590  "[TriangleMeshIO::ReadTriangleMeshFromSTL] triangles IO "
591  "error!");
592  }
593  } else {
594  utility::LogWarning("Read STL failed: unable to read header.");
595  fclose(myFile);
596  return false;
597  }
598 
599  if (num_of_triangles == 0) {
600  utility::LogWarning("Read STL failed: empty file.");
601  fclose(myFile);
602  return false;
603  }
604 
605  mesh.clear();
607  mesh.reserve(num_of_triangles);
608  ccPointCloud* cloud =
610  assert(cloud && cloud->reserveThePointsTable(num_of_triangles * 3));
611 
612  utility::ConsoleProgressBar progress_bar(num_of_triangles,
613  "Reading STL: ", print_progress);
614  for (int i = 0; i < num_of_triangles; i++) {
615  char buffer[50];
616  float* float_buffer;
617  if (myFile) {
618  if (fread(buffer, sizeof(char), 50, myFile) != 50) {
620  "[TriangleMeshIO::ReadTriangleMeshFromSTL] buffer IO "
621  "error!");
622  }
623  float_buffer = reinterpret_cast<float*>(buffer);
624  mesh.addTriangleNorm(
625  Eigen::Map<Eigen::Vector3f>(float_buffer).cast<double>());
626  for (int j = 0; j < 3; j++) {
627  float_buffer = reinterpret_cast<float*>(buffer + 12 * (j + 1));
628  Eigen::Vector3d temp = Eigen::Map<Eigen::Vector3f>(float_buffer)
629  .cast<double>();
630  *cloud->getPointPtr(static_cast<unsigned int>(i * 3 + j)) =
631  temp;
632  }
633  mesh.addTriangle(Eigen::Vector3i(i * 3 + 0, i * 3 + 1, i * 3 + 2));
634  // ignore buffer[48] and buffer [49] because it is rarely used.
635 
636  } else {
637  utility::LogWarning("Read STL failed: not enough triangles.");
638  fclose(myFile);
639  return false;
640  }
641  ++progress_bar;
642  }
643 
644  // do some cleaning
645  {
646  cloud->shrinkToFit();
647  mesh.shrinkToFit();
649  if (normals) {
650  normals->shrink_to_fit();
651  }
652  }
653 
654  cloud->setEnabled(false);
655  // DGM: no need to lock it as it is only used by one mesh!
656  cloud->setLocked(false);
657 
658  fclose(myFile);
659  return true;
660 }
661 
662 bool WriteTriangleMeshToSTL(const std::string& filename,
663  const ccMesh& mesh,
664  bool write_ascii /* = false*/,
665  bool compressed /* = false*/,
666  bool write_vertex_normals /* = true*/,
667  bool write_vertex_colors /* = true*/,
668  bool write_triangle_uvs /* = true*/,
669  bool print_progress) {
670  if (write_triangle_uvs && mesh.hasTriangleUvs()) {
672  "This file format does not support writing textures and uv "
673  "coordinates. Consider using .obj");
674  }
675 
676  if (write_ascii) {
677  utility::LogError("Writing ascii STL file is not supported yet.");
678  }
679 
680  std::ofstream myFile(filename.c_str(), std::ios::out | std::ios::binary);
681 
682  if (!myFile) {
683  utility::LogWarning("Write STL failed: unable to open file.");
684  return false;
685  }
686 
687  if (!mesh.hasTriNormals()) {
688  utility::LogWarning("Write STL failed: compute normals first.");
689  return false;
690  }
691 
692  size_t num_of_triangles = mesh.size();
693  if (num_of_triangles == 0) {
694  utility::LogWarning("Write STL failed: empty file.");
695  return false;
696  }
697  char header[80] = "Created by cloudViewer";
698  myFile.write(header, 80);
699  myFile.write((char*)(&num_of_triangles), 4);
700 
701  utility::ConsoleProgressBar progress_bar(num_of_triangles,
702  "Writing STL: ", print_progress);
703  for (size_t i = 0; i < num_of_triangles; i++) {
704  Eigen::Vector3f float_vector3f =
705  mesh.getTriangleNorm(static_cast<unsigned int>(i))
706  .cast<float>();
707  myFile.write(reinterpret_cast<const char*>(float_vector3f.data()), 12);
708 
709  std::vector<Eigen::Vector3d> vN3;
710  mesh.getTriangleVertices(static_cast<unsigned int>(i), vN3[0].data(),
711  vN3[1].data(), vN3[2].data());
712  for (int j = 0; j < 3; j++) {
713  Eigen::Vector3f float_vector3f = vN3[j].cast<float>();
714  myFile.write(reinterpret_cast<const char*>(float_vector3f.data()),
715  12);
716  }
717  char blank[2] = {0, 0};
718  myFile.write(blank, 2);
719  ++progress_bar;
720  }
721  return true;
722 }
723 
726 }
727 
728 bool ReadTriangleMeshFromOBJ(const std::string& filename,
729  ccMesh& mesh,
730  const ReadTriangleMeshOptions& /*={}*/) {
731  tinyobj::attrib_t attrib;
732  std::vector<tinyobj::shape_t> shapes;
733  std::vector<tinyobj::material_t> materials;
734  std::string warn;
735  std::string err;
736 
737  std::string mtl_base_path =
739  bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err,
740  filename.c_str(), mtl_base_path.c_str());
741  if (!warn.empty()) {
742  utility::LogWarning("Read OBJ failed: {}", warn);
743  }
744  if (!err.empty()) {
745  utility::LogWarning("Read OBJ failed: {}", err);
746  }
747 
748  if (!ret) {
749  return false;
750  }
751 
752  mesh.clear();
753 
754  ccPointCloud* cloud =
756  assert(cloud);
757 
758  cloud->reserveThePointsTable(
759  static_cast<unsigned int>(attrib.vertices.size()));
760 
761  if (attrib.colors.size() > 0) {
762  cloud->reserveTheRGBTable();
763  cloud->showColors(true);
764  }
765 
766  // copy vertex and data
767  for (size_t vidx = 0; vidx < attrib.vertices.size(); vidx += 3) {
768  tinyobj::real_t vx = attrib.vertices[vidx + 0];
769  tinyobj::real_t vy = attrib.vertices[vidx + 1];
770  tinyobj::real_t vz = attrib.vertices[vidx + 2];
771  cloud->addEigenPoint(Eigen::Vector3d(vx, vy, vz));
772  }
773 
774  for (size_t vidx = 0; vidx < attrib.colors.size(); vidx += 3) {
775  tinyobj::real_t r = attrib.colors[vidx + 0];
776  tinyobj::real_t g = attrib.colors[vidx + 1];
777  tinyobj::real_t b = attrib.colors[vidx + 2];
778  cloud->addEigenColor(Eigen::Vector3d(r, g, b));
779  }
780 
781  // resize normal data and create bool indicator vector
782  if (!attrib.normals.empty()) {
783  cloud->resizeTheNormsTable();
784  }
785  std::vector<bool> normals_indicator(cloud->size(), false);
786 
787  // copy face data and copy normals data
788  // append face-wise uv data
789  for (size_t s = 0; s < shapes.size(); s++) {
790  size_t index_offset = 0;
791  for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
792  int fv = shapes[s].mesh.num_face_vertices[f];
793  if (fv != 3) {
795  "Read OBJ failed: facet with number of vertices not "
796  "equal to 3");
797  return false;
798  }
799 
800  Eigen::Vector3i facet;
801  for (int v = 0; v < fv; v++) {
802  tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
803  unsigned int vidx = idx.vertex_index;
804  facet(v) = vidx;
805 
806  if (!attrib.normals.empty() && !normals_indicator[vidx] &&
807  (3 * idx.normal_index + 2) < int(attrib.normals.size())) {
808  tinyobj::real_t nx =
809  attrib.normals[3 * idx.normal_index + 0];
810  tinyobj::real_t ny =
811  attrib.normals[3 * idx.normal_index + 1];
812  tinyobj::real_t nz =
813  attrib.normals[3 * idx.normal_index + 2];
814 
815  cloud->setPointNormal(vidx, CCVector3(nx, ny, nz));
816  normals_indicator[vidx] = true;
817  }
818 
819  if (!attrib.texcoords.empty() &&
820  2 * idx.texcoord_index + 1 < int(attrib.texcoords.size())) {
821  tinyobj::real_t tx =
822  attrib.texcoords[2 * idx.texcoord_index + 0];
823  tinyobj::real_t ty =
824  attrib.texcoords[2 * idx.texcoord_index + 1];
825  mesh.triangle_uvs_.push_back(Eigen::Vector2d(tx, ty));
826  }
827  }
828 
829  mesh.addTriangle(facet);
830  mesh.triangle_material_ids_.push_back(
831  shapes[s].mesh.material_ids[f]);
832  index_offset += fv;
833  }
834  }
835 
836  // if not all normals have been set, then remove the vertex normals
837  bool all_normals_set =
838  std::accumulate(normals_indicator.begin(), normals_indicator.end(),
839  true, [](bool a, bool b) { return a && b; });
840  if (!all_normals_set) {
841  cloud->unallocateNorms();
842  }
843 
844  // if not all triangles have corresponding uvs, then remove uvs
845  if (3 * mesh.size() != mesh.triangle_uvs_.size()) {
846  mesh.triangle_uvs_.clear();
847  }
848 
849  auto textureLoader = [&mtl_base_path](std::string& relativePath) {
850  auto image = io::CreateImageFromFile(mtl_base_path + relativePath);
851  return image->HasData() ? image : std::shared_ptr<geometry::Image>();
852  };
853 
854  using MaterialParameter = ccMesh::Material::MaterialParameter;
855 
856  mesh.materials_.resize(materials.size());
857  for (std::size_t i = 0; i < materials.size(); ++i) {
858  auto& material = materials[i];
859  mesh.materials_[i].first = material.name;
860  auto& meshMaterial = mesh.materials_[i].second;
861 
862  meshMaterial.baseColor = MaterialParameter::CreateRGB(
863  material.diffuse[0], material.diffuse[1], material.diffuse[2]);
864 
865  if (!material.normal_texname.empty()) {
866  meshMaterial.normalMap = textureLoader(material.normal_texname);
867  } else if (!material.bump_texname.empty()) {
868  // try bump, because there is often a misunderstanding of
869  // what bump map or normal map is
870  meshMaterial.normalMap = textureLoader(material.bump_texname);
871  }
872 
873  if (!material.ambient_texname.empty()) {
874  meshMaterial.ambientOcclusion =
875  textureLoader(material.ambient_texname);
876  }
877 
878  if (!material.diffuse_texname.empty()) {
879  meshMaterial.albedo = textureLoader(material.diffuse_texname);
880 
881  // Legacy texture map support
882  if (meshMaterial.albedo) {
883  mesh.textures_.push_back(*meshMaterial.albedo->FlipVertical());
884  }
885  }
886 
887  if (!material.metallic_texname.empty()) {
888  meshMaterial.metallic = textureLoader(material.metallic_texname);
889  }
890 
891  if (!material.roughness_texname.empty()) {
892  meshMaterial.roughness = textureLoader(material.roughness_texname);
893  }
894 
895  if (!material.sheen_texname.empty()) {
896  meshMaterial.reflectance = textureLoader(material.sheen_texname);
897  }
898 
899  // NOTE: We want defaults of 0.0 and 1.0 for baseMetallic and
900  // baseRoughness respectively but 0.0 is a valid value for both and
901  // tiny_obj_loader defaults to 0.0 for both. So, we will assume that
902  // only if one of the values is greater than 0.0 that there are
903  // non-default values set in the .mtl file
904  if (material.roughness > 0.f || material.metallic > 0.f) {
905  meshMaterial.baseMetallic = material.metallic;
906  meshMaterial.baseRoughness = material.roughness;
907  }
908 
909  if (material.sheen > 0.f) {
910  meshMaterial.baseReflectance = material.sheen;
911  }
912 
913  // NOTE: We will unconditionally copy the following parameters because
914  // the TinyObj defaults match CloudViewer's internal defaults
915  meshMaterial.baseClearCoat = material.clearcoat_thickness;
916  meshMaterial.baseClearCoatRoughness = material.clearcoat_roughness;
917  meshMaterial.baseAnisotropy = material.anisotropy;
918  }
919 
920  return true;
921 }
922 
923 bool WriteTriangleMeshToOBJ(const std::string& filename,
924  const ccMesh& mesh,
925  bool write_ascii /* = false*/,
926  bool compressed /* = false*/,
927  bool write_vertex_normals /* = true*/,
928  bool write_vertex_colors /* = true*/,
929  bool write_triangle_uvs /* = true*/,
930  bool print_progress) {
931  std::string object_name = utility::filesystem::GetFileNameWithoutExtension(
933 
934  std::ofstream file(filename.c_str(), std::ios::out | std::ios::binary);
935  if (!file) {
936  utility::LogWarning("Write OBJ failed: unable to open file.");
937  return false;
938  }
939 
940  if (mesh.hasTriNormals()) {
941  utility::LogWarning("Write OBJ can not include triangle normals.");
942  }
943 
944  file << "# Created by cloudViewer " << std::endl;
945  file << "# object name: " << object_name << std::endl;
946  file << "# number of vertices: " << mesh.getVerticeSize() << std::endl;
947  file << "# number of triangles: " << mesh.size() << std::endl;
948 
949  // always write material filename in obj file, regardless of uvs or textures
950  file << "mtllib " << object_name << ".mtl" << std::endl;
951 
953  mesh.getVerticeSize() + mesh.size(),
954  "Writing OBJ: ", print_progress);
955 
956  write_vertex_normals = write_vertex_normals && mesh.hasNormals();
957  write_vertex_colors = write_vertex_colors && mesh.hasColors();
958  for (size_t vidx = 0; vidx < mesh.getVerticeSize(); ++vidx) {
959  const Eigen::Vector3d& vertex = mesh.getVertice(vidx);
960  file << "v " << vertex(0) << " " << vertex(1) << " " << vertex(2);
961  if (write_vertex_colors) {
962  const Eigen::Vector3d& color = mesh.getVertexColor(vidx);
963  file << " " << color(0) << " " << color(1) << " " << color(2);
964  }
965  file << std::endl;
966 
967  if (write_vertex_normals) {
968  const Eigen::Vector3d& normal = mesh.getVertexNormal(vidx);
969  file << "vn " << normal(0) << " " << normal(1) << " " << normal(2)
970  << std::endl;
971  }
972 
973  ++progress_bar;
974  }
975 
976  // we are less strict and allows writing to uvs without known material
977  // potentially this will be useful for exporting conformal map generation
978  write_triangle_uvs = write_triangle_uvs && mesh.hasTriangleUvs();
979 
980  // we don't compress uvs into vertex-wise representation.
981  // loose triangle-wise representation is provided
982  if (write_triangle_uvs) {
983  for (auto& uv : mesh.triangle_uvs_) {
984  file << "vt " << uv(0) << " " << uv(1) << std::endl;
985  }
986  }
987 
988  // write faces with (possibly multiple) material ids
989  // map faces with material ids
990  std::map<int, std::vector<size_t>> material_id_faces_map;
991  if (mesh.hasTriangleMaterialIds()) {
992  for (size_t i = 0; i < mesh.triangle_material_ids_.size(); ++i) {
993  int mi = mesh.triangle_material_ids_[i];
994  auto it = material_id_faces_map.find(mi);
995  if (it == material_id_faces_map.end()) {
996  material_id_faces_map[mi] = {i};
997  } else {
998  it->second.push_back(i);
999  }
1000  }
1001  } else { // every face falls to the default material
1002  material_id_faces_map[0].resize(mesh.size());
1003  std::iota(material_id_faces_map[0].begin(),
1004  material_id_faces_map[0].end(), 0);
1005  }
1006 
1007  // enumerate ids and their corresponding faces
1008  for (auto it = material_id_faces_map.begin();
1009  it != material_id_faces_map.end(); ++it) {
1010  // write the mtl name
1011  if (write_triangle_uvs) {
1012  std::string mtl_name =
1013  object_name + "_" + std::to_string(it->first);
1014  file << "usemtl " << mtl_name << std::endl;
1015  }
1016 
1017  // write the corresponding faces
1018  for (auto tidx : it->second) {
1019  Eigen::Vector3i triangle;
1020  mesh.getTriangleVertIndexes(tidx, triangle);
1021  if (write_vertex_normals && write_triangle_uvs) {
1022  file << "f ";
1023  file << triangle(0) + 1 << "/" << 3 * tidx + 1 << "/"
1024  << triangle(0) + 1 << " ";
1025  file << triangle(1) + 1 << "/" << 3 * tidx + 2 << "/"
1026  << triangle(1) + 1 << " ";
1027  file << triangle(2) + 1 << "/" << 3 * tidx + 3 << "/"
1028  << triangle(2) + 1 << std::endl;
1029  } else if (!write_vertex_normals && write_triangle_uvs) {
1030  file << "f ";
1031  file << triangle(0) + 1 << "/" << 3 * tidx + 1 << " ";
1032  file << triangle(1) + 1 << "/" << 3 * tidx + 2 << " ";
1033  file << triangle(2) + 1 << "/" << 3 * tidx + 3 << std::endl;
1034  } else if (write_vertex_normals && !write_triangle_uvs) {
1035  file << "f " << triangle(0) + 1 << "//" << triangle(0) + 1
1036  << " " << triangle(1) + 1 << "//" << triangle(1) + 1 << " "
1037  << triangle(2) + 1 << "//" << triangle(2) + 1 << std::endl;
1038  } else {
1039  file << "f " << triangle(0) + 1 << " " << triangle(1) + 1 << " "
1040  << triangle(2) + 1 << std::endl;
1041  }
1042  ++progress_bar;
1043  }
1044  }
1045  // end of writing obj.
1047 
1049  // start to write to mtl and texture
1050  if (write_triangle_uvs) {
1051  std::string parent_dir =
1053  std::string mtl_filename = parent_dir + object_name + ".mtl";
1054 
1055  // write headers
1056  std::ofstream mtl_file(mtl_filename.c_str(), std::ios::out);
1057  if (!mtl_file) {
1059  "Write OBJ successful, but failed to write material file.");
1060  return true;
1061  }
1062  mtl_file << "# Created by cloudViewer " << std::endl;
1063  mtl_file << "# object name: " << object_name << std::endl;
1064 
1065  // write textures (if existing)
1066  for (size_t i = 0; i < mesh.textures_.size(); ++i) {
1067  std::string mtl_name = object_name + "_" + std::to_string(i);
1068  mtl_file << "newmtl " << mtl_name << std::endl;
1069  mtl_file << "Ka 1.000 1.000 1.000" << std::endl;
1070  mtl_file << "Kd 1.000 1.000 1.000" << std::endl;
1071  mtl_file << "Ks 0.000 0.000 0.000" << std::endl;
1072 
1073  std::string tex_filename = parent_dir + mtl_name + ".png";
1074  if (!io::WriteImage(tex_filename,
1075  *mesh.textures_[i].FlipVertical())) {
1077  "Write OBJ successful, but failed to write texture "
1078  "file.");
1079  return true;
1080  }
1081  mtl_file << "map_Kd " << mtl_name << ".png\n";
1082  }
1083 
1084  // write the default material
1085  if (!mesh.hasEigenTextures()) {
1086  std::string mtl_name = object_name + "_0";
1087  mtl_file << "newmtl " << mtl_name << std::endl;
1088  mtl_file << "Ka 1.000 1.000 1.000" << std::endl;
1089  mtl_file << "Kd 1.000 1.000 1.000" << std::endl;
1090  mtl_file << "Ks 0.000 0.000 0.000" << std::endl;
1091  }
1092  }
1093  return true;
1094 }
1095 
1098 }
1099 
1100 bool ReadTriangleMeshFromOFF(const std::string& filename,
1101  ccMesh& mesh,
1103  std::ifstream file(filename.c_str(), std::ios::in);
1104  if (!file) {
1105  utility::LogWarning("Read OFF failed: unable to open file: {}",
1106  filename);
1107  return false;
1108  }
1109 
1110  auto GetNextLine = [](std::ifstream& file) -> std::string {
1111  for (std::string line; std::getline(file, line);) {
1112  line = utility::StripString(line);
1113  if (!line.empty() && line[0] != '#') {
1114  return line;
1115  }
1116  }
1117  return "";
1118  };
1119 
1120  std::string header = GetNextLine(file);
1121  if (header != "OFF" && header != "COFF" && header != "NOFF" &&
1122  header != "CNOFF") {
1124  "Read OFF failed: header keyword '{}' not supported.", header);
1125  return false;
1126  }
1127 
1128  std::string info = GetNextLine(file);
1129  unsigned int num_of_vertices, num_of_faces, num_of_edges;
1130  std::istringstream iss(info);
1131  if (!(iss >> num_of_vertices >> num_of_faces >> num_of_edges)) {
1132  utility::LogWarning("Read OFF failed: could not read file info.");
1133  return false;
1134  }
1135 
1136  if (num_of_vertices == 0 || num_of_faces == 0) {
1137  utility::LogWarning("Read OFF failed: mesh has no vertices or faces.");
1138  return false;
1139  }
1140 
1141  mesh.clear();
1142  ccPointCloud* cloud =
1144  assert(cloud);
1145 
1146  cloud->resize(num_of_vertices);
1147  bool parse_vertex_normals = false;
1148  bool parse_vertex_colors = false;
1149  if (header == "NOFF" || header == "CNOFF") {
1150  parse_vertex_normals = true;
1151  cloud->resizeTheNormsTable();
1152  }
1153  if (header == "COFF" || header == "CNOFF") {
1154  parse_vertex_colors = true;
1155  cloud->resizeTheRGBTable();
1156  }
1157 
1158  utility::CountingProgressReporter reporter(params.update_progress);
1159  reporter.SetTotal(num_of_vertices + num_of_faces);
1160 
1161  float vx, vy, vz;
1162  float nx, ny, nz;
1163  float r, g, b, alpha;
1164  for (unsigned int vidx = 0; vidx < num_of_vertices; vidx++) {
1165  std::string line = GetNextLine(file);
1166  std::istringstream iss(line);
1167  if (!(iss >> vx >> vy >> vz)) {
1169  "Read OFF failed: could not read all vertex values.");
1170  return false;
1171  }
1172  *cloud->getPointPtr(vidx) = Eigen::Vector3d(vx, vy, vz);
1173 
1174  if (parse_vertex_normals) {
1175  if (!(iss >> nx >> ny >> nz)) {
1177  "Read OFF failed: could not read all vertex normal "
1178  "values.");
1179  return false;
1180  }
1181  cloud->setPointNormal(vidx, CCVector3(nx, ny, nz));
1182  }
1183  if (parse_vertex_colors) {
1184  if (!(iss >> r >> g >> b >> alpha)) {
1186  "Read OFF failed: could not read all vertex color "
1187  "values.");
1188  return false;
1189  }
1190  cloud->setPointColor(vidx,
1191  ecvColor::Rgb(static_cast<ColorCompType>(r),
1192  static_cast<ColorCompType>(g),
1193  static_cast<ColorCompType>(b)));
1194  }
1195 
1196  ++reporter;
1197  }
1198 
1199  unsigned int n, vertex_index;
1200  std::vector<unsigned int> indices;
1201  for (size_t tidx = 0; tidx < num_of_faces; tidx++) {
1202  std::string line = GetNextLine(file);
1203  std::istringstream iss(line);
1204  iss >> n;
1205  indices.clear();
1206  for (size_t vidx = 0; vidx < n; vidx++) {
1207  if (!(iss >> vertex_index)) {
1209  "Read OFF failed: could not read all vertex "
1210  "indices.");
1211  return false;
1212  }
1213  indices.push_back(vertex_index);
1214  }
1215  if (!AddTrianglesByEarClipping(mesh, indices)) {
1217  "Read OFF failed: A polygon in the mesh could not be "
1218  "decomposed into triangles. Vertex indices: {}",
1219  indices);
1220  return false;
1221  }
1222  ++reporter;
1223  }
1224 
1225  file.close();
1226  return true;
1227 }
1228 
1229 bool WriteTriangleMeshToOFF(const std::string& filename,
1230  const ccMesh& mesh,
1231  bool write_ascii /* = false*/,
1232  bool compressed /* = false*/,
1233  bool write_vertex_normals /* = true*/,
1234  bool write_vertex_colors /* = true*/,
1235  bool write_triangle_uvs /* = true*/,
1236  bool print_progress) {
1237  if (write_triangle_uvs && mesh.hasTriangleUvs()) {
1239  "This file format does not support writing textures and uv "
1240  "coordinates. Consider using .obj");
1241  }
1242 
1243  std::ofstream file(filename.c_str(), std::ios::out);
1244  if (!file) {
1245  utility::LogWarning("Write OFF failed: unable to open file.");
1246  return false;
1247  }
1248 
1249  if (mesh.hasTriNormals()) {
1250  utility::LogWarning("Write OFF cannot include triangle normals.");
1251  }
1252 
1253  size_t num_of_vertices = mesh.getVerticeSize();
1254  size_t num_of_triangles = mesh.size();
1255  if (num_of_vertices == 0 || num_of_triangles == 0) {
1256  utility::LogWarning("Write OFF failed: empty file.");
1257  return false;
1258  }
1259 
1260  write_vertex_normals = write_vertex_normals && mesh.hasNormals();
1261  write_vertex_colors = write_vertex_colors && mesh.hasColors();
1262  if (write_vertex_colors) {
1263  file << "C";
1264  }
1265  if (write_vertex_normals) {
1266  file << "N";
1267  }
1268  file << "OFF" << std::endl;
1269  file << num_of_vertices << " " << num_of_triangles << " 0" << std::endl;
1270 
1271  utility::ConsoleProgressBar progress_bar(num_of_vertices + num_of_triangles,
1272  "Writing OFF: ", print_progress);
1273  for (size_t vidx = 0; vidx < num_of_vertices; ++vidx) {
1274  const Eigen::Vector3d& vertex = mesh.getVertice(vidx);
1275  file << vertex(0) << " " << vertex(1) << " " << vertex(2);
1276  if (write_vertex_normals) {
1277  const Eigen::Vector3d& normal = mesh.getVertexNormal(vidx);
1278  file << " " << normal(0) << " " << normal(1) << " " << normal(2);
1279  }
1280  if (write_vertex_colors) {
1281  const Eigen::Vector3d& color = mesh.getVertexColor(vidx);
1282  file << " " << std::round(color(0) * 255.0) << " "
1283  << std::round(color(1) * 255.0) << " "
1284  << std::round(color(2) * 255.0) << " 255";
1285  }
1286  file << std::endl;
1287  ++progress_bar;
1288  }
1289 
1290  for (size_t tidx = 0; tidx < num_of_triangles; ++tidx) {
1291  Eigen::Vector3i triangle;
1292  mesh.getTriangleVertIndexes(tidx, triangle);
1293  file << "3 " << triangle(0) << " " << triangle(1) << " " << triangle(2)
1294  << std::endl;
1295  ++progress_bar;
1296  }
1297 
1298  file.close();
1299  return true;
1300 }
1301 
1302 // Adapts an array of bytes to an array of T. Will advance of byte_stride each
1303 // elements.
1304 template <typename T>
1306  // Pointer to the bytes
1307  const unsigned char* data_ptr;
1308  // Number of elements in the array
1309  const size_t elem_count;
1310  // Stride in bytes between two elements
1311  const size_t stride;
1312 
1313  // Construct an array adapter.
1314  // \param ptr Pointer to the start of the data, with offset applied
1315  // \param count Number of elements in the array
1316  // \param byte_stride Stride betweens elements in the array
1317  ArrayAdapter(const unsigned char* ptr, size_t count, size_t byte_stride)
1318  : data_ptr(ptr), elem_count(count), stride(byte_stride) {}
1319 
1320  // Returns a *copy* of a single element. Can't be used to modify it.
1321  T operator[](size_t pos) const {
1322  if (pos >= elem_count)
1323  throw std::out_of_range(
1324  "Tried to access beyond the last element of an array "
1325  "adapter with count " +
1327  " while getting element number " + std::to_string(pos));
1328  return *(reinterpret_cast<const T*>(data_ptr + pos * stride));
1329  }
1330 };
1331 
1332 // Interface of any adapted array that returns integer data
1334  virtual ~IntArrayBase() = default;
1335  virtual unsigned int operator[](size_t) const = 0;
1336  virtual size_t size() const = 0;
1337 };
1338 
1339 // An array that loads integer types, and returns them as int
1340 template <class T>
1341 struct IntArray : public IntArrayBase {
1343 
1345  unsigned int operator[](size_t position) const override {
1346  return static_cast<unsigned int>(adapter[position]);
1347  }
1348 
1349  size_t size() const override { return adapter.elem_count; }
1350 };
1351 
1354 }
1355 
1356 bool ReadTriangleMeshFromGLTF(const std::string& filename,
1357  ccMesh& mesh,
1358  const ReadTriangleMeshOptions& params /*={}*/) {
1359  tinygltf::Model model;
1360  tinygltf::TinyGLTF loader;
1361  std::string warn;
1362  std::string err;
1363 
1364  std::string filename_ext =
1366  bool ret;
1367  if (filename_ext == "glb") {
1368  ret = loader.LoadBinaryFromFile(&model, &err, &warn, filename.c_str());
1369  } else {
1370  ret = loader.LoadASCIIFromFile(&model, &err, &warn, filename.c_str());
1371  }
1372 
1373  if (!warn.empty() || !err.empty()) {
1374  utility::LogWarning("Read GLTF failed: unable to open file {}",
1375  filename);
1376  }
1377  if (!ret) {
1378  return false;
1379  }
1380 
1381  if (model.meshes.size() > 1) {
1383  "The file contains more than one mesh. All meshes will be "
1384  "loaded as a single mesh.");
1385  }
1386 
1387  mesh.clear();
1388  ccPointCloud* baseVertice = new ccPointCloud("vertices");
1389  baseVertice->setEnabled(false);
1390 
1391  ccMesh mesh_temp(baseVertice);
1392  mesh_temp.addChild(baseVertice);
1393 
1394  for (const tinygltf::Node& gltf_node : model.nodes) {
1395  if (gltf_node.mesh != -1) {
1396  mesh_temp.clear();
1397  const tinygltf::Mesh& gltf_mesh = model.meshes[gltf_node.mesh];
1398 
1399  for (const tinygltf::Primitive& primitive : gltf_mesh.primitives) {
1400  for (const auto& attribute : primitive.attributes) {
1401  if (attribute.first == "POSITION") {
1402  tinygltf::Accessor& positions_accessor =
1403  model.accessors[attribute.second];
1404  tinygltf::BufferView& positions_view =
1405  model.bufferViews[positions_accessor
1406  .bufferView];
1407  const tinygltf::Buffer& positions_buffer =
1408  model.buffers[positions_view.buffer];
1409  const float* positions = reinterpret_cast<const float*>(
1410  &positions_buffer
1411  .data[positions_view.byteOffset +
1412  positions_accessor.byteOffset]);
1413 
1414  for (size_t i = 0; i < positions_accessor.count; ++i) {
1415  baseVertice->addEigenPoint(Eigen::Vector3d(
1416  positions[i * 3 + 0], positions[i * 3 + 1],
1417  positions[i * 3 + 2]));
1418  }
1419  }
1420 
1421  if (attribute.first == "NORMAL") {
1422  tinygltf::Accessor& normals_accessor =
1423  model.accessors[attribute.second];
1424  tinygltf::BufferView& normals_view =
1425  model.bufferViews[normals_accessor.bufferView];
1426  const tinygltf::Buffer& normals_buffer =
1427  model.buffers[normals_view.buffer];
1428  const float* normals = reinterpret_cast<const float*>(
1429  &normals_buffer
1430  .data[normals_view.byteOffset +
1431  normals_accessor.byteOffset]);
1432 
1433  for (size_t i = 0; i < normals_accessor.count; ++i) {
1434  baseVertice->addEigenNorm(Eigen::Vector3d(
1435  normals[i * 3 + 0], normals[i * 3 + 1],
1436  normals[i * 3 + 2]));
1437  }
1438  }
1439 
1440  if (attribute.first == "COLOR_0") {
1441  tinygltf::Accessor& colors_accessor =
1442  model.accessors[attribute.second];
1443  tinygltf::BufferView& colors_view =
1444  model.bufferViews[colors_accessor.bufferView];
1445  const tinygltf::Buffer& colors_buffer =
1446  model.buffers[colors_view.buffer];
1447 
1448  size_t byte_stride = colors_view.byteStride;
1449  if (byte_stride == 0) {
1450  // According to glTF 2.0 specs:
1451  // When byteStride==0, it means that accessor
1452  // elements are tightly packed.
1453  byte_stride =
1454  colors_accessor.type *
1455  tinygltf::GetComponentSizeInBytes(
1456  colors_accessor.componentType);
1457  }
1458  switch (colors_accessor.componentType) {
1459  case TINYGLTF_COMPONENT_TYPE_FLOAT: {
1460  for (size_t i = 0; i < colors_accessor.count;
1461  ++i) {
1462  const float* colors =
1463  reinterpret_cast<const float*>(
1464  colors_buffer.data.data() +
1465  colors_view.byteOffset +
1466  i * byte_stride);
1467 
1468  baseVertice->addRGBColor(
1470  Eigen::Vector3d(
1471  colors[0],
1472  colors[1],
1473  colors[2])));
1474  }
1475  break;
1476  }
1477  case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: {
1478  double max_val = (double)
1480  for (size_t i = 0; i < colors_accessor.count;
1481  ++i) {
1482  const uint8_t* colors =
1483  reinterpret_cast<const uint8_t*>(
1484  colors_buffer.data.data() +
1485  colors_view.byteOffset +
1486  i * byte_stride);
1487 
1488  baseVertice->addRGBColor(
1490  Eigen::Vector3d(
1491  colors[0] / max_val,
1492  colors[1] / max_val,
1493  colors[2] /
1494  max_val)));
1495  }
1496  break;
1497  }
1498  case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: {
1499  double max_val = (double)
1501  for (size_t i = 0; i < colors_accessor.count;
1502  ++i) {
1503  const uint16_t* colors =
1504  reinterpret_cast<const uint16_t*>(
1505  colors_buffer.data.data() +
1506  colors_view.byteOffset +
1507  i * byte_stride);
1508  baseVertice->addRGBColor(
1510  Eigen::Vector3d(
1511  colors[0] / max_val,
1512  colors[1] / max_val,
1513  colors[2] /
1514  max_val)));
1515  }
1516  break;
1517  }
1518  default: {
1520  "Unrecognized component type for "
1521  "vertex colors");
1522  break;
1523  }
1524  }
1525  }
1526  }
1527 
1528  // Load triangles
1529  std::unique_ptr<IntArrayBase> indices_array_pointer = nullptr;
1530  {
1531  const tinygltf::Accessor& indices_accessor =
1532  model.accessors[primitive.indices];
1533  const tinygltf::BufferView& indices_view =
1534  model.bufferViews[indices_accessor.bufferView];
1535  const tinygltf::Buffer& indices_buffer =
1536  model.buffers[indices_view.buffer];
1537  const auto data_address = indices_buffer.data.data() +
1538  indices_view.byteOffset +
1539  indices_accessor.byteOffset;
1540  const auto byte_stride =
1541  indices_accessor.ByteStride(indices_view);
1542  const auto count = indices_accessor.count;
1543 
1544  // Allocate the index array in the pointer-to-base
1545  // declared in the parent scope
1546  switch (indices_accessor.componentType) {
1547  case TINYGLTF_COMPONENT_TYPE_BYTE:
1548  indices_array_pointer =
1549  std::unique_ptr<IntArray<char>>(
1550  new IntArray<char>(
1552  data_address, count,
1553  byte_stride)));
1554  break;
1555  case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
1556  indices_array_pointer =
1557  std::unique_ptr<IntArray<unsigned char>>(
1560  data_address, count,
1561  byte_stride)));
1562  break;
1563  case TINYGLTF_COMPONENT_TYPE_SHORT:
1564  indices_array_pointer =
1565  std::unique_ptr<IntArray<short>>(
1566  new IntArray<short>(
1568  data_address, count,
1569  byte_stride)));
1570  break;
1571  case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
1572  indices_array_pointer = std::unique_ptr<
1576  data_address, count,
1577  byte_stride)));
1578  break;
1579  case TINYGLTF_COMPONENT_TYPE_INT:
1580  indices_array_pointer =
1581  std::unique_ptr<IntArray<int>>(
1583  data_address, count,
1584  byte_stride)));
1585  break;
1586  case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
1587  indices_array_pointer =
1588  std::unique_ptr<IntArray<unsigned int>>(
1591  data_address, count,
1592  byte_stride)));
1593  break;
1594  default:
1595  break;
1596  }
1597  const auto& indices = *indices_array_pointer;
1598 
1599  switch (primitive.mode) {
1600  case TINYGLTF_MODE_TRIANGLES:
1601  for (size_t i = 0; i < indices_accessor.count;
1602  i += 3) {
1603  mesh_temp.addTriangle(Eigen::Vector3i(
1604  indices[i], indices[i + 1],
1605  indices[i + 2]));
1606  }
1607  break;
1608  case TINYGLTF_MODE_TRIANGLE_STRIP:
1609  for (size_t i = 2; i < indices_accessor.count;
1610  ++i) {
1611  mesh_temp.addTriangle(Eigen::Vector3i(
1612  indices[i - 2], indices[i - 1],
1613  indices[i]));
1614  }
1615  break;
1616  case TINYGLTF_MODE_TRIANGLE_FAN:
1617  for (size_t i = 2; i < indices_accessor.count;
1618  ++i) {
1619  mesh_temp.addTriangle(Eigen::Vector3i(
1620  indices[0], indices[i - 1],
1621  indices[i]));
1622  }
1623  break;
1624  }
1625  }
1626  }
1627 
1628  if (gltf_node.matrix.size() > 0) {
1629  std::vector<double> matrix = gltf_node.matrix;
1630  Eigen::Matrix4d transform =
1631  Eigen::Map<Eigen::Matrix4d>(&matrix[0], 4, 4);
1632  mesh_temp.Transform(transform);
1633  } else {
1634  // The specification states that first the scale is
1635  // applied to the vertices, then the rotation, and then the
1636  // translation.
1637  if (gltf_node.scale.size() > 0) {
1638  Eigen::Matrix4d transform = Eigen::Matrix4d::Identity();
1639  transform(0, 0) = gltf_node.scale[0];
1640  transform(1, 1) = gltf_node.scale[1];
1641  transform(2, 2) = gltf_node.scale[2];
1642  mesh_temp.Transform(transform);
1643  }
1644  if (gltf_node.rotation.size() > 0) {
1645  Eigen::Matrix4d transform = Eigen::Matrix4d::Identity();
1646  // glTF represents a quaternion as qx, qy, qz, qw, while
1647  // Eigen::Quaterniond orders the parameters as qw, qx,
1648  // qy, qz.
1649  transform.topLeftCorner<3, 3>() =
1650  Eigen::Quaterniond(gltf_node.rotation[3],
1651  gltf_node.rotation[0],
1652  gltf_node.rotation[1],
1653  gltf_node.rotation[2])
1654  .toRotationMatrix();
1655  mesh_temp.Transform(transform);
1656  }
1657  if (gltf_node.translation.size() > 0) {
1658  mesh_temp.Translate(Eigen::Vector3d(
1659  gltf_node.translation[0], gltf_node.translation[1],
1660  gltf_node.translation[2]));
1661  }
1662  }
1663  mesh += mesh_temp;
1664  }
1665  }
1666 
1667  return true;
1668 }
1669 
1670 bool WriteTriangleMeshToGLTF(const std::string& filename,
1671  const ccMesh& mesh,
1672  bool write_ascii /* = false*/,
1673  bool compressed /* = false*/,
1674  bool write_vertex_normals /* = true*/,
1675  bool write_vertex_colors /* = true*/,
1676  bool write_triangle_uvs /* = true*/,
1677  bool print_progress) {
1678  if (write_triangle_uvs && mesh.hasTriangleUvs()) {
1680  "This file format does not support writing textures and uv "
1681  "coordinates. Consider using .obj");
1682  }
1683  tinygltf::Model model;
1684  model.asset.generator = "cloudViewer";
1685  model.asset.version = "2.0";
1686  model.defaultScene = 0;
1687 
1688  size_t byte_length;
1689  size_t num_of_vertices = mesh.getVerticeSize();
1690  size_t num_of_triangles = mesh.size();
1691 
1692  float float_temp;
1693  unsigned char* temp = nullptr;
1694 
1695  tinygltf::BufferView indices_buffer_view_array;
1696  bool save_indices_as_uint32 = num_of_vertices > 65536;
1697  indices_buffer_view_array.name = save_indices_as_uint32
1698  ? "buffer-0-bufferview-uint"
1699  : "buffer-0-bufferview-ushort";
1700  indices_buffer_view_array.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
1701  indices_buffer_view_array.buffer = 0;
1702  indices_buffer_view_array.byteLength = 0;
1703  model.bufferViews.push_back(indices_buffer_view_array);
1704  size_t indices_buffer_view_index = model.bufferViews.size() - 1;
1705 
1706  tinygltf::BufferView buffer_view_array;
1707  buffer_view_array.name = "buffer-0-bufferview-vec3",
1708  buffer_view_array.target = TINYGLTF_TARGET_ARRAY_BUFFER;
1709  buffer_view_array.buffer = 0;
1710  buffer_view_array.byteLength = 0;
1711  buffer_view_array.byteOffset = 0;
1712  buffer_view_array.byteStride = 12;
1713  model.bufferViews.push_back(buffer_view_array);
1714  size_t mesh_attributes_buffer_view_index = model.bufferViews.size() - 1;
1715 
1716  tinygltf::Scene gltf_scene;
1717  gltf_scene.nodes.push_back(0);
1718  model.scenes.push_back(gltf_scene);
1719 
1720  tinygltf::Node gltf_node;
1721  gltf_node.mesh = 0;
1722  model.nodes.push_back(gltf_node);
1723 
1724  tinygltf::Mesh gltf_mesh;
1725  tinygltf::Primitive gltf_primitive;
1726 
1727  tinygltf::Accessor indices_accessor;
1728  indices_accessor.name = "buffer-0-accessor-indices-buffer-0-mesh-0";
1729  indices_accessor.type = TINYGLTF_TYPE_SCALAR;
1730  indices_accessor.componentType =
1731  save_indices_as_uint32 ? TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
1732  : TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT;
1733  indices_accessor.count = 3 * num_of_triangles;
1734  byte_length =
1735  3 * num_of_triangles *
1736  (save_indices_as_uint32 ? sizeof(uint32_t) : sizeof(uint16_t));
1737 
1738  indices_accessor.bufferView = int(indices_buffer_view_index);
1739  indices_accessor.byteOffset =
1740  model.bufferViews[indices_buffer_view_index].byteLength;
1741  model.bufferViews[indices_buffer_view_index].byteLength += byte_length;
1742 
1743  std::vector<unsigned char> index_buffer;
1744  for (size_t tidx = 0; tidx < num_of_triangles; ++tidx) {
1745  Eigen::Vector3i triangle;
1746  mesh.getTriangleVertIndexes(tidx, triangle);
1747  size_t uint_size =
1748  save_indices_as_uint32 ? sizeof(uint32_t) : sizeof(uint16_t);
1749  for (size_t i = 0; i < 3; ++i) {
1750  temp = (unsigned char*)&(triangle(i));
1751  for (size_t j = 0; j < uint_size; ++j) {
1752  index_buffer.push_back(temp[j]);
1753  }
1754  }
1755  }
1756 
1757  indices_accessor.minValues.push_back(0);
1758  indices_accessor.maxValues.push_back(3 * int(num_of_triangles) - 1);
1759  model.accessors.push_back(indices_accessor);
1760  gltf_primitive.indices = int(model.accessors.size()) - 1;
1761 
1762  tinygltf::Accessor positions_accessor;
1763  positions_accessor.name = "buffer-0-accessor-position-buffer-0-mesh-0";
1764  positions_accessor.type = TINYGLTF_TYPE_VEC3;
1765  positions_accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
1766  positions_accessor.count = num_of_vertices;
1767  byte_length = 3 * num_of_vertices * sizeof(float);
1768  positions_accessor.bufferView = int(mesh_attributes_buffer_view_index);
1769  positions_accessor.byteOffset =
1770  model.bufferViews[mesh_attributes_buffer_view_index].byteLength;
1771  model.bufferViews[mesh_attributes_buffer_view_index].byteLength +=
1772  byte_length;
1773 
1774  std::vector<unsigned char> mesh_attribute_buffer;
1775  for (size_t vidx = 0; vidx < num_of_vertices; ++vidx) {
1776  Eigen::Vector3d vertex = mesh.getVertice(vidx);
1777  for (size_t i = 0; i < 3; ++i) {
1778  float_temp = (float)vertex(i);
1779  temp = (unsigned char*)&(float_temp);
1780  for (size_t j = 0; j < sizeof(float); ++j) {
1781  mesh_attribute_buffer.push_back(temp[j]);
1782  }
1783  }
1784  }
1785 
1786  Eigen::Vector3d min_bound = mesh.GetMinBound();
1787  positions_accessor.minValues.push_back(min_bound[0]);
1788  positions_accessor.minValues.push_back(min_bound[1]);
1789  positions_accessor.minValues.push_back(min_bound[2]);
1790  Eigen::Vector3d max_bound = mesh.GetMaxBound();
1791  positions_accessor.maxValues.push_back(max_bound[0]);
1792  positions_accessor.maxValues.push_back(max_bound[1]);
1793  positions_accessor.maxValues.push_back(max_bound[2]);
1794  model.accessors.push_back(positions_accessor);
1795  gltf_primitive.attributes.insert(std::make_pair(
1796  "POSITION", static_cast<int>(model.accessors.size()) - 1));
1797 
1798  write_vertex_normals = write_vertex_normals && mesh.hasNormals();
1799  ccPointCloud* cloud =
1801  if (write_vertex_normals) {
1802  tinygltf::Accessor normals_accessor;
1803  normals_accessor.name = "buffer-0-accessor-normal-buffer-0-mesh-0";
1804  normals_accessor.type = TINYGLTF_TYPE_VEC3;
1805  normals_accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
1806  normals_accessor.count = mesh.getVerticeSize();
1807  size_t byte_length = 3 * mesh.getVerticeSize() * sizeof(float);
1808  normals_accessor.bufferView = int(mesh_attributes_buffer_view_index);
1809  normals_accessor.byteOffset =
1810  model.bufferViews[mesh_attributes_buffer_view_index].byteLength;
1811  model.bufferViews[mesh_attributes_buffer_view_index].byteLength +=
1812  byte_length;
1813 
1814  if (cloud) {
1815  for (size_t vidx = 0; vidx < num_of_vertices; ++vidx) {
1816  const Eigen::Vector3d& normal = cloud->getEigenNormal(vidx);
1817  for (size_t i = 0; i < 3; ++i) {
1818  float_temp = (float)normal(i);
1819  temp = (unsigned char*)&(float_temp);
1820  for (size_t j = 0; j < sizeof(float); ++j) {
1821  mesh_attribute_buffer.push_back(temp[j]);
1822  }
1823  }
1824  }
1825  }
1826 
1827  model.accessors.push_back(normals_accessor);
1828  gltf_primitive.attributes.insert(std::make_pair(
1829  "NORMAL", static_cast<int>(model.accessors.size()) - 1));
1830  }
1831 
1832  write_vertex_colors = write_vertex_colors && mesh.hasColors();
1833  if (write_vertex_colors) {
1834  tinygltf::Accessor colors_accessor;
1835  colors_accessor.name = "buffer-0-accessor-color-buffer-0-mesh-0";
1836  colors_accessor.type = TINYGLTF_TYPE_VEC3;
1837  colors_accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
1838  colors_accessor.count = mesh.getVerticeSize();
1839  size_t byte_length = 3 * mesh.getVerticeSize() * sizeof(float);
1840  colors_accessor.bufferView = int(mesh_attributes_buffer_view_index);
1841  colors_accessor.byteOffset =
1842  model.bufferViews[mesh_attributes_buffer_view_index].byteLength;
1843  model.bufferViews[mesh_attributes_buffer_view_index].byteLength +=
1844  byte_length;
1845 
1846  if (cloud) {
1847  for (size_t vidx = 0; vidx < num_of_vertices; ++vidx) {
1848  const ecvColor::Rgb& col =
1849  cloud->getPointColor(static_cast<unsigned int>(vidx));
1850  const Eigen::Vector3d& color = ecvColor::Rgb::ToEigen(col);
1851  for (size_t i = 0; i < 3; ++i) {
1852  float_temp = (float)color(i);
1853  temp = (unsigned char*)&(float_temp);
1854  for (size_t j = 0; j < sizeof(float); ++j) {
1855  mesh_attribute_buffer.push_back(temp[j]);
1856  }
1857  }
1858  }
1859  }
1860 
1861  model.accessors.push_back(colors_accessor);
1862  gltf_primitive.attributes.insert(std::make_pair(
1863  "COLOR_0", static_cast<int>(model.accessors.size()) - 1));
1864  }
1865 
1866  gltf_primitive.mode = TINYGLTF_MODE_TRIANGLES;
1867  gltf_mesh.primitives.push_back(gltf_primitive);
1868  model.meshes.push_back(gltf_mesh);
1869 
1870  model.bufferViews[0].byteOffset = 0;
1871  model.bufferViews[1].byteOffset = index_buffer.size();
1872 
1873  tinygltf::Buffer buffer;
1874  buffer.uri = filename.substr(0, filename.find_last_of(".")) + ".bin";
1875  buffer.data.resize(index_buffer.size() + mesh_attribute_buffer.size());
1876  memcpy(buffer.data.data(), index_buffer.data(), index_buffer.size());
1877  memcpy(buffer.data.data() + index_buffer.size(),
1878  mesh_attribute_buffer.data(), mesh_attribute_buffer.size());
1879  model.buffers.push_back(buffer);
1880 
1881  tinygltf::TinyGLTF loader;
1882  std::string filename_ext =
1884  if (filename_ext == "glb") {
1885  if (!loader.WriteGltfSceneToFile(&model, filename, false, true, true,
1886  true)) {
1887  utility::LogWarning("Write GLTF failed.");
1888  return false;
1889  }
1890  } else {
1891  if (!loader.WriteGltfSceneToFile(&model, filename, false, true, true,
1892  false)) {
1893  utility::LogWarning("Write GLTF failed.");
1894  return false;
1895  }
1896  }
1897 
1898  return true;
1899 }
1900 
1901 } // namespace io
1902 } // namespace cloudViewer
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
IsAscii write_ascii
Compressed compressed
std::string filename
std::shared_ptr< core::Tensor > image
double normal[3]
int count
math::float4 color
math::float3 position
#define PLY_LIST
Definition: Ply.h:82
#define PLY_UINT
Definition: Ply.h:67
#define PLY_ASCII
Definition: Ply.h:51
#define PLY_DOUBLE
Definition: Ply.h:69
#define PLY_UCHAR
Definition: Ply.h:65
math::float2 uv
cmdLineReadable * params[]
boost::geometry::model::polygon< point_xy > polygon
Definition: TreeIso.cpp:37
Array of compressed 3D normals (single index)
Type u[3]
Definition: CVGeom.h:139
virtual void showColors(bool state)
Sets colors visibility.
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
Triangular mesh.
Definition: ecvMesh.h:35
NormsIndexesTableType * getTriNormsTable() const override
Returns per-triangle normals shared array.
Definition: ecvMesh.h:344
cloudViewer::VerticesIndexes * getTriangleVertIndexes(unsigned triangleIndex) override
Returns the indexes of the vertices of a given triangle.
std::vector< int > triangle_material_ids_
List of material ids.
Definition: ecvMesh.h:707
std::vector< Eigen::Vector2d > triangle_uvs_
List of uv coordinates per triangle.
Definition: ecvMesh.h:625
Eigen::Vector3d getTriangleNorm(size_t index) const
bool HasVertexNormals() const
bool hasColors() const override
Returns whether colors are enabled or not.
bool reserve(std::size_t n)
Reserves the memory to store the vertex indexes (3 per triangle)
ccGenericPointCloud * getAssociatedCloud() const override
Returns the vertices cloud.
Definition: ecvMesh.h:143
bool hasEigenTextures() const
Returns true if the mesh has texture.
Definition: ecvMesh.h:726
Eigen::Vector3d getVertice(size_t index) const
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
bool reservePerTriangleNormalIndexes()
Reserves memory to store per-triangle triplets of normal indexes.
bool HasVertices() const
Definition: ecvMesh.h:208
unsigned int getVerticeSize() const
virtual void getTriangleVertices(unsigned triangleIndex, CCVector3 &A, CCVector3 &B, CCVector3 &C) const override
Returns the vertices of a given triangle.
Eigen::Vector3d getVertexColor(size_t index) const
bool hasNormals() const override
Returns whether normals are enabled or not.
void clear()
virtual unsigned size() const override
Returns the number of triangles.
virtual Eigen::Vector3d GetMinBound() const override
Returns min bounds for geometry coordinates.
bool hasTriangleMaterialIds() const
Definition: ecvMesh.h:721
std::vector< cloudViewer::geometry::Image > textures_
Textures of the image.
Definition: ecvMesh.h:709
bool addTriangleNorm(const CCVector3 &N)
Eigen::Vector3d getVertexNormal(size_t index) const
void shrinkToFit()
Removes unused capacity.
Definition: ecvMesh.h:302
std::vector< std::pair< std::string, Material > > materials_
Definition: ecvMesh.h:704
virtual ccMesh & Transform(const Eigen::Matrix4d &transformation) override
Apply transformation (4x4 matrix) to the geometry coordinates.
bool hasTriangleUvs() const
Definition: ecvMesh.h:717
virtual ccMesh & Translate(const Eigen::Vector3d &translation, bool relative=true) override
Apply translation to the geometry coordinates.
virtual Eigen::Vector3d GetMaxBound() const override
Returns max bounds for geometry coordinates.
bool hasTriNormals() const override
Returns whether the mesh has per-triangle normals.
virtual void setLocked(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:117
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void addEigenNorm(const Eigen::Vector3d &N)
bool resizeTheNormsTable()
Resizes the compressed normals array.
void addEigenColor(const Eigen::Vector3d &color)
Eigen::Vector3d getEigenNormal(size_t index) const
bool resizeTheRGBTable(bool fillWithWhite=false)
Resizes the RGB colors array.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
void unallocateNorms()
Erases the cloud normals.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
void setPointNormal(size_t pointIndex, const CCVector3 &N)
Sets a particular point normal (shortcut)
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color corresponding to a given point.
void shrinkToFit()
Removes unused capacity.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
bool reserveThePointsTable(unsigned _numberOfPoints)
Reserves memory to store the points coordinates.
virtual bool hasTriangles() const
Definition: GenericMesh.h:60
void addEigenPoint(const Eigen::Vector3d &point)
CCVector3 * getPointPtr(size_t index)
unsigned size() const override
Definition: PointCloudTpl.h:38
RGB color structure.
Definition: ecvColorTypes.h:49
constexpr static RgbTpl FromEigen(const Eigen::Vector3d &t)
Definition: ecvColorTypes.h:86
static Eigen::Vector3d ToEigen(const Type col[3])
Definition: ecvColorTypes.h:72
long vertex_num
long face_index
ccMesh * mesh_ptr
double colors[3]
long normal_index
std::vector< unsigned int > face
utility::CountingProgressReporter * progress_bar
double normals[3]
long vertex_index
long normal_num
long face_num
long color_index
long color_num
#define LogWarning(...)
Definition: Logging.h:72
#define LogInfo(...)
Definition: Logging.h:81
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
__host__ __device__ float3 cross(float3 a, float3 b)
Definition: cutil_math.h:1295
__host__ __device__ float length(float2 v)
Definition: cutil_math.h:1162
int max(int a, int b)
Definition: cutil_math.h:48
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
Helper functions for the ml ops.
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
bool ReadTriangleMeshFromOBJ(const std::string &filename, ccMesh &mesh, const ReadTriangleMeshOptions &)
bool AutoWriteMesh(const std::string &filename, const ccMesh &mesh, bool write_ascii=false, bool compressed=false, bool write_vertex_normals=true, bool write_vertex_colors=true, bool write_triangle_uvs=true, bool print_progress=false)
bool ReadTriangleMeshFromOFF(const std::string &filename, ccMesh &mesh, const ReadTriangleMeshOptions &params)
bool WriteTriangleMesh(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
bool WriteImage(const std::string &filename, const geometry::Image &image, int quality=kCloudViewerImageIODefaultQuality)
bool ReadTriangleMeshFromGLTF(const std::string &filename, ccMesh &mesh, const ReadTriangleMeshOptions &params)
std::shared_ptr< ccMesh > CreateMeshFromFile(const std::string &filename, bool print_progress)
bool AddTrianglesByEarClipping(ccMesh &mesh, std::vector< unsigned int > &indices)
std::shared_ptr< geometry::Image > CreateImageFromFile(const std::string &filename)
bool ReadTriangleMeshUsingASSIMP(const std::string &filename, ccMesh &mesh, const ReadTriangleMeshOptions &params)
Definition: FileASSIMP.cpp:163
bool WriteTriangleMeshToSTL(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
FileGeometry ReadFileGeometryTypeOFF(const std::string &path)
FileGeometry ReadFileGeometryTypeOBJ(const std::string &path)
bool WriteTriangleMeshToPLY(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
bool AutoReadMesh(const std::string &filename, ccMesh &mesh, const ReadTriangleMeshOptions &params={})
bool ReadTriangleMeshFromPLY(const std::string &filename, ccMesh &mesh, const ReadTriangleMeshOptions &params)
FileGeometry ReadFileGeometryTypeGLTF(const std::string &path)
bool ReadTriangleMeshFromSTL(const std::string &filename, ccMesh &mesh, bool print_progress)
FileGeometry ReadFileGeometryTypeSTL(const std::string &path)
bool WriteTriangleMeshToGLTF(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
bool WriteTriangleMeshToOBJ(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
FileGeometry ReadFileGeometryTypeFBX(const std::string &path)
bool IsPointInsidePolygon(const Eigen::MatrixX2d &polygon, double x, double y)
bool ReadTriangleMesh(const std::string &filename, ccMesh &mesh, ReadTriangleMeshOptions params)
bool WriteTriangleMeshToOFF(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
static const std::string path
Definition: PointCloud.cpp:59
static const std::unordered_map< std::string, std::function< bool(const std::string &, geometry::TriangleMesh &, const cloudViewer::io::ReadTriangleMeshOptions &)> > file_extension_to_trianglemesh_read_function
static const std::unordered_map< std::string, std::function< bool(const std::string &, const geometry::TriangleMesh &, const bool, const bool, const bool, const bool, const bool, const bool)> > file_extension_to_trianglemesh_write_function
std::string GetFileParentDirectory(const std::string &filename)
Definition: FileSystem.cpp:314
std::string GetFileNameWithoutExtension(const std::string &filename)
Definition: FileSystem.cpp:295
std::string GetFileExtensionInLowerCase(const std::string &filename)
Definition: FileSystem.cpp:281
FILE * FOpen(const std::string &filename, const std::string &mode)
Definition: FileSystem.cpp:609
std::string GetFileNameWithoutDirectory(const std::string &filename)
Definition: FileSystem.cpp:301
std::string & StripString(std::string &str, const std::string &chars="\t\n\v\f\r ")
Definition: Helper.cpp:238
Eigen::Vector3uint8 ColorToUint8(const Eigen::Vector3d &color)
Definition: Eigen.cpp:269
std::string ToUpper(const std::string &s)
Convert string to the upper case.
Definition: Helper.cpp:249
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
std::string to_string(const T &n)
Definition: Common.h:20
ArrayAdapter(const unsigned char *ptr, size_t count, size_t byte_stride)
const unsigned char * data_ptr
virtual size_t size() const =0
virtual ~IntArrayBase()=default
virtual unsigned int operator[](size_t) const =0
IntArray(const ArrayAdapter< T > &a)
unsigned int operator[](size_t position) const override
size_t size() const override