Added OBJ loader

Former-commit-id: 1813ffeece4dbbe73e3d8ddf304c71205c670ffb
This commit is contained in:
Lynix 2013-03-29 23:10:04 +01:00
parent ce6902d37c
commit 425f964553
8 changed files with 1164 additions and 36 deletions

42
.gitignore vendored
View File

@ -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/

View File

@ -6,6 +6,7 @@
#include <Nazara/2D/2D.hpp>
#include <Nazara/3D/Config.hpp>
#include <Nazara/3D/Loaders/Mesh.hpp>
#include <Nazara/3D/Loaders/OBJ.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Renderer/Renderer.hpp>
@ -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");

View File

@ -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 <Nazara/Prerequesites.hpp>
void NzLoaders_OBJ_Register();
void NzLoaders_OBJ_Unregister();
#endif // NAZARA_LOADERS_OBJ_HPP

View File

@ -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 <Nazara/3D/Loaders/OBJ.hpp>
#include <Nazara/3D/Loaders/OBJ/MTLParser.hpp>
#include <Nazara/3D/Loaders/OBJ/OBJParser.hpp>
#include <Nazara/3D/Model.hpp>
#include <Nazara/Renderer/Material.hpp>
#include <Nazara/Utility/BufferMapper.hpp>
#include <Nazara/Utility/IndexMapper.hpp>
#include <Nazara/Utility/Mesh.hpp>
#include <Nazara/Utility/StaticMesh.hpp>
#include <limits>
#include <memory>
#include <unordered_map>
#include <Nazara/3D/Debug.hpp>
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<NzMesh> 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<unsigned int> 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<unsigned int> 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<int, std::unordered_map<int, std::unordered_map<int, unsigned int>>> 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<NzIndexBuffer> indexBuffer(new NzIndexBuffer(indices.size(), vertexCount > std::numeric_limits<nzUInt16>::max(), parameters.mesh.storage, nzBufferUsage_Static));
indexBuffer->SetPersistent(false);
std::unique_ptr<NzVertexBuffer> 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<NzVertexBuffer> vertexMapper(vertexBuffer.get(), nzBufferAccess_WriteOnly);
NzMeshVertex* meshVertices = static_cast<NzMeshVertex*>(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<NzStaticMesh> 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<NzMaterial> 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<NzTexture> 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<NzTexture> 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);
}

View File

@ -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 <Nazara/3D/Loaders/OBJ/MTLParser.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Utility/Config.hpp>
#include <cstdio>
#include <memory>
#include <Nazara/3D/Debug.hpp>
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);
}

View File

@ -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 <Nazara/Prerequesites.hpp>
#include <Nazara/Core/Color.hpp>
#include <Nazara/Core/InputStream.hpp>
#include <Nazara/Core/String.hpp>
#include <map>
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<NzString, Material> 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

View File

@ -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 <Nazara/3D/Loaders/OBJ/OBJParser.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Utility/Config.hpp>
#include <cstdio>
#include <map>
#include <memory>
#include <Nazara/3D/Debug.hpp>
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<NzString, std::map<NzString, std::vector<Face>>> meshes;
std::vector<Face>* 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<unsigned int>(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<unsigned int>(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<unsigned int>(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<NzString, unsigned int> 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);
}

View File

@ -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 <Nazara/Prerequesites.hpp>
#include <Nazara/Core/InputStream.hpp>
#include <Nazara/Core/String.hpp>
#include <Nazara/Math/Vector3.hpp>
#include <Nazara/Math/Vector4.hpp>
#include <vector>
class NzOBJParser
{
public:
struct FaceVertex
{
int normal;
int position;
int texCoord;
};
struct Face
{
std::vector<FaceVertex> vertices;
};
struct Mesh
{
std::vector<Face> 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<Mesh> m_meshes;
std::vector<NzString> m_materials;
std::vector<NzVector3f> m_normals;
std::vector<NzVector4f> m_positions;
std::vector<NzVector3f> 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