From 122e4a013cc62a61de9014fe65ae23e965f868dc Mon Sep 17 00:00:00 2001 From: Lynix Date: Tue, 24 Feb 2015 20:00:40 +0100 Subject: [PATCH] (OBJLoader) Improved code Former-commit-id: 78252bd96d1d8eeba596892a9599136f26312618 --- src/Nazara/Graphics/Loaders/OBJ/Loader.cpp | 280 ++++++++++++--------- 1 file changed, 155 insertions(+), 125 deletions(-) diff --git a/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp b/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp index bab3cc8dc..190517f72 100644 --- a/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp +++ b/src/Nazara/Graphics/Loaders/OBJ/Loader.cpp @@ -3,6 +3,8 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include +#include #include #include #include @@ -16,6 +18,8 @@ #include #include +///TODO: N'avoir qu'un seul VertexBuffer communs à tous les meshes + namespace { bool IsSupported(const NzString& extension) @@ -31,10 +35,97 @@ namespace return nzTernary_Unknown; } + bool LoadMaterials(NzModel* model, const NzString& filePath, const NzMaterialParams& parameters, const NzString* materials, const NzOBJParser::Mesh* meshes, unsigned int meshCount) + { + NzFile file(filePath); + if (!file.Open(NzFile::ReadOnly | NzFile::Text)) + { + NazaraError("Failed to open MTL file (" + file.GetPath() + ')'); + return false; + } + + NzMTLParser materialParser(file); + if (!materialParser.Parse()) + { + NazaraError("MTL parser failed"); + return false; + } + + std::unordered_map materialCache; + NzString baseDir = file.GetDirectory(); + for (unsigned int i = 0; i < meshCount; ++i) + { + const NzString& matName = materials[meshes[i].material]; + const NzMTLParser::Material* mtlMat = materialParser.GetMaterial(matName); + if (!mtlMat) + { + NazaraWarning("MTL has no material \"" + matName + '"'); + continue; + } + + auto it = materialCache.find(matName); + if (it == materialCache.end()) + { + NzMaterialRef material = NzMaterial::New(); + material->SetShader(parameters.shaderName); + + nzUInt8 alphaValue = static_cast(mtlMat->alpha*255.f); + + NzColor ambientColor(mtlMat->ambient); + NzColor diffuseColor(mtlMat->diffuse); + NzColor specularColor(mtlMat->specular); + ambientColor.a = alphaValue; + diffuseColor.a = alphaValue; + specularColor.a = alphaValue; + + material->SetAmbientColor(ambientColor); + material->SetDiffuseColor(diffuseColor); + material->SetSpecularColor(specularColor); + material->SetShininess(mtlMat->shininess); + + bool isTranslucent = (alphaValue != 255); + + if (parameters.loadAlphaMap && !mtlMat->alphaMap.IsEmpty()) + { + if (material->SetAlphaMap(baseDir + mtlMat->alphaMap)) + isTranslucent = true; // Une alpha map indique de la transparence + else + NazaraWarning("Failed to load alpha map (" + mtlMat->alphaMap + ')'); + } + + if (parameters.loadDiffuseMap && !mtlMat->diffuseMap.IsEmpty()) + { + if (!material->SetDiffuseMap(baseDir + mtlMat->diffuseMap)) + NazaraWarning("Failed to load diffuse map (" + mtlMat->diffuseMap + ')'); + } + + if (parameters.loadSpecularMap && !mtlMat->specularMap.IsEmpty()) + { + if (!material->SetSpecularMap(baseDir + mtlMat->specularMap)) + NazaraWarning("Failed to load specular map (" + mtlMat->specularMap + ')'); + } + + // Si nous avons une alpha map ou des couleurs transparentes, + // nous devons configurer le matériau pour accepter la transparence au mieux + if (isTranslucent) + { + // On paramètre le matériau pour accepter la transparence au mieux + material->Enable(nzRendererParameter_Blend, true); + material->Enable(nzRendererParameter_DepthWrite, false); + material->SetDstBlend(nzBlendFunc_InvSrcAlpha); + material->SetSrcBlend(nzBlendFunc_SrcAlpha); + } + + it = materialCache.emplace(matName, std::move(material)).first; + } + + model->SetMaterial(meshes[i].material, it->second); + } + } + bool Load(NzModel* model, NzInputStream& stream, const NzModelParameters& parameters) { NzOBJParser parser(stream); - if (!parser.Parse()) { NazaraError("OBJ parser failed"); @@ -53,10 +144,15 @@ namespace const NzVector3f* normals = parser.GetNormals(); const NzVector3f* texCoords = parser.GetTexCoords(); - std::vector faceIndices; - const NzOBJParser::Mesh* meshes = parser.GetMeshes(); unsigned int meshCount = parser.GetMeshCount(); + + NazaraAssert(materials != nullptr && positions != nullptr && normals != nullptr && + texCoords != nullptr && meshes != nullptr && meshCount > 0, + "Invalid OBJParser output"); + + // Un conteneur temporaire pour contenir les indices de face avant triangulation + std::vector faceIndices(3); // Comme il y aura au moins trois sommets for (unsigned int i = 0; i < meshCount; ++i) { unsigned int faceCount = meshes[i].faces.size(); @@ -66,8 +162,35 @@ namespace std::vector indices; indices.reserve(faceCount*3); // Pire cas si les faces sont des triangles - // Bien plus rapide qu'un vector (pour la recherche) - std::unordered_map>> vertices; + // Afin d'utiliser OBJParser::FaceVertex comme clé dans un unordered_map, + // nous devons fournir un foncteur de hash ainsi qu'un foncteur de comparaison + + // Hash + struct FaceVertexHasher + { + std::size_t operator()(const NzOBJParser::FaceVertex& o) const + { + std::size_t seed = 0; + NzHashCombine(seed, o.normal); + NzHashCombine(seed, o.position); + NzHashCombine(seed, o.texCoord); + + return seed; + } + }; + + // Comparaison + struct FaceVertexComparator + { + bool operator()(const NzOBJParser::FaceVertex& lhs, const NzOBJParser::FaceVertex& rhs) const + { + return lhs.normal == rhs.normal && + lhs.position == rhs.position && + lhs.texCoord == rhs.texCoord; + } + }; + + std::unordered_map vertices; unsigned int vertexCount = 0; for (unsigned int j = 0; j < faceCount; ++j) @@ -79,15 +202,11 @@ namespace { const NzOBJParser::FaceVertex& vertex = meshes[i].faces[j].vertices[k]; - auto& map = vertices[vertex.texCoord][vertex.normal]; - auto it = map.find(vertex.position); - if (it == map.end()) - { - faceIndices[k] = vertexCount; - map[vertex.position] = vertexCount++; - } - else - faceIndices[k] = it->second; + auto it = vertices.find(vertex); + if (it == vertices.end()) + it = vertices.emplace(vertex, vertexCount++).first; + + faceIndices[k] = it->second; } for (unsigned int k = 1; k < faceVertexCount-1; ++k) @@ -114,36 +233,29 @@ namespace bool hasTexCoords = true; NzBufferMapper vertexMapper(vertexBuffer, nzBufferAccess_WriteOnly); NzMeshVertex* meshVertices = static_cast(vertexMapper.GetPointer()); - for (auto& uvIt : vertices) + for (auto& vertexPair : vertices) { - for (auto& normalIt : uvIt.second) + const NzOBJParser::FaceVertex& vertexIndices = vertexPair.first; + unsigned int index = vertexPair.second; + + NzMeshVertex& vertex = meshVertices[index]; + + const NzVector4f& vec = positions[vertexIndices.position]; + vertex.position.Set(vec.x, vec.y, vec.z); + vertex.position *= parameters.mesh.scale/vec.w; + + if (vertexIndices.normal >= 0) + vertex.normal = normals[vertexIndices.normal]; + else + hasNormals = false; + + if (vertexIndices.texCoord >= 0) { - for (auto& positionIt : normalIt.second) - { - NzMeshVertex& vertex = meshVertices[positionIt.second]; - - const NzVector4f& vec = positions[positionIt.first]; - vertex.position.Set(vec.x, vec.y, vec.z); - vertex.position *= parameters.mesh.scale/vec.w; - - int index; - - index = normalIt.first; // Normale - if (index >= 0) - vertex.normal = normals[index]; - else - hasNormals = false; - - index = uvIt.first; // Coordonnées de texture - if (index >= 0) - { - const NzVector3f& uvw = texCoords[index]; - vertex.uv.Set(uvw.x, (parameters.mesh.flipUVs) ? 1.f - uvw.y : uvw.y); // Inversion des UVs si demandé - } - else - hasTexCoords = false; - } + const NzVector3f& uvw = texCoords[vertexIndices.texCoord]; + vertex.uv.Set(uvw.x, (parameters.mesh.flipUVs) ? 1.f - uvw.y : uvw.y); // Inversion des UVs si demandé } + else + hasTexCoords = false; } vertexMapper.Unmap(); @@ -184,90 +296,8 @@ namespace NzString mtlLib = parser.GetMtlLib(); if (parameters.loadMaterials && !mtlLib.IsEmpty()) { - NzFile file(stream.GetDirectory() + mtlLib); - if (file.Open(NzFile::ReadOnly | NzFile::Text)) - { - NzMTLParser materialParser(file); - if (materialParser.Parse()) - { - std::unordered_map materialCache; - NzString baseDir = file.GetDirectory(); - for (unsigned int i = 0; i < meshCount; ++i) - { - const NzString& matName = materials[meshes[i].material]; - const NzMTLParser::Material* mtlMat = materialParser.GetMaterial(matName); - if (mtlMat) - { - auto it = materialCache.find(matName); - if (it != materialCache.end()) - model->SetMaterial(meshes[i].material, it->second); - else - { - NzMaterialRef material = NzMaterial::New(); - material->SetShader(parameters.material.shaderName); - - nzUInt8 alphaValue = static_cast(mtlMat->alpha*255.f); - - NzColor ambientColor(mtlMat->ambient); - ambientColor.a = alphaValue; - - NzColor diffuseColor(mtlMat->diffuse); - diffuseColor.a = alphaValue; - - NzColor specularColor(mtlMat->specular); - specularColor.a = alphaValue; - - material->SetAmbientColor(ambientColor); - material->SetDiffuseColor(diffuseColor); - material->SetSpecularColor(specularColor); - material->SetShininess(mtlMat->shininess); - - bool hasAlphaMap = false;; - if (parameters.material.loadAlphaMap && !mtlMat->alphaMap.IsEmpty()) - { - if (material->SetAlphaMap(baseDir + mtlMat->alphaMap)) - hasAlphaMap = true; - else - NazaraWarning("Failed to load alpha map (" + mtlMat->alphaMap + ')'); - } - - if (parameters.material.loadDiffuseMap && !mtlMat->diffuseMap.IsEmpty()) - { - if (!material->SetDiffuseMap(baseDir + mtlMat->diffuseMap)) - NazaraWarning("Failed to load diffuse map (" + mtlMat->diffuseMap + ')'); - } - - if (parameters.material.loadSpecularMap && !mtlMat->specularMap.IsEmpty()) - { - if (!material->SetSpecularMap(baseDir + mtlMat->specularMap)) - NazaraWarning("Failed to load specular map (" + mtlMat->specularMap + ')'); - } - - // Si nous avons une alpha map ou des couleurs transparentes, - // nous devons configurer le matériau pour accepter la transparence au mieux - if (hasAlphaMap || alphaValue != 255) - { - // On paramètre le matériau pour accepter la transparence au mieux - material->Enable(nzRendererParameter_Blend, true); - material->Enable(nzRendererParameter_DepthWrite, false); - material->SetDstBlend(nzBlendFunc_InvSrcAlpha); - material->SetSrcBlend(nzBlendFunc_SrcAlpha); - } - - materialCache[matName] = material; - - model->SetMaterial(meshes[i].material, material); - } - } - else - NazaraWarning("MTL has no material \"" + matName + '"'); - } - } - else - NazaraWarning("MTL parser failed"); - } - else - NazaraWarning("Failed to open MTL file (" + file.GetPath() + ')'); + NzErrorFlags flags(nzErrorFlag_ThrowExceptionDisabled); + LoadMaterials(model, stream.GetDirectory() + mtlLib, parameters.material, materials, meshes, meshCount); } return true;