ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
FileASSIMP.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 <Logging.h>
9 #include <assimp/GltfMaterial.h>
10 #include <assimp/material.h>
11 #include <assimp/postprocess.h>
12 #include <assimp/scene.h>
13 
14 #include <assimp/Exporter.hpp>
15 #include <assimp/Importer.hpp>
16 #include <assimp/ProgressHandler.hpp>
17 #include <unordered_map>
18 #include <vector>
19 
24 
25 #define AI_MATKEY_CLEARCOAT_THICKNESS "$mat.clearcoatthickness", 0, 0
26 #define AI_MATKEY_CLEARCOAT_ROUGHNESS "$mat.clearcoatroughness", 0, 0
27 #define AI_MATKEY_SHEEN "$mat.sheen", 0, 0
28 #define AI_MATKEY_ANISOTROPY "$mat.anisotropy", 0, 0
29 
30 namespace cloudViewer {
31 namespace t {
32 namespace io {
33 
34 // Split all polygons with more than 3 edges into triangles.
35 // Ref:
36 // https://github.com/assimp/assimp/blob/master/include/assimp/postprocess.h
37 const unsigned int kPostProcessFlags_compulsory =
38  aiProcess_JoinIdenticalVertices | aiProcess_Triangulate |
39  aiProcess_SortByPType | aiProcess_PreTransformVertices;
40 
41 const unsigned int kPostProcessFlags_fast =
42  kPostProcessFlags_compulsory | aiProcess_GenNormals |
43  aiProcess_GenUVCoords | aiProcess_RemoveRedundantMaterials |
44  aiProcess_OptimizeMeshes;
45 
47  const std::string& filename,
50  Assimp::Importer importer;
51 
52  unsigned int post_process_flags = kPostProcessFlags_compulsory;
53 
54  if (params.enable_post_processing) {
55  post_process_flags = kPostProcessFlags_fast;
56  }
57 
58  const auto* scene = importer.ReadFile(filename.c_str(), post_process_flags);
59  if (!scene) {
60  utility::LogWarning("Unable to load file {} with ASSIMP: {}", filename,
61  importer.GetErrorString());
62  return false;
63  }
64 
65  std::vector<core::Tensor> mesh_vertices;
66  std::vector<core::Tensor> mesh_vertex_normals;
67  std::vector<core::Tensor> mesh_faces;
68  std::vector<core::Tensor> mesh_vertex_colors;
69  std::vector<core::Tensor> mesh_uvs;
70 
71  size_t current_vidx = 0;
72  size_t count_mesh_with_normals = 0;
73  size_t count_mesh_with_colors = 0;
74  size_t count_mesh_with_uvs = 0;
75 
76  // Merge individual meshes in aiScene into a single TriangleMesh
77  for (size_t midx = 0; midx < scene->mNumMeshes; ++midx) {
78  const auto* assimp_mesh = scene->mMeshes[midx];
79 
81  {assimp_mesh->mNumVertices, 3}, core::Dtype::Float32);
82  auto vertices_ptr = vertices.GetDataPtr<float>();
83  std::memcpy(vertices_ptr, assimp_mesh->mVertices,
84  3 * assimp_mesh->mNumVertices * sizeof(float));
85  mesh_vertices.push_back(vertices);
86 
87  core::Tensor vertex_normals;
88  core::Tensor vertex_colors;
89  core::Tensor triangle_uvs;
90  if (assimp_mesh->mNormals) {
91  // Loop fusion for performance optimization.
92  vertex_normals = core::Tensor::Empty({assimp_mesh->mNumVertices, 3},
94  auto vertex_normals_ptr = vertex_normals.GetDataPtr<float>();
95  std::memcpy(vertex_normals_ptr, assimp_mesh->mNormals,
96  3 * assimp_mesh->mNumVertices * sizeof(float));
97  mesh_vertex_normals.push_back(vertex_normals);
98  count_mesh_with_normals++;
99  }
100 
101  if (assimp_mesh->HasVertexColors(0)) {
102  vertex_colors = core::Tensor::Empty({assimp_mesh->mNumVertices, 3},
104  auto vertex_colors_ptr = vertex_colors.GetDataPtr<float>();
105  for (unsigned int i = 0; i < assimp_mesh->mNumVertices; ++i) {
106  *vertex_colors_ptr++ = assimp_mesh->mColors[0][i].r;
107  *vertex_colors_ptr++ = assimp_mesh->mColors[0][i].g;
108  *vertex_colors_ptr++ = assimp_mesh->mColors[0][i].b;
109  }
110  mesh_vertex_colors.push_back(vertex_colors);
111  count_mesh_with_colors++;
112  }
113 
114  core::Tensor faces = core::Tensor::Empty({assimp_mesh->mNumFaces, 3},
116  auto faces_ptr = faces.GetDataPtr<int64_t>();
118  core::Device("CPU:0"), assimp_mesh->mNumFaces,
119  [&](size_t fidx) {
120  const auto& face = assimp_mesh->mFaces[fidx];
121  faces_ptr[3 * fidx] = face.mIndices[0] + current_vidx;
122  faces_ptr[3 * fidx + 1] = face.mIndices[1] + current_vidx;
123  faces_ptr[3 * fidx + 2] = face.mIndices[2] + current_vidx;
124  });
125 
126  mesh_faces.push_back(faces);
127 
128  if (assimp_mesh->HasTextureCoords(0)) {
129  auto vertex_uvs = core::Tensor::Empty(
130  {assimp_mesh->mNumVertices, 2}, core::Dtype::Float32);
131  auto uvs_ptr = vertex_uvs.GetDataPtr<float>();
132  // NOTE: Can't just memcpy because ASSIMP UVs are 3 element and
133  // TriangleMesh wants 2 element UVs.
134  for (int i = 0; i < (int)assimp_mesh->mNumVertices; ++i) {
135  *uvs_ptr++ = assimp_mesh->mTextureCoords[0][i].x;
136  *uvs_ptr++ = assimp_mesh->mTextureCoords[0][i].y;
137  }
138  triangle_uvs = vertex_uvs.IndexGet({faces});
139  mesh_uvs.push_back(triangle_uvs);
140  count_mesh_with_uvs++;
141  }
142  // Adjust face indices to index into combined mesh vertex array
143  current_vidx += static_cast<int>(assimp_mesh->mNumVertices);
144  }
145 
146  mesh.Clear();
147  if (scene->mNumMeshes > 1) {
148  mesh.SetVertexPositions(core::Concatenate(mesh_vertices));
149  mesh.SetTriangleIndices(core::Concatenate(mesh_faces));
150  // NOTE: For objects with multiple meshes we only store normals, colors,
151  // and uvs if every mesh in the object had them. Mesh class does not
152  // support some vertices having normals/colors/uvs and some not having
153  // them.
154  if (count_mesh_with_normals == scene->mNumMeshes) {
155  mesh.SetVertexNormals(core::Concatenate(mesh_vertex_normals));
156  }
157  if (count_mesh_with_colors == scene->mNumMeshes) {
158  mesh.SetVertexColors(core::Concatenate(mesh_vertex_colors));
159  }
160  if (count_mesh_with_uvs == scene->mNumMeshes) {
161  mesh.SetTriangleAttr("texture_uvs", core::Concatenate(mesh_uvs));
162  }
163  } else {
164  mesh.SetVertexPositions(mesh_vertices[0]);
165  mesh.SetTriangleIndices(mesh_faces[0]);
166  if (count_mesh_with_normals > 0) {
167  mesh.SetVertexNormals(mesh_vertex_normals[0]);
168  }
169  if (count_mesh_with_colors > 0) {
170  mesh.SetVertexColors(mesh_vertex_colors[0]);
171  }
172  if (count_mesh_with_uvs > 0) {
173  mesh.SetTriangleAttr("texture_uvs", mesh_uvs[0]);
174  }
175  }
176 
177  return true;
178 }
179 
180 static void SetTextureMaterialProperty(aiMaterial* mat,
181  aiScene* scene,
182  int texture_idx,
183  aiTextureType tt,
184  t::geometry::Image& img) {
185  // Encode image as PNG
186  std::vector<uint8_t> img_buffer;
187  WriteImageToPNGInMemory(img_buffer, img, 6);
188 
189  // Fill in Assimp's texture class and add to its material
190  auto tex = scene->mTextures[texture_idx];
191  std::string tex_id("*");
192  tex_id += std::to_string(texture_idx);
193  tex->mFilename = tex_id.c_str();
194  tex->mHeight = 0;
195  tex->mWidth = img_buffer.size();
196  // NOTE: Assimp takes ownership of the data so we need to copy it
197  // into a separate buffer that Assimp can take care of delete []-ing
198  uint8_t* img_data = new uint8_t[img_buffer.size()];
199  memcpy(img_data, img_buffer.data(), img_buffer.size());
200  tex->pcData = reinterpret_cast<aiTexel*>(img_data);
201  strcpy(tex->achFormatHint, "png");
202  aiString uri(tex_id);
203  const int uv_index = 0;
204  const aiTextureMapMode mode = aiTextureMapMode_Wrap;
205  mat->AddProperty(&uri, AI_MATKEY_TEXTURE(tt, 0));
206  mat->AddProperty(&uv_index, 1, AI_MATKEY_UVWSRC(tt, 0));
207  mat->AddProperty(&mode, 1, AI_MATKEY_MAPPINGMODE_U(tt, 0));
208  mat->AddProperty(&mode, 1, AI_MATKEY_MAPPINGMODE_V(tt, 0));
209 }
210 
211 namespace {
212 // Add hash function for tuple key
213 struct TupleHash {
214  size_t operator()(const std::tuple<int64_t, float, float>& t) const {
215  auto h1 = std::hash<int64_t>{}(std::get<0>(t));
216  auto h2 = std::hash<float>{}(std::get<1>(t));
217  auto h3 = std::hash<float>{}(std::get<2>(t));
218  return h1 ^ (h2 << 1) ^ (h3 << 2);
219  }
220 };
221 
231 geometry::TriangleMesh MakeVertexUVsUnique(const geometry::TriangleMesh& mesh,
232  bool update_triangle_uvs) {
233  if (!mesh.HasTriangleAttr("texture_uvs")) {
234  return mesh;
235  }
236  auto vertices = mesh.GetVertexPositions().Contiguous();
237  auto indices = mesh.GetTriangleIndices().Contiguous();
238  auto triangle_uvs =
239  mesh.GetTriangleAttr("texture_uvs").To(core::Float32).Contiguous();
240 
241  bool has_normals = mesh.HasVertexNormals();
242  bool has_colors = mesh.HasVertexColors();
243  core::Tensor normals, colors;
244  if (has_normals) {
245  normals = mesh.GetVertexNormals().Contiguous();
246  }
247  if (has_colors) {
248  colors = mesh.GetVertexColors().Contiguous();
249  }
250  geometry::TriangleMesh new_mesh;
251  core::Tensor new_vertices, new_faces, new_normals, new_colors, vertex_uvs;
252  bool need_updates = true;
253 
254  DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(indices.GetDtype(), int, [&]() {
255  scalar_int_t next_vertex_idx = 0;
256  // Map to track unique (vertex_idx, uv) combinations
257  std::unordered_map<std::tuple<scalar_int_t, float, float>, scalar_int_t,
258  TupleHash>
259  vertex_uv_to_new_idx;
260  // First pass: collect all unique vertex-UV combinations
261  auto p_indices = indices.GetDataPtr<scalar_int_t>();
262  auto p_uvs = triangle_uvs.GetDataPtr<float>();
263  for (int64_t i = 0; i < indices.GetShape(0); i++) {
264  for (int j = 0; j < 3; j++) {
265  auto orig_vertex_idx = *p_indices++;
266  float u = *p_uvs++;
267  float v = *p_uvs++;
268  auto key = std::make_tuple(orig_vertex_idx, u, v);
269  if (vertex_uv_to_new_idx.find(key) ==
270  vertex_uv_to_new_idx.end()) {
271  vertex_uv_to_new_idx[key] = next_vertex_idx++;
272  }
273  }
274  }
275  // Create new tensors with the correct size
276  int64_t num_new_vertices = next_vertex_idx;
277  if (num_new_vertices == vertices.GetShape(0)) {
278  need_updates = false;
279  return; // No duplicate UVs found return the original mesh.
280  }
281  new_vertices =
282  core::Tensor::Empty({num_new_vertices, 3}, vertices.GetDtype());
283 
284  if (has_normals) {
285  new_normals = core::Tensor::Empty({num_new_vertices, 3},
286  normals.GetDtype());
287  }
288  if (has_colors) {
289  int color_dims = colors.GetShape(1);
290  new_colors = core::Tensor::Empty({num_new_vertices, color_dims},
291  colors.GetDtype());
292  }
293  vertex_uvs = core::Tensor::Empty({num_new_vertices, 2}, core::Float32);
294 
295  // Fill the new vertex data
296  for (const auto& entry : vertex_uv_to_new_idx) {
297  auto [orig_vertex_idx, u, v] = entry.first;
298  auto new_vertex_idx = entry.second;
299  // Copy vertex position
300  new_vertices[new_vertex_idx] = vertices[orig_vertex_idx];
301  if (has_normals) { // Copy vertex normal if available
302  new_normals[new_vertex_idx] = normals[orig_vertex_idx];
303  }
304  if (has_colors) { // Copy vertex color if available
305  new_colors[new_vertex_idx] = colors[orig_vertex_idx];
306  }
307  // Store UV coordinates
308  vertex_uvs[new_vertex_idx][0] = u;
309  vertex_uvs[new_vertex_idx][1] = v;
310  }
311 
312  // Second pass: build face indices with new vertex indices
313  new_faces = core::Tensor::Empty({indices.GetShape(0), 3},
314  indices.GetDtype());
315  auto faces_ptr = indices.GetDataPtr<scalar_int_t>();
316  auto new_faces_ptr = new_faces.GetDataPtr<scalar_int_t>(); // {F, 3}
317  auto triangle_uvs_ptr = triangle_uvs.GetDataPtr<float>(); // {F, 3, 2}
318  for (int64_t i = 0; i < indices.GetShape(0); i++) {
319  for (int j = 0; j < 3; j++) {
320  auto idx = *faces_ptr++;
321  auto u = *triangle_uvs_ptr++;
322  auto v = *triangle_uvs_ptr++;
323  *new_faces_ptr++ = vertex_uv_to_new_idx[{idx, u, v}];
324  }
325  }
326  });
327  if (!need_updates) {
328  return mesh;
329  }
330 
331  new_mesh.SetVertexPositions(new_vertices);
332  new_mesh.SetTriangleIndices(new_faces);
333  if (has_normals) {
334  new_mesh.SetVertexNormals(new_normals);
335  }
336  if (has_colors) {
337  new_mesh.SetVertexColors(new_colors);
338  }
339  new_mesh.SetVertexAttr("texture_uvs", vertex_uvs);
340 
341  // Convert vertex UVs back to triangle UVs for the new mesh. Not used by
342  // ASSIMP.
343  if (update_triangle_uvs) {
344  auto new_triangle_uvs =
345  core::Tensor::Empty({indices.GetShape(0), 3, 2}, core::Float32);
346  DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(indices.GetDtype(), int, [&]() {
347  scalar_int_t vertex_idx;
348  auto new_faces_ptr = new_faces.GetDataPtr<scalar_int_t>();
349  auto new_triangle_uvs_ptr = new_triangle_uvs.GetDataPtr<float>();
350  auto vertex_uvs_ptr = vertex_uvs.GetDataPtr<float>();
351  for (int64_t i = 0; i < indices.GetShape(0); i++) {
352  for (int j = 0; j < 3; j++) {
353  vertex_idx = *new_faces_ptr++;
354  *new_triangle_uvs_ptr++ = vertex_uvs_ptr[2 * vertex_idx];
355  *new_triangle_uvs_ptr++ =
356  vertex_uvs_ptr[2 * vertex_idx + 1];
357  }
358  }
359  });
360  new_mesh.SetTriangleAttr("texture_uvs", new_triangle_uvs);
361  }
362  // Copy material if present
363  if (mesh.HasMaterial()) {
364  new_mesh.SetMaterial(mesh.GetMaterial());
365  }
366 
367  return new_mesh;
368 }
369 } // namespace
370 
371 bool WriteTriangleMeshUsingASSIMP(const std::string& filename,
372  const geometry::TriangleMesh& mesh,
373  const bool write_ascii,
374  const bool compressed,
375  const bool write_vertex_normals,
376  const bool write_vertex_colors,
377  const bool write_triangle_uvs,
378  const bool print_progress) {
379  // Sanity checks...
380  if (write_ascii) {
382  "TriangleMesh can't be saved in ASCII format as .glb");
383  return false;
384  }
385  if (compressed) {
387  "TriangleMesh can't be saved in compressed format as .glb");
388  return false;
389  }
390  if (!mesh.HasVertexPositions()) {
392  "TriangleMesh has no vertex positions and can't be saved as "
393  ".glb");
394  return false;
395  }
396  // Check for unsupported features
397  if (mesh.HasTriangleNormals()) {
399  "Exporting triangle normals is not supported. Please convert "
400  "to vertex normals or export to a format that supports it.");
401  }
402  if (mesh.HasTriangleColors()) {
404  "Exporting triangle colors is not supported. Please convert to "
405  "vertex colors or export to a format that supports it.");
406  }
407 
408  geometry::TriangleMesh w_mesh = mesh; // writeable mesh copy
409  if (write_triangle_uvs && mesh.HasTriangleAttr("texture_uvs")) {
410  if (!write_vertex_normals && w_mesh.HasVertexNormals()) {
411  w_mesh.RemoveVertexAttr("normals");
412  }
413  if (!write_vertex_colors && w_mesh.HasVertexColors()) {
414  w_mesh.RemoveVertexAttr("colors");
415  }
416  w_mesh = MakeVertexUVsUnique(w_mesh, /*update_triangle_uvs=*/false);
417  }
418 
419  Assimp::Exporter exporter;
420  auto ai_scene = std::unique_ptr<aiScene>(new aiScene);
421 
422  // Fill mesh data...
423  ai_scene->mNumMeshes = 1;
424  ai_scene->mMeshes = new aiMesh*[1];
425  auto ai_mesh = new aiMesh;
426  ai_mesh->mName.Set("Object1");
427  ai_mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
428  // Guaranteed to have both vertex positions and triangle indices
429  auto vertices = w_mesh.GetVertexPositions().Contiguous();
430  auto indices =
432  ai_mesh->mNumVertices = vertices.GetShape(0);
433  ai_mesh->mVertices = new aiVector3D[ai_mesh->mNumVertices];
434  memcpy(&ai_mesh->mVertices->x, vertices.GetDataPtr(),
435  sizeof(float) * ai_mesh->mNumVertices * 3);
436  ai_mesh->mNumFaces = indices.GetShape(0);
437  ai_mesh->mFaces = new aiFace[ai_mesh->mNumFaces];
438  for (unsigned int i = 0; i < ai_mesh->mNumFaces; ++i) {
439  ai_mesh->mFaces[i].mNumIndices = 3;
440  // NOTE: Yes, dynamically allocating 3 ints for each face is inefficient
441  // but this is what Assimp seems to require as it deletes each mIndices
442  // on destruction. We could block allocate space for all the faces,
443  // assign pointers here then zero out the pointers before destruction so
444  // the delete becomes a no-op, but that seems error prone. Could revisit
445  // if this becomes an IO bottleneck.
446  ai_mesh->mFaces[i].mIndices = new unsigned int[3]; // triangles
447  ai_mesh->mFaces[i].mIndices[0] = indices[i][0].Item<unsigned int>();
448  ai_mesh->mFaces[i].mIndices[1] = indices[i][1].Item<unsigned int>();
449  ai_mesh->mFaces[i].mIndices[2] = indices[i][2].Item<unsigned int>();
450  }
451 
452  if (write_vertex_normals && w_mesh.HasVertexNormals()) {
453  auto normals = w_mesh.GetVertexNormals().Contiguous();
454  auto m_normals = normals.GetShape(0);
455  ai_mesh->mNormals = new aiVector3D[m_normals];
456  memcpy(&ai_mesh->mNormals->x, normals.GetDataPtr(),
457  sizeof(float) * m_normals * 3);
458  }
459 
460  if (write_vertex_colors && w_mesh.HasVertexColors()) {
461  auto colors = w_mesh.GetVertexColors().Contiguous();
462  auto m_colors = colors.GetShape(0);
463  ai_mesh->mColors[0] = new aiColor4D[m_colors];
464  if (colors.GetShape(1) == 4) {
465  memcpy(&ai_mesh->mColors[0][0].r, colors.GetDataPtr(),
466  sizeof(float) * m_colors * 4);
467  } else { // must be 3 components
468  auto color_ptr = reinterpret_cast<float*>(colors.GetDataPtr());
469  for (unsigned int i = 0; i < m_colors; ++i) {
470  ai_mesh->mColors[0][i].r = *color_ptr++;
471  ai_mesh->mColors[0][i].g = *color_ptr++;
472  ai_mesh->mColors[0][i].b = *color_ptr++;
473  ai_mesh->mColors[0][i].a = 1.0f;
474  }
475  }
476  }
477 
478  if (write_triangle_uvs &&
479  w_mesh.HasVertexAttr(
480  "texture_uvs")) { // Save vertex UVs converted earlier
481  auto vertex_uvs = w_mesh.GetVertexAttr("texture_uvs").Contiguous();
482  auto uv_ptr = vertex_uvs.GetDataPtr<float>();
483  auto n_uvs = vertex_uvs.GetShape(0);
484  ai_mesh->mNumUVComponents[0] = 2;
485  ai_mesh->mTextureCoords[0] = new aiVector3D[n_uvs];
486  for (unsigned int i = 0; i < n_uvs; ++i) {
487  ai_mesh->mTextureCoords[0][i].x = *uv_ptr++;
488  ai_mesh->mTextureCoords[0][i].y = *uv_ptr++;
489  }
490  } else if (write_triangle_uvs && w_mesh.HasTriangleAttr("texture_uvs")) {
491  auto triangle_uvs = w_mesh.GetTriangleAttr("texture_uvs").Contiguous();
492  auto vertex_uvs = core::Tensor::Empty({ai_mesh->mNumVertices, 2},
494  auto n_uvs = ai_mesh->mNumVertices;
495  for (int64_t i = 0; i < indices.GetShape(0); i++) {
496  vertex_uvs[indices[i][0].Item<uint32_t>()] = triangle_uvs[i][0];
497  vertex_uvs[indices[i][1].Item<uint32_t>()] = triangle_uvs[i][1];
498  vertex_uvs[indices[i][2].Item<uint32_t>()] = triangle_uvs[i][2];
499  }
500  ai_mesh->mTextureCoords[0] = new aiVector3D[n_uvs];
501  auto uv_ptr = reinterpret_cast<float*>(vertex_uvs.GetDataPtr());
502  for (unsigned int i = 0; i < n_uvs; ++i) {
503  ai_mesh->mTextureCoords[0][i].x = *uv_ptr++;
504  ai_mesh->mTextureCoords[0][i].y = *uv_ptr++;
505  }
506  ai_mesh->mNumUVComponents[0] = 2;
507  }
508  ai_scene->mMeshes[0] = ai_mesh;
509 
510  // Fill material data...
511  ai_scene->mNumMaterials = 1;
512  ai_scene->mMaterials = new aiMaterial*[ai_scene->mNumMaterials];
513  auto ai_mat = new aiMaterial;
514  if (w_mesh.HasMaterial()) {
515  ai_mat->GetName().Set("mat1");
516  auto shading_mode = aiShadingMode_PBR_BRDF;
517  ai_mat->AddProperty(&shading_mode, 1, AI_MATKEY_SHADING_MODEL);
518 
519  // Set base material properties
520  // NOTE: not all properties supported by CloudViewer are supported by
521  // Assimp. Those properties (reflectivity, anisotropy) are not exported
522  if (w_mesh.GetMaterial().HasBaseColor()) {
523  auto c = w_mesh.GetMaterial().GetBaseColor();
524  auto ac = aiColor4D(c.x(), c.y(), c.z(), c.w());
525  ai_mat->AddProperty(&ac, 1, AI_MATKEY_COLOR_DIFFUSE);
526  ai_mat->AddProperty(&ac, 1, AI_MATKEY_BASE_COLOR);
527  }
528  if (w_mesh.GetMaterial().HasBaseRoughness()) {
529  auto r = w_mesh.GetMaterial().GetBaseRoughness();
530  ai_mat->AddProperty(&r, 1, AI_MATKEY_ROUGHNESS_FACTOR);
531  }
532  if (w_mesh.GetMaterial().HasBaseMetallic()) {
533  auto m = w_mesh.GetMaterial().GetBaseMetallic();
534  ai_mat->AddProperty(&m, 1, AI_MATKEY_METALLIC_FACTOR);
535  }
536  if (w_mesh.GetMaterial().HasBaseClearcoat()) {
537  auto c = w_mesh.GetMaterial().GetBaseClearcoat();
538  ai_mat->AddProperty(&c, 1, AI_MATKEY_CLEARCOAT_FACTOR);
539  }
540  if (w_mesh.GetMaterial().HasBaseClearcoatRoughness()) {
541  auto r = w_mesh.GetMaterial().GetBaseClearcoatRoughness();
542  ai_mat->AddProperty(&r, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR);
543  }
544  if (w_mesh.GetMaterial().HasEmissiveColor()) {
545  auto c = w_mesh.GetMaterial().GetEmissiveColor();
546  auto ac = aiColor4D(c.x(), c.y(), c.z(), c.w());
547  ai_mat->AddProperty(&ac, 1, AI_MATKEY_COLOR_EMISSIVE);
548  }
549 
550  // Count texture maps...
551  // NOTE: GLTF2 expects a single combined roughness/metal map. If the
552  // model has one we just export it, otherwise if both roughness and
553  // metal maps are available we combine them, otherwise if only one or
554  // the other is available we just export the one map.
555  int n_textures = 0;
556  if (w_mesh.GetMaterial().HasAlbedoMap()) ++n_textures;
557  if (w_mesh.GetMaterial().HasNormalMap()) ++n_textures;
558  if (w_mesh.GetMaterial().HasAORoughnessMetalMap()) {
559  ++n_textures;
560  } else if (w_mesh.GetMaterial().HasRoughnessMap() &&
561  w_mesh.GetMaterial().HasMetallicMap()) {
562  ++n_textures;
563  } else {
564  if (w_mesh.GetMaterial().HasRoughnessMap()) ++n_textures;
565  if (w_mesh.GetMaterial().HasMetallicMap()) ++n_textures;
566  }
567  if (n_textures > 0) {
568  ai_scene->mTextures = new aiTexture*[n_textures];
569  for (int i = 0; i < n_textures; ++i) {
570  ai_scene->mTextures[i] = new aiTexture();
571  }
572  ai_scene->mNumTextures = n_textures;
573  }
574 
575  // Now embed the textures that are available...
576  int current_idx = 0;
577  if (w_mesh.GetMaterial().HasAlbedoMap()) {
578  auto img = w_mesh.GetMaterial().GetAlbedoMap();
579  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
580  aiTextureType_DIFFUSE, img);
581  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
582  aiTextureType_BASE_COLOR, img);
583  ++current_idx;
584  }
585  if (w_mesh.GetMaterial().HasAORoughnessMetalMap()) {
586  auto img = w_mesh.GetMaterial().GetAORoughnessMetalMap();
587  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
588  aiTextureType_UNKNOWN, img);
589  ++current_idx;
590  } else if (w_mesh.GetMaterial().HasRoughnessMap() &&
591  w_mesh.GetMaterial().HasMetallicMap()) {
592  auto rough = w_mesh.GetMaterial().GetRoughnessMap();
593  auto metal = w_mesh.GetMaterial().GetMetallicMap();
594  auto rows = rough.GetRows();
595  auto cols = rough.GetCols();
596  auto rough_metal =
597  geometry::Image(rows, cols, 4, core::Dtype::UInt8);
598  rough_metal.AsTensor() =
599  core::Tensor::Ones(rough_metal.AsTensor().GetShape(),
601  255;
602  auto metal_channel = metal.AsTensor().GetItem(
603  {core::TensorKey::Slice(0, rows + 1, core::None),
604  core::TensorKey::Slice(0, cols + 1, core::None),
606  auto rough_channel = rough.AsTensor().GetItem(
607  {core::TensorKey::Slice(0, rows + 1, core::None),
608  core::TensorKey::Slice(0, cols + 1, core::None),
610  rough_metal.AsTensor().SetItem(
611  {core::TensorKey::Slice(0, rows + 1, core::None),
612  core::TensorKey::Slice(0, cols + 1, core::None),
613  core::TensorKey::Index(2)}, // metallic in blue
614  metal_channel);
615  rough_metal.AsTensor().SetItem(
616  {core::TensorKey::Slice(0, rows + 1, core::None),
617  core::TensorKey::Slice(0, cols + 1, core::None),
618  core::TensorKey::Index(1)}, // roughness in green
619  rough_channel);
620  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
621  aiTextureType_UNKNOWN, rough_metal);
622  ++current_idx;
623  } else {
624  if (w_mesh.GetMaterial().HasRoughnessMap()) {
625  auto img = w_mesh.GetMaterial().GetRoughnessMap();
626  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
627  aiTextureType_UNKNOWN, img);
628  ++current_idx;
629  }
630  if (w_mesh.GetMaterial().HasMetallicMap()) {
631  auto img = w_mesh.GetMaterial().GetMetallicMap();
632  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
633  aiTextureType_UNKNOWN, img);
634  ++current_idx;
635  }
636  }
637  if (w_mesh.GetMaterial().HasNormalMap()) {
638  auto img = w_mesh.GetMaterial().GetNormalMap();
639  SetTextureMaterialProperty(ai_mat, ai_scene.get(), current_idx,
640  aiTextureType_NORMALS, img);
641  ++current_idx;
642  }
643  }
644  ai_scene->mMaterials[0] = ai_mat;
645 
646  auto root_node = new aiNode;
647  root_node->mName.Set("root");
648  root_node->mNumMeshes = 1;
649  root_node->mMeshes = new unsigned int[root_node->mNumMeshes];
650  root_node->mMeshes[0] = 0;
651  ai_scene->mRootNode = root_node;
652 
653  // Export
654  if (exporter.Export(ai_scene.get(), "glb2", filename.c_str()) ==
655  AI_FAILURE) {
657  "Got error: ({}) while writing TriangleMesh to file {}.",
658  exporter.GetErrorString(), filename);
659  return false;
660  }
661 
662  return true;
663 }
664 
665 } // namespace io
666 } // namespace t
667 } // namespace cloudViewer
IsAscii write_ascii
Compressed compressed
std::string filename
#define DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(DTYPE, PREFIX,...)
Definition: Dispatch.h:118
bool has_colors
bool has_normals
cmdLineReadable * params[]
static const Dtype UInt8
Definition: Dtype.h:30
static const Dtype Float32
Definition: Dtype.h:24
static const Dtype UInt32
Definition: Dtype.h:32
static const Dtype Int64
Definition: Dtype.h:29
static TensorKey Index(int64_t index)
Definition: TensorKey.cpp:134
static TensorKey Slice(utility::optional< int64_t > start, utility::optional< int64_t > stop, utility::optional< int64_t > step)
Definition: TensorKey.cpp:138
Tensor Contiguous() const
Definition: Tensor.cpp:772
Tensor GetItem(const TensorKey &tk) const
Definition: Tensor.cpp:473
Tensor SetItem(const Tensor &value)
Set all items. Equivalent to tensor[:] = value in Python.
Definition: Tensor.cpp:564
Tensor IndexGet(const std::vector< Tensor > &index_tensors) const
Advanced indexing getter. This will always allocate a new Tensor.
Definition: Tensor.cpp:905
static Tensor Empty(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor with uninitialized values.
Definition: Tensor.cpp:400
static Tensor Ones(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor fill with ones.
Definition: Tensor.cpp:412
SizeVector GetShape() const
Definition: Tensor.h:1127
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
bool HasMaterial() const
Check if a material has been applied to this Geometry with SetMaterial.
visualization::rendering::Material & GetMaterial()
Get material associated with this Geometry.
The Image class stores image with customizable rows, cols, channels, dtype and device.
Definition: Image.h:29
int64_t GetRows() const
Get the number of rows of the image.
Definition: Image.h:84
A triangle mesh contains vertices and triangles.
Definition: TriangleMesh.h:98
TriangleMesh & Clear() override
Clear all data in the trianglemesh.
Definition: TriangleMesh.h:619
const TensorMap & GetVertexAttr() const
Getter for vertex_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:133
void SetVertexPositions(const core::Tensor &value)
Definition: TriangleMesh.h:261
void SetTriangleIndices(const core::Tensor &value)
Set the value of the "indices" attribute in triangle_attr_.
Definition: TriangleMesh.h:291
void SetVertexNormals(const core::Tensor &value)
Definition: TriangleMesh.h:275
bool HasVertexAttr(const std::string &key) const
Definition: TriangleMesh.h:314
void RemoveVertexAttr(const std::string &key)
Definition: TriangleMesh.h:195
bool HasTriangleAttr(const std::string &key) const
Definition: TriangleMesh.h:343
void SetVertexColors(const core::Tensor &value)
Definition: TriangleMesh.h:268
void SetTriangleAttr(const std::string &key, const core::Tensor &value)
Definition: TriangleMesh.h:285
const TensorMap & GetTriangleAttr() const
Getter for triangle_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:159
const t::geometry::Image & GetMetallicMap() const
Definition: Material.h:169
const t::geometry::Image & GetRoughnessMap() const
Definition: Material.h:172
const t::geometry::Image & GetNormalMap() const
Definition: Material.h:163
const t::geometry::Image & GetAORoughnessMetalMap() const
Definition: Material.h:189
const t::geometry::Image & GetAlbedoMap() const
Definition: Material.h:160
double colors[3]
double normals[3]
#define LogWarning(...)
Definition: Logging.h:72
void ParallelFor(const Device &device, int64_t n, const func_t &func)
Definition: ParallelFor.h:111
Tensor Concatenate(const std::vector< Tensor > &tensors, const utility::optional< int64_t > &axis)
Concatenates the list of tensors in their order, along the given axis into a new tensor....
constexpr utility::nullopt_t None
Definition: TensorKey.h:20
const Dtype Float32
Definition: Dtype.cpp:42
bool WriteImageToPNGInMemory(std::vector< uint8_t > &buffer, const t::geometry::Image &image, int quality)
Definition: FilePNG.cpp:110
const unsigned int kPostProcessFlags_fast
Definition: FileASSIMP.cpp:41
static void SetTextureMaterialProperty(aiMaterial *mat, aiScene *scene, int texture_idx, aiTextureType tt, t::geometry::Image &img)
Definition: FileASSIMP.cpp:180
const unsigned int kPostProcessFlags_compulsory
Definition: FileASSIMP.cpp:37
bool WriteTriangleMeshUsingASSIMP(const std::string &filename, const geometry::TriangleMesh &mesh, const bool write_ascii, const bool compressed, const bool write_vertex_normals, const bool write_vertex_colors, const bool write_triangle_uvs, const bool print_progress)
Definition: FileASSIMP.cpp:371
bool ReadTriangleMeshUsingASSIMP(const std::string &filename, geometry::TriangleMesh &mesh, const cloudViewer::io::ReadTriangleMeshOptions &params)
Definition: FileASSIMP.cpp:46
Generic file read and write utility for python interface.
std::string to_string(const T &n)
Definition: Common.h:20