Blue Brain BioExplorer
MeshLoader.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-2024, EPFL/Blue Brain Project
3  *
4  * The Blue Brain BioExplorer is a tool for scientists to extract and analyse
5  * scientific data from visualization
6  *
7  * This file is part of Blue Brain BioExplorer <https://github.com/BlueBrain/BioExplorer>
8  *
9  * This library is free software; you can redistribute it and/or modify it under
10  * the terms of the GNU Lesser General Public License version 3.0 as published
11  * by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this library; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 
23 #include "MeshLoader.h"
24 
25 #include <assimp/Importer.hpp>
26 #include <assimp/ProgressHandler.hpp>
27 #include <assimp/postprocess.h>
28 #include <assimp/scene.h>
29 #include <assimp/version.h>
31 
32 #include <fstream>
33 #include <numeric>
34 #include <unordered_map>
35 
42 
43 #ifdef USE_CUSTOM_PLY_IMPORTER
44 #include "assimpImporters/PlyLoader.h"
45 #endif
46 #include "assimpImporters/ObjFileImporter.h"
47 
48 namespace core
49 {
50 namespace
51 {
52 const auto PROP_GEOMETRY_QUALITY = "geometryQuality";
53 
54 const auto LOADER_NAME = "mesh";
55 
56 constexpr float TOTAL_PROGRESS = 100.f;
57 constexpr float LOADING_FRACTION = 50.f;
58 constexpr float POST_LOADING_FRACTION = 50.f;
59 
60 class ProgressWatcher : public Assimp::ProgressHandler
61 {
62 public:
63  ProgressWatcher(const LoaderProgress& callback, const std::string& filename)
64  : _callback(callback)
65 
66  {
67  _msg << "Loading " << string_utils::shortenString(filename) << " ...";
68  }
69 
70  bool Update(const float percentage) final
71  {
72  if (percentage > 0.f)
73  _callback.updateProgress(_msg.str(), (std::min(1.f, percentage) * LOADING_FRACTION) / TOTAL_PROGRESS);
74  return true;
75  }
76 
77 private:
78  const LoaderProgress& _callback;
79  std::function<void()> _cancelCheck;
80  std::stringstream _msg;
81 };
82 
83 std::vector<std::string> getSupportedTypes()
84 {
85  std::set<std::string> types;
86  std::string extensions;
87  Assimp::Importer importer;
88  importer.GetExtensionList(extensions);
89 
90  std::istringstream stream(extensions);
91  std::string s;
92  while (std::getline(stream, s, ';'))
93  {
94  auto pos = s.find_last_of(".");
95  types.insert(pos == std::string::npos ? s : s.substr(pos + 1));
96  }
97 
98  std::vector<std::string> output;
99  std::copy(types.begin(), types.end(), std::back_inserter(output));
100  return output;
101 }
102 
103 Assimp::Importer createImporter(const LoaderProgress& callback, const std::string& filename)
104 {
105  Assimp::Importer importer;
106  importer.SetProgressHandler(new ProgressWatcher(callback, filename));
107 
108 // WAR for https://github.com/assimp/assimp/issues/2337; use PLY importer
109 // from commit dcc5887
110 #ifdef USE_CUSTOM_PLY_IMPORTER
111  {
112  importer.UnregisterLoader(importer.GetImporter("ply"));
113  importer.RegisterLoader(new Assimp::PLYImporter());
114  }
115 #endif
116 
117  importer.UnregisterLoader(importer.GetImporter("obj"));
118  importer.RegisterLoader(new Assimp::ObjFileImporter());
119  return importer;
120 }
121 } // namespace
122 
124  : Loader(scene)
125 {
126 }
127 
129  : Loader(scene)
130 {
131  _defaults.setProperty({PROP_GEOMETRY_QUALITY,
133  enumNames<core::GeometryQuality>(),
134  {"Geometry quality"}});
135 }
136 
137 bool MeshLoader::isSupported(const std::string& storage, const std::string& extension) const
138 {
139  const auto types = getSupportedTypes();
140  return std::find(types.begin(), types.end(), extension) != types.end();
141 }
142 
143 ModelDescriptorPtr MeshLoader::importFromStorage(const std::string& storage, const LoaderProgress& callback,
144  const PropertyMap& inProperties) const
145 {
146  // Fill property map since the actual property types are known now.
147  PropertyMap properties = _defaults;
148  properties.merge(inProperties);
149 
150  const auto geometryQuality = stringToEnum<GeometryQuality>(
151  properties.getProperty<std::string>(PROP_GEOMETRY_QUALITY, enumToString(GeometryQuality::high)));
152 
153  auto model = _scene.createModel();
154  auto metadata = importMesh(storage, callback, *model, {}, NO_MATERIAL, geometryQuality);
155 
156  Transformation transformation;
157  transformation.setRotationCenter(model->getBounds().getCenter());
158 
159  auto modelDescriptor = std::make_shared<ModelDescriptor>(std::move(model), storage, metadata);
160  modelDescriptor->setTransformation(transformation);
161  return modelDescriptor;
162 }
163 
165  const PropertyMap& propertiesTmp) const
166 {
167  // Fill property map since the actual property types are known now.
168  PropertyMap properties = getProperties();
169  properties.merge(propertiesTmp);
170 
171  const auto geometryQuality = stringToEnum<GeometryQuality>(
172  properties.getProperty<std::string>(PROP_GEOMETRY_QUALITY, enumToString(GeometryQuality::high)));
173 
174  auto importer = createImporter(callback, blob.name);
175  const aiScene* aiScene = importer.ReadFileFromMemory(blob.data.data(), blob.data.size(),
176  _getQuality(geometryQuality), blob.type.c_str());
177 
178  if (!aiScene)
179  throw std::runtime_error(importer.GetErrorString());
180 
181  if (!aiScene->HasMeshes())
182  throw std::runtime_error("No meshes found");
183 
184  callback.updateProgress("Post-processing...", (LOADING_FRACTION) / TOTAL_PROGRESS);
185 
186  auto model = _scene.createModel();
187 
188  auto metadata = _postLoad(aiScene, *model, Matrix4f(1), NO_MATERIAL, "", callback);
189 
190  Transformation transformation;
191  transformation.setRotationCenter(model->getBounds().getCenter());
192 
193  auto modelDescriptor = std::make_shared<ModelDescriptor>(std::move(model), blob.name, metadata);
194  modelDescriptor->setTransformation(transformation);
195  return modelDescriptor;
196 }
197 
198 void MeshLoader::_createMaterials(Model& model, const aiScene* aiScene, const std::string& folder) const
199 {
200  CORE_DEBUG("Loading " << aiScene->mNumMaterials << " materials");
201 
202  for (size_t m = 0; m < aiScene->mNumMaterials; ++m)
203  {
204  aiMaterial* aimaterial = aiScene->mMaterials[m];
205 
206  aiString valueString;
207  aimaterial->Get(AI_MATKEY_NAME, valueString);
208  std::string name{valueString.C_Str()};
209  auto material = model.createMaterial(m, name);
210 
211  struct TextureTypeMapping
212  {
213  aiTextureType aiType;
214  TextureType type;
215  };
216 
217  const size_t NB_TEXTURE_TYPES = 6;
218  TextureTypeMapping textureTypeMapping[NB_TEXTURE_TYPES] = {{aiTextureType_DIFFUSE, TextureType::diffuse},
219  {aiTextureType_NORMALS, TextureType::normals},
220  {aiTextureType_SPECULAR, TextureType::specular},
221  {aiTextureType_EMISSIVE, TextureType::emissive},
222  {aiTextureType_OPACITY, TextureType::opacity},
223  {aiTextureType_REFLECTION, TextureType::reflection}};
224 
225  for (size_t textureType = 0; textureType < NB_TEXTURE_TYPES; ++textureType)
226  {
227  if (aimaterial->GetTextureCount(textureTypeMapping[textureType].aiType) > 0)
228  {
229  aiString path;
230  if (aimaterial->GetTexture(textureTypeMapping[textureType].aiType, 0, &path, nullptr, nullptr, nullptr,
231  nullptr, nullptr) == AI_SUCCESS)
232  {
233  const std::string fileName = folder + "/" + path.data;
234  CORE_DEBUG("Loading texture: " << fileName);
235  material->setTexture(fileName, textureTypeMapping[textureType].type);
236  }
237  }
238  }
239 
240  aiColor4D value4f(0.f);
241  float value1f;
242  if (aimaterial->Get(AI_MATKEY_COLOR_DIFFUSE, value4f) == AI_SUCCESS)
243  material->setDiffuseColor({value4f.r, value4f.g, value4f.b});
244 
245  value1f = 0.f;
246  if (aimaterial->Get(AI_MATKEY_OPACITY, value1f) != AI_SUCCESS)
247  material->setOpacity(value4f.a);
248  else
249  material->setOpacity(value1f);
250 
251  value1f = 0.f;
252  if (aimaterial->Get(AI_MATKEY_REFLECTIVITY, value1f) == AI_SUCCESS)
253  material->setReflectionIndex(value1f);
254 
255  value4f = aiColor4D(0.f);
256  if (aimaterial->Get(AI_MATKEY_COLOR_SPECULAR, value4f) == AI_SUCCESS)
257  material->setSpecularColor({value4f.r, value4f.g, value4f.b});
258 
259  value1f = 0.f;
260  if (aimaterial->Get(AI_MATKEY_SHININESS, value1f) == AI_SUCCESS)
261  {
262  if (value1f == 0.f)
263  material->setSpecularColor({0.f, 0.f, 0.f});
264  material->setSpecularExponent(value1f);
265  }
266 
267  value4f = aiColor4D(0.f);
268  if (aimaterial->Get(AI_MATKEY_COLOR_EMISSIVE, value4f) == AI_SUCCESS)
269  material->setEmission(value4f.r);
270 
271  value1f = 0.f;
272  if (aimaterial->Get(AI_MATKEY_REFRACTI, value1f) == AI_SUCCESS)
273  if (value1f != 0.f)
274  material->setRefractionIndex(value1f);
275  }
276 }
277 
278 ModelMetadata MeshLoader::_postLoad(const aiScene* aiScene, Model& model, const Matrix4f& transformation,
279  const size_t materialId, const std::string& folder,
280  const LoaderProgress& callback) const
281 {
282  // Always create placeholder material since it is not guaranteed to exist
283  model.createMaterial(materialId, DEFAULT);
284 
285  if (materialId == NO_MATERIAL)
286  _createMaterials(model, aiScene, folder);
287 
288  std::unordered_map<size_t, size_t> nbVertices;
289  std::unordered_map<size_t, size_t> nbFaces;
290  std::unordered_map<size_t, size_t> indexOffsets;
291 
292  const auto trfm = aiScene->mRootNode->mTransformation;
293  Matrix4f matrix{trfm.a1, trfm.b1, trfm.c1, trfm.d1, trfm.a2, trfm.b2, trfm.c2, trfm.d2,
294  trfm.a3, trfm.b3, trfm.c3, trfm.d3, trfm.a4, trfm.b4, trfm.c4, trfm.d4};
295  matrix = matrix * transformation;
296 
297  for (size_t m = 0; m < aiScene->mNumMeshes; ++m)
298  {
299  auto mesh = aiScene->mMeshes[m];
300  auto id = (materialId != NO_MATERIAL ? materialId : mesh->mMaterialIndex);
301  nbVertices[id] += mesh->mNumVertices;
302  nbFaces[id] += mesh->mNumFaces;
303  }
304 
305  for (const auto& i : nbVertices)
306  {
307  auto& triangleMeshes = model.getTriangleMeshes()[i.first];
308  triangleMeshes.vertices.reserve(i.second);
309  triangleMeshes.normals.reserve(i.second);
310  triangleMeshes.textureCoordinates.reserve(i.second);
311  triangleMeshes.colors.reserve(i.second);
312  }
313  for (const auto& i : nbFaces)
314  {
315  auto& triangleMeshes = model.getTriangleMeshes()[i.first];
316  triangleMeshes.indices.reserve(i.second);
317  }
318 
319  for (size_t m = 0; m < aiScene->mNumMeshes; ++m)
320  {
321  auto mesh = aiScene->mMeshes[m];
322  auto id = (materialId != NO_MATERIAL ? materialId : mesh->mMaterialIndex);
323  auto& triangleMeshes = model.getTriangleMeshes()[id];
324 
325  for (size_t i = 0; i < mesh->mNumVertices; ++i)
326  {
327  const auto& v = mesh->mVertices[i];
328  const Vector3f transformedVertex = matrix * Vector4f(v.x, v.y, v.z, 1.f);
329  triangleMeshes.vertices.push_back(transformedVertex);
330  if (mesh->HasNormals())
331  {
332  const auto& n = mesh->mNormals[i];
333  const Vector4f normal = matrix * Vector4f(n.x, n.y, n.z, 0.f);
334  const Vector3f transformedNormal = {normal.x, normal.y, normal.z};
335  triangleMeshes.normals.push_back(transformedNormal);
336  }
337 
338  if (mesh->HasTextureCoords(0))
339  {
340  const auto& t = mesh->mTextureCoords[0][i];
341  triangleMeshes.textureCoordinates.push_back({t.x, t.y});
342  }
343 
344  if (mesh->HasVertexColors(0))
345  {
346  const auto& c = mesh->mColors[0][i];
347  triangleMeshes.colors.push_back({c.r, c.g, c.b, c.a});
348  }
349  }
350 
351  bool nonTriangulatedFaces = false;
352  const size_t offset = indexOffsets[id];
353  for (size_t f = 0; f < mesh->mNumFaces; ++f)
354  {
355  if (mesh->mFaces[f].mNumIndices == 3)
356  {
357  triangleMeshes.indices.push_back(Vector3ui(offset + mesh->mFaces[f].mIndices[0],
358  offset + mesh->mFaces[f].mIndices[1],
359  offset + mesh->mFaces[f].mIndices[2]));
360  }
361  else
362  nonTriangulatedFaces = true;
363  }
364  if (nonTriangulatedFaces)
365  CORE_WARN("Some faces are not triangulated and have been removed");
366  indexOffsets[id] += mesh->mNumVertices;
367 
368  callback.updateProgress("Post-processing...",
369  (LOADING_FRACTION + (((m + 1) / aiScene->mNumMeshes) * POST_LOADING_FRACTION)) /
371  }
372 
373  callback.updateProgress("Post-processing...", 1.f);
374 
375  const auto numVertices =
376  std::accumulate(nbVertices.begin(), nbVertices.end(), 0, [](auto value, auto& i) { return value + i.second; });
377  const auto numFaces =
378  std::accumulate(nbFaces.begin(), nbFaces.end(), 0, [](auto value, auto& i) { return value + i.second; });
379  ModelMetadata metadata{{"meshes", std::to_string(aiScene->mNumMeshes)},
380  {"vertices", std::to_string(numVertices)},
381  {"faces", std::to_string(numFaces)}};
382  return metadata;
383 }
384 
385 size_t MeshLoader::_getQuality(const GeometryQuality geometryQuality) const
386 {
387  switch (geometryQuality)
388  {
390  return aiProcess_Triangulate;
392  return aiProcessPreset_TargetRealtime_Fast;
394  default:
395  return aiProcess_GenSmoothNormals | aiProcess_Triangulate;
396  }
397 }
398 
399 ModelMetadata MeshLoader::importMesh(const std::string& fileName, const LoaderProgress& callback, Model& model,
400  const Matrix4f& transformation, const size_t defaultMaterialId,
401  const GeometryQuality geometryQuality) const
402 {
403  const fs::path file = fileName;
404 
405  auto importer = createImporter(callback, fileName);
406 
407  if (!importer.IsExtensionSupported(file.extension().c_str()))
408  {
409  std::stringstream msg;
410  msg << "File extension " << file.extension() << " is not supported";
411  throw std::runtime_error(msg.str());
412  }
413 
414  std::ifstream meshFile(fileName, std::ios::in);
415  if (!meshFile.good())
416  throw std::runtime_error("Could not open file " + fileName);
417  meshFile.close();
418 
419  const aiScene* aiScene = importer.ReadFile(fileName.c_str(), _getQuality(geometryQuality));
420 
421  if (!aiScene)
422  {
423  std::stringstream msg;
424  msg << "Error parsing mesh " << fileName.c_str() << ": " << importer.GetErrorString();
425  throw std::runtime_error(msg.str());
426  }
427 
428  if (!aiScene->HasMeshes())
429  throw std::runtime_error("Error finding meshes in scene");
430 
431  callback.updateProgress("Post-processing...", (LOADING_FRACTION) / TOTAL_PROGRESS);
432 
433  fs::path filepath = fileName;
434 
435  return _postLoad(aiScene, model, transformation, defaultMaterialId, filepath.parent_path().string(), callback);
436 }
437 
438 std::string MeshLoader::getName() const
439 {
440  return LOADER_NAME;
441 }
442 
443 std::vector<std::string> MeshLoader::getSupportedStorage() const
444 {
445  return getSupportedTypes();
446 }
447 
449 {
450  return _defaults;
451 }
452 } // namespace core
GeometryQuality getGeometryQuality() const
Get the geometry quality (low, medium or high)
void updateProgress(const std::string &message, const float fraction) const
Definition: Loader.h:58
Scene & _scene
Definition: Loader.h:126
bool isSupported(const std::string &storage, const std::string &extension) const final
Definition: MeshLoader.cpp:137
std::string getName() const final
Definition: MeshLoader.cpp:438
ModelMetadata importMesh(const std::string &fileName, const LoaderProgress &callback, Model &model, const Matrix4f &transformation, const size_t defaultMaterialId, const GeometryQuality geometryQuality) const
Definition: MeshLoader.cpp:399
MeshLoader(Scene &scene)
Definition: MeshLoader.cpp:123
PropertyMap getProperties() const final
Definition: MeshLoader.cpp:448
ModelDescriptorPtr importFromStorage(const std::string &storage, const LoaderProgress &callback, const PropertyMap &properties) const final
Definition: MeshLoader.cpp:143
ModelDescriptorPtr importFromBlob(Blob &&blob, const LoaderProgress &callback, const PropertyMap &properties) const final
Definition: MeshLoader.cpp:164
std::vector< std::string > getSupportedStorage() const final
Definition: MeshLoader.cpp:443
The abstract Model class holds the geometry attached to an asset of the scene (mesh,...
Definition: Model.h:469
PLATFORM_API MaterialPtr createMaterial(const size_t materialId, const std::string &name, const PropertyMap &properties={})
Factory method to create an engine-specific material.
Definition: Model.cpp:625
void setProperty(const Property &newProperty)
Definition: PropertyMap.h:307
T getProperty(const std::string &name, T valIfNotFound) const
Definition: PropertyMap.h:323
void merge(const PropertyMap &input)
Scene object This object contains collections of geometries, materials and light sources that are use...
Definition: Scene.h:43
virtual PLATFORM_API ModelPtr createModel() const =0
Factory method to create an engine-specific model.
Defines the translation, rotation and scale parameters to be applied to a scene asset.
void setRotationCenter(const Vector3d &value)
const std::string LOADER_NAME
Definition: AtlasLoader.cpp:42
std::string shortenString(const std::string &string, const size_t maxLength)
Definition: StringUtils.cpp:37
std::string enumToString(const EnumT v)
Definition: EnumUtils.h:64
GeometryQuality
Definition: Types.h:222
std::map< std::string, std::string > ModelMetadata
Definition: Types.h:104
glm::vec3 Vector3f
Definition: MathTypes.h:137
glm::vec< 3, uint32_t > Vector3ui
Definition: MathTypes.h:134
glm::vec4 Vector4f
Definition: MathTypes.h:138
TextureType
Definition: Types.h:244
std::shared_ptr< ModelDescriptor > ModelDescriptorPtr
Definition: Types.h:112
const float TOTAL_PROGRESS
glm::mat4 Matrix4f
Definition: MathTypes.h:125
#define CORE_DEBUG(__msg)
Definition: Logs.h:38
#define CORE_WARN(__msg)
Definition: Logs.h:32