From 425f964553b444a754759a66a3e6f7f7a02d282c Mon Sep 17 00:00:00 2001 From: Lynix Date: Fri, 29 Mar 2013 23:10:04 +0100 Subject: [PATCH] Added OBJ loader Former-commit-id: 1813ffeece4dbbe73e3d8ddf304c71205c670ffb --- .gitignore | 42 +-- src/Nazara/3D/3D.cpp | 5 + src/Nazara/3D/Loaders/OBJ.hpp | 15 + src/Nazara/3D/Loaders/OBJ/Loader.cpp | 271 ++++++++++++++++ src/Nazara/3D/Loaders/OBJ/MTLParser.cpp | 337 ++++++++++++++++++++ src/Nazara/3D/Loaders/OBJ/MTLParser.hpp | 60 ++++ src/Nazara/3D/Loaders/OBJ/OBJParser.cpp | 395 ++++++++++++++++++++++++ src/Nazara/3D/Loaders/OBJ/OBJParser.hpp | 75 +++++ 8 files changed, 1164 insertions(+), 36 deletions(-) create mode 100644 src/Nazara/3D/Loaders/OBJ.hpp create mode 100644 src/Nazara/3D/Loaders/OBJ/Loader.cpp create mode 100644 src/Nazara/3D/Loaders/OBJ/MTLParser.cpp create mode 100644 src/Nazara/3D/Loaders/OBJ/MTLParser.hpp create mode 100644 src/Nazara/3D/Loaders/OBJ/OBJParser.cpp create mode 100644 src/Nazara/3D/Loaders/OBJ/OBJParser.hpp diff --git a/.gitignore b/.gitignore index 693bd0756..1c0238076 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# Nazara build +examples/bin/*.exe +lib/libNazara*.a +lib/Nazara*.dll +lib/Nazara*.so + # Codeblocks *.cbp *.cbTemp @@ -9,12 +15,6 @@ # CodeLite *.project -# Nazara build -examples/bin/*.exe -lib/libNazara*.a -lib/Nazara*.dll -lib/Nazara*.so - # Compiled Object files *.slo *.lo @@ -108,33 +108,3 @@ DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html - -# Click-Once directory -publish - -# Publish Web Output -*.Publish.xml - -# Others -[Oo]bj -sql -TestResults -[Tt]est[Rr]esult* -*.Cache -ClientBin -[Ss]tyle[Cc]op.* -~$* -*.dbmdl - -*.[Pp]ublish.xml - -Generated_Code #added for RIA/Silverlight projects - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML - -# NuGet -packages/ \ No newline at end of file diff --git a/src/Nazara/3D/3D.cpp b/src/Nazara/3D/3D.cpp index d2ceb6be9..a0d40064a 100644 --- a/src/Nazara/3D/3D.cpp +++ b/src/Nazara/3D/3D.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,9 @@ bool Nz3D::Initialize() // Initialisation du module // Loaders + NzLoaders_OBJ_Register(); + + // Loader générique NzLoaders_Mesh_Register(); NazaraNotice("Initialized: 3D module"); @@ -56,6 +60,7 @@ void Nz3D::Uninitialize() // Loaders NzLoaders_Mesh_Unregister(); + NzLoaders_OBJ_Unregister(); NazaraNotice("Uninitialized: 3D module"); diff --git a/src/Nazara/3D/Loaders/OBJ.hpp b/src/Nazara/3D/Loaders/OBJ.hpp new file mode 100644 index 000000000..3e442c5bd --- /dev/null +++ b/src/Nazara/3D/Loaders/OBJ.hpp @@ -0,0 +1,15 @@ +// Copyright (C) 2013 Jérôme Leclercq +// This file is part of the "Nazara Engine - 3D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_LOADERS_OBJ_HPP +#define NAZARA_LOADERS_OBJ_HPP + +#include + +void NzLoaders_OBJ_Register(); +void NzLoaders_OBJ_Unregister(); + +#endif // NAZARA_LOADERS_OBJ_HPP diff --git a/src/Nazara/3D/Loaders/OBJ/Loader.cpp b/src/Nazara/3D/Loaders/OBJ/Loader.cpp new file mode 100644 index 000000000..1754bf40e --- /dev/null +++ b/src/Nazara/3D/Loaders/OBJ/Loader.cpp @@ -0,0 +1,271 @@ +// Copyright (C) 2013 Jérôme Leclercq +// This file is part of the "Nazara Engine - 3D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + bool IsSupported(const NzString& extension) + { + return (extension == "obj"); + } + + bool Check(NzInputStream& stream, const NzModelParameters& parameters) + { + NazaraUnused(stream); + NazaraUnused(parameters); + + return true; ///FIXME: Pas bon + } + + bool Load(NzModel* model, NzInputStream& stream, const NzModelParameters& parameters) + { + NzOBJParser parser(stream); + if (!parser.Parse()) + { + NazaraError("OBJ parser failed"); + return false; + } + + std::unique_ptr mesh(new NzMesh); + mesh->SetPersistent(false); + if (!mesh->CreateStatic()) // Ne devrait jamais échouer + { + NazaraInternalError("Failed to create mesh"); + return false; + } + + const NzString* materials = parser.GetMaterials(); + const NzVector4f* positions = parser.GetPositions(); + const NzVector3f* normals = parser.GetNormals(); + const NzVector3f* texCoords = parser.GetTexCoords(); + + std::vector faceIndices; + + const NzOBJParser::Mesh* meshes = parser.GetMeshes(); + unsigned int meshCount = parser.GetMeshCount(); + for (unsigned int i = 0; i < meshCount; ++i) + { + unsigned int faceCount = meshes[i].faces.size(); + + 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; + + unsigned int vertexCount = 0; + for (unsigned int j = 0; j < faceCount; ++j) + { + unsigned int faceVertexCount = meshes[i].faces[j].vertices.size(); + faceIndices.resize(faceVertexCount); + + for (unsigned int k = 0; k < faceVertexCount; ++k) + { + 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; + } + + for (unsigned int k = 1; k < faceVertexCount-1; ++k) + { + indices.push_back(faceIndices[0]); + indices.push_back(faceIndices[k]); + indices.push_back(faceIndices[k+1]); + } + } + + std::unique_ptr indexBuffer(new NzIndexBuffer(indices.size(), vertexCount > std::numeric_limits::max(), parameters.mesh.storage, nzBufferUsage_Static)); + indexBuffer->SetPersistent(false); + + std::unique_ptr vertexBuffer(new NzVertexBuffer(NzMesh::GetDeclaration(), vertexCount, parameters.mesh.storage, nzBufferUsage_Static)); + vertexBuffer->SetPersistent(false); + + // Remplissage des indices + NzIndexMapper indexMapper(indexBuffer.get(), nzBufferAccess_WriteOnly); + for (unsigned int j = 0; j < indices.size(); ++j) + indexMapper.Set(j, indices[j]); + + indexMapper.Unmap(); + + // Remplissage des vertices + bool hasNormals = true; + bool hasTexCoords = true; + NzBufferMapper vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly); + NzMeshVertex* meshVertices = static_cast(vertexMapper.GetPointer()); + for (auto uvIt : vertices) + { + for (auto normalIt : uvIt.second) + { + for (auto positionIt : normalIt.second) + { + NzMeshVertex& vertex = meshVertices[positionIt.second]; + + const NzVector4f& vec = positions[positionIt.first]; + vertex.position.Set(vec.x/vec.w, vec.y/vec.w, vec.z/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, uvw.y); + } + else + hasTexCoords = false; + } + } + } + + vertexMapper.Unmap(); + + std::unique_ptr subMesh(new NzStaticMesh(mesh.get())); + if (!subMesh->Create(vertexBuffer.get())) + { + NazaraError("Failed to create StaticMesh"); + continue; + } + vertexBuffer.release(); + + subMesh->SetIndexBuffer(indexBuffer.get()); + indexBuffer.release(); + + subMesh->GenerateAABB(); + subMesh->SetMaterialIndex(meshes[i].material); + subMesh->SetPrimitiveType(nzPrimitiveType_TriangleList); + + if (hasNormals && hasTexCoords) + subMesh->GenerateTangents(); + else if (hasTexCoords) + subMesh->GenerateNormalsAndTangents(); + else + subMesh->GenerateNormals(); + + if (mesh->AddSubMesh(meshes[i].name + '_' + materials[meshes[i].material], subMesh.get())) + subMesh.release(); + else + NazaraError("Failed to add SubMesh to Mesh"); + } + + mesh->SetMaterialCount(parser.GetMaterialCount()); + + model->SetMesh(mesh.get()); + mesh.release(); + + // On charge les matériaux si demandé + NzString mtlLib = parser.GetMtlLib(); + if (parameters.loadMaterials && !mtlLib.IsEmpty()) + { + NzString baseDir = stream.GetDirectory(); + NzFile file(baseDir + mtlLib); + if (file.Open(NzFile::ReadOnly | NzFile::Text)) + { + NzMTLParser materialParser(file); + if (materialParser.Parse()) + { + for (unsigned int i = 0; i < meshCount; ++i) + { + const NzString& matName = materials[meshes[i].material]; + const NzMTLParser::Material* mtlMat = materialParser.GetMaterial(matName); + if (mtlMat) + { + std::unique_ptr material(new NzMaterial); + material->SetPersistent(false); + + NzColor ambientColor(mtlMat->ambient); + ambientColor.a = mtlMat->alpha; + + NzColor diffuseColor(mtlMat->diffuse); + diffuseColor.a = mtlMat->alpha; + + NzColor specularColor(mtlMat->specular); + specularColor.a = mtlMat->alpha; + + material->SetAmbientColor(ambientColor); + material->SetDiffuseColor(diffuseColor); + material->SetSpecularColor(specularColor); + material->SetShininess(mtlMat->shininess); + + if (parameters.material.loadDiffuseMap && !mtlMat->diffuseMap.IsEmpty()) + { + std::unique_ptr diffuseMap(new NzTexture); + diffuseMap->SetPersistent(false); + + if (diffuseMap->LoadFromFile(baseDir + mtlMat->diffuseMap)) + { + material->SetDiffuseMap(diffuseMap.get()); + diffuseMap.release(); + } + else + NazaraWarning("Failed to load diffuse map"); + } + + if (parameters.material.loadSpecularMap && !mtlMat->specularMap.IsEmpty()) + { + std::unique_ptr specularMap(new NzTexture); + specularMap->SetPersistent(false); + + if (specularMap->LoadFromFile(baseDir + mtlMat->specularMap)) + { + material->SetSpecularMap(specularMap.get()); + specularMap.release(); + } + else + NazaraWarning("Failed to load specular map"); + } + + model->SetMaterial(meshes[i].material, material.get()); + material.release(); + } + else + NazaraWarning("MTL has no material \"" + matName + '"'); + } + } + else + NazaraWarning("MTL parser failed"); + } + else + NazaraWarning("Failed to open MTL file (" + file.GetPath() + ')'); + } + + return true; + } +} + +void NzLoaders_OBJ_Register() +{ + NzModelLoader::RegisterLoader(IsSupported, Check, Load); +} + +void NzLoaders_OBJ_Unregister() +{ + NzModelLoader::UnregisterLoader(IsSupported, Check, Load); +} diff --git a/src/Nazara/3D/Loaders/OBJ/MTLParser.cpp b/src/Nazara/3D/Loaders/OBJ/MTLParser.cpp new file mode 100644 index 000000000..d2c5f3f69 --- /dev/null +++ b/src/Nazara/3D/Loaders/OBJ/MTLParser.cpp @@ -0,0 +1,337 @@ +// Copyright (C) 2013 Jérôme Leclercq +// This file is part of the "Nazara Engine - 3D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +NzMTLParser::NzMTLParser(NzInputStream& stream) : +m_stream(stream), +m_streamFlags(stream.GetStreamOptions()) +{ + if ((m_streamFlags & nzStreamOption_Text) == 0) + m_stream.SetStreamOptions(m_streamFlags | nzStreamOption_Text); +} + +NzMTLParser::~NzMTLParser() +{ + if ((m_streamFlags & nzStreamOption_Text) == 0) + m_stream.SetStreamOptions(m_streamFlags); +} + +const NzMTLParser::Material* NzMTLParser::GetMaterial(const NzString& materialName) const +{ + auto it = m_materials.find(materialName); + if (it != m_materials.end()) + return &it->second; + else + return nullptr; +} + +bool NzMTLParser::Parse() +{ + m_keepLastLine = false; + m_lineCount = 0; + m_materials.clear(); + + Material* currentMaterial = nullptr; + + while (Advance(false)) + { + NzString keyword = m_currentLine.GetWord(0).ToLower(); + if (keyword == "ka") + { + float r, g, b; + if (std::sscanf(&m_currentLine[3], "%f %f %f", &r, &g, &b) == 3) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->ambient = NzColor(r*255.f, g*255.f, b*255.f); + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "kd") + { + float r, g, b; + if (std::sscanf(&m_currentLine[3], "%f %f %f", &r, &g, &b) == 3) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->diffuse = NzColor(r*255.f, g*255.f, b*255.f); + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "ks") + { + float r, g, b; + if (std::sscanf(&m_currentLine[3], "%f %f %f", &r, &g, &b) == 3) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->specular = NzColor(r*255.f, g*255.f, b*255.f); + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "ni") + { + float density; + if (std::sscanf(&m_currentLine[3], "%f", &density) == 1) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->refractionIndex = density; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "ns") + { + float coef; + if (std::sscanf(&m_currentLine[3], "%f", &coef) == 1) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->shininess = coef; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == 'd' || keyword == "tr") + { + float alpha; + if (std::sscanf(&m_currentLine[(keyword[0] == 'd') ? 2 : 3], "%f", &alpha) == 1) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->alpha = alpha; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "illum") + { + unsigned int model; + if (std::sscanf(&m_currentLine[6], "%u", &model) == 1) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->illumModel = model; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_ka") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->ambientMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_kd") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->diffuseMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_ks") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->specularMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_bump" || keyword == "bump") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->bumpMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_d") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->alphaMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_decal" || keyword == "decal") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->decalMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_disp" || keyword == "disp") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->displacementMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "map_refl" || keyword == "refl") + { + NzString map = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!map.IsEmpty()) + { + if (!currentMaterial) + currentMaterial = &m_materials["default"]; + + currentMaterial->reflectionMap = map; + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (keyword == "newmtl") + { + NzString materialName = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (!materialName.IsEmpty()) + currentMaterial = &m_materials[materialName]; + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + + return true; +} + +bool NzMTLParser::Advance(bool required) +{ + if (!m_keepLastLine) + { + do + { + if (m_stream.EndOfStream()) + { + if (required) + Error("Incomplete MTL file"); + + return false; + } + + m_lineCount++; + + m_currentLine = m_stream.ReadLine(); + m_currentLine = m_currentLine.SubstrTo("#"); // On ignore les commentaires + m_currentLine.Simplify(); // Pour un traitement plus simple + } + while (m_currentLine.IsEmpty()); + } + else + m_keepLastLine = false; + + return true; +} + +void NzMTLParser::Error(const NzString& message) +{ + NazaraError(message + " at line #" + NzString::Number(m_lineCount)); +} + +void NzMTLParser::Warning(const NzString& message) +{ + NazaraWarning(message + " at line #" + NzString::Number(m_lineCount)); +} + +void NzMTLParser::UnrecognizedLine(bool error) +{ + NzString message = "Unrecognized \"" + m_currentLine + '"'; + + if (error) + Error(message); + else + Warning(message); +} diff --git a/src/Nazara/3D/Loaders/OBJ/MTLParser.hpp b/src/Nazara/3D/Loaders/OBJ/MTLParser.hpp new file mode 100644 index 000000000..ea7011910 --- /dev/null +++ b/src/Nazara/3D/Loaders/OBJ/MTLParser.hpp @@ -0,0 +1,60 @@ +// Copyright (C) 2013 Jérôme Leclercq +// This file is part of the "Nazara Engine - 3D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_LOADERS_OBJ_MTLPARSER_HPP +#define NAZARA_LOADERS_OBJ_MTLPARSER_HPP + +#include +#include +#include +#include +#include + +class NzMTLParser +{ + public: + struct Material + { + NzColor ambient; + NzColor diffuse; + NzColor specular; + NzString alphaMap; + NzString ambientMap; + NzString bumpMap; + NzString decalMap; + NzString diffuseMap; + NzString displacementMap; + NzString reflectionMap; + NzString shininessMap; + NzString specularMap; + float alpha; + float refractionIndex; + float shininess; + unsigned int illumModel; + }; + + NzMTLParser(NzInputStream& stream$); + ~NzMTLParser(); + + const Material* GetMaterial(const NzString& materialName) const; + + bool Parse(); + + private: + bool Advance(bool required = true); + void Error(const NzString& message); + void Warning(const NzString& message); + void UnrecognizedLine(bool error = false); + + std::map m_materials; + NzInputStream& m_stream; + NzString m_currentLine; + bool m_keepLastLine; + unsigned int m_lineCount; + unsigned int m_streamFlags; +}; + +#endif // NAZARA_LOADERS_OBJ_MTLPARSER_HPP diff --git a/src/Nazara/3D/Loaders/OBJ/OBJParser.cpp b/src/Nazara/3D/Loaders/OBJ/OBJParser.cpp new file mode 100644 index 000000000..90a3f55df --- /dev/null +++ b/src/Nazara/3D/Loaders/OBJ/OBJParser.cpp @@ -0,0 +1,395 @@ +// Copyright (C) 2013 Jérôme Leclercq +// This file is part of the "Nazara Engine - 3D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include + +NzOBJParser::NzOBJParser(NzInputStream& stream) : +m_stream(stream), +m_streamFlags(stream.GetStreamOptions()) +{ + if ((m_streamFlags & nzStreamOption_Text) == 0) + m_stream.SetStreamOptions(m_streamFlags | nzStreamOption_Text); +} + +NzOBJParser::~NzOBJParser() +{ + if ((m_streamFlags & nzStreamOption_Text) == 0) + m_stream.SetStreamOptions(m_streamFlags); +} + +const NzString* NzOBJParser::GetMaterials() const +{ + return &m_materials[0]; +} + +unsigned int NzOBJParser::GetMaterialCount() const +{ + return m_materials.size(); +} + +const NzOBJParser::Mesh* NzOBJParser::GetMeshes() const +{ + return &m_meshes[0]; +} + +unsigned int NzOBJParser::GetMeshCount() const +{ + return m_meshes.size(); +} + +const NzString& NzOBJParser::GetMtlLib() const +{ + return m_mtlLib; +} + +const NzVector3f* NzOBJParser::GetNormals() const +{ + return &m_normals[0]; +} + +unsigned int NzOBJParser::GetNormalCount() const +{ + return m_normals.size(); +} + +const NzVector4f* NzOBJParser::GetPositions() const +{ + return &m_positions[0]; +} + +unsigned int NzOBJParser::GetPositionCount() const +{ + return m_positions.size(); +} + +const NzVector3f* NzOBJParser::GetTexCoords() const +{ + return &m_texCoords[0]; +} + +unsigned int NzOBJParser::GetTexCoordCount() const +{ + return m_texCoords.size(); +} + +bool NzOBJParser::Parse() +{ + NzString matName, meshName; + matName = meshName = "default"; + m_keepLastLine = false; + m_lineCount = 0; + m_meshes.clear(); + m_mtlLib.Clear(); + + m_normals.clear(); + m_positions.clear(); + m_texCoords.clear(); + + // Beaucoup de meshs font plus de 100 sommets, on prépare le terrain + m_normals.reserve(100); + m_positions.reserve(100); + m_texCoords.reserve(100); + + std::map>> meshes; + + std::vector* currentMesh = &meshes[meshName][matName]; + + while (Advance(false)) + { + switch (std::tolower(m_currentLine[0])) + { + case 'f': // Une face + { + if (m_currentLine.GetSize() < 7) // Le minimum syndical pour définir une face de trois sommets (f 1 2 3) + { + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + break; + } + + unsigned int vertexCount = m_currentLine.Count(' '); + if (vertexCount < 3) + { + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + break; + } + + Face face; + face.vertices.resize(vertexCount); + + bool error = false; + unsigned int pos = 2; + for (unsigned int i = 0; i < vertexCount; ++i) + { + int offset; + int& n = face.vertices[i].normal; + int& p = face.vertices[i].position; + int& t = face.vertices[i].texCoord; + + if (std::sscanf(&m_currentLine[pos], "%d/%d/%d%n", &p, &t, &n, &offset) == 3) + { + p--; + t--; + n--; + } + else if (std::sscanf(&m_currentLine[pos], "%d//%d%n", &p, &n, &offset) == 2) + { + p--; + n--; + t = -1; + } + else if (std::sscanf(&m_currentLine[pos], "%d/%d%n", &p, &t, &offset) == 2) + { + p--; + n = -1; + t--; + } + else if (std::sscanf(&m_currentLine[pos], "%d%n", &p, &offset) == 1) + { + p--; + n = -1; + t = -1; + } + else + { + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + error = true; + break; + } + + pos += offset; + + if (static_cast(p) >= m_positions.size()) + { + Error("Vertex index out of range " + NzString::Number(p) + " > " + NzString::Number(m_positions.size())); + break; + } + else if (n >= 0 && static_cast(n) >= m_normals.size()) + { + Error("Normal index out of range " + NzString::Number(n) + " > " + NzString::Number(m_normals.size())); + break; + } + else if (t >= 0 && static_cast(t) >= m_texCoords.size()) + { + Error("TexCoord index out of range " + NzString::Number(t) + " > " + NzString::Number(m_texCoords.size())); + break; + } + } + + if (!error) + currentMesh->push_back(std::move(face)); + + break; + } + + case 'm': + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + if (m_currentLine.GetWord(0).ToLower() != "mtllib") + UnrecognizedLine(); + #endif + + m_mtlLib = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + break; + + case 'g': + case 'o': + { + if (m_currentLine[1] != ' ') + { + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + break; + } + + NzString objectName = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (objectName.IsEmpty()) + { + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + break; + } + + meshName = objectName; + currentMesh = &meshes[meshName][matName]; + break; + } + + case 's': + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + if (m_currentLine[1] == ' ') + { + NzString param = m_currentLine.Substr(2); + if (param != '0' && param != '1' && param != "on" && param != "off") + UnrecognizedLine(); + } + else + UnrecognizedLine(); + #endif + break; + + case 'u': + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + if (m_currentLine.GetWord(0) != "usemtl") + UnrecognizedLine(); + #endif + + matName = m_currentLine.Substr(m_currentLine.GetWordPosition(1)); + if (matName.IsEmpty()) + { + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + break; + } + + currentMesh = &meshes[meshName][matName]; + break; + + case 'v': + { + NzString word = m_currentLine.GetWord(0).ToLower(); + if (word == 'v') + { + NzVector4f vertex(NzVector3f::Zero(), 1.f); + unsigned int paramCount = std::sscanf(&m_currentLine[2], "%f %f %f %f", &vertex.x, &vertex.y, &vertex.z, &vertex.w); + if (paramCount >= 3) + m_positions.push_back(vertex); + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (word == "vn") + { + NzVector3f normal(NzVector3f::Zero()); + unsigned int paramCount = std::sscanf(&m_currentLine[3], "%f %f %f", &normal.x, &normal.y, &normal.z); + if (paramCount == 3) + m_normals.push_back(normal); + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + else if (word == "vt") + { + NzVector3f uvw(NzVector3f::Zero()); + unsigned int paramCount = std::sscanf(&m_currentLine[3], "%f %f %f", &uvw.x, &uvw.y, &uvw.z); + if (paramCount >= 2) + m_texCoords.push_back(uvw); + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + } + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + else + UnrecognizedLine(); + #endif + + break; + } + + default: + #if NAZARA_UTILITY_STRICT_RESOURCE_PARSING + UnrecognizedLine(); + #endif + break; + } + } + + std::map materials; + unsigned int matCount = 0; + + for (auto meshIt : meshes) + { + for (auto matIt : meshIt.second) + { + if (!matIt.second.empty()) + { + Mesh mesh; + mesh.faces = std::move(matIt.second); + mesh.name = meshIt.first; + + auto it = materials.find(matIt.first); + if (it == materials.end()) + { + mesh.material = matCount; + materials[matIt.first] = matCount++; + } + else + mesh.material = it->second; + + m_meshes.push_back(std::move(mesh)); + } + } + } + + m_materials.resize(matCount); + for (auto it : materials) + m_materials[it.second] = it.first; + + return true; +} + +bool NzOBJParser::Advance(bool required) +{ + if (!m_keepLastLine) + { + do + { + if (m_stream.EndOfStream()) + { + if (required) + Error("Incomplete OBJ file"); + + return false; + } + + m_lineCount++; + + m_currentLine = m_stream.ReadLine(); + m_currentLine = m_currentLine.SubstrTo("#"); // On ignore les commentaires + m_currentLine.Simplify(); // Pour un traitement plus simple + } + while (m_currentLine.IsEmpty()); + } + else + m_keepLastLine = false; + + return true; +} + +void NzOBJParser::Error(const NzString& message) +{ + NazaraError(message + " at line #" + NzString::Number(m_lineCount)); +} + +void NzOBJParser::Warning(const NzString& message) +{ + NazaraWarning(message + " at line #" + NzString::Number(m_lineCount)); +} + +void NzOBJParser::UnrecognizedLine(bool error) +{ + NzString message = "Unrecognized \"" + m_currentLine + '"'; + + if (error) + Error(message); + else + Warning(message); +} diff --git a/src/Nazara/3D/Loaders/OBJ/OBJParser.hpp b/src/Nazara/3D/Loaders/OBJ/OBJParser.hpp new file mode 100644 index 000000000..03594571b --- /dev/null +++ b/src/Nazara/3D/Loaders/OBJ/OBJParser.hpp @@ -0,0 +1,75 @@ +// Copyright (C) 2013 Jérôme Leclercq +// This file is part of the "Nazara Engine - 3D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_LOADERS_OBJ_OBJPARSER_HPP +#define NAZARA_LOADERS_OBJ_OBJPARSER_HPP + +#include +#include +#include +#include +#include +#include + +class NzOBJParser +{ + public: + struct FaceVertex + { + int normal; + int position; + int texCoord; + }; + + struct Face + { + std::vector vertices; + }; + + struct Mesh + { + std::vector faces; + NzString name; + unsigned int material; + }; + + NzOBJParser(NzInputStream& stream$); + ~NzOBJParser(); + + const NzString* GetMaterials() const; + unsigned int GetMaterialCount() const; + const Mesh* GetMeshes() const; + unsigned int GetMeshCount() const; + const NzString& GetMtlLib() const; + const NzVector3f* GetNormals() const; + unsigned int GetNormalCount() const; + const NzVector4f* GetPositions() const; + unsigned int GetPositionCount() const; + const NzVector3f* GetTexCoords() const; + unsigned int GetTexCoordCount() const; + + bool Parse(); + + private: + bool Advance(bool required = true); + void Error(const NzString& message); + void Warning(const NzString& message); + void UnrecognizedLine(bool error = false); + + std::vector m_meshes; + std::vector m_materials; + std::vector m_normals; + std::vector m_positions; + std::vector m_texCoords; + NzInputStream& m_stream; + NzString m_currentLine; + NzString m_mtlLib; + bool m_keepLastLine; + unsigned int m_lineCount; + unsigned int m_streamFlags; +}; + +#endif // NAZARA_LOADERS_OBJ_OBJPARSER_HPP