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>
34 #include <unordered_map>
43 #ifdef USE_CUSTOM_PLY_IMPORTER
44 #include "assimpImporters/PlyLoader.h"
46 #include "assimpImporters/ObjFileImporter.h"
52 const auto PROP_GEOMETRY_QUALITY =
"geometryQuality";
57 constexpr
float LOADING_FRACTION = 50.f;
58 constexpr
float POST_LOADING_FRACTION = 50.f;
60 class ProgressWatcher :
public Assimp::ProgressHandler
63 ProgressWatcher(
const LoaderProgress& callback,
const std::string& filename)
70 bool Update(
const float percentage)
final
73 _callback.updateProgress(_msg.str(), (std::min(1.f, percentage) * LOADING_FRACTION) /
TOTAL_PROGRESS);
78 const LoaderProgress& _callback;
79 std::function<void()> _cancelCheck;
80 std::stringstream _msg;
83 std::vector<std::string> getSupportedTypes()
85 std::set<std::string> types;
86 std::string extensions;
87 Assimp::Importer importer;
88 importer.GetExtensionList(extensions);
90 std::istringstream stream(extensions);
92 while (std::getline(stream, s,
';'))
94 auto pos = s.find_last_of(
".");
95 types.insert(pos == std::string::npos ? s : s.substr(pos + 1));
98 std::vector<std::string> output;
99 std::copy(types.begin(), types.end(), std::back_inserter(output));
103 Assimp::Importer createImporter(
const LoaderProgress& callback,
const std::string& filename)
105 Assimp::Importer importer;
106 importer.SetProgressHandler(
new ProgressWatcher(callback, filename));
110 #ifdef USE_CUSTOM_PLY_IMPORTER
112 importer.UnregisterLoader(importer.GetImporter(
"ply"));
113 importer.RegisterLoader(
new Assimp::PLYImporter());
117 importer.UnregisterLoader(importer.GetImporter(
"obj"));
118 importer.RegisterLoader(
new Assimp::ObjFileImporter());
133 enumNames<core::GeometryQuality>(),
134 {
"Geometry quality"}});
139 const auto types = getSupportedTypes();
140 return std::find(types.begin(), types.end(), extension) != types.end();
148 properties.
merge(inProperties);
150 const auto geometryQuality = stringToEnum<GeometryQuality>(
154 auto metadata =
importMesh(storage, callback, *model, {}, NO_MATERIAL, geometryQuality);
159 auto modelDescriptor = std::make_shared<ModelDescriptor>(std::move(model), storage, metadata);
160 modelDescriptor->setTransformation(transformation);
161 return modelDescriptor;
169 properties.
merge(propertiesTmp);
171 const auto geometryQuality = stringToEnum<GeometryQuality>(
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());
179 throw std::runtime_error(importer.GetErrorString());
181 if (!aiScene->HasMeshes())
182 throw std::runtime_error(
"No meshes found");
188 auto metadata = _postLoad(aiScene, *model,
Matrix4f(1), NO_MATERIAL,
"", callback);
193 auto modelDescriptor = std::make_shared<ModelDescriptor>(std::move(model), blob.name, metadata);
194 modelDescriptor->setTransformation(transformation);
195 return modelDescriptor;
198 void MeshLoader::_createMaterials(
Model& model,
const aiScene* aiScene,
const std::string& folder)
const
200 CORE_DEBUG(
"Loading " << aiScene->mNumMaterials <<
" materials");
202 for (
size_t m = 0; m < aiScene->mNumMaterials; ++m)
204 aiMaterial* aimaterial = aiScene->mMaterials[m];
206 aiString valueString;
207 aimaterial->Get(AI_MATKEY_NAME, valueString);
208 std::string name{valueString.C_Str()};
211 struct TextureTypeMapping
213 aiTextureType aiType;
217 const size_t NB_TEXTURE_TYPES = 6;
218 TextureTypeMapping textureTypeMapping[NB_TEXTURE_TYPES] = {{aiTextureType_DIFFUSE,
TextureType::diffuse},
225 for (
size_t textureType = 0; textureType < NB_TEXTURE_TYPES; ++textureType)
227 if (aimaterial->GetTextureCount(textureTypeMapping[textureType].aiType) > 0)
230 if (aimaterial->GetTexture(textureTypeMapping[textureType].aiType, 0, &path,
nullptr,
nullptr,
nullptr,
231 nullptr,
nullptr) == AI_SUCCESS)
233 const std::string fileName = folder +
"/" + path.data;
235 material->setTexture(fileName, textureTypeMapping[textureType].type);
240 aiColor4D value4f(0.f);
242 if (aimaterial->Get(AI_MATKEY_COLOR_DIFFUSE, value4f) == AI_SUCCESS)
243 material->setDiffuseColor({value4f.r, value4f.g, value4f.b});
246 if (aimaterial->Get(AI_MATKEY_OPACITY, value1f) != AI_SUCCESS)
247 material->setOpacity(value4f.a);
249 material->setOpacity(value1f);
252 if (aimaterial->Get(AI_MATKEY_REFLECTIVITY, value1f) == AI_SUCCESS)
253 material->setReflectionIndex(value1f);
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});
260 if (aimaterial->Get(AI_MATKEY_SHININESS, value1f) == AI_SUCCESS)
263 material->setSpecularColor({0.f, 0.f, 0.f});
264 material->setSpecularExponent(value1f);
267 value4f = aiColor4D(0.f);
268 if (aimaterial->Get(AI_MATKEY_COLOR_EMISSIVE, value4f) == AI_SUCCESS)
269 material->setEmission(value4f.r);
272 if (aimaterial->Get(AI_MATKEY_REFRACTI, value1f) == AI_SUCCESS)
274 material->setRefractionIndex(value1f);
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
283 model.createMaterial(materialId, DEFAULT);
285 if (materialId == NO_MATERIAL)
286 _createMaterials(model, aiScene, folder);
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;
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;
297 for (
size_t m = 0; m < aiScene->mNumMeshes; ++m)
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;
305 for (
const auto& i : nbVertices)
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);
313 for (
const auto& i : nbFaces)
315 auto& triangleMeshes = model.getTriangleMeshes()[i.first];
316 triangleMeshes.indices.reserve(i.second);
319 for (
size_t m = 0; m < aiScene->mNumMeshes; ++m)
321 auto mesh = aiScene->mMeshes[m];
322 auto id = (materialId != NO_MATERIAL ? materialId :
mesh->mMaterialIndex);
323 auto& triangleMeshes = model.getTriangleMeshes()[
id];
325 for (
size_t i = 0; i <
mesh->mNumVertices; ++i)
327 const auto& v =
mesh->mVertices[i];
329 triangleMeshes.vertices.push_back(transformedVertex);
330 if (
mesh->HasNormals())
332 const auto& n =
mesh->mNormals[i];
334 const Vector3f transformedNormal = {normal.x, normal.y, normal.z};
335 triangleMeshes.normals.push_back(transformedNormal);
338 if (
mesh->HasTextureCoords(0))
340 const auto& t =
mesh->mTextureCoords[0][i];
341 triangleMeshes.textureCoordinates.push_back({t.x, t.y});
344 if (
mesh->HasVertexColors(0))
346 const auto& c =
mesh->mColors[0][i];
347 triangleMeshes.colors.push_back({c.r, c.g, c.b, c.a});
351 bool nonTriangulatedFaces =
false;
352 const size_t offset = indexOffsets[
id];
353 for (
size_t f = 0; f <
mesh->mNumFaces; ++f)
355 if (
mesh->mFaces[f].mNumIndices == 3)
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]));
362 nonTriangulatedFaces =
true;
364 if (nonTriangulatedFaces)
365 CORE_WARN(
"Some faces are not triangulated and have been removed");
366 indexOffsets[
id] +=
mesh->mNumVertices;
368 callback.updateProgress(
"Post-processing...",
369 (LOADING_FRACTION + (((m + 1) / aiScene->mNumMeshes) * POST_LOADING_FRACTION)) /
373 callback.updateProgress(
"Post-processing...", 1.f);
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)}};
385 size_t MeshLoader::_getQuality(
const GeometryQuality geometryQuality)
const
387 switch (geometryQuality)
390 return aiProcess_Triangulate;
392 return aiProcessPreset_TargetRealtime_Fast;
395 return aiProcess_GenSmoothNormals | aiProcess_Triangulate;
400 const Matrix4f& transformation,
const size_t defaultMaterialId,
403 const fs::path file = fileName;
405 auto importer = createImporter(callback, fileName);
407 if (!importer.IsExtensionSupported(file.extension().c_str()))
409 std::stringstream msg;
410 msg <<
"File extension " << file.extension() <<
" is not supported";
411 throw std::runtime_error(msg.str());
414 std::ifstream meshFile(fileName, std::ios::in);
415 if (!meshFile.good())
416 throw std::runtime_error(
"Could not open file " + fileName);
419 const aiScene* aiScene = importer.ReadFile(fileName.c_str(), _getQuality(geometryQuality));
423 std::stringstream msg;
424 msg <<
"Error parsing mesh " << fileName.c_str() <<
": " << importer.GetErrorString();
425 throw std::runtime_error(msg.str());
428 if (!aiScene->HasMeshes())
429 throw std::runtime_error(
"Error finding meshes in scene");
433 fs::path filepath = fileName;
435 return _postLoad(aiScene, model, transformation, defaultMaterialId, filepath.parent_path().string(), callback);
445 return getSupportedTypes();
GeometryQuality getGeometryQuality() const
Get the geometry quality (low, medium or high)
void updateProgress(const std::string &message, const float fraction) const
bool isSupported(const std::string &storage, const std::string &extension) const final
std::string getName() const final
ModelMetadata importMesh(const std::string &fileName, const LoaderProgress &callback, Model &model, const Matrix4f &transformation, const size_t defaultMaterialId, const GeometryQuality geometryQuality) const
PropertyMap getProperties() const final
ModelDescriptorPtr importFromStorage(const std::string &storage, const LoaderProgress &callback, const PropertyMap &properties) const final
ModelDescriptorPtr importFromBlob(Blob &&blob, const LoaderProgress &callback, const PropertyMap &properties) const final
std::vector< std::string > getSupportedStorage() const final
The abstract Model class holds the geometry attached to an asset of the scene (mesh,...
PLATFORM_API MaterialPtr createMaterial(const size_t materialId, const std::string &name, const PropertyMap &properties={})
Factory method to create an engine-specific material.
void setProperty(const Property &newProperty)
T getProperty(const std::string &name, T valIfNotFound) const
void merge(const PropertyMap &input)
Scene object This object contains collections of geometries, materials and light sources that are use...
virtual PLATFORM_API ModelPtr createModel() const =0
Factory method to create an engine-specific model.
const std::string LOADER_NAME
std::string shortenString(const std::string &string, const size_t maxLength)
std::string enumToString(const EnumT v)
std::map< std::string, std::string > ModelMetadata
glm::vec< 3, uint32_t > Vector3ui
std::shared_ptr< ModelDescriptor > ModelDescriptorPtr
const float TOTAL_PROGRESS