mesh_parser.h
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2008, Willow Garage, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the Willow Garage nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 /* Author: Ioan Sucan */
36 
37 /* Updated by John Wason, Wason Technology, LLC, Dec 2020. Add loading mesh normals,
38  materials, and textures */
39 
40 #ifndef TESSERACT_SCENE_GRAPH_MESH_PARSER_H
41 #define TESSERACT_SCENE_GRAPH_MESH_PARSER_H
42 
45 #include <assimp/scene.h>
46 #include <assimp/Importer.hpp>
47 #include <assimp/postprocess.h>
48 
49 #ifdef TESSERACT_ASSIMP_USE_PBRMATERIAL
50 #include <assimp/pbrmaterial.h>
51 #endif
52 
53 #include <console_bridge/console.h>
54 
55 #include <tesseract_common/types.h>
57 
58 #include <regex>
59 #include <boost/filesystem/path.hpp>
60 
62 
64 
65 namespace tesseract_geometry
66 {
73 template <class T>
74 std::vector<std::shared_ptr<T>> extractMeshData(const aiScene* scene,
75  const aiNode* node,
76  const aiMatrix4x4& parent_transform,
77  const Eigen::Vector3d& scale,
79  bool normals,
80  bool vertex_colors,
81  bool material_and_texture)
82 {
83  std::vector<std::shared_ptr<T>> meshes;
84  meshes.reserve(node->mNumMeshes);
85 
86  aiMatrix4x4 transform = parent_transform;
87  transform *= node->mTransformation;
88  for (unsigned int j = 0; j < node->mNumMeshes; ++j)
89  {
90  auto vertices = std::make_shared<tesseract_common::VectorVector3d>();
91  auto triangles = std::make_shared<Eigen::VectorXi>();
92  std::shared_ptr<tesseract_common::VectorVector3d> vertex_normals = nullptr;
93  std::shared_ptr<tesseract_common::VectorVector4d> vertex_colors = nullptr;
94  MeshMaterial::Ptr material = nullptr;
95  std::shared_ptr<std::vector<MeshTexture::Ptr>> textures = nullptr;
96 
97  const aiMesh* a = scene->mMeshes[node->mMeshes[j]];
98  vertices->reserve(a->mNumVertices);
99  for (unsigned int i = 0; i < a->mNumVertices; ++i)
100  {
101  aiVector3D v = transform * a->mVertices[i];
102  vertices->emplace_back(static_cast<double>(v.x) * scale(0),
103  static_cast<double>(v.y) * scale(1),
104  static_cast<double>(v.z) * scale(2));
105  }
106 
107  long triangle_count = 0;
108  std::vector<int> local_triangles;
109  local_triangles.reserve(a->mNumFaces);
110  for (unsigned int i = 0; i < a->mNumFaces; ++i)
111  {
112  if (a->mFaces[i].mNumIndices >= 3)
113  {
114  triangle_count += 1;
115  local_triangles.push_back(static_cast<int>(a->mFaces[i].mNumIndices));
116  for (size_t k = 0; k < a->mFaces[i].mNumIndices; ++k)
117  local_triangles.push_back(static_cast<int>(a->mFaces[i].mIndices[k]));
118  }
119  else
120  {
121  CONSOLE_BRIDGE_logDebug("Mesh had a face with less than three vertices: %s", resource->getUrl().c_str());
122  }
123  }
124 
125  triangles->resize(static_cast<long>(local_triangles.size()));
126  for (long i = 0; i < triangles->size(); ++i)
127  (*triangles)[i] = local_triangles[static_cast<size_t>(i)];
128 
129  if (normals && a->HasNormals())
130  {
131  vertex_normals = std::make_shared<tesseract_common::VectorVector3d>();
132  vertex_normals->reserve(a->mNumVertices);
133  for (unsigned int i = 0; i < a->mNumVertices; ++i)
134  {
135  aiVector3D v = transform * a->mNormals[i];
136  vertex_normals->emplace_back(static_cast<double>(v.x) * scale(0),
137  static_cast<double>(v.y) * scale(1),
138  static_cast<double>(v.z) * scale(2));
139  }
140  }
141 
142  if (vertex_colors && a->HasVertexColors(0))
143  {
144  vertex_colors = std::make_shared<tesseract_common::VectorVector4d>();
145  vertex_colors->reserve(a->mNumVertices);
146  for (unsigned int i = 0; i < a->mNumVertices; ++i)
147  {
148  aiColor4D v = a->mColors[0][i];
149  vertex_colors->emplace_back(
150  static_cast<double>(v.r), static_cast<double>(v.g), static_cast<double>(v.b), static_cast<double>(v.a));
151  }
152  }
153 
154  if (material_and_texture)
155  {
156  aiMaterial* mat = scene->mMaterials[a->mMaterialIndex];
157  {
158  Eigen::Vector4d base_color = Eigen::Vector4d::Zero();
159  double metallic = 0.0;
160  double roughness = 0.5;
161  Eigen::Vector4d emissive = Eigen::Vector4d::Zero();
162 
163  aiColor4D pbr_base_color;
164 #ifdef TESSERACT_ASSIMP_USE_PBRMATERIAL
165  if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color) == AI_SUCCESS)
166  {
167  // Use PBR Metallic material properties if available
168  base_color = Eigen::Vector4d(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a);
169  float metallicFactor{ 0 };
170  if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallicFactor) == AI_SUCCESS)
171  {
172  metallic = metallicFactor;
173  }
174  float roughnessFactor{ 0.5 };
175  if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughnessFactor) == AI_SUCCESS)
176  {
177  roughness = roughnessFactor;
178  }
179  aiColor4D pbr_emissive_color;
180  if (mat->Get(AI_MATKEY_COLOR_EMISSIVE, pbr_emissive_color) == AI_SUCCESS)
181  {
182  emissive =
183  Eigen::Vector4d(pbr_emissive_color.r, pbr_emissive_color.g, pbr_emissive_color.b, pbr_emissive_color.a);
184  }
185  }
186  else
187 #endif
188  {
189  // Use old style material. Ambient and specular not supported
190  aiColor4D diffuse_color;
191  if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse_color) == AI_SUCCESS)
192  {
193  base_color = Eigen::Vector4d(diffuse_color.r, diffuse_color.g, diffuse_color.b, diffuse_color.a);
194  }
195 
196  aiColor4D emissive_color;
197  if (mat->Get(AI_MATKEY_COLOR_EMISSIVE, emissive_color) == AI_SUCCESS)
198  {
199  emissive = Eigen::Vector4d(emissive_color.r, emissive_color.g, emissive_color.b, emissive_color.a);
200  }
201  }
202 
203  material = std::make_shared<MeshMaterial>(base_color, metallic, roughness, emissive);
204 
205  for (unsigned int i = 0; i < a->GetNumUVChannels(); i++)
206  {
207  if (a->HasTextureCoords(i))
208  {
209  aiString texName;
210  aiTextureMapping mapping{ aiTextureMapping_OTHER };
211  unsigned int uvIndex{ 0 };
212  if (mat->GetTexture(aiTextureType_DIFFUSE, i, &texName, &mapping, &uvIndex) == AI_SUCCESS)
213  {
214  tesseract_common::Resource::Ptr texture_image;
216  // https://stackoverflow.com/questions/56820244/assimp-doenst-return-texture-data
217  const char* texNamec = texName.C_Str();
218  if ('*' == *texNamec)
219  {
220  int tex_index = std::atoi(texNamec + 1);
221  if (0 > tex_index || scene->mNumTextures <= static_cast<unsigned>(tex_index))
222  continue;
223  auto* texture_data = scene->mTextures[tex_index];
224  // returned pointer is not null, read texture from memory
225  std::string file_type = texture_data->achFormatHint;
226  if (file_type == "jpg" || file_type == "png")
227  {
228  texture_image = std::make_shared<tesseract_common::BytesResource>(
229  "data://", (const uint8_t*)texture_data->pcData, texture_data->mWidth); // NOLINT
230  }
231  else
232  {
233  // TODO: handle other file types
234  continue;
235  }
236  }
237  else
238  {
239  if (!resource)
240  {
241  continue;
242  }
243  std::string texName_str(texName.C_Str());
244  auto tex_resource = resource->locateResource(texName_str);
245  if (!tex_resource)
246  {
247  continue;
248  }
249  texture_image = tex_resource;
250  }
251  aiVector3D* tex_coords = a->mTextureCoords[i];
252  for (unsigned int j = 0; j < a->mNumVertices; ++j)
253  {
254  aiVector3D v = tex_coords[j];
255  uvs.emplace_back(static_cast<double>(v.x), static_cast<double>(v.y));
256  }
257  auto tex = std::make_shared<MeshTexture>(
258  texture_image, std::make_shared<tesseract_common::VectorVector2d>(std::move(uvs)));
259  if (!textures)
260  {
261  textures = std::make_shared<std::vector<MeshTexture::Ptr>>();
262  }
263  textures->push_back(tex);
264  }
265  }
266  }
267  }
268  }
269 
270  meshes.push_back(std::make_shared<T>(vertices,
271  triangles,
272  static_cast<int>(triangle_count),
273  resource,
274  scale,
275  vertex_normals,
276  vertex_colors,
277  material,
278  textures));
279  }
280 
281  for (unsigned int n = 0; n < node->mNumChildren; ++n)
282  {
283  std::vector<std::shared_ptr<T>> child_meshes = extractMeshData<T>(
284  scene, node->mChildren[n], transform, scale, resource, normals, vertex_colors, material_and_texture);
285  meshes.insert(meshes.end(), child_meshes.begin(), child_meshes.end());
286  }
287  return meshes;
288 }
289 
300 template <class T>
301 std::vector<std::shared_ptr<T>> createMeshFromAsset(const aiScene* scene,
302  const Eigen::Vector3d& scale,
304  bool normals,
305  bool vertex_colors,
306  bool material_and_texture)
307 {
308  if (!scene->HasMeshes())
309  {
310  CONSOLE_BRIDGE_logWarn("Assimp reports scene in %s has no meshes", resource->getUrl().c_str());
311  return std::vector<std::shared_ptr<T>>();
312  }
313  std::vector<std::shared_ptr<T>> meshes = extractMeshData<T>(
314  scene, scene->mRootNode, aiMatrix4x4(), scale, resource, normals, vertex_colors, material_and_texture);
315  if (meshes.empty())
316  {
317  CONSOLE_BRIDGE_logWarn("There are no meshes in the scene %s", resource->getUrl().c_str());
318  return std::vector<std::shared_ptr<T>>();
319  }
320 
321  return meshes;
322 }
323 
337 template <class T>
338 std::vector<std::shared_ptr<T>> createMeshFromPath(const std::string& path,
339  const Eigen::Vector3d& scale = Eigen::Vector3d(1, 1, 1),
340  bool triangulate = false,
341  bool flatten = false,
342  bool normals = false,
343  bool vertex_colors = false,
344  bool material_and_texture = false)
345 {
346  // Create an instance of the Importer class
347  Assimp::Importer importer;
348 
349  // Issue #38 fix: as part of the post-processing, we remove all other components in file but
350  // the meshes, as anyway the resulting shapes:Mesh object just receives vertices and triangles.
351  // John Wason Jan 2021 - Adjust flags based on normals, vertex_colors, and material_and_texture parameters
352  unsigned int ai_config_pp_rcv_flags = aiComponent_TANGENTS_AND_BITANGENTS | aiComponent_BONEWEIGHTS |
353  aiComponent_ANIMATIONS | aiComponent_LIGHTS | aiComponent_CAMERAS;
354  if (!normals)
355  {
356  ai_config_pp_rcv_flags |= aiComponent_NORMALS;
357  }
358  if (!vertex_colors)
359  {
360  ai_config_pp_rcv_flags |= aiComponent_COLORS;
361  }
362  if (!material_and_texture)
363  {
364  ai_config_pp_rcv_flags |= aiComponent_TEXCOORDS | aiComponent_TEXTURES | aiComponent_MATERIALS;
365  }
366  importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, (int)ai_config_pp_rcv_flags);
367 
368  // And have it read the given file with some post-processing
369  const aiScene* scene = nullptr;
370  if (triangulate)
371  scene = importer.ReadFile(path.c_str(),
372  aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType |
373  aiProcess_RemoveComponent);
374  else
375  scene = importer.ReadFile(path.c_str(),
376  aiProcess_JoinIdenticalVertices | aiProcess_SortByPType | aiProcess_RemoveComponent);
377 
378  if (!scene)
379  {
380  CONSOLE_BRIDGE_logError("Could not load mesh from \"%s\": %s", path.c_str(), importer.GetErrorString());
381  return std::vector<std::shared_ptr<T>>();
382  }
383 
384  // Assimp enforces Y_UP convention by rotating models with different conventions.
385  // However, that behavior is confusing and doesn't match the ROS convention
386  // where the Z axis is pointing up.
387  // Hopefully this doesn't undo legit use of the root node transformation...
388  // Note that this is also what RViz does internally.
389  // @todo See this issue for possible fix parsing scene metadata https://github.com/assimp/assimp/issues/849
390  scene->mRootNode->mTransformation = aiMatrix4x4();
391 
392  if (flatten)
393  {
394  // These post processing steps flatten the root node transformation into child nodes,
395  // so they must be delayed until after clearing the root node transform above.
396  importer.ApplyPostProcessing(aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph);
397  }
398  else
399  {
400  importer.ApplyPostProcessing(aiProcess_OptimizeGraph);
401  }
402 
403  return createMeshFromAsset<T>(scene, scale, nullptr, normals, vertex_colors, material_and_texture);
404 }
405 
419 template <class T>
420 std::vector<std::shared_ptr<T>> createMeshFromResource(tesseract_common::Resource::Ptr resource,
421  const Eigen::Vector3d& scale = Eigen::Vector3d(1, 1, 1),
422  bool triangulate = false,
423  bool flatten = false,
424  bool normals = false,
425  bool vertex_colors = false,
426  bool material_and_texture = false)
427 {
428  if (!resource)
429  return std::vector<std::shared_ptr<T>>();
430 
431  const char* hint = nullptr;
432  std::string hint_storage;
433 
434  std::string resource_url = resource->getUrl();
435  std::regex hint_re("^.*\\.([A-Za-z0-9]{1,8})$");
436  std::smatch hint_match;
437  if (std::regex_match(resource_url, hint_match, hint_re))
438  {
439  if (hint_match.size() == 2)
440  {
441  hint_storage = hint_match[1].str();
442  hint = hint_storage.c_str();
443  }
444  }
445 
446  std::vector<uint8_t> data = resource->getResourceContents();
447  if (data.empty())
448  {
449  if (resource->isFile())
450  return createMeshFromPath<T>(
451  resource->getFilePath(), scale, triangulate, flatten, normals, vertex_colors, material_and_texture);
452 
453  return std::vector<std::shared_ptr<T>>();
454  }
455 
456  // Create an instance of the Importer class
457  Assimp::Importer importer;
458 
459  // Issue #38 fix: as part of the post-processing, we remove all other components in file but
460  // the meshes, as anyway the resulting shapes:Mesh object just receives vertices and triangles.
461  // John Wason Jan 2021 - Adjust flags based on normals, vertex_colors, and material_and_texture parameters
462  unsigned int ai_config_pp_rcv_flags = aiComponent_TANGENTS_AND_BITANGENTS | aiComponent_BONEWEIGHTS |
463  aiComponent_ANIMATIONS | aiComponent_LIGHTS | aiComponent_CAMERAS;
464  if (!normals)
465  {
466  ai_config_pp_rcv_flags |= aiComponent_NORMALS;
467  }
468  if (!vertex_colors)
469  {
470  ai_config_pp_rcv_flags |= aiComponent_COLORS;
471  }
472  if (!material_and_texture)
473  {
474  ai_config_pp_rcv_flags |= aiComponent_TEXCOORDS | aiComponent_TEXTURES | aiComponent_MATERIALS;
475  }
476  importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, (int)ai_config_pp_rcv_flags);
477 
478  // And have it read the given file with some post-processing
479  const aiScene* scene = nullptr;
480  if (triangulate)
481  scene = importer.ReadFileFromMemory(data.data(),
482  data.size(),
483  aiProcess_Triangulate | aiProcess_JoinIdenticalVertices |
484  aiProcess_SortByPType | aiProcess_RemoveComponent,
485  hint);
486  else
487  scene =
488  importer.ReadFileFromMemory(data.data(),
489  data.size(),
490  aiProcess_JoinIdenticalVertices | aiProcess_SortByPType | aiProcess_RemoveComponent,
491  hint);
492 
493  if (!scene)
494  {
495  CONSOLE_BRIDGE_logError(
496  "Could not load mesh from \"%s\": %s", resource->getUrl().c_str(), importer.GetErrorString());
497  return std::vector<std::shared_ptr<T>>();
498  }
499 
500  // Assimp enforces Y_UP convention by rotating models with different conventions.
501  // However, that behavior is confusing and doesn't match the ROS convention
502  // where the Z axis is pointing up.
503  // Hopefully this doesn't undo legit use of the root node transformation...
504  // Note that this is also what RViz does internally.
505  scene->mRootNode->mTransformation = aiMatrix4x4();
506 
507  if (flatten)
508  {
509  // These post processing steps flatten the root node transformation into child nodes,
510  // so they must be delayed until after clearing the root node transform above.
511  importer.ApplyPostProcessing(aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph);
512  }
513  else
514  {
515  importer.ApplyPostProcessing(aiProcess_OptimizeGraph);
516  }
517 
518  return createMeshFromAsset<T>(scene, scale, resource, normals, vertex_colors, material_and_texture);
519 }
520 
536 template <typename T>
537 static std::vector<std::shared_ptr<T>> createMeshFromBytes(const std::string& url,
538  const uint8_t* bytes,
539  size_t bytes_len,
540  const Eigen::Vector3d& scale = Eigen::Vector3d(1, 1, 1),
541  bool triangulate = false,
542  bool flatten = false,
543  bool normals = false,
544  bool vertex_colors = false,
545  bool material_and_texture = false)
546 {
547  std::shared_ptr<tesseract_common::Resource> resource =
548  std::make_shared<tesseract_common::BytesResource>(url, bytes, bytes_len);
549  return tesseract_geometry::createMeshFromResource<T>(
550  resource, scale, triangulate, flatten, normals, vertex_colors, material_and_texture);
551 }
552 
553 } // namespace tesseract_geometry
554 
555 #endif
tesseract_common::VectorVector2d
AlignedVector< Eigen::Vector2d > VectorVector2d
resource_locator.h
TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
#define TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
tesseract_geometry::createMeshFromBytes
static std::vector< std::shared_ptr< T > > createMeshFromBytes(const std::string &url, const uint8_t *bytes, size_t bytes_len, const Eigen::Vector3d &scale=Eigen::Vector3d(1, 1, 1), bool triangulate=false, bool flatten=false, bool normals=false, bool vertex_colors=false, bool material_and_texture=false)
Create a mesh from byte array.
Definition: mesh_parser.h:569
tesseract_geometry::MeshMaterial::Ptr
std::shared_ptr< MeshMaterial > Ptr
Definition: mesh_material.h:67
tesseract_geometry::createMeshFromAsset
std::vector< std::shared_ptr< T > > createMeshFromAsset(const aiScene *scene, const Eigen::Vector3d &scale, tesseract_common::Resource::Ptr resource, bool normals, bool vertex_colors, bool material_and_texture)
Create list of meshes from the assimp scene.
Definition: mesh_parser.h:333
mesh_material.h
Tesseract Mesh Material read from a mesh file.
tesseract_geometry::extractMeshData
std::vector< std::shared_ptr< T > > extractMeshData(const aiScene *scene, const aiNode *node, const aiMatrix4x4 &parent_transform, const Eigen::Vector3d &scale, tesseract_common::Resource::Ptr resource, bool normals, bool vertex_colors, bool material_and_texture)
Definition: mesh_parser.h:106
TESSERACT_COMMON_IGNORE_WARNINGS_POP
#define TESSERACT_COMMON_IGNORE_WARNINGS_POP
tesseract_common::Resource::Ptr
std::shared_ptr< Resource > Ptr
tesseract_geometry
Definition: fwd.h:31
tesseract_geometry::createMeshFromPath
std::vector< std::shared_ptr< T > > createMeshFromPath(const std::string &path, const Eigen::Vector3d &scale=Eigen::Vector3d(1, 1, 1), bool triangulate=false, bool flatten=false, bool normals=false, bool vertex_colors=false, bool material_and_texture=false)
Create a mesh using assimp from file path.
Definition: mesh_parser.h:370
types.h
macros.h
tesseract_geometry::createMeshFromResource
std::vector< std::shared_ptr< T > > createMeshFromResource(tesseract_common::Resource::Ptr resource, const Eigen::Vector3d &scale=Eigen::Vector3d(1, 1, 1), bool triangulate=false, bool flatten=false, bool normals=false, bool vertex_colors=false, bool material_and_texture=false)
Create a mesh using assimp from resource.
Definition: mesh_parser.h:452


tesseract_geometry
Author(s): Levi Armstrong
autogenerated on Sun May 18 2025 03:01:46