Make mesh able to carry material informations
- Move OBJ Loader to Utility module, where it belongs - Change Mesh material informations from a path to a parameterlist - Improve Mesh code Former-commit-id: 3c18901133fa5ac8281269822f6e2650ddcefd2d
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <Nazara/Math/Algorithm.hpp>
|
||||
#include <Nazara/Math/Quaternion.hpp>
|
||||
#include <Nazara/Utility/BufferMapper.hpp>
|
||||
#include <Nazara/Utility/MaterialData.hpp>
|
||||
#include <Nazara/Utility/Mesh.hpp>
|
||||
#include <Nazara/Utility/StaticMesh.hpp>
|
||||
#include <Nazara/Utility/Formats/MD2Constants.hpp>
|
||||
@@ -99,7 +100,11 @@ namespace Nz
|
||||
for (unsigned int i = 0; i < header.num_skins; ++i)
|
||||
{
|
||||
stream.Read(skin, 68*sizeof(char));
|
||||
mesh->SetMaterial(i, baseDir + skin);
|
||||
|
||||
ParameterList matData;
|
||||
matData.SetParameter(MaterialData::FilePath, baseDir + skin);
|
||||
|
||||
mesh->SetMaterialData(i, std::move(matData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Nazara/Utility/Formats/MD5MeshLoader.hpp>
|
||||
#include <Nazara/Utility/IndexIterator.hpp>
|
||||
#include <Nazara/Utility/IndexMapper.hpp>
|
||||
#include <Nazara/Utility/MaterialData.hpp>
|
||||
#include <Nazara/Utility/SkeletalMesh.hpp>
|
||||
#include <Nazara/Utility/StaticMesh.hpp>
|
||||
#include <Nazara/Utility/Formats/MD5MeshParser.hpp>
|
||||
@@ -189,7 +190,10 @@ namespace Nz
|
||||
vertexMapper.Unmap();
|
||||
|
||||
// Material
|
||||
mesh->SetMaterial(i, baseDir + md5Mesh.shader);
|
||||
ParameterList matData;
|
||||
matData.SetParameter(MaterialData::FilePath, baseDir + md5Mesh.shader);
|
||||
|
||||
mesh->SetMaterialData(i, std::move(matData));
|
||||
|
||||
// Submesh
|
||||
SkeletalMeshRef subMesh = SkeletalMesh::New(mesh);
|
||||
@@ -285,7 +289,10 @@ namespace Nz
|
||||
mesh->AddSubMesh(subMesh);
|
||||
|
||||
// Material
|
||||
mesh->SetMaterial(i, baseDir + md5Mesh.shader);
|
||||
ParameterList matData;
|
||||
matData.SetParameter(MaterialData::FilePath, baseDir + md5Mesh.shader);
|
||||
|
||||
mesh->SetMaterialData(i, std::move(matData));
|
||||
}
|
||||
|
||||
if (parameters.center)
|
||||
|
||||
310
src/Nazara/Utility/Formats/OBJLoader.cpp
Normal file
310
src/Nazara/Utility/Formats/OBJLoader.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
// Copyright (C) 2016 Jérôme Leclercq
|
||||
// This file is part of the "Nazara Engine - Utility module"
|
||||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/Utility/Formats/OBJLoader.hpp>
|
||||
#include <Nazara/Core/Algorithm.hpp>
|
||||
#include <Nazara/Core/ErrorFlags.hpp>
|
||||
#include <Nazara/Utility/BufferMapper.hpp>
|
||||
#include <Nazara/Utility/IndexMapper.hpp>
|
||||
#include <Nazara/Utility/MaterialData.hpp>
|
||||
#include <Nazara/Utility/Mesh.hpp>
|
||||
#include <Nazara/Utility/StaticMesh.hpp>
|
||||
#include <Nazara/Utility/Formats/MTLParser.hpp>
|
||||
#include <Nazara/Utility/Formats/OBJParser.hpp>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <Nazara/Utility/Debug.hpp>
|
||||
|
||||
///TODO: N'avoir qu'un seul VertexBuffer communs à tous les meshes
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool IsSupported(const String& extension)
|
||||
{
|
||||
return (extension == "obj");
|
||||
}
|
||||
|
||||
Ternary Check(Stream& stream, const MeshParams& parameters)
|
||||
{
|
||||
NazaraUnused(stream);
|
||||
|
||||
bool skip;
|
||||
if (parameters.custom.GetBooleanParameter("SkipNativeOBJLoader", &skip) && skip)
|
||||
return Ternary_False;
|
||||
|
||||
return Ternary_Unknown;
|
||||
}
|
||||
|
||||
bool ParseMTL(Mesh* mesh, const String& filePath, const String* materials, const OBJParser::Mesh* meshes, unsigned int meshCount)
|
||||
{
|
||||
File file(filePath);
|
||||
if (!file.Open(OpenMode_ReadOnly | OpenMode_Text))
|
||||
{
|
||||
NazaraError("Failed to open MTL file (" + file.GetPath() + ')');
|
||||
return false;
|
||||
}
|
||||
|
||||
MTLParser materialParser(file);
|
||||
if (!materialParser.Parse())
|
||||
{
|
||||
NazaraError("MTL parser failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_map<String, ParameterList> materialCache;
|
||||
String baseDir = file.GetDirectory();
|
||||
for (unsigned int i = 0; i < meshCount; ++i)
|
||||
{
|
||||
const String& matName = materials[meshes[i].material];
|
||||
const MTLParser::Material* mtlMat = materialParser.GetMaterial(matName);
|
||||
if (!mtlMat)
|
||||
{
|
||||
NazaraWarning("MTL has no material \"" + matName + '"');
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = materialCache.find(matName);
|
||||
if (it == materialCache.end())
|
||||
{
|
||||
ParameterList data;
|
||||
|
||||
data.SetParameter(MaterialData::CustomDefined);
|
||||
|
||||
UInt8 alphaValue = static_cast<UInt8>(mtlMat->alpha*255.f);
|
||||
|
||||
Color ambientColor(mtlMat->ambient);
|
||||
Color diffuseColor(mtlMat->diffuse);
|
||||
Color specularColor(mtlMat->specular);
|
||||
ambientColor.a = alphaValue;
|
||||
diffuseColor.a = alphaValue;
|
||||
specularColor.a = alphaValue;
|
||||
|
||||
data.SetParameter(MaterialData::AmbientColor, ambientColor);
|
||||
data.SetParameter(MaterialData::DiffuseColor, diffuseColor);
|
||||
data.SetParameter(MaterialData::Shininess, mtlMat->shininess);
|
||||
data.SetParameter(MaterialData::SpecularColor, specularColor);
|
||||
|
||||
if (!mtlMat->alphaMap.IsEmpty())
|
||||
data.SetParameter(MaterialData::AlphaTexturePath, baseDir + mtlMat->alphaMap);
|
||||
|
||||
if (!mtlMat->diffuseMap.IsEmpty())
|
||||
data.SetParameter(MaterialData::DiffuseTexturePath, baseDir + mtlMat->diffuseMap);
|
||||
|
||||
if (!mtlMat->specularMap.IsEmpty())
|
||||
data.SetParameter(MaterialData::SpecularTexturePath, baseDir + mtlMat->specularMap);
|
||||
|
||||
// If we either have an alpha value or an alpha map, let's configure the material for transparency
|
||||
if (alphaValue != 255 || !mtlMat->alphaMap.IsEmpty())
|
||||
{
|
||||
// Some default settings
|
||||
data.SetParameter(MaterialData::Blending, true);
|
||||
data.SetParameter(MaterialData::DepthWrite, true);
|
||||
data.SetParameter(MaterialData::DstBlend, static_cast<int>(BlendFunc_InvSrcAlpha));
|
||||
data.SetParameter(MaterialData::SrcBlend, static_cast<int>(BlendFunc_SrcAlpha));
|
||||
}
|
||||
|
||||
it = materialCache.emplace(matName, std::move(data)).first;
|
||||
}
|
||||
|
||||
mesh->SetMaterialData(meshes[i].material, it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Load(Mesh* mesh, Stream& stream, const MeshParams& parameters)
|
||||
{
|
||||
int reservedVertexCount;
|
||||
if (!parameters.custom.GetIntegerParameter("NativeOBJLoader_VertexCount", &reservedVertexCount))
|
||||
reservedVertexCount = 100;
|
||||
|
||||
OBJParser parser(stream);
|
||||
if (!parser.Parse(reservedVertexCount))
|
||||
{
|
||||
NazaraError("OBJ parser failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
mesh->CreateStatic();
|
||||
|
||||
const String* materials = parser.GetMaterials();
|
||||
const Vector4f* positions = parser.GetPositions();
|
||||
const Vector3f* normals = parser.GetNormals();
|
||||
const Vector3f* texCoords = parser.GetTexCoords();
|
||||
|
||||
const OBJParser::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<unsigned int> 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();
|
||||
if (faceCount == 0)
|
||||
continue;
|
||||
|
||||
std::vector<unsigned int> indices;
|
||||
indices.reserve(faceCount*3); // Pire cas si les faces sont des triangles
|
||||
|
||||
// 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 OBJParser::FaceVertex& o) const
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
HashCombine(seed, o.normal);
|
||||
HashCombine(seed, o.position);
|
||||
HashCombine(seed, o.texCoord);
|
||||
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
// Comparaison
|
||||
struct FaceVertexComparator
|
||||
{
|
||||
bool operator()(const OBJParser::FaceVertex& lhs, const OBJParser::FaceVertex& rhs) const
|
||||
{
|
||||
return lhs.normal == rhs.normal &&
|
||||
lhs.position == rhs.position &&
|
||||
lhs.texCoord == rhs.texCoord;
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<OBJParser::FaceVertex, unsigned int, FaceVertexHasher, FaceVertexComparator> 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 OBJParser::FaceVertex& vertex = meshes[i].faces[j].vertices[k];
|
||||
|
||||
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)
|
||||
{
|
||||
indices.push_back(faceIndices[0]);
|
||||
indices.push_back(faceIndices[k]);
|
||||
indices.push_back(faceIndices[k+1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Création des buffers
|
||||
IndexBufferRef indexBuffer = IndexBuffer::New(vertexCount > std::numeric_limits<UInt16>::max(), indices.size(), parameters.storage, BufferUsage_Static);
|
||||
VertexBufferRef vertexBuffer = VertexBuffer::New(VertexDeclaration::Get(VertexLayout_XYZ_Normal_UV_Tangent), vertexCount, parameters.storage, BufferUsage_Static);
|
||||
|
||||
// Remplissage des indices
|
||||
IndexMapper indexMapper(indexBuffer, BufferAccess_WriteOnly);
|
||||
for (unsigned int j = 0; j < indices.size(); ++j)
|
||||
indexMapper.Set(j, indices[j]);
|
||||
|
||||
indexMapper.Unmap(); // Pour laisser les autres tâches affecter l'index buffer
|
||||
|
||||
// Remplissage des vertices
|
||||
bool hasNormals = true;
|
||||
bool hasTexCoords = true;
|
||||
BufferMapper<VertexBuffer> vertexMapper(vertexBuffer, BufferAccess_WriteOnly);
|
||||
MeshVertex* meshVertices = static_cast<MeshVertex*>(vertexMapper.GetPointer());
|
||||
for (auto& vertexPair : vertices)
|
||||
{
|
||||
const OBJParser::FaceVertex& vertexIndices = vertexPair.first;
|
||||
unsigned int index = vertexPair.second;
|
||||
|
||||
MeshVertex& vertex = meshVertices[index];
|
||||
|
||||
const Vector4f& vec = positions[vertexIndices.position];
|
||||
vertex.position.Set(vec.x, vec.y, vec.z);
|
||||
vertex.position *= parameters.scale/vec.w;
|
||||
|
||||
if (vertexIndices.normal >= 0)
|
||||
vertex.normal = normals[vertexIndices.normal];
|
||||
else
|
||||
hasNormals = false;
|
||||
|
||||
if (vertexIndices.texCoord >= 0)
|
||||
{
|
||||
const Vector3f& uvw = texCoords[vertexIndices.texCoord];
|
||||
vertex.uv.Set(uvw.x, (parameters.flipUVs) ? 1.f - uvw.y : uvw.y); // Inversion des UVs si demandé
|
||||
}
|
||||
else
|
||||
hasTexCoords = false;
|
||||
}
|
||||
|
||||
vertexMapper.Unmap();
|
||||
|
||||
StaticMeshRef subMesh = StaticMesh::New(mesh);
|
||||
if (!subMesh->Create(vertexBuffer))
|
||||
{
|
||||
NazaraError("Failed to create StaticMesh");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameters.optimizeIndexBuffers)
|
||||
indexBuffer->Optimize();
|
||||
|
||||
subMesh->GenerateAABB();
|
||||
subMesh->SetIndexBuffer(indexBuffer);
|
||||
subMesh->SetMaterialIndex(meshes[i].material);
|
||||
subMesh->SetPrimitiveMode(PrimitiveMode_TriangleList);
|
||||
|
||||
// Ce que nous pouvons générer dépend des données à disposition (par exemple les tangentes nécessitent des coordonnées de texture)
|
||||
if (hasNormals && hasTexCoords)
|
||||
subMesh->GenerateTangents();
|
||||
else if (hasTexCoords)
|
||||
subMesh->GenerateNormalsAndTangents();
|
||||
else
|
||||
subMesh->GenerateNormals();
|
||||
|
||||
mesh->AddSubMesh(meshes[i].name + '_' + materials[meshes[i].material], subMesh);
|
||||
}
|
||||
mesh->SetMaterialCount(parser.GetMaterialCount());
|
||||
|
||||
if (parameters.center)
|
||||
mesh->Recenter();
|
||||
|
||||
// On charge les matériaux si demandé
|
||||
String mtlLib = parser.GetMtlLib();
|
||||
if (!mtlLib.IsEmpty())
|
||||
{
|
||||
ErrorFlags flags(ErrorFlag_ThrowExceptionDisabled);
|
||||
ParseMTL(mesh, stream.GetDirectory() + mtlLib, materials, meshes, meshCount);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Loaders
|
||||
{
|
||||
void RegisterOBJ()
|
||||
{
|
||||
MeshLoader::RegisterLoader(IsSupported, Check, Load);
|
||||
}
|
||||
|
||||
void UnregisterOBJ()
|
||||
{
|
||||
MeshLoader::UnregisterLoader(IsSupported, Check, Load);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Nazara/Utility/Formats/OBJLoader.hpp
Normal file
21
src/Nazara/Utility/Formats/OBJLoader.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2015 Jérôme Leclercq
|
||||
// This file is part of the "Nazara Engine - Graphics 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>
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
namespace Loaders
|
||||
{
|
||||
void RegisterOBJ();
|
||||
void UnregisterOBJ();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // NAZARA_LOADERS_OBJ_HPP
|
||||
@@ -52,11 +52,11 @@ namespace Nz
|
||||
{
|
||||
MeshImpl()
|
||||
{
|
||||
materials.resize(1); // Un matériau par défaut
|
||||
materialData.resize(1); // Un matériau par défaut
|
||||
}
|
||||
|
||||
std::unordered_map<String, unsigned int> subMeshMap;
|
||||
std::vector<String> materials;
|
||||
std::vector<ParameterList> materialData;
|
||||
std::vector<SubMeshRef> subMeshes;
|
||||
AnimationType animationType;
|
||||
Boxf aabb;
|
||||
@@ -75,93 +75,36 @@ namespace Nz
|
||||
|
||||
void Mesh::AddSubMesh(SubMesh* subMesh)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(subMesh, "Invalid submesh");
|
||||
NazaraAssert(subMesh->GetAnimationType() == m_impl->animationType, "Submesh animation type doesn't match mesh animation type");
|
||||
|
||||
if (!subMesh)
|
||||
{
|
||||
NazaraError("Invalid submesh");
|
||||
return;
|
||||
}
|
||||
|
||||
if (subMesh->GetAnimationType() != m_impl->animationType)
|
||||
{
|
||||
NazaraError("Submesh animation type must match mesh animation type");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_impl->aabbUpdated = false; // On invalide l'AABB
|
||||
m_impl->subMeshes.push_back(subMesh);
|
||||
|
||||
InvalidateAABB();
|
||||
}
|
||||
|
||||
void Mesh::AddSubMesh(const String& identifier, SubMesh* subMesh)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
|
||||
if (identifier.IsEmpty())
|
||||
{
|
||||
NazaraError("Identifier is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = m_impl->subMeshMap.find(identifier);
|
||||
if (it != m_impl->subMeshMap.end())
|
||||
{
|
||||
NazaraError("SubMesh identifier \"" + identifier + "\" is already used");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!subMesh)
|
||||
{
|
||||
NazaraError("Invalid submesh");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_impl->animationType != subMesh->GetAnimationType())
|
||||
{
|
||||
NazaraError("Submesh animation type must match mesh animation type");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(!identifier.IsEmpty(), "Identifier is empty");
|
||||
NazaraAssert(m_impl->subMeshMap.find(identifier) == m_impl->subMeshMap.end(), "SubMesh identifier \"" + identifier + "\" is already in use");
|
||||
NazaraAssert(subMesh, "Invalid submesh");
|
||||
NazaraAssert(subMesh->GetAnimationType() == m_impl->animationType, "Submesh animation type doesn't match mesh animation type");
|
||||
|
||||
int index = m_impl->subMeshes.size();
|
||||
|
||||
m_impl->aabbUpdated = false; // On invalide l'AABB
|
||||
m_impl->subMeshes.push_back(subMesh);
|
||||
m_impl->subMeshMap[identifier] = index;
|
||||
|
||||
InvalidateAABB();
|
||||
}
|
||||
|
||||
SubMesh* Mesh::BuildSubMesh(const Primitive& primitive, const MeshParams& params)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_impl->animationType != AnimationType_Static)
|
||||
{
|
||||
NazaraError("Mesh must be static");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!params.IsValid())
|
||||
{
|
||||
NazaraError("Parameters must be valid");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(m_impl->animationType == AnimationType_Static, "Submesh building only works for static meshes");
|
||||
NazaraAssert(params.IsValid(), "Invalid parameters");
|
||||
|
||||
Boxf aabb;
|
||||
IndexBufferRef indexBuffer;
|
||||
@@ -333,17 +276,7 @@ namespace Nz
|
||||
|
||||
void Mesh::BuildSubMeshes(const PrimitiveList& list, const MeshParams& params)
|
||||
{
|
||||
unsigned int primitiveCount = list.GetSize();
|
||||
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (primitiveCount == 0)
|
||||
{
|
||||
NazaraError("PrimitiveList must have at least one primitive");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (unsigned int i = 0; i < primitiveCount; ++i)
|
||||
for (unsigned int i = 0; i < list.GetSize(); ++i)
|
||||
BuildSubMesh(list.GetPrimitive(i), params);
|
||||
}
|
||||
|
||||
@@ -351,18 +284,17 @@ namespace Nz
|
||||
{
|
||||
Destroy();
|
||||
|
||||
m_impl = new MeshImpl;
|
||||
m_impl->animationType = AnimationType_Skeletal;
|
||||
m_impl->jointCount = jointCount;
|
||||
if (!m_impl->skeleton.Create(jointCount))
|
||||
std::unique_ptr<MeshImpl> impl(new MeshImpl);
|
||||
impl->animationType = AnimationType_Skeletal;
|
||||
impl->jointCount = jointCount;
|
||||
if (!impl->skeleton.Create(jointCount))
|
||||
{
|
||||
delete m_impl;
|
||||
m_impl = nullptr;
|
||||
|
||||
NazaraError("Failed to create skeleton");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_impl = impl.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -389,13 +321,7 @@ namespace Nz
|
||||
|
||||
void Mesh::GenerateNormals()
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
subMesh->GenerateNormals();
|
||||
@@ -403,13 +329,7 @@ namespace Nz
|
||||
|
||||
void Mesh::GenerateNormalsAndTangents()
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
subMesh->GenerateNormalsAndTangents();
|
||||
@@ -417,13 +337,7 @@ namespace Nz
|
||||
|
||||
void Mesh::GenerateTangents()
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
subMesh->GenerateTangents();
|
||||
@@ -431,15 +345,7 @@ namespace Nz
|
||||
|
||||
const Boxf& Mesh::GetAABB() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
|
||||
static Boxf dummy;
|
||||
return dummy;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
if (!m_impl->aabbUpdated)
|
||||
{
|
||||
@@ -461,248 +367,121 @@ namespace Nz
|
||||
|
||||
String Mesh::GetAnimation() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return String();
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return m_impl->animationPath;
|
||||
}
|
||||
|
||||
AnimationType Mesh::GetAnimationType() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return AnimationType_Static;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return m_impl->animationType;
|
||||
}
|
||||
|
||||
unsigned int Mesh::GetJointCount() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_impl->animationType != AnimationType_Skeletal)
|
||||
{
|
||||
NazaraError("Mesh's animation type is not skeletal");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(m_impl->animationType == AnimationType_Skeletal, "Mesh is not skeletal");
|
||||
|
||||
return m_impl->jointCount;
|
||||
}
|
||||
|
||||
String Mesh::GetMaterial(unsigned int index) const
|
||||
ParameterList& Mesh::GetMaterialData(unsigned int index)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return String();
|
||||
}
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(index < m_impl->materialData.size(), "Material index out of range");
|
||||
|
||||
if (index >= m_impl->materials.size())
|
||||
{
|
||||
NazaraError("Material index out of range (" + String::Number(index) + " >= " + String::Number(m_impl->materials.size()) + ')');
|
||||
return String();
|
||||
}
|
||||
#endif
|
||||
return m_impl->materialData[index];
|
||||
}
|
||||
|
||||
return m_impl->materials[index];
|
||||
const ParameterList& Mesh::GetMaterialData(unsigned int index) const
|
||||
{
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(index < m_impl->materialData.size(), "Material index out of range");
|
||||
|
||||
return m_impl->materialData[index];
|
||||
}
|
||||
|
||||
unsigned int Mesh::GetMaterialCount() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return m_impl->materials.size();
|
||||
return m_impl->materialData.size();
|
||||
}
|
||||
|
||||
Skeleton* Mesh::GetSkeleton()
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Animation not created");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_impl->animationType != AnimationType_Skeletal)
|
||||
{
|
||||
NazaraError("Mesh's animation type is not skeletal");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(m_impl->animationType == AnimationType_Skeletal, "Mesh is not skeletal");
|
||||
|
||||
return &m_impl->skeleton;
|
||||
}
|
||||
|
||||
const Skeleton* Mesh::GetSkeleton() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Animation not created");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_impl->animationType != AnimationType_Skeletal)
|
||||
{
|
||||
NazaraError("Mesh's animation type is not skeletal");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(m_impl->animationType == AnimationType_Skeletal, "Mesh is not skeletal");
|
||||
|
||||
return &m_impl->skeleton;
|
||||
}
|
||||
|
||||
SubMesh* Mesh::GetSubMesh(const String& identifier)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
auto it = m_impl->subMeshMap.find(identifier);
|
||||
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (it == m_impl->subMeshMap.end())
|
||||
{
|
||||
NazaraError("SubMesh not found");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(it != m_impl->subMeshMap.end(), "SubMesh " + identifier + " not found");
|
||||
|
||||
return m_impl->subMeshes[it->second];
|
||||
}
|
||||
|
||||
SubMesh* Mesh::GetSubMesh(unsigned int index)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (index >= m_impl->subMeshes.size())
|
||||
{
|
||||
NazaraError("SubMesh index out of range (" + String::Number(index) + " >= " + String::Number(m_impl->subMeshes.size()) + ')');
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(index < m_impl->subMeshes.size(), "Submesh index out of range");
|
||||
|
||||
return m_impl->subMeshes[index];
|
||||
}
|
||||
|
||||
const SubMesh* Mesh::GetSubMesh(const String& identifier) const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
auto it = m_impl->subMeshMap.find(identifier);
|
||||
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (it == m_impl->subMeshMap.end())
|
||||
{
|
||||
NazaraError("SubMesh not found");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(it != m_impl->subMeshMap.end(), "SubMesh " + identifier + " not found");
|
||||
|
||||
return m_impl->subMeshes[it->second];
|
||||
}
|
||||
|
||||
const SubMesh* Mesh::GetSubMesh(unsigned int index) const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (index >= m_impl->subMeshes.size())
|
||||
{
|
||||
NazaraError("SubMesh index out of range (" + String::Number(index) + " >= " + String::Number(m_impl->subMeshes.size()) + ')');
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(index < m_impl->subMeshes.size(), "Submesh index out of range");
|
||||
|
||||
return m_impl->subMeshes[index];
|
||||
}
|
||||
|
||||
unsigned int Mesh::GetSubMeshCount() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return m_impl->subMeshes.size();
|
||||
}
|
||||
|
||||
int Mesh::GetSubMeshIndex(const String& identifier) const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
auto it = m_impl->subMeshMap.find(identifier);
|
||||
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (it == m_impl->subMeshMap.end())
|
||||
{
|
||||
NazaraError("SubMesh not found");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(it != m_impl->subMeshMap.end(), "SubMesh " + identifier + " not found");
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
unsigned int Mesh::GetTriangleCount() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
unsigned int triangleCount = 0;
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
@@ -713,13 +492,7 @@ namespace Nz
|
||||
|
||||
unsigned int Mesh::GetVertexCount() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
unsigned int vertexCount = 0;
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
@@ -730,52 +503,28 @@ namespace Nz
|
||||
|
||||
void Mesh::InvalidateAABB() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
m_impl->aabbUpdated = false;
|
||||
}
|
||||
|
||||
bool Mesh::HasSubMesh(const String& identifier) const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return m_impl->subMeshMap.find(identifier) != m_impl->subMeshMap.end();
|
||||
}
|
||||
|
||||
bool Mesh::HasSubMesh(unsigned int index) const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return index < m_impl->subMeshes.size();
|
||||
}
|
||||
|
||||
bool Mesh::IsAnimable() const
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
return m_impl->animationType != AnimationType_Static;
|
||||
}
|
||||
@@ -802,21 +551,10 @@ namespace Nz
|
||||
|
||||
void Mesh::Recenter()
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(m_impl->animationType == AnimationType_Static, "Mesh is not static");
|
||||
|
||||
if (m_impl->animationType != AnimationType_Static)
|
||||
{
|
||||
NazaraError("Mesh must be static");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Le centre de notre mesh est le centre de l'AABB *globale*
|
||||
// The center of our mesh is the center of our *global* AABB
|
||||
Vector3f center = GetAABB().GetCenter();
|
||||
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
@@ -833,119 +571,62 @@ namespace Nz
|
||||
vertices++;
|
||||
}
|
||||
|
||||
// l'AABB ne change pas de dimensions mais seulement de position, appliquons-lui le même procédé
|
||||
// Our AABB doesn't change shape, only position
|
||||
Boxf aabb = staticMesh->GetAABB();
|
||||
aabb.Translate(-center);
|
||||
|
||||
staticMesh->SetAABB(aabb);
|
||||
}
|
||||
|
||||
// Il ne faut pas oublier d'invalider notre AABB
|
||||
m_impl->aabbUpdated = false;
|
||||
InvalidateAABB();
|
||||
}
|
||||
|
||||
void Mesh::RemoveSubMesh(const String& identifier)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = m_impl->subMeshMap.find(identifier);
|
||||
if (it == m_impl->subMeshMap.end())
|
||||
{
|
||||
NazaraError("SubMesh not found");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int index = it->second;
|
||||
#else
|
||||
unsigned int index = m_impl->subMeshMap[identifier];
|
||||
#endif
|
||||
unsigned int index = GetSubMeshIndex(identifier);
|
||||
|
||||
// On déplace l'itérateur du début d'une distance de x
|
||||
auto it2 = m_impl->subMeshes.begin();
|
||||
std::advance(it2, index);
|
||||
m_impl->subMeshes.erase(it2);
|
||||
|
||||
m_impl->aabbUpdated = false; // On invalide l'AABB
|
||||
InvalidateAABB();
|
||||
}
|
||||
|
||||
void Mesh::RemoveSubMesh(unsigned int index)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= m_impl->subMeshes.size())
|
||||
{
|
||||
NazaraError("SubMesh index out of range (" + String::Number(index) + " >= " + String::Number(m_impl->subMeshes.size()) + ')');
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(index < m_impl->subMeshes.size(), "Submesh index out of range");
|
||||
|
||||
// On déplace l'itérateur du début de x
|
||||
auto it = m_impl->subMeshes.begin();
|
||||
std::advance(it, index);
|
||||
m_impl->subMeshes.erase(it);
|
||||
|
||||
m_impl->aabbUpdated = false; // On invalide l'AABB
|
||||
InvalidateAABB();
|
||||
}
|
||||
|
||||
void Mesh::SetAnimation(const String& animationPath)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
|
||||
m_impl->animationPath = animationPath;
|
||||
}
|
||||
|
||||
void Mesh::SetMaterial(unsigned int matIndex, const String& materialPath)
|
||||
void Mesh::SetMaterialData(unsigned int matIndex, ParameterList data)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(matIndex < m_impl->materialData.size(), "Material index out of range");
|
||||
|
||||
if (matIndex >= m_impl->materials.size())
|
||||
{
|
||||
NazaraError("Material index out of range (" + String::Number(matIndex) + " >= " + String::Number(m_impl->materials.size()) + ')');
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_impl->materials[matIndex] = materialPath;
|
||||
m_impl->materialData[matIndex] = std::move(data);
|
||||
}
|
||||
|
||||
void Mesh::SetMaterialCount(unsigned int matCount)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(matCount > 0, "A mesh should have at least a material");
|
||||
|
||||
if (matCount == 0)
|
||||
{
|
||||
NazaraError("A mesh should have at least a material");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_impl->materials.resize(matCount);
|
||||
m_impl->materialData.resize(matCount);
|
||||
|
||||
#ifdef NAZARA_DEBUG
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
@@ -953,7 +634,7 @@ namespace Nz
|
||||
unsigned int matIndex = subMesh->GetMaterialIndex();
|
||||
if (matIndex >= matCount)
|
||||
{
|
||||
subMesh->SetMaterialIndex(0); // Pour empêcher un crash
|
||||
subMesh->SetMaterialIndex(0); // To prevent a crash
|
||||
NazaraWarning("SubMesh " + String::Pointer(subMesh) + " material index is over mesh new material count (" + String::Number(matIndex) + " >= " + String::Number(matCount) + "), setting it to first material");
|
||||
}
|
||||
}
|
||||
@@ -962,22 +643,8 @@ namespace Nz
|
||||
|
||||
void Mesh::Transform(const Matrix4f& matrix)
|
||||
{
|
||||
#if NAZARA_UTILITY_SAFE
|
||||
if (!m_impl)
|
||||
{
|
||||
NazaraError("Mesh not created");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_impl->animationType != AnimationType_Static)
|
||||
{
|
||||
NazaraError("Mesh must be static");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (matrix.IsIdentity())
|
||||
return;
|
||||
NazaraAssert(m_impl, "Mesh should be created first");
|
||||
NazaraAssert(m_impl->animationType == AnimationType_Static, "Mesh is not static");
|
||||
|
||||
for (SubMesh* subMesh : m_impl->subMeshes)
|
||||
{
|
||||
@@ -1000,8 +667,7 @@ namespace Nz
|
||||
staticMesh->SetAABB(aabb);
|
||||
}
|
||||
|
||||
// Il ne faut pas oublier d'invalider notre AABB
|
||||
m_impl->aabbUpdated = false;
|
||||
InvalidateAABB();
|
||||
}
|
||||
|
||||
bool Mesh::Initialize()
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <Nazara/Utility/Formats/MD2Loader.hpp>
|
||||
#include <Nazara/Utility/Formats/MD5AnimLoader.hpp>
|
||||
#include <Nazara/Utility/Formats/MD5MeshLoader.hpp>
|
||||
#include <Nazara/Utility/Formats/OBJLoader.hpp>
|
||||
#include <Nazara/Utility/Formats/PCXLoader.hpp>
|
||||
#include <Nazara/Utility/Formats/STBLoader.hpp>
|
||||
#include <Nazara/Utility/Formats/STBSaver.hpp>
|
||||
@@ -114,7 +115,7 @@ namespace Nz
|
||||
Loaders::RegisterFreeType();
|
||||
|
||||
// Image
|
||||
Loaders::RegisterDDSLoader(); // Generic loader (STB)
|
||||
Loaders::RegisterDDSLoader(); // DDS Loader (DirectX format)
|
||||
Loaders::RegisterSTBLoader(); // Generic loader (STB)
|
||||
Loaders::RegisterSTBSaver(); // Generic saver (STB)
|
||||
|
||||
@@ -122,6 +123,9 @@ namespace Nz
|
||||
// Animation
|
||||
Loaders::RegisterMD5Anim(); // Loader de fichiers .md5anim (v10)
|
||||
|
||||
// Mesh (text)
|
||||
Loaders::RegisterOBJ();
|
||||
|
||||
// Mesh
|
||||
Loaders::RegisterMD2(); // Loader de fichiers .md2 (v8)
|
||||
Loaders::RegisterMD5Mesh(); // Loader de fichiers .md5mesh (v10)
|
||||
@@ -158,6 +162,7 @@ namespace Nz
|
||||
Loaders::UnregisterMD2();
|
||||
Loaders::UnregisterMD5Anim();
|
||||
Loaders::UnregisterMD5Mesh();
|
||||
Loaders::UnregisterOBJ();
|
||||
Loaders::UnregisterPCX();
|
||||
Loaders::UnregisterSTBLoader();
|
||||
Loaders::UnregisterSTBSaver();
|
||||
|
||||
Reference in New Issue
Block a user